diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index 082cc4a528..add156cb70 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -44,6 +44,14 @@ config VIDEO_BPP32
 	  this option, such displays will not be supported and console output
 	  will be empty.
 
+config VIDEO_ANSI
+	bool "Support ANSI escape sequences in video console"
+	depends on DM_VIDEO
+	default y if DM_VIDEO
+	help
+	  Enable ANSI escape sequence decoding for a more fully functional
+	  console.
+
 config CONSOLE_NORMAL
 	bool "Support a simple text console"
 	depends on DM_VIDEO
diff --git a/drivers/video/vidconsole-uclass.c b/drivers/video/vidconsole-uclass.c
index e081d5a0ee..0a88cc0a42 100644
--- a/drivers/video/vidconsole-uclass.c
+++ b/drivers/video/vidconsole-uclass.c
@@ -9,6 +9,7 @@
  */
 
 #include <common.h>
+#include <linux/ctype.h>
 #include <dm.h>
 #include <video.h>
 #include <video_console.h>
@@ -107,12 +108,120 @@ static void vidconsole_newline(struct udevice *dev)
 	video_sync(dev->parent);
 }
 
+static char *parsenum(char *s, int *num)
+{
+	char *end;
+	*num = simple_strtol(s, &end, 10);
+	return end;
+}
+
+/*
+ * Process a character while accumulating an escape string.  Chars are
+ * accumulated into escape_buf until the end of escape sequence is
+ * found, at which point the sequence is parsed and processed.
+ */
+static void vidconsole_escape_char(struct udevice *dev, char ch)
+{
+	struct vidconsole_priv *priv = dev_get_uclass_priv(dev);
+
+	if (!IS_ENABLED(CONFIG_VIDEO_ANSI))
+		goto error;
+
+	/* Sanity checking for bogus ESC sequences: */
+	if (priv->escape_len >= sizeof(priv->escape_buf))
+		goto error;
+	if (priv->escape_len == 0 && ch != '[')
+		goto error;
+
+	priv->escape_buf[priv->escape_len++] = ch;
+
+	/*
+	 * Escape sequences are terminated by a letter, so keep
+	 * accumulating until we get one:
+	 */
+	if (!isalpha(ch))
+		return;
+
+	/*
+	 * clear escape mode first, otherwise things will get highly
+	 * surprising if you hit any debug prints that come back to
+	 * this console.
+	 */
+	priv->escape = 0;
+
+	switch (ch) {
+	case 'H':
+	case 'f': {
+		int row, col;
+		char *s = priv->escape_buf;
+
+		/*
+		 * Set cursor position: [%d;%df or [%d;%dH
+		 */
+		s++;    /* [ */
+		s = parsenum(s, &row);
+		s++;    /* ; */
+		s = parsenum(s, &col);
+
+		priv->ycur = row * priv->y_charsize;
+		priv->xcur_frac = priv->xstart_frac +
+			VID_TO_POS(col * priv->x_charsize);
+
+		break;
+	}
+	case 'J': {
+		int mode;
+
+		/*
+		 * Clear part/all screen:
+		 *   [J or [0J - clear screen from cursor down
+		 *   [1J       - clear screen from cursor up
+		 *   [2J       - clear entire screen
+		 *
+		 * TODO we really only handle entire-screen case, others
+		 * probably require some additions to video-uclass (and
+		 * are not really needed yet by efi_console)
+		 */
+		parsenum(priv->escape_buf + 1, &mode);
+
+		if (mode == 2) {
+			video_clear(dev->parent);
+			video_sync(dev->parent);
+			priv->ycur = 0;
+			priv->xcur_frac = priv->xstart_frac;
+		} else {
+			debug("unsupported clear mode: %d\n", mode);
+		}
+		break;
+	}
+	default:
+		debug("unrecognized escape sequence: %*s\n",
+		      priv->escape_len, priv->escape_buf);
+	}
+
+	return;
+
+error:
+	/* something went wrong, just revert to normal mode: */
+	priv->escape = 0;
+	return;
+}
+
 int vidconsole_put_char(struct udevice *dev, char ch)
 {
 	struct vidconsole_priv *priv = dev_get_uclass_priv(dev);
 	int ret;
 
+	if (priv->escape) {
+		vidconsole_escape_char(dev, ch);
+		return 0;
+	}
+
 	switch (ch) {
+	case '\x1b':
+		priv->escape_len = 0;
+		priv->escape = 1;
+		break;
 	case '\a':
 		/* beep */
 		break;
diff --git a/drivers/video/video-uclass.c b/drivers/video/video-uclass.c
index 3036e3a1f2..0163039821 100644
--- a/drivers/video/video-uclass.c
+++ b/drivers/video/video-uclass.c
@@ -87,7 +87,7 @@ int video_reserve(ulong *addrp)
 	return 0;
 }
 
-static int video_clear(struct udevice *dev)
+void video_clear(struct udevice *dev)
 {
 	struct video_priv *priv = dev_get_uclass_priv(dev);
 
@@ -100,8 +100,6 @@ static int video_clear(struct udevice *dev)
 	} else {
 		memset(priv->fb, priv->colour_bg, priv->fb_size);
 	}
-
-	return 0;
 }
 
 /* Flush video activity to the caches */
diff --git a/include/video.h b/include/video.h
index 5b4e78b182..61ff653121 100644
--- a/include/video.h
+++ b/include/video.h
@@ -115,6 +115,13 @@ struct video_ops {
 int video_reserve(ulong *addrp);
 
 /**
+ * video_clear() - Clear a device's frame buffer to background color.
+ *
+ * @dev:	Device to clear
+ */
+void video_clear(struct udevice *dev);
+
+/**
  * video_sync() - Sync a device's frame buffer with its hardware
  *
  * Some frame buffers are cached or have a secondary frame buffer. This
diff --git a/include/video_console.h b/include/video_console.h
index 26047934da..9dce234bd9 100644
--- a/include/video_console.h
+++ b/include/video_console.h
@@ -29,6 +29,9 @@
  * @xsize_frac:	Width of the display in fractional units
  * @xstart_frac:	Left margin for the text console in fractional units
  * @last_ch:	Last character written to the text console on this line
+ * @escape:	TRUE if currently accumulating an ANSI escape sequence
+ * @escape_len:	Length of accumulated escape sequence so far
+ * @escape_buf:	Buffer to accumulate escape sequence
  */
 struct vidconsole_priv {
 	struct stdio_dev sdev;
@@ -42,6 +45,14 @@ struct vidconsole_priv {
 	int xsize_frac;
 	int xstart_frac;
 	int last_ch;
+	/*
+	 * ANSI escape sequences are accumulated character by character,
+	 * starting after the ESC char (0x1b) until the entire sequence
+	 * is consumed at which point it is acted upon.
+	 */
+	int escape;
+	int escape_len;
+	char escape_buf[32];
 };
 
 /**
