#include "intel_atomic.h"
 #include "intel_atomic_plane.h"
 #include "intel_bw.h"
+#include "intel_crtc.h"
 #include "intel_de.h"
 #include "intel_display.h"
 #include "intel_display_power.h"
            skl_watermark_ipc_enabled(i915))
                latency += 4;
 
-       if (skl_needs_memory_bw_wa(i915) && wp->x_tiled)
+       if (skl_needs_memory_bw_wa(i915) && wp && wp->x_tiled)
                latency += 15;
 
        return latency;
        return 0;
 }
 
+static bool
+skl_is_vblank_too_short(const struct intel_crtc_state *crtc_state,
+                       int wm0_lines, int latency)
+{
+       const struct drm_display_mode *adjusted_mode =
+               &crtc_state->hw.adjusted_mode;
+
+       /* FIXME missing scaler and DSC pre-fill time */
+       return crtc_state->framestart_delay +
+               intel_usecs_to_scanlines(adjusted_mode, latency) +
+               wm0_lines >
+               adjusted_mode->crtc_vtotal - adjusted_mode->crtc_vblank_start;
+}
+
+static int skl_max_wm0_lines(const struct intel_crtc_state *crtc_state)
+{
+       struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc);
+       enum plane_id plane_id;
+       int wm0_lines = 0;
+
+       for_each_plane_id_on_crtc(crtc, plane_id) {
+               const struct skl_plane_wm *wm = &crtc_state->wm.skl.optimal.planes[plane_id];
+
+               /* FIXME what about !skl_wm_has_lines() platforms? */
+               wm0_lines = max_t(int, wm0_lines, wm->wm[0].lines);
+       }
+
+       return wm0_lines;
+}
+
+static int skl_max_wm_level_for_vblank(struct intel_crtc_state *crtc_state,
+                                      int wm0_lines)
+{
+       struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc);
+       struct drm_i915_private *i915 = to_i915(crtc->base.dev);
+       int level;
+
+       for (level = i915->display.wm.num_levels - 1; level >= 0; level--) {
+               int latency;
+
+               /* FIXME should we care about the latency w/a's? */
+               latency = skl_wm_latency(i915, level, NULL);
+               if (latency == 0)
+                       continue;
+
+               /* FIXME is it correct to use 0 latency for wm0 here? */
+               if (level == 0)
+                       latency = 0;
+
+               if (!skl_is_vblank_too_short(crtc_state, wm0_lines, latency))
+                       return level;
+       }
+
+       return -EINVAL;
+}
+
+static int skl_wm_check_vblank(struct intel_crtc_state *crtc_state)
+{
+       struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc);
+       struct drm_i915_private *i915 = to_i915(crtc->base.dev);
+       int wm0_lines, level;
+
+       if (!crtc_state->hw.active)
+               return 0;
+
+       wm0_lines = skl_max_wm0_lines(crtc_state);
+
+       level = skl_max_wm_level_for_vblank(crtc_state, wm0_lines);
+       if (level < 0)
+               return level;
+
+       /*
+        * FIXME PSR needs to toggle LATENCY_REPORTING_REMOVED_PIPE_*
+        * based on whether we're limited by the vblank duration.
+        *
+        * FIXME also related to skl+ w/a 1136 (also unimplemented as of
+        * now) perhaps?
+        */
+
+       for (level++; level < i915->display.wm.num_levels; level++) {
+               enum plane_id plane_id;
+
+               for_each_plane_id_on_crtc(crtc, plane_id) {
+                       struct skl_plane_wm *wm =
+                               &crtc_state->wm.skl.optimal.planes[plane_id];
+
+                       /*
+                        * FIXME just clear enable or flag the entire
+                        * thing as bad via min_ddb_alloc=U16_MAX?
+                        */
+                       wm->wm[level].enable = false;
+                       wm->uv_wm[level].enable = false;
+               }
+       }
+
+       if (DISPLAY_VER(i915) >= 12 &&
+           i915->display.sagv.block_time_us &&
+           skl_is_vblank_too_short(crtc_state, wm0_lines,
+                                   i915->display.sagv.block_time_us)) {
+               enum plane_id plane_id;
+
+               for_each_plane_id_on_crtc(crtc, plane_id) {
+                       struct skl_plane_wm *wm =
+                               &crtc_state->wm.skl.optimal.planes[plane_id];
+
+                       wm->sagv.wm0.enable = false;
+                       wm->sagv.trans_wm.enable = false;
+               }
+       }
+
+       return 0;
+}
+
 static int skl_build_pipe_wm(struct intel_atomic_state *state,
                             struct intel_crtc *crtc)
 {
 
        crtc_state->wm.skl.optimal = crtc_state->wm.skl.raw;
 
-       return 0;
+       return skl_wm_check_vblank(crtc_state);
 }
 
 static void skl_ddb_entry_write(struct drm_i915_private *i915,