diff --git a/drivers/gpu/drm/i915/display/intel_bw.h b/drivers/gpu/drm/i915/display/intel_bw.h
index b5f61463922f..c32b5285c12f 100644
--- a/drivers/gpu/drm/i915/display/intel_bw.h
+++ b/drivers/gpu/drm/i915/display/intel_bw.h
@@ -18,6 +18,24 @@ struct intel_crtc_state;
 struct intel_bw_state {
 	struct intel_global_state base;
 
+	/*
+	 * Contains a bit mask, used to determine, whether correspondent
+	 * pipe allows SAGV or not.
+	 */
+	u8 pipe_sagv_mask;
+
+	/*
+	 * Used to determine if we already had calculated
+	 * SAGV mask for this state once.
+	 */
+	bool sagv_calculated;
+
+	/*
+	 * Contains final SAGV decision based on current mask,
+	 * to prevent doing the same job over and over again.
+	 */
+	bool can_sagv;
+
 	unsigned int data_rate[I915_MAX_PIPES];
 	u8 num_active_planes[I915_MAX_PIPES];
 };
diff --git a/drivers/gpu/drm/i915/display/intel_display.c b/drivers/gpu/drm/i915/display/intel_display.c
index 5fca80b23a0e..2491f464beb9 100644
--- a/drivers/gpu/drm/i915/display/intel_display.c
+++ b/drivers/gpu/drm/i915/display/intel_display.c
@@ -14016,7 +14016,10 @@ static void verify_wm_state(struct intel_crtc *crtc,
 		/* Watermarks */
 		for (level = 0; level <= max_level; level++) {
 			if (skl_wm_level_equals(&hw_plane_wm->wm[level],
-						&sw_plane_wm->wm[level]))
+						&sw_plane_wm->wm[level]) ||
+			   (skl_wm_level_equals(&hw_plane_wm->wm[level],
+						&sw_plane_wm->sagv_wm0) &&
+			   (level == 0)))
 				continue;
 
 			drm_err(&dev_priv->drm,
@@ -14071,7 +14074,10 @@ static void verify_wm_state(struct intel_crtc *crtc,
 		/* Watermarks */
 		for (level = 0; level <= max_level; level++) {
 			if (skl_wm_level_equals(&hw_plane_wm->wm[level],
-						&sw_plane_wm->wm[level]))
+						&sw_plane_wm->wm[level]) ||
+			   (skl_wm_level_equals(&hw_plane_wm->wm[level],
+						&sw_plane_wm->sagv_wm0) &&
+			   (level == 0)))
 				continue;
 
 			drm_err(&dev_priv->drm,
@@ -15548,9 +15554,11 @@ static void intel_atomic_commit_tail(struct intel_atomic_state *state)
 		 * SKL workaround: bspec recommends we disable the SAGV when we
 		 * have more then one pipe enabled
 		 */
-		if (state->bw_state_changed) {
-			if (!intel_can_enable_sagv(state))
-				intel_disable_sagv(dev_priv);
+		if (INTEL_GEN(dev_priv) < 11) {
+			if (state->bw_state_changed) {
+				if (!intel_can_enable_sagv(dev_priv))
+					intel_disable_sagv(dev_priv);
+			}
 		}
 
 		intel_modeset_verify_disabled(dev_priv, state);
@@ -15651,9 +15659,11 @@ static void intel_atomic_commit_tail(struct intel_atomic_state *state)
 	if (state->modeset)
 		intel_verify_planes(state);
 
-	if (state->bw_state_changed) {
-		if (intel_can_enable_sagv(state)
-			intel_enable_sagv(dev_priv);
+	if (INTEL_GEN(dev_priv) < 11) {
+		if (state->bw_state_changed) {
+			if (intel_can_enable_sagv(dev_priv))
+				intel_enable_sagv(dev_priv);
+		}
 	}
 
 	drm_atomic_helper_commit_hw_done(&state->base);
@@ -15806,7 +15816,6 @@ static int intel_atomic_commit(struct drm_device *dev,
 
 	if (state->global_state_changed) {
 		assert_global_state_locked(dev_priv);
-
 		dev_priv->active_pipes = state->active_pipes;
 	}
 
diff --git a/drivers/gpu/drm/i915/display/intel_display_types.h b/drivers/gpu/drm/i915/display/intel_display_types.h
index c57de98553a8..89a73e735b22 100644
--- a/drivers/gpu/drm/i915/display/intel_display_types.h
+++ b/drivers/gpu/drm/i915/display/intel_display_types.h
@@ -663,6 +663,8 @@ struct skl_plane_wm {
 	struct skl_wm_level wm[8];
 	struct skl_wm_level uv_wm[8];
 	struct skl_wm_level trans_wm;
+	struct skl_wm_level sagv_wm0;
+	struct skl_wm_level uv_sagv_wm0;
 	bool is_planar;
 };
 
diff --git a/drivers/gpu/drm/i915/intel_pm.c b/drivers/gpu/drm/i915/intel_pm.c
index ac4b317ea1bf..011c9d4ba015 100644
--- a/drivers/gpu/drm/i915/intel_pm.c
+++ b/drivers/gpu/drm/i915/intel_pm.c
@@ -42,6 +42,7 @@
 #include "i915_drv.h"
 #include "i915_irq.h"
 #include "i915_trace.h"
+#include "display/intel_bw.h"
 #include "intel_pm.h"
 #include "intel_sideband.h"
 #include "../../../platform/x86/intel_ips.h"
@@ -3620,7 +3621,7 @@ static bool skl_needs_memory_bw_wa(struct drm_i915_private *dev_priv)
 	return IS_GEN9_BC(dev_priv) || IS_BROXTON(dev_priv);
 }
 
-static bool
+bool
 intel_has_sagv(struct drm_i915_private *dev_priv)
 {
 	/* HACK! */
@@ -3743,39 +3744,24 @@ intel_disable_sagv(struct drm_i915_private *dev_priv)
 	return 0;
 }
 
-bool intel_can_enable_sagv(struct intel_atomic_state *state)
+static bool skl_can_enable_sagv_on_pipe(struct intel_atomic_state *state,
+					enum pipe pipe)
 {
 	struct drm_device *dev = state->base.dev;
 	struct drm_i915_private *dev_priv = to_i915(dev);
 	struct intel_crtc *crtc;
 	struct intel_plane *plane;
 	struct intel_crtc_state *crtc_state;
-	enum pipe pipe;
 	int level, latency;
 
-	if (!intel_has_sagv(dev_priv))
-		return false;
-
-	/*
-	 * If there are no active CRTCs, no additional checks need be performed
-	 */
-	if (hweight8(state->active_pipes) == 0)
-		return true;
-
-	/*
-	 * SKL+ workaround: bspec recommends we disable SAGV when we have
-	 * more then one pipe enabled
-	 */
-	if (hweight8(state->active_pipes) > 1)
-		return false;
-
-	/* Since we're now guaranteed to only have one active CRTC... */
-	pipe = ffs(state->active_pipes) - 1;
 	crtc = intel_get_crtc_for_pipe(dev_priv, pipe);
 	crtc_state = to_intel_crtc_state(crtc->base.state);
 
-	if (crtc_state->hw.adjusted_mode.flags & DRM_MODE_FLAG_INTERLACE)
+	if (crtc_state->hw.adjusted_mode.flags & DRM_MODE_FLAG_INTERLACE) {
+		DRM_DEBUG_KMS("No SAGV for interlaced mode on pipe %c\n",
+			      pipe_name(pipe));
 		return false;
+	}
 
 	for_each_intel_plane_on_crtc(dev, crtc, plane) {
 		struct skl_plane_wm *wm =
@@ -3802,13 +3788,173 @@ bool intel_can_enable_sagv(struct intel_atomic_state *state)
 		 * incur memory latencies higher than sagv_block_time_us we
 		 * can't enable SAGV.
 		 */
-		if (latency < dev_priv->sagv_block_time_us)
+		if (latency < dev_priv->sagv_block_time_us) {
+			DRM_DEBUG_KMS("Latency %d < sagv block time %d, no SAGV for pipe %c\n",
+				      latency, dev_priv->sagv_block_time_us, pipe_name(pipe));
 			return false;
+		}
 	}
 
 	return true;
 }
 
+static void skl_compute_sagv_mask(struct intel_atomic_state *state)
+{
+	enum pipe pipe;
+	struct intel_bw_state *new_bw_state = intel_bw_get_state(state);
+
+	if (IS_ERR(new_bw_state)) {
+		WARN(1, "Could not get bw_state\n");
+		return;
+	}
+
+	if (state->active_pipes != 1) {
+		new_bw_state->pipe_sagv_mask = 0;
+		DRM_DEBUG_KMS("No SAGV for multiple pipes on Gen 9\n");
+		return;
+	}
+
+	/* Since we're now guaranteed to only have one active CRTC... */
+	pipe = ffs(state->active_pipes) - 1;
+
+	if (skl_can_enable_sagv_on_pipe(state, pipe))
+		new_bw_state->pipe_sagv_mask |= BIT(pipe);
+	else
+		new_bw_state->pipe_sagv_mask &= ~BIT(pipe);
+}
+
+static void tgl_compute_sagv_mask(struct intel_atomic_state *state);
+
+static void icl_compute_sagv_mask(struct intel_atomic_state *state)
+{
+	struct intel_crtc *crtc;
+	struct intel_crtc_state *new_crtc_state;
+	int i;
+	struct intel_bw_state *new_bw_state = intel_bw_get_state(state);
+
+	if (IS_ERR(new_bw_state)) {
+		WARN(1, "Could not get bw_state\n");
+		return;
+	}
+
+	for_each_new_intel_crtc_in_state(state, crtc,
+					 new_crtc_state, i) {
+		if (skl_can_enable_sagv_on_pipe(state, crtc->pipe))
+			new_bw_state->pipe_sagv_mask |= BIT(crtc->pipe);
+		else
+			new_bw_state->pipe_sagv_mask &= ~BIT(crtc->pipe);
+	}
+}
+
+static void intel_compute_sagv_mask(struct intel_atomic_state *state)
+{
+	int ret;
+	struct drm_device *dev = state->base.dev;
+	struct drm_i915_private *dev_priv = to_i915(dev);
+	struct intel_bw_state *new_bw_state;
+	struct intel_bw_state *old_bw_state;
+
+	/*
+	 * If we are here - means either active_pipes config had changed,
+	 * or watermarks/ddb has been changed, which means most likely
+	 * we had crtcs added to the state(at least state is read locked)
+	 * and we definitely need to recalculate SAGV state now.
+	 */
+	new_bw_state = intel_bw_get_state(state);
+	old_bw_state = intel_bw_get_old_state(state);
+
+	if (IS_ERR(new_bw_state) || IS_ERR(old_bw_state)) {
+		WARN(1, "Could not get bw_state\n");
+		return;
+	}
+
+	new_bw_state->sagv_calculated = false;
+
+	/*
+	 * Now once we got wm levels calculated,
+	 * check if we can have SAGV.
+	 */
+	if (INTEL_GEN(dev_priv) >= 12)
+		tgl_compute_sagv_mask(state);
+	else if (INTEL_GEN(dev_priv) == 11)
+		icl_compute_sagv_mask(state);
+	else
+		skl_compute_sagv_mask(state);
+
+	/*
+	 * For SAGV we need to account all the pipes,
+	 * not only the ones which are in state currently.
+	 * Grab all locks if we detect that we are actually
+	 * going to do something.
+	 */
+	if (new_bw_state->pipe_sagv_mask != old_bw_state->pipe_sagv_mask) {
+		ret = intel_atomic_serialize_global_state(&new_bw_state->base);
+		if (ret) {
+			DRM_DEBUG_KMS("Could not serialize global state\n");
+			return;
+		}
+
+		DRM_DEBUG_KMS("Marking bw state changed for atomic state %p\n", state);
+
+		state->bw_state_changed = true;
+	}
+}
+
+static bool intel_calculate_sagv_result(struct drm_i915_private *dev_priv,
+					struct intel_bw_state *bw_state)
+{
+	bool sagv_result = true;
+	enum pipe pipe;
+
+	for_each_pipe(dev_priv, pipe) {
+		/*
+		 * TODO: We are depending on active_pipes here,
+		 * probably it should be part of some other global state
+		 * obj, like modeset_state or smth, which we should depend on.
+		 * Don't want to clone it here, really.
+		 */
+		int active_pipe_bit = dev_priv->active_pipes & BIT(pipe);
+
+		if (active_pipe_bit) {
+			if ((bw_state->pipe_sagv_mask & BIT(pipe)) == 0) {
+				sagv_result = false;
+				break;
+			}
+		}
+	}
+
+	return sagv_result;
+}
+
+/*
+ * This function to be used before swap state
+ */
+bool intel_can_enable_sagv_for_state(struct intel_atomic_state *state)
+{
+	struct drm_device *dev = state->base.dev;
+	struct drm_i915_private *dev_priv = to_i915(dev);
+	struct intel_bw_state *bw_state  = intel_bw_get_state(state);
+
+	if (IS_ERR(bw_state)) {
+		WARN(1, "Could not get bw_state\n");
+		return false;
+	}
+
+	if (!intel_has_sagv(dev_priv)) {
+		DRM_DEBUG_KMS("No SAGV support detected\n");
+		return false;
+	}
+
+	if (bw_state->sagv_calculated)
+		goto out;
+
+	bw_state->can_sagv = intel_calculate_sagv_result(dev_priv, bw_state);
+	bw_state->sagv_calculated = true;
+
+out:
+	return bw_state->can_sagv;
+}
+
 /*
  * Calculate initial DBuf slice offset, based on slice size
  * and mask(i.e if slice size is 1024 and second slice is enabled
@@ -3842,6 +3988,35 @@ static u16 intel_get_ddb_size(struct drm_i915_private *dev_priv)
 	return ddb_size;
 }
 
+/*
+ * To be used after we swap state
+ */
+bool intel_can_enable_sagv(struct drm_i915_private *dev_priv)
+{
+	struct intel_global_state *global_state;
+	struct intel_bw_state *bw_state;
+
+	global_state = dev_priv->bw_obj.state;
+	if (IS_ERR(global_state)) {
+		WARN(1, "Could not get global state\n");
+		return false;
+	}
+
+	/*
+	 * TODO: Should we still may be lock global state here?
+	 */
+	bw_state = to_intel_bw_state(global_state);
+
+	if (bw_state->sagv_calculated)
+		goto out;
+
+	bw_state->can_sagv = intel_calculate_sagv_result(dev_priv, bw_state);
+	bw_state->sagv_calculated = true;
+
+out:
+	return bw_state->can_sagv;
+}
+
 static u8 skl_compute_dbuf_slices(const struct intel_crtc_state *crtc_state,
 				  u32 active_pipes);
 
@@ -4028,6 +4203,7 @@ skl_cursor_allocation(const struct intel_crtc_state *crtc_state,
 		u32 latency = dev_priv->wm.skl_latency[level];
 
 		skl_compute_plane_wm(crtc_state, level, latency, &wp, &wm, &wm);
+
 		if (wm.min_ddb_alloc == U16_MAX)
 			break;
 
@@ -4554,12 +4730,92 @@ skl_plane_wm_level(struct intel_plane *plane,
 		   int level,
 		   int color_plane)
 {
+	struct drm_atomic_state *state = crtc_state->uapi.state;
+	struct drm_crtc *crtc = crtc_state->uapi.crtc;
+	struct drm_i915_private *dev_priv = to_i915(crtc->dev);
 	const struct skl_plane_wm *wm =
 		&crtc_state->wm.skl.optimal.planes[plane->id];
 
+	if (!level) {
+		bool can_sagv = false;
+
+		/*
+		 * If we haven't yet swapped our state, we should use
+		 * the state to determine SAGV, otherwise use global
+		 * state as atomic state pointer might become stale
+		 * and zeroed out.
+		 */
+		if (state) {
+			struct intel_atomic_state *intel_state =
+				to_intel_atomic_state(state);
+			can_sagv = intel_can_enable_sagv_for_state(intel_state);
+		} else {
+			can_sagv = intel_can_enable_sagv(dev_priv);
+		}
+
+		if (can_sagv)
+			return color_plane ? &wm->uv_sagv_wm0 : &wm->sagv_wm0;
+	}
+
 	return color_plane ? &wm->uv_wm[level] : &wm->wm[level];
 }
 
+static int
+tgl_check_pipe_fits_sagv_wm(struct intel_crtc_state *crtc_state)
+{
+	struct drm_crtc *crtc = crtc_state->uapi.crtc;
+	struct drm_i915_private *dev_priv = to_i915(crtc->dev);
+	struct intel_crtc *intel_crtc = to_intel_crtc(crtc);
+	struct skl_ddb_entry *alloc = &crtc_state->wm.skl.ddb;
+	u16 alloc_size;
+	u64 total_data_rate;
+	enum plane_id plane_id;
+	int num_active;
+	u64 plane_data_rate[I915_MAX_PLANES] = {};
+	u32 blocks;
+
+	/*
+	 * No need to check gen here, we call this only for gen12
+	 */
+	total_data_rate =
+		icl_get_total_relative_data_rate(crtc_state,
+						 plane_data_rate);
+
+	skl_ddb_get_pipe_allocation_limits(dev_priv, crtc_state,
+					   total_data_rate,
+					   alloc, &num_active);
+	alloc_size = skl_ddb_entry_size(alloc);
+	if (alloc_size == 0)
+		return -ENOSPC;
+
+	/*
+	 * Do check if we can fit L0 + sagv_block_time and
+	 * disable SAGV if we can't.
+	 */
+	blocks = 0;
+	for_each_plane_id_on_crtc(intel_crtc, plane_id) {
+		/*
+		 * The only place, where we can't use skl_plane_wm_level
+		 * accessor, because if actually calls intel_can_enable_sagv
+		 * which depends on that function.
+		 */
+		const struct skl_plane_wm *wm =
+			&crtc_state->wm.skl.optimal.planes[plane_id];
+
+		blocks += wm->sagv_wm0.min_ddb_alloc;
+		blocks += wm->uv_sagv_wm0.min_ddb_alloc;
+
+		if (blocks > alloc_size) {
+			DRM_DEBUG_KMS("Not enough ddb blocks(%d<%d) for SAGV on pipe %c\n",
+				      alloc_size, blocks, pipe_name(intel_crtc->pipe));
+			return -ENOSPC;
+		}
+	}
+	DRM_DEBUG_KMS("%d total blocks required for SAGV, ddb entry size %d\n",
+		      blocks, alloc_size);
+	return 0;
+}
+
 static int
 skl_allocate_pipe_ddb(struct intel_crtc_state *crtc_state)
 {
@@ -5143,11 +5399,19 @@ static void skl_compute_plane_wm(const struct intel_crtc_state *crtc_state,
 static void
 skl_compute_wm_levels(const struct intel_crtc_state *crtc_state,
 		      const struct skl_wm_params *wm_params,
-		      struct skl_wm_level *levels)
+		      struct skl_plane_wm *plane_wm,
+		      bool yuv)
 {
 	struct drm_i915_private *dev_priv = to_i915(crtc_state->uapi.crtc->dev);
 	int level, max_level = ilk_wm_max_level(dev_priv);
+	/*
+	 * Check which kind of plane is it and based on that calculate
+	 * correspondent WM levels.
+	 */
+	struct skl_wm_level *levels = yuv ? plane_wm->uv_wm : plane_wm->wm;
 	struct skl_wm_level *result_prev = &levels[0];
+	struct skl_wm_level *sagv_wm = yuv ?
+				&plane_wm->uv_sagv_wm0 : &plane_wm->sagv_wm0;
 
 	for (level = 0; level <= max_level; level++) {
 		struct skl_wm_level *result = &levels[level];
@@ -5158,6 +5422,27 @@ skl_compute_wm_levels(const struct intel_crtc_state *crtc_state,
 
 		result_prev = result;
 	}
+	/*
+	 * For Gen12 if it is an L0 we need to also
+	 * consider sagv_block_time when calculating
+	 * L0 watermark - we will need that when making
+	 * a decision whether enable SAGV or not.
+	 * For older gens we agreed to copy L0 value for
+	 * compatibility.
+	 */
+	if ((INTEL_GEN(dev_priv) >= 12)) {
+		u32 latency = dev_priv->wm.skl_latency[0];
+
+		latency += dev_priv->sagv_block_time_us;
+		skl_compute_plane_wm(crtc_state, 0, latency,
+				     wm_params, &levels[0],
+				     sagv_wm);
+		DRM_DEBUG_KMS("%d L0 blocks required for SAGV vs %d for non-SAGV\n",
+			      sagv_wm->min_ddb_alloc, levels[0].min_ddb_alloc);
+	} else {
+		/* Since all members are POD */
+		*sagv_wm = levels[0];
+	}
 }
 
 static void skl_compute_transition_wm(const struct intel_crtc_state *crtc_state,
@@ -5232,7 +5517,7 @@ static int skl_build_plane_wm_single(struct intel_crtc_state *crtc_state,
 	if (ret)
 		return ret;
 
-	skl_compute_wm_levels(crtc_state, &wm_params, wm->wm);
+	skl_compute_wm_levels(crtc_state, &wm_params, wm, false);
 	skl_compute_transition_wm(crtc_state, &wm_params, wm);
 
 	return 0;
@@ -5254,7 +5539,7 @@ static int skl_build_plane_wm_uv(struct intel_crtc_state *crtc_state,
 	if (ret)
 		return ret;
 
-	skl_compute_wm_levels(crtc_state, &wm_params, wm->uv_wm);
+	skl_compute_wm_levels(crtc_state, &wm_params, wm, true);
 
 	return 0;
 }
@@ -5585,9 +5870,24 @@ skl_print_wm_changes(struct intel_atomic_state *state)
 		for_each_intel_plane_on_crtc(&dev_priv->drm, crtc, plane) {
 			enum plane_id plane_id = plane->id;
 			const struct skl_plane_wm *old_wm, *new_wm;
+			const struct skl_wm_level *old_wm_level, *new_wm_level;
+			u16 old_plane_res_l, new_plane_res_l;
+			u8  old_plane_res_b, new_plane_res_b;
+			u16 old_min_ddb_alloc, new_min_ddb_alloc;
 
 			old_wm = &old_pipe_wm->planes[plane_id];
 			new_wm = &new_pipe_wm->planes[plane_id];
+			old_wm_level = skl_plane_wm_level(plane, old_crtc_state, 0, false);
+			new_wm_level = skl_plane_wm_level(plane, new_crtc_state, 0, false);
+
+			old_plane_res_l = old_wm_level->plane_res_l;
+			old_plane_res_b = old_wm_level->plane_res_b;
+
+			new_plane_res_l = new_wm_level->plane_res_l;
+			new_plane_res_b = new_wm_level->plane_res_b;
+
+			old_min_ddb_alloc = old_wm_level->min_ddb_alloc;
+			new_min_ddb_alloc = new_wm_level->min_ddb_alloc;
 
 			if (skl_plane_wm_equals(dev_priv, old_wm, new_wm))
 				continue;
@@ -5611,7 +5911,7 @@ skl_print_wm_changes(struct intel_atomic_state *state)
 				    "[PLANE:%d:%s]   lines %c%3d,%c%3d,%c%3d,%c%3d,%c%3d,%c%3d,%c%3d,%c%3d,%c%3d"
 				      " -> %c%3d,%c%3d,%c%3d,%c%3d,%c%3d,%c%3d,%c%3d,%c%3d,%c%3d\n",
 				    plane->base.base.id, plane->base.name,
-				    enast(old_wm->wm[0].ignore_lines), old_wm->wm[0].plane_res_l,
+				    enast(old_wm->wm[0].ignore_lines), old_plane_res_l,
 				    enast(old_wm->wm[1].ignore_lines), old_wm->wm[1].plane_res_l,
 				    enast(old_wm->wm[2].ignore_lines), old_wm->wm[2].plane_res_l,
 				    enast(old_wm->wm[3].ignore_lines), old_wm->wm[3].plane_res_l,
@@ -5621,7 +5921,7 @@ skl_print_wm_changes(struct intel_atomic_state *state)
 				    enast(old_wm->wm[7].ignore_lines), old_wm->wm[7].plane_res_l,
 				    enast(old_wm->trans_wm.ignore_lines), old_wm->trans_wm.plane_res_l,
 
-				    enast(new_wm->wm[0].ignore_lines), new_wm->wm[0].plane_res_l,
+				    enast(new_wm->wm[0].ignore_lines), new_plane_res_l,
 				    enast(new_wm->wm[1].ignore_lines), new_wm->wm[1].plane_res_l,
 				    enast(new_wm->wm[2].ignore_lines), new_wm->wm[2].plane_res_l,
 				    enast(new_wm->wm[3].ignore_lines), new_wm->wm[3].plane_res_l,
@@ -5635,12 +5935,12 @@ skl_print_wm_changes(struct intel_atomic_state *state)
 				    "[PLANE:%d:%s]  blocks %4d,%4d,%4d,%4d,%4d,%4d,%4d,%4d,%4d"
 				    " -> %4d,%4d,%4d,%4d,%4d,%4d,%4d,%4d,%4d\n",
 				    plane->base.base.id, plane->base.name,
-				    old_wm->wm[0].plane_res_b, old_wm->wm[1].plane_res_b,
+				    old_plane_res_b, old_wm->wm[1].plane_res_b,
 				    old_wm->wm[2].plane_res_b, old_wm->wm[3].plane_res_b,
 				    old_wm->wm[4].plane_res_b, old_wm->wm[5].plane_res_b,
 				    old_wm->wm[6].plane_res_b, old_wm->wm[7].plane_res_b,
 				    old_wm->trans_wm.plane_res_b,
-				    new_wm->wm[0].plane_res_b, new_wm->wm[1].plane_res_b,
+				    new_plane_res_b, new_wm->wm[1].plane_res_b,
 				    new_wm->wm[2].plane_res_b, new_wm->wm[3].plane_res_b,
 				    new_wm->wm[4].plane_res_b, new_wm->wm[5].plane_res_b,
 				    new_wm->wm[6].plane_res_b, new_wm->wm[7].plane_res_b,
@@ -5650,12 +5950,12 @@ skl_print_wm_changes(struct intel_atomic_state *state)
 				    "[PLANE:%d:%s] min_ddb %4d,%4d,%4d,%4d,%4d,%4d,%4d,%4d,%4d"
 				    " -> %4d,%4d,%4d,%4d,%4d,%4d,%4d,%4d,%4d\n",
 				    plane->base.base.id, plane->base.name,
-				    old_wm->wm[0].min_ddb_alloc, old_wm->wm[1].min_ddb_alloc,
+				    old_min_ddb_alloc, old_wm->wm[1].min_ddb_alloc,
 				    old_wm->wm[2].min_ddb_alloc, old_wm->wm[3].min_ddb_alloc,
 				    old_wm->wm[4].min_ddb_alloc, old_wm->wm[5].min_ddb_alloc,
 				    old_wm->wm[6].min_ddb_alloc, old_wm->wm[7].min_ddb_alloc,
 				    old_wm->trans_wm.min_ddb_alloc,
-				    new_wm->wm[0].min_ddb_alloc, new_wm->wm[1].min_ddb_alloc,
+				    new_min_ddb_alloc, new_wm->wm[1].min_ddb_alloc,
 				    new_wm->wm[2].min_ddb_alloc, new_wm->wm[3].min_ddb_alloc,
 				    new_wm->wm[4].min_ddb_alloc, new_wm->wm[5].min_ddb_alloc,
 				    new_wm->wm[6].min_ddb_alloc, new_wm->wm[7].min_ddb_alloc,
@@ -5795,6 +6095,59 @@ static int skl_wm_add_affected_planes(struct intel_atomic_state *state,
 	return 0;
 }
 
+static void tgl_compute_sagv_mask(struct intel_atomic_state *state)
+{
+	struct drm_i915_private *dev_priv = to_i915(state->base.dev);
+	struct intel_crtc *crtc;
+	struct intel_crtc_state *new_crtc_state;
+	struct intel_crtc_state *old_crtc_state;
+	int ret;
+	int i;
+	struct intel_plane *plane;
+	struct intel_bw_state *new_bw_state  = intel_bw_get_state(state);
+
+	if (IS_ERR(new_bw_state)) {
+		WARN(1, "Could not get bw_state\n");
+		return;
+	}
+
+	for_each_oldnew_intel_crtc_in_state(state, crtc, old_crtc_state,
+					    new_crtc_state, i) {
+		int pipe_bit = BIT(crtc->pipe);
+		bool skip = true;
+
+		for_each_intel_plane_on_crtc(&dev_priv->drm, crtc, plane) {
+			enum plane_id plane_id = plane->id;
+			const struct skl_plane_wm *old_wm, *new_wm;
+			bool equal_wm;
+
+			old_wm = &old_crtc_state->wm.skl.optimal.planes[plane_id];
+			new_wm = &new_crtc_state->wm.skl.optimal.planes[plane_id];
+
+			equal_wm = skl_plane_wm_equals(dev_priv, old_wm, new_wm);
+
+			if (!equal_wm) {
+				skip = false;
+				break;
+			}
+		}
+
+		/*
+		 * Check if wm levels are actually the same as for previous
+		 * state, which means we can just skip doing this long check
+		 * and just  copy correspondent bit from previous state.
+		 */
+		if (skip)
+			continue;
+
+		ret = tgl_check_pipe_fits_sagv_wm(new_crtc_state);
+		if (!ret)
+			new_bw_state->pipe_sagv_mask |= pipe_bit;
+		else
+			new_bw_state->pipe_sagv_mask &= ~pipe_bit;
+	}
+}
+
 static int
 skl_compute_wm(struct intel_atomic_state *state)
 {
@@ -5823,6 +6176,13 @@ skl_compute_wm(struct intel_atomic_state *state)
 			return ret;
 	}
 
+	/*
+	 * No active_pipe_changes and no wm/ddb changes means
+	 * we can just skip recalculating SAGV mask.
+	 */
+	if ((state->active_pipe_changes != 0) || state->ddb_state_changed)
+		intel_compute_sagv_mask(state);
+
 	ret = skl_compute_ddb(state);
 	if (ret)
 		return ret;
@@ -5942,6 +6302,9 @@ void skl_pipe_wm_get_hw_state(struct intel_crtc *crtc,
 				val = I915_READ(CUR_WM(pipe, level));
 
 			skl_wm_level_from_reg_val(val, &wm->wm[level]);
+			if (level == 0)
+				memcpy(&wm->sagv_wm0, &wm->wm[level],
+				       sizeof(struct skl_wm_level));
 		}
 
 		if (plane_id != PLANE_CURSOR)
diff --git a/drivers/gpu/drm/i915/intel_pm.h b/drivers/gpu/drm/i915/intel_pm.h
index d60a85421c5a..561a17a5d4e0 100644
--- a/drivers/gpu/drm/i915/intel_pm.h
+++ b/drivers/gpu/drm/i915/intel_pm.h
@@ -41,7 +41,9 @@ void skl_pipe_wm_get_hw_state(struct intel_crtc *crtc,
 			      struct skl_pipe_wm *out);
 void g4x_wm_sanitize(struct drm_i915_private *dev_priv);
 void vlv_wm_sanitize(struct drm_i915_private *dev_priv);
-bool intel_can_enable_sagv(struct intel_atomic_state *state);
+bool intel_can_enable_sagv(struct drm_i915_private *dev_priv);
+bool intel_can_enable_sagv_for_state(struct intel_atomic_state *state);
+bool intel_has_sagv(struct drm_i915_private *dev_priv);
 int intel_enable_sagv(struct drm_i915_private *dev_priv);
 int intel_disable_sagv(struct drm_i915_private *dev_priv);
 bool skl_wm_level_equals(const struct skl_wm_level *l1,
