diff --git a/drivers/gpu/drm/rockchip/rockchip_ebc.c b/drivers/gpu/drm/rockchip/rockchip_ebc.c
index ebe60d5e011a..095d66e67c2f 100644
--- a/drivers/gpu/drm/rockchip/rockchip_ebc.c
+++ b/drivers/gpu/drm/rockchip/rockchip_ebc.c
@@ -17,6 +17,7 @@
 #include <drm/drm_atomic_helper.h>
 #include <drm/drm_bridge.h>
 #include <drm/drm_drv.h>
+#include <drm/drm_epd_helper.h>
 #include <drm/drm_fb_helper.h>
 #include <drm/drm_gem_atomic_helper.h>
 #include <drm/drm_gem_framebuffer_helper.h>
@@ -141,6 +142,10 @@ struct rockchip_ebc {
 	bool				reset_complete;
 };
 
+static int default_waveform = DRM_EPD_WF_GC16;
+module_param(default_waveform, int, 0644);
+MODULE_PARM_DESC(default_waveform, "waveform to use for display updates");
+
 static bool skip_reset;
 module_param(skip_reset, bool, 0444);
 MODULE_PARM_DESC(skip_reset, "skip the initial display reset");
@@ -165,12 +170,86 @@ static const struct drm_mode_config_funcs rockchip_ebc_mode_config_funcs = {
 	.atomic_commit		= drm_atomic_helper_commit,
 };
 
+/**
+ * struct rockchip_ebc_ctx - context for performing display refreshes
+ *
+ * @kref: Reference count, maintained as part of the CRTC's atomic state
+ * @prev: Display contents (Y4) before this refresh
+ * @next: Display contents (Y4) after this refresh
+ * @final: Display contents (Y4) after all pending refreshes
+ * @phase: Buffers for selecting a phase from the EBC's LUT, 1 byte/pixel
+ * @gray4_pitch: Horizontal line length of a Y4 pixel buffer in bytes
+ * @gray4_size: Size of a Y4 pixel buffer in bytes
+ * @phase_pitch: Horizontal line length of a phase buffer in bytes
+ * @phase_size: Size of a phase buffer in bytes
+ */
+struct rockchip_ebc_ctx {
+	struct kref			kref;
+	u8				*prev;
+	u8				*next;
+	u8				*final;
+	u8				*phase[2];
+	u32				gray4_pitch;
+	u32				gray4_size;
+	u32				phase_pitch;
+	u32				phase_size;
+};
+
+static void rockchip_ebc_ctx_free(struct rockchip_ebc_ctx *ctx)
+{
+	kfree(ctx->prev);
+	kfree(ctx->next);
+	kfree(ctx->final);
+	kfree(ctx->phase[0]);
+	kfree(ctx->phase[1]);
+	kfree(ctx);
+}
+
+static struct rockchip_ebc_ctx *rockchip_ebc_ctx_alloc(u32 width, u32 height)
+{
+	u32 gray4_size = width * height / 2;
+	u32 phase_size = width * height;
+	struct rockchip_ebc_ctx *ctx;
+
+	ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
+	if (!ctx)
+		return NULL;
+
+	ctx->prev = kmalloc(gray4_size, GFP_KERNEL);
+	ctx->next = kmalloc(gray4_size, GFP_KERNEL);
+	ctx->final = kmalloc(gray4_size, GFP_KERNEL);
+	ctx->phase[0] = kmalloc(phase_size, GFP_KERNEL);
+	ctx->phase[1] = kmalloc(phase_size, GFP_KERNEL);
+	if (!ctx->prev || !ctx->next || !ctx->final ||
+	    !ctx->phase[0] || !ctx->phase[1]) {
+		rockchip_ebc_ctx_free(ctx);
+		return NULL;
+	}
+
+	kref_init(&ctx->kref);
+	ctx->gray4_pitch = width / 2;
+	ctx->gray4_size  = gray4_size;
+	ctx->phase_pitch = width;
+	ctx->phase_size  = phase_size;
+
+	return ctx;
+}
+
+static void rockchip_ebc_ctx_release(struct kref *kref)
+{
+	struct rockchip_ebc_ctx *ctx =
+		container_of(kref, struct rockchip_ebc_ctx, kref);
+
+	return rockchip_ebc_ctx_free(ctx);
+}
+
 /*
  * CRTC
  */
 
 struct ebc_crtc_state {
 	struct drm_crtc_state		base;
+	struct rockchip_ebc_ctx		*ctx;
 };
 
 static inline struct ebc_crtc_state *
@@ -179,11 +258,70 @@ to_ebc_crtc_state(struct drm_crtc_state *crtc_state)
 	return container_of(crtc_state, struct ebc_crtc_state, base);
 }
 
