diff --git a/drivers/gpu/drm/mxc-epdc/Makefile b/drivers/gpu/drm/mxc-epdc/Makefile
index 0263ef2bf0db..a55e2bfe824a 100644
--- a/drivers/gpu/drm/mxc-epdc/Makefile
+++ b/drivers/gpu/drm/mxc-epdc/Makefile
@@ -1,5 +1,5 @@
 # SPDX-License-Identifier: GPL-2.0
-mxc_epdc_drm-y := mxc_epdc_drv.o epdc_hw.o epdc_waveform.o
+mxc_epdc_drm-y := mxc_epdc_drv.o epdc_hw.o epdc_update.o epdc_waveform.o
 
 obj-$(CONFIG_DRM_MXC_EPDC) += mxc_epdc_drm.o
 
diff --git a/drivers/gpu/drm/mxc-epdc/epdc_hw.c b/drivers/gpu/drm/mxc-epdc/epdc_hw.c
index a74cbd237e0d..22a065ac6992 100644
--- a/drivers/gpu/drm/mxc-epdc/epdc_hw.c
+++ b/drivers/gpu/drm/mxc-epdc/epdc_hw.c
@@ -20,6 +20,7 @@
 #include "mxc_epdc.h"
 #include "epdc_regs.h"
 #include "epdc_hw.h"
+#include "epdc_update.h"
 #include "epdc_waveform.h"
 
 void mxc_epdc_powerup(struct mxc_epdc *priv)
@@ -410,6 +411,7 @@ void mxc_epdc_init_sequence(struct mxc_epdc *priv, struct drm_display_mode *m)
 
 	priv->in_init = true;
 	mxc_epdc_powerup(priv);
+	mxc_epdc_draw_mode0(priv);
 	/* Force power down event */
 	priv->powering_down = true;
 	mxc_epdc_powerdown(priv);
