diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index 52f5bc6127..8f0feec6af 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -22,6 +22,18 @@ config BACKLIGHT
 	  This provides backlight uclass driver that enables basic panel
 	  backlight support.
 
+config VIDEO_COPY
+	bool "Enable copying the frame buffer to a hardware copy"
+	depends on DM_VIDEO
+	help
+	  On some machines (e.g. x86), reading from the frame buffer is very
+	  slow because it is uncached. To improve performance, this feature
+	  allows the frame buffer to be kept in cached memory (allocated by
+	  U-Boot) and then copied to the hardware frame-buffer as needed.
+
+	  To use this, your video driver must set @copy_base in
+	  struct video_uc_platdata.
+
 config BACKLIGHT_PWM
 	bool "Generic PWM based Backlight Driver"
 	depends on BACKLIGHT && DM_PWM
diff --git a/drivers/video/video-uclass.c b/drivers/video/video-uclass.c
index 1f2874554a..341db68e38 100644
--- a/drivers/video/video-uclass.c
+++ b/drivers/video/video-uclass.c
@@ -4,6 +4,7 @@
  */
 
 #include <common.h>
+#include <console.h>
 #include <cpu_func.h>
 #include <dm.h>
 #include <log.h>
@@ -201,6 +202,59 @@ int video_get_ysize(struct udevice *dev)
 	return priv->ysize;
 }
 
+#ifdef CONFIG_VIDEO_COPY
+int video_sync_copy(struct udevice *dev, void *from, void *to)
+{
+	struct video_priv *priv = dev_get_uclass_priv(dev);
+
+	if (priv->copy_fb) {
+		long offset, size;
+
+		/* Find the offset of the first byte to copy */
+		if ((ulong)to > (ulong)from) {
+			size = to - from;
+			offset = from - priv->fb;
+		} else {
+			size = from - to;
+			offset = to - priv->fb;
+		}
+
+		/*
+		 * Allow a bit of leeway for valid requests somewhere near the
+		 * frame buffer
+		 */
+		if (offset < -priv->fb_size || offset > 2 * priv->fb_size) {
+#ifdef DEBUG
+			char str[80];
+
+			snprintf(str, sizeof(str),
+				 "[sync_copy fb=%p, from=%p, to=%p, offset=%lx]",
+				 priv->fb, from, to, offset);
+			console_puts_select_stderr(true, str);
+#endif
+			return -EFAULT;
+		}
+
+		/*
+		 * Silently crop the memcpy. This allows callers to avoid doing
+		 * this themselves. It is common for the end pointer to go a
+		 * few lines after the end of the frame buffer, since most of
+		 * the update algorithms terminate a line after their last write
+		 */
+		if (offset + size > priv->fb_size) {
+			size = priv->fb_size - offset;
+		} else if (offset < 0) {
+			size += offset;
+			offset = 0;
+		}
+
+		memcpy(priv->copy_fb + offset, priv->fb + offset, size);
+	}
+
+	return 0;
+}
+#endif
+
 /* Set up the colour map */
 static int video_pre_probe(struct udevice *dev)
 {
diff --git a/include/video.h b/include/video.h
index 813b5653b0..1a0ffd8037 100644
--- a/include/video.h
+++ b/include/video.h
@@ -30,11 +30,14 @@ struct udevice;
  *	buffer should start on. If 0, 1MB is assumed
  * @size: Frame-buffer size, in bytes
  * @base: Base address of frame buffer, 0 if not yet known
+ * @copy_base: Base address of a hardware copy of the frame buffer. See
+ *	CONFIG_VIDEO_COPY.
  */
 struct video_uc_platdata {
 	uint align;
 	uint size;
 	ulong base;
+	ulong copy_base;
 };
 
 enum video_polarity {
@@ -75,6 +78,8 @@ enum video_log2_bpp {
  * @font_size:	Font size in pixels (0 to use a default value)
  * @fb:		Frame buffer
  * @fb_size:	Frame buffer size
+ * @copy_fb:	Copy of the frame buffer to keep up to date; see struct
+ *		video_uc_platdata
  * @line_length:	Length of each frame buffer line, in bytes. This can be
  *		set by the driver, but if not, the uclass will set it after
  *		probing
@@ -101,6 +106,7 @@ struct video_priv {
 	 */
 	void *fb;
 	int fb_size;
+	void *copy_fb;
 	int line_length;
 	u32 colour_fg;
 	u32 colour_bg;
@@ -214,6 +220,29 @@ void video_set_flush_dcache(struct udevice *dev, bool flush);
  */
 void video_set_default_colors(struct udevice *dev, bool invert);
 
+#ifdef CONFIG_VIDEO_COPY
+/**
+ * vidconsole_sync_copy() - Sync back to the copy framebuffer
+ *
+ * This ensures that the copy framebuffer has the same data as the framebuffer
+ * for a particular region. It should be called after the framebuffer is updated
+ *
+ * @from and @to can be in either order. The region between them is synced.
+ *
+ * @dev: Vidconsole device being updated
+ * @from: Start/end address within the framebuffer (->fb)
+ * @to: Other address within the frame buffer
+ * @return 0 if OK, -EFAULT if the start address is before the start of the
+ *	frame buffer start
+ */
+int video_sync_copy(struct udevice *dev, void *from, void *to);
+#else
+static inline int video_sync_copy(struct udevice *dev, void *from, void *to)
+{
+	return 0;
+}
+#endif
+
 #endif /* CONFIG_DM_VIDEO */
 
 #ifndef CONFIG_DM_VIDEO
