diff --git a/drivers/gpu/drm/i915/display/intel_display.c b/drivers/gpu/drm/i915/display/intel_display.c
index acb3c5974e99..7f86c358cf45 100644
--- a/drivers/gpu/drm/i915/display/intel_display.c
+++ b/drivers/gpu/drm/i915/display/intel_display.c
@@ -14230,7 +14230,7 @@ static void intel_update_crtc(struct intel_crtc *crtc,
 	else
 		i9xx_update_planes_on_crtc(state, crtc);
 
-	intel_pipe_update_end(new_crtc_state);
+	intel_pipe_update_end(new_crtc_state, NULL);
 
 	if (new_crtc_state->update_pipe && !modeset &&
 	    old_crtc_state->hw.mode.private_flags & I915_MODE_FLAG_INHERITED)
@@ -14312,6 +14312,56 @@ static void intel_commit_modeset_disables(struct intel_atomic_state *state)
 	}
 }
 
+static void intel_update_bigjoiner(struct intel_crtc *crtc,
+				   struct intel_atomic_state *state,
+				   struct intel_crtc_state *old_crtc_state,
+				   struct intel_crtc_state *new_crtc_state)
+{
+	struct drm_i915_private *dev_priv = to_i915(state->base.dev);
+	bool modeset = needs_modeset(new_crtc_state);
+	struct intel_crtc *slave = new_crtc_state->bigjoiner_linked_crtc;
+	struct intel_crtc_state *new_slave_crtc_state =
+		intel_atomic_get_new_crtc_state(state, slave);
+	struct intel_crtc_state *old_slave_crtc_state =
+		intel_atomic_get_old_crtc_state(state, slave);
+
+	if (modeset) {
+		/* Enable slave first */
+		update_scanline_offset(new_slave_crtc_state);
+		drm_calc_timestamping_constants(&slave->base, &new_slave_crtc_state->hw.transcoder_mode);
+		dev_priv->display.crtc_enable(new_slave_crtc_state, state);
+
+		/* Then master */
+		update_scanline_offset(new_crtc_state);
+		drm_calc_timestamping_constants(&crtc->base, &new_crtc_state->hw.transcoder_mode);
+		dev_priv->display.crtc_enable(new_crtc_state, state);
+
+		/* vblanks work again, re-enable pipe CRC. */
+		intel_crtc_enable_pipe_crc(crtc);
+
+	} else {
+		intel_pre_plane_update(old_crtc_state, new_crtc_state);
+		intel_pre_plane_update(old_slave_crtc_state, new_slave_crtc_state);
+
+		if (new_crtc_state->update_pipe)
+			intel_encoders_update_pipe(crtc, new_crtc_state, state);
+	}
+
+	/*
+	 * Perform vblank evasion around commit operation, and make sure to
+	 * commit both planes simultaneously for best results.
+	 */
+	intel_pipe_update_start(new_crtc_state);
+
+	commit_pipe_config(state, old_crtc_state, new_crtc_state);
+	commit_pipe_config(state, old_slave_crtc_state, new_slave_crtc_state);
+
+	skl_update_planes_on_crtc(state, crtc);
+	icl_update_bigjoiner_planes_on_crtc(state, slave);
+
+	intel_pipe_update_end(new_crtc_state, new_slave_crtc_state);
+}
+
 static void intel_commit_modeset_enables(struct intel_atomic_state *state)
 {
 	struct intel_crtc *crtc;
@@ -14330,7 +14380,7 @@ static void intel_commit_modeset_enables(struct intel_atomic_state *state)
 static void skl_commit_modeset_enables(struct intel_atomic_state *state)
 {
 	struct drm_i915_private *dev_priv = to_i915(state->base.dev);
-	struct intel_crtc *crtc;
+	struct intel_crtc *crtc, *slave;
 	struct intel_crtc_state *old_crtc_state, *new_crtc_state;
 	unsigned int updated = 0;
 	bool progress;
@@ -14339,11 +14389,47 @@ static void skl_commit_modeset_enables(struct intel_atomic_state *state)
 	u8 hw_enabled_slices = dev_priv->wm.skl_hw.ddb.enabled_slices;
 	u8 required_slices = state->wm_results.ddb.enabled_slices;
 	struct skl_ddb_entry entries[I915_MAX_PIPES] = {};
+	struct skl_ddb_entry new_entries[I915_MAX_PIPES] = {};
+	const struct intel_crtc_state *slave_crtc_state;
+	u32 dirty_pipes = state->wm_results.dirty_pipes;
+
+	for_each_oldnew_intel_crtc_in_state(state, crtc, old_crtc_state, new_crtc_state, i) {
+		if (new_crtc_state->bigjoiner_slave) {
+			/* clear dirty bit, we're updated in master */
+			dirty_pipes &= ~drm_crtc_mask(&crtc->base);
+			continue;
+		}
+
+		if (new_crtc_state->hw.active) {
+			if (new_crtc_state->bigjoiner) {
+				slave = new_crtc_state->bigjoiner_linked_crtc;
+				slave_crtc_state =
+					intel_atomic_get_new_crtc_state(state,
+									slave);
+
+				/* put both entries in */
+				new_entries[i].start = new_crtc_state->wm.skl.ddb.start;
+				new_entries[i].end = slave_crtc_state->wm.skl.ddb.end;
+			} else {
+				new_entries[i] = new_crtc_state->wm.skl.ddb;
+			}
+		}
 
-	for_each_oldnew_intel_crtc_in_state(state, crtc, old_crtc_state, new_crtc_state, i)
 		/* ignore allocations for crtc's that have been turned off. */
-		if (new_crtc_state->hw.active)
+		if (!new_crtc_state->hw.active || needs_modeset(new_crtc_state))
+			continue;
+
+		if (old_crtc_state->bigjoiner) {
+			slave = old_crtc_state->bigjoiner_linked_crtc;
+			slave_crtc_state =
+				intel_atomic_get_old_crtc_state(state, slave);
+
+			entries[i].start = old_crtc_state->wm.skl.ddb.start;
+			entries[i].end = slave_crtc_state->wm.skl.ddb.end;
+		} else {
 			entries[i] = old_crtc_state->wm.skl.ddb;
+		}
+	}
 
 	/* If 2nd DBuf slice required, enable it here */
 	if (INTEL_GEN(dev_priv) >= 11 && required_slices > hw_enabled_slices)
@@ -14364,16 +14450,15 @@ static void skl_commit_modeset_enables(struct intel_atomic_state *state)
 
 			pipe = crtc->pipe;
 
-			if (updated & cmask || !new_crtc_state->hw.active)
+			if (updated & cmask || !new_crtc_state->hw.active ||
+			    new_crtc_state->bigjoiner_slave)
 				continue;
 
-			if (skl_ddb_allocation_overlaps(&new_crtc_state->wm.skl.ddb,
-							entries,
+			if (skl_ddb_allocation_overlaps(&new_entries[i], entries,
 							INTEL_NUM_PIPES(dev_priv), i))
 				continue;
 
 			updated |= cmask;
-			entries[i] = new_crtc_state->wm.skl.ddb;
 
 			/*
 			 * If this is an already active pipe, it's DDB changed,
@@ -14381,18 +14466,23 @@ static void skl_commit_modeset_enables(struct intel_atomic_state *state)
 			 * then we need to wait for a vblank to pass for the
 			 * new ddb allocation to take effect.
 			 */
-			if (!skl_ddb_entry_equal(&new_crtc_state->wm.skl.ddb,
-						 &old_crtc_state->wm.skl.ddb) &&
-			    !new_crtc_state->uapi.active_changed &&
-			    state->wm_results.dirty_pipes != updated)
+			if (!skl_ddb_entry_equal(&entries[i], &new_entries[i]) &&
+			    !needs_modeset(new_crtc_state) &&
+			    dirty_pipes != updated)
 				vbl_wait = true;
 
-			intel_update_crtc(crtc, state, old_crtc_state,
-					  new_crtc_state);
+			if (new_crtc_state->bigjoiner)
+				intel_update_bigjoiner(crtc, state,
+						       old_crtc_state,
+						       new_crtc_state);
+			else
+				intel_update_crtc(crtc, state, old_crtc_state,
+						  new_crtc_state);
 
 			if (vbl_wait)
 				intel_wait_for_vblank(dev_priv, pipe);
 
+			entries[i] = new_entries[i];
 			progress = true;
 		}
 	} while (progress);
diff --git a/drivers/gpu/drm/i915/display/intel_sprite.c b/drivers/gpu/drm/i915/display/intel_sprite.c
index bca7ff8a8907..9337f5a8dce0 100644
--- a/drivers/gpu/drm/i915/display/intel_sprite.c
+++ b/drivers/gpu/drm/i915/display/intel_sprite.c
@@ -98,6 +98,8 @@ void intel_pipe_update_start(const struct intel_crtc_state *new_crtc_state)
 
 	/* FIXME needs to be calibrated sensibly */
 	min = vblank_start - intel_usecs_to_scanlines(adjusted_mode,
+						      new_crtc_state->bigjoiner ?
+						      2 * VBLANK_EVASION_TIME_US :
 						      VBLANK_EVASION_TIME_US);
 	max = vblank_start - 1;
 
@@ -188,7 +190,8 @@ void intel_pipe_update_start(const struct intel_crtc_state *new_crtc_state)
  * re-enables interrupts and verifies the update was actually completed
  * before a vblank.
  */
-void intel_pipe_update_end(struct intel_crtc_state *new_crtc_state)
+void intel_pipe_update_end(struct intel_crtc_state *new_crtc_state,
+			   struct intel_crtc_state *slave_crtc_state)
 {
 	struct intel_crtc *crtc = to_intel_crtc(new_crtc_state->uapi.crtc);
 	enum pipe pipe = crtc->pipe;
@@ -203,15 +206,24 @@ void intel_pipe_update_end(struct intel_crtc_state *new_crtc_state)
 	 * Would be slightly nice to just grab the vblank count and arm the
 	 * event outside of the critical section - the spinlock might spin for a
 	 * while ... */
-	if (new_crtc_state->uapi.event) {
-		WARN_ON(drm_crtc_vblank_get(&crtc->base) != 0);
+	if (new_crtc_state->uapi.event || (slave_crtc_state && slave_crtc_state->uapi.event)) {
+		if (new_crtc_state->uapi.event)
+			WARN_ON(drm_crtc_vblank_get(&crtc->base) != 0);
+		if (slave_crtc_state && slave_crtc_state->uapi.event)
+			WARN_ON(drm_crtc_vblank_get(&crtc->base) != 0);
 
 		spin_lock(&crtc->base.dev->event_lock);
-		drm_crtc_arm_vblank_event(&crtc->base,
-				          new_crtc_state->uapi.event);
+		if (new_crtc_state->uapi.event)
+			drm_crtc_arm_vblank_event(&crtc->base,
+						  new_crtc_state->uapi.event);
+		if (slave_crtc_state && slave_crtc_state->uapi.event)
+			drm_crtc_arm_vblank_event(&crtc->base,
+						  slave_crtc_state->uapi.event);
 		spin_unlock(&crtc->base.dev->event_lock);
 
 		new_crtc_state->uapi.event = NULL;
+		if (slave_crtc_state)
+			slave_crtc_state->uapi.event = NULL;
 	}
 
 	local_irq_enable();
diff --git a/drivers/gpu/drm/i915/display/intel_sprite.h b/drivers/gpu/drm/i915/display/intel_sprite.h
index 229336214f68..6df62fae9368 100644
--- a/drivers/gpu/drm/i915/display/intel_sprite.h
+++ b/drivers/gpu/drm/i915/display/intel_sprite.h
@@ -24,7 +24,8 @@ struct intel_plane *intel_sprite_plane_create(struct drm_i915_private *dev_priv,
 int intel_sprite_set_colorkey_ioctl(struct drm_device *dev, void *data,
 				    struct drm_file *file_priv);
 void intel_pipe_update_start(const struct intel_crtc_state *new_crtc_state);
-void intel_pipe_update_end(struct intel_crtc_state *new_crtc_state);
+void intel_pipe_update_end(struct intel_crtc_state *new_crtc_state,
+			   struct intel_crtc_state *slave_crtc_state);
 int intel_plane_check_stride(const struct intel_plane_state *plane_state);
 int intel_plane_check_src_coordinates(struct intel_plane_state *plane_state);
 int chv_plane_check_rotation(const struct intel_plane_state *plane_state);