diff --git a/drivers/gpu/drm/mxc-epdc/epdc_update.c b/drivers/gpu/drm/mxc-epdc/epdc_update.c
new file mode 100644
index 000000000000..0c061982aa0b
--- /dev/null
+++ b/drivers/gpu/drm/mxc-epdc/epdc_update.c
@@ -0,0 +1,1210 @@
+// SPDX-License-Identifier: GPL-2.0+
+// Copyright (C) 2022 Andreas Kemnade
+//
+/*
+ * based on the EPDC framebuffer driver
+ * Copyright (C) 2010-2016 Freescale Semiconductor, Inc.
+ * Copyright 2017 NXP
+ */
+
+#include <linux/bits.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include "mxc_epdc.h"
+#include "epdc_hw.h"
+#include "epdc_regs.h"
+#include "epdc_waveform.h"
+
+#define EPDC_V2_NUM_LUTS	64
+#define EPDC_V2_MAX_NUM_UPDATES 64
+#define INVALID_LUT	     (-1)
+#define DRY_RUN_NO_LUT	  100
+
+#define MERGE_OK	0
+#define MERGE_FAIL      1
+#define MERGE_BLOCK     2
+
+struct update_desc_list {
+	struct list_head list;
+	struct mxcfb_update_data upd_data;/* Update parameters */
+	u32 update_order;	/* Numeric ordering value for update */
+};
+
+/* This structure represents a list node containing both
+ * a memory region allocated as an output buffer for the PxP
+ * update processing task, and the update description (mode, region, etc.)
+ */
+struct update_data_list {
+	struct list_head list;
+	struct update_desc_list *update_desc;
+	int lut_num;		/* Assigned before update is processed into working buffer */
+	u64 collision_mask;	/* Set when update creates collision */
+				/* Mask of the LUTs the update collides with */
+};
+
+/********************************************************
+ * Start Low-Level EPDC Functions
+ ********************************************************/
+
+static inline void epdc_lut_complete_intr(struct mxc_epdc *priv, u32 lut_num, bool enable)
+{
+	if (enable) {
+		if (lut_num < 32)
+			epdc_write(priv, EPDC_IRQ_MASK1_SET, BIT(lut_num));
+		else
+			epdc_write(priv, EPDC_IRQ_MASK2_SET, BIT(lut_num - 32));
+	} else {
+		if (lut_num < 32)
+			epdc_write(priv, EPDC_IRQ_MASK1_CLEAR, BIT(lut_num));
+		else
+			epdc_write(priv, EPDC_IRQ_MASK2_CLEAR, BIT(lut_num - 32));
+	}
+}
+
+static inline void epdc_working_buf_intr(struct mxc_epdc *priv, bool enable)
+{
+	if (enable)
+		epdc_write(priv, EPDC_IRQ_MASK_SET, EPDC_IRQ_WB_CMPLT_IRQ);
+	else
+		epdc_write(priv, EPDC_IRQ_MASK_CLEAR, EPDC_IRQ_WB_CMPLT_IRQ);
+}
+
+static inline void epdc_clear_working_buf_irq(struct mxc_epdc *priv)
+{
+	epdc_write(priv, EPDC_IRQ_CLEAR,
+		   EPDC_IRQ_WB_CMPLT_IRQ | EPDC_IRQ_LUT_COL_IRQ);
+}
+
+static inline void epdc_eof_intr(struct mxc_epdc *priv, bool enable)
+{
+	if (enable)
+		epdc_write(priv, EPDC_IRQ_MASK_SET, EPDC_IRQ_FRAME_END_IRQ);
+	else
+		epdc_write(priv, EPDC_IRQ_MASK_CLEAR, EPDC_IRQ_FRAME_END_IRQ);
+}
+
+static inline void epdc_clear_eof_irq(struct mxc_epdc *priv)
+{
+	epdc_write(priv, EPDC_IRQ_CLEAR, EPDC_IRQ_FRAME_END_IRQ);
+}
+
+static inline bool epdc_signal_eof(struct mxc_epdc *priv)
+{
+	return (epdc_read(priv, EPDC_IRQ_MASK) & epdc_read(priv, EPDC_IRQ)
+		& EPDC_IRQ_FRAME_END_IRQ) ? true : false;
+}
+
+
+static void epdc_set_update_area(struct mxc_epdc *priv, u32 addr,
+				 u32 x, u32 y, u32 width, u32 height,
+				 u32 stride)
+{
+	u32 val;
+
+	epdc_write(priv, EPDC_UPD_ADDR, addr);
+	val = (y << EPDC_UPD_CORD_YCORD_OFFSET) | x;
+	epdc_write(priv, EPDC_UPD_CORD, val);
+	val = (height << EPDC_UPD_SIZE_HEIGHT_OFFSET) | width;
+	epdc_write(priv, EPDC_UPD_SIZE, val);
+	epdc_write(priv, EPDC_UPD_STRIDE, stride);
+}
+
+static bool is_free_list_full(struct mxc_epdc *priv)
+{
+	int count = 0;
+	struct update_data_list *plist;
+
+	/* Count buffers in free buffer list */
+	list_for_each_entry(plist, &priv->upd_buf_free_list, list)
+		count++;
+
+	/* Check to see if all buffers are in this list */
+	return (count == priv->max_num_updates);
+}
+
+static inline bool epdc_is_lut_complete(struct mxc_epdc *priv, u32 lut_num)
+{
+	u32 val;
+	bool is_compl;
+
+	if (lut_num < 32) {
+		val = epdc_read(priv, EPDC_IRQ1);
+		is_compl = val & BIT(lut_num) ? true : false;
+	} else {
+		val = epdc_read(priv, EPDC_IRQ2);
+		is_compl = val & BIT(lut_num - 32) ? true : false;
+	}
+
+	return is_compl;
+}
+static inline void epdc_clear_lut_complete_irq(struct mxc_epdc *priv, u32 lut_num)
+{
+	if (lut_num < 32)
+		epdc_write(priv, EPDC_IRQ1_CLEAR, BIT(lut_num));
+	else
+		epdc_write(priv, EPDC_IRQ2_CLEAR, BIT(lut_num - 32));
+}
+
+static inline bool epdc_is_working_buffer_busy(struct mxc_epdc *priv)
+{
+	u32 val = epdc_read(priv, EPDC_STATUS);
+	bool is_busy = (val & EPDC_STATUS_WB_BUSY) ? true : false;
+
+	return is_busy;
+}
+
+static inline bool epdc_is_working_buffer_complete(struct mxc_epdc *priv)
+{
+	u32 val = epdc_read(priv, EPDC_IRQ);
+	bool is_compl = (val & EPDC_IRQ_WB_CMPLT_IRQ) ? true : false;
+
+	return is_compl;
+}
+
+static inline bool epdc_is_lut_cancelled(struct mxc_epdc *priv)
+{
+	u32 val = epdc_read(priv, EPDC_STATUS);
+	bool is_void = (val & EPDC_STATUS_UPD_VOID) ? true : false;
+
+	return is_void;
+}
+
+static inline bool epdc_is_collision(struct mxc_epdc *priv)
+{
+	u32 val = epdc_read(priv, EPDC_IRQ);
+
+	return (val & EPDC_IRQ_LUT_COL_IRQ) ? true : false;
+}
+
+static inline u64 epdc_get_colliding_luts(struct mxc_epdc *priv)
+{
+	u64 val = (u64)(epdc_read(priv, EPDC_STATUS_COL));
+
+	val |= (u64)epdc_read(priv, EPDC_STATUS_COL2) << 32;
+	return val;
+}
+
+static irqreturn_t mxc_epdc_irq_handler(int irq, void *dev_id)
+{
+	struct mxc_epdc *priv = dev_id;
+	u32 ints_fired, luts1_ints_fired, luts2_ints_fired;
+
+	/*
+	 * If we just completed one-time panel init, bypass
+	 * queue handling, clear interrupt and return
+	 */
+	if (priv->in_init) {
+		if (epdc_is_working_buffer_complete(priv)) {
+			epdc_working_buf_intr(priv, false);
+			epdc_clear_working_buf_irq(priv);
+			dev_dbg(priv->drm.dev, "Cleared WB for init update\n");
+		}
+
+		if (epdc_is_lut_complete(priv, 0)) {
+			epdc_lut_complete_intr(priv, 0, false);
+			epdc_clear_lut_complete_irq(priv, 0);
+			priv->in_init = false;
+			dev_dbg(priv->drm.dev, "Cleared LUT complete for init update\n");
+		}
+
+		return IRQ_HANDLED;
+	}
+
+	ints_fired = epdc_read(priv, EPDC_IRQ_MASK) & epdc_read(priv, EPDC_IRQ);
+
+	luts1_ints_fired = epdc_read(priv, EPDC_IRQ_MASK1) & epdc_read(priv, EPDC_IRQ1);
+	luts2_ints_fired = epdc_read(priv, EPDC_IRQ_MASK2) & epdc_read(priv, EPDC_IRQ2);
+
+	if (!(ints_fired || luts1_ints_fired || luts2_ints_fired))
+		return IRQ_HANDLED;
+
+	if (epdc_read(priv, EPDC_IRQ) & EPDC_IRQ_TCE_UNDERRUN_IRQ) {
+		dev_err(priv->drm.dev,
+			"TCE underrun! Will continue to update panel\n");
+		/* Clear TCE underrun IRQ */
+		epdc_write(priv, EPDC_IRQ_CLEAR, EPDC_IRQ_TCE_UNDERRUN_IRQ);
+	}
+
+	/* Check if we are waiting on EOF to sync a new update submission */
+	if (epdc_signal_eof(priv)) {
+		epdc_eof_intr(priv, false);
+		epdc_clear_eof_irq(priv);
+	}
+
+	/*
+	 * Workaround for EPDC v2.0/v2.1 errata: Must read collision status
+	 * before clearing IRQ, or else collision status for bits 16:63
+	 * will be automatically cleared.  So we read it here, and there is
+	 * no conflict with using it in epdc_intr_work_func since the
+	 * working buffer processing flow is strictly sequential (i.e.,
+	 * only one WB processing done at a time, so the data grabbed
+	 * here should be up-to-date and accurate when the WB processing
+	 * completes.  Also, note that there is no impact to other versions
+	 * of EPDC by reading LUT status here.
+	 */
+	if (priv->cur_update != NULL)
+		priv->epdc_colliding_luts = epdc_get_colliding_luts(priv);
+
+	/* Clear the interrupt mask for any interrupts signalled */
+	epdc_write(priv, EPDC_IRQ_MASK_CLEAR, ints_fired);
+	epdc_write(priv, EPDC_IRQ_MASK1_CLEAR, luts1_ints_fired);
+	epdc_write(priv, EPDC_IRQ_MASK2_CLEAR, luts2_ints_fired);
+
+	dev_dbg(priv->drm.dev,
+		"EPDC interrupts fired = 0x%x, LUTS1 fired = 0x%x, LUTS2 fired = 0x%x\n",
+		ints_fired, luts1_ints_fired, luts2_ints_fired);
+
+	queue_work(priv->epdc_intr_workqueue,
+		&priv->epdc_intr_work);
+
+
+	return IRQ_HANDLED;
+}
+
+static void cleanup_update_list(void *data)
+{
+	struct mxc_epdc *priv = (struct mxc_epdc *)data;
+	struct update_data_list *plist, *temp_list;
+
+	list_for_each_entry_safe(plist, temp_list, &priv->upd_buf_free_list,
+			list) {
+		list_del(&plist->list);
+		kfree(plist);
+	}
+}
+
+static void epdc_done_work_func(struct work_struct *work)
+{
+	struct mxc_epdc *priv =
+		container_of(work, struct mxc_epdc,
+			epdc_done_work.work);
+	mxc_epdc_powerdown(priv);
+}
+
+static int epdc_submit_merge(struct update_desc_list *upd_desc_list,
+				struct update_desc_list *update_to_merge,
+				struct mxc_epdc *priv)
+{
+	struct mxcfb_update_data *a, *b;
+	struct drm_rect *arect, *brect;
+
+	a = &upd_desc_list->upd_data;
+	b = &update_to_merge->upd_data;
+	arect = &upd_desc_list->upd_data.update_region;
+	brect = &update_to_merge->upd_data.update_region;
+
+	if (a->update_mode != b->update_mode)
+		a->update_mode = UPDATE_MODE_FULL;
+
+	if (a->waveform_mode != b->waveform_mode)
+		a->waveform_mode = WAVEFORM_MODE_AUTO;
+
+	if ((arect->x1 > brect->x2) ||
+	    (brect->x1 > arect->x2) ||
+	    (arect->y1 > brect->y2) ||
+	    (brect->y1 > arect->y2))
+		return MERGE_FAIL;
+
+	arect->x1 = min(arect->x1, brect->x1);
+	arect->x2 = max(arect->x2, brect->x2);
+	arect->y1 = min(arect->y1, brect->y1);
+	arect->y2 = max(arect->y2, brect->y2);
+
+	/* Merged update should take on the earliest order */
+	upd_desc_list->update_order = max(upd_desc_list->update_order,
+					  update_to_merge->update_order);
+
+	return MERGE_OK;
+}
+
+static void epdc_from_rgb_clear_lower_nibble(struct drm_rect *clip,
+					     void *vaddr, int pitch,
+					     u8 *dst, int dst_pitch)
+{
+	unsigned int x, y;
+
+	dst += clip->y1 * dst_pitch;
+
+	for (y = clip->y1; y < clip->y2; y++, dst += dst_pitch) {
+		u32 *src;
+
+		src = vaddr + (y * pitch);
+		src += clip->x1;
+		for (x = clip->x1; x < clip->x2; x++) {
+			u8 r = (*src & 0x00ff0000) >> 16;
+			u8 g = (*src & 0x0000ff00) >> 8;
+			u8 b =  *src & 0x000000ff;
+
+			/* ITU BT.601: Y = 0.299 R + 0.587 G + 0.114 B */
+			u8 gray = (3 * r + 6 * g + b) / 10;
+
+			/*
+			 * done in Tolino 3.0.x kernels via PXP_LUT_AA
+			 * needed for 5 bit waveforms
+			 */
+
+			dst[x] = gray & 0xF0;
+			src++;
+		}
+	}
+}
+
+/* found by experimentation, reduced number of levels of gray */
+static void epdc_from_rgb_shift(struct drm_rect *clip, void *vaddr, int pitch,
+				u8 *dst, int dst_pitch)
+{
+	unsigned int x, y;
+
+	dst += clip->y1 * dst_pitch;
+
+	for (y = clip->y1; y < clip->y2; y++, dst += dst_pitch) {
+		u32 *src;
+
+		src = vaddr + (y * pitch);
+		src += clip->x1;
+		for (x = clip->x1; x < clip->x2; x++) {
+			u8 r = (*src & 0x00ff0000) >> 16;
+			u8 g = (*src & 0x0000ff00) >> 8;
+			u8 b =  *src & 0x000000ff;
+
+			/* ITU BT.601: Y = 0.299 R + 0.587 G + 0.114 B */
+			u8 gray = (3 * r + 6 * g + b) / 10;
+
+			dst[x] = (gray >> 2) | 0xC0;
+			src++;
+		}
+	}
+}
+
+static void epdc_submit_update(struct mxc_epdc *priv,
+			       u32 lut_num, u32 waveform_mode, u32 update_mode,
+			       bool use_dry_run, bool use_test_mode, u32 np_val)
+{
+	u32 reg_val = 0;
+
+	if (use_test_mode) {
+		reg_val |=
+		    ((np_val << EPDC_UPD_FIXED_FIXNP_OFFSET) &
+		     EPDC_UPD_FIXED_FIXNP_MASK) | EPDC_UPD_FIXED_FIXNP_EN;
+
+		epdc_write(priv, EPDC_UPD_FIXED, reg_val);
+
+		reg_val = EPDC_UPD_CTRL_USE_FIXED;
+	} else {
+		epdc_write(priv, EPDC_UPD_FIXED, reg_val);
+	}
+
+	if (waveform_mode == WAVEFORM_MODE_AUTO)
+		reg_val |= EPDC_UPD_CTRL_AUTOWV;
+	else
+		reg_val |= ((waveform_mode <<
+				EPDC_UPD_CTRL_WAVEFORM_MODE_OFFSET) &
+				EPDC_UPD_CTRL_WAVEFORM_MODE_MASK);
+
+	reg_val |= (use_dry_run ? EPDC_UPD_CTRL_DRY_RUN : 0) |
+	    ((lut_num << EPDC_UPD_CTRL_LUT_SEL_OFFSET) &
+	     EPDC_UPD_CTRL_LUT_SEL_MASK) |
+	    update_mode;
+	epdc_write(priv, EPDC_UPD_CTRL, reg_val);
+}
+
+static inline bool epdc_is_lut_active(struct mxc_epdc *priv, u32 lut_num)
+{
+	u32 val;
+	bool is_active;
+
+	if (lut_num < 32) {
+		val = epdc_read(priv, EPDC_STATUS_LUTS);
+		is_active = val & BIT(lut_num) ? true : false;
+	} else {
+		val = epdc_read(priv, EPDC_STATUS_LUTS2);
+		is_active = val & BIT(lut_num - 32) ? true : false;
+	}
+
+	return is_active;
+}
+
+static inline bool epdc_any_luts_active(struct mxc_epdc *priv)
+{
+	bool any_active;
+
+	any_active = (epdc_read(priv, EPDC_STATUS_LUTS) |
+		      epdc_read(priv, EPDC_STATUS_LUTS2))	? true : false;
+
+	return any_active;
+}
+
+static inline bool epdc_any_luts_available(struct mxc_epdc *priv)
+{
+	bool luts_available =
+	    (epdc_read(priv, EPDC_STATUS_NEXTLUT) &
+	     EPDC_STATUS_NEXTLUT_NEXT_LUT_VALID) ? true : false;
+	return luts_available;
+}
+
+static inline int epdc_get_next_lut(struct mxc_epdc *priv)
+{
+	u32 val =
+	    epdc_read(priv, EPDC_STATUS_NEXTLUT) &
+	    EPDC_STATUS_NEXTLUT_NEXT_LUT_MASK;
+	return val;
+}
+
+static int epdc_choose_next_lut(struct mxc_epdc *priv, int *next_lut)
+{
+	u64 luts_status, unprocessed_luts, used_luts;
+	/* Available LUTs are reduced to 16 in 5-bit waveform mode */
+	bool format_p5n = ((epdc_read(priv, EPDC_FORMAT) &
+	EPDC_FORMAT_BUF_PIXEL_FORMAT_MASK) ==
+	EPDC_FORMAT_BUF_PIXEL_FORMAT_P5N);
+
+	luts_status = epdc_read(priv, EPDC_STATUS_LUTS);
+	if (format_p5n)
+		luts_status &= 0xFFFF;
+	else
+		luts_status |= ((u64)epdc_read(priv, EPDC_STATUS_LUTS2) << 32);
+
+	unprocessed_luts = epdc_read(priv, EPDC_IRQ1) |
+		((u64)epdc_read(priv, EPDC_IRQ2) << 32);
+	if (format_p5n)
+		unprocessed_luts &= 0xFFFF;
+
+	/*
+	 * Note on unprocessed_luts: There is a race condition
+	 * where a LUT completes, but has not been processed by
+	 * IRQ handler workqueue, and then a new update request
+	 * attempts to use that LUT.  We prevent that here by
+	 * ensuring that the LUT we choose doesn't have its IRQ
+	 * bit set (indicating it has completed but not yet been
+	 * processed).
+	 */
+	used_luts = luts_status | unprocessed_luts;
+
+	if (format_p5n) {
+		*next_lut = fls64(used_luts);
+		if (*next_lut > 15)
+			*next_lut = ffz(used_luts);
+	} else {
+		if ((u32)used_luts != ~0UL)
+			*next_lut = ffz((u32)used_luts);
+		else if ((u32)(used_luts >> 32) != ~0UL)
+			*next_lut = ffz((u32)(used_luts >> 32)) + 32;
+		else
+			*next_lut = INVALID_LUT;
+	}
+
+	if (used_luts & 0x8000)
+		return 1;
+	else
+		return 0;
+}
+
+
+
+
+static void epdc_submit_work_func(struct work_struct *work)
+{
+	struct update_data_list *next_update, *temp_update;
+	struct update_desc_list *next_desc, *temp_desc;
+	struct mxc_epdc *priv =
+		container_of(work, struct mxc_epdc, epdc_submit_work);
+	struct update_data_list *upd_data_list = NULL;
+	struct drm_rect adj_update_region, *upd_region;
+	bool end_merge = false;
+	u32 update_addr;
+	uint8_t *update_addr_virt;
+	int ret;
+
+	/* Protect access to buffer queues and to update HW */
+	mutex_lock(&priv->queue_mutex);
+
+	/*
+	 * Are any of our collision updates able to go now?
+	 * Go through all updates in the collision list and check to see
+	 * if the collision mask has been fully cleared
+	 */
+	list_for_each_entry_safe(next_update, temp_update,
+				&priv->upd_buf_collision_list, list) {
+
+		if (next_update->collision_mask != 0)
+			continue;
+
+		dev_dbg(priv->drm.dev, "A collision update is ready to go!\n");
+
+		/* Force waveform mode to auto for resubmitted collisions */
+		next_update->update_desc->upd_data.waveform_mode =
+			WAVEFORM_MODE_AUTO;
+
+		/*
+		 * We have a collision cleared, so select it for resubmission.
+		 * If an update is already selected, attempt to merge.
+		 */
+		if (!upd_data_list) {
+			upd_data_list = next_update;
+			list_del_init(&next_update->list);
+		} else {
+			switch (epdc_submit_merge(upd_data_list->update_desc,
+						  next_update->update_desc,
+						  priv)) {
+			case MERGE_OK:
+				dev_dbg(priv->drm.dev,
+					"Update merged [collision]\n");
+				list_del_init(&next_update->update_desc->list);
+				kfree(next_update->update_desc);
+				next_update->update_desc = NULL;
+				list_del_init(&next_update->list);
+				/* Add to free buffer list */
+				list_add_tail(&next_update->list,
+					 &priv->upd_buf_free_list);
+				break;
+			case MERGE_FAIL:
+				dev_dbg(priv->drm.dev,
+					"Update not merged [collision]\n");
+				break;
+			case MERGE_BLOCK:
+				dev_dbg(priv->drm.dev,
+					"Merge blocked [collision]\n");
+				end_merge = true;
+				break;
+			}
+
+			if (end_merge) {
+				end_merge = false;
+				break;
+			}
+		}
+	}
+
+	/*
+	 * If we didn't find a collision update ready to go, we
+	 * need to get a free buffer and match it to a pending update.
+	 */
+
+	/*
+	 * Can't proceed if there are no free buffers (and we don't
+	 * already have a collision update selected)
+	 */
+	if (!upd_data_list &&
+		list_empty(&priv->upd_buf_free_list)) {
+		mutex_unlock(&priv->queue_mutex);
+		return;
+	}
+
+	list_for_each_entry_safe(next_desc, temp_desc,
+			&priv->upd_pending_list, list) {
+
+		dev_dbg(priv->drm.dev, "Found a pending update!\n");
+
+		if (!upd_data_list) {
+			if (list_empty(&priv->upd_buf_free_list))
+				break;
+			upd_data_list =
+				list_entry(priv->upd_buf_free_list.next,
+					struct update_data_list, list);
+			list_del_init(&upd_data_list->list);
+			upd_data_list->update_desc = next_desc;
+			list_del_init(&next_desc->list);
+		} else {
+			switch (epdc_submit_merge(upd_data_list->update_desc,
+					next_desc, priv)) {
+			case MERGE_OK:
+				dev_dbg(priv->drm.dev,
+					"Update merged [queue]\n");
+				list_del_init(&next_desc->list);
+				kfree(next_desc);
+				break;
+			case MERGE_FAIL:
+				dev_dbg(priv->drm.dev,
+					"Update not merged [queue]\n");
+				break;
+			case MERGE_BLOCK:
+				dev_dbg(priv->drm.dev,
+					"Merge blocked [collision]\n");
+				end_merge = true;
+				break;
+			}
+
+			if (end_merge)
+				break;
+		}
+	}
+
+	/* Is update list empty? */
+	if (!upd_data_list) {
+		mutex_unlock(&priv->queue_mutex);
+		return;
+	}
+
+	if ((!priv->powered)
+		|| priv->powering_down)
+		mxc_epdc_powerup(priv);
+
+	/*
+	 * Set update buffer pointer to the start of
+	 * the update region in the frame buffer.
+	 */
+	upd_region = &upd_data_list->update_desc->upd_data.update_region;
+	update_addr = priv->epdc_mem_phys +
+		((upd_region->y1 * priv->epdc_mem_width) +
+		upd_region->x1);
+	update_addr_virt = (u8 *)(priv->epdc_mem_virt) +
+		((upd_region->y1 * priv->epdc_mem_width) +
+		upd_region->x1);
+
+	adj_update_region = upd_data_list->update_desc->upd_data.update_region;
+	/*
+	 * Is the working buffer idle?
+	 * If the working buffer is busy, we must wait for the resource
+	 * to become free. The IST will signal this event.
+	 */
+	if (priv->cur_update != NULL) {
+		dev_dbg(priv->drm.dev, "working buf busy!\n");
+
+		/* Initialize event signalling an update resource is free */
+		init_completion(&priv->update_res_free);
+
+		priv->waiting_for_wb = true;
+
+		/* Leave spinlock while waiting for WB to complete */
+		mutex_unlock(&priv->queue_mutex);
+		wait_for_completion(&priv->update_res_free);
+		mutex_lock(&priv->queue_mutex);
+	}
+
+	/*
+	 * If there are no LUTs available,
+	 * then we must wait for the resource to become free.
+	 * The IST will signal this event.
+	 */
+	if (!epdc_any_luts_available(priv)) {
+		dev_dbg(priv->drm.dev, "no luts available!\n");
+
+		/* Initialize event signalling an update resource is free */
+		init_completion(&priv->update_res_free);
+
+		priv->waiting_for_lut = true;
+
+		/* Leave spinlock while waiting for LUT to free up */
+		mutex_unlock(&priv->queue_mutex);
+		wait_for_completion(&priv->update_res_free);
+		mutex_lock(&priv->queue_mutex);
+	}
+
+	ret = epdc_choose_next_lut(priv, &upd_data_list->lut_num);
+
+	/* LUTs are available, so we get one here */
+	priv->cur_update = upd_data_list;
+
+	/* Reset mask for LUTS that have completed during WB processing */
+	priv->luts_complete_wb = 0;
+
+	/* Mark LUT with order */
+	priv->lut_update_order[upd_data_list->lut_num] =
+			upd_data_list->update_desc->update_order;
+
+	epdc_lut_complete_intr(priv, upd_data_list->lut_num,
+				       true);
+
+	/* Enable Collision and WB complete IRQs */
+	epdc_working_buf_intr(priv, true);
+
+	epdc_write(priv, EPDC_TEMP,
+		   mxc_epdc_fb_get_temp_index(priv, TEMP_USE_AMBIENT));
+
+	/* Program EPDC update to process buffer */
+
+	epdc_set_update_area(priv, update_addr,
+			     adj_update_region.x1, adj_update_region.y1,
+			     drm_rect_width(&adj_update_region),
+			     drm_rect_height(&adj_update_region),
+			     priv->epdc_mem_width);
+
+	if (priv->wv_modes_update &&
+		(upd_data_list->update_desc->upd_data.waveform_mode
+			== WAVEFORM_MODE_AUTO)) {
+		mxc_epdc_set_update_waveform(priv, &priv->wv_modes);
+		priv->wv_modes_update = false;
+	}
+
+	epdc_submit_update(priv, upd_data_list->lut_num,
+			   upd_data_list->update_desc->upd_data.waveform_mode,
+			   upd_data_list->update_desc->upd_data.update_mode,
+			   false,
+			   false, 0);
+
+	/* Release buffer queues */
+	mutex_unlock(&priv->queue_mutex);
+}
+
+void mxc_epdc_flush_updates(struct mxc_epdc *priv)
+{
+	int ret;
+
+	if (priv->in_init)
+		return;
+
+	/* Grab queue lock to prevent any new updates from being submitted */
+	mutex_lock(&priv->queue_mutex);
+
+	/*
+	 * 3 places to check for updates that are active or pending:
+	 *   1) Updates in the pending list
+	 *   2) Update buffers in use (e.g., PxP processing)
+	 *   3) Active updates to panel - We can key off of EPDC
+	 *      power state to know if we have active updates.
+	 */
+	if (!list_empty(&priv->upd_pending_list) ||
+		!is_free_list_full(priv) ||
+		(priv->updates_active == true)) {
+		/* Initialize event signalling updates are done */
+		init_completion(&priv->updates_done);
+		priv->waiting_for_idle = true;
+
+		mutex_unlock(&priv->queue_mutex);
+		/* Wait for any currently active updates to complete */
+		ret = wait_for_completion_timeout(&priv->updates_done,
+						msecs_to_jiffies(8000));
+		if (!ret)
+			dev_err(priv->drm.dev,
+				"Flush updates timeout! ret = 0x%x\n", ret);
+
+		mutex_lock(&priv->queue_mutex);
+		priv->waiting_for_idle = false;
+	}
+
+	mutex_unlock(&priv->queue_mutex);
+}
+
+void mxc_epdc_draw_mode0(struct mxc_epdc *priv)
+{
+	u32 *upd_buf_ptr;
+	int i;
+	u32 xres, yres;
+
+	upd_buf_ptr = (u32 *)priv->epdc_mem_virt;
+
+	epdc_working_buf_intr(priv, true);
+	epdc_lut_complete_intr(priv, 0, true);
+
+	/* Use unrotated (native) width/height */
+	xres = priv->epdc_mem_width;
+	yres = priv->epdc_mem_height;
+
+	/* Program EPDC update to process buffer */
+	epdc_set_update_area(priv, priv->epdc_mem_phys, 0, 0, xres, yres, 0);
+	epdc_submit_update(priv, 0, priv->wv_modes.mode_init, UPDATE_MODE_FULL,
+		false, true, 0xFF);
+
+	dev_dbg(priv->drm.dev, "Mode0 update - Waiting for LUT to complete...\n");
+
+	/* Will timeout after ~4-5 seconds */
+
+	for (i = 0; i < 40; i++) {
+		if (!epdc_is_lut_active(priv, 0)) {
+			dev_dbg(priv->drm.dev, "Mode0 init complete\n");
+			return;
+		}
+		msleep(100);
+	}
+
+	dev_err(priv->drm.dev, "Mode0 init failed!\n");
+}
+
+
+int mxc_epdc_send_single_update(struct drm_rect *clip, int pitch, void *vaddr,
+				struct mxc_epdc *priv)
+{
+	struct update_desc_list *upd_desc;
+
+	if (priv->rev < 30)
+		epdc_from_rgb_clear_lower_nibble(clip, vaddr, pitch,
+						 (u8 *)priv->epdc_mem_virt,
+						 priv->epdc_mem_width);
+	else
+		epdc_from_rgb_shift(clip, vaddr, pitch,
+				    (u8 *)priv->epdc_mem_virt,
+				    priv->epdc_mem_width);
+
+	/* Has EPDC HW been initialized? */
+	if (!priv->hw_ready) {
+		/* Throw message if we are not mid-initialization */
+		if (!priv->hw_initializing)
+			dev_err(priv->drm.dev, "Display HW not properly initialized. Aborting update.\n");
+		return -EPERM;
+	}
+
+	mutex_lock(&priv->queue_mutex);
+
+	if (priv->waiting_for_idle) {
+		dev_dbg(priv->drm.dev, "EPDC not active. Update request abort.\n");
+		mutex_unlock(&priv->queue_mutex);
+		return -EPERM;
+	}
+
+
+	/*
+	 * Create new update data structure, fill it with new update
+	 * data and add it to the list of pending updates
+	 */
+	upd_desc = kzalloc(sizeof(struct update_desc_list), GFP_KERNEL);
+	if (!upd_desc) {
+		mutex_unlock(&priv->queue_mutex);
+		return -ENOMEM;
+	}
+	/* Initialize per-update marker list */
+	upd_desc->upd_data.update_region = *clip;
+	upd_desc->upd_data.waveform_mode = WAVEFORM_MODE_AUTO;
+	upd_desc->upd_data.temp = TEMP_USE_AMBIENT;
+	upd_desc->upd_data.update_mode = UPDATE_MODE_PARTIAL;
+	upd_desc->update_order = priv->order_cnt++;
+	list_add_tail(&upd_desc->list, &priv->upd_pending_list);
+
+	/* Queued update scheme processing */
+
+	mutex_unlock(&priv->queue_mutex);
+
+	/* Signal workqueue to handle new update */
+	queue_work(priv->epdc_submit_workqueue,
+		   &priv->epdc_submit_work);
+
+	return 0;
+}
+
+static void epdc_intr_work_func(struct work_struct *work)
+{
+	struct mxc_epdc *priv =
+		container_of(work, struct mxc_epdc, epdc_intr_work);
+	struct update_data_list *collision_update;
+	u64 temp_mask;
+	u32 lut;
+	bool ignore_collision = false;
+	int i;
+	bool wb_lut_done = false;
+	bool free_update = true;
+	u32 epdc_luts_active, epdc_wb_busy, epdc_luts_avail, epdc_lut_cancelled;
+	u32 epdc_collision;
+	u64 epdc_irq_stat;
+	bool epdc_waiting_on_wb;
+	u32 coll_coord, coll_size;
+
+	/* Protect access to buffer queues and to update HW */
+	mutex_lock(&priv->queue_mutex);
+
+	/* Capture EPDC status one time to limit exposure to race conditions */
+	epdc_luts_active = epdc_any_luts_active(priv);
+	epdc_wb_busy = epdc_is_working_buffer_busy(priv);
+	epdc_lut_cancelled = epdc_is_lut_cancelled(priv);
+	epdc_luts_avail = epdc_any_luts_available(priv);
+	epdc_collision = epdc_is_collision(priv);
+
+	epdc_irq_stat = (u64)epdc_read(priv, EPDC_IRQ1) |
+			((u64)epdc_read(priv, EPDC_IRQ2) << 32);
+	epdc_waiting_on_wb = (priv->cur_update != NULL) ? true : false;
+
+	/* Free any LUTs that have completed */
+	for (i = 0; i < priv->num_luts; i++) {
+		if ((epdc_irq_stat & (1ULL << i)) == 0)
+			continue;
+
+		dev_dbg(priv->drm.dev, "LUT %d completed\n", i);
+
+		/* Disable IRQ for completed LUT */
+		epdc_lut_complete_intr(priv, i, false);
+
+		/*
+		 * Go through all updates in the collision list and
+		 * unmask any updates that were colliding with
+		 * the completed LUT.
+		 */
+		list_for_each_entry(collision_update,
+				    &priv->upd_buf_collision_list, list) {
+			collision_update->collision_mask =
+			    collision_update->collision_mask & ~BIT(i);
+		}
+
+		epdc_clear_lut_complete_irq(priv, i);
+
+		priv->luts_complete_wb |= 1ULL << i;
+
+		priv->lut_update_order[i] = 0;
+
+		/* Signal completion if submit workqueue needs a LUT */
+		if (priv->waiting_for_lut) {
+			complete(&priv->update_res_free);
+			priv->waiting_for_lut = false;
+		}
+
+		/*
+		 * Detect race condition where WB and its LUT complete
+		 * (i.e. full update completes) in one swoop
+		 */
+		if (epdc_waiting_on_wb &&
+			(i == priv->cur_update->lut_num))
+			wb_lut_done = true;
+	}
+
+	/* Check to see if all updates have completed */
+	if (list_empty(&priv->upd_pending_list) &&
+		is_free_list_full(priv) &&
+		!epdc_waiting_on_wb &&
+		!epdc_luts_active) {
+
+		priv->updates_active = false;
+
+		if (priv->pwrdown_delay != FB_POWERDOWN_DISABLE) {
+			/*
+			 * Set variable to prevent overlapping
+			 * enable/disable requests
+			 */
+			priv->powering_down = true;
+
+			/* Schedule task to disable EPDC HW until next update */
+			schedule_delayed_work(&priv->epdc_done_work,
+				msecs_to_jiffies(priv->pwrdown_delay));
+
+			/* Reset counter to reduce chance of overflow */
+			priv->order_cnt = 0;
+		}
+
+		if (priv->waiting_for_idle)
+			complete(&priv->updates_done);
+	}
+
+	/* Is Working Buffer busy? */
+	if (epdc_wb_busy) {
+		/* Can't submit another update until WB is done */
+		mutex_unlock(&priv->queue_mutex);
+		return;
+	}
+
+	/*
+	 * Were we waiting on working buffer?
+	 * If so, update queues and check for collisions
+	 */
+	if (epdc_waiting_on_wb) {
+		dev_dbg(priv->drm.dev, "\nWorking buffer completed\n");
+
+		/* Signal completion if submit workqueue was waiting on WB */
+		if (priv->waiting_for_wb) {
+			complete(&priv->update_res_free);
+			priv->waiting_for_wb = false;
+		}
+
+		if (epdc_lut_cancelled && !epdc_collision) {
+			/*
+			 * Note: The update may be cancelled (void) if all
+			 * pixels collided. In that case we handle it as a
+			 * collision, not a cancel.
+			 */
+
+			/* Clear LUT status (might be set if no AUTOWV used) */
+
+			/*
+			 * Disable and clear IRQ for the LUT used.
+			 * Even though LUT is cancelled in HW, the LUT
+			 * complete bit may be set if AUTOWV not used.
+			 */
+			epdc_lut_complete_intr(priv,
+					priv->cur_update->lut_num, false);
+			epdc_clear_lut_complete_irq(priv,
+					priv->cur_update->lut_num);
+
+			priv->lut_update_order[priv->cur_update->lut_num] = 0;
+
+			/* Signal completion if submit workqueue needs a LUT */
+			if (priv->waiting_for_lut) {
+				complete(&priv->update_res_free);
+				priv->waiting_for_lut = false;
+			}
+		} else if (epdc_collision) {
+			/* Check list of colliding LUTs, and add to our collision mask */
+			priv->cur_update->collision_mask =
+			    priv->epdc_colliding_luts;
+
+			/* Clear collisions that completed since WB began */
+			priv->cur_update->collision_mask &=
+				~priv->luts_complete_wb;
+
+			dev_dbg(priv->drm.dev, "Collision mask = 0x%llx\n",
+			       priv->epdc_colliding_luts);
+
+			/*
+			 * For EPDC 2.0 and later, minimum collision bounds
+			 * are provided by HW.  Recompute new bounds here.
+			 */
+
+			coll_coord = epdc_read(priv, EPDC_UPD_COL_CORD);
+			coll_size = epdc_read(priv, EPDC_UPD_COL_SIZE);
+			drm_rect_init(&priv->cur_update->update_desc->upd_data.update_region,
+				(coll_coord & EPDC_UPD_COL_CORD_XCORD_MASK)
+					>> EPDC_UPD_COL_CORD_XCORD_OFFSET,
+				(coll_coord & EPDC_UPD_COL_CORD_YCORD_MASK)
+					>> EPDC_UPD_COL_CORD_YCORD_OFFSET,
+				((coll_size & EPDC_UPD_COL_SIZE_WIDTH_MASK)
+					>> EPDC_UPD_COL_SIZE_WIDTH_OFFSET),
+				((coll_size & EPDC_UPD_COL_SIZE_HEIGHT_MASK)
+					>> EPDC_UPD_COL_SIZE_HEIGHT_OFFSET));
+
+			/*
+			 * If we collide with newer updates, then
+			 * we don't need to re-submit the update. The
+			 * idea is that the newer updates should take
+			 * precedence anyways, so we don't want to
+			 * overwrite them.
+			 */
+			for (temp_mask = priv->cur_update->collision_mask, lut = 0;
+				temp_mask != 0;
+				lut++, temp_mask = temp_mask >> 1) {
+				if (!(temp_mask & 0x1))
+					continue;
+
+				if (priv->lut_update_order[lut] >=
+					priv->cur_update->update_desc->update_order) {
+					dev_dbg(priv->drm.dev,
+						"Ignoring collision with newer update.\n");
+					ignore_collision = true;
+					break;
+				}
+			}
+
+			if (!ignore_collision) {
+				free_update = false;
+
+				/* Move to collision list */
+				list_add_tail(&priv->cur_update->list,
+					 &priv->upd_buf_collision_list);
+			}
+		}
+
+		/* Do we need to free the current update descriptor? */
+		if (free_update) {
+			/* Free update descriptor */
+			kfree(priv->cur_update->update_desc);
+
+			/* Add to free buffer list */
+			list_add_tail(&priv->cur_update->list,
+				 &priv->upd_buf_free_list);
+
+			/* Check to see if all updates have completed */
+			if (list_empty(&priv->upd_pending_list) &&
+				is_free_list_full(priv) &&
+				!epdc_luts_active) {
+
+				priv->updates_active = false;
+
+				if (priv->pwrdown_delay !=
+						FB_POWERDOWN_DISABLE) {
+					/*
+					 * Set variable to prevent overlapping
+					 * enable/disable requests
+					 */
+					priv->powering_down = true;
+
+					/* Schedule EPDC disable */
+					schedule_delayed_work(&priv->epdc_done_work,
+						msecs_to_jiffies(priv->pwrdown_delay));
+
+					/* Reset counter to reduce chance of overflow */
+					priv->order_cnt = 0;
+				}
+
+				if (priv->waiting_for_idle)
+					complete(&priv->updates_done);
+			}
+		}
+
+		/* Clear current update */
+		priv->cur_update = NULL;
+
+		/* Clear IRQ for working buffer */
+		epdc_working_buf_intr(priv, false);
+		epdc_clear_working_buf_irq(priv);
+	}
+
+	/* Schedule task to submit collision and pending update */
+	if (!priv->powering_down)
+		queue_work(priv->epdc_submit_workqueue,
+			&priv->epdc_submit_work);
+
+	/* Release buffer queues */
+	mutex_unlock(&priv->queue_mutex);
+}
+
+
+int mxc_epdc_init_update(struct mxc_epdc *priv)
+{
+	struct update_data_list *upd_list;
+	int ret;
+	int irq;
+	int i;
+	/*
+	 * Initialize lists for pending updates,
+	 * active update requests, update collisions,
+	 * and freely available updates.
+	 */
+	priv->num_luts = EPDC_V2_NUM_LUTS;
+	priv->max_num_updates = EPDC_V2_MAX_NUM_UPDATES;
+
+	INIT_LIST_HEAD(&priv->upd_pending_list);
+	INIT_LIST_HEAD(&priv->upd_buf_queue);
+	INIT_LIST_HEAD(&priv->upd_buf_free_list);
+	INIT_LIST_HEAD(&priv->upd_buf_collision_list);
+
+	devm_add_action_or_reset(priv->drm.dev, cleanup_update_list, priv);
+	/* Allocate update buffers and add them to the list */
+	for (i = 0; i < priv->max_num_updates; i++) {
+		upd_list = kzalloc(sizeof(*upd_list), GFP_KERNEL);
+		if (upd_list == NULL)
+			return -ENOMEM;
+
+		/* Add newly allocated buffer to free list */
+		list_add(&upd_list->list, &priv->upd_buf_free_list);
+	}
+
+
+
+	/* Initialize marker list */
+	INIT_LIST_HEAD(&priv->full_marker_list);
+
+	/* Initialize all LUTs to inactive */
+	priv->lut_update_order =
+		devm_kzalloc(priv->drm.dev, priv->num_luts * sizeof(u32 *), GFP_KERNEL);
+	if (!priv->lut_update_order)
+		return -ENOMEM;
+
+	for (i = 0; i < priv->num_luts; i++)
+		priv->lut_update_order[i] = 0;
+
+	INIT_DELAYED_WORK(&priv->epdc_done_work, epdc_done_work_func);
+	priv->epdc_submit_workqueue = alloc_workqueue("EPDC Submit",
+					WQ_MEM_RECLAIM | WQ_HIGHPRI |
+					WQ_CPU_INTENSIVE | WQ_UNBOUND, 1);
+	INIT_WORK(&priv->epdc_submit_work, epdc_submit_work_func);
+	priv->epdc_intr_workqueue = alloc_workqueue("EPDC Interrupt",
+					WQ_MEM_RECLAIM | WQ_HIGHPRI |
+					WQ_CPU_INTENSIVE | WQ_UNBOUND, 1);
+	INIT_WORK(&priv->epdc_intr_work, epdc_intr_work_func);
+
+	mutex_init(&priv->queue_mutex);
+
+	/* Retrieve EPDC IRQ num */
+	irq = platform_get_irq(to_platform_device(priv->drm.dev), 0);
+	if (irq < 0) {
+		dev_err(priv->drm.dev, "cannot get IRQ resource\n");
+		return -ENODEV;
+	}
+	priv->epdc_irq = irq;
+
+	/* Register IRQ handler */
+	ret = devm_request_irq(priv->drm.dev, priv->epdc_irq,
+				mxc_epdc_irq_handler, 0, "epdc", priv);
+	if (ret) {
+		dev_err(priv->drm.dev, "request_irq (%d) failed with error %d\n",
+			priv->epdc_irq, ret);
+		return ret;
+	}
+
+	return 0;
+
+}
+
diff --git a/drivers/gpu/drm/mxc-epdc/epdc_update.h b/drivers/gpu/drm/mxc-epdc/epdc_update.h
new file mode 100644
index 000000000000..b6f30cf8eda7
--- /dev/null
+++ b/drivers/gpu/drm/mxc-epdc/epdc_update.h
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/* Copyright (C) 2020 Andreas Kemnade */
+int mxc_epdc_send_single_update(struct drm_rect *clip, int pitch,
+				void *vaddr,
+				struct mxc_epdc *priv);
+void mxc_epdc_draw_mode0(struct mxc_epdc *priv);
+int mxc_epdc_init_update(struct mxc_epdc *priv);
+void mxc_epdc_flush_updates(struct mxc_epdc *priv);
+
diff --git a/drivers/gpu/drm/mxc-epdc/mxc_epdc.h b/drivers/gpu/drm/mxc-epdc/mxc_epdc.h
index f7b1cbc4cc4e..db91e615bd89 100644
--- a/drivers/gpu/drm/mxc-epdc/mxc_epdc.h
+++ b/drivers/gpu/drm/mxc-epdc/mxc_epdc.h
@@ -12,6 +12,35 @@
 #include "epdc_regs.h"
 
 #define TEMP_USE_AMBIENT			0x1000
