diff --git a/arch/arm/cpu/armv7/omap-common/Makefile b/arch/arm/cpu/armv7/omap-common/Makefile
index 464a5d1d732a7540f1cbea19ab285397c5cc781b..87a7ac03f92021f50bd9252e6d087820c1cbc6b4 100644
--- a/arch/arm/cpu/armv7/omap-common/Makefile
+++ b/arch/arm/cpu/armv7/omap-common/Makefile
@@ -6,7 +6,13 @@
 #
 
 obj-y	:= reset.o
+ifeq ($(CONFIG_TIMER),)
 obj-y	+= timer.o
+else
+ifdef CONFIG_SPL_BUILD
+obj-y	+= timer.o
+endif
+endif
 obj-y	+= utils.o
 
 ifneq ($(CONFIG_OMAP44XX)$(CONFIG_OMAP54XX),)
diff --git a/arch/arm/dts/Makefile b/arch/arm/dts/Makefile
index e4f8aaef55f46e867cfe5402c629d664649599dd..7706b41b2d11e4672e343a095eac1287111a5b34 100644
--- a/arch/arm/dts/Makefile
+++ b/arch/arm/dts/Makefile
@@ -93,7 +93,7 @@ dtb-$(CONFIG_TARGET_BEAGLE_X15) += am57xx-beagle-x15.dtb
 dtb-$(CONFIG_TARGET_STV0991) += stv0991.dtb
 
 dtb-$(CONFIG_LS102XA) += ls1021a-qds.dtb \
-	ls1021a-twr.dtb
+	ls1021a-twr-duart.dtb ls1021a-twr-lpuart.dtb
 dtb-$(CONFIG_FSL_LSCH3) += fsl-ls2080a-qds.dtb \
 	fsl-ls2080a-rdb.dtb
 dtb-$(CONFIG_FSL_LSCH2) += fsl-ls1043a-qds.dtb \
diff --git a/arch/arm/dts/am335x-boneblack.dts b/arch/arm/dts/am335x-boneblack.dts
index 679248aa0222810ae222311b312e7098d6eb7ad2..27ebe4a65df59cc1bc0b904bc9e4d5a2e773ae05 100644
--- a/arch/arm/dts/am335x-boneblack.dts
+++ b/arch/arm/dts/am335x-boneblack.dts
@@ -15,6 +15,7 @@
 	compatible = "ti,am335x-bone-black", "ti,am335x-bone", "ti,am33xx";
 	chosen {
 		stdout-path = &uart0;
+		tick-timer = &timer2;
 	};
 };
 
diff --git a/arch/arm/dts/am335x-evm.dts b/arch/arm/dts/am335x-evm.dts
index e1c5d4f76c6d66cecc8a6f3690464d39f5de65f6..c0bc2af9a526828fc7cbe0ffeb734574b875a4f7 100644
--- a/arch/arm/dts/am335x-evm.dts
+++ b/arch/arm/dts/am335x-evm.dts
@@ -16,6 +16,7 @@
 
 	chosen {
 		stdout-path = &uart0;
+		tick-timer = &timer2;
 	};
 
 	cpus {
diff --git a/arch/arm/dts/am437x-gp-evm.dts b/arch/arm/dts/am437x-gp-evm.dts
index b5f0b4ee69a7ba1d7ac7665f7546abd1dcd9ba19..8e23b9660917a868cf4ff8d043713871b6d583c0 100644
--- a/arch/arm/dts/am437x-gp-evm.dts
+++ b/arch/arm/dts/am437x-gp-evm.dts
@@ -26,6 +26,7 @@
 
 	chosen {
 		stdout-path = &uart0;
+		tick-timer = &timer2;
 	};
 
 	vmmcsd_fixed: fixedregulator-sd {
diff --git a/arch/arm/dts/am437x-sk-evm.dts b/arch/arm/dts/am437x-sk-evm.dts
index 89feaf3eb7181857bb38208e8f681be81aed8c12..260edb93ec57c7868fc05d7eb11c0a341737b839 100644
--- a/arch/arm/dts/am437x-sk-evm.dts
+++ b/arch/arm/dts/am437x-sk-evm.dts
@@ -26,6 +26,7 @@
 
 	chosen {
 		stdout-path = &uart0;
+		tick-timer = &timer2;
 	};
 
 	backlight {
diff --git a/arch/arm/dts/dra7-evm.dts b/arch/arm/dts/dra7-evm.dts
index 797d411d6f7160d6c52ad64d86c1db23b287f881..242fd535164916e0330eea6e1bd75c9d4410ae76 100644
--- a/arch/arm/dts/dra7-evm.dts
+++ b/arch/arm/dts/dra7-evm.dts
@@ -16,6 +16,7 @@
 
 	chosen {
 		stdout-path = &uart1;
+		tick-timer = &timer2;
 	};
 
 	memory {
diff --git a/arch/arm/dts/dra72-evm.dts b/arch/arm/dts/dra72-evm.dts
index a62550f0e02dba58830a420199edd5ae805be018..fc2d167deee49f4c4abaee52932eecc8a778a7f8 100644
--- a/arch/arm/dts/dra72-evm.dts
+++ b/arch/arm/dts/dra72-evm.dts
@@ -16,6 +16,7 @@
 
 	chosen {
 		stdout-path = &uart1;
+		tick-timer = &timer2;
 	};
 
 	memory {
diff --git a/arch/arm/dts/ls1021a-twr-duart.dts b/arch/arm/dts/ls1021a-twr-duart.dts
new file mode 100644
index 0000000000000000000000000000000000000000..aaf72966f9f19a11436f5585ef8cd80b05c4fd0c
--- /dev/null
+++ b/arch/arm/dts/ls1021a-twr-duart.dts
@@ -0,0 +1,16 @@
+/*
+ * Freescale ls1021a TWR board device tree source
+ *
+ * Copyright 2013-2015 Freescale Semiconductor, Inc.
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+/dts-v1/;
+#include "ls1021a-twr.dtsi"
+
+/ {
+	chosen {
+		stdout-path = &uart0;
+	};
+};
diff --git a/arch/arm/dts/ls1021a-twr-lpuart.dts b/arch/arm/dts/ls1021a-twr-lpuart.dts
new file mode 100644
index 0000000000000000000000000000000000000000..2941ec0164eb75820288f7c92c91de1e2d504e13
--- /dev/null
+++ b/arch/arm/dts/ls1021a-twr-lpuart.dts
@@ -0,0 +1,16 @@
+/*
+ * Freescale ls1021a TWR board device tree source
+ *
+ * Copyright 2013-2015 Freescale Semiconductor, Inc.
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+/dts-v1/;
+#include "ls1021a-twr.dtsi"
+
+/ {
+	chosen {
+		stdout-path = &lpuart0;
+	};
+};
diff --git a/arch/arm/dts/ls1021a-twr.dts b/arch/arm/dts/ls1021a-twr.dtsi
similarity index 93%
rename from arch/arm/dts/ls1021a-twr.dts
rename to arch/arm/dts/ls1021a-twr.dtsi
index 6ccd33279b809cdcb5e0d61f5a988544fa7e05d8..d1be9ae5c2e2c546be18a64c6373a6af7aafc072 100644
--- a/arch/arm/dts/ls1021a-twr.dts
+++ b/arch/arm/dts/ls1021a-twr.dtsi
@@ -1,12 +1,11 @@
 /*
- * Freescale ls1021a TWR board device tree source
+ * Freescale ls1021a TWR board common device tree source
  *
  * Copyright 2013-2015 Freescale Semiconductor, Inc.
  *
  * SPDX-License-Identifier:	GPL-2.0+
  */
 
-/dts-v1/;
 #include "ls1021a.dtsi"
 
 / {
@@ -19,6 +18,10 @@
 		spi0 = &qspi;
 		spi1 = &dspi1;
 	};
+
+	chosen {
+		stdout-path = &uart0;
+	};
 };
 
 &qspi {
diff --git a/arch/arm/dts/ls1021a.dtsi b/arch/arm/dts/ls1021a.dtsi
index 7fadd7ca57a7d57b24a119987051fb5860627d14..ee0e55461d2c920235df708d07a306580cca4e4c 100644
--- a/arch/arm/dts/ls1021a.dtsi
+++ b/arch/arm/dts/ls1021a.dtsi
@@ -218,7 +218,6 @@
 			compatible = "fsl,16550-FIFO64", "ns16550a";
 			reg = <0x21c0500 0x100>;
 			interrupts = <GIC_SPI 86 IRQ_TYPE_LEVEL_HIGH>;
-			clock-frequency = <0>;
 			fifo-size = <15>;
 			status = "disabled";
 		};
@@ -227,7 +226,6 @@
 			compatible = "fsl,16550-FIFO64", "ns16550a";
 			reg = <0x21c0600 0x100>;
 			interrupts = <GIC_SPI 86 IRQ_TYPE_LEVEL_HIGH>;
-			clock-frequency = <0>;
 			fifo-size = <15>;
 			status = "disabled";
 		};
@@ -236,7 +234,6 @@
 			compatible = "fsl,16550-FIFO64", "ns16550a";
 			reg = <0x21d0500 0x100>;
 			interrupts = <GIC_SPI 87 IRQ_TYPE_LEVEL_HIGH>;
-			clock-frequency = <0>;
 			fifo-size = <15>;
 			status = "disabled";
 		};
@@ -245,7 +242,6 @@
 			compatible = "fsl,16550-FIFO64", "ns16550a";
 			reg = <0x21d0600 0x100>;
 			interrupts = <GIC_SPI 87 IRQ_TYPE_LEVEL_HIGH>;
-			clock-frequency = <0>;
 			fifo-size = <15>;
 			status = "disabled";
 		};
diff --git a/arch/sandbox/dts/sandbox.dts b/arch/sandbox/dts/sandbox.dts
index d2addb4cf0ba0d8066e809ac7abbbc53569a69bf..e3f02bf13cacd6ad5f739433674337a45b5d5b58 100644
--- a/arch/sandbox/dts/sandbox.dts
+++ b/arch/sandbox/dts/sandbox.dts
@@ -117,6 +117,7 @@
 	};
 
 	lcd {
+		u-boot,dm-pre-reloc;
 		compatible = "sandbox,lcd-sdl";
 		xres = <1366>;
 		yres = <768>;
diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts
index 2e0d320b1e48d4c9b37a4d600c91d63b98afec76..9b8d658bf3bcfd37b3eb505e917d49c25e1fa8bb 100644
--- a/arch/sandbox/dts/test.dts
+++ b/arch/sandbox/dts/test.dts
@@ -195,6 +195,13 @@
 		vss-microvolts = <0>;
 	};
 
+	lcd {
+		u-boot,dm-pre-reloc;
+		compatible = "sandbox,lcd-sdl";
+		xres = <1366>;
+		yres = <768>;
+	};
+
 	leds {
 		compatible = "gpio-leds";
 
diff --git a/board/sandbox/sandbox.c b/board/sandbox/sandbox.c
index 592f7728c02d5c72f0e842b61296ea5fbcf14012..b41e9decb306b0e5d05d57f14e51ccfb43b7533f 100644
--- a/board/sandbox/sandbox.c
+++ b/board/sandbox/sandbox.c
@@ -47,23 +47,6 @@ int dram_init(void)
 	return 0;
 }
 
-#ifdef CONFIG_BOARD_EARLY_INIT_F
-int board_early_init_f(void)
-{
-#ifdef CONFIG_VIDEO_SANDBOX_SDL
-	int ret;
-
-	ret = sandbox_lcd_sdl_early_init();
-	if (ret) {
-		puts("Could not init sandbox LCD emulation\n");
-		return ret;
-	}
-#endif
-
-	return 0;
-}
-#endif
-
 #ifdef CONFIG_BOARD_LATE_INIT
 int board_late_init(void)
 {
diff --git a/common/Makefile b/common/Makefile
index 2a1d9f8331894b11d0c4797b216b8621ac869ccb..249227597af6f0bb3254c7605a5bc8ce45e37aa3 100644
--- a/common/Makefile
+++ b/common/Makefile
@@ -205,7 +205,9 @@ obj-$(CONFIG_I2C_EDID) += edid.o
 obj-$(CONFIG_KALLSYMS) += kallsyms.o
 obj-y += splash.o
 obj-$(CONFIG_SPLASH_SOURCE) += splash_source.o
+ifndef CONFIG_DM_VIDEO
 obj-$(CONFIG_LCD) += lcd.o lcd_console.o
+endif
 obj-$(CONFIG_LCD_ROTATION) += lcd_console_rotation.o
 obj-$(CONFIG_LCD_DT_SIMPLEFB) += lcd_simplefb.o
 obj-$(CONFIG_LYNXKDI) += lynxkdi.o
diff --git a/common/board_f.c b/common/board_f.c
index 8094ac4efe0d6feb120433e021f0cd2e723c85dd..c470b5921ad6b5a6453268a8f36c0b84895039f4 100644
--- a/common/board_f.c
+++ b/common/board_f.c
@@ -46,6 +46,7 @@
 #include <spi.h>
 #include <status_led.h>
 #include <trace.h>
+#include <video.h>
 #include <watchdog.h>
 #include <asm/errno.h>
 #include <asm/io.h>
@@ -437,36 +438,41 @@ static int reserve_mmu(void)
 }
 #endif
 
-#ifdef CONFIG_LCD
+#ifdef CONFIG_DM_VIDEO
+static int reserve_video(void)
+{
+	ulong addr;
+	int ret;
+
+	addr = gd->relocaddr;
+	ret = video_reserve(&addr);
+	if (ret)
+		return ret;
+	gd->relocaddr = addr;
+
+	return 0;
+}
+#else
+
+# ifdef CONFIG_LCD
 static int reserve_lcd(void)
 {
-#ifdef CONFIG_FB_ADDR
+#  ifdef CONFIG_FB_ADDR
 	gd->fb_base = CONFIG_FB_ADDR;
-#else
+#  else
 	/* reserve memory for LCD display (always full pages) */
 	gd->relocaddr = lcd_setmem(gd->relocaddr);
 	gd->fb_base = gd->relocaddr;
-#endif /* CONFIG_FB_ADDR */
-	return 0;
-}
-#endif /* CONFIG_LCD */
-
-static int reserve_trace(void)
-{
-#ifdef CONFIG_TRACE
-	gd->relocaddr -= CONFIG_TRACE_BUFFER_SIZE;
-	gd->trace_buff = map_sysmem(gd->relocaddr, CONFIG_TRACE_BUFFER_SIZE);
-	debug("Reserving %dk for trace data at: %08lx\n",
-	      CONFIG_TRACE_BUFFER_SIZE >> 10, gd->relocaddr);
-#endif
+#  endif /* CONFIG_FB_ADDR */
 
 	return 0;
 }
+# endif /* CONFIG_LCD */
 
-#if defined(CONFIG_VIDEO) && (!defined(CONFIG_PPC) || defined(CONFIG_8xx)) && \
+# if defined(CONFIG_VIDEO) && (!defined(CONFIG_PPC) || defined(CONFIG_8xx)) && \
 		!defined(CONFIG_ARM) && !defined(CONFIG_X86) && \
 		!defined(CONFIG_BLACKFIN) && !defined(CONFIG_M68K)
-static int reserve_video(void)
+static int reserve_legacy_video(void)
 {
 	/* reserve memory for video display (always full pages) */
 	gd->relocaddr = video_setmem(gd->relocaddr);
@@ -474,8 +480,21 @@ static int reserve_video(void)
 
 	return 0;
 }
+# endif
+#endif /* !CONFIG_DM_VIDEO */
+
+static int reserve_trace(void)
+{
+#ifdef CONFIG_TRACE
+	gd->relocaddr -= CONFIG_TRACE_BUFFER_SIZE;
+	gd->trace_buff = map_sysmem(gd->relocaddr, CONFIG_TRACE_BUFFER_SIZE);
+	debug("Reserving %dk for trace data at: %08lx\n",
+	      CONFIG_TRACE_BUFFER_SIZE >> 10, gd->relocaddr);
 #endif
 
+	return 0;
+}
+
 static int reserve_uboot(void)
 {
 	/*
@@ -957,16 +976,20 @@ static init_fnc_t init_sequence_f[] = {
 		defined(CONFIG_ARM)
 	reserve_mmu,
 #endif
-#ifdef CONFIG_LCD
+#ifdef CONFIG_DM_VIDEO
+	reserve_video,
+#else
+# ifdef CONFIG_LCD
 	reserve_lcd,
-#endif
-	reserve_trace,
+# endif
 	/* TODO: Why the dependency on CONFIG_8xx? */
-#if defined(CONFIG_VIDEO) && (!defined(CONFIG_PPC) || defined(CONFIG_8xx)) && \
+# if defined(CONFIG_VIDEO) && (!defined(CONFIG_PPC) || defined(CONFIG_8xx)) && \
 		!defined(CONFIG_ARM) && !defined(CONFIG_X86) && \
 		!defined(CONFIG_BLACKFIN) && !defined(CONFIG_M68K)
-	reserve_video,
-#endif
+	reserve_legacy_video,
+# endif
+#endif /* CONFIG_DM_VIDEO */
+	reserve_trace,
 #if !defined(CONFIG_BLACKFIN)
 	reserve_uboot,
 #endif
diff --git a/common/cmd_bmp.c b/common/cmd_bmp.c
index cb1f07119b4f6c7963590c394a779f4339a2934e..fd5b7db28852a7dcf225b066744e4591658dcb28 100644
--- a/common/cmd_bmp.c
+++ b/common/cmd_bmp.c
@@ -10,11 +10,14 @@
  */
 
 #include <common.h>
+#include <dm.h>
 #include <lcd.h>
+#include <mapmem.h>
 #include <bmp_layout.h>
 #include <command.h>
 #include <asm/byteorder.h>
 #include <malloc.h>
+#include <mapmem.h>
 #include <splash.h>
 #include <video.h>
 
@@ -57,7 +60,8 @@ struct bmp_image *gunzip_bmp(unsigned long addr, unsigned long *lenp,
 	/* align to 32-bit-aligned-address + 2 */
 	bmp = (struct bmp_image *)((((unsigned int)dst + 1) & ~3) + 2);
 
-	if (gunzip(bmp, CONFIG_SYS_VIDEO_LOGO_MAX_SIZE, (uchar *)addr, &len) != 0) {
+	if (gunzip(bmp, CONFIG_SYS_VIDEO_LOGO_MAX_SIZE, map_sysmem(addr, 0),
+		   &len) != 0) {
 		free(dst);
 		return NULL;
 	}
@@ -187,7 +191,7 @@ U_BOOT_CMD(
  */
 static int bmp_info(ulong addr)
 {
-	struct bmp_image *bmp = (struct bmp_image *)addr;
+	struct bmp_image *bmp = (struct bmp_image *)map_sysmem(addr, 0);
 	void *bmp_alloc_addr = NULL;
 	unsigned long len;
 
@@ -223,8 +227,11 @@ static int bmp_info(ulong addr)
  */
 int bmp_display(ulong addr, int x, int y)
 {
+#ifdef CONFIG_DM_VIDEO
+	struct udevice *dev;
+#endif
 	int ret;
-	struct bmp_image *bmp = (struct bmp_image *)addr;
+	struct bmp_image *bmp = map_sysmem(addr, 0);
 	void *bmp_alloc_addr = NULL;
 	unsigned long len;
 
@@ -236,11 +243,27 @@ int bmp_display(ulong addr, int x, int y)
 		printf("There is no valid bmp file at the given address\n");
 		return 1;
 	}
-
-#if defined(CONFIG_LCD)
-	ret = lcd_display_bitmap((ulong)bmp, x, y);
+	addr = map_to_sysmem(bmp);
+
+#ifdef CONFIG_DM_VIDEO
+	ret = uclass_first_device(UCLASS_VIDEO, &dev);
+	if (!ret) {
+		if (!dev)
+			ret = -ENODEV;
+		if (!ret) {
+			bool align = false;
+
+# ifdef CONFIG_SPLASH_SCREEN_ALIGN
+			align = true;
+# endif /* CONFIG_SPLASH_SCREEN_ALIGN */
+			ret = video_bmp_display(dev, addr, x, y, align);
+		}
+	}
+	return ret ? CMD_RET_FAILURE : 0;
+#elif defined(CONFIG_LCD)
+	ret = lcd_display_bitmap(addr, x, y);
 #elif defined(CONFIG_VIDEO)
-	ret = video_display_bitmap((unsigned long)bmp, x, y);
+	ret = video_display_bitmap(addr, x, y);
 #else
 # error bmp_display() requires CONFIG_LCD or CONFIG_VIDEO
 #endif
diff --git a/common/cmd_i2c.c b/common/cmd_i2c.c
index 552c875f62ddba9a083d686fca9d84b44fbec26c..b3bb64408fe18af0c2a754ed653384bd55dee880 100644
--- a/common/cmd_i2c.c
+++ b/common/cmd_i2c.c
@@ -1809,7 +1809,8 @@ static int do_i2c_bus_num(cmd_tbl_t *cmdtp, int flag, int argc,
 		if (ret)
 			printf("Failure changing bus number (%d)\n", ret);
 	}
-	return ret;
+
+	return ret ? CMD_RET_FAILURE : 0;
 }
 #endif  /* defined(CONFIG_SYS_I2C) */
 
@@ -1852,7 +1853,8 @@ static int do_i2c_bus_speed(cmd_tbl_t * cmdtp, int flag, int argc, char * const
 		if (ret)
 			printf("Failure changing bus speed (%d)\n", ret);
 	}
-	return ret;
+
+	return ret ? CMD_RET_FAILURE : 0;
 }
 
 /**
diff --git a/common/fdt_support.c b/common/fdt_support.c
index 09f923716ca1888d8b4e00bf44a2d1af7a594681..75d0858e76317e71fb1f8839e5e8f8f0b1499653 100644
--- a/common/fdt_support.c
+++ b/common/fdt_support.c
@@ -131,18 +131,6 @@ static int fdt_fixup_stdout(void *fdt, int chosenoff)
 			      OF_STDOUT_PATH, strlen(OF_STDOUT_PATH) + 1);
 }
 #elif defined(CONFIG_OF_STDOUT_VIA_ALIAS) && defined(CONFIG_CONS_INDEX)
-static void fdt_fill_multisername(char *sername, size_t maxlen)
-{
-	const char *outname = stdio_devices[stdout]->name;
-
-	if (strcmp(outname, "serial") > 0)
-		strncpy(sername, outname, maxlen);
-
-	/* eserial? */
-	if (strcmp(outname + 1, "serial") > 0)
-		strncpy(sername, outname + 1, maxlen);
-}
-
 static int fdt_fixup_stdout(void *fdt, int chosenoff)
 {
 	int err;
@@ -152,9 +140,7 @@ static int fdt_fixup_stdout(void *fdt, int chosenoff)
 	int len;
 	char tmp[256]; /* long enough */
 
-	fdt_fill_multisername(sername, sizeof(sername) - 1);
-	if (!sername[0])
-		sprintf(sername, "serial%d", CONFIG_CONS_INDEX - 1);
+	sprintf(sername, "serial%d", CONFIG_CONS_INDEX - 1);
 
 	aliasoff = fdt_path_offset(fdt, "/aliases");
 	if (aliasoff < 0) {
diff --git a/common/lcd.c b/common/lcd.c
index d29308aeb6a8ab32d5c5f73c91af37f287333e20..2f3594a4176cb673bc9bfa095a58b6c22098d059 100644
--- a/common/lcd.c
+++ b/common/lcd.c
@@ -31,10 +31,6 @@
 #endif
 #endif
 
-#ifdef CONFIG_SANDBOX
-#include <asm/sdl.h>
-#endif
-
 #ifndef CONFIG_LCD_ALIGNMENT
 #define CONFIG_LCD_ALIGNMENT PAGE_SIZE
 #endif
@@ -72,13 +68,6 @@ void lcd_sync(void)
 	if (lcd_flush_dcache)
 		flush_dcache_range((u32)lcd_base,
 			(u32)(lcd_base + lcd_get_size(&line_length)));
-#elif defined(CONFIG_SANDBOX) && defined(CONFIG_VIDEO_SANDBOX_SDL)
-	static ulong last_sync;
-
-	if (get_timer(last_sync) > 10) {
-		sandbox_sdl_sync(lcd_base);
-		last_sync = get_timer(0);
-	}
 #endif
 }
 
diff --git a/common/stdio.c b/common/stdio.c
index 8311ac768c90d0b0fc0b072215cfa5bd36070e2f..7252bab1f6c13283e80f2a05df59cf276fbb19b1 100644
--- a/common/stdio.c
+++ b/common/stdio.c
@@ -281,12 +281,23 @@ int stdio_add_devices(void)
 	i2c_init (CONFIG_SYS_I2C_SPEED, CONFIG_SYS_I2C_SLAVE);
 #endif
 #endif
-#ifdef CONFIG_LCD
+#ifdef CONFIG_DM_VIDEO
+	struct udevice *vdev;
+
+	for (ret = uclass_first_device(UCLASS_VIDEO, &vdev);
+	     vdev;
+	     ret = uclass_next_device(&vdev))
+		;
+	if (ret)
+		printf("%s: Video device failed (ret=%d)\n", __func__, ret);
+#else
+# if defined(CONFIG_LCD)
 	drv_lcd_init ();
-#endif
-#if defined(CONFIG_VIDEO) || defined(CONFIG_CFB_CONSOLE)
+# endif
+# if defined(CONFIG_VIDEO) || defined(CONFIG_CFB_CONSOLE)
 	drv_video_init ();
-#endif
+# endif
+#endif /* CONFIG_DM_VIDEO */
 #if defined(CONFIG_KEYBOARD) && !defined(CONFIG_DM_KEYBOARD)
 	drv_keyboard_init ();
 #endif
diff --git a/common/usb_storage.c b/common/usb_storage.c
index 4fdb55f9faf9061f730abb02710094aed2f5f547..8737cf7ceaabbb673b094ff8237e4c9c3fb9d78b 100644
--- a/common/usb_storage.c
+++ b/common/usb_storage.c
@@ -65,7 +65,6 @@ static const unsigned char us_direction[256/8] = {
 static ccb usb_ccb __attribute__((aligned(ARCH_DMA_MINALIGN)));
 static __u32 CBWTag;
 
-#define USB_MAX_STOR_DEV 7
 static int usb_max_devs; /* number of highest available usb device */
 
 static block_dev_desc_t usb_dev_desc[USB_MAX_STOR_DEV];
diff --git a/configs/am335x_boneblack_vboot_defconfig b/configs/am335x_boneblack_vboot_defconfig
index 5dcb942f3900f6db65727f638da6060b20ef1313..060aa1cd98eccb2ddf37c0c7aaa32e34cc873ff0 100644
--- a/configs/am335x_boneblack_vboot_defconfig
+++ b/configs/am335x_boneblack_vboot_defconfig
@@ -22,3 +22,5 @@ CONFIG_SPI_FLASH=y
 CONFIG_SPI_FLASH_WINBOND=y
 CONFIG_DM_ETH=y
 CONFIG_SYS_NS16550=y
+CONFIG_TIMER=y
+CONFIG_OMAP_TIMER=y
diff --git a/configs/am335x_gp_evm_defconfig b/configs/am335x_gp_evm_defconfig
index 74d9ffb677bac79c3be783075dcd7171e58f25d2..49461e2cdad99d0aabc0e0bf16a060e1ac3528c3 100644
--- a/configs/am335x_gp_evm_defconfig
+++ b/configs/am335x_gp_evm_defconfig
@@ -16,3 +16,5 @@ CONFIG_SPI_FLASH_WINBOND=y
 CONFIG_DM_ETH=y
 CONFIG_SYS_NS16550=y
 CONFIG_RSA=y
+CONFIG_TIMER=y
+CONFIG_OMAP_TIMER=y
diff --git a/configs/am437x_gp_evm_defconfig b/configs/am437x_gp_evm_defconfig
index 7155c98f8f9421ff46580394fb82a1ff1c617357..1d79ba19ebd7e6874b0442b52a646db53a387282 100644
--- a/configs/am437x_gp_evm_defconfig
+++ b/configs/am437x_gp_evm_defconfig
@@ -18,3 +18,5 @@ CONFIG_SPI_FLASH=y
 CONFIG_SPI_FLASH_MACRONIX=y
 CONFIG_SYS_NS16550=y
 CONFIG_TI_QSPI=y
+CONFIG_TIMER=y
+CONFIG_OMAP_TIMER=y
diff --git a/configs/am437x_sk_evm_defconfig b/configs/am437x_sk_evm_defconfig
index 8f78eeb8b91080e5602ddf8084972e038bdf3c8e..9eb41f9322233f549aa0ddd15331df02af7f5446 100644
--- a/configs/am437x_sk_evm_defconfig
+++ b/configs/am437x_sk_evm_defconfig
@@ -21,3 +21,5 @@ CONFIG_TI_QSPI=y
 CONFIG_DM_SPI=y
 CONFIG_DM_SPI_FLASH=y
 CONFIG_SPI_FLASH_BAR=y
+CONFIG_TIMER=y
+CONFIG_OMAP_TIMER=y
diff --git a/configs/colibri_vf_defconfig b/configs/colibri_vf_defconfig
index f8441e351b85d575e6696c4f7cab22e93bdfb5f9..45917c837fa362d90a2b465fa986bb4d35f7ad11 100644
--- a/configs/colibri_vf_defconfig
+++ b/configs/colibri_vf_defconfig
@@ -8,3 +8,4 @@ CONFIG_CMD_GPIO=y
 CONFIG_DM=y
 CONFIG_NAND_VF610_NFC=y
 CONFIG_SYS_NAND_VF610_NFC_60_ECC_BYTES=y
+CONFIG_FSL_LPUART=y
diff --git a/configs/colibri_vf_dtb_defconfig b/configs/colibri_vf_dtb_defconfig
index 3596cecb7d683d4aa0bade5281f356597dea5e9e..b1a843a7878f47621d7607ce11948ea2ebb1a8e0 100644
--- a/configs/colibri_vf_dtb_defconfig
+++ b/configs/colibri_vf_dtb_defconfig
@@ -11,3 +11,4 @@ CONFIG_OF_CONTROL=y
 CONFIG_DM=y
 CONFIG_NAND_VF610_NFC=y
 CONFIG_SYS_NAND_VF610_NFC_60_ECC_BYTES=y
+CONFIG_FSL_LPUART=y
diff --git a/configs/dra72_evm_defconfig b/configs/dra72_evm_defconfig
index b57ecca6968076b75bcdf80592a79ac50020b325..32d1dc1d2b4a2fcc41aab2921e4517e03dd06ab4 100644
--- a/configs/dra72_evm_defconfig
+++ b/configs/dra72_evm_defconfig
@@ -22,3 +22,5 @@ CONFIG_SYS_NS16550=y
 CONFIG_TI_QSPI=y
 CONFIG_DM_SPI=y
 CONFIG_DM_SPI_FLASH=y
+CONFIG_TIMER=y
+CONFIG_OMAP_TIMER=y
diff --git a/configs/dra74_evm_defconfig b/configs/dra74_evm_defconfig
index 6e5a7051a430eebea2b1cdecdaf78508b096604e..99462619095f4f82e9c84017bcbbb26ef2de8418 100644
--- a/configs/dra74_evm_defconfig
+++ b/configs/dra74_evm_defconfig
@@ -21,3 +21,5 @@ CONFIG_SYS_NS16550=y
 CONFIG_TI_QSPI=y
 CONFIG_DM_SPI=y
 CONFIG_DM_SPI_FLASH=y
+CONFIG_TIMER=y
+CONFIG_OMAP_TIMER=y
diff --git a/configs/ls1021aqds_ddr4_nor_lpuart_defconfig b/configs/ls1021aqds_ddr4_nor_lpuart_defconfig
index 68bd117b1ce7ff29dec919361644b5aa312df1b3..44b2a0d1e5cf81a6c2ae82196429607539596931 100644
--- a/configs/ls1021aqds_ddr4_nor_lpuart_defconfig
+++ b/configs/ls1021aqds_ddr4_nor_lpuart_defconfig
@@ -4,3 +4,4 @@ CONFIG_SYS_EXTRA_OPTIONS="SYS_FSL_DDR4,LPUART"
 # CONFIG_CMD_SETEXPR is not set
 CONFIG_NETDEVICES=y
 CONFIG_E1000=y
+CONFIG_FSL_LPUART=y
diff --git a/configs/ls1021aqds_nor_lpuart_defconfig b/configs/ls1021aqds_nor_lpuart_defconfig
index b2f6832303ab0e6dbf29612f3d37d195bbb4b353..1186af2a14842a6fe49edcfc297f2abbcaa172f9 100644
--- a/configs/ls1021aqds_nor_lpuart_defconfig
+++ b/configs/ls1021aqds_nor_lpuart_defconfig
@@ -4,3 +4,4 @@ CONFIG_SYS_EXTRA_OPTIONS="LPUART"
 # CONFIG_CMD_SETEXPR is not set
 CONFIG_NETDEVICES=y
 CONFIG_E1000=y
+CONFIG_FSL_LPUART=y
diff --git a/configs/ls1021atwr_nor_defconfig b/configs/ls1021atwr_nor_defconfig
index aa874fdcfd166753146e2482048dfbfbb11b065a..b7b3a8df09a6f99bdc04ed60fe1db6c99ecb2f13 100644
--- a/configs/ls1021atwr_nor_defconfig
+++ b/configs/ls1021atwr_nor_defconfig
@@ -1,6 +1,10 @@
 CONFIG_ARM=y
 CONFIG_TARGET_LS1021ATWR=y
+CONFIG_DM_SERIAL=y
+CONFIG_DEFAULT_DEVICE_TREE="ls1021a-twr-duart"
 # CONFIG_CMD_SETEXPR is not set
+CONFIG_OF_CONTROL=y
+CONFIG_DM=y
 CONFIG_NETDEVICES=y
 CONFIG_E1000=y
 CONFIG_SYS_NS16550=y
diff --git a/configs/ls1021atwr_nor_lpuart_defconfig b/configs/ls1021atwr_nor_lpuart_defconfig
index d7afca9bc51b5fc2cb20796d84218879e7dede94..599342f1e4feba53202bf4d564035056ebf8b68d 100644
--- a/configs/ls1021atwr_nor_lpuart_defconfig
+++ b/configs/ls1021atwr_nor_lpuart_defconfig
@@ -1,6 +1,11 @@
 CONFIG_ARM=y
 CONFIG_TARGET_LS1021ATWR=y
+CONFIG_DM_SERIAL=y
+CONFIG_DEFAULT_DEVICE_TREE="ls1021a-twr-lpuart"
 CONFIG_SYS_EXTRA_OPTIONS="LPUART"
 # CONFIG_CMD_SETEXPR is not set
+CONFIG_OF_CONTROL=y
+CONFIG_DM=y
 CONFIG_NETDEVICES=y
 CONFIG_E1000=y
+CONFIG_FSL_LPUART=y
diff --git a/configs/ls1021atwr_qspi_defconfig b/configs/ls1021atwr_qspi_defconfig
index 0c71df64aabe11ac6423009a9c5af0c04ba67e91..d7c7e4cc9aa9ef6e9cbc2e919a5d2f54c8bb8a8a 100644
--- a/configs/ls1021atwr_qspi_defconfig
+++ b/configs/ls1021atwr_qspi_defconfig
@@ -1,7 +1,7 @@
 CONFIG_ARM=y
 CONFIG_TARGET_LS1021ATWR=y
 CONFIG_DM_SPI=y
-CONFIG_DEFAULT_DEVICE_TREE="ls1021a-twr"
+CONFIG_DEFAULT_DEVICE_TREE="ls1021a-twr-duart"
 CONFIG_SYS_EXTRA_OPTIONS="QSPI_BOOT"
 # CONFIG_CMD_IMLS is not set
 # CONFIG_CMD_SETEXPR is not set
diff --git a/configs/ls1021atwr_sdcard_qspi_defconfig b/configs/ls1021atwr_sdcard_qspi_defconfig
index 2b4ebd9510fd78b4e266de8f6e46449e41ab5ae4..453a3bb7a28043d45630fe66fb5f1f78b9aa1676 100644
--- a/configs/ls1021atwr_sdcard_qspi_defconfig
+++ b/configs/ls1021atwr_sdcard_qspi_defconfig
@@ -1,7 +1,7 @@
 CONFIG_ARM=y
 CONFIG_TARGET_LS1021ATWR=y
 CONFIG_DM_SPI=y
-CONFIG_DEFAULT_DEVICE_TREE="ls1021a-twr"
+CONFIG_DEFAULT_DEVICE_TREE="ls1021a-twr-duart"
 CONFIG_SPL=y
 CONFIG_SYS_EXTRA_OPTIONS="RAMBOOT_PBL,SPL_FSL_PBL,SD_BOOT,SD_BOOT_QSPI"
 CONFIG_OF_CONTROL=y
diff --git a/configs/pcm052_defconfig b/configs/pcm052_defconfig
index 912564583276a13475a4608e637ea8e0af4f005e..26ab733148e03e731cb63f6552764033353c9a12 100644
--- a/configs/pcm052_defconfig
+++ b/configs/pcm052_defconfig
@@ -3,3 +3,4 @@ CONFIG_TARGET_PCM052=y
 CONFIG_SYS_EXTRA_OPTIONS="IMX_CONFIG=board/phytec/pcm052/imximage.cfg,ENV_IS_IN_NAND"
 CONFIG_NAND_VF610_NFC=y
 CONFIG_SYS_NAND_BUSWIDTH_16BIT=y
+CONFIG_FSL_LPUART=y
diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig
index caa7336280a1d45ee507047915159423734004a6..09ced0184a7a20a833f352e27c773091981f0a76 100644
--- a/configs/sandbox_defconfig
+++ b/configs/sandbox_defconfig
@@ -1,6 +1,7 @@
 CONFIG_SYS_MALLOC_F_LEN=0x2000
 CONFIG_PCI=y
 CONFIG_DEFAULT_DEVICE_TREE="sandbox"
+CONFIG_DM_PCI_COMPAT=y
 CONFIG_FIT=y
 CONFIG_FIT_VERBOSE=y
 CONFIG_FIT_SIGNATURE=y
@@ -51,7 +52,6 @@ CONFIG_SPI_FLASH_SST=y
 CONFIG_SPI_FLASH_WINBOND=y
 CONFIG_DM_ETH=y
 CONFIG_DM_PCI=y
-CONFIG_DM_PCI_COMPAT=y
 CONFIG_PCI_SANDBOX=y
 CONFIG_PINCTRL=y
 CONFIG_PINCONF=y
@@ -76,6 +76,9 @@ CONFIG_USB_EMUL=y
 CONFIG_USB_STORAGE=y
 CONFIG_USB_KEYBOARD=y
 CONFIG_SYS_USB_EVENT_POLL=y
+CONFIG_DM_VIDEO=y
+CONFIG_VIDEO_ROTATION=y
+CONFIG_VIDEO_SANDBOX_SDL=y
 CONFIG_CMD_DHRYSTONE=y
 CONFIG_TPM=y
 CONFIG_LZ4=y
diff --git a/configs/vf610twr_defconfig b/configs/vf610twr_defconfig
index dc8df5c9975d8103d077d15781c2ec0472622ac3..d51c93b4778e75616c76ba9430f69de2b3cf7dd1 100644
--- a/configs/vf610twr_defconfig
+++ b/configs/vf610twr_defconfig
@@ -6,3 +6,4 @@ CONFIG_SYS_EXTRA_OPTIONS="IMX_CONFIG=board/freescale/vf610twr/imximage.cfg,ENV_I
 CONFIG_NAND_VF610_NFC=y
 CONFIG_SYS_NAND_BUSWIDTH_16BIT=y
 CONFIG_SPI_FLASH=y
+CONFIG_FSL_LPUART=y
diff --git a/configs/vf610twr_nand_defconfig b/configs/vf610twr_nand_defconfig
index 98880f3a8b706592b88753fac82763d5d6296fb7..299fa8f916be7008360819c530152f53bf9d9d04 100644
--- a/configs/vf610twr_nand_defconfig
+++ b/configs/vf610twr_nand_defconfig
@@ -6,3 +6,4 @@ CONFIG_SYS_EXTRA_OPTIONS="IMX_CONFIG=board/freescale/vf610twr/imximage.cfg,ENV_I
 CONFIG_NAND_VF610_NFC=y
 CONFIG_SYS_NAND_BUSWIDTH_16BIT=y
 CONFIG_SPI_FLASH=y
+CONFIG_FSL_LPUART=y
diff --git a/doc/device-tree-bindings/chosen.txt b/doc/device-tree-bindings/chosen.txt
new file mode 100644
index 0000000000000000000000000000000000000000..bf9a30a8f97cdb0211dd4eb2a6189d3d460ac7dd
--- /dev/null
+++ b/doc/device-tree-bindings/chosen.txt
@@ -0,0 +1,43 @@
+The chosen node
+---------------
+The chosen node does not represent a real device, but serves as a place
+for passing data like which serial device to used to print the logs etc
+
+
+stdout-path property
+--------------------
+Device trees may specify the device to be used for boot console output
+with a stdout-path property under /chosen.
+
+Example
+-------
+/ {
+	chosen {
+		stdout-path = "/serial@f00:115200";
+	};
+
+	serial@f00 {
+		compatible = "vendor,some-uart";
+		reg = <0xf00 0x10>;
+	};
+};
+
+tick-timer property
+-------------------
+In a system there are multiple timers, specify which timer to be used
+as the tick-timer. Earlier it was hardcoded in the timer driver now
+since device tree has all the timer nodes. Specify which timer to be
+used as tick timer.
+
+Example
+-------
+/ {
+	chosen {
+		tick-timer = "/timer2@f00";
+	};
+
+	timer2@f00 {
+		compatible = "vendor,some-timer";
+		reg = <0xf00 0x10>;
+	};
+};
diff --git a/doc/driver-model/serial-howto.txt b/doc/driver-model/serial-howto.txt
index 4706d56ea71aef6f20a599c1aace6d1b9d9a691d..c933b9081be1aba9079dee8f916a2696d219286b 100644
--- a/doc/driver-model/serial-howto.txt
+++ b/doc/driver-model/serial-howto.txt
@@ -11,7 +11,6 @@ is time for maintainers to start converting over the remaining serial drivers:
    opencores_yanu.c
    serial_bfin.c
    serial_imx.c
-   serial_lpuart.c
    serial_max3100.c
    serial_pxa.c
    serial_s3c24x0.c
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index 890f22f48e3474a5da5d033c7edab0504e5ea86e..9fcde39b71f71870ae36e57c1707547e5275641c 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -1,3 +1,5 @@
+menu "Clock"
+
 config CLK
 	bool "Enable clock driver support"
 	depends on DM
@@ -17,3 +19,5 @@ config SPL_CLK
 	  SPL, enable this option. It might provide a cleaner interface to
 	  setting up clocks within SPL, and allows the same drivers to be
 	  used as U-Boot proper.
+
+endmenu
diff --git a/drivers/clk/clk-uclass.c b/drivers/clk/clk-uclass.c
index 73dfd7d016052c04bc273cc5e134e829852ba940..19f6f07c6f43ead3c12813afe6bb6d3a15b7073b 100644
--- a/drivers/clk/clk-uclass.c
+++ b/drivers/clk/clk-uclass.c
@@ -32,6 +32,16 @@ ulong clk_set_rate(struct udevice *dev, ulong rate)
 	return ops->set_rate(dev, rate);
 }
 
+int clk_enable(struct udevice *dev, int periph)
+{
+	struct clk_ops *ops = clk_get_ops(dev);
+
+	if (!ops->enable)
+		return -ENOSYS;
+
+	return ops->enable(dev, periph);
+}
+
 ulong clk_get_periph_rate(struct udevice *dev, int periph)
 {
 	struct clk_ops *ops = clk_get_ops(dev);
diff --git a/drivers/clk/clk_rk3036.c b/drivers/clk/clk_rk3036.c
index 6c802b6283fa0c39b9805f152398ef90ada92975..f650810250a02f960b9f2c70f133efad9f52776e 100644
--- a/drivers/clk/clk_rk3036.c
+++ b/drivers/clk/clk_rk3036.c
@@ -314,7 +314,7 @@ static ulong rk3036_clk_set_rate(struct udevice *dev, ulong rate)
 	return 0;
 }
 
-ulong rk3036_set_periph_rate(struct udevice *dev, int periph, ulong rate)
+static ulong rk3036_set_periph_rate(struct udevice *dev, int periph, ulong rate)
 {
 	struct rk3036_clk_priv *priv = dev_get_priv(dev);
 	ulong new_rate;
diff --git a/drivers/clk/clk_rk3288.c b/drivers/clk/clk_rk3288.c
index 54d49305b0c20a63bc3d4bdb9fdc1aa21e25841f..0172ad13bb7038ecf89ca40494fd30f87cb8b1dd 100644
--- a/drivers/clk/clk_rk3288.c
+++ b/drivers/clk/clk_rk3288.c
@@ -508,7 +508,7 @@ static ulong rockchip_spi_set_clk(struct rk3288_cru *cru, uint clk_general_rate,
 	return rockchip_spi_get_clk(cru, clk_general_rate, periph);
 }
 
-ulong rk3288_set_periph_rate(struct udevice *dev, int periph, ulong rate)
+static ulong rk3288_set_periph_rate(struct udevice *dev, int periph, ulong rate)
 {
 	struct rk3288_clk_priv *priv = dev_get_priv(dev);
 	ulong new_rate;
diff --git a/drivers/clk/clk_sandbox.c b/drivers/clk/clk_sandbox.c
index 058225a766dc1c83fc370e99a6c538c956e137b6..367130f8b76b12a662d41086932e165bfb185390 100644
--- a/drivers/clk/clk_sandbox.c
+++ b/drivers/clk/clk_sandbox.c
@@ -32,7 +32,7 @@ static ulong sandbox_clk_set_rate(struct udevice *dev, ulong rate)
 	return 0;
 }
 
-ulong sandbox_get_periph_rate(struct udevice *dev, int periph)
+static ulong sandbox_get_periph_rate(struct udevice *dev, int periph)
 {
 	struct sandbox_clk_priv *priv = dev_get_priv(dev);
 
@@ -41,7 +41,8 @@ ulong sandbox_get_periph_rate(struct udevice *dev, int periph)
 	return priv->periph_rate[periph];
 }
 
-ulong sandbox_set_periph_rate(struct udevice *dev, int periph, ulong rate)
+static ulong sandbox_set_periph_rate(struct udevice *dev, int periph,
+				     ulong rate)
 {
 	struct sandbox_clk_priv *priv = dev_get_priv(dev);
 	ulong old_rate;
diff --git a/drivers/core/device.c b/drivers/core/device.c
index 818d03fac15de55a331ed07c4bb6d8a8d31e25df..1e5584a7ce30a9c5c4b2189fce5c5d6563988930 100644
--- a/drivers/core/device.c
+++ b/drivers/core/device.c
@@ -135,6 +135,11 @@ int device_bind(struct udevice *parent, const struct driver *drv,
 		if (ret)
 			goto fail_child_post_bind;
 	}
+	if (uc->uc_drv->post_bind) {
+		ret = uc->uc_drv->post_bind(dev);
+		if (ret)
+			goto fail_uclass_post_bind;
+	}
 
 	if (parent)
 		dm_dbg("Bound device %s to %s\n", dev->name, parent->name);
@@ -145,6 +150,8 @@ int device_bind(struct udevice *parent, const struct driver *drv,
 
 	return 0;
 
+fail_uclass_post_bind:
+	/* There is no child unbind() method, so no clean-up required */
 fail_child_post_bind:
 	if (CONFIG_IS_ENABLED(DM_DEVICE_REMOVE)) {
 		if (drv->unbind && drv->unbind(dev)) {
diff --git a/drivers/core/uclass.c b/drivers/core/uclass.c
index 1af09472a2cb76cc82b704bb626054ad5093c1da..e1acefe7279e443075681a585e41d9c2cc7a7b61 100644
--- a/drivers/core/uclass.c
+++ b/drivers/core/uclass.c
@@ -278,6 +278,7 @@ static int uclass_find_device_by_of_offset(enum uclass_id id, int node,
 	return -ENODEV;
 }
 
+#if CONFIG_IS_ENABLED(OF_CONTROL)
 static int uclass_find_device_by_phandle(enum uclass_id id,
 					 struct udevice *parent,
 					 const char *name,
@@ -308,6 +309,7 @@ static int uclass_find_device_by_phandle(enum uclass_id id,
 
 	return -ENODEV;
 }
+#endif
 
 int uclass_get_device_tail(struct udevice *dev, int ret,
 				  struct udevice **devp)
@@ -374,6 +376,7 @@ int uclass_get_device_by_of_offset(enum uclass_id id, int node,
 	return uclass_get_device_tail(dev, ret, devp);
 }
 
+#if CONFIG_IS_ENABLED(OF_CONTROL)
 int uclass_get_device_by_phandle(enum uclass_id id, struct udevice *parent,
 				 const char *name, struct udevice **devp)
 {
@@ -384,6 +387,7 @@ int uclass_get_device_by_phandle(enum uclass_id id, struct udevice *parent,
 	ret = uclass_find_device_by_phandle(id, parent, name, &dev);
 	return uclass_get_device_tail(dev, ret, devp);
 }
+#endif
 
 int uclass_first_device(enum uclass_id id, struct udevice **devp)
 {
@@ -426,11 +430,6 @@ int uclass_bind_device(struct udevice *dev)
 				goto err;
 		}
 	}
-	if (uc->uc_drv->post_bind) {
-		ret = uc->uc_drv->post_bind(dev);
-		if (ret)
-			goto err;
-	}
 
 	return 0;
 err:
diff --git a/drivers/gpio/gpio-uclass.c b/drivers/gpio/gpio-uclass.c
index 4cce11fe215f05b2cfbc1d55b5c4e020940b1ee6..3ed4d8914c216f41dd09a2914650754d52da870a 100644
--- a/drivers/gpio/gpio-uclass.c
+++ b/drivers/gpio/gpio-uclass.c
@@ -154,6 +154,7 @@ int dm_gpio_request(struct gpio_desc *desc, const char *label)
 
 static int dm_gpio_requestf(struct gpio_desc *desc, const char *fmt, ...)
 {
+#if !defined(CONFIG_SPL_BUILD) || !defined(CONFIG_USE_TINY_PRINTF)
 	va_list args;
 	char buf[40];
 
@@ -161,6 +162,9 @@ static int dm_gpio_requestf(struct gpio_desc *desc, const char *fmt, ...)
 	vscnprintf(buf, sizeof(buf), fmt, args);
 	va_end(args);
 	return dm_gpio_request(desc, buf);
+#else
+	return dm_gpio_request(desc, fmt);
+#endif
 }
 
 /**
@@ -199,6 +203,7 @@ int gpio_request(unsigned gpio, const char *label)
  */
 int gpio_requestf(unsigned gpio, const char *fmt, ...)
 {
+#if !defined(CONFIG_SPL_BUILD) || !defined(CONFIG_USE_TINY_PRINTF)
 	va_list args;
 	char buf[40];
 
@@ -206,6 +211,9 @@ int gpio_requestf(unsigned gpio, const char *fmt, ...)
 	vscnprintf(buf, sizeof(buf), fmt, args);
 	va_end(args);
 	return gpio_request(gpio, buf);
+#else
+	return gpio_request(gpio, fmt);
+#endif
 }
 
 int _dm_gpio_free(struct udevice *dev, uint offset)
diff --git a/drivers/mmc/mmc.c b/drivers/mmc/mmc.c
index e6028d503f279ee6617b29e9657d1c8a17e39a85..ede5d6eeec73e6fa2a6b6f38a8cd150a58d400e7 100644
--- a/drivers/mmc/mmc.c
+++ b/drivers/mmc/mmc.c
@@ -1780,18 +1780,28 @@ static int mmc_probe(bd_t *bis)
 #elif defined(CONFIG_DM_MMC)
 static int mmc_probe(bd_t *bis)
 {
-	int ret;
+	int ret, i;
 	struct uclass *uc;
-	struct udevice *m;
+	struct udevice *dev;
 
 	ret = uclass_get(UCLASS_MMC, &uc);
 	if (ret)
 		return ret;
 
-	uclass_foreach_dev(m, uc) {
-		ret = device_probe(m);
+	/*
+	 * Try to add them in sequence order. Really with driver model we
+	 * should allow holes, but the current MMC list does not allow that.
+	 * So if we request 0, 1, 3 we will get 0, 1, 2.
+	 */
+	for (i = 0; ; i++) {
+		ret = uclass_get_device_by_seq(UCLASS_MMC, i, &dev);
+		if (ret == -ENODEV)
+			break;
+	}
+	uclass_foreach_dev(dev, uc) {
+		ret = device_probe(dev);
 		if (ret)
-			printf("%s - probe failed: %d\n", m->name, ret);
+			printf("%s - probe failed: %d\n", dev->name, ret);
 	}
 
 	return 0;
diff --git a/drivers/pinctrl/pinctrl-uclass.c b/drivers/pinctrl/pinctrl-uclass.c
index b5fdcd12a8251de2fc6f013a539b5ca41af94712..c42b312ddd090a0d4b9400fd6cbeb1be0ace6aec 100644
--- a/drivers/pinctrl/pinctrl-uclass.c
+++ b/drivers/pinctrl/pinctrl-uclass.c
@@ -111,12 +111,16 @@ static int pinconfig_post_bind(struct udevice *dev)
 {
 	const void *fdt = gd->fdt_blob;
 	int offset = dev->of_offset;
+	bool pre_reloc_only = !(gd->flags & GD_FLG_RELOC);
 	const char *name;
 	int ret;
 
 	for (offset = fdt_first_subnode(fdt, offset);
 	     offset > 0;
 	     offset = fdt_next_subnode(fdt, offset)) {
+		if (pre_reloc_only &&
+		    !fdt_getprop(fdt, offset, "u-boot,dm-pre-reloc", NULL))
+			continue;
 		/*
 		 * If this node has "compatible" property, this is not
 		 * a pin configuration node, but a normal device. skip.
diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig
index 92084a2908a59aa543682134ddacc364030a1176..83068cfd50f4c53aac9f1a906602be22624d4b5f 100644
--- a/drivers/serial/Kconfig
+++ b/drivers/serial/Kconfig
@@ -228,6 +228,12 @@ config ALTERA_UART
 	  Select this to enable an UART for Altera devices. Please find
 	  details on the "Embedded Peripherals IP User Guide" of Altera.
 
+config FSL_LPUART
+	bool "Freescale LPUART support"
+	help
+	  Select this to enable a Low Power UART for Freescale VF610 and
+	  QorIQ Layerscape devices.
+
 config SYS_NS16550
 	bool "NS16550 UART or compatible"
 	help
diff --git a/drivers/serial/sandbox.c b/drivers/serial/sandbox.c
index cd2f91e28e8f96b0d651cac208cbbe1c4fb12303..45dff98d9d10cea6e28bc2c7ce89723cc8ea7526 100644
--- a/drivers/serial/sandbox.c
+++ b/drivers/serial/sandbox.c
@@ -16,6 +16,7 @@
 #include <lcd.h>
 #include <os.h>
 #include <serial.h>
+#include <video.h>
 #include <linux/compiler.h>
 #include <asm/state.h>
 
@@ -114,9 +115,7 @@ static int sandbox_serial_pending(struct udevice *dev, bool input)
 		return 0;
 
 	os_usleep(100);
-#ifdef CONFIG_LCD
-	lcd_sync();
-#endif
+	video_sync_all();
 	if (next_index == serial_buf_read)
 		return 1;	/* buffer full */
 
diff --git a/drivers/serial/serial_lpuart.c b/drivers/serial/serial_lpuart.c
index 63fc388b264feef2497601d2dc010bda975e9a72..3f9c4d14eab06ce16e787149c0b0cd83a66c0bab 100644
--- a/drivers/serial/serial_lpuart.c
+++ b/drivers/serial/serial_lpuart.c
@@ -5,6 +5,7 @@
  */
 
 #include <common.h>
+#include <dm.h>
 #include <watchdog.h>
 #include <asm/io.h>
 #include <serial.h>
@@ -12,15 +13,15 @@
 #include <asm/arch/imx-regs.h>
 #include <asm/arch/clock.h>
 
-#define US1_TDRE        (1 << 7)
-#define US1_RDRF        (1 << 5)
-#define US1_OR          (1 << 3)
-#define UC2_TE          (1 << 3)
-#define UC2_RE          (1 << 2)
-#define CFIFO_TXFLUSH   (1 << 7)
-#define CFIFO_RXFLUSH   (1 << 6)
-#define SFIFO_RXOF      (1 << 2)
-#define SFIFO_RXUF      (1 << 0)
+#define US1_TDRE	(1 << 7)
+#define US1_RDRF	(1 << 5)
+#define US1_OR		(1 << 3)
+#define UC2_TE		(1 << 3)
+#define UC2_RE		(1 << 2)
+#define CFIFO_TXFLUSH	(1 << 7)
+#define CFIFO_RXFLUSH	(1 << 6)
+#define SFIFO_RXOF	(1 << 2)
+#define SFIFO_RXUF	(1 << 0)
 
 #define STAT_LBKDIF	(1 << 31)
 #define STAT_RXEDGIF	(1 << 30)
@@ -34,7 +35,7 @@
 #define STAT_MA1F	(1 << 15)
 #define STAT_MA2F	(1 << 14)
 #define STAT_FLAGS	(STAT_LBKDIF | STAT_RXEDGIF | STAT_IDLE | STAT_OR | \
-			STAT_NF | STAT_FE | STAT_PF | STAT_MA1F | STAT_MA2F)
+			 STAT_NF | STAT_FE | STAT_PF | STAT_MA1F | STAT_MA2F)
 
 #define CTRL_TE		(1 << 19)
 #define CTRL_RE		(1 << 18)
@@ -49,23 +50,24 @@ DECLARE_GLOBAL_DATA_PTR;
 
 struct lpuart_fsl *base = (struct lpuart_fsl *)LPUART_BASE;
 
+struct lpuart_serial_platdata {
+	struct lpuart_fsl *reg;
+};
+
 #ifndef CONFIG_LPUART_32B_REG
-static void lpuart_serial_setbrg(void)
+static void _lpuart_serial_setbrg(struct lpuart_fsl *base, int baudrate)
 {
 	u32 clk = mxc_get_clock(MXC_UART_CLK);
 	u16 sbr;
 
-	if (!gd->baudrate)
-		gd->baudrate = CONFIG_BAUDRATE;
+	sbr = (u16)(clk / (16 * baudrate));
 
-	sbr = (u16)(clk / (16 * gd->baudrate));
 	/* place adjustment later - n/32 BRFA */
-
 	__raw_writeb(sbr >> 8, &base->ubdh);
 	__raw_writeb(sbr & 0xff, &base->ubdl);
 }
 
-static int lpuart_serial_getc(void)
+static int _lpuart_serial_getc(struct lpuart_fsl *base)
 {
 	while (!(__raw_readb(&base->us1) & (US1_RDRF | US1_OR)))
 		WATCHDOG_RESET();
@@ -75,10 +77,10 @@ static int lpuart_serial_getc(void)
 	return __raw_readb(&base->ud);
 }
 
-static void lpuart_serial_putc(const char c)
+static void _lpuart_serial_putc(struct lpuart_fsl *base, const char c)
 {
 	if (c == '\n')
-		serial_putc('\r');
+		_lpuart_serial_putc(base, '\r');
 
 	while (!(__raw_readb(&base->us1) & US1_TDRE))
 		WATCHDOG_RESET();
@@ -86,10 +88,8 @@ static void lpuart_serial_putc(const char c)
 	__raw_writeb(c, &base->ud);
 }
 
-/*
- * Test whether a character is in the RX buffer
- */
-static int lpuart_serial_tstc(void)
+/* Test whether a character is in the RX buffer */
+static int _lpuart_serial_tstc(struct lpuart_fsl *base)
 {
 	if (__raw_readb(&base->urcfifo) == 0)
 		return 0;
@@ -101,7 +101,7 @@ static int lpuart_serial_tstc(void)
  * Initialise the serial port with the given baudrate. The settings
  * are always 8 data bits, no parity, 1 stop bit, no start bits.
  */
-static int lpuart_serial_init(void)
+static int _lpuart_serial_init(struct lpuart_fsl *base)
 {
 	u8 ctrl;
 
@@ -120,14 +120,39 @@ static int lpuart_serial_init(void)
 	__raw_writeb(CFIFO_TXFLUSH | CFIFO_RXFLUSH, &base->ucfifo);
 
 	/* provide data bits, parity, stop bit, etc */
-
-	serial_setbrg();
+	_lpuart_serial_setbrg(base, gd->baudrate);
 
 	__raw_writeb(UC2_RE | UC2_TE, &base->uc2);
 
 	return 0;
 }
 
+#ifndef CONFIG_DM_SERIAL
+static void lpuart_serial_setbrg(void)
+{
+	_lpuart_serial_setbrg(base, gd->baudrate);
+}
+
+static int lpuart_serial_getc(void)
+{
+	return _lpuart_serial_getc(base);
+}
+
+static void lpuart_serial_putc(const char c)
+{
+	_lpuart_serial_putc(base, c);
+}
+
+static int lpuart_serial_tstc(void)
+{
+	return _lpuart_serial_tstc(base);
+}
+
+static int lpuart_serial_init(void)
+{
+	return _lpuart_serial_init(base);
+}
+
 static struct serial_device lpuart_serial_drv = {
 	.name = "lpuart_serial",
 	.start = lpuart_serial_init,
@@ -138,22 +163,67 @@ static struct serial_device lpuart_serial_drv = {
 	.getc = lpuart_serial_getc,
 	.tstc = lpuart_serial_tstc,
 };
+#else /* CONFIG_DM_SERIAL */
+static int lpuart_serial_setbrg(struct udevice *dev, int baudrate)
+{
+	struct lpuart_serial_platdata *plat = dev->platdata;
+	struct lpuart_fsl *reg = plat->reg;
+
+	_lpuart_serial_setbrg(reg, baudrate);
+
+	return 0;
+}
+
+static int lpuart_serial_getc(struct udevice *dev)
+{
+	struct lpuart_serial_platdata *plat = dev->platdata;
+	struct lpuart_fsl *reg = plat->reg;
+
+	return _lpuart_serial_getc(reg);
+}
+
+static int lpuart_serial_putc(struct udevice *dev, const char c)
+{
+	struct lpuart_serial_platdata *plat = dev->platdata;
+	struct lpuart_fsl *reg = plat->reg;
+
+	_lpuart_serial_putc(reg, c);
+
+	return 0;
+}
+
+static int lpuart_serial_pending(struct udevice *dev, bool input)
+{
+	struct lpuart_serial_platdata *plat = dev->platdata;
+	struct lpuart_fsl *reg = plat->reg;
+
+	if (input)
+		return _lpuart_serial_tstc(reg);
+	else
+		return __raw_readb(&reg->us1) & US1_TDRE ? 0 : 1;
+}
+
+static int lpuart_serial_probe(struct udevice *dev)
+{
+	struct lpuart_serial_platdata *plat = dev->platdata;
+	struct lpuart_fsl *reg = plat->reg;
+
+	return _lpuart_serial_init(reg);
+}
+#endif /* CONFIG_DM_SERIAL */
 #else
-static void lpuart32_serial_setbrg(void)
+static void _lpuart32_serial_setbrg(struct lpuart_fsl *base, int baudrate)
 {
 	u32 clk = CONFIG_SYS_CLK_FREQ;
 	u32 sbr;
 
-	if (!gd->baudrate)
-		gd->baudrate = CONFIG_BAUDRATE;
+	sbr = (clk / (16 * baudrate));
 
-	sbr = (clk / (16 * gd->baudrate));
 	/* place adjustment later - n/32 BRFA */
-
 	out_be32(&base->baud, sbr);
 }
 
-static int lpuart32_serial_getc(void)
+static int _lpuart32_serial_getc(struct lpuart_fsl *base)
 {
 	u32 stat;
 
@@ -165,10 +235,10 @@ static int lpuart32_serial_getc(void)
 	return in_be32(&base->data) & 0x3ff;
 }
 
-static void lpuart32_serial_putc(const char c)
+static void _lpuart32_serial_putc(struct lpuart_fsl *base, const char c)
 {
 	if (c == '\n')
-		serial_putc('\r');
+		_lpuart32_serial_putc(base, '\r');
 
 	while (!(in_be32(&base->stat) & STAT_TDRE))
 		WATCHDOG_RESET();
@@ -176,10 +246,8 @@ static void lpuart32_serial_putc(const char c)
 	out_be32(&base->data, c);
 }
 
-/*
- * Test whether a character is in the RX buffer
- */
-static int lpuart32_serial_tstc(void)
+/* Test whether a character is in the RX buffer */
+static int _lpuart32_serial_tstc(struct lpuart_fsl *base)
 {
 	if ((in_be32(&base->water) >> 24) == 0)
 		return 0;
@@ -191,7 +259,7 @@ static int lpuart32_serial_tstc(void)
  * Initialise the serial port with the given baudrate. The settings
  * are always 8 data bits, no parity, 1 stop bit, no start bits.
  */
-static int lpuart32_serial_init(void)
+static int _lpuart32_serial_init(struct lpuart_fsl *base)
 {
 	u8 ctrl;
 
@@ -204,15 +272,41 @@ static int lpuart32_serial_init(void)
 	out_be32(&base->fifo, ~(FIFO_TXFE | FIFO_RXFE));
 
 	out_be32(&base->match, 0);
-	/* provide data bits, parity, stop bit, etc */
 
-	serial_setbrg();
+	/* provide data bits, parity, stop bit, etc */
+	_lpuart32_serial_setbrg(base, gd->baudrate);
 
 	out_be32(&base->ctrl, CTRL_RE | CTRL_TE);
 
 	return 0;
 }
 
+#ifndef CONFIG_DM_SERIAL
+static void lpuart32_serial_setbrg(void)
+{
+	_lpuart32_serial_setbrg(base, gd->baudrate);
+}
+
+static int lpuart32_serial_getc(void)
+{
+	return _lpuart32_serial_getc(base);
+}
+
+static void lpuart32_serial_putc(const char c)
+{
+	_lpuart32_serial_putc(base, c);
+}
+
+static int lpuart32_serial_tstc(void)
+{
+	return _lpuart32_serial_tstc(base);
+}
+
+static int lpuart32_serial_init(void)
+{
+	return _lpuart32_serial_init(base);
+}
+
 static struct serial_device lpuart32_serial_drv = {
 	.name = "lpuart32_serial",
 	.start = lpuart32_serial_init,
@@ -223,8 +317,57 @@ static struct serial_device lpuart32_serial_drv = {
 	.getc = lpuart32_serial_getc,
 	.tstc = lpuart32_serial_tstc,
 };
+#else /* CONFIG_DM_SERIAL */
+static int lpuart32_serial_setbrg(struct udevice *dev, int baudrate)
+{
+	struct lpuart_serial_platdata *plat = dev->platdata;
+	struct lpuart_fsl *reg = plat->reg;
+
+	_lpuart32_serial_setbrg(reg, baudrate);
+
+	return 0;
+}
+
+static int lpuart32_serial_getc(struct udevice *dev)
+{
+	struct lpuart_serial_platdata *plat = dev->platdata;
+	struct lpuart_fsl *reg = plat->reg;
+
+	return _lpuart32_serial_getc(reg);
+}
+
+static int lpuart32_serial_putc(struct udevice *dev, const char c)
+{
+	struct lpuart_serial_platdata *plat = dev->platdata;
+	struct lpuart_fsl *reg = plat->reg;
+
+	_lpuart32_serial_putc(reg, c);
+
+	return 0;
+}
+
+static int lpuart32_serial_pending(struct udevice *dev, bool input)
+{
+	struct lpuart_serial_platdata *plat = dev->platdata;
+	struct lpuart_fsl *reg = plat->reg;
+
+	if (input)
+		return _lpuart32_serial_tstc(reg);
+	else
+		return in_be32(&reg->stat) & STAT_TDRE ? 0 : 1;
+}
+
+static int lpuart32_serial_probe(struct udevice *dev)
+{
+	struct lpuart_serial_platdata *plat = dev->platdata;
+	struct lpuart_fsl *reg = plat->reg;
+
+	return _lpuart32_serial_init(reg);
+}
+#endif /* CONFIG_DM_SERIAL */
 #endif
 
+#ifndef CONFIG_DM_SERIAL
 void lpuart_serial_initialize(void)
 {
 #ifdef CONFIG_LPUART_32B_REG
@@ -242,3 +385,66 @@ __weak struct serial_device *default_serial_console(void)
 	return &lpuart_serial_drv;
 #endif
 }
+#else /* CONFIG_DM_SERIAL */
+static int lpuart_serial_ofdata_to_platdata(struct udevice *dev)
+{
+	struct lpuart_serial_platdata *plat = dev->platdata;
+	fdt_addr_t addr;
+
+	addr = dev_get_addr(dev);
+	if (addr == FDT_ADDR_T_NONE)
+		return -EINVAL;
+
+	plat->reg = (struct lpuart_fsl *)addr;
+
+	return 0;
+}
+
+#ifndef CONFIG_LPUART_32B_REG
+static const struct dm_serial_ops lpuart_serial_ops = {
+	.putc = lpuart_serial_putc,
+	.pending = lpuart_serial_pending,
+	.getc = lpuart_serial_getc,
+	.setbrg = lpuart_serial_setbrg,
+};
+
+static const struct udevice_id lpuart_serial_ids[] = {
+	{ .compatible = "fsl,vf610-lpuart" },
+	{ }
+};
+
+U_BOOT_DRIVER(serial_lpuart) = {
+	.name	= "serial_lpuart",
+	.id	= UCLASS_SERIAL,
+	.of_match = lpuart_serial_ids,
+	.ofdata_to_platdata = lpuart_serial_ofdata_to_platdata,
+	.platdata_auto_alloc_size = sizeof(struct lpuart_serial_platdata),
+	.probe = lpuart_serial_probe,
+	.ops	= &lpuart_serial_ops,
+	.flags = DM_FLAG_PRE_RELOC,
+};
+#else /* CONFIG_LPUART_32B_REG */
+static const struct dm_serial_ops lpuart32_serial_ops = {
+	.putc = lpuart32_serial_putc,
+	.pending = lpuart32_serial_pending,
+	.getc = lpuart32_serial_getc,
+	.setbrg = lpuart32_serial_setbrg,
+};
+
+static const struct udevice_id lpuart32_serial_ids[] = {
+	{ .compatible = "fsl,ls1021a-lpuart" },
+	{ }
+};
+
+U_BOOT_DRIVER(serial_lpuart32) = {
+	.name	= "serial_lpuart32",
+	.id	= UCLASS_SERIAL,
+	.of_match = lpuart32_serial_ids,
+	.ofdata_to_platdata = lpuart_serial_ofdata_to_platdata,
+	.platdata_auto_alloc_size = sizeof(struct lpuart_serial_platdata),
+	.probe = lpuart32_serial_probe,
+	.ops	= &lpuart32_serial_ops,
+	.flags = DM_FLAG_PRE_RELOC,
+};
+#endif /* CONFIG_LPUART_32B_REG */
+#endif /* CONFIG_DM_SERIAL */
diff --git a/drivers/timer/Kconfig b/drivers/timer/Kconfig
index 2b10d2bc6c46b01689b5621dc889b2c81ae314c0..ff65a731def3a073ba24a451263c4f39a6cc49ff 100644
--- a/drivers/timer/Kconfig
+++ b/drivers/timer/Kconfig
@@ -30,4 +30,10 @@ config X86_TSC_TIMER
 	help
 	  Select this to enable Time-Stamp Counter (TSC) timer for x86.
 
+config OMAP_TIMER
+	bool "Omap timer support"
+	depends on TIMER
+	help
+	  Select this to enable an timer for Omap devices.
+
 endmenu
diff --git a/drivers/timer/Makefile b/drivers/timer/Makefile
index fe954eca9a5fde08cdb9cb5b017fa8434bb9d79c..f351fbb4e0ca10c594df39e6c527892e9cd164d7 100644
--- a/drivers/timer/Makefile
+++ b/drivers/timer/Makefile
@@ -8,3 +8,4 @@ obj-$(CONFIG_TIMER)		+= timer-uclass.o
 obj-$(CONFIG_ALTERA_TIMER)	+= altera_timer.o
 obj-$(CONFIG_SANDBOX_TIMER)	+= sandbox_timer.o
 obj-$(CONFIG_X86_TSC_TIMER)	+= tsc_timer.o
+obj-$(CONFIG_OMAP_TIMER)	+= omap-timer.o
diff --git a/drivers/timer/omap-timer.c b/drivers/timer/omap-timer.c
new file mode 100644
index 0000000000000000000000000000000000000000..3bb38c522c07ac0f2bbfea694041f5e8b9c65ea7
--- /dev/null
+++ b/drivers/timer/omap-timer.c
@@ -0,0 +1,108 @@
+/*
+ * TI OMAP timer driver
+ *
+ * Copyright (C) 2015, Texas Instruments, Incorporated
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <errno.h>
+#include <timer.h>
+#include <asm/io.h>
+#include <asm/arch/clock.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+/* Timer register bits */
+#define TCLR_START			BIT(0)	/* Start=1 */
+#define TCLR_AUTO_RELOAD		BIT(1)	/* Auto reload */
+#define TCLR_PRE_EN			BIT(5)	/* Pre-scaler enable */
+#define TCLR_PTV_SHIFT			(2)	/* Pre-scaler shift value */
+
+#define TIMER_CLOCK             (V_SCLK / (2 << CONFIG_SYS_PTV))
+
+struct omap_gptimer_regs {
+	unsigned int tidr;		/* offset 0x00 */
+	unsigned char res1[12];
+	unsigned int tiocp_cfg;		/* offset 0x10 */
+	unsigned char res2[12];
+	unsigned int tier;		/* offset 0x20 */
+	unsigned int tistatr;		/* offset 0x24 */
+	unsigned int tistat;		/* offset 0x28 */
+	unsigned int tisr;		/* offset 0x2c */
+	unsigned int tcicr;		/* offset 0x30 */
+	unsigned int twer;		/* offset 0x34 */
+	unsigned int tclr;		/* offset 0x38 */
+	unsigned int tcrr;		/* offset 0x3c */
+	unsigned int tldr;		/* offset 0x40 */
+	unsigned int ttgr;		/* offset 0x44 */
+	unsigned int twpc;		/* offset 0x48 */
+	unsigned int tmar;		/* offset 0x4c */
+	unsigned int tcar1;		/* offset 0x50 */
+	unsigned int tscir;		/* offset 0x54 */
+	unsigned int tcar2;		/* offset 0x58 */
+};
+
+/* Omap Timer Priv */
+struct omap_timer_priv {
+	struct omap_gptimer_regs *regs;
+};
+
+static int omap_timer_get_count(struct udevice *dev, u64 *count)
+{
+	struct omap_timer_priv *priv = dev_get_priv(dev);
+
+	*count = readl(&priv->regs->tcrr);
+
+	return 0;
+}
+
+static int omap_timer_probe(struct udevice *dev)
+{
+	struct timer_dev_priv *uc_priv = dev_get_uclass_priv(dev);
+	struct omap_timer_priv *priv = dev_get_priv(dev);
+
+	uc_priv->clock_rate = TIMER_CLOCK;
+
+	/* start the counter ticking up, reload value on overflow */
+	writel(0, &priv->regs->tldr);
+	/* enable timer */
+	writel((CONFIG_SYS_PTV << 2) | TCLR_PRE_EN | TCLR_AUTO_RELOAD |
+	       TCLR_START, &priv->regs->tclr);
+
+	return 0;
+}
+
+static int omap_timer_ofdata_to_platdata(struct udevice *dev)
+{
+	struct omap_timer_priv *priv = dev_get_priv(dev);
+
+	priv->regs = (struct omap_gptimer_regs *)dev_get_addr(dev);
+
+	return 0;
+}
+
+
+static const struct timer_ops omap_timer_ops = {
+	.get_count = omap_timer_get_count,
+};
+
+static const struct udevice_id omap_timer_ids[] = {
+	{ .compatible = "ti,am335x-timer" },
+	{ .compatible = "ti,am4372-timer" },
+	{ .compatible = "ti,omap5430-timer" },
+	{}
+};
+
+U_BOOT_DRIVER(omap_timer) = {
+	.name	= "omap_timer",
+	.id	= UCLASS_TIMER,
+	.of_match = omap_timer_ids,
+	.ofdata_to_platdata = omap_timer_ofdata_to_platdata,
+	.priv_auto_alloc_size = sizeof(struct omap_timer_priv),
+	.probe = omap_timer_probe,
+	.ops	= &omap_timer_ops,
+	.flags = DM_FLAG_PRE_RELOC,
+};
diff --git a/drivers/timer/sandbox_timer.c b/drivers/timer/sandbox_timer.c
index 00a9944f78e6c2719f4687ef88be5a945f94c99c..a8da936349498cb817b22d291dd8007e1feb474c 100644
--- a/drivers/timer/sandbox_timer.c
+++ b/drivers/timer/sandbox_timer.c
@@ -27,6 +27,11 @@ static int sandbox_timer_get_count(struct udevice *dev, u64 *count)
 
 static int sandbox_timer_probe(struct udevice *dev)
 {
+	struct timer_dev_priv *uc_priv = dev_get_uclass_priv(dev);
+
+	if (!uc_priv->clock_rate)
+		uc_priv->clock_rate = 1000000;
+
 	return 0;
 }
 
@@ -47,3 +52,8 @@ U_BOOT_DRIVER(sandbox_timer) = {
 	.ops	= &sandbox_timer_ops,
 	.flags = DM_FLAG_PRE_RELOC,
 };
+
+/* This is here in case we don't have a device tree */
+U_BOOT_DEVICE(sandbox_timer_non_fdt) = {
+	.name = "sandbox_timer",
+};
diff --git a/drivers/timer/timer-uclass.c b/drivers/timer/timer-uclass.c
index aca421bdea331c4193d98adaf8ec350402ca23d6..83d1a35e0622afc325fcc250a91d5aec52c20eb9 100644
--- a/drivers/timer/timer-uclass.c
+++ b/drivers/timer/timer-uclass.c
@@ -6,6 +6,8 @@
 
 #include <common.h>
 #include <dm.h>
+#include <dm/lists.h>
+#include <dm/device-internal.h>
 #include <errno.h>
 #include <timer.h>
 
@@ -47,6 +49,16 @@ static int timer_pre_probe(struct udevice *dev)
 	return 0;
 }
 
+static int timer_post_probe(struct udevice *dev)
+{
+	struct timer_dev_priv *uc_priv = dev_get_uclass_priv(dev);
+
+	if (!uc_priv->clock_rate)
+		return -EINVAL;
+
+	return 0;
+}
+
 u64 timer_conv_64(u32 count)
 {
 	/* increment tbh if tbl has rolled over */
@@ -56,9 +68,53 @@ u64 timer_conv_64(u32 count)
 	return ((u64)gd->timebase_h << 32) | gd->timebase_l;
 }
 
+int notrace dm_timer_init(void)
+{
+	const void *blob = gd->fdt_blob;
+	struct udevice *dev = NULL;
+	int node;
+	int ret;
+
+	if (gd->timer)
+		return 0;
+
+	/* Check for a chosen timer to be used for tick */
+	node = fdtdec_get_chosen_node(blob, "tick-timer");
+	if (node < 0) {
+		/* No chosen timer, trying first available timer */
+		ret = uclass_first_device(UCLASS_TIMER, &dev);
+		if (ret)
+			return ret;
+		if (!dev)
+			return -ENODEV;
+	} else {
+		if (uclass_get_device_by_of_offset(UCLASS_TIMER, node, &dev)) {
+			/*
+			 * If the timer is not marked to be bound before
+			 * relocation, bind it anyway.
+			 */
+			if (node > 0 &&
+			    !lists_bind_fdt(gd->dm_root, blob, node, &dev)) {
+				ret = device_probe(dev);
+				if (ret)
+					return ret;
+			}
+		}
+	}
+
+	if (dev) {
+		gd->timer = dev;
+		return 0;
+	}
+
+	return -ENODEV;
+}
+
 UCLASS_DRIVER(timer) = {
 	.id		= UCLASS_TIMER,
 	.name		= "timer",
 	.pre_probe	= timer_pre_probe,
+	.flags		= DM_UC_FLAG_SEQ_ALIAS,
+	.post_probe	= timer_post_probe,
 	.per_device_auto_alloc_size = sizeof(struct timer_dev_priv),
 };
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index caf1efcbb3377b087c7fd5560ca5fa888590af9a..ae122daa0496a1db08e4322150e1c131084ba94c 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -4,6 +4,59 @@
 
 menu "Graphics support"
 
+config DM_VIDEO
+	bool "Enable driver model support for LCD/video"
+	depends on DM
+	help
+	  This enables driver model for LCD and video devices. These support
+	  a bitmap display of various sizes and depths which can be drawn on
+	  to display a command-line console or splash screen. Enabling this
+	  option compiles in the video uclass and routes all LCD/video access
+	  through this.
+
+config VIDEO_BPP8
+	bool "Support 8-bit-per-pixel displays"
+	depends on DM_VIDEO
+	default y if DM_VIDEO
+	help
+	  Support drawing text and bitmaps onto a 8-bit-per-pixel display.
+	  Enabling this will include code to support this display. Without
+	  this option, such displays will not be supported and console output
+	  will be empty.
+
+config VIDEO_BPP16
+	bool "Support 16-bit-per-pixel displays"
+	depends on DM_VIDEO
+	default y if DM_VIDEO
+	help
+	  Support drawing text and bitmaps onto a 16-bit-per-pixel display.
+	  Enabling this will include code to support this display. Without
+	  this option, such displays will not be supported and console output
+	  will be empty.
+
+config VIDEO_BPP32
+	bool "Support 32-bit-per-pixel displays"
+	depends on DM_VIDEO
+	default y if DM_VIDEO
+	help
+	  Support drawing text and bitmaps onto a 32-bit-per-pixel display.
+	  Enabling this will include code to support this display. Without
+	  this option, such displays will not be supported and console output
+	  will be empty.
+
+config VIDEO_ROTATION
+	bool "Support rotated displays"
+	depends on DM_VIDEO
+	help
+	  Sometimes, for example if the display is mounted in portrait
+	  mode or even if it's mounted landscape but rotated by 180degree,
+	  we need to rotate our content of the display relative to the
+	  framebuffer, so that user can read the messages which are
+	  printed out. Enable this option to include a text driver which can
+	  support this. The rotation is set by the 'rot' parameter in
+	  struct video_priv: 0=unrotated, 1=90 degrees clockwise, 2=180
+	  degrees, 3=270 degrees.
+
 config VIDEO_VESA
 	bool "Enable VESA video driver support"
 	default n
@@ -247,6 +300,15 @@ config DISPLAY_PORT
 	   to drive LCD panels. This framework provides support for enabling
 	   these displays where supported by the video hardware.
 
+config VIDEO_SANDBOX_SDL
+	bool "Enable sandbox video console using SDL"
+	depends on SANDBOX
+	help
+	  When using sandbox you can enable an emulated LCD display which
+	  appears as an SDL (Simple DirectMedia Layer) window. This is a
+	  console device and can display stdout output. Within U-Boot is is
+	  a normal bitmap display and can display images as well as text.
+
 config VIDEO_TEGRA124
 	bool "Enable video support on Tegra124"
 	help
diff --git a/drivers/video/Makefile b/drivers/video/Makefile
index e85fd8a6771b8521c96590c1aa8f59c4269353ca..ee046296e6137487d547cb3148bee6239a5123f7 100644
--- a/drivers/video/Makefile
+++ b/drivers/video/Makefile
@@ -7,6 +7,9 @@
 
 ifdef CONFIG_DM
 obj-$(CONFIG_DISPLAY_PORT) += dp-uclass.o
+obj-$(CONFIG_DM_VIDEO) += video-uclass.o vidconsole-uclass.o console_normal.o
+obj-$(CONFIG_DM_VIDEO) += video_bmp.o
+obj-$(CONFIG_VIDEO_ROTATION) += console_rotate.o
 endif
 
 obj-$(CONFIG_ATI_RADEON_FB) += ati_radeon_fb.o videomodes.o
diff --git a/drivers/video/console_normal.c b/drivers/video/console_normal.c
new file mode 100644
index 0000000000000000000000000000000000000000..d1031c8ed1752118e27c1c2cb6aecc4d761c2037
--- /dev/null
+++ b/drivers/video/console_normal.c
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 2015 Google, Inc
+ * (C) Copyright 2001-2015
+ * DENX Software Engineering -- wd@denx.de
+ * Compulab Ltd - http://compulab.co.il/
+ * Bernecker & Rainer Industrieelektronik GmbH - http://www.br-automation.com
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <video.h>
+#include <video_console.h>
+#include <video_font.h>		/* Get font data, width and height */
+
+static int console_normal_set_row(struct udevice *dev, uint row, int clr)
+{
+	struct video_priv *vid_priv = dev_get_uclass_priv(dev->parent);
+	void *line;
+	int pixels = VIDEO_FONT_HEIGHT * vid_priv->line_length;
+	int i;
+
+	line = vid_priv->fb + row * VIDEO_FONT_HEIGHT * vid_priv->line_length;
+	switch (vid_priv->bpix) {
+#ifdef CONFIG_VIDEO_BPP8
+	case VIDEO_BPP8: {
+		uint8_t *dst = line;
+
+		for (i = 0; i < pixels; i++)
+			*dst++ = clr;
+		break;
+	}
+#endif
+#ifdef CONFIG_VIDEO_BPP16
+	case VIDEO_BPP16: {
+		uint16_t *dst = line;
+
+		for (i = 0; i < pixels; i++)
+			*dst++ = clr;
+		break;
+	}
+#endif
+#ifdef CONFIG_VIDEO_BPP32
+	case VIDEO_BPP32: {
+		uint32_t *dst = line;
+
+		for (i = 0; i < pixels; i++)
+			*dst++ = clr;
+		break;
+	}
+#endif
+	default:
+		return -ENOSYS;
+	}
+
+	return 0;
+}
+
+static int console_normal_move_rows(struct udevice *dev, uint rowdst,
+				     uint rowsrc, uint count)
+{
+	struct video_priv *vid_priv = dev_get_uclass_priv(dev->parent);
+	void *dst;
+	void *src;
+
+	dst = vid_priv->fb + rowdst * VIDEO_FONT_HEIGHT * vid_priv->line_length;
+	src = vid_priv->fb + rowsrc * VIDEO_FONT_HEIGHT * vid_priv->line_length;
+	memmove(dst, src, VIDEO_FONT_HEIGHT * vid_priv->line_length * count);
+
+	return 0;
+}
+
+static int console_normal_putc_xy(struct udevice *dev, uint x, uint y, char ch)
+{
+	struct udevice *vid = dev->parent;
+	struct video_priv *vid_priv = dev_get_uclass_priv(vid);
+	int i, row;
+	void *line = vid_priv->fb + y * vid_priv->line_length +
+		x * VNBYTES(vid_priv->bpix);
+
+	for (row = 0; row < VIDEO_FONT_HEIGHT; row++) {
+		uchar bits = video_fontdata[ch * VIDEO_FONT_HEIGHT + row];
+
+		switch (vid_priv->bpix) {
+#ifdef CONFIG_VIDEO_BPP8
+		case VIDEO_BPP8: {
+			uint8_t *dst = line;
+
+			for (i = 0; i < VIDEO_FONT_WIDTH; i++) {
+				*dst++ = (bits & 0x80) ? vid_priv->colour_fg
+					: vid_priv->colour_bg;
+				bits <<= 1;
+			}
+			break;
+		}
+#endif
+#ifdef CONFIG_VIDEO_BPP16
+		case VIDEO_BPP16: {
+			uint16_t *dst = line;
+
+			for (i = 0; i < VIDEO_FONT_WIDTH; i++) {
+				*dst++ = (bits & 0x80) ? vid_priv->colour_fg
+					: vid_priv->colour_bg;
+				bits <<= 1;
+			}
+			break;
+		}
+#endif
+#ifdef CONFIG_VIDEO_BPP32
+		case VIDEO_BPP32: {
+			uint32_t *dst = line;
+
+			for (i = 0; i < VIDEO_FONT_WIDTH; i++) {
+				*dst++ = (bits & 0x80) ? vid_priv->colour_fg
+					: vid_priv->colour_bg;
+				bits <<= 1;
+			}
+			break;
+		}
+#endif
+		default:
+			return -ENOSYS;
+		}
+		line += vid_priv->line_length;
+	}
+
+	return 0;
+}
+
+struct vidconsole_ops console_normal_ops = {
+	.putc_xy	= console_normal_putc_xy,
+	.move_rows	= console_normal_move_rows,
+	.set_row	= console_normal_set_row,
+};
+
+U_BOOT_DRIVER(vidconsole_normal) = {
+	.name	= "vidconsole0",
+	.id	= UCLASS_VIDEO_CONSOLE,
+	.ops	= &console_normal_ops,
+};
diff --git a/drivers/video/console_rotate.c b/drivers/video/console_rotate.c
new file mode 100644
index 0000000000000000000000000000000000000000..ebb31d8cd0d1879dda03270bbed143305683ff01
--- /dev/null
+++ b/drivers/video/console_rotate.c
@@ -0,0 +1,436 @@
+/*
+ * Copyright (c) 2015 Google, Inc
+ * (C) Copyright 2015
+ * Bernecker & Rainer Industrieelektronik GmbH - http://www.br-automation.com
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <video.h>
+#include <video_console.h>
+#include <video_font.h>		/* Get font data, width and height */
+
+static int console_set_row_1(struct udevice *dev, uint row, int clr)
+{
+	struct video_priv *vid_priv = dev_get_uclass_priv(dev->parent);
+	int pbytes = VNBYTES(vid_priv->bpix);
+	void *line;
+	int i, j;
+
+	line = vid_priv->fb + vid_priv->line_length -
+		(row + 1) * VIDEO_FONT_HEIGHT * pbytes;
+	for (j = 0; j < vid_priv->ysize; j++) {
+		switch (vid_priv->bpix) {
+#ifdef CONFIG_VIDEO_BPP8
+		case VIDEO_BPP8: {
+			uint8_t *dst = line;
+
+			for (i = 0; i < VIDEO_FONT_HEIGHT; i++)
+				*dst++ = clr;
+			break;
+		}
+#endif
+#ifdef CONFIG_VIDEO_BPP16
+		case VIDEO_BPP16: {
+			uint16_t *dst = line;
+
+			for (i = 0; i < VIDEO_FONT_HEIGHT; i++)
+				*dst++ = clr;
+			break;
+		}
+#endif
+#ifdef CONFIG_VIDEO_BPP32
+		case VIDEO_BPP32: {
+			uint32_t *dst = line;
+
+			for (i = 0; i < VIDEO_FONT_HEIGHT; i++)
+				*dst++ = clr;
+			break;
+		}
+#endif
+		default:
+			return -ENOSYS;
+		}
+		line += vid_priv->line_length;
+	}
+
+	return 0;
+}
+
+static int console_move_rows_1(struct udevice *dev, uint rowdst, uint rowsrc,
+			       uint count)
+{
+	struct video_priv *vid_priv = dev_get_uclass_priv(dev->parent);
+	void *dst;
+	void *src;
+	int pbytes = VNBYTES(vid_priv->bpix);
+	int j;
+
+	dst = vid_priv->fb + vid_priv->line_length -
+		(rowdst + count) * VIDEO_FONT_HEIGHT * pbytes;
+	src = vid_priv->fb + vid_priv->line_length -
+		(rowsrc + count) * VIDEO_FONT_HEIGHT * pbytes;
+
+	for (j = 0; j < vid_priv->ysize; j++) {
+		memmove(dst, src, VIDEO_FONT_HEIGHT * pbytes * count);
+		src += vid_priv->line_length;
+		dst += vid_priv->line_length;
+	}
+
+	return 0;
+}
+
+static int console_putc_xy_1(struct udevice *dev, uint x, uint y, char ch)
+{
+	struct udevice *vid = dev->parent;
+	struct video_priv *vid_priv = dev_get_uclass_priv(vid);
+	int pbytes = VNBYTES(vid_priv->bpix);
+	int i, col;
+	int mask = 0x80;
+	void *line = vid_priv->fb + (x + 1) * vid_priv->line_length -
+		(y + 1) * pbytes;
+	uchar *pfont = video_fontdata + ch * VIDEO_FONT_HEIGHT;
+
+	for (col = 0; col < VIDEO_FONT_HEIGHT; col++) {
+		switch (vid_priv->bpix) {
+#ifdef CONFIG_VIDEO_BPP8
+		case VIDEO_BPP8: {
+			uint8_t *dst = line;
+
+			for (i = 0; i < VIDEO_FONT_HEIGHT; i++) {
+				*dst-- = (pfont[i] & mask) ? vid_priv->colour_fg
+					: vid_priv->colour_bg;
+			}
+			break;
+		}
+#endif
+#ifdef CONFIG_VIDEO_BPP16
+		case VIDEO_BPP16: {
+			uint16_t *dst = line;
+
+			for (i = 0; i < VIDEO_FONT_HEIGHT; i++) {
+				*dst-- = (pfont[i] & mask) ? vid_priv->colour_fg
+					: vid_priv->colour_bg;
+			}
+			break;
+		}
+#endif
+#ifdef CONFIG_VIDEO_BPP32
+		case VIDEO_BPP32: {
+			uint32_t *dst = line;
+
+			for (i = 0; i < VIDEO_FONT_HEIGHT; i++) {
+				*dst-- = (pfont[i] & mask) ? vid_priv->colour_fg
+					: vid_priv->colour_bg;
+			}
+			break;
+		}
+#endif
+		default:
+			return -ENOSYS;
+		}
+		line += vid_priv->line_length;
+		mask >>= 1;
+	}
+
+	return 0;
+}
+
+
+static int console_set_row_2(struct udevice *dev, uint row, int clr)
+{
+	struct video_priv *vid_priv = dev_get_uclass_priv(dev->parent);
+	void *line;
+	int pixels = VIDEO_FONT_HEIGHT * vid_priv->xsize;
+	int i;
+
+	line = vid_priv->fb + vid_priv->ysize * vid_priv->line_length -
+		(row + 1) * VIDEO_FONT_HEIGHT * vid_priv->line_length;
+	switch (vid_priv->bpix) {
+#ifdef CONFIG_VIDEO_BPP8
+	case VIDEO_BPP8: {
+		uint8_t *dst = line;
+
+		for (i = 0; i < pixels; i++)
+			*dst++ = clr;
+		break;
+	}
+#endif
+#ifdef CONFIG_VIDEO_BPP16
+	case VIDEO_BPP16: {
+		uint16_t *dst = line;
+
+		for (i = 0; i < pixels; i++)
+			*dst++ = clr;
+		break;
+	}
+#endif
+#ifdef CONFIG_VIDEO_BPP32
+	case VIDEO_BPP32: {
+		uint32_t *dst = line;
+
+		for (i = 0; i < pixels; i++)
+			*dst++ = clr;
+		break;
+	}
+#endif
+	default:
+		return -ENOSYS;
+	}
+
+	return 0;
+}
+
+static int console_move_rows_2(struct udevice *dev, uint rowdst, uint rowsrc,
+			       uint count)
+{
+	struct video_priv *vid_priv = dev_get_uclass_priv(dev->parent);
+	void *dst;
+	void *src;
+	void *end;
+
+	end = vid_priv->fb + vid_priv->ysize * vid_priv->line_length;
+	dst = end - (rowdst + count) * VIDEO_FONT_HEIGHT *
+		vid_priv->line_length;
+	src = end - (rowsrc + count) * VIDEO_FONT_HEIGHT *
+		vid_priv->line_length;
+	memmove(dst, src, VIDEO_FONT_HEIGHT * vid_priv->line_length * count);
+
+	return 0;
+}
+
+static int console_putc_xy_2(struct udevice *dev, uint x, uint y, char ch)
+{
+	struct udevice *vid = dev->parent;
+	struct video_priv *vid_priv = dev_get_uclass_priv(vid);
+	int i, row;
+	void *line;
+
+	line = vid_priv->fb + (vid_priv->ysize - y - 1) *
+		vid_priv->line_length +
+		(vid_priv->xsize - x - VIDEO_FONT_WIDTH - 1) *
+		VNBYTES(vid_priv->bpix);
+
+	for (row = 0; row < VIDEO_FONT_HEIGHT; row++) {
+		uchar bits = video_fontdata[ch * VIDEO_FONT_HEIGHT + row];
+
+		switch (vid_priv->bpix) {
+#ifdef CONFIG_VIDEO_BPP8
+		case VIDEO_BPP8: {
+			uint8_t *dst = line;
+
+			for (i = 0; i < VIDEO_FONT_WIDTH; i++) {
+				*dst-- = (bits & 0x80) ? vid_priv->colour_fg
+					: vid_priv->colour_bg;
+				bits <<= 1;
+			}
+			break;
+		}
+#endif
+#ifdef CONFIG_VIDEO_BPP16
+		case VIDEO_BPP16: {
+			uint16_t *dst = line;
+
+			for (i = 0; i < VIDEO_FONT_WIDTH; i++) {
+				*dst-- = (bits & 0x80) ? vid_priv->colour_fg
+					: vid_priv->colour_bg;
+				bits <<= 1;
+			}
+			break;
+		}
+#endif
+#ifdef CONFIG_VIDEO_BPP32
+		case VIDEO_BPP32: {
+			uint32_t *dst = line;
+
+			for (i = 0; i < VIDEO_FONT_WIDTH; i++) {
+				*dst-- = (bits & 0x80) ? vid_priv->colour_fg
+					: vid_priv->colour_bg;
+				bits <<= 1;
+			}
+			break;
+		}
+#endif
+		default:
+			return -ENOSYS;
+		}
+		line -= vid_priv->line_length;
+	}
+
+	return 0;
+}
+
+static int console_set_row_3(struct udevice *dev, uint row, int clr)
+{
+	struct video_priv *vid_priv = dev_get_uclass_priv(dev->parent);
+	int pbytes = VNBYTES(vid_priv->bpix);
+	void *line;
+	int i, j;
+
+	line = vid_priv->fb + row * VIDEO_FONT_HEIGHT * pbytes;
+	for (j = 0; j < vid_priv->ysize; j++) {
+		switch (vid_priv->bpix) {
+#ifdef CONFIG_VIDEO_BPP8
+		case VIDEO_BPP8: {
+			uint8_t *dst = line;
+
+			for (i = 0; i < VIDEO_FONT_HEIGHT; i++)
+				*dst++ = clr;
+			break;
+		}
+#endif
+#ifdef CONFIG_VIDEO_BPP16
+		case VIDEO_BPP16: {
+			uint16_t *dst = line;
+
+			for (i = 0; i < VIDEO_FONT_HEIGHT; i++)
+				*dst++ = clr;
+			break;
+		}
+#endif
+#ifdef CONFIG_VIDEO_BPP32
+		case VIDEO_BPP32: {
+			uint32_t *dst = line;
+
+			for (i = 0; i < VIDEO_FONT_HEIGHT; i++)
+				*dst++ = clr;
+			break;
+		}
+#endif
+		default:
+			return -ENOSYS;
+		}
+		line += vid_priv->line_length;
+	}
+
+	return 0;
+}
+
+static int console_move_rows_3(struct udevice *dev, uint rowdst, uint rowsrc,
+			       uint count)
+{
+	struct video_priv *vid_priv = dev_get_uclass_priv(dev->parent);
+	void *dst;
+	void *src;
+	int pbytes = VNBYTES(vid_priv->bpix);
+	int j;
+
+	dst = vid_priv->fb + rowdst * VIDEO_FONT_HEIGHT * pbytes;
+	src = vid_priv->fb + rowsrc * VIDEO_FONT_HEIGHT * pbytes;
+
+	for (j = 0; j < vid_priv->ysize; j++) {
+		memmove(dst, src, VIDEO_FONT_HEIGHT * pbytes * count);
+		src += vid_priv->line_length;
+		dst += vid_priv->line_length;
+	}
+
+	return 0;
+}
+
+static int console_putc_xy_3(struct udevice *dev, uint x, uint y, char ch)
+{
+	struct udevice *vid = dev->parent;
+	struct video_priv *vid_priv = dev_get_uclass_priv(vid);
+	int pbytes = VNBYTES(vid_priv->bpix);
+	int i, col;
+	int mask = 0x80;
+	void *line = vid_priv->fb + (vid_priv->ysize - x - 1) *
+		vid_priv->line_length + y * pbytes;
+	uchar *pfont = video_fontdata + ch * VIDEO_FONT_HEIGHT;
+
+	for (col = 0; col < VIDEO_FONT_HEIGHT; col++) {
+		switch (vid_priv->bpix) {
+#ifdef CONFIG_VIDEO_BPP8
+		case VIDEO_BPP8: {
+			uint8_t *dst = line;
+
+			for (i = 0; i < VIDEO_FONT_HEIGHT; i++) {
+				*dst++ = (pfont[i] & mask) ? vid_priv->colour_fg
+					: vid_priv->colour_bg;
+			}
+			break;
+		}
+#endif
+#ifdef CONFIG_VIDEO_BPP16
+		case VIDEO_BPP16: {
+			uint16_t *dst = line;
+
+			for (i = 0; i < VIDEO_FONT_HEIGHT; i++) {
+				*dst++ = (pfont[i] & mask) ? vid_priv->colour_fg
+					: vid_priv->colour_bg;
+			}
+			break;
+		}
+#endif
+#ifdef CONFIG_VIDEO_BPP32
+		case VIDEO_BPP32: {
+			uint32_t *dst = line;
+
+			for (i = 0; i < VIDEO_FONT_HEIGHT; i++) {
+				*dst++ = (pfont[i] & mask) ? vid_priv->colour_fg
+					: vid_priv->colour_bg;
+			}
+			break;
+		}
+#endif
+		default:
+			return -ENOSYS;
+		}
+		line -= vid_priv->line_length;
+		mask >>= 1;
+	}
+
+	return 0;
+}
+
+
+static int console_probe_1_3(struct udevice *dev)
+{
+	struct vidconsole_priv *priv = dev_get_uclass_priv(dev);
+	struct video_priv *vid_priv = dev_get_uclass_priv(dev->parent);
+
+	priv->cols = vid_priv->ysize / VIDEO_FONT_WIDTH;
+	priv->rows = vid_priv->xsize / VIDEO_FONT_HEIGHT;
+
+	return 0;
+}
+
+struct vidconsole_ops console_ops_1 = {
+	.putc_xy	= console_putc_xy_1,
+	.move_rows	= console_move_rows_1,
+	.set_row	= console_set_row_1,
+};
+
+struct vidconsole_ops console_ops_2 = {
+	.putc_xy	= console_putc_xy_2,
+	.move_rows	= console_move_rows_2,
+	.set_row	= console_set_row_2,
+};
+
+struct vidconsole_ops console_ops_3 = {
+	.putc_xy	= console_putc_xy_3,
+	.move_rows	= console_move_rows_3,
+	.set_row	= console_set_row_3,
+};
+
+U_BOOT_DRIVER(vidconsole_1) = {
+	.name	= "vidconsole1",
+	.id	= UCLASS_VIDEO_CONSOLE,
+	.ops	= &console_ops_1,
+	.probe	= console_probe_1_3,
+};
+
+U_BOOT_DRIVER(vidconsole_2) = {
+	.name	= "vidconsole2",
+	.id	= UCLASS_VIDEO_CONSOLE,
+	.ops	= &console_ops_2,
+};
+
+U_BOOT_DRIVER(vidconsole_3) = {
+	.name	= "vidconsole3",
+	.id	= UCLASS_VIDEO_CONSOLE,
+	.ops	= &console_ops_3,
+	.probe	= console_probe_1_3,
+};
diff --git a/drivers/video/sandbox_sdl.c b/drivers/video/sandbox_sdl.c
index ba4578e9d1530b30fea8c65b972198f4dc61b756..21448a14115bd65dfab8c3985b94abcf736c2129 100644
--- a/drivers/video/sandbox_sdl.c
+++ b/drivers/video/sandbox_sdl.c
@@ -5,75 +5,67 @@
  */
 
 #include <common.h>
+#include <dm.h>
 #include <fdtdec.h>
-#include <lcd.h>
-#include <malloc.h>
+#include <video.h>
 #include <asm/sdl.h>
 #include <asm/u-boot-sandbox.h>
+#include <dm/test.h>
 
 DECLARE_GLOBAL_DATA_PTR;
 
 enum {
-	/* Maximum LCD size we support */
+	/* Default LCD size we support */
 	LCD_MAX_WIDTH		= 1366,
 	LCD_MAX_HEIGHT		= 768,
-	LCD_MAX_LOG2_BPP	= 4,		/* 2^4 = 16 bpp */
 };
 
-vidinfo_t panel_info;
-
-void lcd_setcolreg(ushort regno, ushort red, ushort green, ushort blue)
+static int sandbox_sdl_probe(struct udevice *dev)
 {
-}
+	struct sandbox_sdl_plat *plat = dev_get_platdata(dev);
+	struct video_priv *uc_priv = dev_get_uclass_priv(dev);
+	int ret;
 
-void lcd_ctrl_init(void *lcdbase)
-{
-	/*
-	 * Allocate memory to keep BMP color conversion map. This is required
-	 * for 8 bit BMPs only (hence 256 colors). If malloc fails - keep
-	 * going, it is not even clear if displyaing the bitmap will be
-	 * required on the way up.
-	 */
-	panel_info.cmap = malloc(256 * NBITS(panel_info.vl_bpix) / 8);
-}
-
-void lcd_enable(void)
-{
-	if (sandbox_sdl_init_display(panel_info.vl_col, panel_info.vl_row,
-				     panel_info.vl_bpix))
+	ret = sandbox_sdl_init_display(plat->xres, plat->yres, plat->bpix);
+	if (ret) {
 		puts("LCD init failed\n");
+		return ret;
+	}
+	uc_priv->xsize = plat->xres;
+	uc_priv->ysize = plat->yres;
+	uc_priv->bpix = plat->bpix;
+	uc_priv->rot = plat->rot;
+
+	return 0;
 }
 
-int sandbox_lcd_sdl_early_init(void)
+static int sandbox_sdl_bind(struct udevice *dev)
 {
+	struct video_uc_platdata *uc_plat = dev_get_uclass_platdata(dev);
+	struct sandbox_sdl_plat *plat = dev_get_platdata(dev);
 	const void *blob = gd->fdt_blob;
-	int xres = LCD_MAX_WIDTH, yres = LCD_MAX_HEIGHT;
-	int node;
+	int node = dev->of_offset;
 	int ret = 0;
 
-	/*
-	 * The code in common/lcd.c does not cope with not being able to
-	 * set up a frame buffer. It will just happily keep writing to
-	 * invalid memory. So here we make sure that at least some buffer
-	 * is available even if it actually won't be displayed.
-	 */
-	node = fdtdec_next_compatible(blob, 0, COMPAT_SANDBOX_LCD_SDL);
-	if (node >= 0) {
-		xres = fdtdec_get_int(blob, node, "xres", LCD_MAX_WIDTH);
-		yres = fdtdec_get_int(blob, node, "yres", LCD_MAX_HEIGHT);
-		if (xres < 0 || xres > LCD_MAX_WIDTH) {
-			xres = LCD_MAX_WIDTH;
-			ret = -EINVAL;
-		}
-		if (yres < 0 || yres > LCD_MAX_HEIGHT) {
-			yres = LCD_MAX_HEIGHT;
-			ret = -EINVAL;
-		}
-	}
-
-	panel_info.vl_col = xres;
-	panel_info.vl_row = yres;
-	panel_info.vl_bpix = LCD_COLOR16;
+	plat->xres = fdtdec_get_int(blob, node, "xres", LCD_MAX_WIDTH);
+	plat->yres = fdtdec_get_int(blob, node, "yres", LCD_MAX_HEIGHT);
+	plat->bpix = VIDEO_BPP16;
+	uc_plat->size = plat->xres * plat->yres * (1 << plat->bpix) / 8;
+	debug("%s: Frame buffer size %x\n", __func__, uc_plat->size);
 
 	return ret;
 }
+
+static const struct udevice_id sandbox_sdl_ids[] = {
+	{ .compatible = "sandbox,lcd-sdl" },
+	{ }
+};
+
+U_BOOT_DRIVER(sdl_sandbox) = {
+	.name	= "sdl_sandbox",
+	.id	= UCLASS_VIDEO,
+	.of_match = sandbox_sdl_ids,
+	.bind	= sandbox_sdl_bind,
+	.probe	= sandbox_sdl_probe,
+	.platdata_auto_alloc_size	= sizeof(struct sandbox_sdl_plat),
+};
diff --git a/drivers/video/vidconsole-uclass.c b/drivers/video/vidconsole-uclass.c
new file mode 100644
index 0000000000000000000000000000000000000000..ea10189432fcae3145bb8974d763f9bd22f318a4
--- /dev/null
+++ b/drivers/video/vidconsole-uclass.c
@@ -0,0 +1,239 @@
+/*
+ * Copyright (c) 2015 Google, Inc
+ * (C) Copyright 2001-2015
+ * DENX Software Engineering -- wd@denx.de
+ * Compulab Ltd - http://compulab.co.il/
+ * Bernecker & Rainer Industrieelektronik GmbH - http://www.br-automation.com
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <video.h>
+#include <video_console.h>
+#include <video_font.h>		/* Get font data, width and height */
+
+/* By default we scroll by a single line */
+#ifndef CONFIG_CONSOLE_SCROLL_LINES
+#define CONFIG_CONSOLE_SCROLL_LINES 1
+#endif
+
+int vidconsole_putc_xy(struct udevice *dev, uint x, uint y, char ch)
+{
+	struct vidconsole_ops *ops = vidconsole_get_ops(dev);
+
+	if (!ops->putc_xy)
+		return -ENOSYS;
+	return ops->putc_xy(dev, x, y, ch);
+}
+
+int vidconsole_move_rows(struct udevice *dev, uint rowdst, uint rowsrc,
+			 uint count)
+{
+	struct vidconsole_ops *ops = vidconsole_get_ops(dev);
+
+	if (!ops->move_rows)
+		return -ENOSYS;
+	return ops->move_rows(dev, rowdst, rowsrc, count);
+}
+
+int vidconsole_set_row(struct udevice *dev, uint row, int clr)
+{
+	struct vidconsole_ops *ops = vidconsole_get_ops(dev);
+
+	if (!ops->set_row)
+		return -ENOSYS;
+	return ops->set_row(dev, row, clr);
+}
+
+/* Move backwards one space */
+static void vidconsole_back(struct udevice *dev)
+{
+	struct vidconsole_priv *priv = dev_get_uclass_priv(dev);
+
+	if (--priv->curr_col < 0) {
+		priv->curr_col = priv->cols - 1;
+		if (--priv->curr_row < 0)
+			priv->curr_row = 0;
+	}
+
+	vidconsole_putc_xy(dev, priv->curr_col * VIDEO_FONT_WIDTH,
+			   priv->curr_row * VIDEO_FONT_HEIGHT, ' ');
+}
+
+/* Move to a newline, scrolling the display if necessary */
+static void vidconsole_newline(struct udevice *dev)
+{
+	struct vidconsole_priv *priv = dev_get_uclass_priv(dev);
+	struct udevice *vid_dev = dev->parent;
+	struct video_priv *vid_priv = dev_get_uclass_priv(vid_dev);
+	const int rows = CONFIG_CONSOLE_SCROLL_LINES;
+	int i;
+
+	priv->curr_col = 0;
+
+	/* Check if we need to scroll the terminal */
+	if (++priv->curr_row >= priv->rows) {
+		vidconsole_move_rows(dev, 0, rows, priv->rows - rows);
+		for (i = 0; i < rows; i++)
+			vidconsole_set_row(dev, priv->rows - i - 1,
+					   vid_priv->colour_bg);
+		priv->curr_row -= rows;
+	}
+	video_sync(dev->parent);
+}
+
+int vidconsole_put_char(struct udevice *dev, char ch)
+{
+	struct vidconsole_priv *priv = dev_get_uclass_priv(dev);
+	int ret;
+
+	switch (ch) {
+	case '\r':
+		priv->curr_col = 0;
+		break;
+	case '\n':
+		vidconsole_newline(dev);
+		break;
+	case '\t':	/* Tab (8 chars alignment) */
+		priv->curr_col +=  8;
+		priv->curr_col &= ~7;
+
+		if (priv->curr_col >= priv->cols)
+			vidconsole_newline(dev);
+		break;
+	case '\b':
+		vidconsole_back(dev);
+		break;
+	default:
+		/*
+		 * Failure of this function normally indicates an unsupported
+		 * colour depth. Check this and return an error to help with
+		 * diagnosis.
+		 */
+		ret = vidconsole_putc_xy(dev,
+					 priv->curr_col * VIDEO_FONT_WIDTH,
+					 priv->curr_row * VIDEO_FONT_HEIGHT,
+					 ch);
+		if (ret)
+			return ret;
+		if (++priv->curr_col >= priv->cols)
+			vidconsole_newline(dev);
+		break;
+	}
+
+	return 0;
+}
+
+static void vidconsole_putc(struct stdio_dev *sdev, const char ch)
+{
+	struct udevice *dev = sdev->priv;
+
+	vidconsole_put_char(dev, ch);
+}
+
+static void vidconsole_puts(struct stdio_dev *sdev, const char *s)
+{
+	struct udevice *dev = sdev->priv;
+
+	while (*s)
+		vidconsole_put_char(dev, *s++);
+}
+
+/* Set up the number of rows and colours (rotated drivers override this) */
+static int vidconsole_pre_probe(struct udevice *dev)
+{
+	struct vidconsole_priv *priv = dev_get_uclass_priv(dev);
+	struct udevice *vid = dev->parent;
+	struct video_priv *vid_priv = dev_get_uclass_priv(vid);
+
+	priv->rows = vid_priv->ysize / VIDEO_FONT_HEIGHT;
+	priv->cols = vid_priv->xsize / VIDEO_FONT_WIDTH;
+
+	return 0;
+}
+
+/* Register the device with stdio */
+static int vidconsole_post_probe(struct udevice *dev)
+{
+	struct vidconsole_priv *priv = dev_get_uclass_priv(dev);
+	struct stdio_dev *sdev = &priv->sdev;
+	int ret;
+
+	strlcpy(sdev->name, dev->name, sizeof(sdev->name));
+	sdev->flags = DEV_FLAGS_OUTPUT;
+	sdev->putc = vidconsole_putc;
+	sdev->puts = vidconsole_puts;
+	sdev->priv = dev;
+	ret = stdio_register(sdev);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+UCLASS_DRIVER(vidconsole) = {
+	.id		= UCLASS_VIDEO_CONSOLE,
+	.name		= "vidconsole0",
+	.pre_probe	= vidconsole_pre_probe,
+	.post_probe	= vidconsole_post_probe,
+	.per_device_auto_alloc_size	= sizeof(struct vidconsole_priv),
+};
+
+void vidconsole_position_cursor(struct udevice *dev, unsigned col, unsigned row)
+{
+	struct vidconsole_priv *priv = dev_get_uclass_priv(dev);
+
+	priv->curr_col = min_t(short, col, priv->cols - 1);
+	priv->curr_row = min_t(short, row, priv->rows - 1);
+}
+
+static int do_video_setcursor(cmd_tbl_t *cmdtp, int flag, int argc,
+			      char *const argv[])
+{
+	unsigned int col, row;
+	struct udevice *dev;
+
+	if (argc != 3)
+		return CMD_RET_USAGE;
+
+	uclass_first_device(UCLASS_VIDEO_CONSOLE, &dev);
+	if (!dev)
+		return CMD_RET_FAILURE;
+	col = simple_strtoul(argv[1], NULL, 10);
+	row = simple_strtoul(argv[2], NULL, 10);
+	vidconsole_position_cursor(dev, col, row);
+
+	return 0;
+}
+
+static int do_video_puts(cmd_tbl_t *cmdtp, int flag, int argc,
+			 char *const argv[])
+{
+	struct udevice *dev;
+	const char *s;
+
+	if (argc != 2)
+		return CMD_RET_USAGE;
+
+	uclass_first_device(UCLASS_VIDEO_CONSOLE, &dev);
+	if (!dev)
+		return CMD_RET_FAILURE;
+	for (s = argv[1]; *s; s++)
+		vidconsole_put_char(dev, *s);
+
+	return 0;
+}
+
+U_BOOT_CMD(
+	setcurs, 3,	1,	do_video_setcursor,
+	"set cursor position within screen",
+	"    <col> <row> in character"
+);
+
+U_BOOT_CMD(
+	lcdputs, 2,	1,	do_video_puts,
+	"print string on video framebuffer",
+	"    <string>"
+);
diff --git a/drivers/video/video-uclass.c b/drivers/video/video-uclass.c
new file mode 100644
index 0000000000000000000000000000000000000000..63d0d9d7d3f5586f41b06f288db51e58295c8d29
--- /dev/null
+++ b/drivers/video/video-uclass.c
@@ -0,0 +1,249 @@
+/*
+ * Copyright (c) 2015 Google, Inc
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <mapmem.h>
+#include <stdio_dev.h>
+#include <video.h>
+#include <video_console.h>
+#include <dm/lists.h>
+#include <dm/device-internal.h>
+#include <dm/uclass-internal.h>
+#ifdef CONFIG_SANDBOX
+#include <asm/sdl.h>
+#endif
+
+/*
+ * Theory of operation:
+ *
+ * Before relocation each device is bound. The driver for each device must
+ * set the @align and @size values in struct video_uc_platdata. This
+ * information represents the requires size and alignment of the frame buffer
+ * for the device. The values can be an over-estimate but cannot be too
+ * small. The actual values will be suppled (in the same manner) by the bind()
+ * method after relocation.
+ *
+ * This information is then picked up by video_reserve() which works out how
+ * much memory is needed for all devices. This is allocated between
+ * gd->video_bottom and gd->video_top.
+ *
+ * After relocation the same process occurs. The driver supplies the same
+ * @size and @align information and this time video_post_bind() checks that
+ * the drivers does not overflow the allocated memory.
+ *
+ * The frame buffer address is actually set (to plat->base) in
+ * video_post_probe(). This function also clears the frame buffer and
+ * allocates a suitable text console device. This can then be used to write
+ * text to the video device.
+ */
+DECLARE_GLOBAL_DATA_PTR;
+
+static ulong alloc_fb(struct udevice *dev, ulong *addrp)
+{
+	struct video_uc_platdata *plat = dev_get_uclass_platdata(dev);
+	ulong base, align, size;
+
+	align = plat->align ? plat->align : 1 << 20;
+	base = *addrp - plat->size;
+	base &= ~(align - 1);
+	plat->base = base;
+	size = *addrp - base;
+	*addrp = base;
+
+	return size;
+}
+
+int video_reserve(ulong *addrp)
+{
+	struct udevice *dev;
+	ulong size;
+
+	gd->video_top = *addrp;
+	for (uclass_find_first_device(UCLASS_VIDEO, &dev);
+	     dev;
+	     uclass_find_next_device(&dev)) {
+		size = alloc_fb(dev, addrp);
+		debug("%s: Reserving %lx bytes at %lx for video device '%s'\n",
+		      __func__, size, *addrp, dev->name);
+	}
+	gd->video_bottom = *addrp;
+	debug("Video frame buffers from %lx to %lx\n", gd->video_bottom,
+	      gd->video_top);
+
+	return 0;
+}
+
+static int video_clear(struct udevice *dev)
+{
+	struct video_priv *priv = dev_get_uclass_priv(dev);
+
+	if (priv->bpix == VIDEO_BPP32) {
+		u32 *ppix = priv->fb;
+		u32 *end = priv->fb + priv->fb_size;
+
+		while (ppix < end)
+			*ppix++ = priv->colour_bg;
+	} else {
+		memset(priv->fb, priv->colour_bg, priv->fb_size);
+	}
+
+	return 0;
+}
+
+/* Flush video activity to the caches */
+void video_sync(struct udevice *vid)
+{
+	/*
+	 * flush_dcache_range() is declared in common.h but it seems that some
+	 * architectures do not actually implement it. Is there a way to find
+	 * out whether it exists? For now, ARM is safe.
+	 */
+#if defined(CONFIG_ARM) && !defined(CONFIG_SYS_DCACHE_OFF)
+	struct video_priv *priv = dev_get_uclass_priv(vid);
+
+	if (priv->flush_dcache) {
+		flush_dcache_range((ulong)priv->fb,
+				   (ulong)priv->fb + priv->fb_size);
+	}
+#elif defined(CONFIG_VIDEO_SANDBOX_SDL)
+	struct video_priv *priv = dev_get_uclass_priv(vid);
+	static ulong last_sync;
+
+	if (get_timer(last_sync) > 10) {
+		sandbox_sdl_sync(priv->fb);
+		last_sync = get_timer(0);
+	}
+#endif
+}
+
+void video_sync_all(void)
+{
+	struct udevice *dev;
+
+	for (uclass_find_first_device(UCLASS_VIDEO, &dev);
+	     dev;
+	     uclass_find_next_device(&dev)) {
+		if (device_active(dev))
+			video_sync(dev);
+	}
+}
+
+int video_get_xsize(struct udevice *dev)
+{
+	struct video_priv *priv = dev_get_uclass_priv(dev);
+
+	return priv->xsize;
+}
+
+int video_get_ysize(struct udevice *dev)
+{
+	struct video_priv *priv = dev_get_uclass_priv(dev);
+
+	return priv->ysize;
+}
+
+/* Set up the colour map */
+static int video_pre_probe(struct udevice *dev)
+{
+	struct video_priv *priv = dev_get_uclass_priv(dev);
+
+	priv->cmap = calloc(256, sizeof(ushort));
+	if (!priv->cmap)
+		return -ENOMEM;
+
+	return 0;
+}
+
+static int video_pre_remove(struct udevice *dev)
+{
+	struct video_priv *priv = dev_get_uclass_priv(dev);
+
+	free(priv->cmap);
+
+	return 0;
+}
+
+/* Set up the display ready for use */
+static int video_post_probe(struct udevice *dev)
+{
+	struct video_uc_platdata *plat = dev_get_uclass_platdata(dev);
+	struct video_priv *priv = dev_get_uclass_priv(dev);
+	char name[30], drv[15], *str;
+	struct udevice *cons;
+	int ret;
+
+	/* Set up the line and display size */
+	priv->fb = map_sysmem(plat->base, plat->size);
+	priv->line_length = priv->xsize * VNBYTES(priv->bpix);
+	priv->fb_size = priv->line_length * priv->ysize;
+
+	/* Set up colours - we could in future support other colours */
+#ifdef CONFIG_SYS_WHITE_ON_BLACK
+	priv->colour_fg = 0xffffff;
+#else
+	priv->colour_bg = 0xffffff;
+#endif
+	video_clear(dev);
+
+	/*
+	 * Create a text console devices. For now we always do this, although
+	 * it might be useful to support only bitmap drawing on the device
+	 * for boards that don't need to display text.
+	 */
+	snprintf(name, sizeof(name), "%s.vidconsole", dev->name);
+	str = strdup(name);
+	if (!str)
+		return -ENOMEM;
+	snprintf(drv, sizeof(drv), "vidconsole%d", priv->rot);
+	ret = device_bind_driver(dev, drv, str, &cons);
+	if (ret) {
+		debug("%s: Cannot bind console driver\n", __func__);
+		return ret;
+	}
+	ret = device_probe(cons);
+	if (ret) {
+		debug("%s: Cannot probe console driver\n", __func__);
+		return ret;
+	}
+
+	return 0;
+};
+
+/* Post-relocation, allocate memory for the frame buffer */
+static int video_post_bind(struct udevice *dev)
+{
+	ulong addr = gd->video_top;
+	ulong size;
+
+	/* Before relocation there is nothing to do here */
+	if ((!gd->flags & GD_FLG_RELOC))
+		return 0;
+	size = alloc_fb(dev, &addr);
+	if (addr < gd->video_bottom) {
+		/* Device tree node may need the 'u-boot,dm-pre-reloc' tag */
+		printf("Video device '%s' cannot allocate frame buffer memory -ensure the device is set up before relocation\n",
+		       dev->name);
+		return -ENOSPC;
+	}
+	debug("%s: Claiming %lx bytes at %lx for video device '%s'\n",
+	      __func__, size, addr, dev->name);
+	gd->video_bottom = addr;
+
+	return 0;
+}
+
+UCLASS_DRIVER(video) = {
+	.id		= UCLASS_VIDEO,
+	.name		= "video",
+	.flags		= DM_UC_FLAG_SEQ_ALIAS,
+	.post_bind	= video_post_bind,
+	.pre_probe	= video_pre_probe,
+	.post_probe	= video_post_probe,
+	.pre_remove	= video_pre_remove,
+	.per_device_auto_alloc_size	= sizeof(struct video_priv),
+	.per_device_platdata_auto_alloc_size = sizeof(struct video_uc_platdata),
+};
diff --git a/drivers/video/video_bmp.c b/drivers/video/video_bmp.c
new file mode 100644
index 0000000000000000000000000000000000000000..c9075d62dd7622049b8d929bccb71303f81f21a3
--- /dev/null
+++ b/drivers/video/video_bmp.c
@@ -0,0 +1,353 @@
+/*
+ * Copyright (c) 2015 Google, Inc
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#include <common.h>
+#include <bmp_layout.h>
+#include <dm.h>
+#include <mapmem.h>
+#include <video.h>
+#include <watchdog.h>
+#include <asm/unaligned.h>
+
+#ifdef CONFIG_VIDEO_BMP_RLE8
+#define BMP_RLE8_ESCAPE		0
+#define BMP_RLE8_EOL		0
+#define BMP_RLE8_EOBMP		1
+#define BMP_RLE8_DELTA		2
+
+static void draw_unencoded_bitmap(ushort **fbp, uchar *bmap, ushort *cmap,
+				  int cnt)
+{
+	while (cnt > 0) {
+		*(*fbp)++ = cmap[*bmap++];
+		cnt--;
+	}
+}
+
+static void draw_encoded_bitmap(ushort **fbp, ushort col, int cnt)
+{
+	ushort *fb = *fbp;
+
+	while (cnt > 0) {
+		*fb++ = col;
+		cnt--;
+	}
+	*fbp = fb;
+}
+
+static void video_display_rle8_bitmap(struct udevice *dev,
+				      struct bmp_image *bmp, ushort *cmap,
+				      uchar *fb, int x_off, int y_off)
+{
+	struct video_priv *priv = dev_get_uclass_priv(dev);
+	uchar *bmap;
+	ulong width, height;
+	ulong cnt, runlen;
+	int x, y;
+	int decode = 1;
+
+	debug("%s\n", __func__);
+	width = get_unaligned_le32(&bmp->header.width);
+	height = get_unaligned_le32(&bmp->header.height);
+	bmap = (uchar *)bmp + get_unaligned_le32(&bmp->header.data_offset);
+
+	x = 0;
+	y = height - 1;
+
+	while (decode) {
+		if (bmap[0] == BMP_RLE8_ESCAPE) {
+			switch (bmap[1]) {
+			case BMP_RLE8_EOL:
+				/* end of line */
+				bmap += 2;
+				x = 0;
+				y--;
+				/* 16bpix, 2-byte per pixel, width should *2 */
+				fb -= (width * 2 + priv->line_length);
+				break;
+			case BMP_RLE8_EOBMP:
+				/* end of bitmap */
+				decode = 0;
+				break;
+			case BMP_RLE8_DELTA:
+				/* delta run */
+				x += bmap[2];
+				y -= bmap[3];
+				/* 16bpix, 2-byte per pixel, x should *2 */
+				fb = (uchar *)(priv->fb + (y + y_off - 1)
+					* priv->line_length + (x + x_off) * 2);
+				bmap += 4;
+				break;
+			default:
+				/* unencoded run */
+				runlen = bmap[1];
+				bmap += 2;
+				if (y < height) {
+					if (x < width) {
+						if (x + runlen > width)
+							cnt = width - x;
+						else
+							cnt = runlen;
+						draw_unencoded_bitmap(
+							(ushort **)&fb,
+							bmap, cmap, cnt);
+					}
+					x += runlen;
+				}
+				bmap += runlen;
+				if (runlen & 1)
+					bmap++;
+			}
+		} else {
+			/* encoded run */
+			if (y < height) {
+				runlen = bmap[0];
+				if (x < width) {
+					/* aggregate the same code */
+					while (bmap[0] == 0xff &&
+					       bmap[2] != BMP_RLE8_ESCAPE &&
+					       bmap[1] == bmap[3]) {
+						runlen += bmap[2];
+						bmap += 2;
+					}
+					if (x + runlen > width)
+						cnt = width - x;
+					else
+						cnt = runlen;
+					draw_encoded_bitmap((ushort **)&fb,
+						cmap[bmap[1]], cnt);
+				}
+				x += runlen;
+			}
+			bmap += 2;
+		}
+	}
+}
+#endif
+
+__weak void fb_put_byte(uchar **fb, uchar **from)
+{
+	*(*fb)++ = *(*from)++;
+}
+
+#if defined(CONFIG_BMP_16BPP)
+__weak void fb_put_word(uchar **fb, uchar **from)
+{
+	*(*fb)++ = *(*from)++;
+	*(*fb)++ = *(*from)++;
+}
+#endif /* CONFIG_BMP_16BPP */
+
+#define BMP_ALIGN_CENTER	0x7fff
+
+/**
+ * video_splash_align_axis() - Align a single coordinate
+ *
+ *- if a coordinate is 0x7fff then the image will be centred in
+ *  that direction
+ *- if a coordinate is -ve then it will be offset to the
+ *  left/top of the centre by that many pixels
+ *- if a coordinate is positive it will be used unchnaged.
+ *
+ * @axis:	Input and output coordinate
+ * @panel_size:	Size of panel in pixels for that axis
+ * @picture_size:	Size of bitmap in pixels for that axis
+ */
+static void video_splash_align_axis(int *axis, unsigned long panel_size,
+				    unsigned long picture_size)
+{
+	unsigned long panel_picture_delta = panel_size - picture_size;
+	unsigned long axis_alignment;
+
+	if (*axis == BMP_ALIGN_CENTER)
+		axis_alignment = panel_picture_delta / 2;
+	else if (*axis < 0)
+		axis_alignment = panel_picture_delta + *axis + 1;
+	else
+		return;
+
+	*axis = max(0, (int)axis_alignment);
+}
+
+static void video_set_cmap(struct udevice *dev,
+			   struct bmp_color_table_entry *cte, unsigned colours)
+{
+	struct video_priv *priv = dev_get_uclass_priv(dev);
+	int i;
+	ushort *cmap = priv->cmap;
+
+	debug("%s: colours=%d\n", __func__, colours);
+	for (i = 0; i < colours; ++i) {
+		*cmap = ((cte->red   << 8) & 0xf800) |
+			((cte->green << 3) & 0x07e0) |
+			((cte->blue  >> 3) & 0x001f);
+		cmap++;
+		cte++;
+	}
+}
+
+int video_bmp_display(struct udevice *dev, ulong bmp_image, int x, int y,
+		      bool align)
+{
+	struct video_priv *priv = dev_get_uclass_priv(dev);
+	ushort *cmap_base = NULL;
+	ushort i, j;
+	uchar *fb;
+	struct bmp_image *bmp = map_sysmem(bmp_image, 0);
+	uchar *bmap;
+	ushort padded_width;
+	unsigned long width, height, byte_width;
+	unsigned long pwidth = priv->xsize;
+	unsigned colours, bpix, bmp_bpix;
+	struct bmp_color_table_entry *palette;
+	int hdr_size;
+
+	if (!bmp || !(bmp->header.signature[0] == 'B' &&
+	    bmp->header.signature[1] == 'M')) {
+		printf("Error: no valid bmp image at %lx\n", bmp_image);
+
+		return -EINVAL;
+	}
+
+	width = get_unaligned_le32(&bmp->header.width);
+	height = get_unaligned_le32(&bmp->header.height);
+	bmp_bpix = get_unaligned_le16(&bmp->header.bit_count);
+	hdr_size = get_unaligned_le16(&bmp->header.size);
+	debug("hdr_size=%d, bmp_bpix=%d\n", hdr_size, bmp_bpix);
+	palette = (void *)bmp + 14 + hdr_size;
+
+	colours = 1 << bmp_bpix;
+
+	bpix = VNBITS(priv->bpix);
+
+	if (bpix != 1 && bpix != 8 && bpix != 16 && bpix != 32) {
+		printf("Error: %d bit/pixel mode, but BMP has %d bit/pixel\n",
+		       bpix, bmp_bpix);
+
+		return -EINVAL;
+	}
+
+	/*
+	 * We support displaying 8bpp BMPs on 16bpp LCDs
+	 * and displaying 24bpp BMPs on 32bpp LCDs
+	 * */
+	if (bpix != bmp_bpix &&
+	    !(bmp_bpix == 8 && bpix == 16) &&
+	    !(bmp_bpix == 24 && bpix == 32)) {
+		printf("Error: %d bit/pixel mode, but BMP has %d bit/pixel\n",
+		       bpix, get_unaligned_le16(&bmp->header.bit_count));
+		return -EPERM;
+	}
+
+	debug("Display-bmp: %d x %d  with %d colours, display %d\n",
+	      (int)width, (int)height, (int)colours, 1 << bpix);
+
+	if (bmp_bpix == 8)
+		video_set_cmap(dev, palette, colours);
+
+	padded_width = (width & 0x3 ? (width & ~0x3) + 4 : width);
+
+	if (align) {
+		video_splash_align_axis(&x, priv->xsize, width);
+		video_splash_align_axis(&y, priv->ysize, height);
+	}
+
+	if ((x + width) > pwidth)
+		width = pwidth - x;
+	if ((y + height) > priv->ysize)
+		height = priv->ysize - y;
+
+	bmap = (uchar *)bmp + get_unaligned_le32(&bmp->header.data_offset);
+	fb = (uchar *)(priv->fb +
+		(y + height - 1) * priv->line_length + x * bpix / 8);
+
+	switch (bmp_bpix) {
+	case 1:
+	case 8: {
+		cmap_base = priv->cmap;
+#ifdef CONFIG_VIDEO_BMP_RLE8
+		u32 compression = get_unaligned_le32(&bmp->header.compression);
+		debug("compressed %d %d\n", compression, BMP_BI_RLE8);
+		if (compression == BMP_BI_RLE8) {
+			if (bpix != 16) {
+				/* TODO implement render code for bpix != 16 */
+				printf("Error: only support 16 bpix");
+				return -EPROTONOSUPPORT;
+			}
+			video_display_rle8_bitmap(dev, bmp, cmap_base, fb, x,
+						  y);
+			break;
+		}
+#endif
+
+		if (bpix != 16)
+			byte_width = width;
+		else
+			byte_width = width * 2;
+
+		for (i = 0; i < height; ++i) {
+			WATCHDOG_RESET();
+			for (j = 0; j < width; j++) {
+				if (bpix != 16) {
+					fb_put_byte(&fb, &bmap);
+				} else {
+					*(uint16_t *)fb = cmap_base[*bmap];
+					bmap++;
+					fb += sizeof(uint16_t) / sizeof(*fb);
+				}
+			}
+			bmap += (padded_width - width);
+			fb -= byte_width + priv->line_length;
+		}
+		break;
+	}
+#if defined(CONFIG_BMP_16BPP)
+	case 16:
+		for (i = 0; i < height; ++i) {
+			WATCHDOG_RESET();
+			for (j = 0; j < width; j++)
+				fb_put_word(&fb, &bmap);
+
+			bmap += (padded_width - width) * 2;
+			fb -= width * 2 + lcd_line_length;
+		}
+		break;
+#endif /* CONFIG_BMP_16BPP */
+#if defined(CONFIG_BMP_24BMP)
+	case 24:
+		for (i = 0; i < height; ++i) {
+			for (j = 0; j < width; j++) {
+				*(fb++) = *(bmap++);
+				*(fb++) = *(bmap++);
+				*(fb++) = *(bmap++);
+				*(fb++) = 0;
+			}
+			fb -= lcd_line_length + width * (bpix / 8);
+		}
+		break;
+#endif /* CONFIG_BMP_24BMP */
+#if defined(CONFIG_BMP_32BPP)
+	case 32:
+		for (i = 0; i < height; ++i) {
+			for (j = 0; j < width; j++) {
+				*(fb++) = *(bmap++);
+				*(fb++) = *(bmap++);
+				*(fb++) = *(bmap++);
+				*(fb++) = *(bmap++);
+			}
+			fb -= lcd_line_length + width * (bpix / 8);
+		}
+		break;
+#endif /* CONFIG_BMP_32BPP */
+	default:
+		break;
+	};
+
+	video_sync(dev);
+
+	return 0;
+}
+
diff --git a/include/asm-generic/global_data.h b/include/asm-generic/global_data.h
index 5d8b043f1441ed93fad348fc099c65385578a404..a587d3c2030a27831473ef1ef4cb8352957e6172 100644
--- a/include/asm-generic/global_data.h
+++ b/include/asm-generic/global_data.h
@@ -122,6 +122,10 @@ typedef struct global_data {
 	struct membuff console_out;	/* console output */
 	struct membuff console_in;	/* console input */
 #endif
+#ifdef CONFIG_DM_VIDEO
+	ulong video_top;		/* Top of video frame buffer area */
+	ulong video_bottom;		/* Bottom of video frame buffer area */
+#endif
 } gd_t;
 #endif
 
diff --git a/include/bzlib.h b/include/bzlib.h
index 2d864d56b75676600a21750537c4b84e7a1bc1df..19314f8f665752769d51ebea6523ccb23118fc3f 100644
--- a/include/bzlib.h
+++ b/include/bzlib.h
@@ -68,7 +68,10 @@
 
 /* Configure for U-Boot environment */
 #define BZ_NO_STDIO
+
+#ifndef CONFIG_SANDBOX
 #define BZ_NO_COMPRESS
+#endif
 /* End of configuration for U-Boot environment */
 
 #ifdef __cplusplus
diff --git a/include/clk.h b/include/clk.h
index 254ad2b8761bf4acef3a5b4b06b3c6bc4c11ac11..941808a50e253a83dda83d8fecaaa01c5292d50b 100644
--- a/include/clk.h
+++ b/include/clk.h
@@ -8,6 +8,10 @@
 #ifndef _CLK_H_
 #define _CLK_H_
 
+#include <linux/types.h>
+
+struct udevice;
+
 int soc_clk_dump(void);
 
 struct clk_ops {
@@ -29,19 +33,28 @@ struct clk_ops {
 	ulong (*set_rate)(struct udevice *dev, ulong rate);
 
 	/**
-	* clk_set_periph_rate() - Set clock rate for a peripheral
-	*
-	* @dev:	Device to adjust (UCLASS_CLK)
-	* @rate:	New clock rate in Hz
-	* @return new clock rate in Hz, or -ve error code
-	*/
+	 * enable() - Enable the clock for a peripheral
+	 *
+	 * @dev:	clock provider
+	 * @periph:	Peripheral ID to enable
+	 * @return zero on success, or -ve error code
+	 */
+	int (*enable)(struct udevice *dev, int periph);
+
+	/**
+	 * get_periph_rate() - Get clock rate for a peripheral
+	 *
+	 * @dev:	Device to check (UCLASS_CLK)
+	 * @periph:	Peripheral ID to check
+	 * @return clock rate in Hz, or -ve error code
+	 */
 	ulong (*get_periph_rate)(struct udevice *dev, int periph);
 
 	/**
-	 * clk_set_periph_rate() - Set current clock rate for a peripheral
+	 * set_periph_rate() - Set current clock rate for a peripheral
 	 *
 	 * @dev:	Device to update (UCLASS_CLK)
-	 * @periph:	Peripheral ID to cupdate
+	 * @periph:	Peripheral ID to update
 	 * @return new clock rate in Hz, or -ve error code
 	 */
 	ulong (*set_periph_rate)(struct udevice *dev, int periph, ulong rate);
@@ -58,7 +71,7 @@ struct clk_ops {
 ulong clk_get_rate(struct udevice *dev);
 
 /**
- * set_rate() - Set current clock rate
+ * clk_set_rate() - Set current clock rate
  *
  * @dev:	Device to adjust
  * @rate:	New clock rate in Hz
@@ -66,6 +79,15 @@ ulong clk_get_rate(struct udevice *dev);
  */
 ulong clk_set_rate(struct udevice *dev, ulong rate);
 
+/**
+ * clk_enable() - Enable the clock for a peripheral
+ *
+ * @dev:	clock provider
+ * @periph:	Peripheral ID to enable
+ * @return zero on success, or -ve error code
+ */
+int clk_enable(struct udevice *dev, int periph);
+
 /**
  * clk_get_periph_rate() - Get current clock rate for a peripheral
  *
@@ -78,7 +100,7 @@ ulong clk_get_periph_rate(struct udevice *dev, int periph);
  * clk_set_periph_rate() - Set current clock rate for a peripheral
  *
  * @dev:	Device to update (UCLASS_CLK)
- * @periph:	Peripheral ID to cupdate
+ * @periph:	Peripheral ID to update
  * @return new clock rate in Hz, or -ve error code
  */
 ulong clk_set_periph_rate(struct udevice *dev, int periph, ulong rate);
diff --git a/include/configs/am335x_evm.h b/include/configs/am335x_evm.h
index cf6a6063b5e3ec899b5fa88cfc11be75b7afe5c8..6ebe0b3866f9b137472cc080c9eb8f1e38233186 100644
--- a/include/configs/am335x_evm.h
+++ b/include/configs/am335x_evm.h
@@ -357,6 +357,7 @@
  */
 #ifdef CONFIG_SPL_BUILD
 #undef CONFIG_DM_MMC
+#undef CONFIG_TIMER
 #endif
 
 #if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_USBETH_SUPPORT)
diff --git a/include/configs/am43xx_evm.h b/include/configs/am43xx_evm.h
index de7538fc9e6d9ff74a7d0a790a8e81063a6788fb..c3867efe9a0ff3dae77be30faf68bfb48874c371 100644
--- a/include/configs/am43xx_evm.h
+++ b/include/configs/am43xx_evm.h
@@ -144,6 +144,7 @@
 #undef CONFIG_DM_MMC
 #undef CONFIG_DM_SPI
 #undef CONFIG_DM_SPI_FLASH
+#undef CONFIG_TIMER
 #endif
 
 #ifndef CONFIG_SPL_BUILD
diff --git a/include/configs/colibri_vf.h b/include/configs/colibri_vf.h
index 708c79af9735fde80f69a59e4d62821ce6046489..5aed3a5fcd1db29d8db1655bf5d472795b5210a4 100644
--- a/include/configs/colibri_vf.h
+++ b/include/configs/colibri_vf.h
@@ -36,7 +36,6 @@
 
 #define CONFIG_BOARD_EARLY_INIT_F
 
-#define CONFIG_FSL_LPUART
 #define LPUART_BASE			UART0_BASE
 
 /* Allow to overwrite serial and ethaddr */
diff --git a/include/configs/ls1021aqds.h b/include/configs/ls1021aqds.h
index 2e8dbc7a78b340e5ab879d170f4b19c5181255e1..e8b1ecaeb19c6da7b85f51423a8b5ebe824058f6 100644
--- a/include/configs/ls1021aqds.h
+++ b/include/configs/ls1021aqds.h
@@ -371,7 +371,6 @@ unsigned long get_board_ddr_clk(void);
  * Serial Port
  */
 #ifdef CONFIG_LPUART
-#define CONFIG_FSL_LPUART
 #define CONFIG_LPUART_32B_REG
 #else
 #define CONFIG_CONS_INDEX		1
diff --git a/include/configs/ls1021atwr.h b/include/configs/ls1021atwr.h
index c12ba3ac910bcac6a49f728e2ce2f2ddf933b02e..317ba62d3d05a2408824466710104d42a1407d44 100644
--- a/include/configs/ls1021atwr.h
+++ b/include/configs/ls1021atwr.h
@@ -266,12 +266,13 @@
  * Serial Port
  */
 #ifdef CONFIG_LPUART
-#define CONFIG_FSL_LPUART
 #define CONFIG_LPUART_32B_REG
 #else
 #define CONFIG_CONS_INDEX		1
 #define CONFIG_SYS_NS16550_SERIAL
+#ifndef CONFIG_DM_SERIAL
 #define CONFIG_SYS_NS16550_REG_SIZE	1
+#endif
 #define CONFIG_SYS_NS16550_CLK		get_serial_clock()
 #endif
 
diff --git a/include/configs/pcm052.h b/include/configs/pcm052.h
index b851bba25d90dec30d1a39d315d6e72895aa7745..891bdb0ecf15030acc763039d400412ca11a02d2 100644
--- a/include/configs/pcm052.h
+++ b/include/configs/pcm052.h
@@ -27,7 +27,6 @@
 
 #define CONFIG_BOARD_EARLY_INIT_F
 
-#define CONFIG_FSL_LPUART
 #define LPUART_BASE			UART1_BASE
 
 /* Allow to overwrite serial and ethaddr */
diff --git a/include/configs/sandbox.h b/include/configs/sandbox.h
index d3112e1760e45631c1e0b8f107ee57ea1462c340..6498981cef248715e98dabae34b1061616249229 100644
--- a/include/configs/sandbox.h
+++ b/include/configs/sandbox.h
@@ -158,24 +158,23 @@
 
 /* LCD and keyboard require SDL support */
 #ifdef CONFIG_SANDBOX_SDL
-#define CONFIG_LCD
-#define CONFIG_VIDEO_SANDBOX_SDL
 #define CONFIG_CMD_BMP
-#define CONFIG_BOARD_EARLY_INIT_F
 #define CONFIG_CONSOLE_MUX
 #define CONFIG_SYS_CONSOLE_IS_IN_ENV
 #define LCD_BPP			LCD_COLOR16
 #define CONFIG_LCD_BMP_RLE8
+#define CONFIG_VIDEO_BMP_RLE8
+#define CONFIG_SPLASH_SCREEN_ALIGN
 
 #define CONFIG_KEYBOARD
 
 #define SANDBOX_SERIAL_SETTINGS		"stdin=serial,cros-ec-keyb,usbkbd\0" \
-					"stdout=serial,lcd\0" \
-					"stderr=serial,lcd\0"
+					"stdout=serial,lcd.vidconsole\0" \
+					"stderr=serial,lcd.vidconsole\0"
 #else
 #define SANDBOX_SERIAL_SETTINGS		"stdin=serial\0" \
-					"stdout=serial,lcd\0" \
-					"stderr=serial,lcd\0"
+					"stdout=serial,lcd.vidconsole\0" \
+					"stderr=serial,lcd.vidconsole\0"
 #endif
 
 #define SANDBOX_ETH_SETTINGS		"ethaddr=00:00:11:22:33:44\0" \
diff --git a/include/configs/ti_omap5_common.h b/include/configs/ti_omap5_common.h
index 2d492f8ba7cd87e39fa811b6bcd7079ef6513b9e..d164e6abd4506019edde04d8bd63abac35dc5943 100644
--- a/include/configs/ti_omap5_common.h
+++ b/include/configs/ti_omap5_common.h
@@ -164,6 +164,7 @@
  */
 #ifdef CONFIG_SPL_BUILD
 #undef CONFIG_DM_MMC
+#undef CONFIG_TIMER
 #endif
 
 #endif /* __CONFIG_TI_OMAP5_COMMON_H */
diff --git a/include/configs/vf610twr.h b/include/configs/vf610twr.h
index 34df6f03529087bc9e7d1932d85cd9299e9e0bce..dcfafaf63119fdfe41c07da3d3354f707d02e3fe 100644
--- a/include/configs/vf610twr.h
+++ b/include/configs/vf610twr.h
@@ -34,7 +34,6 @@
 
 #define CONFIG_BOARD_EARLY_INIT_F
 
-#define CONFIG_FSL_LPUART
 #define LPUART_BASE			UART1_BASE
 
 /* Allow to overwrite serial and ethaddr */
diff --git a/include/dm/test.h b/include/dm/test.h
index a4bc5c8404aef87a6ec04a05f319d76d6f7c1eed..ca924d9237b586f9072917d980c42b1b1311d99a 100644
--- a/include/dm/test.h
+++ b/include/dm/test.h
@@ -155,6 +155,14 @@ enum {
 /* Declare a new driver model test */
 #define DM_TEST(_name, _flags)	UNIT_TEST(_name, _flags, dm_test)
 
+/* This platform data is needed in tests, so declare it here */
+struct sandbox_sdl_plat {
+	int xres;
+	int yres;
+	int bpix;
+	int rot;
+};
+
 /* Declare ping methods for the drivers */
 int test_ping(struct udevice *dev, int pingval, int *pingret);
 int testfdt_ping(struct udevice *dev, int pingval, int *pingret);
diff --git a/include/dm/uclass-id.h b/include/dm/uclass-id.h
index 27fa0b68db158f821b852c14f228fc63418bec0b..a0a3a79aac975c234a0b05e1b647d38cad3009de 100644
--- a/include/dm/uclass-id.h
+++ b/include/dm/uclass-id.h
@@ -66,7 +66,9 @@ enum uclass_id {
 	UCLASS_USB,		/* USB bus */
 	UCLASS_USB_DEV_GENERIC,	/* USB generic device */
 	UCLASS_USB_HUB,		/* USB hub */
+	UCLASS_VIDEO,		/* Video or LCD device */
 	UCLASS_VIDEO_BRIDGE,	/* Video bridge, e.g. DisplayPort to LVDS */
+	UCLASS_VIDEO_CONSOLE,	/* Text console driver for video device */
 
 	UCLASS_COUNT,
 	UCLASS_INVALID = -1,
diff --git a/include/fdtdec.h b/include/fdtdec.h
index 27b350e241279ad2906c65954e9028d390e10644..25e98c9c9e1ae2e3f0c73fd069c05374e8f54c3d 100644
--- a/include/fdtdec.h
+++ b/include/fdtdec.h
@@ -151,7 +151,6 @@ enum fdt_compat_id {
 	COMPAT_GENERIC_SPI_FLASH,	/* Generic SPI Flash chip */
 	COMPAT_MAXIM_98095_CODEC,	/* MAX98095 Codec */
 	COMPAT_SAMSUNG_EXYNOS5_I2C,	/* Exynos5 High Speed I2C Controller */
-	COMPAT_SANDBOX_LCD_SDL,		/* Sandbox LCD emulation with SDL */
 	COMPAT_SAMSUNG_EXYNOS_SYSMMU,	/* Exynos sysmmu */
 	COMPAT_INTEL_MICROCODE,		/* Intel microcode update */
 	COMPAT_MEMORY_SPD,		/* Memory SPD information */
diff --git a/include/lcd.h b/include/lcd.h
index 59202b7e59de8ddf7f4fb4321bf1886b47a7d2fd..d7651a8f0801e4c14fe6c786e2c1815a2a5afb45 100644
--- a/include/lcd.h
+++ b/include/lcd.h
@@ -18,6 +18,12 @@
 #include <asm/byteorder.h>
 #endif
 
+int bmp_display(ulong addr, int x, int y);
+struct bmp_image *gunzip_bmp(unsigned long addr, unsigned long *lenp,
+			     void **alloc_addr);
+
+#ifndef CONFIG_DM_VIDEO
+
 extern char lcd_is_enabled;
 extern int lcd_line_length;
 extern struct vidinfo panel_info;
@@ -26,10 +32,6 @@ void lcd_ctrl_init(void *lcdbase);
 void lcd_enable(void);
 void lcd_setcolreg(ushort regno, ushort red, ushort green, ushort blue);
 
-struct bmp_image *gunzip_bmp(unsigned long addr, unsigned long *lenp,
-			     void **alloc_addr);
-int bmp_display(ulong addr, int x, int y);
-
 /**
  * Set whether we need to flush the dcache when changing the LCD image. This
  * defaults to off.
@@ -209,4 +211,6 @@ void lcd_sync(void);
 #define PAGE_SIZE	4096
 #endif
 
+#endif /* !CONFIG_DM_VIDEO */
+
 #endif	/* _LCD_H_ */
diff --git a/include/timer.h b/include/timer.h
index 7fee17e3d24193c105f44f61ff2f099dc56811eb..f14725cc280b5f93a8074f329b5b4641fbb69753 100644
--- a/include/timer.h
+++ b/include/timer.h
@@ -7,6 +7,15 @@
 #ifndef _TIMER_H_
 #define _TIMER_H_
 
+/*
+ * dm_timer_init - initialize a timer for time keeping. On success
+ * initializes gd->timer so that lib/timer can use it for future
+ * referrence.
+ *
+ * @return - 0 on success or error number
+ */
+int dm_timer_init(void);
+
 /*
  * timer_conv_64 - convert 32-bit counter value to 64-bit
  *
diff --git a/include/video.h b/include/video.h
index 65e4ec1e1a3df81ed83562914b902a18530b6fe3..b20f06f335d0d93eeeea260c2cc8513201490e9f 100644
--- a/include/video.h
+++ b/include/video.h
@@ -1,14 +1,167 @@
 /*
-** MPC823 Video Controller
-** =======================
-** (C) 2000 by Paolo Scaffardi (arsenio@tin.it)
-** AIRVENT SAM s.p.a - RIMINI(ITALY)
-**
-*/
+ * Video uclass and legacy implementation
+ *
+ * Copyright (c) 2015 Google, Inc
+ *
+ * MPC823 Video Controller
+ * =======================
+ * (C) 2000 by Paolo Scaffardi (arsenio@tin.it)
+ * AIRVENT SAM s.p.a - RIMINI(ITALY)
+ *
+ */
 
 #ifndef _VIDEO_H_
 #define _VIDEO_H_
 
+#ifdef CONFIG_DM_VIDEO
+
+#include <stdio_dev.h>
+
+struct video_uc_platdata {
+	uint align;
+	uint size;
+	ulong base;
+};
+
+/*
+ * Bits per pixel selector. Each value n is such that the bits-per-pixel is
+ * 2 ^ n
+ */
+enum video_log2_bpp {
+	VIDEO_BPP1	= 0,
+	VIDEO_BPP2,
+	VIDEO_BPP4,
+	VIDEO_BPP8,
+	VIDEO_BPP16,
+	VIDEO_BPP32,
+};
+
+/*
+ * Convert enum video_log2_bpp to bytes and bits. Note we omit the outer
+ * brackets to allow multiplication by fractional pixels.
+ */
+#define VNBYTES(bpix)	(1 << (bpix)) / 8
+
+#define VNBITS(bpix)	(1 << (bpix))
+
+/**
+ * struct video_priv - Device information used by the video uclass
+ *
+ * @xsize:	Number of pixel columns (e.g. 1366)
+ * @ysize:	Number of pixels rows (e.g.. 768)
+ * @tor:	Display rotation (0=none, 1=90 degrees clockwise, etc.)
+ * @bpix:	Encoded bits per pixel
+ * @fb:		Frame buffer
+ * @fb_size:	Frame buffer size
+ * @line_length:	Length of each frame buffer line, in bytes
+ * @colour_fg:	Foreground colour (pixel value)
+ * @colour_bg:	Background colour (pixel value)
+ * @flush_dcache:	true to enable flushing of the data cache after
+ *		the LCD is updated
+ * @cmap:	Colour map for 8-bit-per-pixel displays
+ */
+struct video_priv {
+	/* Things set up by the driver: */
+	ushort xsize;
+	ushort ysize;
+	ushort rot;
+	enum video_log2_bpp bpix;
+
+	/*
+	 * Things that are private to the uclass: don't use these in the
+	 * driver
+	 */
+	void *fb;
+	int fb_size;
+	int line_length;
+	int colour_fg;
+	int colour_bg;
+	bool flush_dcache;
+	ushort *cmap;
+};
+
+/* Placeholder - there are no video operations at present */
+struct video_ops {
+};
+
+#define video_get_ops(dev)        ((struct video_ops *)(dev)->driver->ops)
+
+/**
+ * video_reserve() - Reserve frame-buffer memory for video devices
+ *
+ * Note: This function is for internal use.
+ *
+ * This uses the uclass platdata's @size and @align members to figure out
+ * a size and position for each frame buffer as part of the pre-relocation
+ * process of determining the post-relocation memory layout.
+ *
+ * gd->video_top is set to the initial value of *@addrp and gd->video_bottom
+ * is set to the final value.
+ *
+ * @addrp:	On entry, the top of available memory. On exit, the new top,
+ *		after allocating the required memory.
+ * @return 0
+ */
+int video_reserve(ulong *addrp);
+
+/**
+ * video_sync() - Sync a device's frame buffer with its hardware
+ *
+ * Some frame buffers are cached or have a secondary frame buffer. This
+ * function syncs these up so that the current contents of the U-Boot frame
+ * buffer are displayed to the user.
+ *
+ * @dev:	Device to sync
+ */
+void video_sync(struct udevice *vid);
+
+/**
+ * video_sync_all() - Sync all devices' frame buffers with there hardware
+ *
+ * This calls video_sync() on all active video devices.
+ */
+void video_sync_all(void);
+
+/**
+ * video_bmp_display() - Display a BMP file
+ *
+ * @dev:	Device to display the bitmap on
+ * @bmp_image:	Address of bitmap image to display
+ * @x:		X position in pixels from the left
+ * @y:		Y position in pixels from the top
+ * @align:	true to adjust the coordinates to centre the image. If false
+ *		the coordinates are used as is. If true:
+ *
+ *		- if a coordinate is 0x7fff then the image will be centred in
+ *		  that direction
+ *		- if a coordinate is -ve then it will be offset to the
+ *		  left/top of the centre by that many pixels
+ *		- if a coordinate is positive it will be used unchnaged.
+ * @return 0 if OK, -ve on error
+ */
+int video_bmp_display(struct udevice *dev, ulong bmp_image, int x, int y,
+		      bool align);
+
+/**
+ * video_get_xsize() - Get the width of the display in pixels
+ *
+ * @dev:	Device to check
+ * @return device frame buffer width in pixels
+ */
+int video_get_xsize(struct udevice *dev);
+
+/**
+ * video_get_ysize() - Get the height of the display in pixels
+ *
+ * @dev:	Device to check
+ * @return device frame buffer height in pixels
+ */
+int video_get_ysize(struct udevice *dev);
+
+#endif /* CONFIG_DM_VIDEO */
+
+#ifndef CONFIG_DM_VIDEO
+
 /* Video functions */
 
 struct stdio_dev;
@@ -73,4 +226,7 @@ int kwh043st20_f01_spi_startup(unsigned int bus, unsigned int cs,
 int lg4573_spi_startup(unsigned int bus, unsigned int cs,
 	unsigned int max_hz, unsigned int spi_mode);
 #endif
+
+#endif /* CONFIG_DM_VIDEO */
+
 #endif
diff --git a/include/video_console.h b/include/video_console.h
new file mode 100644
index 0000000000000000000000000000000000000000..c0fc79273a5b41be2a6d7d39eaca6e2b64eed9d8
--- /dev/null
+++ b/include/video_console.h
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2015 Google, Inc
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#ifndef __video_console_h
+#define __video_console_h
+
+/**
+ * struct vidconsole_priv - uclass-private data about a console device
+ *
+ * @sdev:	stdio device, acting as an output sink
+ * @curr_col:	Current text column (0=left)
+ * @curr_row:	Current row (0=top)
+ * @rows:	Number of text rows
+ * @cols:	Number of text columns
+ */
+struct vidconsole_priv {
+	struct stdio_dev sdev;
+	int curr_col;
+	int curr_row;
+	int rows;
+	int cols;
+};
+
+/**
+ * struct vidconsole_ops - Video console operations
+ *
+ * These operations work on either an absolute console position (measured
+ * in pixels) or a text row number (measured in rows, where each row consists
+ * of an entire line of text - typically 16 pixels).
+ */
+struct vidconsole_ops {
+	/**
+	 * putc_xy() - write a single character to a position
+	 *
+	 * @dev:	Device to write to
+	 * @x:		Pixel X position (0=left-most pixel)
+	 * @y:		Pixel Y position (0=top-most pixel)
+	 * @ch:		Character to write
+	 * @return 0 if OK, -ve on error
+	 */
+	int (*putc_xy)(struct udevice *dev, uint x, uint y, char ch);
+
+	/**
+	 * move_rows() - Move text rows from one place to another
+	 *
+	 * @dev:	Device to adjust
+	 * @rowdst:	Destination text row (0=top)
+	 * @rowsrc:	Source start text row
+	 * @count:	Number of text rows to move
+	 * @return 0 if OK, -ve on error
+	 */
+	int (*move_rows)(struct udevice *dev, uint rowdst, uint rowsrc,
+			  uint count);
+
+	/**
+	 * set_row() - Set the colour of a text row
+	 *
+	 * Every pixel contained within the text row is adjusted
+	 *
+	 * @dev:	Device to adjust
+	 * @row:	Text row to adjust (0=top)
+	 * @clr:	Raw colour (pixel value) to write to each pixel
+	 * @return 0 if OK, -ve on error
+	 */
+	int (*set_row)(struct udevice *dev, uint row, int clr);
+};
+
+/* Get a pointer to the driver operations for a video console device */
+#define vidconsole_get_ops(dev)  ((struct vidconsole_ops *)(dev)->driver->ops)
+
+/**
+ * vidconsole_putc_xy() - write a single character to a position
+ *
+ * @dev:	Device to write to
+ * @x:		Pixel X position (0=left-most pixel)
+ * @y:		Pixel Y position (0=top-most pixel)
+ * @ch:		Character to write
+ * @return 0 if OK, -ve on error
+ */
+int vidconsole_putc_xy(struct udevice *dev, uint x, uint y, char ch);
+
+/**
+ * vidconsole_move_rows() - Move text rows from one place to another
+ *
+ * @dev:	Device to adjust
+ * @rowdst:	Destination text row (0=top)
+ * @rowsrc:	Source start text row
+ * @count:	Number of text rows to move
+ * @return 0 if OK, -ve on error
+ */
+int vidconsole_move_rows(struct udevice *dev, uint rowdst, uint rowsrc,
+			 uint count);
+
+/**
+ * vidconsole_set_row() - Set the colour of a text row
+ *
+ * Every pixel contained within the text row is adjusted
+ *
+ * @dev:	Device to adjust
+ * @row:	Text row to adjust (0=top)
+ * @clr:	Raw colour (pixel value) to write to each pixel
+ * @return 0 if OK, -ve on error
+ */
+int vidconsole_set_row(struct udevice *dev, uint row, int clr);
+
+/**
+ * vidconsole_put_char() - Output a character to the current console position
+ *
+ * Outputs a character to the console and advances the cursor. This function
+ * handles wrapping to new lines and scrolling the console. Special
+ * characters are handled also: \n, \r, \b and \t.
+ *
+ * The device always starts with the cursor at position 0,0 (top left). It
+ * can be adjusted manually using vidconsole_position_cursor().
+ *
+ * @dev:	Device to adjust
+ * @ch:		Character to write
+ * @return 0 if OK, -ve on error
+ */
+int vidconsole_put_char(struct udevice *dev, char ch);
+
+/**
+ * vidconsole_position_cursor() - Move the text cursor
+ *
+ * @dev:	Device to adjust
+ * @col:	New cursor text column
+ * @row:	New cursor text row
+ * @return 0 if OK, -ve on error
+ */
+void vidconsole_position_cursor(struct udevice *dev, unsigned col,
+				unsigned row);
+
+#endif
diff --git a/lib/bzip2/Makefile b/lib/bzip2/Makefile
index f0b81ad2c2b0db1963286e35be0810c6ff8dd16a..585d776ba8a2621f5d6be10f35a1ace578209a8e 100644
--- a/lib/bzip2/Makefile
+++ b/lib/bzip2/Makefile
@@ -4,3 +4,4 @@
 
 obj-y += bzlib.o bzlib_crctable.o bzlib_decompress.o \
 	bzlib_randtable.o bzlib_huffman.o
+obj-$(CONFIG_SANDBOX) += bzlib_compress.o bzlib_blocksort.o
diff --git a/lib/bzip2/bzlib_blocksort.c b/lib/bzip2/bzlib_blocksort.c
new file mode 100644
index 0000000000000000000000000000000000000000..278552150222908beeb7e96e87b69890400d619e
--- /dev/null
+++ b/lib/bzip2/bzlib_blocksort.c
@@ -0,0 +1,1134 @@
+
+/*-------------------------------------------------------------*/
+/*--- Block sorting machinery                               ---*/
+/*---                                           blocksort.c ---*/
+/*-------------------------------------------------------------*/
+
+/*--
+  This file is a part of bzip2 and/or libbzip2, a program and
+  library for lossless, block-sorting data compression.
+
+  Copyright (C) 1996-2002 Julian R Seward.  All rights reserved.
+
+  Redistribution and use in source and binary forms, with or without
+  modification, are permitted provided that the following conditions
+  are met:
+
+  1. Redistributions of source code must retain the above copyright
+     notice, this list of conditions and the following disclaimer.
+
+  2. The origin of this software must not be misrepresented; you must
+     not claim that you wrote the original software.  If you use this
+     software in a product, an acknowledgment in the product
+     documentation would be appreciated but is not required.
+
+  3. Altered source versions must be plainly marked as such, and must
+     not be misrepresented as being the original software.
+
+  4. The name of the author may not be used to endorse or promote
+     products derived from this software without specific prior written
+     permission.
+
+  THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
+  OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+  ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+  GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+  Julian Seward, Cambridge, UK.
+  jseward@acm.org
+  bzip2/libbzip2 version 1.0.6 of 6 September 2010
+  Copyright (C) 1996-2010 Julian Seward <jseward@bzip.org>
+
+  This program is based on (at least) the work of:
+     Mike Burrows
+     David Wheeler
+     Peter Fenwick
+     Alistair Moffat
+     Radford Neal
+     Ian H. Witten
+     Robert Sedgewick
+     Jon L. Bentley
+
+  For more information on these sources, see the manual.
+--*/
+
+#include "bzlib_private.h"
+
+/*---------------------------------------------*/
+/*--- Fallback O(N log(N)^2) sorting        ---*/
+/*--- algorithm, for repetitive blocks      ---*/
+/*---------------------------------------------*/
+
+/*---------------------------------------------*/
+static 
+__inline__
+void fallbackSimpleSort ( UInt32* fmap, 
+                          UInt32* eclass, 
+                          Int32   lo, 
+                          Int32   hi )
+{
+   Int32 i, j, tmp;
+   UInt32 ec_tmp;
+
+   if (lo == hi) return;
+
+   if (hi - lo > 3) {
+      for ( i = hi-4; i >= lo; i-- ) {
+         tmp = fmap[i];
+         ec_tmp = eclass[tmp];
+         for ( j = i+4; j <= hi && ec_tmp > eclass[fmap[j]]; j += 4 )
+            fmap[j-4] = fmap[j];
+         fmap[j-4] = tmp;
+      }
+   }
+
+   for ( i = hi-1; i >= lo; i-- ) {
+      tmp = fmap[i];
+      ec_tmp = eclass[tmp];
+      for ( j = i+1; j <= hi && ec_tmp > eclass[fmap[j]]; j++ )
+         fmap[j-1] = fmap[j];
+      fmap[j-1] = tmp;
+   }
+}
+
+
+/*---------------------------------------------*/
+#define fswap(zz1, zz2) \
+   { Int32 zztmp = zz1; zz1 = zz2; zz2 = zztmp; }
+
+#define fvswap(zzp1, zzp2, zzn)       \
+{                                     \
+   Int32 yyp1 = (zzp1);               \
+   Int32 yyp2 = (zzp2);               \
+   Int32 yyn  = (zzn);                \
+   while (yyn > 0) {                  \
+      fswap(fmap[yyp1], fmap[yyp2]);  \
+      yyp1++; yyp2++; yyn--;          \
+   }                                  \
+}
+
+
+#define fmin(a,b) ((a) < (b)) ? (a) : (b)
+
+#define fpush(lz,hz) { stackLo[sp] = lz; \
+                       stackHi[sp] = hz; \
+                       sp++; }
+
+#define fpop(lz,hz) { sp--;              \
+                      lz = stackLo[sp];  \
+                      hz = stackHi[sp]; }
+
+#define FALLBACK_QSORT_SMALL_THRESH 10
+#define FALLBACK_QSORT_STACK_SIZE   100
+
+
+static
+void fallbackQSort3 ( UInt32* fmap, 
+                      UInt32* eclass,
+                      Int32   loSt, 
+                      Int32   hiSt )
+{
+   Int32 unLo, unHi, ltLo, gtHi, n, m;
+   Int32 sp, lo, hi;
+   UInt32 med, r, r3;
+   Int32 stackLo[FALLBACK_QSORT_STACK_SIZE];
+   Int32 stackHi[FALLBACK_QSORT_STACK_SIZE];
+
+   r = 0;
+
+   sp = 0;
+   fpush ( loSt, hiSt );
+
+   while (sp > 0) {
+
+      AssertH ( sp < FALLBACK_QSORT_STACK_SIZE - 1, 1004 );
+
+      fpop ( lo, hi );
+      if (hi - lo < FALLBACK_QSORT_SMALL_THRESH) {
+         fallbackSimpleSort ( fmap, eclass, lo, hi );
+         continue;
+      }
+
+      /* Random partitioning.  Median of 3 sometimes fails to
+         avoid bad cases.  Median of 9 seems to help but 
+         looks rather expensive.  This too seems to work but
+         is cheaper.  Guidance for the magic constants 
+         7621 and 32768 is taken from Sedgewick's algorithms
+         book, chapter 35.
+      */
+      r = ((r * 7621) + 1) % 32768;
+      r3 = r % 3;
+      if (r3 == 0) med = eclass[fmap[lo]]; else
+      if (r3 == 1) med = eclass[fmap[(lo+hi)>>1]]; else
+                   med = eclass[fmap[hi]];
+
+      unLo = ltLo = lo;
+      unHi = gtHi = hi;
+
+      while (1) {
+         while (1) {
+            if (unLo > unHi) break;
+            n = (Int32)eclass[fmap[unLo]] - (Int32)med;
+            if (n == 0) { 
+               fswap(fmap[unLo], fmap[ltLo]); 
+               ltLo++; unLo++; 
+               continue; 
+            };
+            if (n > 0) break;
+            unLo++;
+         }
+         while (1) {
+            if (unLo > unHi) break;
+            n = (Int32)eclass[fmap[unHi]] - (Int32)med;
+            if (n == 0) { 
+               fswap(fmap[unHi], fmap[gtHi]); 
+               gtHi--; unHi--; 
+               continue; 
+            };
+            if (n < 0) break;
+            unHi--;
+         }
+         if (unLo > unHi) break;
+         fswap(fmap[unLo], fmap[unHi]); unLo++; unHi--;
+      }
+
+      AssertD ( unHi == unLo-1, "fallbackQSort3(2)" );
+
+      if (gtHi < ltLo) continue;
+
+      n = fmin(ltLo-lo, unLo-ltLo); fvswap(lo, unLo-n, n);
+      m = fmin(hi-gtHi, gtHi-unHi); fvswap(unLo, hi-m+1, m);
+
+      n = lo + unLo - ltLo - 1;
+      m = hi - (gtHi - unHi) + 1;
+
+      if (n - lo > hi - m) {
+         fpush ( lo, n );
+         fpush ( m, hi );
+      } else {
+         fpush ( m, hi );
+         fpush ( lo, n );
+      }
+   }
+}
+
+#undef fmin
+#undef fpush
+#undef fpop
+#undef fswap
+#undef fvswap
+#undef FALLBACK_QSORT_SMALL_THRESH
+#undef FALLBACK_QSORT_STACK_SIZE
+
+
+/*---------------------------------------------*/
+/* Pre:
+      nblock > 0
+      eclass exists for [0 .. nblock-1]
+      ((UChar*)eclass) [0 .. nblock-1] holds block
+      ptr exists for [0 .. nblock-1]
+
+   Post:
+      ((UChar*)eclass) [0 .. nblock-1] holds block
+      All other areas of eclass destroyed
+      fmap [0 .. nblock-1] holds sorted order
+      bhtab [ 0 .. 2+(nblock/32) ] destroyed
+*/
+
+#define       SET_BH(zz)  bhtab[(zz) >> 5] |= (1 << ((zz) & 31))
+#define     CLEAR_BH(zz)  bhtab[(zz) >> 5] &= ~(1 << ((zz) & 31))
+#define     ISSET_BH(zz)  (bhtab[(zz) >> 5] & (1 << ((zz) & 31)))
+#define      WORD_BH(zz)  bhtab[(zz) >> 5]
+#define UNALIGNED_BH(zz)  ((zz) & 0x01f)
+
+static
+void fallbackSort ( UInt32* fmap, 
+                    UInt32* eclass, 
+                    UInt32* bhtab,
+                    Int32   nblock,
+                    Int32   verb )
+{
+   Int32 ftab[257];
+   Int32 ftabCopy[256];
+   Int32 H, i, j, k, l, r, cc, cc1;
+   Int32 nNotDone;
+   Int32 nBhtab;
+   UChar* eclass8 = (UChar*)eclass;
+
+   /*--
+      Initial 1-char radix sort to generate
+      initial fmap and initial BH bits.
+   --*/
+   if (verb >= 4)
+      VPrintf0 ( "        bucket sorting ...\n" );
+   for (i = 0; i < 257;    i++) ftab[i] = 0;
+   for (i = 0; i < nblock; i++) ftab[eclass8[i]]++;
+   for (i = 0; i < 256;    i++) ftabCopy[i] = ftab[i];
+   for (i = 1; i < 257;    i++) ftab[i] += ftab[i-1];
+
+   for (i = 0; i < nblock; i++) {
+      j = eclass8[i];
+      k = ftab[j] - 1;
+      ftab[j] = k;
+      fmap[k] = i;
+   }
+
+   nBhtab = 2 + (nblock / 32);
+   for (i = 0; i < nBhtab; i++) bhtab[i] = 0;
+   for (i = 0; i < 256; i++) SET_BH(ftab[i]);
+
+   /*--
+      Inductively refine the buckets.  Kind-of an
+      "exponential radix sort" (!), inspired by the
+      Manber-Myers suffix array construction algorithm.
+   --*/
+
+   /*-- set sentinel bits for block-end detection --*/
+   for (i = 0; i < 32; i++) { 
+      SET_BH(nblock + 2*i);
+      CLEAR_BH(nblock + 2*i + 1);
+   }
+
+   /*-- the log(N) loop --*/
+   H = 1;
+   while (1) {
+
+      if (verb >= 4) 
+         VPrintf1 ( "        depth %6d has ", H );
+
+      j = 0;
+      for (i = 0; i < nblock; i++) {
+         if (ISSET_BH(i)) j = i;
+         k = fmap[i] - H; if (k < 0) k += nblock;
+         eclass[k] = j;
+      }
+
+      nNotDone = 0;
+      r = -1;
+      while (1) {
+
+	 /*-- find the next non-singleton bucket --*/
+         k = r + 1;
+         while (ISSET_BH(k) && UNALIGNED_BH(k)) k++;
+         if (ISSET_BH(k)) {
+            while (WORD_BH(k) == 0xffffffff) k += 32;
+            while (ISSET_BH(k)) k++;
+         }
+         l = k - 1;
+         if (l >= nblock) break;
+         while (!ISSET_BH(k) && UNALIGNED_BH(k)) k++;
+         if (!ISSET_BH(k)) {
+            while (WORD_BH(k) == 0x00000000) k += 32;
+            while (!ISSET_BH(k)) k++;
+         }
+         r = k - 1;
+         if (r >= nblock) break;
+
+         /*-- now [l, r] bracket current bucket --*/
+         if (r > l) {
+            nNotDone += (r - l + 1);
+            fallbackQSort3 ( fmap, eclass, l, r );
+
+            /*-- scan bucket and generate header bits-- */
+            cc = -1;
+            for (i = l; i <= r; i++) {
+               cc1 = eclass[fmap[i]];
+               if (cc != cc1) { SET_BH(i); cc = cc1; };
+            }
+         }
+      }
+
+      if (verb >= 4) 
+         VPrintf1 ( "%6d unresolved strings\n", nNotDone );
+
+      H *= 2;
+      if (H > nblock || nNotDone == 0) break;
+   }
+
+   /*-- 
+      Reconstruct the original block in
+      eclass8 [0 .. nblock-1], since the
+      previous phase destroyed it.
+   --*/
+   if (verb >= 4)
+      VPrintf0 ( "        reconstructing block ...\n" );
+   j = 0;
+   for (i = 0; i < nblock; i++) {
+      while (ftabCopy[j] == 0) j++;
+      ftabCopy[j]--;
+      eclass8[fmap[i]] = (UChar)j;
+   }
+   AssertH ( j < 256, 1005 );
+}
+
+#undef       SET_BH
+#undef     CLEAR_BH
+#undef     ISSET_BH
+#undef      WORD_BH
+#undef UNALIGNED_BH
+
+
+/*---------------------------------------------*/
+/*--- The main, O(N^2 log(N)) sorting       ---*/
+/*--- algorithm.  Faster for "normal"       ---*/
+/*--- non-repetitive blocks.                ---*/
+/*---------------------------------------------*/
+
+/*---------------------------------------------*/
+static
+__inline__
+Bool mainGtU ( UInt32  i1, 
+               UInt32  i2,
+               UChar*  block, 
+               UInt16* quadrant,
+               UInt32  nblock,
+               Int32*  budget )
+{
+   Int32  k;
+   UChar  c1, c2;
+   UInt16 s1, s2;
+
+   AssertD ( i1 != i2, "mainGtU" );
+   /* 1 */
+   c1 = block[i1]; c2 = block[i2];
+   if (c1 != c2) return (c1 > c2);
+   i1++; i2++;
+   /* 2 */
+   c1 = block[i1]; c2 = block[i2];
+   if (c1 != c2) return (c1 > c2);
+   i1++; i2++;
+   /* 3 */
+   c1 = block[i1]; c2 = block[i2];
+   if (c1 != c2) return (c1 > c2);
+   i1++; i2++;
+   /* 4 */
+   c1 = block[i1]; c2 = block[i2];
+   if (c1 != c2) return (c1 > c2);
+   i1++; i2++;
+   /* 5 */
+   c1 = block[i1]; c2 = block[i2];
+   if (c1 != c2) return (c1 > c2);
+   i1++; i2++;
+   /* 6 */
+   c1 = block[i1]; c2 = block[i2];
+   if (c1 != c2) return (c1 > c2);
+   i1++; i2++;
+   /* 7 */
+   c1 = block[i1]; c2 = block[i2];
+   if (c1 != c2) return (c1 > c2);
+   i1++; i2++;
+   /* 8 */
+   c1 = block[i1]; c2 = block[i2];
+   if (c1 != c2) return (c1 > c2);
+   i1++; i2++;
+   /* 9 */
+   c1 = block[i1]; c2 = block[i2];
+   if (c1 != c2) return (c1 > c2);
+   i1++; i2++;
+   /* 10 */
+   c1 = block[i1]; c2 = block[i2];
+   if (c1 != c2) return (c1 > c2);
+   i1++; i2++;
+   /* 11 */
+   c1 = block[i1]; c2 = block[i2];
+   if (c1 != c2) return (c1 > c2);
+   i1++; i2++;
+   /* 12 */
+   c1 = block[i1]; c2 = block[i2];
+   if (c1 != c2) return (c1 > c2);
+   i1++; i2++;
+
+   k = nblock + 8;
+
+   do {
+      /* 1 */
+      c1 = block[i1]; c2 = block[i2];
+      if (c1 != c2) return (c1 > c2);
+      s1 = quadrant[i1]; s2 = quadrant[i2];
+      if (s1 != s2) return (s1 > s2);
+      i1++; i2++;
+      /* 2 */
+      c1 = block[i1]; c2 = block[i2];
+      if (c1 != c2) return (c1 > c2);
+      s1 = quadrant[i1]; s2 = quadrant[i2];
+      if (s1 != s2) return (s1 > s2);
+      i1++; i2++;
+      /* 3 */
+      c1 = block[i1]; c2 = block[i2];
+      if (c1 != c2) return (c1 > c2);
+      s1 = quadrant[i1]; s2 = quadrant[i2];
+      if (s1 != s2) return (s1 > s2);
+      i1++; i2++;
+      /* 4 */
+      c1 = block[i1]; c2 = block[i2];
+      if (c1 != c2) return (c1 > c2);
+      s1 = quadrant[i1]; s2 = quadrant[i2];
+      if (s1 != s2) return (s1 > s2);
+      i1++; i2++;
+      /* 5 */
+      c1 = block[i1]; c2 = block[i2];
+      if (c1 != c2) return (c1 > c2);
+      s1 = quadrant[i1]; s2 = quadrant[i2];
+      if (s1 != s2) return (s1 > s2);
+      i1++; i2++;
+      /* 6 */
+      c1 = block[i1]; c2 = block[i2];
+      if (c1 != c2) return (c1 > c2);
+      s1 = quadrant[i1]; s2 = quadrant[i2];
+      if (s1 != s2) return (s1 > s2);
+      i1++; i2++;
+      /* 7 */
+      c1 = block[i1]; c2 = block[i2];
+      if (c1 != c2) return (c1 > c2);
+      s1 = quadrant[i1]; s2 = quadrant[i2];
+      if (s1 != s2) return (s1 > s2);
+      i1++; i2++;
+      /* 8 */
+      c1 = block[i1]; c2 = block[i2];
+      if (c1 != c2) return (c1 > c2);
+      s1 = quadrant[i1]; s2 = quadrant[i2];
+      if (s1 != s2) return (s1 > s2);
+      i1++; i2++;
+
+      if (i1 >= nblock) i1 -= nblock;
+      if (i2 >= nblock) i2 -= nblock;
+
+      k -= 8;
+      (*budget)--;
+   }
+      while (k >= 0);
+
+   return False;
+}
+
+
+/*---------------------------------------------*/
+/*--
+   Knuth's increments seem to work better
+   than Incerpi-Sedgewick here.  Possibly
+   because the number of elems to sort is
+   usually small, typically <= 20.
+--*/
+static
+Int32 incs[14] = { 1, 4, 13, 40, 121, 364, 1093, 3280,
+                   9841, 29524, 88573, 265720,
+                   797161, 2391484 };
+
+static
+void mainSimpleSort ( UInt32* ptr,
+                      UChar*  block,
+                      UInt16* quadrant,
+                      Int32   nblock,
+                      Int32   lo, 
+                      Int32   hi, 
+                      Int32   d,
+                      Int32*  budget )
+{
+   Int32 i, j, h, bigN, hp;
+   UInt32 v;
+
+   bigN = hi - lo + 1;
+   if (bigN < 2) return;
+
+   hp = 0;
+   while (incs[hp] < bigN) hp++;
+   hp--;
+
+   for (; hp >= 0; hp--) {
+      h = incs[hp];
+
+      i = lo + h;
+      while (True) {
+
+         /*-- copy 1 --*/
+         if (i > hi) break;
+         v = ptr[i];
+         j = i;
+         while ( mainGtU ( 
+                    ptr[j-h]+d, v+d, block, quadrant, nblock, budget 
+                 ) ) {
+            ptr[j] = ptr[j-h];
+            j = j - h;
+            if (j <= (lo + h - 1)) break;
+         }
+         ptr[j] = v;
+         i++;
+
+         /*-- copy 2 --*/
+         if (i > hi) break;
+         v = ptr[i];
+         j = i;
+         while ( mainGtU ( 
+                    ptr[j-h]+d, v+d, block, quadrant, nblock, budget 
+                 ) ) {
+            ptr[j] = ptr[j-h];
+            j = j - h;
+            if (j <= (lo + h - 1)) break;
+         }
+         ptr[j] = v;
+         i++;
+
+         /*-- copy 3 --*/
+         if (i > hi) break;
+         v = ptr[i];
+         j = i;
+         while ( mainGtU ( 
+                    ptr[j-h]+d, v+d, block, quadrant, nblock, budget 
+                 ) ) {
+            ptr[j] = ptr[j-h];
+            j = j - h;
+            if (j <= (lo + h - 1)) break;
+         }
+         ptr[j] = v;
+         i++;
+
+         if (*budget < 0) return;
+      }
+   }
+}
+
+
+/*---------------------------------------------*/
+/*--
+   The following is an implementation of
+   an elegant 3-way quicksort for strings,
+   described in a paper "Fast Algorithms for
+   Sorting and Searching Strings", by Robert
+   Sedgewick and Jon L. Bentley.
+--*/
+
+#define mswap(zz1, zz2) \
+   { Int32 zztmp = zz1; zz1 = zz2; zz2 = zztmp; }
+
+#define mvswap(zzp1, zzp2, zzn)       \
+{                                     \
+   Int32 yyp1 = (zzp1);               \
+   Int32 yyp2 = (zzp2);               \
+   Int32 yyn  = (zzn);                \
+   while (yyn > 0) {                  \
+      mswap(ptr[yyp1], ptr[yyp2]);    \
+      yyp1++; yyp2++; yyn--;          \
+   }                                  \
+}
+
+static 
+__inline__
+UChar mmed3 ( UChar a, UChar b, UChar c )
+{
+   UChar t;
+   if (a > b) { t = a; a = b; b = t; };
+   if (b > c) { 
+      b = c;
+      if (a > b) b = a;
+   }
+   return b;
+}
+
+#define mmin(a,b) ((a) < (b)) ? (a) : (b)
+
+#define mpush(lz,hz,dz) { stackLo[sp] = lz; \
+                          stackHi[sp] = hz; \
+                          stackD [sp] = dz; \
+                          sp++; }
+
+#define mpop(lz,hz,dz) { sp--;             \
+                         lz = stackLo[sp]; \
+                         hz = stackHi[sp]; \
+                         dz = stackD [sp]; }
+
+
+#define mnextsize(az) (nextHi[az]-nextLo[az])
+
+#define mnextswap(az,bz)                                        \
+   { Int32 tz;                                                  \
+     tz = nextLo[az]; nextLo[az] = nextLo[bz]; nextLo[bz] = tz; \
+     tz = nextHi[az]; nextHi[az] = nextHi[bz]; nextHi[bz] = tz; \
+     tz = nextD [az]; nextD [az] = nextD [bz]; nextD [bz] = tz; }
+
+
+#define MAIN_QSORT_SMALL_THRESH 20
+#define MAIN_QSORT_DEPTH_THRESH (BZ_N_RADIX + BZ_N_QSORT)
+#define MAIN_QSORT_STACK_SIZE 100
+
+static
+void mainQSort3 ( UInt32* ptr,
+                  UChar*  block,
+                  UInt16* quadrant,
+                  Int32   nblock,
+                  Int32   loSt, 
+                  Int32   hiSt, 
+                  Int32   dSt,
+                  Int32*  budget )
+{
+   Int32 unLo, unHi, ltLo, gtHi, n, m, med;
+   Int32 sp, lo, hi, d;
+
+   Int32 stackLo[MAIN_QSORT_STACK_SIZE];
+   Int32 stackHi[MAIN_QSORT_STACK_SIZE];
+   Int32 stackD [MAIN_QSORT_STACK_SIZE];
+
+   Int32 nextLo[3];
+   Int32 nextHi[3];
+   Int32 nextD [3];
+
+   sp = 0;
+   mpush ( loSt, hiSt, dSt );
+
+   while (sp > 0) {
+
+      AssertH ( sp < MAIN_QSORT_STACK_SIZE - 2, 1001 );
+
+      mpop ( lo, hi, d );
+      if (hi - lo < MAIN_QSORT_SMALL_THRESH || 
+          d > MAIN_QSORT_DEPTH_THRESH) {
+         mainSimpleSort ( ptr, block, quadrant, nblock, lo, hi, d, budget );
+         if (*budget < 0) return;
+         continue;
+      }
+
+      med = (Int32) 
+            mmed3 ( block[ptr[ lo         ]+d],
+                    block[ptr[ hi         ]+d],
+                    block[ptr[ (lo+hi)>>1 ]+d] );
+
+      unLo = ltLo = lo;
+      unHi = gtHi = hi;
+
+      while (True) {
+         while (True) {
+            if (unLo > unHi) break;
+            n = ((Int32)block[ptr[unLo]+d]) - med;
+            if (n == 0) { 
+               mswap(ptr[unLo], ptr[ltLo]); 
+               ltLo++; unLo++; continue; 
+            };
+            if (n >  0) break;
+            unLo++;
+         }
+         while (True) {
+            if (unLo > unHi) break;
+            n = ((Int32)block[ptr[unHi]+d]) - med;
+            if (n == 0) { 
+               mswap(ptr[unHi], ptr[gtHi]); 
+               gtHi--; unHi--; continue; 
+            };
+            if (n <  0) break;
+            unHi--;
+         }
+         if (unLo > unHi) break;
+         mswap(ptr[unLo], ptr[unHi]); unLo++; unHi--;
+      }
+
+      AssertD ( unHi == unLo-1, "mainQSort3(2)" );
+
+      if (gtHi < ltLo) {
+         mpush(lo, hi, d+1 );
+         continue;
+      }
+
+      n = mmin(ltLo-lo, unLo-ltLo); mvswap(lo, unLo-n, n);
+      m = mmin(hi-gtHi, gtHi-unHi); mvswap(unLo, hi-m+1, m);
+
+      n = lo + unLo - ltLo - 1;
+      m = hi - (gtHi - unHi) + 1;
+
+      nextLo[0] = lo;  nextHi[0] = n;   nextD[0] = d;
+      nextLo[1] = m;   nextHi[1] = hi;  nextD[1] = d;
+      nextLo[2] = n+1; nextHi[2] = m-1; nextD[2] = d+1;
+
+      if (mnextsize(0) < mnextsize(1)) mnextswap(0,1);
+      if (mnextsize(1) < mnextsize(2)) mnextswap(1,2);
+      if (mnextsize(0) < mnextsize(1)) mnextswap(0,1);
+
+      AssertD (mnextsize(0) >= mnextsize(1), "mainQSort3(8)" );
+      AssertD (mnextsize(1) >= mnextsize(2), "mainQSort3(9)" );
+
+      mpush (nextLo[0], nextHi[0], nextD[0]);
+      mpush (nextLo[1], nextHi[1], nextD[1]);
+      mpush (nextLo[2], nextHi[2], nextD[2]);
+   }
+}
+
+#undef mswap
+#undef mvswap
+#undef mpush
+#undef mpop
+#undef mmin
+#undef mnextsize
+#undef mnextswap
+#undef MAIN_QSORT_SMALL_THRESH
+#undef MAIN_QSORT_DEPTH_THRESH
+#undef MAIN_QSORT_STACK_SIZE
+
+
+/*---------------------------------------------*/
+/* Pre:
+      nblock > N_OVERSHOOT
+      block32 exists for [0 .. nblock-1 +N_OVERSHOOT]
+      ((UChar*)block32) [0 .. nblock-1] holds block
+      ptr exists for [0 .. nblock-1]
+
+   Post:
+      ((UChar*)block32) [0 .. nblock-1] holds block
+      All other areas of block32 destroyed
+      ftab [0 .. 65536 ] destroyed
+      ptr [0 .. nblock-1] holds sorted order
+      if (*budget < 0), sorting was abandoned
+*/
+
+#define BIGFREQ(b) (ftab[((b)+1) << 8] - ftab[(b) << 8])
+#define SETMASK (1 << 21)
+#define CLEARMASK (~(SETMASK))
+
+static
+void mainSort ( UInt32* ptr, 
+                UChar*  block,
+                UInt16* quadrant, 
+                UInt32* ftab,
+                Int32   nblock,
+                Int32   verb,
+                Int32*  budget )
+{
+   Int32  i, j, k, ss, sb;
+   Int32  runningOrder[256];
+   Bool   bigDone[256];
+   Int32  copyStart[256];
+   Int32  copyEnd  [256];
+   UChar  c1;
+   Int32  numQSorted;
+   UInt16 s;
+   if (verb >= 4) VPrintf0 ( "        main sort initialise ...\n" );
+
+   /*-- set up the 2-byte frequency table --*/
+   for (i = 65536; i >= 0; i--) ftab[i] = 0;
+
+   j = block[0] << 8;
+   i = nblock-1;
+   for (; i >= 3; i -= 4) {
+      quadrant[i] = 0;
+      j = (j >> 8) | ( ((UInt16)block[i]) << 8);
+      ftab[j]++;
+      quadrant[i-1] = 0;
+      j = (j >> 8) | ( ((UInt16)block[i-1]) << 8);
+      ftab[j]++;
+      quadrant[i-2] = 0;
+      j = (j >> 8) | ( ((UInt16)block[i-2]) << 8);
+      ftab[j]++;
+      quadrant[i-3] = 0;
+      j = (j >> 8) | ( ((UInt16)block[i-3]) << 8);
+      ftab[j]++;
+   }
+   for (; i >= 0; i--) {
+      quadrant[i] = 0;
+      j = (j >> 8) | ( ((UInt16)block[i]) << 8);
+      ftab[j]++;
+   }
+
+   /*-- (emphasises close relationship of block & quadrant) --*/
+   for (i = 0; i < BZ_N_OVERSHOOT; i++) {
+      block   [nblock+i] = block[i];
+      quadrant[nblock+i] = 0;
+   }
+
+   if (verb >= 4) VPrintf0 ( "        bucket sorting ...\n" );
+
+   /*-- Complete the initial radix sort --*/
+   for (i = 1; i <= 65536; i++) ftab[i] += ftab[i-1];
+
+   s = block[0] << 8;
+   i = nblock-1;
+   for (; i >= 3; i -= 4) {
+      s = (s >> 8) | (block[i] << 8);
+      j = ftab[s] -1;
+      ftab[s] = j;
+      ptr[j] = i;
+      s = (s >> 8) | (block[i-1] << 8);
+      j = ftab[s] -1;
+      ftab[s] = j;
+      ptr[j] = i-1;
+      s = (s >> 8) | (block[i-2] << 8);
+      j = ftab[s] -1;
+      ftab[s] = j;
+      ptr[j] = i-2;
+      s = (s >> 8) | (block[i-3] << 8);
+      j = ftab[s] -1;
+      ftab[s] = j;
+      ptr[j] = i-3;
+   }
+   for (; i >= 0; i--) {
+      s = (s >> 8) | (block[i] << 8);
+      j = ftab[s] -1;
+      ftab[s] = j;
+      ptr[j] = i;
+   }
+
+   /*--
+      Now ftab contains the first loc of every small bucket.
+      Calculate the running order, from smallest to largest
+      big bucket.
+   --*/
+   for (i = 0; i <= 255; i++) {
+      bigDone     [i] = False;
+      runningOrder[i] = i;
+   }
+
+   {
+      Int32 vv;
+      Int32 h = 1;
+      do h = 3 * h + 1; while (h <= 256);
+      do {
+         h = h / 3;
+         for (i = h; i <= 255; i++) {
+            vv = runningOrder[i];
+            j = i;
+            while ( BIGFREQ(runningOrder[j-h]) > BIGFREQ(vv) ) {
+               runningOrder[j] = runningOrder[j-h];
+               j = j - h;
+               if (j <= (h - 1)) goto zero;
+            }
+            zero:
+            runningOrder[j] = vv;
+         }
+      } while (h != 1);
+   }
+
+   /*--
+      The main sorting loop.
+   --*/
+
+   numQSorted = 0;
+
+   for (i = 0; i <= 255; i++) {
+
+      /*--
+         Process big buckets, starting with the least full.
+         Basically this is a 3-step process in which we call
+         mainQSort3 to sort the small buckets [ss, j], but
+         also make a big effort to avoid the calls if we can.
+      --*/
+      ss = runningOrder[i];
+
+      /*--
+         Step 1:
+         Complete the big bucket [ss] by quicksorting
+         any unsorted small buckets [ss, j], for j != ss.  
+         Hopefully previous pointer-scanning phases have already
+         completed many of the small buckets [ss, j], so
+         we don't have to sort them at all.
+      --*/
+      for (j = 0; j <= 255; j++) {
+         if (j != ss) {
+            sb = (ss << 8) + j;
+            if ( ! (ftab[sb] & SETMASK) ) {
+               Int32 lo = ftab[sb]   & CLEARMASK;
+               Int32 hi = (ftab[sb+1] & CLEARMASK) - 1;
+               if (hi > lo) {
+                  if (verb >= 4)
+                     VPrintf4 ( "        qsort [0x%x, 0x%x]   "
+                                "done %d   this %d\n",
+                                ss, j, numQSorted, hi - lo + 1 );
+                  mainQSort3 ( 
+                     ptr, block, quadrant, nblock, 
+                     lo, hi, BZ_N_RADIX, budget 
+                  );   
+                  numQSorted += (hi - lo + 1);
+                  if (*budget < 0) return;
+               }
+            }
+            ftab[sb] |= SETMASK;
+         }
+      }
+
+      AssertH ( !bigDone[ss], 1006 );
+
+      /*--
+         Step 2:
+         Now scan this big bucket [ss] so as to synthesise the
+         sorted order for small buckets [t, ss] for all t,
+         including, magically, the bucket [ss,ss] too.
+         This will avoid doing Real Work in subsequent Step 1's.
+      --*/
+      {
+         for (j = 0; j <= 255; j++) {
+            copyStart[j] =  ftab[(j << 8) + ss]     & CLEARMASK;
+            copyEnd  [j] = (ftab[(j << 8) + ss + 1] & CLEARMASK) - 1;
+         }
+         for (j = ftab[ss << 8] & CLEARMASK; j < copyStart[ss]; j++) {
+            k = ptr[j]-1; if (k < 0) k += nblock;
+            c1 = block[k];
+            if (!bigDone[c1])
+               ptr[ copyStart[c1]++ ] = k;
+         }
+         for (j = (ftab[(ss+1) << 8] & CLEARMASK) - 1; j > copyEnd[ss]; j--) {
+            k = ptr[j]-1; if (k < 0) k += nblock;
+            c1 = block[k];
+            if (!bigDone[c1]) 
+               ptr[ copyEnd[c1]-- ] = k;
+         }
+      }
+
+      AssertH ( (copyStart[ss]-1 == copyEnd[ss])
+                || 
+                /* Extremely rare case missing in bzip2-1.0.0 and 1.0.1.
+                   Necessity for this case is demonstrated by compressing 
+                   a sequence of approximately 48.5 million of character 
+                   251; 1.0.0/1.0.1 will then die here. */
+                (copyStart[ss] == 0 && copyEnd[ss] == nblock-1),
+                1007 )
+
+      for (j = 0; j <= 255; j++) ftab[(j << 8) + ss] |= SETMASK;
+
+      /*--
+         Step 3:
+         The [ss] big bucket is now done.  Record this fact,
+         and update the quadrant descriptors.  Remember to
+         update quadrants in the overshoot area too, if
+         necessary.  The "if (i < 255)" test merely skips
+         this updating for the last bucket processed, since
+         updating for the last bucket is pointless.
+
+         The quadrant array provides a way to incrementally
+         cache sort orderings, as they appear, so as to 
+         make subsequent comparisons in fullGtU() complete
+         faster.  For repetitive blocks this makes a big
+         difference (but not big enough to be able to avoid
+         the fallback sorting mechanism, exponential radix sort).
+
+         The precise meaning is: at all times:
+
+            for 0 <= i < nblock and 0 <= j <= nblock
+
+            if block[i] != block[j], 
+
+               then the relative values of quadrant[i] and 
+                    quadrant[j] are meaningless.
+
+               else {
+                  if quadrant[i] < quadrant[j]
+                     then the string starting at i lexicographically
+                     precedes the string starting at j
+
+                  else if quadrant[i] > quadrant[j]
+                     then the string starting at j lexicographically
+                     precedes the string starting at i
+
+                  else
+                     the relative ordering of the strings starting
+                     at i and j has not yet been determined.
+               }
+      --*/
+      bigDone[ss] = True;
+
+      if (i < 255) {
+         Int32 bbStart  = ftab[ss << 8] & CLEARMASK;
+         Int32 bbSize   = (ftab[(ss+1) << 8] & CLEARMASK) - bbStart;
+         Int32 shifts   = 0;
+
+         while ((bbSize >> shifts) > 65534) shifts++;
+
+         for (j = bbSize-1; j >= 0; j--) {
+            Int32 a2update     = ptr[bbStart + j];
+            UInt16 qVal        = (UInt16)(j >> shifts);
+            quadrant[a2update] = qVal;
+            if (a2update < BZ_N_OVERSHOOT)
+               quadrant[a2update + nblock] = qVal;
+         }
+         AssertH ( ((bbSize-1) >> shifts) <= 65535, 1002 );
+      }
+
+   }
+
+   if (verb >= 4)
+      VPrintf3 ( "        %d pointers, %d sorted, %d scanned\n",
+                 nblock, numQSorted, nblock - numQSorted );
+}
+
+#undef BIGFREQ
+#undef SETMASK
+#undef CLEARMASK
+
+
+/*---------------------------------------------*/
+/* Pre:
+      nblock > 0
+      arr2 exists for [0 .. nblock-1 +N_OVERSHOOT]
+      ((UChar*)arr2)  [0 .. nblock-1] holds block
+      arr1 exists for [0 .. nblock-1]
+
+   Post:
+      ((UChar*)arr2) [0 .. nblock-1] holds block
+      All other areas of block destroyed
+      ftab [ 0 .. 65536 ] destroyed
+      arr1 [0 .. nblock-1] holds sorted order
+*/
+void BZ2_blockSort ( EState* s )
+{
+   UInt32* ptr    = s->ptr; 
+   UChar*  block  = s->block;
+   UInt32* ftab   = s->ftab;
+   Int32   nblock = s->nblock;
+   Int32   verb   = s->verbosity;
+   Int32   wfact  = s->workFactor;
+   UInt16* quadrant;
+   Int32   budget;
+   Int32   budgetInit;
+   Int32   i;
+
+   if (nblock < 10000) {
+      fallbackSort ( s->arr1, s->arr2, ftab, nblock, verb );
+   } else {
+      /* Calculate the location for quadrant, remembering to get
+         the alignment right.  Assumes that &(block[0]) is at least
+         2-byte aligned -- this should be ok since block is really
+         the first section of arr2.
+      */
+      i = nblock+BZ_N_OVERSHOOT;
+      if (i & 1) i++;
+      quadrant = (UInt16*)(&(block[i]));
+
+      /* (wfact-1) / 3 puts the default-factor-30
+         transition point at very roughly the same place as 
+         with v0.1 and v0.9.0.  
+         Not that it particularly matters any more, since the
+         resulting compressed stream is now the same regardless
+         of whether or not we use the main sort or fallback sort.
+      */
+      if (wfact < 1  ) wfact = 1;
+      if (wfact > 100) wfact = 100;
+      budgetInit = nblock * ((wfact-1) / 3);
+      budget = budgetInit;
+
+      mainSort ( ptr, block, quadrant, ftab, nblock, verb, &budget );
+      if (verb >= 3) 
+         VPrintf3 ( "      %d work, %d block, ratio %5.2f\n",
+                    budgetInit - budget,
+                    nblock, 
+                    (float)(budgetInit - budget) /
+                    (float)(nblock==0 ? 1 : nblock) ); 
+      if (budget < 0) {
+         if (verb >= 2) 
+            VPrintf0 ( "    too repetitive; using fallback"
+                       " sorting algorithm\n" );
+         fallbackSort ( s->arr1, s->arr2, ftab, nblock, verb );
+      }
+   }
+
+   s->origPtr = -1;
+   for (i = 0; i < s->nblock; i++)
+      if (ptr[i] == 0)
+         { s->origPtr = i; break; };
+
+   AssertH( s->origPtr != -1, 1003 );
+}
+
+
+/*-------------------------------------------------------------*/
+/*--- end                                       blocksort.c ---*/
+/*-------------------------------------------------------------*/
diff --git a/lib/bzip2/bzlib_compress.c b/lib/bzip2/bzlib_compress.c
new file mode 100644
index 0000000000000000000000000000000000000000..c8da1c72e901bd50cbd1041deaf59d28bd705132
--- /dev/null
+++ b/lib/bzip2/bzlib_compress.c
@@ -0,0 +1,714 @@
+
+/*-------------------------------------------------------------*/
+/*--- Compression machinery (not incl block sorting)        ---*/
+/*---                                            compress.c ---*/
+/*-------------------------------------------------------------*/
+
+/*--
+  This file is a part of bzip2 and/or libbzip2, a program and
+  library for lossless, block-sorting data compression.
+
+  Copyright (C) 1996-2002 Julian R Seward.  All rights reserved.
+
+  Redistribution and use in source and binary forms, with or without
+  modification, are permitted provided that the following conditions
+  are met:
+
+  1. Redistributions of source code must retain the above copyright
+     notice, this list of conditions and the following disclaimer.
+
+  2. The origin of this software must not be misrepresented; you must
+     not claim that you wrote the original software.  If you use this
+     software in a product, an acknowledgment in the product
+     documentation would be appreciated but is not required.
+
+  3. Altered source versions must be plainly marked as such, and must
+     not be misrepresented as being the original software.
+
+  4. The name of the author may not be used to endorse or promote
+     products derived from this software without specific prior written
+     permission.
+
+  THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
+  OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+  ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+  GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+  Julian Seward, Cambridge, UK.
+  jseward@acm.org
+  bzip2/libbzip2 version 1.0.6 of 6 September 2010
+  Copyright (C) 1996-2010 Julian Seward <jseward@bzip.org>
+
+  This program is based on (at least) the work of:
+     Mike Burrows
+     David Wheeler
+     Peter Fenwick
+     Alistair Moffat
+     Radford Neal
+     Ian H. Witten
+     Robert Sedgewick
+     Jon L. Bentley
+
+  For more information on these sources, see the manual.
+--*/
+
+/* CHANGES
+    0.9.0    -- original version.
+    0.9.0a/b -- no changes in this file.
+    0.9.0c   -- changed setting of nGroups in sendMTFValues() 
+                so as to do a bit better on small files
+*/
+
+#include "bzlib_private.h"
+
+
+/*---------------------------------------------------*/
+/*--- Bit stream I/O                              ---*/
+/*---------------------------------------------------*/
+
+/*---------------------------------------------------*/
+void BZ2_bsInitWrite ( EState* s )
+{
+   s->bsLive = 0;
+   s->bsBuff = 0;
+}
+
+
+/*---------------------------------------------------*/
+static
+void bsFinishWrite ( EState* s )
+{
+   while (s->bsLive > 0) {
+      s->zbits[s->numZ] = (UChar)(s->bsBuff >> 24);
+      s->numZ++;
+      s->bsBuff <<= 8;
+      s->bsLive -= 8;
+   }
+}
+
+
+/*---------------------------------------------------*/
+#define bsNEEDW(nz)                           \
+{                                             \
+   while (s->bsLive >= 8) {                   \
+      s->zbits[s->numZ]                       \
+         = (UChar)(s->bsBuff >> 24);          \
+      s->numZ++;                              \
+      s->bsBuff <<= 8;                        \
+      s->bsLive -= 8;                         \
+   }                                          \
+}
+
+
+/*---------------------------------------------------*/
+static
+__inline__
+void bsW ( EState* s, Int32 n, UInt32 v )
+{
+   bsNEEDW ( n );
+   s->bsBuff |= (v << (32 - s->bsLive - n));
+   s->bsLive += n;
+}
+
+
+/*---------------------------------------------------*/
+static
+void bsPutUInt32 ( EState* s, UInt32 u )
+{
+   bsW ( s, 8, (u >> 24) & 0xffL );
+   bsW ( s, 8, (u >> 16) & 0xffL );
+   bsW ( s, 8, (u >>  8) & 0xffL );
+   bsW ( s, 8,  u        & 0xffL );
+}
+
+
+/*---------------------------------------------------*/
+static
+void bsPutUChar ( EState* s, UChar c )
+{
+   bsW( s, 8, (UInt32)c );
+}
+
+
+/*---------------------------------------------------*/
+/*--- The back end proper                         ---*/
+/*---------------------------------------------------*/
+
+/*---------------------------------------------------*/
+static
+void makeMaps_e ( EState* s )
+{
+   Int32 i;
+   s->nInUse = 0;
+   for (i = 0; i < 256; i++)
+      if (s->inUse[i]) {
+         s->unseqToSeq[i] = s->nInUse;
+         s->nInUse++;
+      }
+}
+
+
+/*---------------------------------------------------*/
+static
+void generateMTFValues ( EState* s )
+{
+   UChar   yy[256];
+   Int32   i, j;
+   Int32   zPend;
+   Int32   wr;
+   Int32   EOB;
+
+   /* 
+      After sorting (eg, here),
+         s->arr1 [ 0 .. s->nblock-1 ] holds sorted order,
+         and
+         ((UChar*)s->arr2) [ 0 .. s->nblock-1 ] 
+         holds the original block data.
+
+      The first thing to do is generate the MTF values,
+      and put them in
+         ((UInt16*)s->arr1) [ 0 .. s->nblock-1 ].
+      Because there are strictly fewer or equal MTF values
+      than block values, ptr values in this area are overwritten
+      with MTF values only when they are no longer needed.
+
+      The final compressed bitstream is generated into the
+      area starting at
+         (UChar*) (&((UChar*)s->arr2)[s->nblock])
+
+      These storage aliases are set up in bzCompressInit(),
+      except for the last one, which is arranged in 
+      compressBlock().
+   */
+   UInt32* ptr   = s->ptr;
+   UChar* block  = s->block;
+   UInt16* mtfv  = s->mtfv;
+
+   makeMaps_e ( s );
+   EOB = s->nInUse+1;
+
+   for (i = 0; i <= EOB; i++) s->mtfFreq[i] = 0;
+
+   wr = 0;
+   zPend = 0;
+   for (i = 0; i < s->nInUse; i++) yy[i] = (UChar) i;
+
+   for (i = 0; i < s->nblock; i++) {
+      UChar ll_i;
+      AssertD ( wr <= i, "generateMTFValues(1)" );
+      j = ptr[i]-1; if (j < 0) j += s->nblock;
+      ll_i = s->unseqToSeq[block[j]];
+      AssertD ( ll_i < s->nInUse, "generateMTFValues(2a)" );
+
+      if (yy[0] == ll_i) { 
+         zPend++;
+      } else {
+
+         if (zPend > 0) {
+            zPend--;
+            while (True) {
+               if (zPend & 1) {
+                  mtfv[wr] = BZ_RUNB; wr++; 
+                  s->mtfFreq[BZ_RUNB]++; 
+               } else {
+                  mtfv[wr] = BZ_RUNA; wr++; 
+                  s->mtfFreq[BZ_RUNA]++; 
+               }
+               if (zPend < 2) break;
+               zPend = (zPend - 2) / 2;
+            };
+            zPend = 0;
+         }
+         {
+            register UChar  rtmp;
+            register UChar* ryy_j;
+            register UChar  rll_i;
+            rtmp  = yy[1];
+            yy[1] = yy[0];
+            ryy_j = &(yy[1]);
+            rll_i = ll_i;
+            while ( rll_i != rtmp ) {
+               register UChar rtmp2;
+               ryy_j++;
+               rtmp2  = rtmp;
+               rtmp   = *ryy_j;
+               *ryy_j = rtmp2;
+            };
+            yy[0] = rtmp;
+            j = ryy_j - &(yy[0]);
+            mtfv[wr] = j+1; wr++; s->mtfFreq[j+1]++;
+         }
+
+      }
+   }
+
+   if (zPend > 0) {
+      zPend--;
+      while (True) {
+         if (zPend & 1) {
+            mtfv[wr] = BZ_RUNB; wr++; 
+            s->mtfFreq[BZ_RUNB]++; 
+         } else {
+            mtfv[wr] = BZ_RUNA; wr++; 
+            s->mtfFreq[BZ_RUNA]++; 
+         }
+         if (zPend < 2) break;
+         zPend = (zPend - 2) / 2;
+      };
+      zPend = 0;
+   }
+
+   mtfv[wr] = EOB; wr++; s->mtfFreq[EOB]++;
+
+   s->nMTF = wr;
+}
+
+
+/*---------------------------------------------------*/
+#define BZ_LESSER_ICOST  0
+#define BZ_GREATER_ICOST 15
+
+static
+void sendMTFValues ( EState* s )
+{
+   Int32 v, t, i, j, gs, ge, totc, bt, bc, iter;
+   Int32 nSelectors, alphaSize, minLen, maxLen, selCtr;
+   Int32 nGroups, nBytes;
+
+   /*--
+   UChar  len [BZ_N_GROUPS][BZ_MAX_ALPHA_SIZE];
+   is a global since the decoder also needs it.
+
+   Int32  code[BZ_N_GROUPS][BZ_MAX_ALPHA_SIZE];
+   Int32  rfreq[BZ_N_GROUPS][BZ_MAX_ALPHA_SIZE];
+   are also globals only used in this proc.
+   Made global to keep stack frame size small.
+   --*/
+
+
+   UInt16 cost[BZ_N_GROUPS];
+   Int32  fave[BZ_N_GROUPS];
+
+   UInt16* mtfv = s->mtfv;
+
+   if (s->verbosity >= 3)
+      VPrintf3( "      %d in block, %d after MTF & 1-2 coding, "
+                "%d+2 syms in use\n", 
+                s->nblock, s->nMTF, s->nInUse );
+
+   alphaSize = s->nInUse+2;
+   for (t = 0; t < BZ_N_GROUPS; t++)
+      for (v = 0; v < alphaSize; v++)
+         s->len[t][v] = BZ_GREATER_ICOST;
+
+   /*--- Decide how many coding tables to use ---*/
+   AssertH ( s->nMTF > 0, 3001 );
+   if (s->nMTF < 200)  nGroups = 2; else
+   if (s->nMTF < 600)  nGroups = 3; else
+   if (s->nMTF < 1200) nGroups = 4; else
+   if (s->nMTF < 2400) nGroups = 5; else
+                       nGroups = 6;
+
+   /*--- Generate an initial set of coding tables ---*/
+   { 
+      Int32 nPart, remF, tFreq, aFreq;
+
+      nPart = nGroups;
+      remF  = s->nMTF;
+      gs = 0;
+      while (nPart > 0) {
+         tFreq = remF / nPart;
+         ge = gs-1;
+         aFreq = 0;
+         while (aFreq < tFreq && ge < alphaSize-1) {
+            ge++;
+            aFreq += s->mtfFreq[ge];
+         }
+
+         if (ge > gs 
+             && nPart != nGroups && nPart != 1 
+             && ((nGroups-nPart) % 2 == 1)) {
+            aFreq -= s->mtfFreq[ge];
+            ge--;
+         }
+
+         if (s->verbosity >= 3)
+            VPrintf5( "      initial group %d, [%d .. %d], "
+                      "has %d syms (%4.1f%%)\n",
+                      nPart, gs, ge, aFreq, 
+                      (100.0 * (float)aFreq) / (float)(s->nMTF) );
+ 
+         for (v = 0; v < alphaSize; v++)
+            if (v >= gs && v <= ge) 
+               s->len[nPart-1][v] = BZ_LESSER_ICOST; else
+               s->len[nPart-1][v] = BZ_GREATER_ICOST;
+ 
+         nPart--;
+         gs = ge+1;
+         remF -= aFreq;
+      }
+   }
+
+   /*--- 
+      Iterate up to BZ_N_ITERS times to improve the tables.
+   ---*/
+   for (iter = 0; iter < BZ_N_ITERS; iter++) {
+
+      for (t = 0; t < nGroups; t++) fave[t] = 0;
+
+      for (t = 0; t < nGroups; t++)
+         for (v = 0; v < alphaSize; v++)
+            s->rfreq[t][v] = 0;
+
+      /*---
+        Set up an auxiliary length table which is used to fast-track
+	the common case (nGroups == 6). 
+      ---*/
+      if (nGroups == 6) {
+         for (v = 0; v < alphaSize; v++) {
+            s->len_pack[v][0] = (s->len[1][v] << 16) | s->len[0][v];
+            s->len_pack[v][1] = (s->len[3][v] << 16) | s->len[2][v];
+            s->len_pack[v][2] = (s->len[5][v] << 16) | s->len[4][v];
+	 }
+      }
+
+      nSelectors = 0;
+      totc = 0;
+      gs = 0;
+      while (True) {
+
+         /*--- Set group start & end marks. --*/
+         if (gs >= s->nMTF) break;
+         ge = gs + BZ_G_SIZE - 1; 
+         if (ge >= s->nMTF) ge = s->nMTF-1;
+
+         /*-- 
+            Calculate the cost of this group as coded
+            by each of the coding tables.
+         --*/
+         for (t = 0; t < nGroups; t++) cost[t] = 0;
+
+         if (nGroups == 6 && 50 == ge-gs+1) {
+            /*--- fast track the common case ---*/
+            register UInt32 cost01, cost23, cost45;
+            register UInt16 icv;
+            cost01 = cost23 = cost45 = 0;
+
+#           define BZ_ITER(nn)                \
+               icv = mtfv[gs+(nn)];           \
+               cost01 += s->len_pack[icv][0]; \
+               cost23 += s->len_pack[icv][1]; \
+               cost45 += s->len_pack[icv][2]; \
+
+            BZ_ITER(0);  BZ_ITER(1);  BZ_ITER(2);  BZ_ITER(3);  BZ_ITER(4);
+            BZ_ITER(5);  BZ_ITER(6);  BZ_ITER(7);  BZ_ITER(8);  BZ_ITER(9);
+            BZ_ITER(10); BZ_ITER(11); BZ_ITER(12); BZ_ITER(13); BZ_ITER(14);
+            BZ_ITER(15); BZ_ITER(16); BZ_ITER(17); BZ_ITER(18); BZ_ITER(19);
+            BZ_ITER(20); BZ_ITER(21); BZ_ITER(22); BZ_ITER(23); BZ_ITER(24);
+            BZ_ITER(25); BZ_ITER(26); BZ_ITER(27); BZ_ITER(28); BZ_ITER(29);
+            BZ_ITER(30); BZ_ITER(31); BZ_ITER(32); BZ_ITER(33); BZ_ITER(34);
+            BZ_ITER(35); BZ_ITER(36); BZ_ITER(37); BZ_ITER(38); BZ_ITER(39);
+            BZ_ITER(40); BZ_ITER(41); BZ_ITER(42); BZ_ITER(43); BZ_ITER(44);
+            BZ_ITER(45); BZ_ITER(46); BZ_ITER(47); BZ_ITER(48); BZ_ITER(49);
+
+#           undef BZ_ITER
+
+            cost[0] = cost01 & 0xffff; cost[1] = cost01 >> 16;
+            cost[2] = cost23 & 0xffff; cost[3] = cost23 >> 16;
+            cost[4] = cost45 & 0xffff; cost[5] = cost45 >> 16;
+
+         } else {
+	    /*--- slow version which correctly handles all situations ---*/
+            for (i = gs; i <= ge; i++) { 
+               UInt16 icv = mtfv[i];
+               for (t = 0; t < nGroups; t++) cost[t] += s->len[t][icv];
+            }
+         }
+ 
+         /*-- 
+            Find the coding table which is best for this group,
+            and record its identity in the selector table.
+         --*/
+         bc = 999999999; bt = -1;
+         for (t = 0; t < nGroups; t++)
+            if (cost[t] < bc) { bc = cost[t]; bt = t; };
+         totc += bc;
+         fave[bt]++;
+         s->selector[nSelectors] = bt;
+         nSelectors++;
+
+         /*-- 
+            Increment the symbol frequencies for the selected table.
+          --*/
+         if (nGroups == 6 && 50 == ge-gs+1) {
+            /*--- fast track the common case ---*/
+
+#           define BZ_ITUR(nn) s->rfreq[bt][ mtfv[gs+(nn)] ]++
+
+            BZ_ITUR(0);  BZ_ITUR(1);  BZ_ITUR(2);  BZ_ITUR(3);  BZ_ITUR(4);
+            BZ_ITUR(5);  BZ_ITUR(6);  BZ_ITUR(7);  BZ_ITUR(8);  BZ_ITUR(9);
+            BZ_ITUR(10); BZ_ITUR(11); BZ_ITUR(12); BZ_ITUR(13); BZ_ITUR(14);
+            BZ_ITUR(15); BZ_ITUR(16); BZ_ITUR(17); BZ_ITUR(18); BZ_ITUR(19);
+            BZ_ITUR(20); BZ_ITUR(21); BZ_ITUR(22); BZ_ITUR(23); BZ_ITUR(24);
+            BZ_ITUR(25); BZ_ITUR(26); BZ_ITUR(27); BZ_ITUR(28); BZ_ITUR(29);
+            BZ_ITUR(30); BZ_ITUR(31); BZ_ITUR(32); BZ_ITUR(33); BZ_ITUR(34);
+            BZ_ITUR(35); BZ_ITUR(36); BZ_ITUR(37); BZ_ITUR(38); BZ_ITUR(39);
+            BZ_ITUR(40); BZ_ITUR(41); BZ_ITUR(42); BZ_ITUR(43); BZ_ITUR(44);
+            BZ_ITUR(45); BZ_ITUR(46); BZ_ITUR(47); BZ_ITUR(48); BZ_ITUR(49);
+
+#           undef BZ_ITUR
+
+         } else {
+	    /*--- slow version which correctly handles all situations ---*/
+            for (i = gs; i <= ge; i++)
+               s->rfreq[bt][ mtfv[i] ]++;
+         }
+
+         gs = ge+1;
+      }
+      if (s->verbosity >= 3) {
+         VPrintf2 ( "      pass %d: size is %d, grp uses are ", 
+                   iter+1, totc/8 );
+         for (t = 0; t < nGroups; t++)
+            VPrintf1 ( "%d ", fave[t] );
+         VPrintf0 ( "\n" );
+      }
+
+      /*--
+        Recompute the tables based on the accumulated frequencies.
+      --*/
+      /* maxLen was changed from 20 to 17 in bzip2-1.0.3.  See 
+         comment in huffman.c for details. */
+      for (t = 0; t < nGroups; t++)
+         BZ2_hbMakeCodeLengths ( &(s->len[t][0]), &(s->rfreq[t][0]), 
+                                 alphaSize, 17 /*20*/ );
+   }
+
+
+   AssertH( nGroups < 8, 3002 );
+   AssertH( nSelectors < 32768 &&
+            nSelectors <= (2 + (900000 / BZ_G_SIZE)),
+            3003 );
+
+
+   /*--- Compute MTF values for the selectors. ---*/
+   {
+      UChar pos[BZ_N_GROUPS], ll_i, tmp2, tmp;
+      for (i = 0; i < nGroups; i++) pos[i] = i;
+      for (i = 0; i < nSelectors; i++) {
+         ll_i = s->selector[i];
+         j = 0;
+         tmp = pos[j];
+         while ( ll_i != tmp ) {
+            j++;
+            tmp2 = tmp;
+            tmp = pos[j];
+            pos[j] = tmp2;
+         };
+         pos[0] = tmp;
+         s->selectorMtf[i] = j;
+      }
+   };
+
+   /*--- Assign actual codes for the tables. --*/
+   for (t = 0; t < nGroups; t++) {
+      minLen = 32;
+      maxLen = 0;
+      for (i = 0; i < alphaSize; i++) {
+         if (s->len[t][i] > maxLen) maxLen = s->len[t][i];
+         if (s->len[t][i] < minLen) minLen = s->len[t][i];
+      }
+      AssertH ( !(maxLen > 17 /*20*/ ), 3004 );
+      AssertH ( !(minLen < 1),  3005 );
+      BZ2_hbAssignCodes ( &(s->code[t][0]), &(s->len[t][0]), 
+                          minLen, maxLen, alphaSize );
+   }
+
+   /*--- Transmit the mapping table. ---*/
+   { 
+      Bool inUse16[16];
+      for (i = 0; i < 16; i++) {
+          inUse16[i] = False;
+          for (j = 0; j < 16; j++)
+             if (s->inUse[i * 16 + j]) inUse16[i] = True;
+      }
+     
+      nBytes = s->numZ;
+      for (i = 0; i < 16; i++)
+         if (inUse16[i]) bsW(s,1,1); else bsW(s,1,0);
+
+      for (i = 0; i < 16; i++)
+         if (inUse16[i])
+            for (j = 0; j < 16; j++) {
+               if (s->inUse[i * 16 + j]) bsW(s,1,1); else bsW(s,1,0);
+            }
+
+      if (s->verbosity >= 3) 
+         VPrintf1( "      bytes: mapping %d, ", s->numZ-nBytes );
+   }
+
+   /*--- Now the selectors. ---*/
+   nBytes = s->numZ;
+   bsW ( s, 3, nGroups );
+   bsW ( s, 15, nSelectors );
+   for (i = 0; i < nSelectors; i++) { 
+      for (j = 0; j < s->selectorMtf[i]; j++) bsW(s,1,1);
+      bsW(s,1,0);
+   }
+   if (s->verbosity >= 3)
+      VPrintf1( "selectors %d, ", s->numZ-nBytes );
+
+   /*--- Now the coding tables. ---*/
+   nBytes = s->numZ;
+
+   for (t = 0; t < nGroups; t++) {
+      Int32 curr = s->len[t][0];
+      bsW ( s, 5, curr );
+      for (i = 0; i < alphaSize; i++) {
+         while (curr < s->len[t][i]) { bsW(s,2,2); curr++; /* 10 */ };
+         while (curr > s->len[t][i]) { bsW(s,2,3); curr--; /* 11 */ };
+         bsW ( s, 1, 0 );
+      }
+   }
+
+   if (s->verbosity >= 3)
+      VPrintf1 ( "code lengths %d, ", s->numZ-nBytes );
+
+   /*--- And finally, the block data proper ---*/
+   nBytes = s->numZ;
+   selCtr = 0;
+   gs = 0;
+   while (True) {
+      if (gs >= s->nMTF) break;
+      ge = gs + BZ_G_SIZE - 1; 
+      if (ge >= s->nMTF) ge = s->nMTF-1;
+      AssertH ( s->selector[selCtr] < nGroups, 3006 );
+
+      if (nGroups == 6 && 50 == ge-gs+1) {
+            /*--- fast track the common case ---*/
+            UInt16 mtfv_i;
+            UChar* s_len_sel_selCtr 
+               = &(s->len[s->selector[selCtr]][0]);
+            Int32* s_code_sel_selCtr
+               = &(s->code[s->selector[selCtr]][0]);
+
+#           define BZ_ITAH(nn)                      \
+               mtfv_i = mtfv[gs+(nn)];              \
+               bsW ( s,                             \
+                     s_len_sel_selCtr[mtfv_i],      \
+                     s_code_sel_selCtr[mtfv_i] )
+
+            BZ_ITAH(0);  BZ_ITAH(1);  BZ_ITAH(2);  BZ_ITAH(3);  BZ_ITAH(4);
+            BZ_ITAH(5);  BZ_ITAH(6);  BZ_ITAH(7);  BZ_ITAH(8);  BZ_ITAH(9);
+            BZ_ITAH(10); BZ_ITAH(11); BZ_ITAH(12); BZ_ITAH(13); BZ_ITAH(14);
+            BZ_ITAH(15); BZ_ITAH(16); BZ_ITAH(17); BZ_ITAH(18); BZ_ITAH(19);
+            BZ_ITAH(20); BZ_ITAH(21); BZ_ITAH(22); BZ_ITAH(23); BZ_ITAH(24);
+            BZ_ITAH(25); BZ_ITAH(26); BZ_ITAH(27); BZ_ITAH(28); BZ_ITAH(29);
+            BZ_ITAH(30); BZ_ITAH(31); BZ_ITAH(32); BZ_ITAH(33); BZ_ITAH(34);
+            BZ_ITAH(35); BZ_ITAH(36); BZ_ITAH(37); BZ_ITAH(38); BZ_ITAH(39);
+            BZ_ITAH(40); BZ_ITAH(41); BZ_ITAH(42); BZ_ITAH(43); BZ_ITAH(44);
+            BZ_ITAH(45); BZ_ITAH(46); BZ_ITAH(47); BZ_ITAH(48); BZ_ITAH(49);
+
+#           undef BZ_ITAH
+
+      } else {
+	 /*--- slow version which correctly handles all situations ---*/
+         for (i = gs; i <= ge; i++) {
+            bsW ( s, 
+                  s->len  [s->selector[selCtr]] [mtfv[i]],
+                  s->code [s->selector[selCtr]] [mtfv[i]] );
+         }
+      }
+
+
+      gs = ge+1;
+      selCtr++;
+   }
+   AssertH( selCtr == nSelectors, 3007 );
+
+   if (s->verbosity >= 3)
+      VPrintf1( "codes %d\n", s->numZ-nBytes );
+   else /* squash compiler 'used but not set' warning */
+      nBytes = nBytes;
+}
+
+
+/*---------------------------------------------------*/
+void BZ2_compressBlock ( EState* s, Bool is_last_block )
+{
+   if (s->nblock > 0) {
+
+      BZ_FINALISE_CRC ( s->blockCRC );
+      s->combinedCRC = (s->combinedCRC << 1) | (s->combinedCRC >> 31);
+      s->combinedCRC ^= s->blockCRC;
+      if (s->blockNo > 1) s->numZ = 0;
+
+      if (s->verbosity >= 2)
+         VPrintf4( "    block %d: crc = 0x%08x, "
+                   "combined CRC = 0x%08x, size = %d\n",
+                   s->blockNo, s->blockCRC, s->combinedCRC, s->nblock );
+
+      BZ2_blockSort ( s );
+   }
+
+   s->zbits = (UChar*) (&((UChar*)s->arr2)[s->nblock]);
+
+   /*-- If this is the first block, create the stream header. --*/
+   if (s->blockNo == 1) {
+      BZ2_bsInitWrite ( s );
+      bsPutUChar ( s, BZ_HDR_B );
+      bsPutUChar ( s, BZ_HDR_Z );
+      bsPutUChar ( s, BZ_HDR_h );
+      bsPutUChar ( s, (UChar)(BZ_HDR_0 + s->blockSize100k) );
+   }
+
+   if (s->nblock > 0) {
+
+      bsPutUChar ( s, 0x31 ); bsPutUChar ( s, 0x41 );
+      bsPutUChar ( s, 0x59 ); bsPutUChar ( s, 0x26 );
+      bsPutUChar ( s, 0x53 ); bsPutUChar ( s, 0x59 );
+
+      /*-- Now the block's CRC, so it is in a known place. --*/
+      bsPutUInt32 ( s, s->blockCRC );
+
+      /*-- 
+         Now a single bit indicating (non-)randomisation. 
+         As of version 0.9.5, we use a better sorting algorithm
+         which makes randomisation unnecessary.  So always set
+         the randomised bit to 'no'.  Of course, the decoder
+         still needs to be able to handle randomised blocks
+         so as to maintain backwards compatibility with
+         older versions of bzip2.
+      --*/
+      bsW(s,1,0);
+
+      bsW ( s, 24, s->origPtr );
+      generateMTFValues ( s );
+      sendMTFValues ( s );
+   }
+
+
+   /*-- If this is the last block, add the stream trailer. --*/
+   if (is_last_block) {
+
+      bsPutUChar ( s, 0x17 ); bsPutUChar ( s, 0x72 );
+      bsPutUChar ( s, 0x45 ); bsPutUChar ( s, 0x38 );
+      bsPutUChar ( s, 0x50 ); bsPutUChar ( s, 0x90 );
+      bsPutUInt32 ( s, s->combinedCRC );
+      if (s->verbosity >= 2)
+         VPrintf1( "    final combined CRC = 0x%08x\n   ", s->combinedCRC );
+      bsFinishWrite ( s );
+   }
+}
+
+
+/*-------------------------------------------------------------*/
+/*--- end                                        compress.c ---*/
+/*-------------------------------------------------------------*/
diff --git a/lib/fdtdec.c b/lib/fdtdec.c
index b50d10516105cc0c1c4c8a72a25eec24207a6916..d56e1b11f01d43d784be9120c7dbef14b8cd0361 100644
--- a/lib/fdtdec.c
+++ b/lib/fdtdec.c
@@ -56,7 +56,6 @@ static const char * const compat_names[COMPAT_COUNT] = {
 	COMPAT(GENERIC_SPI_FLASH, "spi-flash"),
 	COMPAT(MAXIM_98095_CODEC, "maxim,max98095-codec"),
 	COMPAT(SAMSUNG_EXYNOS5_I2C, "samsung,exynos5-hsi2c"),
-	COMPAT(SANDBOX_LCD_SDL, "sandbox,lcd-sdl"),
 	COMPAT(SAMSUNG_EXYNOS_SYSMMU, "samsung,sysmmu-v3.3"),
 	COMPAT(INTEL_MICROCODE, "intel,microcode"),
 	COMPAT(MEMORY_SPD, "memory-spd"),
diff --git a/lib/time.c b/lib/time.c
index f37a6628d6e80d56e36d4cbaab4f0481a3a3048f..e9f6861b9843773f780ec3bd278340dcb55a2a2e 100644
--- a/lib/time.c
+++ b/lib/time.c
@@ -41,23 +41,6 @@ extern unsigned long __weak timer_read_counter(void);
 #endif
 
 #ifdef CONFIG_TIMER
-static int notrace dm_timer_init(void)
-{
-	struct udevice *dev;
-	int ret;
-
-	if (!gd->timer) {
-		ret = uclass_first_device(UCLASS_TIMER, &dev);
-		if (ret)
-			return ret;
-		if (!dev)
-			return -ENODEV;
-		gd->timer = dev;
-	}
-
-	return 0;
-}
-
 ulong notrace get_tbclk(void)
 {
 	int ret;
diff --git a/lib/tiny-printf.c b/lib/tiny-printf.c
index 403b134cd11222c493df68ab027cb085124ae681..a06abed49590ecbdbb8017f4df68e0c2208f3016 100644
--- a/lib/tiny-printf.c
+++ b/lib/tiny-printf.c
@@ -82,13 +82,21 @@ int vprintf(const char *fmt, va_list va)
 					num = -(int)num;
 					out('-');
 				}
-				for (div = 1000000000; div; div /= 10)
-					div_out(&num, div);
+				if (!num) {
+					out_dgt(0);
+				} else {
+					for (div = 1000000000; div; div /= 10)
+						div_out(&num, div);
+				}
 				break;
 			case 'x':
 				num = va_arg(va, unsigned int);
-				for (div = 0x10000000; div; div /= 0x10)
-					div_out(&num, div);
+				if (!num) {
+					out_dgt(0);
+				} else {
+					for (div = 0x10000000; div; div /= 0x10)
+						div_out(&num, div);
+				}
 				break;
 			case 'c':
 				out((char)(va_arg(va, int)));
@@ -108,8 +116,10 @@ int vprintf(const char *fmt, va_list va)
 				w--;
 			while (w-- > 0)
 				putc(lz ? '0' : ' ');
-			while ((ch = *p++))
-				putc(ch);
+			if (p) {
+				while ((ch = *p++))
+					putc(ch);
+			}
 		}
 	}
 
diff --git a/test/cmd_repeat.sh b/test/cmd_repeat.sh
deleted file mode 100755
index 990e79900f47dc7c25f0fe85fa643a4e0e525fd0..0000000000000000000000000000000000000000
--- a/test/cmd_repeat.sh
+++ /dev/null
@@ -1,29 +0,0 @@
-#!/bin/sh
-
-# Test for U-Boot cli including command repeat
-
-BASE="$(dirname $0)"
-. $BASE/common.sh
-
-run_test() {
-	./${OUTPUT_DIR}/u-boot <<END
-setenv ctrlc_ignore y
-md 0
-
-reset
-END
-}
-check_results() {
-	echo "Check results"
-
-	grep -q 00000100 ${tmp} || fail "Command did not repeat"
-}
-
-echo "Test CLI repeat"
-echo
-tmp="$(tempfile)"
-build_uboot
-run_test >${tmp}
-check_results ${tmp}
-rm ${tmp}
-echo "Test passed"
diff --git a/test/command_ut.c b/test/command_ut.c
index 926573a39543c816281a879922ce9ca4c83dd3b1..54bf62b9bc301a5e1c2bdfa0be8224d33852acda 100644
--- a/test/command_ut.c
+++ b/test/command_ut.c
@@ -7,9 +7,6 @@
 #define DEBUG
 
 #include <common.h>
-#ifdef CONFIG_SANDBOX
-#include <os.h>
-#endif
 
 static const char test_cmd[] = "setenv list 1\n setenv list ${list}2; "
 		"setenv list ${list}3\0"
@@ -20,21 +17,6 @@ static int do_ut_cmd(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
 	printf("%s: Testing commands\n", __func__);
 	run_command("env default -f -a", 0);
 
-	/* run a single command */
-	run_command("setenv single 1", 0);
-	assert(!strcmp("1", getenv("single")));
-
-	/* make sure that compound statements work */
-#ifdef CONFIG_SYS_HUSH_PARSER
-	run_command("if test -n ${single} ; then setenv check 1; fi", 0);
-	assert(!strcmp("1", getenv("check")));
-	run_command("setenv check", 0);
-#endif
-
-	/* commands separated by ; */
-	run_command_list("setenv list 1; setenv list ${list}1", -1, 0);
-	assert(!strcmp("11", getenv("list")));
-
 	/* commands separated by \n */
 	run_command_list("setenv list 1\n setenv list ${list}1", -1, 0);
 	assert(!strcmp("11", getenv("list")));
@@ -43,11 +25,6 @@ static int do_ut_cmd(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
 	run_command_list("setenv list 1${list}\n", -1, 0);
 	assert(!strcmp("111", getenv("list")));
 
-	/* three commands in a row */
-	run_command_list("setenv list 1\n setenv list ${list}2; "
-		"setenv list ${list}3", -1, 0);
-	assert(!strcmp("123", getenv("list")));
-
 	/* a command string with \0 in it. Stuff after \0 should be ignored */
 	run_command("setenv list", 0);
 	run_command_list(test_cmd, sizeof(test_cmd), 0);
@@ -66,13 +43,6 @@ static int do_ut_cmd(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
 	assert(run_command_list("false", -1, 0) == 1);
 	assert(run_command_list("echo", -1, 0) == 0);
 
-	run_command("setenv foo 'setenv monty 1; setenv python 2'", 0);
-	run_command("run foo", 0);
-	assert(getenv("monty") != NULL);
-	assert(!strcmp("1", getenv("monty")));
-	assert(getenv("python") != NULL);
-	assert(!strcmp("2", getenv("python")));
-
 #ifdef CONFIG_SYS_HUSH_PARSER
 	run_command("setenv foo 'setenv black 1\nsetenv adder 2'", 0);
 	run_command("run foo", 0);
@@ -80,112 +50,6 @@ static int do_ut_cmd(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
 	assert(!strcmp("1", getenv("black")));
 	assert(getenv("adder") != NULL);
 	assert(!strcmp("2", getenv("adder")));
-
-	/* Test the 'test' command */
-
-#define HUSH_TEST(name, expr, expected_result) \
-	run_command("if test " expr " ; then " \
-			"setenv " #name "_" #expected_result " y; else " \
-			"setenv " #name "_" #expected_result " n; fi", 0); \
-	assert(!strcmp(#expected_result, getenv(#name "_" #expected_result))); \
-	setenv(#name "_" #expected_result, NULL);
-
-	/* Basic operators */
-	HUSH_TEST(streq, "aaa = aaa", y);
-	HUSH_TEST(streq, "aaa = bbb", n);
-
-	HUSH_TEST(strneq, "aaa != bbb", y);
-	HUSH_TEST(strneq, "aaa != aaa", n);
-
-	HUSH_TEST(strlt, "aaa < bbb", y);
-	HUSH_TEST(strlt, "bbb < aaa", n);
-
-	HUSH_TEST(strgt, "bbb > aaa", y);
-	HUSH_TEST(strgt, "aaa > bbb", n);
-
-	HUSH_TEST(eq, "123 -eq 123", y);
-	HUSH_TEST(eq, "123 -eq 456", n);
-
-	HUSH_TEST(ne, "123 -ne 456", y);
-	HUSH_TEST(ne, "123 -ne 123", n);
-
-	HUSH_TEST(lt, "123 -lt 456", y);
-	HUSH_TEST(lt_eq, "123 -lt 123", n);
-	HUSH_TEST(lt, "456 -lt 123", n);
-
-	HUSH_TEST(le, "123 -le 456", y);
-	HUSH_TEST(le_eq, "123 -le 123", y);
-	HUSH_TEST(le, "456 -le 123", n);
-
-	HUSH_TEST(gt, "456 -gt 123", y);
-	HUSH_TEST(gt_eq, "123 -gt 123", n);
-	HUSH_TEST(gt, "123 -gt 456", n);
-
-	HUSH_TEST(ge, "456 -ge 123", y);
-	HUSH_TEST(ge_eq, "123 -ge 123", y);
-	HUSH_TEST(ge, "123 -ge 456", n);
-
-	HUSH_TEST(z, "-z \"\"", y);
-	HUSH_TEST(z, "-z \"aaa\"", n);
-
-	HUSH_TEST(n, "-n \"aaa\"", y);
-	HUSH_TEST(n, "-n \"\"", n);
-
-	/* Inversion of simple tests */
-	HUSH_TEST(streq_inv, "! aaa = aaa", n);
-	HUSH_TEST(streq_inv, "! aaa = bbb", y);
-
-	HUSH_TEST(streq_inv_inv, "! ! aaa = aaa", y);
-	HUSH_TEST(streq_inv_inv, "! ! aaa = bbb", n);
-
-	/* Binary operators */
-	HUSH_TEST(or_0_0, "aaa != aaa -o bbb != bbb", n);
-	HUSH_TEST(or_0_1, "aaa != aaa -o bbb = bbb", y);
-	HUSH_TEST(or_1_0, "aaa = aaa -o bbb != bbb", y);
-	HUSH_TEST(or_1_1, "aaa = aaa -o bbb = bbb", y);
-
-	HUSH_TEST(and_0_0, "aaa != aaa -a bbb != bbb", n);
-	HUSH_TEST(and_0_1, "aaa != aaa -a bbb = bbb", n);
-	HUSH_TEST(and_1_0, "aaa = aaa -a bbb != bbb", n);
-	HUSH_TEST(and_1_1, "aaa = aaa -a bbb = bbb", y);
-
-	/* Inversion within binary operators */
-	HUSH_TEST(or_0_0_inv, "! aaa != aaa -o ! bbb != bbb", y);
-	HUSH_TEST(or_0_1_inv, "! aaa != aaa -o ! bbb = bbb", y);
-	HUSH_TEST(or_1_0_inv, "! aaa = aaa -o ! bbb != bbb", y);
-	HUSH_TEST(or_1_1_inv, "! aaa = aaa -o ! bbb = bbb", n);
-
-	HUSH_TEST(or_0_0_inv_inv, "! ! aaa != aaa -o ! ! bbb != bbb", n);
-	HUSH_TEST(or_0_1_inv_inv, "! ! aaa != aaa -o ! ! bbb = bbb", y);
-	HUSH_TEST(or_1_0_inv_inv, "! ! aaa = aaa -o ! ! bbb != bbb", y);
-	HUSH_TEST(or_1_1_inv_inv, "! ! aaa = aaa -o ! ! bbb = bbb", y);
-
-	setenv("ut_var_nonexistent", NULL);
-	setenv("ut_var_exists", "1");
-	HUSH_TEST(z_varexp_quoted, "-z \"$ut_var_nonexistent\"", y);
-	HUSH_TEST(z_varexp_quoted, "-z \"$ut_var_exists\"", n);
-	setenv("ut_var_exists", NULL);
-
-	run_command("setenv ut_var_space \" \"", 0);
-	assert(!strcmp(getenv("ut_var_space"), " "));
-	run_command("setenv ut_var_test $ut_var_space", 0);
-	assert(!getenv("ut_var_test"));
-	run_command("setenv ut_var_test \"$ut_var_space\"", 0);
-	assert(!strcmp(getenv("ut_var_test"), " "));
-	run_command("setenv ut_var_test \" 1${ut_var_space}${ut_var_space} 2 \"", 0);
-	assert(!strcmp(getenv("ut_var_test"), " 1   2 "));
-	setenv("ut_var_space", NULL);
-	setenv("ut_var_test", NULL);
-
-#ifdef CONFIG_SANDBOX
-	/* File existence */
-	HUSH_TEST(e, "-e hostfs - creating_this_file_breaks_uboot_unit_test", n);
-	run_command("sb save hostfs - creating_this_file_breaks_uboot_unit_test 0 1", 0);
-	HUSH_TEST(e, "-e hostfs - creating_this_file_breaks_uboot_unit_test", y);
-	/* Perhaps this could be replaced by an "rm" shell command one day */
-	assert(!os_unlink("creating_this_file_breaks_uboot_unit_test"));
-	HUSH_TEST(e, "-e hostfs - creating_this_file_breaks_uboot_unit_test", n);
-#endif
 #endif
 
 	assert(run_command("", 0) == 0);
diff --git a/test/dm/Makefile b/test/dm/Makefile
index 3ff1b75e6f840600b5bbb97cbead84b03497aab8..d4f3f22e583f803ab819a012af64612d7a1f3472 100644
--- a/test/dm/Makefile
+++ b/test/dm/Makefile
@@ -34,5 +34,6 @@ obj-$(CONFIG_DM_USB) += usb.o
 obj-$(CONFIG_DM_PMIC) += pmic.o
 obj-$(CONFIG_DM_REGULATOR) += regulator.o
 obj-$(CONFIG_TIMER) += timer.o
+obj-$(CONFIG_DM_VIDEO) += video.o
 obj-$(CONFIG_ADC) += adc.o
 endif
diff --git a/test/dm/video.c b/test/dm/video.c
new file mode 100644
index 0000000000000000000000000000000000000000..9f5e7fce375fff6c9be269c188004ef512696e28
--- /dev/null
+++ b/test/dm/video.c
@@ -0,0 +1,271 @@
+/*
+ * Copyright (c) 2014 Google, Inc
+ * Written by Simon Glass <sjg@chromium.org>
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#include <common.h>
+#include <bzlib.h>
+#include <dm.h>
+#include <mapmem.h>
+#include <os.h>
+#include <video.h>
+#include <video_console.h>
+#include <dm/test.h>
+#include <dm/uclass-internal.h>
+#include <test/ut.h>
+
+/*
+ * These tests use the standard sandbox frame buffer, the resolution of which
+ * is defined in the device tree. This only supports 16bpp so the tests only
+ * test that code path. It would be possible to adjust this fairly easily,
+ * by adjusting the bpix value in struct sandbox_sdl_plat. However the code
+ * in sandbox_sdl_sync() would also need to change to handle the different
+ * surface depth.
+ */
+DECLARE_GLOBAL_DATA_PTR;
+
+/* Basic test of the video uclass */
+static int dm_test_video_base(struct unit_test_state *uts)
+{
+	struct video_priv *priv;
+	struct udevice *dev;
+
+	ut_assertok(uclass_get_device(UCLASS_VIDEO, 0, &dev));
+	ut_asserteq(1366, video_get_xsize(dev));
+	ut_asserteq(768, video_get_ysize(dev));
+	priv = dev_get_uclass_priv(dev);
+	ut_asserteq(priv->fb_size, 1366 * 768 * 2);
+
+	return 0;
+}
+DM_TEST(dm_test_video_base, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT);
+
+/**
+ * compress_frame_buffer() - Compress the frame buffer and return its size
+ *
+ * We want to write tests which perform operations on the video console and
+ * check that the frame buffer ends up with the correct contents. But it is
+ * painful to store 'known good' images for comparison with the frame
+ * buffer. As an alternative, we can compress the frame buffer and check the
+ * size of the compressed data. This provides a pretty good level of
+ * certainty and the resulting tests need only check a single value.
+ *
+ * @dev:	Video device
+ * @return compressed size of the frame buffer, or -ve on error
+ */
+static int compress_frame_buffer(struct udevice *dev)
+{
+	struct video_priv *priv = dev_get_uclass_priv(dev);
+	uint destlen;
+	void *dest;
+	int ret;
+
+	destlen = priv->fb_size;
+	dest = malloc(priv->fb_size);
+	if (!dest)
+		return -ENOMEM;
+	ret = BZ2_bzBuffToBuffCompress(dest, &destlen,
+				       priv->fb, priv->fb_size,
+				       3, 0, 0);
+	free(dest);
+	if (ret)
+		return ret;
+
+	return destlen;
+}
+
+/*
+ * Call this function at any point to halt and show the current display. Be
+ * sure to run the test with the -l flag.
+ */
+static void __maybe_unused see_output(void)
+{
+	video_sync_all();
+	while (1);
+}
+
+/* Test text output works on the video console */
+static int dm_test_video_text(struct unit_test_state *uts)
+{
+	struct udevice *dev, *con;
+	int i;
+
+#define WHITE		0xffff
+#define SCROLL_LINES	100
+
+	ut_assertok(uclass_get_device(UCLASS_VIDEO, 0, &dev));
+	ut_asserteq(46, compress_frame_buffer(dev));
+
+	ut_assertok(uclass_get_device(UCLASS_VIDEO_CONSOLE, 0, &con));
+	vidconsole_putc_xy(con, 0, 0, 'a');
+	ut_asserteq(79, compress_frame_buffer(dev));
+
+	vidconsole_putc_xy(con, 0, 0, ' ');
+	ut_asserteq(46, compress_frame_buffer(dev));
+
+	for (i = 0; i < 20; i++)
+		vidconsole_putc_xy(con, i * 8, 0, ' ' + i);
+	ut_asserteq(273, compress_frame_buffer(dev));
+
+	vidconsole_set_row(con, 0, WHITE);
+	ut_asserteq(46, compress_frame_buffer(dev));
+
+	for (i = 0; i < 20; i++)
+		vidconsole_putc_xy(con, i * 8, 0, ' ' + i);
+	ut_asserteq(273, compress_frame_buffer(dev));
+
+	return 0;
+}
+DM_TEST(dm_test_video_text, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT);
+
+/* Test handling of special characters in the console */
+static int dm_test_video_chars(struct unit_test_state *uts)
+{
+	struct udevice *dev, *con;
+	const char *test_string = "Well\b\b\b\bxhe is\r \n\ta very modest  \bman\n\t\tand Has much to\b\bto be modest about.";
+	const char *s;
+
+	ut_assertok(uclass_get_device(UCLASS_VIDEO, 0, &dev));
+	ut_assertok(uclass_get_device(UCLASS_VIDEO_CONSOLE, 0, &con));
+	for (s = test_string; *s; s++)
+		vidconsole_put_char(con, *s);
+	ut_asserteq(466, compress_frame_buffer(dev));
+
+	return 0;
+}
+DM_TEST(dm_test_video_chars, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT);
+
+/**
+ * check_vidconsole_output() - Run a text console test
+ *
+ * @uts:	Test state
+ * @rot:	Console rotation (0, 90, 180, 270)
+ * @wrap_size:	Expected size of compressed frame buffer for the wrap test
+ * @scroll_size: Same for the scroll test
+ * @return 0 on success
+ */
+static int check_vidconsole_output(struct unit_test_state *uts, int rot,
+				   int wrap_size, int scroll_size)
+{
+	struct udevice *dev, *con;
+	struct sandbox_sdl_plat *plat;
+	int i;
+
+	ut_assertok(uclass_find_device(UCLASS_VIDEO, 0, &dev));
+	ut_assert(!device_active(dev));
+	plat = dev_get_platdata(dev);
+	plat->rot = rot;
+
+	ut_assertok(uclass_get_device(UCLASS_VIDEO, 0, &dev));
+	ut_assertok(uclass_get_device(UCLASS_VIDEO_CONSOLE, 0, &con));
+	ut_asserteq(46, compress_frame_buffer(dev));
+
+	/* Check display wrap */
+	for (i = 0; i < 120; i++)
+		vidconsole_put_char(con, 'A' + i % 50);
+	ut_asserteq(wrap_size, compress_frame_buffer(dev));
+
+	/* Check display scrolling */
+	for (i = 0; i < SCROLL_LINES; i++) {
+		vidconsole_put_char(con, 'A' + i % 50);
+		vidconsole_put_char(con, '\n');
+	}
+	ut_asserteq(scroll_size, compress_frame_buffer(dev));
+
+	/* If we scroll enough, the screen becomes blank again */
+	for (i = 0; i < SCROLL_LINES; i++)
+		vidconsole_put_char(con, '\n');
+	ut_asserteq(46, compress_frame_buffer(dev));
+
+	return 0;
+}
+
+/* Test text output through the console uclass */
+static int dm_test_video_context(struct unit_test_state *uts)
+{
+	return check_vidconsole_output(uts, 0, 788, 453);
+}
+DM_TEST(dm_test_video_context, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT);
+
+/* Test rotated text output through the console uclass */
+static int dm_test_video_rotation1(struct unit_test_state *uts)
+{
+	ut_assertok(check_vidconsole_output(uts, 1, 1112, 680));
+
+	return 0;
+}
+DM_TEST(dm_test_video_rotation1, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT);
+
+/* Test rotated text output through the console uclass */
+static int dm_test_video_rotation2(struct unit_test_state *uts)
+{
+	ut_assertok(check_vidconsole_output(uts, 2, 785, 446));
+
+	return 0;
+}
+DM_TEST(dm_test_video_rotation2, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT);
+
+/* Test rotated text output through the console uclass */
+static int dm_test_video_rotation3(struct unit_test_state *uts)
+{
+	ut_assertok(check_vidconsole_output(uts, 3, 1134, 681));
+
+	return 0;
+}
+DM_TEST(dm_test_video_rotation3, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT);
+
+/* Read a file into memory and return a pointer to it */
+static int read_file(struct unit_test_state *uts, const char *fname,
+		     ulong *addrp)
+{
+	int buf_size = 100000;
+	ulong addr = 0;
+	int size, fd;
+	char *buf;
+
+	buf = map_sysmem(addr, 0);
+	ut_assert(buf != NULL);
+	fd = os_open(fname, OS_O_RDONLY);
+	ut_assert(fd >= 0);
+	size = os_read(fd, buf, buf_size);
+	ut_assert(size >= 0);
+	ut_assert(size < buf_size);
+	os_close(fd);
+	*addrp = addr;
+
+	return 0;
+}
+
+/* Test drawing a bitmap file */
+static int dm_test_video_bmp(struct unit_test_state *uts)
+{
+	struct udevice *dev;
+	ulong addr;
+
+	ut_assertok(uclass_get_device(UCLASS_VIDEO, 0, &dev));
+	ut_assertok(read_file(uts, "tools/logos/denx.bmp", &addr));
+
+	ut_assertok(video_bmp_display(dev, addr, 0, 0, false));
+	ut_asserteq(1368, compress_frame_buffer(dev));
+
+	return 0;
+}
+DM_TEST(dm_test_video_bmp, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT);
+
+/* Test drawing a compressed bitmap file */
+static int dm_test_video_bmp_comp(struct unit_test_state *uts)
+{
+	struct udevice *dev;
+	ulong addr;
+
+	ut_assertok(uclass_get_device(UCLASS_VIDEO, 0, &dev));
+	ut_assertok(read_file(uts, "tools/logos/denx-comp.bmp", &addr));
+
+	ut_assertok(video_bmp_display(dev, addr, 0, 0, false));
+	ut_asserteq(1368, compress_frame_buffer(dev));
+
+	return 0;
+}
+DM_TEST(dm_test_video_bmp_comp, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT);
diff --git a/test/py/.gitignore b/test/py/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..0d20b6487c61e7d1bde93acf4a14b7a89083a16d
--- /dev/null
+++ b/test/py/.gitignore
@@ -0,0 +1 @@
+*.pyc
diff --git a/test/py/README.md b/test/py/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..8036299d07352a36dcd9f454dc99969df5d59c35
--- /dev/null
+++ b/test/py/README.md
@@ -0,0 +1,300 @@
+# U-Boot pytest suite
+
+## Introduction
+
+This tool aims to test U-Boot by executing U-Boot shell commands using the
+console interface. A single top-level script exists to execute or attach to the
+U-Boot console, run the entire script of tests against it, and summarize the
+results. Advantages of this approach are:
+
+- Testing is performed in the same way a user or script would interact with
+  U-Boot; there can be no disconnect.
+- There is no need to write or embed test-related code into U-Boot itself.
+  It is asserted that writing test-related code in Python is simpler and more
+  flexible that writing it all in C.
+- It is reasonably simple to interact with U-Boot in this way.
+
+## Requirements
+
+The test suite is implemented using pytest. Interaction with the U-Boot console
+involves executing some binary and interacting with its stdin/stdout. You will
+need to implement various "hook" scripts that are called by the test suite at
+the appropriate time.
+
+On Debian or Debian-like distributions, the following packages are required.
+Similar package names should exist in other distributions.
+
+| Package        | Version tested (Ubuntu 14.04) |
+| -------------- | ----------------------------- |
+| python         | 2.7.5-5ubuntu3                |
+| python-pytest  | 2.5.1-1                       |
+
+The test script supports either:
+
+- Executing a sandbox port of U-Boot on the local machine as a sub-process,
+  and interacting with it over stdin/stdout.
+- Executing an external "hook" scripts to flash a U-Boot binary onto a
+  physical board, attach to the board's console stream, and reset the board.
+  Further details are described later.
+
+### Using `virtualenv` to provide requirements
+
+Older distributions (e.g. Ubuntu 10.04) may not provide all the required
+packages, or may provide versions that are too old to run the test suite. One
+can use the Python `virtualenv` script to locally install more up-to-date
+versions of the required packages without interfering with the OS installation.
+For example:
+
+```bash
+$ cd /path/to/u-boot
+$ sudo apt-get install python python-virtualenv
+$ virtualenv venv
+$ . ./venv/bin/activate
+$ pip install pytest
+```
+
+## Testing sandbox
+
+To run the testsuite on the sandbox port (U-Boot built as a native user-space
+application), simply execute:
+
+```
+./test/py/test.py --bd sandbox --build
+```
+
+The `--bd` option tells the test suite which board type is being tested. This
+lets the test suite know which features the board has, and hence exactly what
+can be tested.
+
+The `--build` option tells U-Boot to compile U-Boot. Alternatively, you may
+omit this option and build U-Boot yourself, in whatever way you choose, before
+running the test script.
+
+The test script will attach to U-Boot, execute all valid tests for the board,
+then print a summary of the test process. A complete log of the test session
+will be written to `${build_dir}/test-log.html`. This is best viewed in a web
+browser, but may be read directly as plain text, perhaps with the aid of the
+`html2text` utility.
+
+## Command-line options
+
+- `--board-type`, `--bd`, `-B` set the type of the board to be tested. For
+  example, `sandbox` or `seaboard`.
+- `--board-identity`, `--id` set the identity of the board to be tested.
+  This allows differentiation between multiple instances of the same type of
+  physical board that are attached to the same host machine. This parameter is
+  not interpreted by the test script in any way, but rather is simply passed
+  to the hook scripts described below, and may be used in any site-specific
+  way deemed necessary.
+- `--build` indicates that the test script should compile U-Boot itself
+  before running the tests. If using this option, make sure that any
+  environment variables required by the build process are already set, such as
+  `$CROSS_COMPILE`.
+- `--build-dir` sets the directory containing the compiled U-Boot binaries.
+  If omitted, this is `${source_dir}/build-${board_type}`.
+- `--result-dir` sets the directory to write results, such as log files,
+  into. If omitted, the build directory is used.
+- `--persistent-data-dir` sets the directory used to store persistent test
+  data. This is test data that may be re-used across test runs, such as file-
+  system images.
+
+`pytest` also implements a number of its own command-line options. Please see
+`pytest` documentation for complete details. Execute `py.test --version` for
+a brief summary. Note that U-Boot's test.py script passes all command-line
+arguments directly to `pytest` for processing.
+
+## Testing real hardware
+
+The tools and techniques used to interact with real hardware will vary
+radically between different host and target systems, and the whims of the user.
+For this reason, the test suite does not attempt to directly interact with real
+hardware in any way. Rather, it executes a standardized set of "hook" scripts
+via `$PATH`. These scripts implement certain actions on behalf of the test
+suite. This keeps the test suite simple and isolated from system variances
+unrelated to U-Boot features.
+
+### Hook scripts
+
+#### Environment variables
+
+The following environment variables are set when running hook scripts:
+
+- `UBOOT_BOARD_TYPE` the board type being tested.
+- `UBOOT_BOARD_IDENTITY` the board identity being tested, or `na` if none was
+  specified.
+- `UBOOT_SOURCE_DIR` the U-Boot source directory.
+- `UBOOT_TEST_PY_DIR` the full path to `test/py/` in the source directory.
+- `UBOOT_BUILD_DIR` the U-Boot build directory.
+- `UBOOT_RESULT_DIR` the test result directory.
+- `UBOOT_PERSISTENT_DATA_DIR` the test peristent data directory.
+
+#### `u-boot-test-console`
+
+This script provides access to the U-Boot console. The script's stdin/stdout
+should be connected to the board's console. This process should continue to run
+indefinitely, until killed. The test suite will run this script in parallel
+with all other hooks.
+
+This script may be implemented e.g. by exec()ing `cu`, `kermit`, `conmux`, etc.
+
+If you are able to run U-Boot under a hardware simulator such as qemu, then
+you would likely spawn that simulator from this script. However, note that
+`u-boot-test-reset` may be called multiple times per test script run, and must
+cause U-Boot to start execution from scratch each time. Hopefully your
+simulator includes a virtual reset button! If not, you can launch the
+simulator from `u-boot-test-reset` instead, while arranging for this console
+process to always communicate with the current simulator instance.
+
+#### `u-boot-test-flash`
+
+Prior to running the test suite against a board, some arrangement must be made
+so that the board executes the particular U-Boot binary to be tested. Often,
+this involves writing the U-Boot binary to the board's flash ROM. The test
+suite calls this hook script for that purpose.
+
+This script should perform the entire flashing process synchronously; the
+script should only exit once flashing is complete, and a board reset will
+cause the newly flashed U-Boot binary to be executed.
+
+It is conceivable that this script will do nothing. This might be useful in
+the following cases:
+
+- Some other process has already written the desired U-Boot binary into the
+  board's flash prior to running the test suite.
+- The board allows U-Boot to be downloaded directly into RAM, and executed
+  from there. Use of this feature will reduce wear on the board's flash, so
+  may be preferable if available, and if cold boot testing of U-Boot is not
+  required. If this feature is used, the `u-boot-test-reset` script should
+  peform this download, since the board could conceivably be reset multiple
+  times in a single test run.
+
+It is up to the user to determine if those situations exist, and to code this
+hook script appropriately.
+
+This script will typically be implemented by calling out to some SoC- or
+board-specific vendor flashing utility.
+
+#### `u-boot-test-reset`
+
+Whenever the test suite needs to reset the target board, this script is
+executed. This is guaranteed to happen at least once, prior to executing the
+first test function. If any test fails, the test infra-structure will execute
+this script again to restore U-Boot to an operational state before running the
+next test function.
+
+This script will likely be implemented by communicating with some form of
+relay or electronic switch attached to the board's reset signal.
+
+The semantics of this script require that when it is executed, U-Boot will
+start running from scratch. If the U-Boot binary to be tested has been written
+to flash, pulsing the board's reset signal is likely all this script need do.
+However, in some scenarios, this script may perform other actions. For
+example, it may call out to some SoC- or board-specific vendor utility in order
+to download the U-Boot binary directly into RAM and execute it. This would
+avoid the need for `u-boot-test-flash` to actually write U-Boot to flash, thus
+saving wear on the flash chip(s).
+
+### Board-type-specific configuration
+
+Each board has a different configuration and behaviour. Many of these
+differences can be automatically detected by parsing the `.config` file in the
+build directory. However, some differences can't yet be handled automatically.
+
+For each board, an optional Python module `u_boot_board_${board_type}` may exist
+to provide board-specific information to the test script. Any global value
+defined in these modules is available for use by any test function. The data
+contained in these scripts must be purely derived from U-Boot source code.
+Hence, these configuration files are part of the U-Boot source tree too.
+
+### Execution environment configuration
+
+Each user's hardware setup may enable testing different subsets of the features
+implemented by a particular board's configuration of U-Boot. For example, a
+U-Boot configuration may support USB device mode and USB Mass Storage, but this
+can only be tested if a USB cable is connected between the board and the host
+machine running the test script.
+
+For each board, optional Python modules `u_boot_boardenv_${board_type}` and
+`u_boot_boardenv_${board_type}_${board_identity}` may exist to provide
+board-specific and board-identity-specific information to the test script. Any
+global value defined in these modules is available for use by any test
+function. The data contained in these is specific to a particular user's
+hardware configuration. Hence, these configuration files are not part of the
+U-Boot source tree, and should be installed outside of the source tree. Users
+should set `$PYTHONPATH` prior to running the test script to allow these
+modules to be loaded.
+
+### Board module parameter usage
+
+The test scripts rely on the following variables being defined by the board
+module:
+
+- None at present.
+
+### U-Boot `.config` feature usage
+
+The test scripts rely on various U-Boot `.config` features, either directly in
+order to test those features, or indirectly in order to query information from
+the running U-Boot instance in order to test other features.
+
+One example is that testing of the `md` command requires knowledge of a RAM
+address to use for the test. This data is parsed from the output of the
+`bdinfo` command, and hence relies on CONFIG_CMD_BDI being enabled.
+
+For a complete list of dependencies, please search the test scripts for
+instances of:
+
+- `buildconfig.get(...`
+- `@pytest.mark.buildconfigspec(...`
+
+### Complete invocation example
+
+Assuming that you have installed the hook scripts into $HOME/ubtest/bin, and
+any required environment configuration Python modules into $HOME/ubtest/py,
+then you would likely invoke the test script as follows:
+
+If U-Boot has already been built:
+
+```bash
+PATH=$HOME/ubtest/bin:$PATH \
+    PYTHONPATH=${HOME}/ubtest/py:${PYTHONPATH} \
+    ./test/py/test.py --bd seaboard
+```
+
+If you want the test script to compile U-Boot for you too, then you likely
+need to set `$CROSS_COMPILE` to allow this, and invoke the test script as
+follow:
+
+```bash
+CROSS_COMPILE=arm-none-eabi- \
+    PATH=$HOME/ubtest/bin:$PATH \
+    PYTHONPATH=${HOME}/ubtest/py:${PYTHONPATH} \
+    ./test/py/test.py --bd seaboard --build
+```
+
+## Writing tests
+
+Please refer to the pytest documentation for details of writing pytest tests.
+Details specific to the U-Boot test suite are described below.
+
+A test fixture named `u_boot_console` should be used by each test function. This
+provides the means to interact with the U-Boot console, and retrieve board and
+environment configuration information.
+
+The function `u_boot_console.run_command()` executes a shell command on the
+U-Boot console, and returns all output from that command. This allows
+validation or interpretation of the command output. This function validates
+that certain strings are not seen on the U-Boot console. These include shell
+error messages and the U-Boot sign-on message (in order to detect unexpected
+board resets). See the source of `u_boot_console_base.py` for a complete list of
+"bad" strings. Some test scenarios are expected to trigger these strings. Use
+`u_boot_console.disable_check()` to temporarily disable checking for specific
+strings. See `test_unknown_cmd.py` for an example.
+
+Board- and board-environment configuration values may be accessed as sub-fields
+of the `u_boot_console.config` object, for example
+`u_boot_console.config.ram_base`.
+
+Build configuration values (from `.config`) may be accessed via the dictionary
+`u_boot_console.config.buildconfig`, with keys equal to the Kconfig variable
+names.
diff --git a/test/py/conftest.py b/test/py/conftest.py
new file mode 100644
index 0000000000000000000000000000000000000000..e1674dfce053512be1dccd923371604db6bceb37
--- /dev/null
+++ b/test/py/conftest.py
@@ -0,0 +1,422 @@
+# Copyright (c) 2015 Stephen Warren
+# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
+#
+# SPDX-License-Identifier: GPL-2.0
+
+# Implementation of pytest run-time hook functions. These are invoked by
+# pytest at certain points during operation, e.g. startup, for each executed
+# test, at shutdown etc. These hooks perform functions such as:
+# - Parsing custom command-line options.
+# - Pullilng in user-specified board configuration.
+# - Creating the U-Boot console test fixture.
+# - Creating the HTML log file.
+# - Monitoring each test's results.
+# - Implementing custom pytest markers.
+
+import atexit
+import errno
+import os
+import os.path
+import pexpect
+import pytest
+from _pytest.runner import runtestprotocol
+import ConfigParser
+import StringIO
+import sys
+
+# Globals: The HTML log file, and the connection to the U-Boot console.
+log = None
+console = None
+
+def mkdir_p(path):
+    '''Create a directory path.
+
+    This includes creating any intermediate/parent directories. Any errors
+    caused due to already extant directories are ignored.
+
+    Args:
+        path: The directory path to create.
+
+    Returns:
+        Nothing.
+    '''
+
+    try:
+        os.makedirs(path)
+    except OSError as exc:
+        if exc.errno == errno.EEXIST and os.path.isdir(path):
+            pass
+        else:
+            raise
+
+def pytest_addoption(parser):
+    '''pytest hook: Add custom command-line options to the cmdline parser.
+
+    Args:
+        parser: The pytest command-line parser.
+
+    Returns:
+        Nothing.
+    '''
+
+    parser.addoption('--build-dir', default=None,
+        help='U-Boot build directory (O=)')
+    parser.addoption('--result-dir', default=None,
+        help='U-Boot test result/tmp directory')
+    parser.addoption('--persistent-data-dir', default=None,
+        help='U-Boot test persistent generated data directory')
+    parser.addoption('--board-type', '--bd', '-B', default='sandbox',
+        help='U-Boot board type')
+    parser.addoption('--board-identity', '--id', default='na',
+        help='U-Boot board identity/instance')
+    parser.addoption('--build', default=False, action='store_true',
+        help='Compile U-Boot before running tests')
+
+def pytest_configure(config):
+    '''pytest hook: Perform custom initialization at startup time.
+
+    Args:
+        config: The pytest configuration.
+
+    Returns:
+        Nothing.
+    '''
+
+    global log
+    global console
+    global ubconfig
+
+    test_py_dir = os.path.dirname(os.path.abspath(__file__))
+    source_dir = os.path.dirname(os.path.dirname(test_py_dir))
+
+    board_type = config.getoption('board_type')
+    board_type_filename = board_type.replace('-', '_')
+
+    board_identity = config.getoption('board_identity')
+    board_identity_filename = board_identity.replace('-', '_')
+
+    build_dir = config.getoption('build_dir')
+    if not build_dir:
+        build_dir = source_dir + '/build-' + board_type
+    mkdir_p(build_dir)
+
+    result_dir = config.getoption('result_dir')
+    if not result_dir:
+        result_dir = build_dir
+    mkdir_p(result_dir)
+
+    persistent_data_dir = config.getoption('persistent_data_dir')
+    if not persistent_data_dir:
+        persistent_data_dir = build_dir + '/persistent-data'
+    mkdir_p(persistent_data_dir)
+
+    import multiplexed_log
+    log = multiplexed_log.Logfile(result_dir + '/test-log.html')
+
+    if config.getoption('build'):
+        if build_dir != source_dir:
+            o_opt = 'O=%s' % build_dir
+        else:
+            o_opt = ''
+        cmds = (
+            ['make', o_opt, '-s', board_type + '_defconfig'],
+            ['make', o_opt, '-s', '-j8'],
+        )
+        runner = log.get_runner('make', sys.stdout)
+        for cmd in cmds:
+            runner.run(cmd, cwd=source_dir)
+        runner.close()
+
+    class ArbitraryAttributeContainer(object):
+        pass
+
+    ubconfig = ArbitraryAttributeContainer()
+    ubconfig.brd = dict()
+    ubconfig.env = dict()
+
+    modules = [
+        (ubconfig.brd, 'u_boot_board_' + board_type_filename),
+        (ubconfig.env, 'u_boot_boardenv_' + board_type_filename),
+        (ubconfig.env, 'u_boot_boardenv_' + board_type_filename + '_' +
+            board_identity_filename),
+    ]
+    for (dict_to_fill, module_name) in modules:
+        try:
+            module = __import__(module_name)
+        except ImportError:
+            continue
+        dict_to_fill.update(module.__dict__)
+
+    ubconfig.buildconfig = dict()
+
+    for conf_file in ('.config', 'include/autoconf.mk'):
+        dot_config = build_dir + '/' + conf_file
+        if not os.path.exists(dot_config):
+            raise Exception(conf_file + ' does not exist; ' +
+                'try passing --build option?')
+
+        with open(dot_config, 'rt') as f:
+            ini_str = '[root]\n' + f.read()
+            ini_sio = StringIO.StringIO(ini_str)
+            parser = ConfigParser.RawConfigParser()
+            parser.readfp(ini_sio)
+            ubconfig.buildconfig.update(parser.items('root'))
+
+    ubconfig.test_py_dir = test_py_dir
+    ubconfig.source_dir = source_dir
+    ubconfig.build_dir = build_dir
+    ubconfig.result_dir = result_dir
+    ubconfig.persistent_data_dir = persistent_data_dir
+    ubconfig.board_type = board_type
+    ubconfig.board_identity = board_identity
+
+    env_vars = (
+        'board_type',
+        'board_identity',
+        'source_dir',
+        'test_py_dir',
+        'build_dir',
+        'result_dir',
+        'persistent_data_dir',
+    )
+    for v in env_vars:
+        os.environ['U_BOOT_' + v.upper()] = getattr(ubconfig, v)
+
+    if board_type == 'sandbox':
+        import u_boot_console_sandbox
+        console = u_boot_console_sandbox.ConsoleSandbox(log, ubconfig)
+    else:
+        import u_boot_console_exec_attach
+        console = u_boot_console_exec_attach.ConsoleExecAttach(log, ubconfig)
+
+def pytest_generate_tests(metafunc):
+    '''pytest hook: parameterize test functions based on custom rules.
+
+    If a test function takes parameter(s) (fixture names) of the form brd__xxx
+    or env__xxx, the brd and env configuration dictionaries are consulted to
+    find the list of values to use for those parameters, and the test is
+    parametrized so that it runs once for each combination of values.
+
+    Args:
+        metafunc: The pytest test function.
+
+    Returns:
+        Nothing.
+    '''
+
+    subconfigs = {
+        'brd': console.config.brd,
+        'env': console.config.env,
+    }
+    for fn in metafunc.fixturenames:
+        parts = fn.split('__')
+        if len(parts) < 2:
+            continue
+        if parts[0] not in subconfigs:
+            continue
+        subconfig = subconfigs[parts[0]]
+        vals = []
+        val = subconfig.get(fn, [])
+        # If that exact name is a key in the data source:
+        if val:
+            # ... use the dict value as a single parameter value.
+            vals = (val, )
+        else:
+            # ... otherwise, see if there's a key that contains a list of
+            # values to use instead.
+            vals = subconfig.get(fn + 's', [])
+        metafunc.parametrize(fn, vals)
+
+@pytest.fixture(scope='session')
+def u_boot_console(request):
+    '''Generate the value of a test's u_boot_console fixture.
+
+    Args:
+        request: The pytest request.
+
+    Returns:
+        The fixture value.
+    '''
+
+    return console
+
+tests_not_run = set()
+tests_failed = set()
+tests_skipped = set()
+tests_passed = set()
+
+def pytest_itemcollected(item):
+    '''pytest hook: Called once for each test found during collection.
+
+    This enables our custom result analysis code to see the list of all tests
+    that should eventually be run.
+
+    Args:
+        item: The item that was collected.
+
+    Returns:
+        Nothing.
+    '''
+
+    tests_not_run.add(item.name)
+
+def cleanup():
+    '''Clean up all global state.
+
+    Executed (via atexit) once the entire test process is complete. This
+    includes logging the status of all tests, and the identity of any failed
+    or skipped tests.
+
+    Args:
+        None.
+
+    Returns:
+        Nothing.
+    '''
+
+    if console:
+        console.close()
+    if log:
+        log.status_pass('%d passed' % len(tests_passed))
+        if tests_skipped:
+            log.status_skipped('%d skipped' % len(tests_skipped))
+            for test in tests_skipped:
+                log.status_skipped('... ' + test)
+        if tests_failed:
+            log.status_fail('%d failed' % len(tests_failed))
+            for test in tests_failed:
+                log.status_fail('... ' + test)
+        if tests_not_run:
+            log.status_fail('%d not run' % len(tests_not_run))
+            for test in tests_not_run:
+                log.status_fail('... ' + test)
+        log.close()
+atexit.register(cleanup)
+
+def setup_boardspec(item):
+    '''Process any 'boardspec' marker for a test.
+
+    Such a marker lists the set of board types that a test does/doesn't
+    support. If tests are being executed on an unsupported board, the test is
+    marked to be skipped.
+
+    Args:
+        item: The pytest test item.
+
+    Returns:
+        Nothing.
+    '''
+
+    mark = item.get_marker('boardspec')
+    if not mark:
+        return
+    required_boards = []
+    for board in mark.args:
+        if board.startswith('!'):
+            if ubconfig.board_type == board[1:]:
+                pytest.skip('board not supported')
+                return
+        else:
+            required_boards.append(board)
+    if required_boards and ubconfig.board_type not in required_boards:
+        pytest.skip('board not supported')
+
+def setup_buildconfigspec(item):
+    '''Process any 'buildconfigspec' marker for a test.
+
+    Such a marker lists some U-Boot configuration feature that the test
+    requires. If tests are being executed on an U-Boot build that doesn't
+    have the required feature, the test is marked to be skipped.
+
+    Args:
+        item: The pytest test item.
+
+    Returns:
+        Nothing.
+    '''
+
+    mark = item.get_marker('buildconfigspec')
+    if not mark:
+        return
+    for option in mark.args:
+        if not ubconfig.buildconfig.get('config_' + option.lower(), None):
+            pytest.skip('.config feature not enabled')
+
+def pytest_runtest_setup(item):
+    '''pytest hook: Configure (set up) a test item.
+
+    Called once for each test to perform any custom configuration. This hook
+    is used to skip the test if certain conditions apply.
+
+    Args:
+        item: The pytest test item.
+
+    Returns:
+        Nothing.
+    '''
+
+    log.start_section(item.name)
+    setup_boardspec(item)
+    setup_buildconfigspec(item)
+
+def pytest_runtest_protocol(item, nextitem):
+    '''pytest hook: Called to execute a test.
+
+    This hook wraps the standard pytest runtestprotocol() function in order
+    to acquire visibility into, and record, each test function's result.
+
+    Args:
+        item: The pytest test item to execute.
+        nextitem: The pytest test item that will be executed after this one.
+
+    Returns:
+        A list of pytest reports (test result data).
+    '''
+
+    reports = runtestprotocol(item, nextitem=nextitem)
+    failed = None
+    skipped = None
+    for report in reports:
+        if report.outcome == 'failed':
+            failed = report
+            break
+        if report.outcome == 'skipped':
+            if not skipped:
+                skipped = report
+
+    if failed:
+        tests_failed.add(item.name)
+    elif skipped:
+        tests_skipped.add(item.name)
+    else:
+        tests_passed.add(item.name)
+    tests_not_run.remove(item.name)
+
+    try:
+        if failed:
+            msg = 'FAILED:\n' + str(failed.longrepr)
+            log.status_fail(msg)
+        elif skipped:
+            msg = 'SKIPPED:\n' + str(skipped.longrepr)
+            log.status_skipped(msg)
+        else:
+            log.status_pass('OK')
+    except:
+        # If something went wrong with logging, it's better to let the test
+        # process continue, which may report other exceptions that triggered
+        # the logging issue (e.g. console.log wasn't created). Hence, just
+        # squash the exception. If the test setup failed due to e.g. syntax
+        # error somewhere else, this won't be seen. However, once that issue
+        # is fixed, if this exception still exists, it will then be logged as
+        # part of the test's stdout.
+        import traceback
+        print 'Exception occurred while logging runtest status:'
+        traceback.print_exc()
+        # FIXME: Can we force a test failure here?
+
+    log.end_section(item.name)
+
+    if failed:
+        console.cleanup_spawn()
+
+    return reports
diff --git a/test/py/multiplexed_log.css b/test/py/multiplexed_log.css
new file mode 100644
index 0000000000000000000000000000000000000000..50f7b9092983d0bcf447aef98e13cbb2cb42990b
--- /dev/null
+++ b/test/py/multiplexed_log.css
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2015 Stephen Warren
+ * Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved.
+ *
+ * SPDX-License-Identifier: GPL-2.0
+ */
+
+/*
+ * This provides pretty formatting of the HTML log file, e.g.
+ * - colored bars beside/above log sections for easily parsed delineation.
+ * - color highlighting of various messages.
+ */
+
+body {
+    background-color: black;
+    color: #ffffff;
+}
+
+pre {
+    margin-top: 0px;
+    margin-bottom: 0px;
+}
+
+.implicit {
+    color: #808080;
+}
+
+.section {
+    border-style: solid;
+    border-color: #303030;
+    border-width: 0px 0px 0px 5px;
+    padding-left: 5px
+}
+
+.section-header {
+    background-color: #303030;
+    margin-left: -5px;
+    margin-top: 5px;
+}
+
+.section-trailer {
+    display: none;
+}
+
+.stream {
+    border-style: solid;
+    border-color: #303030;
+    border-width: 0px 0px 0px 5px;
+    padding-left: 5px
+}
+
+.stream-header {
+    background-color: #303030;
+    margin-left: -5px;
+    margin-top: 5px;
+}
+
+.stream-trailer {
+    display: none;
+}
+
+.error {
+    color: #ff0000
+}
+
+.warning {
+    color: #ffff00
+}
+
+.info {
+    color: #808080
+}
+
+.action {
+    color: #8080ff
+}
+
+.status-pass {
+    color: #00ff00
+}
+
+.status-skipped {
+    color: #ffff00
+}
+
+.status-fail {
+    color: #ff0000
+}
diff --git a/test/py/multiplexed_log.py b/test/py/multiplexed_log.py
new file mode 100644
index 0000000000000000000000000000000000000000..48f2b51de15f7f6a1ef3b9c407f5bcf49cc785f9
--- /dev/null
+++ b/test/py/multiplexed_log.py
@@ -0,0 +1,515 @@
+# Copyright (c) 2015 Stephen Warren
+# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
+#
+# SPDX-License-Identifier: GPL-2.0
+
+# Generate an HTML-formatted log file containing multiple streams of data,
+# each represented in a well-delineated/-structured fashion.
+
+import cgi
+import os.path
+import shutil
+import subprocess
+
+mod_dir = os.path.dirname(os.path.abspath(__file__))
+
+class LogfileStream(object):
+    '''A file-like object used to write a single logical stream of data into
+    a multiplexed log file. Objects of this type should be created by factory
+    functions in the Logfile class rather than directly.'''
+
+    def __init__(self, logfile, name, chained_file):
+        '''Initialize a new object.
+
+        Args:
+            logfile: The Logfile object to log to.
+            name: The name of this log stream.
+            chained_file: The file-like object to which all stream data should be
+            logged to in addition to logfile. Can be None.
+
+        Returns:
+            Nothing.
+        '''
+
+        self.logfile = logfile
+        self.name = name
+        self.chained_file = chained_file
+
+    def close(self):
+        '''Dummy function so that this class is "file-like".
+
+        Args:
+            None.
+
+        Returns:
+            Nothing.
+        '''
+
+        pass
+
+    def write(self, data, implicit=False):
+        '''Write data to the log stream.
+
+        Args:
+            data: The data to write tot he file.
+            implicit: Boolean indicating whether data actually appeared in the
+                stream, or was implicitly generated. A valid use-case is to
+                repeat a shell prompt at the start of each separate log
+                section, which makes the log sections more readable in
+                isolation.
+
+        Returns:
+            Nothing.
+        '''
+
+        self.logfile.write(self, data, implicit)
+        if self.chained_file:
+            self.chained_file.write(data)
+
+    def flush(self):
+        '''Flush the log stream, to ensure correct log interleaving.
+
+        Args:
+            None.
+
+        Returns:
+            Nothing.
+        '''
+
+        self.logfile.flush()
+        if self.chained_file:
+            self.chained_file.flush()
+
+class RunAndLog(object):
+    '''A utility object used to execute sub-processes and log their output to
+    a multiplexed log file. Objects of this type should be created by factory
+    functions in the Logfile class rather than directly.'''
+
+    def __init__(self, logfile, name, chained_file):
+        '''Initialize a new object.
+
+        Args:
+            logfile: The Logfile object to log to.
+            name: The name of this log stream or sub-process.
+            chained_file: The file-like object to which all stream data should
+                be logged to in addition to logfile. Can be None.
+
+        Returns:
+            Nothing.
+        '''
+
+        self.logfile = logfile
+        self.name = name
+        self.chained_file = chained_file
+
+    def close(self):
+        '''Clean up any resources managed by this object.'''
+        pass
+
+    def run(self, cmd, cwd=None):
+        '''Run a command as a sub-process, and log the results.
+
+        Args:
+            cmd: The command to execute.
+            cwd: The directory to run the command in. Can be None to use the
+                current directory.
+
+        Returns:
+            Nothing.
+        '''
+
+        msg = "+" + " ".join(cmd) + "\n"
+        if self.chained_file:
+            self.chained_file.write(msg)
+        self.logfile.write(self, msg)
+
+        try:
+            p = subprocess.Popen(cmd, cwd=cwd,
+                stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+            (stdout, stderr) = p.communicate()
+            output = ''
+            if stdout:
+                if stderr:
+                    output += 'stdout:\n'
+                output += stdout
+            if stderr:
+                if stdout:
+                    output += 'stderr:\n'
+                output += stderr
+            exit_status = p.returncode
+            exception = None
+        except subprocess.CalledProcessError as cpe:
+            output = cpe.output
+            exit_status = cpe.returncode
+            exception = cpe
+        except Exception as e:
+            output = ''
+            exit_status = 0
+            exception = e
+        if output and not output.endswith('\n'):
+            output += '\n'
+        if exit_status and not exception:
+            exception = Exception('Exit code: ' + str(exit_status))
+        if exception:
+            output += str(exception) + '\n'
+        self.logfile.write(self, output)
+        if self.chained_file:
+            self.chained_file.write(output)
+        if exception:
+            raise exception
+
+class SectionCtxMgr(object):
+    '''A context manager for Python's "with" statement, which allows a certain
+    portion of test code to be logged to a separate section of the log file.
+    Objects of this type should be created by factory functions in the Logfile
+    class rather than directly.'''
+
+    def __init__(self, log, marker):
+        '''Initialize a new object.
+
+        Args:
+            log: The Logfile object to log to.
+            marker: The name of the nested log section.
+
+        Returns:
+            Nothing.
+        '''
+
+        self.log = log
+        self.marker = marker
+
+    def __enter__(self):
+        self.log.start_section(self.marker)
+
+    def __exit__(self, extype, value, traceback):
+        self.log.end_section(self.marker)
+
+class Logfile(object):
+    '''Generates an HTML-formatted log file containing multiple streams of
+    data, each represented in a well-delineated/-structured fashion.'''
+
+    def __init__(self, fn):
+        '''Initialize a new object.
+
+        Args:
+            fn: The filename to write to.
+
+        Returns:
+            Nothing.
+        '''
+
+        self.f = open(fn, "wt")
+        self.last_stream = None
+        self.blocks = []
+        self.cur_evt = 1
+        shutil.copy(mod_dir + "/multiplexed_log.css", os.path.dirname(fn))
+        self.f.write("""\
+<html>
+<head>
+<link rel="stylesheet" type="text/css" href="multiplexed_log.css">
+</head>
+<body>
+<tt>
+""")
+
+    def close(self):
+        '''Close the log file.
+
+        After calling this function, no more data may be written to the log.
+
+        Args:
+            None.
+
+        Returns:
+            Nothing.
+        '''
+
+        self.f.write("""\
+</tt>
+</body>
+</html>
+""")
+        self.f.close()
+
+    # The set of characters that should be represented as hexadecimal codes in
+    # the log file.
+    _nonprint = ("%" + "".join(chr(c) for c in range(0, 32) if c not in (9, 10)) +
+                 "".join(chr(c) for c in range(127, 256)))
+
+    def _escape(self, data):
+        '''Render data format suitable for inclusion in an HTML document.
+
+        This includes HTML-escaping certain characters, and translating
+        control characters to a hexadecimal representation.
+
+        Args:
+            data: The raw string data to be escaped.
+
+        Returns:
+            An escaped version of the data.
+        '''
+
+        data = data.replace(chr(13), "")
+        data = "".join((c in self._nonprint) and ("%%%02x" % ord(c)) or
+                       c for c in data)
+        data = cgi.escape(data)
+        return data
+
+    def _terminate_stream(self):
+        '''Write HTML to the log file to terminate the current stream's data.
+
+        Args:
+            None.
+
+        Returns:
+            Nothing.
+        '''
+
+        self.cur_evt += 1
+        if not self.last_stream:
+            return
+        self.f.write("</pre>\n")
+        self.f.write("<div class=\"stream-trailer\" id=\"" +
+                     self.last_stream.name + "\">End stream: " +
+                     self.last_stream.name + "</div>\n")
+        self.f.write("</div>\n")
+        self.last_stream = None
+
+    def _note(self, note_type, msg):
+        '''Write a note or one-off message to the log file.
+
+        Args:
+            note_type: The type of note. This must be a value supported by the
+                accompanying multiplexed_log.css.
+            msg: The note/message to log.
+
+        Returns:
+            Nothing.
+        '''
+
+        self._terminate_stream()
+        self.f.write("<div class=\"" + note_type + "\">\n<pre>")
+        self.f.write(self._escape(msg))
+        self.f.write("\n</pre></div>\n")
+
+    def start_section(self, marker):
+        '''Begin a new nested section in the log file.
+
+        Args:
+            marker: The name of the section that is starting.
+
+        Returns:
+            Nothing.
+        '''
+
+        self._terminate_stream()
+        self.blocks.append(marker)
+        blk_path = "/".join(self.blocks)
+        self.f.write("<div class=\"section\" id=\"" + blk_path + "\">\n")
+        self.f.write("<div class=\"section-header\" id=\"" + blk_path +
+                     "\">Section: " + blk_path + "</div>\n")
+
+    def end_section(self, marker):
+        '''Terminate the current nested section in the log file.
+
+        This function validates proper nesting of start_section() and
+        end_section() calls. If a mismatch is found, an exception is raised.
+
+        Args:
+            marker: The name of the section that is ending.
+
+        Returns:
+            Nothing.
+        '''
+
+        if (not self.blocks) or (marker != self.blocks[-1]):
+            raise Exception("Block nesting mismatch: \"%s\" \"%s\"" %
+                            (marker, "/".join(self.blocks)))
+        self._terminate_stream()
+        blk_path = "/".join(self.blocks)
+        self.f.write("<div class=\"section-trailer\" id=\"section-trailer-" +
+                     blk_path + "\">End section: " + blk_path + "</div>\n")
+        self.f.write("</div>\n")
+        self.blocks.pop()
+
+    def section(self, marker):
+        '''Create a temporary section in the log file.
+
+        This function creates a context manager for Python's "with" statement,
+        which allows a certain portion of test code to be logged to a separate
+        section of the log file.
+
+        Usage:
+            with log.section("somename"):
+                some test code
+
+        Args:
+            marker: The name of the nested section.
+
+        Returns:
+            A context manager object.
+        '''
+
+        return SectionCtxMgr(self, marker)
+
+    def error(self, msg):
+        '''Write an error note to the log file.
+
+        Args:
+            msg: A message describing the error.
+
+        Returns:
+            Nothing.
+        '''
+
+        self._note("error", msg)
+
+    def warning(self, msg):
+        '''Write an warning note to the log file.
+
+        Args:
+            msg: A message describing the warning.
+
+        Returns:
+            Nothing.
+        '''
+
+        self._note("warning", msg)
+
+    def info(self, msg):
+        '''Write an informational note to the log file.
+
+        Args:
+            msg: An informational message.
+
+        Returns:
+            Nothing.
+        '''
+
+        self._note("info", msg)
+
+    def action(self, msg):
+        '''Write an action note to the log file.
+
+        Args:
+            msg: A message describing the action that is being logged.
+
+        Returns:
+            Nothing.
+        '''
+
+        self._note("action", msg)
+
+    def status_pass(self, msg):
+        '''Write a note to the log file describing test(s) which passed.
+
+        Args:
+            msg: A message describing passed test(s).
+
+        Returns:
+            Nothing.
+        '''
+
+        self._note("status-pass", msg)
+
+    def status_skipped(self, msg):
+        '''Write a note to the log file describing skipped test(s).
+
+        Args:
+            msg: A message describing passed test(s).
+
+        Returns:
+            Nothing.
+        '''
+
+        self._note("status-skipped", msg)
+
+    def status_fail(self, msg):
+        '''Write a note to the log file describing failed test(s).
+
+        Args:
+            msg: A message describing passed test(s).
+
+        Returns:
+            Nothing.
+        '''
+
+        self._note("status-fail", msg)
+
+    def get_stream(self, name, chained_file=None):
+        '''Create an object to log a single stream's data into the log file.
+
+        This creates a "file-like" object that can be written to in order to
+        write a single stream's data to the log file. The implementation will
+        handle any required interleaving of data (from multiple streams) in
+        the log, in a way that makes it obvious which stream each bit of data
+        came from.
+
+        Args:
+            name: The name of the stream.
+            chained_file: The file-like object to which all stream data should
+                be logged to in addition to this log. Can be None.
+
+        Returns:
+            A file-like object.
+        '''
+
+        return LogfileStream(self, name, chained_file)
+
+    def get_runner(self, name, chained_file=None):
+        '''Create an object that executes processes and logs their output.
+
+        Args:
+            name: The name of this sub-process.
+            chained_file: The file-like object to which all stream data should
+                be logged to in addition to logfile. Can be None.
+
+        Returns:
+            A RunAndLog object.
+        '''
+
+        return RunAndLog(self, name, chained_file)
+
+    def write(self, stream, data, implicit=False):
+        '''Write stream data into the log file.
+
+        This function should only be used by instances of LogfileStream or
+        RunAndLog.
+
+        Args:
+            stream: The stream whose data is being logged.
+            data: The data to log.
+            implicit: Boolean indicating whether data actually appeared in the
+                stream, or was implicitly generated. A valid use-case is to
+                repeat a shell prompt at the start of each separate log
+                section, which makes the log sections more readable in
+                isolation.
+
+        Returns:
+            Nothing.
+        '''
+
+        if stream != self.last_stream:
+            self._terminate_stream()
+            self.f.write("<div class=\"stream\" id=\"%s\">\n" % stream.name)
+            self.f.write("<div class=\"stream-header\" id=\"" + stream.name +
+                         "\">Stream: " + stream.name + "</div>\n")
+            self.f.write("<pre>")
+        if implicit:
+            self.f.write("<span class=\"implicit\">")
+        self.f.write(self._escape(data))
+        if implicit:
+            self.f.write("</span>")
+        self.last_stream = stream
+
+    def flush(self):
+        '''Flush the log stream, to ensure correct log interleaving.
+
+        Args:
+            None.
+
+        Returns:
+            Nothing.
+        '''
+
+        self.f.flush()
diff --git a/test/py/pytest.ini b/test/py/pytest.ini
new file mode 100644
index 0000000000000000000000000000000000000000..67e514f42058c785fd16f9e516775627a7a84490
--- /dev/null
+++ b/test/py/pytest.ini
@@ -0,0 +1,11 @@
+# Copyright (c) 2015 Stephen Warren
+# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
+#
+# SPDX-License-Identifier: GPL-2.0
+
+# Static configuration data for pytest. pytest reads this at startup time.
+
+[pytest]
+markers =
+    boardspec: U-Boot: Describes the set of boards a test can/can't run on.
+    buildconfigspec: U-Boot: Describes Kconfig/config-header constraints.
diff --git a/test/py/test.py b/test/py/test.py
new file mode 100755
index 0000000000000000000000000000000000000000..9c23898774ed3c8a0db98316fe67128b59f95876
--- /dev/null
+++ b/test/py/test.py
@@ -0,0 +1,32 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2015 Stephen Warren
+# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
+#
+# SPDX-License-Identifier: GPL-2.0
+
+# Wrapper script to invoke pytest with the directory name that contains the
+# U-Boot tests.
+
+import os
+import os.path
+import sys
+
+# Get rid of argv[0]
+sys.argv.pop(0)
+
+# argv; py.test test_directory_name user-supplied-arguments
+args = ["py.test", os.path.dirname(__file__) + "/tests"]
+args.extend(sys.argv)
+
+try:
+    os.execvp("py.test", args)
+except:
+    # Log full details of any exception for detailed analysis
+    import traceback
+    traceback.print_exc()
+    # Hint to the user that they likely simply haven't installed the required
+    # dependencies.
+    print >>sys.stderr, """
+exec(py.test) failed; perhaps you are missing some dependencies?
+See test/py/README.md for the list."""
diff --git a/test/py/tests/test_000_version.py b/test/py/tests/test_000_version.py
new file mode 100644
index 0000000000000000000000000000000000000000..d262f0534efd145bdf8fe99f051ab70a0b4f45be
--- /dev/null
+++ b/test/py/tests/test_000_version.py
@@ -0,0 +1,20 @@
+# Copyright (c) 2015 Stephen Warren
+# Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved.
+#
+# SPDX-License-Identifier: GPL-2.0
+
+# pytest runs tests the order of their module path, which is related to the
+# filename containing the test. This file is named such that it is sorted
+# first, simply as a very basic sanity check of the functionality of the U-Boot
+# command prompt.
+
+def test_version(u_boot_console):
+    '''Test that the "version" command prints the U-Boot version.'''
+
+    # "version" prints the U-Boot sign-on message. This is usually considered
+    # an error, so that any unexpected reboot causes an error. Here, this
+    # error detection is disabled since the sign-on message is expected.
+    with u_boot_console.disable_check('main_signon'):
+        response = u_boot_console.run_command('version')
+    # Ensure "version" printed what we expected.
+    u_boot_console.validate_version_string_in_text(response)
diff --git a/test/py/tests/test_env.py b/test/py/tests/test_env.py
new file mode 100644
index 0000000000000000000000000000000000000000..a3e8dd30330b97261f8d6d1ffcc6e967dc56f1b7
--- /dev/null
+++ b/test/py/tests/test_env.py
@@ -0,0 +1,221 @@
+# Copyright (c) 2015 Stephen Warren
+# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
+#
+# SPDX-License-Identifier: GPL-2.0
+
+# Test operation of shell commands relating to environment variables.
+
+import pytest
+
+# FIXME: This might be useful for other tests;
+# perhaps refactor it into ConsoleBase or some other state object?
+class StateTestEnv(object):
+    '''Container that represents the state of all U-Boot environment variables.
+    This enables quick determination of existant/non-existant variable
+    names.
+    '''
+
+    def __init__(self, u_boot_console):
+        '''Initialize a new StateTestEnv object.
+
+        Args:
+            u_boot_console: A U-Boot console.
+
+        Returns:
+            Nothing.
+        '''
+
+        self.u_boot_console = u_boot_console
+        self.get_env()
+        self.set_var = self.get_non_existent_var()
+
+    def get_env(self):
+        '''Read all current environment variables from U-Boot.
+
+        Args:
+            None.
+
+        Returns:
+            Nothing.
+        '''
+
+        response = self.u_boot_console.run_command('printenv')
+        self.env = {}
+        for l in response.splitlines():
+            if not '=' in l:
+                continue
+            (var, value) = l.strip().split('=', 1)
+            self.env[var] = value
+
+    def get_existent_var(self):
+        '''Return the name of an environment variable that exists.
+
+        Args:
+            None.
+
+        Returns:
+            The name of an environment variable.
+        '''
+
+        for var in self.env:
+            return var
+
+    def get_non_existent_var(self):
+        '''Return the name of an environment variable that does not exist.
+
+        Args:
+            None.
+
+        Returns:
+            The name of an environment variable.
+        '''
+
+        n = 0
+        while True:
+            var = 'test_env_' + str(n)
+            if var not in self.env:
+                return var
+            n += 1
+
+@pytest.fixture(scope='module')
+def state_test_env(u_boot_console):
+    '''pytest fixture to provide a StateTestEnv object to tests.'''
+
+    return StateTestEnv(u_boot_console)
+
+def unset_var(state_test_env, var):
+    '''Unset an environment variable.
+
+    This both executes a U-Boot shell command and updates a StateTestEnv
+    object.
+
+    Args:
+        state_test_env: The StateTestEnv object to updata.
+        var: The variable name to unset.
+
+    Returns:
+        Nothing.
+    '''
+
+    state_test_env.u_boot_console.run_command('setenv %s' % var)
+    if var in state_test_env.env:
+        del state_test_env.env[var]
+
+def set_var(state_test_env, var, value):
+    '''Set an environment variable.
+
+    This both executes a U-Boot shell command and updates a StateTestEnv
+    object.
+
+    Args:
+        state_test_env: The StateTestEnv object to updata.
+        var: The variable name to set.
+        value: The value to set the variable to.
+
+    Returns:
+        Nothing.
+    '''
+
+    state_test_env.u_boot_console.run_command('setenv %s "%s"' % (var, value))
+    state_test_env.env[var] = value
+
+def validate_empty(state_test_env, var):
+    '''Validate that a variable is not set, using U-Boot shell commands.
+
+    Args:
+        var: The variable name to test.
+
+    Returns:
+        Nothing.
+    '''
+
+    response = state_test_env.u_boot_console.run_command('echo $%s' % var)
+    assert response == ''
+
+def validate_set(state_test_env, var, value):
+    '''Validate that a variable is set, using U-Boot shell commands.
+
+    Args:
+        var: The variable name to test.
+        value: The value the variable is expected to have.
+
+    Returns:
+        Nothing.
+    '''
+
+    # echo does not preserve leading, internal, or trailing whitespace in the
+    # value. printenv does, and hence allows more complete testing.
+    response = state_test_env.u_boot_console.run_command('printenv %s' % var)
+    assert response == ('%s=%s' % (var, value))
+
+def test_env_echo_exists(state_test_env):
+    '''Test echoing a variable that exists.'''
+
+    var = state_test_env.get_existent_var()
+    value = state_test_env.env[var]
+    validate_set(state_test_env, var, value)
+
+def test_env_echo_non_existent(state_test_env):
+    '''Test echoing a variable that doesn't exist.'''
+
+    var = state_test_env.set_var
+    validate_empty(state_test_env, var)
+
+def test_env_printenv_non_existent(state_test_env):
+    '''Test printenv error message for non-existant variables.'''
+
+    var = state_test_env.set_var
+    c = state_test_env.u_boot_console
+    with c.disable_check('error_notification'):
+        response = c.run_command('printenv %s' % var)
+    assert(response == '## Error: "%s" not defined' % var)
+
+def test_env_unset_non_existent(state_test_env):
+    '''Test unsetting a nonexistent variable.'''
+
+    var = state_test_env.get_non_existent_var()
+    unset_var(state_test_env, var)
+    validate_empty(state_test_env, var)
+
+def test_env_set_non_existent(state_test_env):
+    '''Test set a non-existant variable.'''
+
+    var = state_test_env.set_var
+    value = 'foo'
+    set_var(state_test_env, var, value)
+    validate_set(state_test_env, var, value)
+
+def test_env_set_existing(state_test_env):
+    '''Test setting an existant variable.'''
+
+    var = state_test_env.set_var
+    value = 'bar'
+    set_var(state_test_env, var, value)
+    validate_set(state_test_env, var, value)
+
+def test_env_unset_existing(state_test_env):
+    '''Test unsetting a variable.'''
+
+    var = state_test_env.set_var
+    unset_var(state_test_env, var)
+    validate_empty(state_test_env, var)
+
+def test_env_expansion_spaces(state_test_env):
+    '''Test expanding a variable that contains a space in its value.'''
+
+    var_space = None
+    var_test = None
+    try:
+        var_space = state_test_env.get_non_existent_var()
+        set_var(state_test_env, var_space, ' ')
+
+        var_test = state_test_env.get_non_existent_var()
+        value = ' 1${%(var_space)s}${%(var_space)s} 2 ' % locals()
+        set_var(state_test_env, var_test, value)
+        value = ' 1   2 '
+        validate_set(state_test_env, var_test, value)
+    finally:
+        if var_space:
+            unset_var(state_test_env, var_space)
+        if var_test:
+            unset_var(state_test_env, var_test)
diff --git a/test/py/tests/test_help.py b/test/py/tests/test_help.py
new file mode 100644
index 0000000000000000000000000000000000000000..894f3b5f170001bd1111f1580a3bda34bcdf15ed
--- /dev/null
+++ b/test/py/tests/test_help.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2015 Stephen Warren
+# Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved.
+#
+# SPDX-License-Identifier: GPL-2.0
+
+def test_help(u_boot_console):
+    '''Test that the "help" command can be executed.'''
+
+    u_boot_console.run_command('help')
diff --git a/test/py/tests/test_hush_if_test.py b/test/py/tests/test_hush_if_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..cf4c3aeeb7313473b3a21b6e4da6ea4d0f23a929
--- /dev/null
+++ b/test/py/tests/test_hush_if_test.py
@@ -0,0 +1,154 @@
+# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
+#
+# SPDX-License-Identifier: GPL-2.0
+
+# Test operation of the "if" shell command.
+
+import os
+import os.path
+import pytest
+
+# The list of "if test" conditions to test.
+subtests = (
+    # Base if functionality.
+
+    ('true', True),
+    ('false', False),
+
+    # Basic operators.
+
+    ('test aaa = aaa', True),
+    ('test aaa = bbb', False),
+
+    ('test aaa != bbb', True),
+    ('test aaa != aaa', False),
+
+    ('test aaa < bbb', True),
+    ('test bbb < aaa', False),
+
+    ('test bbb > aaa', True),
+    ('test aaa > bbb', False),
+
+    ('test 123 -eq 123', True),
+    ('test 123 -eq 456', False),
+
+    ('test 123 -ne 456', True),
+    ('test 123 -ne 123', False),
+
+    ('test 123 -lt 456', True),
+    ('test 123 -lt 123', False),
+    ('test 456 -lt 123', False),
+
+    ('test 123 -le 456', True),
+    ('test 123 -le 123', True),
+    ('test 456 -le 123', False),
+
+    ('test 456 -gt 123', True),
+    ('test 123 -gt 123', False),
+    ('test 123 -gt 456', False),
+
+    ('test 456 -ge 123', True),
+    ('test 123 -ge 123', True),
+    ('test 123 -ge 456', False),
+
+    ('test -z ""', True),
+    ('test -z "aaa"', False),
+
+    ('test -n "aaa"', True),
+    ('test -n ""', False),
+
+    # Inversion of simple tests.
+
+    ('test ! aaa = aaa', False),
+    ('test ! aaa = bbb', True),
+    ('test ! ! aaa = aaa', True),
+    ('test ! ! aaa = bbb', False),
+
+    # Binary operators.
+
+    ('test aaa != aaa -o bbb != bbb', False),
+    ('test aaa != aaa -o bbb = bbb', True),
+    ('test aaa = aaa -o bbb != bbb', True),
+    ('test aaa = aaa -o bbb = bbb', True),
+
+    ('test aaa != aaa -a bbb != bbb', False),
+    ('test aaa != aaa -a bbb = bbb', False),
+    ('test aaa = aaa -a bbb != bbb', False),
+    ('test aaa = aaa -a bbb = bbb', True),
+
+    # Inversion within binary operators.
+
+    ('test ! aaa != aaa -o ! bbb != bbb', True),
+    ('test ! aaa != aaa -o ! bbb = bbb', True),
+    ('test ! aaa = aaa -o ! bbb != bbb', True),
+    ('test ! aaa = aaa -o ! bbb = bbb', False),
+
+    ('test ! ! aaa != aaa -o ! ! bbb != bbb', False),
+    ('test ! ! aaa != aaa -o ! ! bbb = bbb', True),
+    ('test ! ! aaa = aaa -o ! ! bbb != bbb', True),
+    ('test ! ! aaa = aaa -o ! ! bbb = bbb', True),
+
+    # -z operator.
+
+    ('test -z "$ut_var_nonexistent"', True),
+    ('test -z "$ut_var_exists"', False),
+)
+
+def exec_hush_if(u_boot_console, expr, result):
+    '''Execute a shell "if" command, and validate its result.'''
+
+    cmd = 'if ' + expr + '; then echo true; else echo false; fi'
+    response = u_boot_console.run_command(cmd)
+    assert response.strip() == str(result).lower()
+
+@pytest.mark.buildconfigspec('sys_hush_parser')
+def test_hush_if_test_setup(u_boot_console):
+    '''Set up environment variables used during the "if" tests.'''
+
+    u_boot_console.run_command('setenv ut_var_nonexistent')
+    u_boot_console.run_command('setenv ut_var_exists 1')
+
+@pytest.mark.buildconfigspec('sys_hush_parser')
+@pytest.mark.parametrize('expr,result', subtests)
+def test_hush_if_test(u_boot_console, expr, result):
+    '''Test a single "if test" condition.'''
+
+    exec_hush_if(u_boot_console, expr, result)
+
+@pytest.mark.buildconfigspec('sys_hush_parser')
+def test_hush_if_test_teardown(u_boot_console):
+    '''Clean up environment variables used during the "if" tests.'''
+
+    u_boot_console.run_command('setenv ut_var_exists')
+
+@pytest.mark.buildconfigspec('sys_hush_parser')
+# We might test this on real filesystems via UMS, DFU, 'save', etc.
+# Of those, only UMS currently allows file removal though.
+@pytest.mark.boardspec('sandbox')
+def test_hush_if_test_host_file_exists(u_boot_console):
+    '''Test the "if test -e" shell command.'''
+
+    test_file = u_boot_console.config.result_dir + \
+        '/creating_this_file_breaks_u_boot_tests'
+
+    try:
+        os.unlink(test_file)
+    except:
+        pass
+    assert not os.path.exists(test_file)
+
+    expr = 'test -e hostfs - ' + test_file
+    exec_hush_if(u_boot_console, expr, False)
+
+    try:
+        with file(test_file, 'wb'):
+            pass
+        assert os.path.exists(test_file)
+
+        expr = 'test -e hostfs - ' + test_file
+        exec_hush_if(u_boot_console, expr, True)
+    finally:
+        os.unlink(test_file)
+
+    expr = 'test -e hostfs - ' + test_file
+    exec_hush_if(u_boot_console, expr, False)
diff --git a/test/py/tests/test_md.py b/test/py/tests/test_md.py
new file mode 100644
index 0000000000000000000000000000000000000000..94603c7df609efe9ab6d8e6b9825b2cf69ee38ea
--- /dev/null
+++ b/test/py/tests/test_md.py
@@ -0,0 +1,36 @@
+# Copyright (c) 2015 Stephen Warren
+# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
+#
+# SPDX-License-Identifier: GPL-2.0
+
+import pytest
+
+@pytest.mark.buildconfigspec('cmd_memory')
+def test_md(u_boot_console):
+    '''Test that md reads memory as expected, and that memory can be modified
+    using the mw command.'''
+
+    ram_base = u_boot_console.find_ram_base()
+    addr = '%08x' % ram_base
+    val = 'a5f09876'
+    expected_response = addr + ': ' + val
+    u_boot_console.run_command('mw ' + addr + ' 0 10')
+    response = u_boot_console.run_command('md ' + addr + ' 10')
+    assert(not (expected_response in response))
+    u_boot_console.run_command('mw ' + addr + ' ' + val)
+    response = u_boot_console.run_command('md ' + addr + ' 10')
+    assert(expected_response in response)
+
+@pytest.mark.buildconfigspec('cmd_memory')
+def test_md_repeat(u_boot_console):
+    '''Test command repeat (via executing an empty command) operates correctly
+    for "md"; the command must repeat and dump an incrementing address.'''
+
+    ram_base = u_boot_console.find_ram_base()
+    addr_base = '%08x' % ram_base
+    words = 0x10
+    addr_repeat = '%08x' % (ram_base + (words * 4))
+    u_boot_console.run_command('md %s %x' % (addr_base, words))
+    response = u_boot_console.run_command('')
+    expected_response = addr_repeat + ': '
+    assert(expected_response in response)
diff --git a/test/py/tests/test_sandbox_exit.py b/test/py/tests/test_sandbox_exit.py
new file mode 100644
index 0000000000000000000000000000000000000000..2aa8eb4abc68656376e3b5fcd5c1f11939427fed
--- /dev/null
+++ b/test/py/tests/test_sandbox_exit.py
@@ -0,0 +1,24 @@
+# Copyright (c) 2015 Stephen Warren
+# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
+#
+# SPDX-License-Identifier: GPL-2.0
+
+import pytest
+import signal
+
+@pytest.mark.boardspec('sandbox')
+@pytest.mark.buildconfigspec('reset')
+def test_reset(u_boot_console):
+    '''Test that the "reset" command exits sandbox process.'''
+
+    u_boot_console.run_command('reset', wait_for_prompt=False)
+    assert(u_boot_console.validate_exited())
+    u_boot_console.ensure_spawned()
+
+@pytest.mark.boardspec('sandbox')
+def test_ctrl_c(u_boot_console):
+    '''Test that sending SIGINT to sandbox causes it to exit.'''
+
+    u_boot_console.kill(signal.SIGINT)
+    assert(u_boot_console.validate_exited())
+    u_boot_console.ensure_spawned()
diff --git a/test/py/tests/test_shell_basics.py b/test/py/tests/test_shell_basics.py
new file mode 100644
index 0000000000000000000000000000000000000000..719ce611d71c9841702354d136d5ac41684e62f9
--- /dev/null
+++ b/test/py/tests/test_shell_basics.py
@@ -0,0 +1,42 @@
+# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
+#
+# SPDX-License-Identifier: GPL-2.0
+
+# Test basic shell functionality, such as commands separate by semi-colons.
+
+def test_shell_execute(u_boot_console):
+    '''Test any shell command.'''
+
+    response = u_boot_console.run_command('echo hello')
+    assert response.strip() == 'hello'
+
+def test_shell_semicolon_two(u_boot_console):
+    '''Test two shell commands separate by a semi-colon.'''
+
+    cmd = 'echo hello; echo world'
+    response = u_boot_console.run_command(cmd)
+    # This validation method ignores the exact whitespace between the strings
+    assert response.index('hello') < response.index('world')
+
+def test_shell_semicolon_three(u_boot_console):
+    '''Test three shell commands separate by a semi-colon, with variable
+    expansion dependencies between them.'''
+
+    cmd = 'setenv list 1; setenv list ${list}2; setenv list ${list}3; ' + \
+        'echo ${list}'
+    response = u_boot_console.run_command(cmd)
+    assert response.strip() == '123'
+    u_boot_console.run_command('setenv list')
+
+def test_shell_run(u_boot_console):
+    '''Test the "run" shell command.'''
+
+    u_boot_console.run_command('setenv foo \"setenv monty 1; setenv python 2\"')
+    u_boot_console.run_command('run foo')
+    response = u_boot_console.run_command('echo $monty')
+    assert response.strip() == '1'
+    response = u_boot_console.run_command('echo $python')
+    assert response.strip() == '2'
+    u_boot_console.run_command('setenv foo')
+    u_boot_console.run_command('setenv monty')
+    u_boot_console.run_command('setenv python')
diff --git a/test/py/tests/test_sleep.py b/test/py/tests/test_sleep.py
new file mode 100644
index 0000000000000000000000000000000000000000..64f1ddf9a09f4e59952b333ed83f863326348647
--- /dev/null
+++ b/test/py/tests/test_sleep.py
@@ -0,0 +1,24 @@
+# Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved.
+#
+# SPDX-License-Identifier: GPL-2.0
+
+import pytest
+import time
+
+def test_sleep(u_boot_console):
+    '''Test the sleep command, and validate that it sleeps for approximately
+    the correct amount of time.'''
+
+    # Do this before we time anything, to make sure U-Boot is already running.
+    # Otherwise, the system boot time is included in the time measurement.
+    u_boot_console.ensure_spawned()
+
+    # 3s isn't too long, but is enough to cross a few second boundaries.
+    sleep_time = 3
+    tstart = time.time()
+    u_boot_console.run_command('sleep %d' % sleep_time)
+    tend = time.time()
+    elapsed = tend - tstart
+    delta_to_expected = abs(elapsed - sleep_time)
+    # 0.25s margin is hopefully enough to account for any system overhead.
+    assert delta_to_expected < 0.25
diff --git a/test/py/tests/test_ums.py b/test/py/tests/test_ums.py
new file mode 100644
index 0000000000000000000000000000000000000000..a137221c7a5b04d27a5c87f14795ee1177492306
--- /dev/null
+++ b/test/py/tests/test_ums.py
@@ -0,0 +1,94 @@
+# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
+#
+# SPDX-License-Identifier: GPL-2.0
+
+# Test U-Boot's "ums" command. At present, this test only ensures that a UMS
+# device can be enumerated by the host/test machine. In the future, this test
+# should be enhanced to validate disk IO.
+
+import os
+import pytest
+import time
+
+'''
+Note: This test relies on:
+
+a) boardenv_* to contain configuration values to define which USB ports are
+available for testing. Without this, this test will be automatically skipped.
+For example:
+
+env__usb_dev_ports = (
+    {'tgt_usb_ctlr': '0', 'host_ums_dev_node': '/dev/disk/by-path/pci-0000:00:14.0-usb-0:13:1.0-scsi-0:0:0:0'},
+)
+
+env__block_devs = (
+    {'type': 'mmc', 'id': '0'}, # eMMC; always present
+    {'type': 'mmc', 'id': '1'}, # SD card; present since I plugged one in
+)
+
+b) udev rules to set permissions on devices nodes, so that sudo is not
+required. For example:
+
+ACTION=="add", SUBSYSTEM=="block", SUBSYSTEMS=="usb", KERNELS=="3-13", MODE:="666"
+
+(You may wish to change the group ID instead of setting the permissions wide
+open. All that matters is that the user ID running the test can access the
+device.)
+'''
+
+def open_ums_device(host_ums_dev_node):
+    '''Attempt to open a device node, returning either the opened file handle,
+    or None on any error.'''
+
+    try:
+        return open(host_ums_dev_node, 'rb')
+    except:
+        return None
+
+def wait_for_ums_device(host_ums_dev_node):
+    '''Continually attempt to open the device node exported by the "ums"
+    command, and either return the opened file handle, or raise an exception
+    after a timeout.'''
+
+    for i in xrange(100):
+        fh = open_ums_device(host_ums_dev_node)
+        if fh:
+            return fh
+        time.sleep(0.1)
+    raise Exception('UMS device did not appear')
+
+def wait_for_ums_device_gone(host_ums_dev_node):
+    '''Continually attempt to open the device node exported by the "ums"
+    command, and either return once the device has disappeared, or raise an
+    exception if it does not before a timeout occurs.'''
+
+    for i in xrange(100):
+        fh = open_ums_device(host_ums_dev_node)
+        if not fh:
+            return
+        fh.close()
+        time.sleep(0.1)
+    raise Exception('UMS device did not disappear')
+
+@pytest.mark.buildconfigspec('cmd_usb_mass_storage')
+def test_ums(u_boot_console, env__usb_dev_port, env__block_devs):
+    '''Test the "ums" command; the host system must be able to enumerate a UMS
+    device when "ums" is running, and this device must disappear when "ums" is
+    aborted.'''
+
+    tgt_usb_ctlr = env__usb_dev_port['tgt_usb_ctlr']
+    host_ums_dev_node = env__usb_dev_port['host_ums_dev_node']
+
+    # We're interested in testing USB device mode on each port, not the cross-
+    # product of that with each device. So, just pick the first entry in the
+    # device list here. We'll test each block device somewhere else.
+    tgt_dev_type = env__block_devs[0]['type']
+    tgt_dev_id = env__block_devs[0]['id']
+
+    cmd = 'ums %s %s %s' % (tgt_usb_ctlr, tgt_dev_type, tgt_dev_id)
+    u_boot_console.run_command('ums 0 mmc 0', wait_for_prompt=False)
+    fh = wait_for_ums_device(host_ums_dev_node)
+    fh.read(4096)
+    fh.close()
+    u_boot_console.ctrlc()
+    wait_for_ums_device_gone(host_ums_dev_node)
diff --git a/test/py/tests/test_unknown_cmd.py b/test/py/tests/test_unknown_cmd.py
new file mode 100644
index 0000000000000000000000000000000000000000..2de93e0026ff075611707e3041bb09357185c97f
--- /dev/null
+++ b/test/py/tests/test_unknown_cmd.py
@@ -0,0 +1,14 @@
+# Copyright (c) 2015 Stephen Warren
+# Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved.
+#
+# SPDX-License-Identifier: GPL-2.0
+
+def test_unknown_command(u_boot_console):
+    '''Test that executing an unknown command causes U-Boot to print an
+    error.'''
+
+    # The "unknown command" error is actively expected here,
+    # so error detection for it is disabled.
+    with u_boot_console.disable_check('unknown_command'):
+        response = u_boot_console.run_command('non_existent_cmd')
+    assert('Unknown command \'non_existent_cmd\' - try \'help\'' in response)
diff --git a/test/py/u_boot_console_base.py b/test/py/u_boot_console_base.py
new file mode 100644
index 0000000000000000000000000000000000000000..520f9a9e9f311d1b66d022ae14016344ee4b0e0f
--- /dev/null
+++ b/test/py/u_boot_console_base.py
@@ -0,0 +1,360 @@
+# Copyright (c) 2015 Stephen Warren
+# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
+#
+# SPDX-License-Identifier: GPL-2.0
+
+# Common logic to interact with U-Boot via the console. This class provides
+# the interface that tests use to execute U-Boot shell commands and wait for
+# their results. Sub-classes exist to perform board-type-specific setup
+# operations, such as spawning a sub-process for Sandbox, or attaching to the
+# serial console of real hardware.
+
+import multiplexed_log
+import os
+import pytest
+import re
+import sys
+
+# Regexes for text we expect U-Boot to send to the console.
+pattern_u_boot_spl_signon = re.compile('(U-Boot SPL \\d{4}\\.\\d{2}-[^\r\n]*)')
+pattern_u_boot_main_signon = re.compile('(U-Boot \\d{4}\\.\\d{2}-[^\r\n]*)')
+pattern_stop_autoboot_prompt = re.compile('Hit any key to stop autoboot: ')
+pattern_unknown_command = re.compile('Unknown command \'.*\' - try \'help\'')
+pattern_error_notification = re.compile('## Error: ')
+
+class ConsoleDisableCheck(object):
+    '''Context manager (for Python's with statement) that temporarily disables
+    the specified console output error check. This is useful when deliberately
+    executing a command that is known to trigger one of the error checks, in
+    order to test that the error condition is actually raised. This class is
+    used internally by ConsoleBase::disable_check(); it is not intended for
+    direct usage.'''
+
+    def __init__(self, console, check_type):
+        self.console = console
+        self.check_type = check_type
+
+    def __enter__(self):
+        self.console.disable_check_count[self.check_type] += 1
+
+    def __exit__(self, extype, value, traceback):
+        self.console.disable_check_count[self.check_type] -= 1
+
+class ConsoleBase(object):
+    '''The interface through which test functions interact with the U-Boot
+    console. This primarily involves executing shell commands, capturing their
+    results, and checking for common error conditions. Some common utilities
+    are also provided too.'''
+
+    def __init__(self, log, config, max_fifo_fill):
+        '''Initialize a U-Boot console connection.
+
+        Can only usefully be called by sub-classes.
+
+        Args:
+            log: A mulptiplex_log.Logfile object, to which the U-Boot output
+                will be logged.
+            config: A configuration data structure, as built by conftest.py.
+            max_fifo_fill: The maximum number of characters to send to U-Boot
+                command-line before waiting for U-Boot to echo the characters
+                back. For UART-based HW without HW flow control, this value
+                should be set less than the UART RX FIFO size to avoid
+                overflow, assuming that U-Boot can't keep up with full-rate
+                traffic at the baud rate.
+
+        Returns:
+            Nothing.
+        '''
+
+        self.log = log
+        self.config = config
+        self.max_fifo_fill = max_fifo_fill
+
+        self.logstream = self.log.get_stream('console', sys.stdout)
+
+        # Array slice removes leading/trailing quotes
+        self.prompt = self.config.buildconfig['config_sys_prompt'][1:-1]
+        self.prompt_escaped = re.escape(self.prompt)
+        self.p = None
+        self.disable_check_count = {
+            'spl_signon': 0,
+            'main_signon': 0,
+            'unknown_command': 0,
+            'error_notification': 0,
+        }
+
+        self.at_prompt = False
+        self.at_prompt_logevt = None
+        self.ram_base = None
+
+    def close(self):
+        '''Terminate the connection to the U-Boot console.
+
+        This function is only useful once all interaction with U-Boot is
+        complete. Once this function is called, data cannot be sent to or
+        received from U-Boot.
+
+        Args:
+            None.
+
+        Returns:
+            Nothing.
+        '''
+
+        if self.p:
+            self.p.close()
+        self.logstream.close()
+
+    def run_command(self, cmd, wait_for_echo=True, send_nl=True,
+            wait_for_prompt=True):
+        '''Execute a command via the U-Boot console.
+
+        The command is always sent to U-Boot.
+
+        U-Boot echoes any command back to its output, and this function
+        typically waits for that to occur. The wait can be disabled by setting
+        wait_for_echo=False, which is useful e.g. when sending CTRL-C to
+        interrupt a long-running command such as "ums".
+
+        Command execution is typically triggered by sending a newline
+        character. This can be disabled by setting send_nl=False, which is
+        also useful when sending CTRL-C.
+
+        This function typically waits for the command to finish executing, and
+        returns the console output that it generated. This can be disabled by
+        setting wait_for_prompt=False, which is useful when invoking a long-
+        running command such as "ums".
+
+        Args:
+            cmd: The command to send.
+            wait_for_each: Boolean indicating whether to wait for U-Boot to
+                echo the command text back to its output.
+            send_nl: Boolean indicating whether to send a newline character
+                after the command string.
+            wait_for_prompt: Boolean indicating whether to wait for the
+                command prompt to be sent by U-Boot. This typically occurs
+                immediately after the command has been executed.
+
+        Returns:
+            If wait_for_prompt == False:
+                Nothing.
+            Else:
+                The output from U-Boot during command execution. In other
+                words, the text U-Boot emitted between the point it echod the
+                command string and emitted the subsequent command prompts.
+        '''
+
+        self.ensure_spawned()
+
+        if self.at_prompt and \
+                self.at_prompt_logevt != self.logstream.logfile.cur_evt:
+            self.logstream.write(self.prompt, implicit=True)
+
+        bad_patterns = []
+        bad_pattern_ids = []
+        if (self.disable_check_count['spl_signon'] == 0 and
+                self.u_boot_spl_signon):
+            bad_patterns.append(self.u_boot_spl_signon_escaped)
+            bad_pattern_ids.append('SPL signon')
+        if self.disable_check_count['main_signon'] == 0:
+            bad_patterns.append(self.u_boot_main_signon_escaped)
+            bad_pattern_ids.append('U-Boot main signon')
+        if self.disable_check_count['unknown_command'] == 0:
+            bad_patterns.append(pattern_unknown_command)
+            bad_pattern_ids.append('Unknown command')
+        if self.disable_check_count['error_notification'] == 0:
+            bad_patterns.append(pattern_error_notification)
+            bad_pattern_ids.append('Error notification')
+        try:
+            self.at_prompt = False
+            if send_nl:
+                cmd += '\n'
+            while cmd:
+                # Limit max outstanding data, so UART FIFOs don't overflow
+                chunk = cmd[:self.max_fifo_fill]
+                cmd = cmd[self.max_fifo_fill:]
+                self.p.send(chunk)
+                if not wait_for_echo:
+                    continue
+                chunk = re.escape(chunk)
+                chunk = chunk.replace('\\\n', '[\r\n]')
+                m = self.p.expect([chunk] + bad_patterns)
+                if m != 0:
+                    self.at_prompt = False
+                    raise Exception('Bad pattern found on console: ' +
+                                    bad_pattern_ids[m - 1])
+            if not wait_for_prompt:
+                return
+            m = self.p.expect([self.prompt_escaped] + bad_patterns)
+            if m != 0:
+                self.at_prompt = False
+                raise Exception('Bad pattern found on console: ' +
+                                bad_pattern_ids[m - 1])
+            self.at_prompt = True
+            self.at_prompt_logevt = self.logstream.logfile.cur_evt
+            # Only strip \r\n; space/TAB might be significant if testing
+            # indentation.
+            return self.p.before.strip('\r\n')
+        except Exception as ex:
+            self.log.error(str(ex))
+            self.cleanup_spawn()
+            raise
+
+    def ctrlc(self):
+        '''Send a CTRL-C character to U-Boot.
+
+        This is useful in order to stop execution of long-running synchronous
+        commands such as "ums".
+
+        Args:
+            None.
+
+        Returns:
+            Nothing.
+        '''
+
+        self.run_command(chr(3), wait_for_echo=False, send_nl=False)
+
+    def ensure_spawned(self):
+        '''Ensure a connection to a correctly running U-Boot instance.
+
+        This may require spawning a new Sandbox process or resetting target
+        hardware, as defined by the implementation sub-class.
+
+        This is an internal function and should not be called directly.
+
+        Args:
+            None.
+
+        Returns:
+            Nothing.
+        '''
+
+        if self.p:
+            return
+        try:
+            self.at_prompt = False
+            self.log.action('Starting U-Boot')
+            self.p = self.get_spawn()
+            # Real targets can take a long time to scroll large amounts of
+            # text if LCD is enabled. This value may need tweaking in the
+            # future, possibly per-test to be optimal. This works for 'help'
+            # on board 'seaboard'.
+            self.p.timeout = 30000
+            self.p.logfile_read = self.logstream
+            if self.config.buildconfig.get('CONFIG_SPL', False) == 'y':
+                self.p.expect([pattern_u_boot_spl_signon])
+                self.u_boot_spl_signon = self.p.after
+                self.u_boot_spl_signon_escaped = re.escape(self.p.after)
+            else:
+                self.u_boot_spl_signon = None
+            self.p.expect([pattern_u_boot_main_signon])
+            self.u_boot_main_signon = self.p.after
+            self.u_boot_main_signon_escaped = re.escape(self.p.after)
+            build_idx = self.u_boot_main_signon.find(', Build:')
+            if build_idx == -1:
+                self.u_boot_version_string = self.u_boot_main_signon
+            else:
+                self.u_boot_version_string = self.u_boot_main_signon[:build_idx]
+            while True:
+                match = self.p.expect([self.prompt_escaped,
+                                       pattern_stop_autoboot_prompt])
+                if match == 1:
+                    self.p.send(chr(3)) # CTRL-C
+                    continue
+                break
+            self.at_prompt = True
+            self.at_prompt_logevt = self.logstream.logfile.cur_evt
+        except Exception as ex:
+            self.log.error(str(ex))
+            self.cleanup_spawn()
+            raise
+
+    def cleanup_spawn(self):
+        '''Shut down all interaction with the U-Boot instance.
+
+        This is used when an error is detected prior to re-establishing a
+        connection with a fresh U-Boot instance.
+
+        This is an internal function and should not be called directly.
+
+        Args:
+            None.
+
+        Returns:
+            Nothing.
+        '''
+
+        try:
+            if self.p:
+                self.p.close()
+        except:
+            pass
+        self.p = None
+
+    def validate_version_string_in_text(self, text):
+        '''Assert that a command's output includes the U-Boot signon message.
+
+        This is primarily useful for validating the "version" command without
+        duplicating the signon text regex in a test function.
+
+        Args:
+            text: The command output text to check.
+
+        Returns:
+            Nothing. An exception is raised if the validation fails.
+        '''
+
+        assert(self.u_boot_version_string in text)
+
+    def disable_check(self, check_type):
+        '''Temporarily disable an error check of U-Boot's output.
+
+        Create a new context manager (for use with the "with" statement) which
+        temporarily disables a particular console output error check.
+
+        Args:
+            check_type: The type of error-check to disable. Valid values may
+            be found in self.disable_check_count above.
+
+        Returns:
+            A context manager object.
+        '''
+
+        return ConsoleDisableCheck(self, check_type)
+
+    def find_ram_base(self):
+        '''Find the running U-Boot's RAM location.
+
+        Probe the running U-Boot to determine the address of the first bank
+        of RAM. This is useful for tests that test reading/writing RAM, or
+        load/save files that aren't associated with some standard address
+        typically represented in an environment variable such as
+        ${kernel_addr_r}. The value is cached so that it only needs to be
+        actively read once.
+
+        Args:
+            None.
+
+        Returns:
+            The address of U-Boot's first RAM bank, as an integer.
+        '''
+
+        if self.config.buildconfig.get('config_cmd_bdi', 'n') != 'y':
+            pytest.skip('bdinfo command not supported')
+        if self.ram_base == -1:
+            pytest.skip('Previously failed to find RAM bank start')
+        if self.ram_base is not None:
+            return self.ram_base
+
+        with self.log.section('find_ram_base'):
+            response = self.run_command('bdinfo')
+            for l in response.split('\n'):
+                if '-> start' in l:
+                    self.ram_base = int(l.split('=')[1].strip(), 16)
+                    break
+            if self.ram_base is None:
+                self.ram_base = -1
+                raise Exception('Failed to find RAM bank start in `bdinfo`')
+
+        return self.ram_base
diff --git a/test/py/u_boot_console_exec_attach.py b/test/py/u_boot_console_exec_attach.py
new file mode 100644
index 0000000000000000000000000000000000000000..0ca9e7c178ec47e06d0b98f49edf3b61b84c5132
--- /dev/null
+++ b/test/py/u_boot_console_exec_attach.py
@@ -0,0 +1,65 @@
+# Copyright (c) 2015 Stephen Warren
+# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
+#
+# SPDX-License-Identifier: GPL-2.0
+
+# Logic to interact with U-Boot running on real hardware, typically via a
+# physical serial port.
+
+import sys
+from u_boot_spawn import Spawn
+from u_boot_console_base import ConsoleBase
+
+class ConsoleExecAttach(ConsoleBase):
+    '''Represents a physical connection to a U-Boot console, typically via a
+    serial port. This implementation executes a sub-process to attach to the
+    console, expecting that the stdin/out of the sub-process will be forwarded
+    to/from the physical hardware. This approach isolates the test infra-
+    structure from the user-/installation-specific details of how to
+    communicate with, and the identity of, serial ports etc.'''
+
+    def __init__(self, log, config):
+        '''Initialize a U-Boot console connection.
+
+        Args:
+            log: A multiplexed_log.Logfile instance.
+            config: A "configuration" object as defined in conftest.py.
+
+        Returns:
+            Nothing.
+        '''
+
+        # The max_fifo_fill value might need tweaking per-board/-SoC?
+        # 1 would be safe anywhere, but is very slow (a pexpect issue?).
+        # 16 is a common FIFO size.
+        # HW flow control would mean this could be infinite.
+        super(ConsoleExecAttach, self).__init__(log, config, max_fifo_fill=16)
+
+        self.log.action('Flashing U-Boot')
+        cmd = ['u-boot-test-flash', config.board_type, config.board_identity]
+        runner = self.log.get_runner(cmd[0], sys.stdout)
+        runner.run(cmd)
+        runner.close()
+
+    def get_spawn(self):
+        '''Connect to a fresh U-Boot instance.
+
+        The target board is reset, so that U-Boot begins running from scratch.
+
+        Args:
+            None.
+
+        Returns:
+            A u_boot_spawn.Spawn object that is attached to U-Boot.
+        '''
+
+        args = [self.config.board_type, self.config.board_identity]
+        s = Spawn(['u-boot-test-console'] + args)
+
+        self.log.action('Resetting board')
+        cmd = ['u-boot-test-reset'] + args
+        runner = self.log.get_runner(cmd[0], sys.stdout)
+        runner.run(cmd)
+        runner.close()
+
+        return s
diff --git a/test/py/u_boot_console_sandbox.py b/test/py/u_boot_console_sandbox.py
new file mode 100644
index 0000000000000000000000000000000000000000..88b137e8c3a1feff164bc5dfd4f26d82f82eecc4
--- /dev/null
+++ b/test/py/u_boot_console_sandbox.py
@@ -0,0 +1,79 @@
+# Copyright (c) 2015 Stephen Warren
+# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
+#
+# SPDX-License-Identifier: GPL-2.0
+
+# Logic to interact with the sandbox port of U-Boot, running as a sub-process.
+
+import time
+from u_boot_spawn import Spawn
+from u_boot_console_base import ConsoleBase
+
+class ConsoleSandbox(ConsoleBase):
+    '''Represents a connection to a sandbox U-Boot console, executed as a sub-
+    process.'''
+
+    def __init__(self, log, config):
+        '''Initialize a U-Boot console connection.
+
+        Args:
+            log: A multiplexed_log.Logfile instance.
+            config: A "configuration" object as defined in conftest.py.
+
+        Returns:
+            Nothing.
+        '''
+
+        super(ConsoleSandbox, self).__init__(log, config, max_fifo_fill=1024)
+
+    def get_spawn(self):
+        '''Connect to a fresh U-Boot instance.
+
+        A new sandbox process is created, so that U-Boot begins running from
+        scratch.
+
+        Args:
+            None.
+
+        Returns:
+            A u_boot_spawn.Spawn object that is attached to U-Boot.
+        '''
+
+        return Spawn([self.config.build_dir + '/u-boot'])
+
+    def kill(self, sig):
+        '''Send a specific Unix signal to the sandbox process.
+
+        Args:
+            sig: The Unix signal to send to the process.
+
+        Returns:
+            Nothing.
+        '''
+
+        self.ensure_spawned()
+        self.log.action('kill %d' % sig)
+        self.p.kill(sig)
+
+    def validate_exited(self):
+        '''Determine whether the sandbox process has exited.
+
+        If required, this function waits a reasonable time for the process to
+        exit.
+
+        Args:
+            None.
+
+        Returns:
+            Boolean indicating whether the process has exited.
+        '''
+
+        p = self.p
+        self.p = None
+        for i in xrange(100):
+            ret = not p.isalive()
+            if ret:
+                break
+            time.sleep(0.1)
+        p.close()
+        return ret
diff --git a/test/py/u_boot_spawn.py b/test/py/u_boot_spawn.py
new file mode 100644
index 0000000000000000000000000000000000000000..1baee63df25c272e33de70e14b4234d5d9c1d14a
--- /dev/null
+++ b/test/py/u_boot_spawn.py
@@ -0,0 +1,174 @@
+# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
+#
+# SPDX-License-Identifier: GPL-2.0
+
+# Logic to spawn a sub-process and interact with its stdio.
+
+import os
+import re
+import pty
+import signal
+import select
+import time
+
+class Timeout(Exception):
+    '''An exception sub-class that indicates that a timeout occurred.'''
+    pass
+
+class Spawn(object):
+    '''Represents the stdio of a freshly created sub-process. Commands may be
+    sent to the process, and responses waited for.
+    '''
+
+    def __init__(self, args):
+        '''Spawn (fork/exec) the sub-process.
+
+        Args:
+            args: array of processs arguments. argv[0] is the command to execute.
+
+        Returns:
+            Nothing.
+        '''
+
+        self.waited = False
+        self.buf = ''
+        self.logfile_read = None
+        self.before = ''
+        self.after = ''
+        self.timeout = None
+
+        (self.pid, self.fd) = pty.fork()
+        if self.pid == 0:
+            try:
+                # For some reason, SIGHUP is set to SIG_IGN at this point when
+                # run under "go" (www.go.cd). Perhaps this happens under any
+                # background (non-interactive) system?
+                signal.signal(signal.SIGHUP, signal.SIG_DFL)
+                os.execvp(args[0], args)
+            except:
+                print 'CHILD EXECEPTION:'
+                import traceback
+                traceback.print_exc()
+            finally:
+                os._exit(255)
+
+        self.poll = select.poll()
+        self.poll.register(self.fd, select.POLLIN | select.POLLPRI | select.POLLERR | select.POLLHUP | select.POLLNVAL)
+
+    def kill(self, sig):
+        '''Send unix signal "sig" to the child process.
+
+        Args:
+            sig: The signal number to send.
+
+        Returns:
+            Nothing.
+        '''
+
+        os.kill(self.pid, sig)
+
+    def isalive(self):
+        '''Determine whether the child process is still running.
+
+        Args:
+            None.
+
+        Returns:
+            Boolean indicating whether process is alive.
+        '''
+
+        if self.waited:
+            return False
+
+        w = os.waitpid(self.pid, os.WNOHANG)
+        if w[0] == 0:
+            return True
+
+        self.waited = True
+        return False
+
+    def send(self, data):
+        '''Send data to the sub-process's stdin.
+
+        Args:
+            data: The data to send to the process.
+
+        Returns:
+            Nothing.
+        '''
+
+        os.write(self.fd, data)
+
+    def expect(self, patterns):
+        '''Wait for the sub-process to emit specific data.
+
+        This function waits for the process to emit one pattern from the
+        supplied list of patterns, or for a timeout to occur.
+
+        Args:
+            patterns: A list of strings or regex objects that we expect to
+                see in the sub-process' stdout.
+
+        Returns:
+            The index within the patterns array of the pattern the process
+            emitted.
+
+        Notable exceptions:
+            Timeout, if the process did not emit any of the patterns within
+            the expected time.
+        '''
+
+        for pi in xrange(len(patterns)):
+            if type(patterns[pi]) == type(''):
+                patterns[pi] = re.compile(patterns[pi])
+
+        try:
+            while True:
+                earliest_m = None
+                earliest_pi = None
+                for pi in xrange(len(patterns)):
+                    pattern = patterns[pi]
+                    m = pattern.search(self.buf)
+                    if not m:
+                        continue
+                    if earliest_m and m.start() > earliest_m.start():
+                        continue
+                    earliest_m = m
+                    earliest_pi = pi
+                if earliest_m:
+                    pos = earliest_m.start()
+                    posafter = earliest_m.end() + 1
+                    self.before = self.buf[:pos]
+                    self.after = self.buf[pos:posafter]
+                    self.buf = self.buf[posafter:]
+                    return earliest_pi
+                events = self.poll.poll(self.timeout)
+                if not events:
+                    raise Timeout()
+                c = os.read(self.fd, 1024)
+                if not c:
+                    raise EOFError()
+                if self.logfile_read:
+                    self.logfile_read.write(c)
+                self.buf += c
+        finally:
+            if self.logfile_read:
+                self.logfile_read.flush()
+
+    def close(self):
+        '''Close the stdio connection to the sub-process.
+
+        This also waits a reasonable time for the sub-process to stop running.
+
+        Args:
+            None.
+
+        Returns:
+            Nothing.
+        '''
+
+        os.close(self.fd)
+        for i in xrange(100):
+            if not self.isalive():
+                break
+            time.sleep(0.1)
diff --git a/tools/logos/denx-comp.bmp b/tools/logos/denx-comp.bmp
new file mode 100644
index 0000000000000000000000000000000000000000..89d0f471c85cf734b5f0e2bb2508d39f5b9e72b8
Binary files /dev/null and b/tools/logos/denx-comp.bmp differ
diff --git a/tools/patman/gitutil.py b/tools/patman/gitutil.py
index 9e739d89b6ff7ac261d6bc96b6c66def4b7d2937..5f1b4f6e7641065766dbbe4d22b8c96ffaa8feca 100644
--- a/tools/patman/gitutil.py
+++ b/tools/patman/gitutil.py
@@ -328,7 +328,7 @@ def BuildEmailList(in_list, tag=None, alias=None, raise_on_error=True):
     return result
 
 def EmailPatches(series, cover_fname, args, dry_run, raise_on_error, cc_fname,
-        self_only=False, alias=None, in_reply_to=None):
+        self_only=False, alias=None, in_reply_to=None, thread=False):
     """Email a patch series.
 
     Args:
@@ -342,6 +342,8 @@ def EmailPatches(series, cover_fname, args, dry_run, raise_on_error, cc_fname,
         self_only: True to just email to yourself as a test
         in_reply_to: If set we'll pass this to git as --in-reply-to.
             Should be a message ID that this is in reply to.
+        thread: True to add --thread to git send-email (make
+            all patches reply to cover-letter or first patch in series)
 
     Returns:
         Git command that was/would be run
@@ -400,6 +402,8 @@ def EmailPatches(series, cover_fname, args, dry_run, raise_on_error, cc_fname,
     cmd = ['git', 'send-email', '--annotate']
     if in_reply_to:
         cmd.append('--in-reply-to="%s"' % in_reply_to)
+    if thread:
+        cmd.append('--thread')
 
     cmd += to
     cmd += cc
diff --git a/tools/patman/patman.py b/tools/patman/patman.py
index 6fe8fe068c36f5028c1ccb3ed65fc994450c11a6..d05c5ff8e1d317a240896f04b5f03a642b743c6b 100755
--- a/tools/patman/patman.py
+++ b/tools/patman/patman.py
@@ -61,6 +61,8 @@ parser.add_option('--no-check', action='store_false', dest='check_patch',
                   help="Don't check for patch compliance")
 parser.add_option('--no-tags', action='store_false', dest='process_tags',
                   default=True, help="Don't process subject tags as aliaes")
+parser.add_option('-T', '--thread', action='store_true', dest='thread',
+                  default=False, help='Create patches as a single thread')
 
 parser.usage += """
 
@@ -161,7 +163,7 @@ else:
     if its_a_go:
         cmd = gitutil.EmailPatches(series, cover_fname, args,
                 options.dry_run, not options.ignore_bad_tags, cc_file,
-                in_reply_to=options.in_reply_to)
+                in_reply_to=options.in_reply_to, thread=options.thread)
     else:
         print col.Color(col.RED, "Not sending emails due to errors/warnings")