drm/msm/sde: add virtual writeback device and connector

Enable virtual writeback connector by allowing a property on the
connector to specify output frame buffer id, which takes in a
framebuffer object added via ADDFB2 ioctl.  A new writeback
configure ioctl is added to configure connection state and
display modes of writeback connector.

Change-Id: Ifce411a3f0798e3af7dd7f19da27d67cdd849bfb
Signed-off-by: Adrian Salido-Moreno <adrianm@codeaurora.org>
Signed-off-by: Alan Kwong <akwong@codeaurora.org>
Signed-off-by: Clarence Ip <cip@codeaurora.org>
Signed-off-by: Dhaval Patel <pdhaval@codeaurora.org>
Signed-off-by: Narendra Muppalla <narendram@codeaurora.org>
diff --git a/drivers/gpu/drm/msm/Kconfig b/drivers/gpu/drm/msm/Kconfig
index b03bcdd..a9c5aa6 100644
--- a/drivers/gpu/drm/msm/Kconfig
+++ b/drivers/gpu/drm/msm/Kconfig
@@ -96,3 +96,11 @@
 	default n
 	help
 	  Chose this option if HDCP supported is needed in DRM/KMS driver.
+
+config DRM_SDE_WB
+	bool "Enable Writeback support in SDE DRM"
+	depends on DRM_MSM
+	default y
+	help
+	  Choose this option for writeback connector support.
+
diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
index 366da8a..280a7dc 100644
--- a/drivers/gpu/drm/msm/Makefile
+++ b/drivers/gpu/drm/msm/Makefile
@@ -131,3 +131,6 @@
 	sde/sde_formats.o
 
 obj-$(CONFIG_DRM_MSM) += display-manager/display_manager.o
+
+obj-$(CONFIG_DRM_SDE_WB) += sde/sde_wb.o \
+	sde/sde_encoder_phys_wb.o
diff --git a/drivers/gpu/drm/msm/msm_drv.c b/drivers/gpu/drm/msm/msm_drv.c
index a3822ee..9830b2f 100644
--- a/drivers/gpu/drm/msm/msm_drv.c
+++ b/drivers/gpu/drm/msm/msm_drv.c
@@ -21,6 +21,7 @@
 #include "msm_gpu.h"
 #include "msm_kms.h"
 #include "display_manager.h"
+#include "sde_wb.h"
 
 
 /*
@@ -782,6 +783,7 @@
 	DRM_IOCTL_DEF_DRV(MSM_GEM_SUBMIT,   msm_ioctl_gem_submit,   DRM_AUTH|DRM_RENDER_ALLOW),
 	DRM_IOCTL_DEF_DRV(MSM_WAIT_FENCE,   msm_ioctl_wait_fence,   DRM_AUTH|DRM_RENDER_ALLOW),
 	DRM_IOCTL_DEF_DRV(MSM_GEM_MADVISE,  msm_ioctl_gem_madvise,  DRM_AUTH|DRM_RENDER_ALLOW),
+	DRM_IOCTL_DEF_DRV(SDE_WB_CONFIG, sde_wb_config, DRM_UNLOCKED|DRM_AUTH),
 };
 
 static const struct vm_operations_struct vm_ops = {
diff --git a/drivers/gpu/drm/msm/msm_drv.h b/drivers/gpu/drm/msm/msm_drv.h
index 0c2a552..69db252 100644
--- a/drivers/gpu/drm/msm/msm_drv.h
+++ b/drivers/gpu/drm/msm/msm_drv.h
@@ -116,6 +116,10 @@
 	/* range properties */
 	CONNECTOR_PROP_OUT_FB = CONNECTOR_PROP_BLOBCOUNT,
 	CONNECTOR_PROP_RETIRE_FENCE,
+	CONNECTOR_PROP_DST_X,
+	CONNECTOR_PROP_DST_Y,
+	CONNECTOR_PROP_DST_W,
+	CONNECTOR_PROP_DST_H,
 
 	/* enum/bitmask properties */
 
diff --git a/drivers/gpu/drm/msm/sde/sde_crtc.c b/drivers/gpu/drm/msm/sde/sde_crtc.c
index 0e1e829..d7e8dcc 100644
--- a/drivers/gpu/drm/msm/sde/sde_crtc.c
+++ b/drivers/gpu/drm/msm/sde/sde_crtc.c
@@ -120,6 +120,34 @@
 		}
 	}
 
+	/*
+	 * Get default LMs if specified in platform config,
+	 * otherwise acquire the free LMs.
+	 */
+	for (i = WB_0; i < WB_MAX; i++) {
+		if (enc_hw_res.wbs[i]) {
+			struct sde_crtc_mixer *mixer =
+				&sde_crtc->mixer[sde_crtc->num_mixers];
+			plat_hw_res_map = sde_rm_get_res_map(sde_kms,
+					SDE_NONE, i);
+
+			lm_idx = plat_hw_res_map->lm;
+			if (!lm_idx && unused_lm_count)
+				lm_idx = unused_lm_id[--unused_lm_count];
+
+			DBG("Acquiring LM %d", lm_idx);
+			mixer->hw_lm = sde_rm_acquire_mixer(sde_kms, lm_idx);
+			if (IS_ERR_OR_NULL(mixer->hw_lm)) {
+				DRM_ERROR("Invalid mixer\n");
+				return -EACCES;
+			}
+			/* interface info */
+			mixer->wb_idx = i;
+			mixer->mode = enc_hw_res.wbs[i];
+			sde_crtc->num_mixers++;
+		}
+	}
+
 	DBG("control paths %d, num_mixers %d, lm[0] %d, ctl[0] %d ",
 			sde_crtc->num_ctls, sde_crtc->num_mixers,
 			sde_crtc->mixer[0].hw_lm->idx,
@@ -413,8 +441,23 @@
 	for (i = 0; i < sde_crtc->num_ctls; i++) {
 		mixer = &sde_crtc->mixer[i];
 		ctl = mixer->hw_ctl;
-		ctl->ops.get_bitmask_intf(ctl, &mixer->flush_mask,
-				mixer->intf_idx);
+
+		switch (mixer->mode) {
+		case INTF_MODE_CMD:
+		case INTF_MODE_VIDEO:
+			ctl->ops.get_bitmask_intf(ctl, &mixer->flush_mask,
+					mixer->intf_idx);
+			break;
+		case INTF_MODE_WB_LINE:
+			ctl->ops.get_bitmask_wb(ctl, &mixer->flush_mask,
+					mixer->wb_idx);
+			break;
+		default:
+			DBG("Invalid ctl %d interface mode %d", ctl->idx,
+					mixer->mode);
+			return -EINVAL;
+		}
+
 		ctl->ops.update_pending_flush(ctl, mixer->flush_mask);
 		DBG("added CTL_ID %d mask 0x%x to pending flush", ctl->idx,
 						mixer->flush_mask);
@@ -1116,10 +1159,11 @@
 		} else if (!m->hw_ctl) {
 			seq_printf(s, "Mixer[%d] has no CTL\n", i);
 		} else {
-			seq_printf(s, "LM_%d/CTL_%d -> INTF_%d\n",
+			seq_printf(s, "LM_%d/CTL_%d -> INTF_%d, WB_%d\n",
 					m->hw_lm->idx - LM_0,
 					m->hw_ctl->idx - CTL_0,
-					m->intf_idx - INTF_0);
+					m->intf_idx - INTF_0,
+					m->wb_idx - WB_0);
 		}
 	}
 	seq_printf(s, "Border: %d\n", sde_crtc->stage_cfg.border_enable);
diff --git a/drivers/gpu/drm/msm/sde/sde_crtc.h b/drivers/gpu/drm/msm/sde/sde_crtc.h
index 417e54b..c34be7f4 100644
--- a/drivers/gpu/drm/msm/sde/sde_crtc.h
+++ b/drivers/gpu/drm/msm/sde/sde_crtc.h
@@ -31,6 +31,7 @@
  * @hw_lm       : LM HW Driver context
  * @hw_ctl      : CTL Path HW driver context
  * @intf_idx    : Interface idx
+ * @wb_idx      : Writeback idx
  * @mode        : Interface mode Active/CMD
  * @flush_mask  : Flush mask value for this commit
  */
@@ -39,6 +40,7 @@
 	struct sde_hw_mixer *hw_lm;
 	struct sde_hw_ctl   *hw_ctl;
 	enum sde_intf       intf_idx;
+	enum sde_wb         wb_idx;
 	enum sde_intf_mode  mode;
 	u32 flush_mask;
 };
diff --git a/drivers/gpu/drm/msm/sde/sde_encoder.c b/drivers/gpu/drm/msm/sde/sde_encoder.c
index 5d4d6ce..5194679 100644
--- a/drivers/gpu/drm/msm/sde/sde_encoder.c
+++ b/drivers/gpu/drm/msm/sde/sde_encoder.c
@@ -256,6 +256,57 @@
 	return ret;
 }
 
+static int sde_encoder_virt_atomic_check(struct drm_encoder *drm_enc,
+		    struct drm_crtc_state *crtc_state,
+		    struct drm_connector_state *conn_state)
+{
+	struct sde_encoder_virt *sde_enc = NULL;
+	const struct drm_display_mode *mode;
+	struct drm_display_mode *adj_mode;
+	int i = 0;
+	int ret = 0;
+
+	DBG("");
+
+	if (!drm_enc || !crtc_state || !conn_state) {
+		DRM_ERROR("Invalid pointer");
+		return -EINVAL;
+	}
+
+	sde_enc = to_sde_encoder_virt(drm_enc);
+	mode = &crtc_state->mode;
+	adj_mode = &crtc_state->adjusted_mode;
+	MSM_EVT(drm_enc->dev, 0, 0);
+
+	/* perform atomic check on the first physical encoder (master) */
+	for (i = 0; i < sde_enc->num_phys_encs; i++) {
+		struct sde_encoder_phys *phys = sde_enc->phys_encs[i];
+
+		if (phys && phys->ops.atomic_check) {
+			ret = phys->ops.atomic_check(phys, crtc_state,
+					conn_state);
+			if (ret)
+				DRM_ERROR("Mode unsupported, phys_enc %d.%d\n",
+						drm_enc->base.id, i);
+			break;
+		} else if (phys && phys->ops.mode_fixup) {
+			if (!phys->ops.mode_fixup(phys, mode, adj_mode)) {
+				DRM_ERROR("Mode unsupported, phys_enc %d.%d\n",
+						drm_enc->base.id, i);
+				ret = -EINVAL;
+			}
+			break;
+		}
+	}
+
+	/* Call to populate mode->crtc* information required by framework */
+	drm_mode_set_crtcinfo(adj_mode, 0);
+
+	MSM_EVT(drm_enc->dev, adj_mode->flags, adj_mode->private_flags);
+
+	return ret;
+}
+
 static void sde_encoder_virt_mode_set(struct drm_encoder *drm_enc,
 				      struct drm_display_mode *mode,
 				      struct drm_display_mode *adj_mode)
@@ -351,6 +402,7 @@
 	.mode_set = sde_encoder_virt_mode_set,
 	.disable = sde_encoder_virt_disable,
 	.enable = sde_encoder_virt_enable,
+	.atomic_check = sde_encoder_virt_atomic_check,
 };
 
 static const struct drm_encoder_funcs sde_encoder_funcs = {
@@ -374,6 +426,15 @@
 	return INTF_MAX;
 }
 
+static enum sde_wb sde_encoder_get_wb(struct sde_mdss_cfg *catalog,
+		enum sde_intf_type type, u32 controller_id)
+{
+	if (controller_id < catalog->wb_count)
+		return catalog->wb[controller_id].id;
+
+	return WB_MAX;
+}
+
 static void sde_encoder_vblank_callback(struct drm_encoder *drm_enc)
 {
 	struct sde_encoder_virt *sde_enc = NULL;
@@ -567,6 +628,45 @@
 	return 0;
 }
 