+#define GRAYSCALE_8BIT				0x1
+#define GRAYSCALE_8BIT_INVERTED			0x2
+#define GRAYSCALE_4BIT			  0x3
+#define GRAYSCALE_4BIT_INVERTED		 0x4
+
+#define AUTO_UPDATE_MODE_REGION_MODE		0
+#define AUTO_UPDATE_MODE_AUTOMATIC_MODE		1
+
+#define UPDATE_SCHEME_SNAPSHOT			0
+#define UPDATE_SCHEME_QUEUE			1
+#define UPDATE_SCHEME_QUEUE_AND_MERGE		2
+
+#define UPDATE_MODE_PARTIAL			0x0
+#define UPDATE_MODE_FULL			0x1
+
+#define WAVEFORM_MODE_GLR16			4
+#define WAVEFORM_MODE_GLD16			5
+#define WAVEFORM_MODE_AUTO			257
+
+#define FB_POWERDOWN_DISABLE			-1
+
+struct mxcfb_update_data {
+	struct drm_rect update_region;
+	u32 waveform_mode;
+	u32 update_mode;
+	int temp;
+	int dither_mode;
+	int quant_bit;
+};
 
 struct mxcfb_waveform_modes {
 	int mode_init;
@@ -87,6 +116,27 @@ struct mxc_epdc {
 	bool hw_initializing;
 	bool waiting_for_idle;
 
+	int order_cnt;
+	struct list_head upd_pending_list;
+	struct list_head upd_buf_queue;
+	struct list_head upd_buf_free_list;
+	struct list_head upd_buf_collision_list;
+	struct update_data_list *cur_update;
+	struct mutex queue_mutex;
+	int epdc_irq;
+	struct list_head full_marker_list;
+	u32 *lut_update_order;
+	u64 epdc_colliding_luts;
+	u64 luts_complete_wb;
+	struct completion updates_done;
+	struct delayed_work epdc_done_work;
+	struct workqueue_struct *epdc_submit_workqueue;
+	struct work_struct epdc_submit_work;
+	struct workqueue_struct *epdc_intr_workqueue;
+	struct work_struct epdc_intr_work;
+	bool waiting_for_wb;
+	bool waiting_for_lut;
+	struct completion update_res_free;
 };
 
 static inline u32 epdc_read(struct mxc_epdc *priv, u32 reg)
diff --git a/drivers/gpu/drm/mxc-epdc/mxc_epdc_drv.c b/drivers/gpu/drm/mxc-epdc/mxc_epdc_drv.c
index 4810e5c5bc6e..a0bde06187ca 100644
--- a/drivers/gpu/drm/mxc-epdc/mxc_epdc_drv.c
+++ b/drivers/gpu/drm/mxc-epdc/mxc_epdc_drv.c
@@ -12,6 +12,7 @@
 #include <drm/drm_atomic.h>
 #include <drm/drm_atomic_helper.h>
 #include <drm/drm_crtc_helper.h>
+#include <drm/drm_damage_helper.h>
 #include <drm/drm_drv.h>
 #include <drm/drm_fb_cma_helper.h>
 #include <drm/drm_fb_helper.h>
@@ -26,6 +27,7 @@
 #include <drm/drm_probe_helper.h>
 #include "mxc_epdc.h"
 #include "epdc_hw.h"
+#include "epdc_update.h"
 #include "epdc_waveform.h"
 
 #define DRIVER_NAME "mxc_epdc"
@@ -211,6 +213,7 @@ static void mxc_epdc_pipe_disable(struct drm_simple_display_pipe *pipe)
 	struct mxc_epdc *priv = drm_pipe_to_mxc_epdc(pipe);
 
 	dev_dbg(priv->drm.dev, "pipe disable\n");
+	mxc_epdc_flush_updates(priv);
 
 	if (priv->epdc_mem_virt) {
 		dma_free_wc(priv->drm.dev, priv->epdc_mem_width * priv->epdc_mem_height,
@@ -227,11 +230,35 @@ static void mxc_epdc_pipe_disable(struct drm_simple_display_pipe *pipe)
 }
 
 static void mxc_epdc_pipe_update(struct drm_simple_display_pipe *pipe,
-				   struct drm_plane_state *plane_state)
+				   struct drm_plane_state *old_state)
 {
 	struct mxc_epdc *priv = drm_pipe_to_mxc_epdc(pipe);
+	struct drm_gem_cma_object *gem;
+	struct drm_atomic_helper_damage_iter iter;
+	struct drm_rect clip;
+
 
 	dev_dbg(priv->drm.dev, "pipe update\n");
+	if (!old_state->fb) {
+		dev_dbg(priv->drm.dev, "no fb, nothing to update\n");
+		return;
+	}
+
+	if (priv->epdc_mem_virt == NULL)
+		return;
+
+	gem = drm_fb_cma_get_gem_obj(old_state->fb, 0);
+	drm_atomic_helper_damage_iter_init(&iter, old_state, pipe->plane.state);
+	drm_atomic_for_each_plane_damage(&iter, &clip) {
+
+		dev_dbg(priv->drm.dev, "damaged: %d,%d-%d,%d\n",
+			clip.x1, clip.y1, clip.x2, clip.y2);
+
+		mxc_epdc_send_single_update(&clip, old_state->fb->pitches[0],
+					    gem->vaddr, priv);
+	}
+
+	return;
 }
 
 static const struct drm_simple_display_pipe_funcs mxc_epdc_funcs = {
@@ -280,6 +307,10 @@ static int mxc_epdc_probe(struct platform_device *pdev)
 	if (ret)
 		return ret;
 
+	ret = mxc_epdc_init_update(priv);
+	if (ret)
+		return ret;
+
 	ret = request_firmware(&firmware, "imx/epdc/epdc.fw", priv->drm.dev);
 	if (ret)
 		return ret;