+static void rockchip_ebc_global_refresh(struct rockchip_ebc *ebc,
+					const struct rockchip_ebc_ctx *ctx)
+{
+	struct drm_device *drm = &ebc->drm;
+	u32 gray4_size = ctx->gray4_size;
+
+	drm_dbg(drm, "global refresh\n");
+
+	memcpy(ctx->prev, ctx->next, gray4_size);
+}
+
+static void rockchip_ebc_partial_refresh(struct rockchip_ebc *ebc,
+					 struct rockchip_ebc_ctx *ctx)
+{
+	struct drm_device *drm = &ebc->drm;
+
+	drm_dbg(drm, "partial refresh\n");
+}
+
+static void rockchip_ebc_refresh(struct rockchip_ebc *ebc,
+				 struct rockchip_ebc_ctx *ctx,
+				 bool global_refresh,
+				 enum drm_epd_waveform waveform)
+{
+	if (global_refresh)
+		rockchip_ebc_global_refresh(ebc, ctx);
+	else
+		rockchip_ebc_partial_refresh(ebc, ctx);
+}
+
 static int rockchip_ebc_refresh_thread(void *data)
 {
 	struct rockchip_ebc *ebc = data;
+	struct rockchip_ebc_ctx *ctx;
 
 	while (!kthread_should_stop()) {
+		/* The context will change each time the thread is unparked. */
+		ctx = to_ebc_crtc_state(READ_ONCE(ebc->crtc.state))->ctx;
+
+		/*
+		 * Initialize the buffers before use. This is deferred to the
+		 * kthread to avoid slowing down atomic_check.
+		 *
+		 * ctx->prev and ctx->next are set to 0xff, all white, because:
+		 *  1) the display is set to white by the reset waveform, and
+		 *  2) the driver maintains the invariant that the display is
+		 *     all white whenever the CRTC is disabled.
+		 *
+		 * ctx->final is initialized by the first plane update.
+		 *
+		 * ctx->phase is set to 0xff, the number of the last possible
+		 * phase, because the LUT for that phase is known to be all
+		 * zeroes. (The last real phase in a waveform is zero in order
+		 * to discharge the display, and unused phases in the LUT are
+		 * zeroed out.) This prevents undesired driving of the display
+		 * in 3-window mode between when the framebuffer is blitted
+		 * (and thus prev != next) and when the refresh thread starts
+		 * counting phases for that region.
+		 */
+		memset(ctx->prev, 0xff, ctx->gray4_size);
+		memset(ctx->next, 0xff, ctx->gray4_size);
+		memset(ctx->phase[0], 0xff, ctx->phase_size);
+		memset(ctx->phase[1], 0xff, ctx->phase_size);
+
 		/*
 		 * LUTs use both the old and the new pixel values as inputs.
 		 * However, the initial contents of the display are unknown.
@@ -192,11 +330,11 @@ static int rockchip_ebc_refresh_thread(void *data)
 		 */
 		if (!ebc->reset_complete) {
 			ebc->reset_complete = true;
-			drm_dbg(&ebc->drm, "display reset\n");
+			rockchip_ebc_refresh(ebc, ctx, true, DRM_EPD_WF_RESET);
 		}
 
 		while (!kthread_should_park()) {
-			drm_dbg(&ebc->drm, "display update\n");
+			rockchip_ebc_refresh(ebc, ctx, false, default_waveform);
 
 			set_current_state(TASK_IDLE);
 			schedule();
@@ -207,7 +345,8 @@ static int rockchip_ebc_refresh_thread(void *data)
 		 * Clear the display before disabling the CRTC. Use the
 		 * highest-quality waveform to minimize visible artifacts.
 		 */
-		drm_dbg(&ebc->drm, "display clear\n");
+		memset(ctx->next, 0xff, ctx->gray4_size);
+		rockchip_ebc_refresh(ebc, ctx, true, DRM_EPD_WF_GC16);
 
 		kthread_parkme();
 	}
@@ -312,7 +451,9 @@ static int rockchip_ebc_crtc_atomic_check(struct drm_crtc *crtc,
 					  struct drm_atomic_state *state)
 {
 	struct rockchip_ebc *ebc = crtc_to_ebc(crtc);
+	struct ebc_crtc_state *ebc_crtc_state;
 	struct drm_crtc_state *crtc_state;
+	struct rockchip_ebc_ctx *ctx;
 
 	crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
 	if (!crtc_state->mode_changed)
@@ -320,14 +461,26 @@ static int rockchip_ebc_crtc_atomic_check(struct drm_crtc *crtc,
 
 	if (crtc_state->enable) {
 		struct drm_display_mode *mode = &crtc_state->adjusted_mode;
+
 		long rate = mode->clock * 1000;
 
 		rate = clk_round_rate(ebc->dclk, rate);
 		if (rate < 0)
 			return rate;
 		mode->clock = rate / 1000;
+
+		ctx = rockchip_ebc_ctx_alloc(mode->hdisplay, mode->vdisplay);
+		if (!ctx)
+			return -ENOMEM;
+	} else {
+		ctx = NULL;
 	}
 
+	ebc_crtc_state = to_ebc_crtc_state(crtc_state);
+	if (ebc_crtc_state->ctx)
+		kref_put(&ebc_crtc_state->ctx->kref, rockchip_ebc_ctx_release);
+	ebc_crtc_state->ctx = ctx;
+
 	return 0;
 }
 
@@ -397,6 +550,10 @@ rockchip_ebc_crtc_duplicate_state(struct drm_crtc *crtc)
 
 	__drm_atomic_helper_crtc_duplicate_state(crtc, &ebc_crtc_state->base);
 
+	ebc_crtc_state->ctx = to_ebc_crtc_state(crtc->state)->ctx;
+	if (ebc_crtc_state->ctx)
+		kref_get(&ebc_crtc_state->ctx->kref);
+
 	return &ebc_crtc_state->base;
 }
 
@@ -405,6 +562,9 @@ static void rockchip_ebc_crtc_destroy_state(struct drm_crtc *crtc,
 {
 	struct ebc_crtc_state *ebc_crtc_state = to_ebc_crtc_state(crtc_state);
 
+	if (ebc_crtc_state->ctx)
+		kref_put(&ebc_crtc_state->ctx->kref, rockchip_ebc_ctx_release);
+
 	__drm_atomic_helper_crtc_destroy_state(&ebc_crtc_state->base);
 
 	kfree(ebc_crtc_state);