+static int sde_encoder_virt_add_phys_enc_wb(
+		struct sde_encoder_virt *sde_enc,
+		struct sde_kms *sde_kms,
+		enum sde_wb wb_idx,
+		enum sde_ctl ctl_idx,
+		enum sde_cdm cdm_idx,
+		enum sde_enc_split_role split_role)
+{
+	struct sde_encoder_phys *enc = NULL;
+	struct sde_encoder_virt_ops parent_ops = {
+		sde_encoder_vblank_callback,
+		sde_encoder_handle_phys_enc_ready_for_kickoff
+	};
+
+	DBG("");
+
+	if (sde_enc->num_phys_encs + 1 >=
+			ARRAY_SIZE(sde_enc->phys_encs)) {
+		DRM_ERROR("Too many physical encoders %d, unable to add\n",
+			  sde_enc->num_phys_encs);
+		return -EINVAL;
+	}
+
+	enc = sde_encoder_phys_wb_init(sde_kms, wb_idx,
+			ctl_idx, cdm_idx, split_role, &sde_enc->base,
+			parent_ops);
+
+	if (IS_ERR_OR_NULL(enc)) {
+		DRM_ERROR("Failed to initialize phys wb enc: %ld\n",
+			PTR_ERR(enc));
+		return enc == 0 ? -EINVAL : PTR_ERR(enc);
+	}
+
+	sde_enc->phys_encs[sde_enc->num_phys_encs] = enc;
+	++sde_enc->num_phys_encs;
+
+	return 0;
+}
+
 static int sde_encoder_setup_display(struct sde_encoder_virt *sde_enc,
 				 struct sde_kms *sde_kms,
 				 struct msm_display_info *disp_info,
@@ -584,6 +684,9 @@
 	} else if (disp_info->intf_type == DRM_MODE_CONNECTOR_HDMIA) {
 		*drm_enc_mode = DRM_MODE_ENCODER_TMDS;
 		intf_type = INTF_HDMI;
+	} else if (disp_info->intf_type == DRM_MODE_CONNECTOR_VIRTUAL) {
+		*drm_enc_mode = DRM_MODE_ENCODER_VIRTUAL;
+		intf_type = INTF_WB;
 	} else {
 		DRM_ERROR("Unsupported display interface type");
 		return -EINVAL;
@@ -602,7 +705,9 @@
 		const struct sde_hw_res_map *hw_res_map = NULL;
 		enum sde_intf intf_idx = INTF_MAX;
 		enum sde_pingpong pp_idx = PINGPONG_MAX;
+		enum sde_wb wb_idx = WB_MAX;
 		enum sde_ctl ctl_idx = CTL_MAX;
+		enum sde_cdm cdm_idx = SDE_NONE;
 		u32 controller_id = disp_info->h_tile_instance[i];
 		enum sde_enc_split_role split_role = ENC_ROLE_SOLO;
 
@@ -616,26 +721,45 @@
 		DBG("h_tile_instance %d = %d, split_role %d",
 				i, controller_id, split_role);
 
-		intf_idx = sde_encoder_get_intf(sde_kms->catalog,
-				intf_type, controller_id);
-		if (intf_idx == INTF_MAX) {
-			DRM_ERROR("Error: could not get the interface id\n");
-			ret = -EINVAL;
+		if (intf_type == INTF_WB) {
+			wb_idx = sde_encoder_get_wb(sde_kms->catalog,
+					intf_type, controller_id);
+			if (wb_idx == WB_MAX) {
+				DRM_ERROR(
+					"Error: could not get the writeback id\n");
+				ret = -EINVAL;
+			}
+			intf_idx = SDE_NONE;
+		} else {
+			intf_idx = sde_encoder_get_intf(sde_kms->catalog,
+					intf_type, controller_id);
+			if (intf_idx == INTF_MAX) {
+				DRM_ERROR(
+					"Error: could not get the interface id\n");
+				ret = -EINVAL;
+			}
+			wb_idx = SDE_NONE;
 		}
 
-		hw_res_map = sde_rm_get_res_map(sde_kms, intf_idx, SDE_NONE);
+		hw_res_map = sde_rm_get_res_map(sde_kms, intf_idx, wb_idx);
 		if (IS_ERR_OR_NULL(hw_res_map)) {
 			ret = -EINVAL;
 		} else {
 			pp_idx = hw_res_map->pp;
 			ctl_idx = hw_res_map->ctl;
+			cdm_idx = hw_res_map->cdm;
 		}
 
 		if (!ret) {
-			ret = sde_encoder_virt_add_phys_encs(
-					disp_info->capabilities,
-					sde_enc, sde_kms, intf_idx, pp_idx,
-					ctl_idx, split_role);
+			if (intf_type == INTF_WB)
+				ret = sde_encoder_virt_add_phys_enc_wb(
+						sde_enc, sde_kms, wb_idx,
+						ctl_idx, cdm_idx, split_role);
+			else
+				ret = sde_encoder_virt_add_phys_encs(
+						disp_info->capabilities,
+						sde_enc, sde_kms, intf_idx,
+						pp_idx, ctl_idx, split_role);
 			if (ret)
 				DRM_ERROR("Failed to add phys encs\n");
 		}
diff --git a/drivers/gpu/drm/msm/sde/sde_encoder_phys.h b/drivers/gpu/drm/msm/sde/sde_encoder_phys.h
index cc9fd9f..45114d4 100644
--- a/drivers/gpu/drm/msm/sde/sde_encoder_phys.h
+++ b/drivers/gpu/drm/msm/sde/sde_encoder_phys.h
@@ -20,6 +20,10 @@
 #include "sde_hw_pingpong.h"
 #include "sde_hw_ctl.h"
 #include "sde_hw_top.h"
+#include "sde_hw_wb.h"
+#include "sde_hw_cdm.h"
+
+#define SDE_ENCODER_NAME_MAX	16
 
 /**
  * enum sde_enc_split_role - Role this physical encoder will play in a
@@ -62,6 +66,7 @@
  *				This likely caches the mode, for use at enable.
  * @enable:			DRM Call. Enable a DRM mode.
  * @disable:			DRM Call. Disable mode.
+ * @atomic_check:		DRM Call. Atomic check new DRM state.
  * @destroy:			DRM Call. Destroy and release resources.
  * @get_hw_resources:		Populate the structure with the hardware
  *				resources that this phys_enc is using.
@@ -86,6 +91,9 @@
 			struct drm_display_mode *adjusted_mode);
 	void (*enable)(struct sde_encoder_phys *encoder);
 	void (*disable)(struct sde_encoder_phys *encoder);
+	int (*atomic_check)(struct sde_encoder_phys *encoder,
+			    struct drm_crtc_state *crtc_state,
+			    struct drm_connector_state *conn_state);
 	void (*destroy)(struct sde_encoder_phys *encoder);
 	void (*get_hw_resources)(struct sde_encoder_phys *encoder,
 			struct sde_encoder_hw_resources *hw_res);
@@ -118,6 +126,8 @@
  * @parent_ops:		Callbacks exposed by the parent to the phys_enc
  * @hw_mdptop:		Hardware interface to the top registers
  * @hw_ctl:		Hardware interface to the ctl registers
+ * @hw_cdm:		Hardware interface to the cdm registers
+ * @cdm_cfg:		Chroma-down hardware configuration
  * @sde_kms:		Pointer to the sde_kms top level
  * @cached_mode:	DRM mode cached at mode_set time, acted on in enable
  * @enabled:		Whether the encoder has enabled and running a mode
@@ -132,6 +142,8 @@
 	struct sde_encoder_virt_ops parent_ops;
 	struct sde_hw_mdp *hw_mdptop;
 	struct sde_hw_ctl *hw_ctl;
+	struct sde_hw_cdm *hw_cdm;
+	struct sde_hw_cdm_cfg cdm_cfg;
 	struct sde_kms *sde_kms;
 	struct drm_display_mode cached_mode;
 	enum sde_enc_split_role split_role;
@@ -185,6 +197,51 @@
 };
 
 /**
+ * struct sde_encoder_phys_wb - sub-class of sde_encoder_phys to handle
+ *	writeback specific operations
+ * @base:		Baseclass physical encoder structure
+ * @hw_wb:		Hardware interface to the wb registers
+ * @irq_idx:		IRQ interface lookup index
+ * @wbdone_timeout:	Timeout value for writeback done in msec
+ * @bypass_irqreg:	Bypass irq register/unregister if non-zero
+ * @wbdone_complete:	for wbdone irq synchronization
+ * @wb_cfg:		Writeback hardware configuration
+ * @intf_cfg:		Interface hardware configuration
+ * @wb_roi:		Writeback region-of-interest
+ * @wb_fmt:		Writeback pixel format
+ * @frame_count:	Counter of completed writeback operations
+ * @kickoff_count:	Counter of issued writeback operations
+ * @mmu_id:		mmu identifier for non-secure/secure domain
+ * @wb_dev:		Pointer to writeback device
+ * @start_time:		Start time of writeback latest request
+ * @end_time:		End time of writeback latest request
+ * @wb_name:		Name of this writeback device
+ * @debugfs_root:	Root entry of writeback debugfs
+ */
+struct sde_encoder_phys_wb {
+	struct sde_encoder_phys base;
+	struct sde_hw_wb *hw_wb;
+	int irq_idx;
+	u32 wbdone_timeout;
+	u32 bypass_irqreg;
+	struct completion wbdone_complete;
+	struct sde_hw_wb_cfg wb_cfg;
+	struct sde_hw_intf_cfg intf_cfg;
+	struct sde_rect wb_roi;
+	const struct sde_format *wb_fmt;
+	u32 frame_count;
+	u32 kickoff_count;
+	int mmu_id[SDE_IOMMU_DOMAIN_MAX];
+	struct sde_wb_device *wb_dev;
+	ktime_t start_time;
+	ktime_t end_time;
+#ifdef CONFIG_DEBUG_FS
+	char wb_name[SDE_ENCODER_NAME_MAX];
+	struct dentry *debugfs_root;
+#endif
+};
+
+/**
  * sde_encoder_phys_vid_init - Construct a new video mode physical encoder
  * @sde_kms:		Pointer to the sde_kms top level
  * @intf_idx:		Interface index this phys_enc will control
@@ -222,6 +279,44 @@
 		struct drm_encoder *parent,
 		struct sde_encoder_virt_ops parent_ops);
 
+/**
+ * sde_encoder_phys_wb_init - Construct a new writeback physical encoder
+ * @sde_kms:		Pointer to the sde_kms top level
+ * @wb_idx:		Writeback index this phys_enc will control
+ * @ctl_idx:		Control index this phys_enc requires
+ * @cdm_idx:		Chromadown index this phys_enc requires
+ * @split_role:		Role to play in a split-panel configuration
+ * @parent:		Pointer to the containing virtual encoder
+ * @parent_ops:		Callbacks exposed by the parent to the phys_enc
+ *
+ * Return: Error code or newly allocated encoder
+ */
+#ifdef CONFIG_DRM_SDE_WB
+struct sde_encoder_phys *sde_encoder_phys_wb_init(
+		struct sde_kms *sde_kms,
+		enum sde_wb wb_idx,
+		enum sde_ctl ctl_idx,
+		enum sde_cdm cdm_idx,
+		enum sde_enc_split_role split_role,
+		struct drm_encoder *parent,
+		struct sde_encoder_virt_ops parent_ops);
+#else
+static inline
+struct sde_encoder_phys *sde_encoder_phys_wb_init(
+		struct sde_kms *sde_kms,
+		enum sde_wb wb_idx,
+		enum sde_ctl ctl_idx,
+		enum sde_cdm cdm_idx,
+		enum sde_enc_split_role split_role,
+		struct drm_encoder *parent,
+		struct sde_encoder_virt_ops parent_ops)
+{
+	return NULL;
+}
+#endif
 
+void sde_encoder_phys_setup_cdm(struct sde_encoder_phys *phys_enc,
+		struct drm_framebuffer *fb, const struct sde_format *format,
+		struct sde_rect *wb_roi);
 
 #endif /* __sde_encoder_phys_H__ */
diff --git a/drivers/gpu/drm/msm/sde/sde_encoder_phys_wb.c b/drivers/gpu/drm/msm/sde/sde_encoder_phys_wb.c
new file mode 100644
index 0000000..1f09470
--- /dev/null
+++ b/drivers/gpu/drm/msm/sde/sde_encoder_phys_wb.c
@@ -0,0 +1,1028 @@
+/*
+ * Copyright (c) 2015-2016 The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#define pr_fmt(fmt)	"sde-wb:[%s] " fmt, __func__
+
+#include <linux/jiffies.h>
+#include <linux/debugfs.h>
+
+#include "sde_encoder_phys.h"
+#include "sde_formats.h"
+#include "sde_hw_top.h"
+#include "sde_hw_interrupts.h"
+#include "sde_wb.h"
+
+/* wait for at most 2 vsync for lowest refresh rate (24hz) */
+#define WAIT_TIMEOUT_MSEC			84
+
+#define to_sde_encoder_phys_wb(x) \
+	container_of(x, struct sde_encoder_phys_wb, base)
+
+#define DEV(phy_enc) (phy_enc->parent->dev)
+
+/**
+ * sde_encoder_phys_wb_get_intr_type - get interrupt type based on block mode
+ * @hw_wb:	Pointer to h/w writeback driver
+ */
+static enum sde_intr_type sde_encoder_phys_wb_get_intr_type(
+		struct sde_hw_wb *hw_wb)
+{
+	return (hw_wb->caps->features & BIT(SDE_WB_BLOCK_MODE)) ?
+			SDE_IRQ_TYPE_WB_ROT_COMP : SDE_IRQ_TYPE_WB_WFD_COMP;
+}
+
+/**
+ * sde_encoder_phys_wb_set_traffic_shaper - set traffic shaper for writeback
+ * @phys_enc:	Pointer to physical encoder
+ */
+static void sde_encoder_phys_wb_set_traffic_shaper(
+		struct sde_encoder_phys *phys_enc)
+{
+	struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
+	struct sde_hw_wb_cfg *wb_cfg = &wb_enc->wb_cfg;
+
+	/* traffic shaper is only enabled for rotator */
+	wb_cfg->ts_cfg.en = false;
+}
+
+/**
+ * sde_encoder_phys_setup_cdm - setup chroma down block
+ * @phys_enc:	Pointer to physical encoder
+ * @fb:		Pointer to output framebuffer
+ * @format:	Output format
+ */
+void sde_encoder_phys_setup_cdm(struct sde_encoder_phys *phys_enc,
+		struct drm_framebuffer *fb, const struct sde_format *format,
+		struct sde_rect *wb_roi)
+{
+	struct sde_hw_cdm *hw_cdm = phys_enc->hw_cdm;
+	struct sde_hw_cdm_cfg *cdm_cfg = &phys_enc->cdm_cfg;
+	int ret;
+
+	if (!SDE_FORMAT_IS_YUV(format)) {
+		SDE_DEBUG("[cdm_disable fmt:%x]\n",
+				format->base.pixel_format);
+
+		if (hw_cdm && hw_cdm->ops.disable)
+			hw_cdm->ops.disable(hw_cdm);
+
+		return;
+	}
+
+	memset(cdm_cfg, 0, sizeof(struct sde_hw_cdm_cfg));
+
+	cdm_cfg->output_width = wb_roi->w;
+	cdm_cfg->output_height = wb_roi->h;
+	cdm_cfg->output_fmt = format;
+	cdm_cfg->output_type = CDM_CDWN_OUTPUT_WB;
+	cdm_cfg->output_bit_depth = CDM_CDWN_OUTPUT_8BIT;
+
+	/* enable 10 bit logic */
+	switch (cdm_cfg->output_fmt->chroma_sample) {
+	case SDE_CHROMA_RGB:
+		cdm_cfg->h_cdwn_type = CDM_CDWN_DISABLE;
+		cdm_cfg->v_cdwn_type = CDM_CDWN_DISABLE;
+		break;
+	case SDE_CHROMA_H2V1:
+		cdm_cfg->h_cdwn_type = CDM_CDWN_COSITE;
+		cdm_cfg->v_cdwn_type = CDM_CDWN_DISABLE;
+		break;
+	case SDE_CHROMA_420:
+		cdm_cfg->h_cdwn_type = CDM_CDWN_COSITE;
+		cdm_cfg->v_cdwn_type = CDM_CDWN_OFFSITE;
+		break;
+	case SDE_CHROMA_H1V2:
+	default:
+		SDE_ERROR("unsupported chroma sampling type\n");
+		cdm_cfg->h_cdwn_type = CDM_CDWN_DISABLE;
+		cdm_cfg->v_cdwn_type = CDM_CDWN_DISABLE;
+		break;
+	}
+
+	SDE_DEBUG("[cdm_enable:%d,%d,%X,%d,%d,%d,%d]\n",
+			cdm_cfg->output_width,
+			cdm_cfg->output_height,
+			cdm_cfg->output_fmt->base.pixel_format,
+			cdm_cfg->output_type,
+			cdm_cfg->output_bit_depth,
+			cdm_cfg->h_cdwn_type,
+			cdm_cfg->v_cdwn_type);
+
+	if (hw_cdm && hw_cdm->ops.setup_cdwn) {
+		ret = hw_cdm->ops.setup_cdwn(hw_cdm, cdm_cfg);
+		if (ret < 0) {
+			SDE_ERROR("failed to setup CDM %d\n", ret);
+			return;
+		}
+	}
+
+	if (hw_cdm && hw_cdm->ops.enable) {
+		ret = hw_cdm->ops.enable(hw_cdm, cdm_cfg);
+		if (ret < 0) {
+			SDE_ERROR("failed to enable CDM %d\n", ret);
+			return;
+		}
+	}
+}
+
+/**
+ * sde_encoder_phys_wb_setup_fb - setup output framebuffer
+ * @phys_enc:	Pointer to physical encoder
+ * @fb:		Pointer to output framebuffer
+ * @wb_roi:	Pointer to output region of interest
+ */
+static void sde_encoder_phys_wb_setup_fb(struct sde_encoder_phys *phys_enc,
+		struct drm_framebuffer *fb, struct sde_rect *wb_roi)
+{
+	struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
+	struct sde_hw_wb *hw_wb = wb_enc->hw_wb;
+	struct sde_hw_wb_cfg *wb_cfg = &wb_enc->wb_cfg;
+	const struct msm_format *format;
+	int ret, mmu_id;
+
+	memset(wb_cfg, 0, sizeof(struct sde_hw_wb_cfg));
+
+	wb_cfg->intf_mode = INTF_MODE_WB_LINE;
+	wb_cfg->is_secure = (fb->flags & DRM_MODE_FB_SECURE) ? true : false;
+	mmu_id = (wb_cfg->is_secure) ?
+			wb_enc->mmu_id[SDE_IOMMU_DOMAIN_SECURE] :
+			wb_enc->mmu_id[SDE_IOMMU_DOMAIN_UNSECURE];
+
+	SDE_DEBUG("[fb_secure:%d]\n", wb_cfg->is_secure);
+
+	format = msm_framebuffer_format(fb);
+	wb_cfg->dest.format = sde_get_sde_format_ext(
+			format->pixel_format,
+			fb->modifier,
+			drm_format_num_planes(fb->pixel_format));
+	if (!wb_cfg->dest.format) {
+		/* this error should be detected during atomic_check */
+		SDE_ERROR("failed to get format %x\n", format->pixel_format);
+		return;
+	}
+
+	ret = sde_format_populate_layout_with_roi(mmu_id, fb, wb_roi,
+			&wb_cfg->dest);
+	if (ret) {
+		/* this error should be detected during atomic_check */
+		SDE_DEBUG("failed to populate layout %d\n", ret);
+		return;
+	}
+
+	if ((wb_cfg->dest.format->fetch_planes == SDE_PLANE_PLANAR) &&
+			(wb_cfg->dest.format->element[0] == C1_B_Cb))
+		swap(wb_cfg->dest.plane_addr[1], wb_cfg->dest.plane_addr[2]);
+
+	SDE_DEBUG("[fb_offset:%8.8x,%8.8x,%8.8x,%8.8x]\n",
+			wb_cfg->dest.plane_addr[0],
+			wb_cfg->dest.plane_addr[1],
+			wb_cfg->dest.plane_addr[2],
+			wb_cfg->dest.plane_addr[3]);
+	SDE_DEBUG("[fb_stride:%8.8x,%8.8x,%8.8x,%8.8x]\n",
+			wb_cfg->dest.plane_pitch[0],
+			wb_cfg->dest.plane_pitch[1],
+			wb_cfg->dest.plane_pitch[2],
+			wb_cfg->dest.plane_pitch[3]);
+
+	if (hw_wb->ops.setup_outformat)
+		hw_wb->ops.setup_outformat(hw_wb, wb_cfg);
+
+	if (hw_wb->ops.setup_outaddress)
+		hw_wb->ops.setup_outaddress(hw_wb, wb_cfg);
+}
+
+/**
+ * sde_encoder_phys_wb_setup_cdp - setup chroma down prefetch block
+ * @phys_enc:	Pointer to physical encoder
+ */
+static void sde_encoder_phys_wb_setup_cdp(struct sde_encoder_phys *phys_enc)
+{
+	struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
+	struct sde_hw_wb *hw_wb = wb_enc->hw_wb;
+	struct sde_hw_intf_cfg *intf_cfg = &wb_enc->intf_cfg;
+
+	memset(intf_cfg, 0, sizeof(struct sde_hw_intf_cfg));
+
+	intf_cfg->intf = SDE_NONE;
+	intf_cfg->wb = hw_wb->idx;
+
+	if (phys_enc->hw_ctl->ops.setup_intf_cfg)
+		phys_enc->hw_ctl->ops.setup_intf_cfg(phys_enc->hw_ctl,
+				intf_cfg);
+}
+
+/**
+ * sde_encoder_phys_wb_atomic_check - verify and fixup given atomic states
+ * @phys_enc:	Pointer to physical encoder
+ * @crtc_state:	Pointer to CRTC atomic state
+ * @conn_state:	Pointer to connector atomic state
+ */
+static int sde_encoder_phys_wb_atomic_check(
+		struct sde_encoder_phys *phys_enc,
+		struct drm_crtc_state *crtc_state,
+		struct drm_connector_state *conn_state)
+{
+	struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
+	struct sde_hw_wb *hw_wb = wb_enc->hw_wb;
+	const struct sde_wb_cfg *wb_cfg = hw_wb->caps;
+	struct drm_framebuffer *fb;
+	const struct sde_format *fmt;
+	struct sde_rect wb_roi;
+	const struct drm_display_mode *mode = &crtc_state->mode;
+	int rc;
+
+	SDE_DEBUG("[atomic_check:%d,%d,\"%s\",%d,%d]\n",
+			hw_wb->idx - WB_0, mode->base.id, mode->name,
+			mode->hdisplay, mode->vdisplay);
+
+	memset(&wb_roi, 0, sizeof(struct sde_rect));
+
+	rc = sde_wb_connector_state_get_output_roi(conn_state, &wb_roi);
+	if (rc) {
+		SDE_ERROR("failed to get roi %d\n", rc);
+		return rc;
+	}
+
+	SDE_DEBUG("[roi:%u,%u,%u,%u]\n", wb_roi.x, wb_roi.y,
+			wb_roi.w, wb_roi.h);
+
+	fb = sde_wb_connector_state_get_output_fb(conn_state);
+	if (!fb) {
+		SDE_ERROR("no output framebuffer\n");
+		return -EINVAL;
+	}
+
+	SDE_DEBUG("[fb_id:%u][fb:%u,%u]\n", fb->base.id,
+			fb->width, fb->height);
+
+	fmt = sde_get_sde_format_ext(fb->pixel_format, fb->modifier,
+			drm_format_num_planes(fb->pixel_format));
+	if (!fmt) {
+		SDE_ERROR("unsupported output pixel format:%d\n",
+				fb->pixel_format);
+		return -EINVAL;
+	}
+
+	SDE_DEBUG("[fb_fmt:%x,%llx]\n", fb->pixel_format,
+			fb->modifier[0]);
+
+	if (SDE_FORMAT_IS_YUV(fmt) &&
+			!(wb_cfg->features & BIT(SDE_WB_YUV_CONFIG))) {
+		SDE_ERROR("invalid output format %x\n", fmt->base.pixel_format);
+		return -EINVAL;
+	}
+
+	if (SDE_FORMAT_IS_UBWC(fmt) &&
+			!(wb_cfg->features & BIT(SDE_WB_UBWC_1_0))) {
+		SDE_ERROR("invalid output format %x\n", fmt->base.pixel_format);
+		return -EINVAL;
+	}
+
+	if (wb_roi.w && wb_roi.h) {
+		if (wb_roi.w != mode->hdisplay) {
+			SDE_ERROR("invalid roi w=%d, mode w=%d\n", wb_roi.w,
+					mode->hdisplay);
+			return -EINVAL;
+		} else if (wb_roi.h != mode->vdisplay) {
+			SDE_ERROR("invalid roi h=%d, mode h=%d\n", wb_roi.h,
+					mode->vdisplay);
+			return -EINVAL;
+		} else if (wb_roi.x + wb_roi.w > fb->width) {
+			SDE_ERROR("invalid roi x=%d, w=%d, fb w=%d\n",
+					wb_roi.x, wb_roi.w, fb->width);
+			return -EINVAL;
+		} else if (wb_roi.y + wb_roi.h > fb->height) {
+			SDE_ERROR("invalid roi y=%d, h=%d, fb h=%d\n",
+					wb_roi.y, wb_roi.h, fb->height);
+			return -EINVAL;
+		} else if (wb_roi.w > wb_cfg->sblk->maxlinewidth) {
+			SDE_ERROR("invalid roi w=%d, maxlinewidth=%u\n",
+					wb_roi.w, wb_cfg->sblk->maxlinewidth);
+			return -EINVAL;
+		}
+	} else {
+		if (wb_roi.x || wb_roi.y) {
+			SDE_ERROR("invalid roi x=%d, y=%d\n",
+					wb_roi.x, wb_roi.y);
+			return -EINVAL;
+		} else if (fb->width != mode->hdisplay) {
+			SDE_ERROR("invalid fb w=%d, mode w=%d\n", fb->width,
+					mode->hdisplay);
+			return -EINVAL;
+		} else if (fb->height != mode->vdisplay) {
+			SDE_ERROR("invalid fb h=%d, mode h=%d\n", fb->height,
+					mode->vdisplay);
+			return -EINVAL;
+		} else if (fb->width > wb_cfg->sblk->maxlinewidth) {
+			SDE_ERROR("invalid fb w=%d, maxlinewidth=%u\n",
+					fb->width, wb_cfg->sblk->maxlinewidth);
+			return -EINVAL;
+		}
+	}
+
+	return 0;
+}
+
+/**
+ * sde_encoder_phys_wb_flush - flush hardware update
+ * @phys_enc:	Pointer to physical encoder
+ */
+static void sde_encoder_phys_wb_flush(struct sde_encoder_phys *phys_enc)
+{
+	struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
+	struct sde_hw_wb *hw_wb = wb_enc->hw_wb;
+	struct sde_hw_ctl *hw_ctl = phys_enc->hw_ctl;
+	struct sde_hw_cdm *hw_cdm = phys_enc->hw_cdm;
+	u32 flush_mask = 0;
+
+	SDE_DEBUG("[wb:%d]\n", hw_wb->idx - WB_0);
+
+	if (hw_ctl->ops.get_bitmask_wb)
+		hw_ctl->ops.get_bitmask_wb(hw_ctl, &flush_mask, hw_wb->idx);
+
+	if (hw_ctl->ops.get_bitmask_cdm && hw_cdm)
+		hw_ctl->ops.get_bitmask_cdm(hw_ctl, &flush_mask, hw_cdm->idx);
+
+	if (hw_ctl->ops.update_pending_flush)
+		hw_ctl->ops.update_pending_flush(hw_ctl, flush_mask);
+
+	SDE_DEBUG("Flushing CTL_ID %d, flush_mask %x, WB %d\n",
+			hw_ctl->idx - CTL_0, flush_mask, hw_wb->idx - WB_0);
+}
+
+/**
+ * sde_encoder_phys_wb_setup - setup writeback encoder
+ * @phys_enc:	Pointer to physical encoder
+ */
+static void sde_encoder_phys_wb_setup(
+		struct sde_encoder_phys *phys_enc)
+{
+	struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
+	struct sde_hw_wb *hw_wb = wb_enc->hw_wb;
+	struct drm_display_mode mode = phys_enc->cached_mode;
+	struct drm_framebuffer *fb;
+	struct sde_rect *wb_roi = &wb_enc->wb_roi;
+
+	SDE_DEBUG("[mode_set:%d,%d,\"%s\",%d,%d]\n",
+			hw_wb->idx - WB_0, mode.base.id, mode.name,
+			mode.hdisplay, mode.vdisplay);
+
+	memset(wb_roi, 0, sizeof(struct sde_rect));
+
+	fb = sde_wb_get_output_fb(wb_enc->wb_dev);
+	if (!fb) {
+		SDE_DEBUG("no output framebuffer\n");
+		return;
+	}
+
+	SDE_DEBUG("[fb_id:%u][fb:%u,%u]\n", fb->base.id,
+			fb->width, fb->height);
+
+	sde_wb_get_output_roi(wb_enc->wb_dev, wb_roi);
+	if (wb_roi->w == 0 || wb_roi->h == 0) {
+		wb_roi->x = 0;
+		wb_roi->y = 0;
+		wb_roi->w = fb->width;
+		wb_roi->h = fb->height;
+	}
+
+	SDE_DEBUG("[roi:%u,%u,%u,%u]\n", wb_roi->x, wb_roi->y,
+			wb_roi->w, wb_roi->h);
+
+	wb_enc->wb_fmt = sde_get_sde_format_ext(fb->pixel_format, fb->modifier,
+			drm_format_num_planes(fb->pixel_format));
+	if (!wb_enc->wb_fmt) {
+		SDE_ERROR("unsupported output pixel format: %d\n",
+				fb->pixel_format);
+		return;
+	}
+
+	SDE_DEBUG("[fb_fmt:%x,%llx]\n", fb->pixel_format,
+			fb->modifier[0]);
+
+	sde_encoder_phys_wb_set_traffic_shaper(phys_enc);
+
+	sde_encoder_phys_setup_cdm(phys_enc, fb, wb_enc->wb_fmt, wb_roi);
+
+	sde_encoder_phys_wb_setup_fb(phys_enc, fb, wb_roi);
+
+	sde_encoder_phys_wb_setup_cdp(phys_enc);
+}
+
+/**
+ * sde_encoder_phys_wb_unregister_irq - unregister writeback interrupt handler
+ * @phys_enc:	Pointer to physical encoder
+ */
+static int sde_encoder_phys_wb_unregister_irq(
+		struct sde_encoder_phys *phys_enc)
+{
+	struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
+	struct sde_hw_wb *hw_wb = wb_enc->hw_wb;
+
+	if (wb_enc->bypass_irqreg)
+		return 0;
+
+	sde_disable_irq(phys_enc->sde_kms, &wb_enc->irq_idx, 1);
+	sde_register_irq_callback(phys_enc->sde_kms, wb_enc->irq_idx, NULL);
+
+	SDE_DEBUG("un-register IRQ for wb %d, irq_idx=%d\n",
+			hw_wb->idx - WB_0,
+			wb_enc->irq_idx);
+
+	return 0;
+}
+
+/**
+ * sde_encoder_phys_wb_done_irq - writeback interrupt handler
+ * @arg:	Pointer to writeback encoder
+ * @irq_idx:	interrupt index
+ */
+static void sde_encoder_phys_wb_done_irq(void *arg, int irq_idx)
+{
+	struct sde_encoder_phys_wb *wb_enc = arg;
+	struct sde_encoder_phys *phys_enc = &wb_enc->base;
+	struct sde_hw_wb *hw_wb = wb_enc->hw_wb;
+
+	SDE_DEBUG("[wb:%d,%u]\n", hw_wb->idx - WB_0,
+			wb_enc->frame_count);
+
+	complete_all(&wb_enc->wbdone_complete);
+
+	phys_enc->parent_ops.handle_vblank_virt(phys_enc->parent);
+}
+
+/**
+ * sde_encoder_phys_wb_register_irq - register writeback interrupt handler
+ * @phys_enc:	Pointer to physical encoder
+ */
+static int sde_encoder_phys_wb_register_irq(struct sde_encoder_phys *phys_enc)
+{
+	struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
+	struct sde_hw_wb *hw_wb = wb_enc->hw_wb;
+	struct sde_irq_callback irq_cb;
+	enum sde_intr_type intr_type;
+	int ret = 0;
+
+	if (wb_enc->bypass_irqreg)
+		return 0;
+
+	intr_type = sde_encoder_phys_wb_get_intr_type(hw_wb);
+	wb_enc->irq_idx = sde_irq_idx_lookup(phys_enc->sde_kms,
+			intr_type, hw_wb->idx);
+	if (wb_enc->irq_idx < 0) {
+		SDE_ERROR(
+			"failed to lookup IRQ index for WB_DONE with wb=%d\n",
+			hw_wb->idx - WB_0);
+		return -EINVAL;
+	}
+
+	irq_cb.func = sde_encoder_phys_wb_done_irq;
+	irq_cb.arg = wb_enc;
+	ret = sde_register_irq_callback(phys_enc->sde_kms, wb_enc->irq_idx,
+			&irq_cb);
+	if (ret) {
+		SDE_ERROR("failed to register IRQ callback WB_DONE\n");
+		return ret;
+	}
+
+	ret = sde_enable_irq(phys_enc->sde_kms, &wb_enc->irq_idx, 1);
+	if (ret) {
+		SDE_ERROR(
+			"failed to enable IRQ for WB_DONE, wb %d, irq_idx=%d\n",
+				hw_wb->idx - WB_0,
+				wb_enc->irq_idx);
+		wb_enc->irq_idx = -EINVAL;
+
+		/* Unregister callback on IRQ enable failure */
+		sde_register_irq_callback(phys_enc->sde_kms, wb_enc->irq_idx,
+				NULL);
+		return ret;
+	}
+
+	SDE_DEBUG("registered IRQ for wb %d, irq_idx=%d\n",
+			hw_wb->idx - WB_0,
+			wb_enc->irq_idx);
+
+	return ret;
+}
+
+/**
+ * sde_encoder_phys_wb_mode_set - set display mode
+ * @phys_enc:	Pointer to physical encoder
+ * @mode:	Pointer to requested display mode
+ * @adj_mode:	Pointer to adjusted display mode
+ */
+static void sde_encoder_phys_wb_mode_set(
+		struct sde_encoder_phys *phys_enc,
+		struct drm_display_mode *mode,
+		struct drm_display_mode *adj_mode)
+{
+	struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
+	struct sde_hw_wb *hw_wb = wb_enc->hw_wb;
+
+	phys_enc->cached_mode = *adj_mode;
+
+	SDE_DEBUG("[mode_set_cache:%d,%d,\"%s\",%d,%d]\n",
+			hw_wb->idx - WB_0, mode->base.id,
+			mode->name, mode->hdisplay, mode->vdisplay);
+}
+
+/**
+ * sde_encoder_phys_wb_control_vblank_irq - Control vblank interrupt
+ * @phys_enc:	Pointer to physical encoder
+ * @enable:	Enable interrupt
+ */
+static int sde_encoder_phys_wb_control_vblank_irq(
+		struct sde_encoder_phys *phys_enc,
+		bool enable)
+{
+	struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
+	struct sde_hw_wb *hw_wb = wb_enc->hw_wb;
+	int ret = 0;
+
+	SDE_DEBUG("[wb:%d,%d]\n", hw_wb->idx - WB_0, enable);
+
+	if (enable)
+		ret = sde_encoder_phys_wb_register_irq(phys_enc);
+	else
+		ret = sde_encoder_phys_wb_unregister_irq(phys_enc);
+
+	if (ret)
+		SDE_ERROR("control vblank irq error %d, enable %d\n", ret,
+				enable);
+
+	return ret;
+}
+
+/**
+ * sde_encoder_phys_wb_wait_for_commit_done - wait until request is committed
+ * @phys_enc:	Pointer to physical encoder
+ */
+static int sde_encoder_phys_wb_wait_for_commit_done(
+		struct sde_encoder_phys *phys_enc)
+{
+	unsigned long ret;
+	struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
+	u32 irq_status;
+	u64 wb_time = 0;
+	int rc = 0;
+
+	/* Return EWOULDBLOCK since we know the wait isn't necessary */
+	if (WARN_ON(phys_enc->enable_state != SDE_ENC_ENABLED))
+		return -EWOULDBLOCK;
+
+	MSM_EVT(DEV(phys_enc), wb_enc->frame_count, 0);
+
+	ret = wait_for_completion_timeout(&wb_enc->wbdone_complete,
+			msecs_to_jiffies(wb_enc->wbdone_timeout));
+
+	if (!ret) {
+		MSM_EVT(DEV(phys_enc), wb_enc->frame_count, 0);
+
+		irq_status = sde_read_irq(phys_enc->sde_kms,
+				wb_enc->irq_idx, true);
+		if (irq_status) {
+			SDE_DEBUG("wb:%d done but irq not triggered\n",
+					wb_enc->wb_dev->wb_idx - WB_0);
+			sde_encoder_phys_wb_done_irq(wb_enc, wb_enc->irq_idx);
+		} else {
+			SDE_ERROR("wb:%d kickoff timed out\n",
+					wb_enc->wb_dev->wb_idx - WB_0);
+			rc = -ETIMEDOUT;
+		}
+	}
+
+	sde_encoder_phys_wb_unregister_irq(phys_enc);
+
+	if (!rc)
+		wb_enc->end_time = ktime_get();
+
+	/* once operation is done, disable traffic shaper */
+	if (wb_enc->wb_cfg.ts_cfg.en && wb_enc->hw_wb &&
+			wb_enc->hw_wb->ops.setup_trafficshaper) {
+		wb_enc->wb_cfg.ts_cfg.en = false;
+		wb_enc->hw_wb->ops.setup_trafficshaper(
+				wb_enc->hw_wb, &wb_enc->wb_cfg);
+	}
+
+	/* remove vote for iommu/clk/bus */
+	wb_enc->frame_count++;
+
+	if (!rc) {
+		wb_time = (u64)ktime_to_us(wb_enc->end_time) -
+				(u64)ktime_to_us(wb_enc->start_time);
+		SDE_DEBUG("wb:%d took %llu us\n",
+			wb_enc->wb_dev->wb_idx - WB_0, wb_time);
+	}
+
+	MSM_EVT(DEV(phys_enc), wb_enc->frame_count, wb_time);
+
+	return rc;
+}
+
+/**
+ * sde_encoder_phys_wb_prepare_for_kickoff - pre-kickoff processing
+ * @phys_enc:	Pointer to physical encoder
+ * @need_to_wait:	 Wait for next submission
+ */
+static void sde_encoder_phys_wb_prepare_for_kickoff(
+		struct sde_encoder_phys *phys_enc,
+		bool *need_to_wait)
+{
+	struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
+	int ret;
+
+	SDE_DEBUG("[wb:%d,%u]\n", wb_enc->hw_wb->idx - WB_0,
+			wb_enc->kickoff_count);
+
+	*need_to_wait = false;
+
+	reinit_completion(&wb_enc->wbdone_complete);
+
+	ret = sde_encoder_phys_wb_register_irq(phys_enc);
+	if (ret) {
+		SDE_ERROR("failed to register irq %d\n", ret);
+		return;
+	}
+
+	wb_enc->kickoff_count++;
+
+	/* set OT limit & enable traffic shaper */
+	sde_encoder_phys_wb_setup(phys_enc);
+
+	sde_encoder_phys_wb_flush(phys_enc);
+
+	/* vote for iommu/clk/bus */
+	wb_enc->start_time = ktime_get();
+
+	MSM_EVT(DEV(phys_enc), *need_to_wait, wb_enc->kickoff_count);
+}
+
+/**
+ * sde_encoder_phys_wb_handle_post_kickoff - post-kickoff processing
+ * @phys_enc:	Pointer to physical encoder
+ */
+static void sde_encoder_phys_wb_handle_post_kickoff(
+		struct sde_encoder_phys *phys_enc)
+{
+	struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
+
+	SDE_DEBUG("[wb:%d]\n", wb_enc->hw_wb->idx - WB_0);
+
+	MSM_EVT(DEV(phys_enc), 0, 0);
+}
+
+/**
+ * sde_encoder_phys_wb_enable - enable writeback encoder
+ * @phys_enc:	Pointer to physical encoder
+ */
+static void sde_encoder_phys_wb_enable(struct sde_encoder_phys *phys_enc)
+{
+	struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
+	struct sde_hw_wb *hw_wb = wb_enc->hw_wb;
+	struct drm_connector *connector;
+
+	SDE_DEBUG("[wb:%d]\n", hw_wb->idx - WB_0);
+
+	/* find associated writeback connector */
+	drm_for_each_connector(connector, phys_enc->parent->dev) {
+		if (connector->encoder == phys_enc->parent)
+			break;
+	}
+	if (!connector || connector->encoder != phys_enc->parent) {
+		SDE_ERROR("failed to find writeback connector\n");
+		return;
+	}
+	wb_enc->wb_dev = sde_wb_connector_get_wb(connector);
+
+	phys_enc->enable_state = SDE_ENC_ENABLED;
+}
+
+/**
+ * sde_encoder_phys_wb_disable - disable writeback encoder
+ * @phys_enc:	Pointer to physical encoder
+ */
+static void sde_encoder_phys_wb_disable(struct sde_encoder_phys *phys_enc)
+{
+	struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
+	struct sde_hw_wb *hw_wb = wb_enc->hw_wb;
+
+	SDE_DEBUG("[wb:%d]\n", hw_wb->idx - WB_0);
+
+	if (phys_enc->enable_state == SDE_ENC_DISABLED) {
+		SDE_ERROR("encoder is already disabled\n");
+		return;
+	}
+
+	if (wb_enc->frame_count != wb_enc->kickoff_count) {
+		SDE_DEBUG("[wait_for_done: wb:%d, frame:%u, kickoff:%u]\n",
+				hw_wb->idx - WB_0, wb_enc->frame_count,
+				wb_enc->kickoff_count);
+		sde_encoder_phys_wb_wait_for_commit_done(phys_enc);
+	}
+
+	phys_enc->enable_state = SDE_ENC_DISABLED;
+}
+
+/**
+ * sde_encoder_phys_wb_get_hw_resources - get hardware resources
+ * @phys_enc:	Pointer to physical encoder
+ * @hw_res:	Pointer to encoder resources
+ */
+static void sde_encoder_phys_wb_get_hw_resources(
+		struct sde_encoder_phys *phys_enc,
+		struct sde_encoder_hw_resources *hw_res)
+{
+	struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
+	struct sde_hw_wb *hw_wb = wb_enc->hw_wb;
+	const struct sde_hw_res_map *hw_res_map;
+
+	SDE_DEBUG("[wb:%d]\n", hw_wb->idx - WB_0);
+
+	hw_res->wbs[hw_wb->idx] = INTF_MODE_WB_LINE;
+
+	/*
+	 * Validate if we want to use the default map
+	 * defaults should not be in use,
+	 * otherwise signal/return failure
+	 */
+	hw_res_map = sde_rm_get_res_map(phys_enc->sde_kms,
+			SDE_NONE, hw_wb->idx);
+	if (IS_ERR_OR_NULL(hw_res_map)) {
+		SDE_ERROR("failed to get hw_res_map: %ld\n",
+				PTR_ERR(hw_res_map));
+		return;
+	}
+
+	/*
+	 * cached ctl_idx at init time, shouldn't we use that?
+	 */
+	hw_res->ctls[hw_res_map->ctl] = true;
+}
+
+#ifdef CONFIG_DEBUG_FS
+/**
+ * sde_encoder_phys_wb_init_debugfs - initialize writeback encoder debugfs
+ * @phys_enc:	Pointer to physical encoder
+ * @sde_kms:	Pointer to SDE KMS object
+ */
+static int sde_encoder_phys_wb_init_debugfs(
+		struct sde_encoder_phys *phys_enc, struct sde_kms *kms)
+{
+	struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
+
+	if (!phys_enc || !kms || !wb_enc->hw_wb)
+		return -EINVAL;
+
+	snprintf(wb_enc->wb_name, ARRAY_SIZE(wb_enc->wb_name), "encoder_wb%d",
+			wb_enc->hw_wb->idx - WB_0);
+
+	wb_enc->debugfs_root =
+		debugfs_create_dir(wb_enc->wb_name,
+				sde_debugfs_get_root(kms));
+	if (!wb_enc->debugfs_root) {
+		SDE_ERROR("failed to create debugfs\n");
+		return -ENOMEM;
+	}
+
+	if (!debugfs_create_u32("wbdone_timeout", 0644,
+			wb_enc->debugfs_root, &wb_enc->wbdone_timeout)) {
+		SDE_ERROR("failed to create debugfs/wbdone_timeout\n");
+		return -ENOMEM;
+	}
+
+	if (!debugfs_create_u32("bypass_irqreg", 0644,
+			wb_enc->debugfs_root, &wb_enc->bypass_irqreg)) {
+		SDE_ERROR("failed to create debugfs/bypass_irqreg\n");
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
+/**
+ * sde_encoder_phys_wb_destroy_debugfs - destroy writeback encoder debugfs
+ * @phys_enc:	Pointer to physical encoder
+ */
+static void sde_encoder_phys_wb_destroy_debugfs(
+		struct sde_encoder_phys *phys_enc)
+{
+	struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
+
+	if (!phys_enc)
+		return;
+
+	debugfs_remove_recursive(wb_enc->debugfs_root);
+}
+#else
+static void sde_encoder_phys_wb_init_debugfs(
+		struct sde_encoder_phys *phys_enc, struct sde_kms *kms)
+{
+}
+static void sde_encoder_phys_wb_destroy_debugfs(
+		struct sde_encoder_phys *phys_enc)
+{
+}
+#endif
+
+/**
+ * sde_encoder_phys_wb_destroy - destroy writeback encoder
+ * @phys_enc:	Pointer to physical encoder
+ */
+static void sde_encoder_phys_wb_destroy(struct sde_encoder_phys *phys_enc)
+{
+	struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
+	struct sde_hw_wb *hw_wb = wb_enc->hw_wb;
+
+	SDE_DEBUG("[wb:%d]\n", hw_wb->idx - WB_0);
+
+	if (!phys_enc)
+		return;
+
+	sde_encoder_phys_wb_destroy_debugfs(phys_enc);
+
+	if (phys_enc->hw_ctl)
+		sde_rm_release_ctl_path(phys_enc->sde_kms,
+				phys_enc->hw_ctl->idx);
+	if (phys_enc->hw_cdm)
+		sde_rm_release_cdm_path(phys_enc->sde_kms,
+				phys_enc->hw_cdm->idx);
+	if (hw_wb)
+		sde_hw_wb_destroy(hw_wb);
+	if (phys_enc->hw_mdptop)
+		sde_hw_mdp_destroy(phys_enc->hw_mdptop);
+
+	kfree(wb_enc);
+}
+
+/**
+ * sde_encoder_phys_wb_init_ops - initialize writeback operations
+ * @ops:	Pointer to encoder operation table
+ */
+static void sde_encoder_phys_wb_init_ops(struct sde_encoder_phys_ops *ops)
+{
+	ops->mode_set = sde_encoder_phys_wb_mode_set;
+	ops->enable = sde_encoder_phys_wb_enable;
+	ops->disable = sde_encoder_phys_wb_disable;
+	ops->destroy = sde_encoder_phys_wb_destroy;
+	ops->atomic_check = sde_encoder_phys_wb_atomic_check;
+	ops->get_hw_resources = sde_encoder_phys_wb_get_hw_resources;
+	ops->control_vblank_irq = sde_encoder_phys_wb_control_vblank_irq;
+	ops->wait_for_commit_done = sde_encoder_phys_wb_wait_for_commit_done;
+	ops->prepare_for_kickoff = sde_encoder_phys_wb_prepare_for_kickoff;
+	ops->handle_post_kickoff = sde_encoder_phys_wb_handle_post_kickoff;
+}
+
+/**
+ * sde_encoder_phys_wb_init - initialize writeback encoder
+ * @sde_kms:	Pointer to SDE KMS object
+ * @wb_idx:	Writeback index
+ * @ctl_idx:	Control index
+ * @cdm_idx:	Chroma down index
+ * @split_role:	Master/slave mode
+ * @parent:	Pointer to virtual encoder
+ * @parent_ops:	Pointer to virtual encoder operations
+ */
+struct sde_encoder_phys *sde_encoder_phys_wb_init(
+		struct sde_kms *sde_kms,
+		enum sde_wb wb_idx,
+		enum sde_ctl ctl_idx,
+		enum sde_cdm cdm_idx,
+		enum sde_enc_split_role split_role,
+		struct drm_encoder *parent,
+		struct sde_encoder_virt_ops parent_ops)
+{
+	struct sde_encoder_phys *phys_enc;
+	struct sde_encoder_phys_wb *wb_enc;
+	struct sde_hw_mdp *hw_mdp;
+	int ret = 0;
+
+	SDE_DEBUG("\n");
+
+	wb_enc = kzalloc(sizeof(*wb_enc), GFP_KERNEL);
+	if (!wb_enc) {
+		ret = -ENOMEM;
+		goto fail_alloc;
+	}
+	wb_enc->irq_idx = -EINVAL;
+	wb_enc->wbdone_timeout = WAIT_TIMEOUT_MSEC;
+	init_completion(&wb_enc->wbdone_complete);
+
+	phys_enc = &wb_enc->base;
+
+	if (sde_kms->vbif[VBIF_NRT]) {
+		wb_enc->mmu_id[SDE_IOMMU_DOMAIN_UNSECURE] =
+			sde_kms->mmu_id[MSM_SMMU_DOMAIN_NRT_UNSECURE];
+		wb_enc->mmu_id[SDE_IOMMU_DOMAIN_SECURE] =
+			sde_kms->mmu_id[MSM_SMMU_DOMAIN_NRT_SECURE];
+	} else {
+		wb_enc->mmu_id[SDE_IOMMU_DOMAIN_UNSECURE] =
+			sde_kms->mmu_id[MSM_SMMU_DOMAIN_UNSECURE];
+		wb_enc->mmu_id[SDE_IOMMU_DOMAIN_SECURE] =
+			sde_kms->mmu_id[MSM_SMMU_DOMAIN_SECURE];
+	}
+
+	hw_mdp = sde_hw_mdptop_init(MDP_TOP, sde_kms->mmio, sde_kms->catalog);
+	if (IS_ERR_OR_NULL(hw_mdp)) {
+		ret = PTR_ERR(hw_mdp);
+		SDE_ERROR("failed to init hw_top: %d\n", ret);
+		goto fail_mdp_init;
+	}
+	phys_enc->hw_mdptop = hw_mdp;
+
+	if (wb_idx != SDE_NONE) {
+		struct sde_hw_wb *hw_wb;
+
+		hw_wb = sde_hw_wb_init(wb_idx, sde_kms->mmio,
+				sde_kms->catalog, phys_enc->hw_mdptop);
+		if (IS_ERR_OR_NULL(hw_wb)) {
+			ret = PTR_ERR(hw_wb);
+			SDE_ERROR("failed to init hw_wb%d: %d\n",
+					wb_idx - WB_0, ret);
+			goto fail_wb_init;
+		}
+		wb_enc->hw_wb = hw_wb;
+	} else {
+		ret = -EINVAL;
+		SDE_ERROR("invalid wb_idx\n");
+		goto fail_wb_check;
+	}
+
+	if (cdm_idx != SDE_NONE) {
+		struct sde_hw_cdm *hw_cdm;
+
+		SDE_DEBUG("Acquiring CDM %d\n", cdm_idx - CDM_0);
+		hw_cdm = sde_rm_acquire_cdm_path(sde_kms, cdm_idx,
+				phys_enc->hw_mdptop);
+		if (IS_ERR_OR_NULL(hw_cdm)) {
+			ret = PTR_ERR(hw_cdm);
+			SDE_ERROR("failed to init hw_cdm%d: %d\n",
+					cdm_idx - CDM_0, ret);
+			goto fail_cdm_init;
+		}
+		phys_enc->hw_cdm = hw_cdm;
+	}
+
+	if (ctl_idx != SDE_NONE) {
+		struct sde_hw_ctl *hw_ctl;
+
+		SDE_DEBUG("Acquiring CTL %d\n", ctl_idx - CTL_0);
+		hw_ctl = sde_rm_acquire_ctl_path(sde_kms, ctl_idx);
+		if (IS_ERR_OR_NULL(hw_ctl)) {
+			ret = PTR_ERR(hw_ctl);
+			SDE_ERROR("failed to init hw_ctl%d: %d\n",
+					ctl_idx - CTL_0, ret);
+			goto fail_ctl_init;
+		}
+		phys_enc->hw_ctl = hw_ctl;
+	} else {
+		ret = -EINVAL;
+		SDE_ERROR("invalid ctl_idx\n");
+		goto fail_ctl_check;
+	}
+
+	sde_encoder_phys_wb_init_ops(&phys_enc->ops);
+	phys_enc->parent = parent;
+	phys_enc->parent_ops = parent_ops;
+	phys_enc->sde_kms = sde_kms;
+	phys_enc->split_role = split_role;
+	spin_lock_init(&phys_enc->spin_lock);
+
+	ret = sde_encoder_phys_wb_init_debugfs(phys_enc, sde_kms);
+	if (ret) {
+		SDE_ERROR("failed to init debugfs %d\n", ret);
+		goto fail_debugfs_init;
+	}
+
+	SDE_DEBUG("Created sde_encoder_phys_wb for wb %d\n",
+			wb_enc->hw_wb->idx - WB_0);
+
+	return phys_enc;
+
+fail_debugfs_init:
+	sde_rm_release_ctl_path(sde_kms, ctl_idx);
+fail_ctl_init:
+fail_ctl_check:
+	sde_rm_release_cdm_path(sde_kms, cdm_idx);
+fail_cdm_init:
+	sde_hw_wb_destroy(wb_enc->hw_wb);
+fail_wb_init:
+fail_wb_check:
+	sde_hw_mdp_destroy(phys_enc->hw_mdptop);
+fail_mdp_init:
+	kfree(wb_enc);
+fail_alloc:
+	return ERR_PTR(ret);
+}
+
diff --git a/drivers/gpu/drm/msm/sde/sde_formats.c b/drivers/gpu/drm/msm/sde/sde_formats.c
index 5655a8a..6d9c574 100644
--- a/drivers/gpu/drm/msm/sde/sde_formats.c
+++ b/drivers/gpu/drm/msm/sde/sde_formats.c
@@ -363,6 +363,9 @@
 	uint32_t *v_sample,
 	uint32_t *h_sample)
 {
+	if (!v_sample || !h_sample)
+		return;
+
 	switch (chroma_sample) {
 	case SDE_CHROMA_H2V1:
 		*v_sample = 1;
@@ -702,6 +705,69 @@
 	return ret;
 }
 
+static void _sde_format_calc_offset_linear(struct sde_hw_fmt_layout *source,
+		u32 x, u32 y)
+{
+	if ((x == 0) && (y == 0))
+		return;
+
+	source->plane_addr[0] += y * source->plane_pitch[0];
+
+	if (source->num_planes == 1) {
+		source->plane_addr[0] += x * source->format->bpp;
+	} else {
+		uint32_t xoff, yoff;
+		uint32_t v_subsample = 1;
+		uint32_t h_subsample = 1;
+
+		_sde_get_v_h_subsample_rate(source->format->chroma_sample,
+				&v_subsample, &h_subsample);
+
+		xoff = x / h_subsample;
+		yoff = y / v_subsample;
+
+		source->plane_addr[0] += x;
+		source->plane_addr[1] += xoff +
+				(yoff * source->plane_pitch[1]);
+		if (source->num_planes == 2) /* pseudo planar */
+			source->plane_addr[1] += xoff;
+		else /* planar */
+			source->plane_addr[2] += xoff +
+				(yoff * source->plane_pitch[2]);
+	}
+}
+
+int sde_format_populate_layout_with_roi(
+		int mmu_id,
+		struct drm_framebuffer *fb,
+		struct sde_rect *roi,
+		struct sde_hw_fmt_layout *layout)
+{
+	int ret;
+
+	ret = sde_format_populate_layout(mmu_id, fb, layout);
+	if (ret || !roi)
+		return ret;
+
+	if (!roi->w || !roi->h || (roi->x + roi->w > fb->width) ||
+			(roi->y + roi->h > fb->height)) {
+		DRM_ERROR("invalid roi=[%d,%d,%d,%d], fb=[%u,%u]\n",
+				roi->x, roi->y, roi->w, roi->h,
+				fb->width, fb->height);
+		ret = -EINVAL;
+	} else if (SDE_FORMAT_IS_LINEAR(layout->format)) {
+		_sde_format_calc_offset_linear(layout, roi->x, roi->y);
+		layout->width = roi->w;
+		layout->height = roi->h;
+	} else if (roi->x || roi->y || (roi->w != fb->width) ||
+			(roi->h != fb->height)) {
+		DRM_ERROR("non-linear layout with roi not supported\n");
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
 int sde_format_check_modified_format(
 		const struct msm_kms *kms,
 		const struct msm_format *msm_fmt,
diff --git a/drivers/gpu/drm/msm/sde/sde_formats.h b/drivers/gpu/drm/msm/sde/sde_formats.h
index c890484..db2a4d3 100644
--- a/drivers/gpu/drm/msm/sde/sde_formats.h
+++ b/drivers/gpu/drm/msm/sde/sde_formats.h
@@ -87,4 +87,20 @@
 		struct drm_framebuffer *fb,
 		struct sde_hw_fmt_layout *fmtl);
 
+/**
+ * sde_format_populate_layout_with_roi - populate the given format layout
+ *                     based on mmu, fb, roi, and format found in the fb
+ * @mmu_id:            mmu id handle
+ * @fb:                framebuffer pointer
+ * @roi:               region of interest (optional)
+ * @fmtl:              format layout structure to populate
+ *
+ * Return: error code on failure, 0 on success
+ */
+int sde_format_populate_layout_with_roi(
+		int mmu_id,
+		struct drm_framebuffer *fb,
+		struct sde_rect *roi,
+		struct sde_hw_fmt_layout *fmtl);
+
 #endif /*_SDE_FORMATS_H */
diff --git a/drivers/gpu/drm/msm/sde/sde_hw_catalog.h b/drivers/gpu/drm/msm/sde/sde_hw_catalog.h
index a7b660a..e41aa5f 100644
--- a/drivers/gpu/drm/msm/sde/sde_hw_catalog.h
+++ b/drivers/gpu/drm/msm/sde/sde_hw_catalog.h
@@ -408,10 +408,13 @@
  * @id                 enum identifying this block
  * @base               register offset of this block
  * @features           bit mask identifying sub-blocks/features
+ * @sblk               sub-block information
+ * @format_list: Pointer to list of supported formats
  */
 struct sde_wb_cfg {
 	SDE_HW_BLK_INFO;
-	struct sde_wb_sub_blocks *sblk;
+	const struct sde_wb_sub_blocks *sblk;
+	const struct sde_format_extended *format_list;
 };
 
 /**
diff --git a/drivers/gpu/drm/msm/sde/sde_hw_catalog_8996.c b/drivers/gpu/drm/msm/sde/sde_hw_catalog_8996.c
index 2f8bcc0..f071437 100644
--- a/drivers/gpu/drm/msm/sde/sde_hw_catalog_8996.c
+++ b/drivers/gpu/drm/msm/sde/sde_hw_catalog_8996.c
@@ -140,6 +140,89 @@
 	{0, 0},
 };
 
+static const struct sde_format_extended wb0_formats[] = {
+	{DRM_FORMAT_RGB565, 0},
+	{DRM_FORMAT_RGB888, 0},
+	{DRM_FORMAT_ARGB8888, 0},
+	{DRM_FORMAT_RGBA8888, 0},
+	{DRM_FORMAT_XRGB8888, 0},
+	{DRM_FORMAT_RGBX8888, 0},
+	{DRM_FORMAT_ARGB1555, 0},
+	{DRM_FORMAT_RGBA5551, 0},
+	{DRM_FORMAT_XRGB1555, 0},
+	{DRM_FORMAT_RGBX5551, 0},
+	{DRM_FORMAT_ARGB4444, 0},
+	{DRM_FORMAT_RGBA4444, 0},
+	{DRM_FORMAT_RGBX4444, 0},
+	{DRM_FORMAT_XRGB4444, 0},
+
+	{DRM_FORMAT_BGR565, 0},
+	{DRM_FORMAT_BGR888, 0},
+	{DRM_FORMAT_ABGR8888, 0},
+	{DRM_FORMAT_BGRA8888, 0},
+	{DRM_FORMAT_BGRX8888, 0},
+	{DRM_FORMAT_ABGR1555, 0},
+	{DRM_FORMAT_BGRA5551, 0},
+	{DRM_FORMAT_XBGR1555, 0},
+	{DRM_FORMAT_BGRX5551, 0},
+	{DRM_FORMAT_ABGR4444, 0},
+	{DRM_FORMAT_BGRA4444, 0},
+	{DRM_FORMAT_BGRX4444, 0},
+	{DRM_FORMAT_XBGR4444, 0},
+
+	{DRM_FORMAT_RGBX8888, DRM_FORMAT_MOD_QCOM_COMPRESSED},
+	{DRM_FORMAT_RGBA8888, DRM_FORMAT_MOD_QCOM_COMPRESSED},
+	{DRM_FORMAT_RGB565, DRM_FORMAT_MOD_QCOM_COMPRESSED},
+
+	{DRM_FORMAT_YUV420, 0},
+	{DRM_FORMAT_NV12, 0},
+	{DRM_FORMAT_NV16, 0},
+	{DRM_FORMAT_YUYV, 0},
+
+	{DRM_FORMAT_NV12, DRM_FORMAT_MOD_QCOM_COMPRESSED},
+	{DRM_FORMAT_AYUV, DRM_FORMAT_MOD_QCOM_COMPRESSED},
+
+	{0, 0},
+};
+
+static const struct sde_format_extended wb2_formats[] = {
+	{DRM_FORMAT_RGB565, 0},
+	{DRM_FORMAT_RGB888, 0},
+	{DRM_FORMAT_ARGB8888, 0},
+	{DRM_FORMAT_RGBA8888, 0},
+	{DRM_FORMAT_XRGB8888, 0},
+	{DRM_FORMAT_RGBX8888, 0},
+	{DRM_FORMAT_ARGB1555, 0},
+	{DRM_FORMAT_RGBA5551, 0},
+	{DRM_FORMAT_XRGB1555, 0},
+	{DRM_FORMAT_RGBX5551, 0},
+	{DRM_FORMAT_ARGB4444, 0},
+	{DRM_FORMAT_RGBA4444, 0},
+	{DRM_FORMAT_RGBX4444, 0},
+	{DRM_FORMAT_XRGB4444, 0},
+
+	{DRM_FORMAT_BGR565, 0},
+	{DRM_FORMAT_BGR888, 0},
+	{DRM_FORMAT_ABGR8888, 0},
+	{DRM_FORMAT_BGRA8888, 0},
+	{DRM_FORMAT_BGRX8888, 0},
+	{DRM_FORMAT_ABGR1555, 0},
+	{DRM_FORMAT_BGRA5551, 0},
+	{DRM_FORMAT_XBGR1555, 0},
+	{DRM_FORMAT_BGRX5551, 0},
+	{DRM_FORMAT_ABGR4444, 0},
+	{DRM_FORMAT_BGRA4444, 0},
+	{DRM_FORMAT_BGRX4444, 0},
+	{DRM_FORMAT_XBGR4444, 0},
+
+	{DRM_FORMAT_YUV420, 0},
+	{DRM_FORMAT_NV12, 0},
+	{DRM_FORMAT_NV16, 0},
+	{DRM_FORMAT_YUYV, 0},
+
+	{0, 0},
+};
+
 /**
  * set_cfg_1xx_init(): populate sde sub-blocks reg offsets and instance counts
  */
@@ -257,6 +340,16 @@
 			.version = 0x1},
 	};
 
+	/* Writeback 0/1 capability */
+	static const struct sde_wb_sub_blocks wb0 = {
+		.maxlinewidth = 2048,
+	};
+
+	/* Writeback 2 capability */
+	static const struct sde_wb_sub_blocks wb2 = {
+		.maxlinewidth = 4096,
+	};
+
 	/* Setup Register maps and defaults */
 	*cfg = (struct sde_mdss_cfg){
 		.mdss_count = 1,
@@ -376,11 +469,17 @@
 		.wb_count = 3,
 		.wb = {
 			{.id = WB_0, .base = 0x00065000,
-				.features = WB01_17X_MASK},
+				.features = WB01_17X_MASK,
+				.sblk = &wb0,
+				.format_list = wb0_formats},
 			{.id = WB_1, .base = 0x00065800,
-				.features = WB01_17X_MASK},
+				.features = WB01_17X_MASK,
+				.sblk = &wb0,
+				.format_list = wb0_formats},
 			{.id = WB_2, .base = 0x00066000,
-				.features = WB2_17X_MASK},
+				.features = WB2_17X_MASK,
+				.sblk = &wb2,
+				.format_list = wb2_formats},
 		},
 		.ad_count = 2,
 		.ad = {
diff --git a/drivers/gpu/drm/msm/sde/sde_hw_mdss.h b/drivers/gpu/drm/msm/sde/sde_hw_mdss.h
index 73cecbf..a4f5656 100644
--- a/drivers/gpu/drm/msm/sde/sde_hw_mdss.h
+++ b/drivers/gpu/drm/msm/sde/sde_hw_mdss.h
@@ -148,7 +148,10 @@
 	INTF_HDMI = 0x3,
 	INTF_LCDC = 0x5,
 	INTF_EDP = 0x9,
-	INTF_TYPE_MAX
+	INTF_TYPE_MAX,
+
+	/* virtual interfaces */
+	INTF_WB = 0x100,
 };
 
 enum sde_intf_mode {
diff --git a/drivers/gpu/drm/msm/sde/sde_wb.c b/drivers/gpu/drm/msm/sde/sde_wb.c
new file mode 100644
index 0000000..2d2f7cf
--- /dev/null
+++ b/drivers/gpu/drm/msm/sde/sde_wb.c
@@ -0,0 +1,692 @@
+/*
+ * Copyright (c) 2015-2016, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#define pr_fmt(fmt)	"sde-wb:[%s] " fmt, __func__
+
+#include "msm_kms.h"
+#include "sde_kms.h"
+#include "sde_wb.h"
+#include "sde_formats.h"
+
+/* maximum display mode resolution if not available from catalog */
+#define SDE_WB_MODE_MAX_WIDTH	4096
+#define SDE_WB_MODE_MAX_HEIGHT	4096
+
+/* Serialization lock for sde_wb_list */
+static DEFINE_MUTEX(sde_wb_list_lock);
+
+/* List of all writeback devices installed */
+static LIST_HEAD(sde_wb_list);
+
+/**
+ * sde_wb_is_format_valid - check if given format/modifier is supported
+ * @wb_dev:	Pointer to writeback device
+ * @pixel_format:	Fourcc pixel format
+ * @format_modifier:	Format modifier
+ * Returns:		true if valid; false otherwise
+ */
+static int sde_wb_is_format_valid(struct sde_wb_device *wb_dev,
+		u32 pixel_format, u64 format_modifier)
+{
+	const struct sde_format_extended *fmts = wb_dev->wb_cfg->format_list;
+	int i;
+
+	if (!fmts)
+		return false;
+
+	for (i = 0; fmts[i].fourcc_format; i++)
+		if ((fmts[i].modifier == format_modifier) &&
+				(fmts[i].fourcc_format == pixel_format))
+			return true;
+
+	return false;
+}
+
+enum drm_connector_status
+sde_wb_connector_detect(struct drm_connector *connector,
+		bool force,
+		void *display)
+{
+	enum drm_connector_status rc = connector_status_unknown;
+
+	SDE_DEBUG("\n");
+
+	if (display)
+		rc = ((struct sde_wb_device *)display)->detect_status;
+
+	return rc;
+}
+
+int sde_wb_connector_get_modes(struct drm_connector *connector, void *display)
+{
+	struct sde_wb_device *wb_dev;
+	int num_modes = 0;
+
+	if (!connector || !display)
+		return 0;
+
+	wb_dev = display;
+
+	SDE_DEBUG("\n");
+
+	mutex_lock(&wb_dev->wb_lock);
+	if (wb_dev->count_modes && wb_dev->modes) {
+		struct drm_display_mode *mode;
+		int i, ret;
+
+		for (i = 0; i < wb_dev->count_modes; i++) {
+			mode = drm_mode_create(connector->dev);
+			if (!mode) {
+				SDE_ERROR("failed to create mode\n");
+				break;
+			}
+			ret = drm_mode_convert_umode(mode,
+					&wb_dev->modes[i]);
+			if (ret) {
+				SDE_ERROR("failed to convert mode %d\n", ret);
+				break;
+			}
+
+			drm_mode_probed_add(connector, mode);
+			num_modes++;
+		}
+	} else {
+		u32 max_width = (wb_dev->wb_cfg && wb_dev->wb_cfg->sblk) ?
+				wb_dev->wb_cfg->sblk->maxlinewidth :
+				SDE_WB_MODE_MAX_WIDTH;
+
+		num_modes = drm_add_modes_noedid(connector, max_width,
+				SDE_WB_MODE_MAX_HEIGHT);
+	}
+	mutex_unlock(&wb_dev->wb_lock);
+	return num_modes;
+}
+
+struct drm_framebuffer *
+sde_wb_connector_state_get_output_fb(struct drm_connector_state *state)
+{
+	if (!state || !state->connector ||
+		(state->connector->connector_type !=
+				DRM_MODE_CONNECTOR_VIRTUAL)) {
+		SDE_ERROR("invalid params\n");
+		return NULL;
+	}
+
+	SDE_DEBUG("\n");
+
+	return sde_connector_get_out_fb(state);
+}
+
+int sde_wb_connector_state_get_output_roi(struct drm_connector_state *state,
+		struct sde_rect *roi)
+{
+	if (!state || !roi || !state->connector ||
+		(state->connector->connector_type !=
+				DRM_MODE_CONNECTOR_VIRTUAL)) {
+		SDE_ERROR("invalid params\n");
+		return -EINVAL;
+	}
+
+	SDE_DEBUG("\n");
+
+	roi->x = sde_connector_get_property(state, CONNECTOR_PROP_DST_X);
+	roi->y = sde_connector_get_property(state, CONNECTOR_PROP_DST_Y);
+	roi->w = sde_connector_get_property(state, CONNECTOR_PROP_DST_W);
+	roi->h = sde_connector_get_property(state, CONNECTOR_PROP_DST_H);
+
+	return 0;
+}
+
+/**
+ * sde_wb_connector_set_modes - set writeback modes and connection status
+ * @wb_dev:	Pointer to write back device
+ * @count_modes:	Count of modes
+ * @modes:	Pointer to writeback mode requested
+ * @connected:	Connection status requested
+ * Returns:	0 if success; error code otherwise
+ */
+static
+int sde_wb_connector_set_modes(struct sde_wb_device *wb_dev,
+		u32 count_modes, struct drm_mode_modeinfo __user *modes,
+		bool connected)
+{
+	int ret = 0;
+
+	if (!wb_dev || !wb_dev->connector ||
+			(wb_dev->connector->connector_type !=
+			 DRM_MODE_CONNECTOR_VIRTUAL)) {
+		SDE_ERROR("invalid params\n");
+		return -EINVAL;
+	}
+
+	SDE_DEBUG("\n");
+
+	if (connected) {
+		SDE_DEBUG("connect\n");
+
+		if (wb_dev->modes) {
+			wb_dev->count_modes = 0;
+
+			kfree(wb_dev->modes);
+			wb_dev->modes = NULL;
+		}
+
+		if (count_modes && modes) {
+			wb_dev->modes = kcalloc(count_modes,
+					sizeof(struct drm_mode_modeinfo),
+					GFP_KERNEL);
+			if (!wb_dev->modes) {
+				SDE_ERROR("invalid params\n");
+				ret = -ENOMEM;
+				goto error;
+			}
+
+			if (copy_from_user(wb_dev->modes, modes,
+					count_modes *
+					sizeof(struct drm_mode_modeinfo))) {
+				SDE_ERROR("failed to copy modes\n");
+				kfree(wb_dev->modes);
+				wb_dev->modes = NULL;
+				ret = -EFAULT;
+				goto error;
+			}
+
+			wb_dev->count_modes = count_modes;
+		}
+
+		wb_dev->detect_status = connector_status_connected;
+	} else {
+		SDE_DEBUG("disconnect\n");
+
+		if (wb_dev->modes) {
+			wb_dev->count_modes = 0;
+
+			kfree(wb_dev->modes);
+			wb_dev->modes = NULL;
+		}
+
+		wb_dev->detect_status = connector_status_disconnected;
+	}
+
+error:
+	return ret;
+}
+
+int sde_wb_connector_set_property(struct drm_connector *connector,
+		struct drm_connector_state *state,
+		int property_index,
+		uint64_t value,
+		void *display)
+{
+	struct sde_wb_device *wb_dev = display;
+	struct drm_framebuffer *out_fb;
+	int rc = 0;
+
+	SDE_DEBUG("\n");
+
+	if (state && (property_index == CONNECTOR_PROP_OUT_FB)) {
+		const struct sde_format *sde_format;
+
+		out_fb = sde_connector_get_out_fb(state);
+		if (!out_fb)
+			goto done;
+
+		sde_format = sde_get_sde_format_ext(out_fb->pixel_format,
+				out_fb->modifier,
+				drm_format_num_planes(out_fb->pixel_format));
+		if (!sde_format) {
+			SDE_ERROR("failed to get sde format\n");
+			rc = -EINVAL;
+			goto done;
+		}
+
+		if (!sde_wb_is_format_valid(wb_dev, out_fb->pixel_format,
+				out_fb->modifier[0])) {
+			SDE_ERROR("unsupported writeback format 0x%x/0x%llx\n",
+					out_fb->pixel_format,
+					out_fb->modifier[0]);
+			rc = -EINVAL;
+			goto done;
+		}
+	}
+
+done:
+	return rc;
+}
+
+int sde_wb_get_info(struct msm_display_info *info, void *display)
+{
+	struct sde_wb_device *wb_dev = display;
+
+	if (!info || !wb_dev) {
+		pr_err("invalid params\n");
+		return -EINVAL;
+	}
+
+	info->intf_type = DRM_MODE_CONNECTOR_VIRTUAL;
+	info->num_of_h_tiles = 1;
+	info->h_tile_instance[0] = sde_wb_get_index(display);
+	info->is_connected = true;
+	info->capabilities = MSM_DISPLAY_CAP_HOT_PLUG | MSM_DISPLAY_CAP_EDID;
+	info->max_width = (wb_dev->wb_cfg && wb_dev->wb_cfg->sblk) ?
+			wb_dev->wb_cfg->sblk->maxlinewidth :
+			SDE_WB_MODE_MAX_WIDTH;
+	info->max_height = SDE_WB_MODE_MAX_HEIGHT;
+	info->compression = MSM_DISPLAY_COMPRESS_NONE;
+	return 0;
+}
+
+int sde_wb_connector_post_init(struct drm_connector *connector,
+		void *info,
+		void *display)
+{
+	struct sde_connector *c_conn;
+	struct sde_wb_device *wb_dev = display;
+	const struct sde_format_extended *format_list;
+
+	if (!connector || !info || !display || !wb_dev->wb_cfg) {
+		SDE_ERROR("invalid params\n");
+		return -EINVAL;
+	}
+
+	c_conn = to_sde_connector(connector);
+	wb_dev->connector = connector;
+	wb_dev->detect_status = connector_status_connected;
+	format_list = wb_dev->wb_cfg->format_list;
+
+	/*
+	 * Add extra connector properties
+	 */
+	msm_property_install_range(&c_conn->property_info, "FB_ID",
+			0, ~0, ~0, CONNECTOR_PROP_OUT_FB);
+	msm_property_install_range(&c_conn->property_info, "DST_X",
+			0, UINT_MAX, 0, CONNECTOR_PROP_DST_X);
+	msm_property_install_range(&c_conn->property_info, "DST_Y",
+			0, UINT_MAX, 0, CONNECTOR_PROP_DST_Y);
+	msm_property_install_range(&c_conn->property_info, "DST_W",
+			0, UINT_MAX, 0, CONNECTOR_PROP_DST_W);
+	msm_property_install_range(&c_conn->property_info, "DST_H",
+			0, UINT_MAX, 0, CONNECTOR_PROP_DST_H);
+
+	/*
+	 * Populate info buffer
+	 */
+	if (format_list) {
+		sde_kms_info_start(info, "pixel_formats");
+		while (format_list->fourcc_format) {
+			sde_kms_info_append_format(info,
+					format_list->fourcc_format,
+					format_list->modifier);
+			++format_list;
+		}
+		sde_kms_info_stop(info);
+	}
+
+	sde_kms_info_add_keyint(info,
+			"wb_intf_index",
+			wb_dev->wb_idx - WB_0);
+
+	sde_kms_info_add_keyint(info,
+			"maxlinewidth",
+			wb_dev->wb_cfg->sblk->maxlinewidth);
+
+	sde_kms_info_start(info, "features");
+	if (wb_dev->wb_cfg && (wb_dev->wb_cfg->features & SDE_WB_UBWC_1_0))
+		sde_kms_info_append(info, "wb_ubwc");
+	sde_kms_info_stop(info);
+
+	return 0;
+}
+
+struct drm_framebuffer *sde_wb_get_output_fb(struct sde_wb_device *wb_dev)
+{
+	struct drm_framebuffer *fb;
+
+	if (!wb_dev || !wb_dev->connector) {
+		SDE_ERROR("invalid params\n");
+		return NULL;
+	}
+
+	SDE_DEBUG("\n");
+
+	mutex_lock(&wb_dev->wb_lock);
+	fb = sde_wb_connector_state_get_output_fb(wb_dev->connector->state);
+	mutex_unlock(&wb_dev->wb_lock);
+
+	return fb;
+}
+
+int sde_wb_get_output_roi(struct sde_wb_device *wb_dev, struct sde_rect *roi)
+{
+	int rc;
+
+	if (!wb_dev || !wb_dev->connector || !roi) {
+		SDE_ERROR("invalid params\n");
+		return -EINVAL;
+	}
+
+	SDE_DEBUG("\n");
+
+	mutex_lock(&wb_dev->wb_lock);
+	rc = sde_wb_connector_state_get_output_roi(
+			wb_dev->connector->state, roi);
+	mutex_unlock(&wb_dev->wb_lock);
+
+	return rc;
+}
+
+u32 sde_wb_get_num_of_displays(void)
+{
+	u32 count = 0;
+	struct sde_wb_device *wb_dev;
+
+	SDE_DEBUG("\n");
+
+	mutex_lock(&sde_wb_list_lock);
+	list_for_each_entry(wb_dev, &sde_wb_list, wb_list) {
+		count++;
+	}
+	mutex_unlock(&sde_wb_list_lock);
+
+	return count;
+}
+
+int wb_display_get_displays(void **display_array, u32 max_display_count)
+{
+	struct sde_wb_device *curr;
+	int i = 0;
+
+	SDE_DEBUG("\n");
+
+	if (!display_array || !max_display_count) {
+		if (!display_array)
+			SDE_ERROR("invalid param\n");
+		return 0;
+	}
+
+	mutex_lock(&sde_wb_list_lock);
+	list_for_each_entry(curr, &sde_wb_list, wb_list) {
+		if (i >= max_display_count)
+			break;
+		display_array[i++] = curr;
+	}
+	mutex_unlock(&sde_wb_list_lock);
+
+	return i;
+}
+
+int sde_wb_config(struct drm_device *drm_dev, void *data,
+				struct drm_file *file_priv)
+{
+	struct sde_drm_wb_cfg *config = data;
+	struct msm_drm_private *priv;
+	struct sde_wb_device *wb_dev = NULL;
+	struct sde_wb_device *curr;
+	struct drm_connector *connector;
+	uint32_t flags;
+	uint32_t connector_id;
+	uint32_t count_modes;
+	uint64_t modes;
+	int rc;
+
+	if (!drm_dev || !data) {
+		SDE_ERROR("invalid params\n");
+		return -EINVAL;
+	}
+
+	SDE_DEBUG("\n");
+
+	flags = config->flags;
+	connector_id = config->connector_id;
+	count_modes = config->count_modes;
+	modes = config->modes;
+
+	priv = drm_dev->dev_private;
+
+	connector = drm_connector_find(drm_dev, connector_id);
+	if (!connector) {
+		SDE_ERROR("failed to find connector\n");
+		rc = -ENOENT;
+		goto fail;
+	}
+
+	mutex_lock(&sde_wb_list_lock);
+	list_for_each_entry(curr, &sde_wb_list, wb_list) {
+		if (curr->connector == connector) {
+			wb_dev = curr;
+			break;
+		}
+	}
+	mutex_unlock(&sde_wb_list_lock);
+
+	if (!wb_dev) {
+		SDE_ERROR("failed to find wb device\n");
+		rc = -ENOENT;
+		goto fail;
+	}
+
+	mutex_lock(&wb_dev->wb_lock);
+
+	rc = sde_wb_connector_set_modes(wb_dev, count_modes,
+		(struct drm_mode_modeinfo __user *) (uintptr_t) modes,
+		(flags & SDE_DRM_WB_CFG_FLAGS_CONNECTED) ? true : false);
+
+	mutex_unlock(&wb_dev->wb_lock);
+	drm_helper_hpd_irq_event(drm_dev);
+fail:
+	return rc;
+}
+
+int sde_wb_dev_init(struct sde_wb_device *wb_dev)
+{
+	int rc = 0;
+
+	if (!wb_dev) {
+		SDE_ERROR("invalid params\n");
+		return -EINVAL;
+	}
+
+	SDE_DEBUG("\n");
+
+	return rc;
+}
+
+int sde_wb_dev_deinit(struct sde_wb_device *wb_dev)
+{
+	int rc = 0;
+
+	if (!wb_dev) {
+		SDE_ERROR("invalid params\n");
+		return -EINVAL;
+	}
+
+	SDE_DEBUG("\n");
+
+	return rc;
+}
+
+int sde_wb_bind(struct sde_wb_device *wb_dev, struct drm_device *drm_dev)
+{
+	int rc = 0;
+
+	if (!wb_dev || !drm_dev) {
+		SDE_ERROR("invalid params\n");
+		return -EINVAL;
+	}
+
+	SDE_DEBUG("\n");
+
+	mutex_lock(&wb_dev->wb_lock);
+	wb_dev->drm_dev = drm_dev;
+	mutex_unlock(&wb_dev->wb_lock);
+
+	return rc;
+}
+
+int sde_wb_unbind(struct sde_wb_device *wb_dev)
+{
+	int rc = 0;
+
+	if (!wb_dev) {
+		SDE_ERROR("invalid params\n");
+		return -EINVAL;
+	}
+
+	SDE_DEBUG("\n");
+
+	mutex_lock(&wb_dev->wb_lock);
+	wb_dev->drm_dev = NULL;
+	mutex_unlock(&wb_dev->wb_lock);
+
+	return rc;
+}
+
+int sde_wb_drm_init(struct sde_wb_device *wb_dev, struct drm_encoder *encoder)
+{
+	int rc = 0;
+
+	if (!wb_dev || !encoder) {
+		SDE_ERROR("invalid params\n");
+		return -EINVAL;
+	}
+
+	SDE_DEBUG("\n");
+
+	mutex_lock(&wb_dev->wb_lock);
+
+	if (wb_dev->drm_dev->dev_private) {
+		struct msm_drm_private *priv = wb_dev->drm_dev->dev_private;
+		struct sde_kms *sde_kms = to_sde_kms(priv->kms);
+
+		if (wb_dev->index < sde_kms->catalog->wb_count) {
+			wb_dev->wb_idx = sde_kms->catalog->wb[wb_dev->index].id;
+			wb_dev->wb_cfg = &sde_kms->catalog->wb[wb_dev->index];
+		}
+	}
+
+	wb_dev->drm_dev = encoder->dev;
+	wb_dev->encoder = encoder;
+	mutex_unlock(&wb_dev->wb_lock);
+	return rc;
+}
+
+int sde_wb_drm_deinit(struct sde_wb_device *wb_dev)
+{
+	int rc = 0;
+
+	if (!wb_dev) {
+		SDE_ERROR("invalid params\n");
+		return -EINVAL;
+	}
+
+	SDE_DEBUG("\n");
+
+	return rc;
+}
+
+/**
+ * sde_wb_probe - load writeback module
+ * @pdev:	Pointer to platform device
+ */
+static int sde_wb_probe(struct platform_device *pdev)
+{
+	struct sde_wb_device *wb_dev;
+	int ret;
+
+	wb_dev = devm_kzalloc(&pdev->dev, sizeof(*wb_dev), GFP_KERNEL);
+	if (!wb_dev)
+		return -ENOMEM;
+
+	SDE_DEBUG("\n");
+
+	ret = of_property_read_u32(pdev->dev.of_node, "cell-index",
+			&wb_dev->index);
+	if (ret) {
+		SDE_DEBUG("cell index not set, default to 0\n");
+		wb_dev->index = 0;
+	}
+
+	wb_dev->name = of_get_property(pdev->dev.of_node, "label", NULL);
+	if (!wb_dev->name) {
+		SDE_DEBUG("label not set, default to unknown\n");
+		wb_dev->name = "unknown";
+	}
+
+	wb_dev->wb_idx = SDE_NONE;
+
+	mutex_init(&wb_dev->wb_lock);
+	platform_set_drvdata(pdev, wb_dev);
+
+	mutex_lock(&sde_wb_list_lock);
+	list_add(&wb_dev->wb_list, &sde_wb_list);
+	mutex_unlock(&sde_wb_list_lock);
+
+	return 0;
+}
+
+/**
+ * sde_wb_remove - unload writeback module
+ * @pdev:	Pointer to platform device
+ */
+static int sde_wb_remove(struct platform_device *pdev)
+{
+	struct sde_wb_device *wb_dev;
+	struct sde_wb_device *curr, *next;
+
+	wb_dev = platform_get_drvdata(pdev);
+	if (!wb_dev)
+		return 0;
+
+	SDE_DEBUG("\n");
+
+	mutex_lock(&sde_wb_list_lock);
+	list_for_each_entry_safe(curr, next, &sde_wb_list, wb_list) {
+		if (curr == wb_dev) {
+			list_del(&wb_dev->wb_list);
+			break;
+		}
+	}
+	mutex_unlock(&sde_wb_list_lock);
+
+	kfree(wb_dev->modes);
+	mutex_destroy(&wb_dev->wb_lock);
+
+	platform_set_drvdata(pdev, NULL);
+	devm_kfree(&pdev->dev, wb_dev);
+
+	return 0;
+}
+
+static const struct of_device_id dt_match[] = {
+	{ .compatible = "qcom,wb-display"},
+	{}
+};
+
+static struct platform_driver sde_wb_driver = {
+	.probe = sde_wb_probe,
+	.remove = sde_wb_remove,
+	.driver = {
+		.name = "sde_wb",
+		.of_match_table = dt_match,
+	},
+};
+
+void sde_wb_register(void)
+{
+	platform_driver_register(&sde_wb_driver);
+}
+
+void sde_wb_unregister(void)
+{
+	platform_driver_unregister(&sde_wb_driver);
+}
diff --git a/drivers/gpu/drm/msm/sde/sde_wb.h b/drivers/gpu/drm/msm/sde/sde_wb.h
new file mode 100644
index 0000000..7905620
--- /dev/null
+++ b/drivers/gpu/drm/msm/sde/sde_wb.h
@@ -0,0 +1,376 @@
+/* Copyright (c) 2015-2016, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __SDE_WB_H__
+#define __SDE_WB_H__
+
+#include <linux/platform_device.h>
+
+#include "msm_kms.h"
+#include "sde_kms.h"
+
+/**
+ * struct sde_wb_device - Writeback device context
+ * @drm_dev:		Pointer to controlling DRM device
+ * @index:		Index of hardware instance from device tree
+ * @wb_idx:		Writeback identifier of enum sde_wb
+ * @wb_cfg:		Writeback configuration catalog
+ * @name:		Name of writeback device from device tree
+ * @display_type:	Display type from device tree
+ * @wb_list		List of all writeback devices
+ * @wb_lock		Serialization lock for writeback context structure
+ * @connector:		Connector associated with writeback device
+ * @encoder:		Encoder associated with writeback device
+ * @count_modes:	Length of writeback connector modes array
+ * @modes:		Writeback connector modes array
+ */
+struct sde_wb_device {
+	struct drm_device *drm_dev;
+
+	u32 index;
+	u32 wb_idx;
+	struct sde_wb_cfg *wb_cfg;
+	const char *name;
+
+	struct list_head wb_list;
+	struct mutex wb_lock;
+
+	struct drm_connector *connector;
+	struct drm_encoder *encoder;
+
+	enum drm_connector_status detect_status;
+	u32 count_modes;
+	struct drm_mode_modeinfo *modes;
+};
+
+/**
+ * sde_wb_get_index - get device index of the given writeback device
+ * @wb_dev:	Pointer to writeback device
+ * Returns:	Index of hardware instance
+ */
+static inline
+int sde_wb_get_index(struct sde_wb_device *wb_dev)
+{
+	return wb_dev ? wb_dev->index : -1;
+}
+
+#ifdef CONFIG_DRM_SDE_WB
+/**
+ * sde_wb_get_output_fb - get framebuffer in current atomic state
+ * @wb_dev:	Pointer to writeback device
+ * Returns:	Pointer to framebuffer
+ */
+struct drm_framebuffer *sde_wb_get_output_fb(struct sde_wb_device *wb_dev);
+
+/**
+ * sde_wb_get_output_roi - get region-of-interest in current atomic state
+ * @wb_dev:	Pointer to writeback device
+ * @roi:	Pointer to region of interest
+ * Returns:	0 if success; error code otherwise
+ */
+int sde_wb_get_output_roi(struct sde_wb_device *wb_dev, struct sde_rect *roi);
+
+/**
+ * sde_wb_get_num_of_displays - get total number of writeback devices
+ * Returns:	Number of writeback devices
+ */
+u32 sde_wb_get_num_of_displays(void);
+
+/**
+ * wb_display_get_displays - returns pointers for supported display devices
+ * @display_array: Pointer to display array to be filled
+ * @max_display_count: Size of display_array
+ * @Returns: Number of display entries filled
+ */
+int wb_display_get_displays(void **display_array, u32 max_display_count);
+
+/**
+ * wb_display_get_displays - returns pointers for supported display devices
+ * @display_array: Pointer to display array to be filled
+ * @max_display_count: Size of display_array
+ * @Returns: Number of display entries filled
+ */
+int sde_wb_dev_init(struct sde_wb_device *wb_dev);
+
+/**
+ * sde_wb_dev_deinit - perform device de-initialization
+ * @wb_dev:	Pointer to writeback device
+ * Returns:	0 if success; error code otherwise
+ */
+int sde_wb_dev_deinit(struct sde_wb_device *wb_dev);
+
+/**
+ * sde_wb_bind - bind writeback device with controlling device
+ * @wb_dev:	Pointer to writeback device
+ * @drm_dev:	Pointer to controlling DRM device
+ * Returns:	0 if success; error code otherwise
+ */
+int sde_wb_bind(struct sde_wb_device *wb_dev, struct drm_device *drm_dev);
+
+/**
+ * sde_wb_unbind - unbind writeback from controlling device
+ * @wb_dev:	Pointer to writeback device
+ * Returns:	0 if success; error code otherwise
+ */
+int sde_wb_unbind(struct sde_wb_device *wb_dev);
+
+/**
+ * sde_wb_drm_init - perform DRM initialization
+ * @wb_dev:	Pointer to writeback device
+ * @encoder:	Pointer to associated encoder
+ * Returns:	0 if success; error code otherwise
+ */
+int sde_wb_drm_init(struct sde_wb_device *wb_dev, struct drm_encoder *encoder);
+
+/**
+ * sde_wb_drm_deinit - perform DRM de-initialization
+ * @wb_dev:	Pointer to writeback device
+ * Returns:	0 if success; error code otherwise
+ */
+int sde_wb_drm_deinit(struct sde_wb_device *wb_dev);
+
+/**
+ * sde_wb_register - register writeback module
+ */
+void sde_wb_register(void);
+
+/**
+ * sde_wb_unregister - unregister writeback module
+ */
+void sde_wb_unregister(void);
+
+/**
+ * sde_wb_config - setup connection status and available drm modes of the
+ *			given writeback connector
+ * @drm_dev:	Pointer to DRM device
+ * @data:	Pointer to writeback configuration
+ * @file_priv:	Pointer file private data
+ * Returns:	0 if success; error code otherwise
+ *
+ * This function will initiate hot-plug detection event.
+ */
+int sde_wb_config(struct drm_device *drm_dev, void *data,
+				struct drm_file *file_priv);
+
+/**
+ * sde_wb_connector_post_init - perform writeback specific initialization
+ * @connector: Pointer to drm connector structure
+ * @info: Pointer to connector info
+ * @display: Pointer to private display structure
+ * Returns: Zero on success
+ */
+int sde_wb_connector_post_init(struct drm_connector *connector,
+		void *info,
+		void *display);
+
+/**
+ * sde_wb_connector_detect - perform writeback connection status detection
+ * @connector:	Pointer to connector
+ * @force:	Indicate force detection
+ * @display:	Pointer to writeback device
+ * Returns:	connector status
+ */
+enum drm_connector_status
+sde_wb_connector_detect(struct drm_connector *connector,
+		bool force,
+		void *display);
+
+/**
+ * sde_wb_connector_get_modes - get display modes of connector
+ * @connector:	Pointer to connector
+ * @display:	Pointer to writeback device
+ * Returns:	Number of modes
+ *
+ * If display modes are not specified in writeback configuration IOCTL, this
+ * function will install default EDID modes up to maximum resolution support.
+ */
+int sde_wb_connector_get_modes(struct drm_connector *connector, void *display);
+
+/**
+ * sde_wb_connector_set_property - set atomic connector property
+ * @connector: Pointer to drm connector structure
+ * @state: Pointer to drm connector state structure
+ * @property_index: DRM property index
+ * @value: Incoming property value
+ * @display: Pointer to private display structure
+ * Returns: Zero on success
+ */
+int sde_wb_connector_set_property(struct drm_connector *connector,
+		struct drm_connector_state *state,
+		int property_index,
+		uint64_t value,
+		void *display);
+
+/**
+ * sde_wb_get_info - retrieve writeback 'display' information
+ * @info: Pointer to display info structure
+ * @display: Pointer to private display structure
+ * Returns: Zero on success
+ */
+int sde_wb_get_info(struct msm_display_info *info, void *display);
+
+/**
+ * sde_wb_connector_get_wb - retrieve writeback device of the given connector
+ * @connector: Pointer to drm connector
+ * Returns: Pointer to writeback device on success; NULL otherwise
+ */
+static inline
+struct sde_wb_device *sde_wb_connector_get_wb(struct drm_connector *connector)
+{
+	if (!connector ||
+		(connector->connector_type != DRM_MODE_CONNECTOR_VIRTUAL)) {
+		SDE_ERROR("invalid params\n");
+		return NULL;
+	}
+
+	return sde_connector_get_display(connector);
+}
+
+/**
+ * sde_wb_connector_state_get_output_fb - get framebuffer of given state
+ * @state:	Pointer to connector state
+ * Returns:	Pointer to framebuffer
+ */
+struct drm_framebuffer *
+sde_wb_connector_state_get_output_fb(struct drm_connector_state *state);
+
+/**
+ * sde_wb_connector_state_get_output_roi - get roi from given atomic state
+ * @state:	Pointer to atomic state
+ * @roi:	Pointer to region of interest
+ * Returns:	0 if success; error code otherwise
+ */
+int sde_wb_connector_state_get_output_roi(struct drm_connector_state *state,
+		struct sde_rect *roi);
+
+#else
+static inline
+struct drm_framebuffer *sde_wb_get_output_fb(struct sde_wb_device *wb_dev)
+{
+	return NULL;
+}
+static inline
+int sde_wb_get_output_roi(struct sde_wb_device *wb_dev, struct sde_rect *roi)
+{
+	return 0;
+}
+static inline
+u32 sde_wb_get_num_of_displays(void)
+{
+	return 0;
+}
+static inline
+int wb_display_get_displays(void **display_array, u32 max_display_count)
+{
+	return 0;
+}
+static inline
+int sde_wb_dev_init(struct sde_wb_device *wb_dev)
+{
+	return 0;
+}
+static inline
+int sde_wb_dev_deinit(struct sde_wb_device *wb_dev)
+{
+	return 0;
+}
+static inline
+int sde_wb_bind(struct sde_wb_device *wb_dev, struct drm_device *drm_dev)
+{
+	return 0;
+}
+static inline
+int sde_wb_unbind(struct sde_wb_device *wb_dev)
+{
+	return 0;
+}
+static inline
+int sde_wb_drm_init(struct sde_wb_device *wb_dev, struct drm_encoder *encoder)
+{
+	return 0;
+}
+static inline
+int sde_wb_drm_deinit(struct sde_wb_device *wb_dev)
+{
+	return 0;
+}
+static inline
+void sde_wb_register(void)
+{
+}
+static inline
+void sde_wb_unregister(void)
+{
+}
+static inline
+int sde_wb_config(struct drm_device *drm_dev, void *data,
+				struct drm_file *file_priv)
+{
+	return 0;
+}
+static inline
+int sde_wb_connector_post_init(struct drm_connector *connector,
+		void *info,
+		void *display)
+{
+	return 0;
+}
+static inline
+enum drm_connector_status
+sde_wb_connector_detect(struct drm_connector *connector,
+		bool force,
+		void *display)
+{
+	return connector_status_disconnected;
+}
+static inline
+int sde_wb_connector_get_modes(struct drm_connector *connector, void *display)
+{
+	return -EINVAL;
+}
+static inline
+int sde_wb_connector_set_property(struct drm_connector *connector,
+		struct drm_connector_state *state,
+		int property_index,
+		uint64_t value,
+		void *display)
+{
+	return 0;
+}
+static inline
+int sde_wb_get_info(struct msm_display_info *info, void *display)
+{
+	return 0;
+}
+static inline
+struct sde_wb_device *sde_wb_connector_get_wb(struct drm_connector *connector)
+{
+	return NULL;
+}
+
+static inline
+struct drm_framebuffer *
+sde_wb_connector_state_get_output_fb(struct drm_connector_state *state)
+{
+	return NULL;
+}
+
+static inline
+int sde_wb_connector_state_get_output_roi(struct drm_connector_state *state,
+		struct sde_rect *roi)
+{
+	return 0;
+}
+
+#endif
+#endif /* __SDE_WB_H__ */
+