drm/i915: Two stage watermarks for g4x
authorVille Syrjälä <ville.syrjala@linux.intel.com>
Fri, 21 Apr 2017 18:14:29 +0000 (21:14 +0300)
committerVille Syrjälä <ville.syrjala@linux.intel.com>
Wed, 10 May 2017 13:48:31 +0000 (16:48 +0300)
Implement proper two stage watermark programming for g4x. As with
other pre-SKL platforms, the watermark registers aren't double
buffered on g4x. Hence we must sequence the watermark update
carefully around plane updates.

The code is quite heavily modelled on the VLV/CHV code, with some
fairly significant differences due to the different hardware
architecture:
* g4x doesn't use inverted watermark values
* CxSR actually affects the watermarks since it controls memory self
  refresh in addition to the max FIFO mode
* A further HPLL SR mode is possible with higher memory wakeup
  latency
* g4x has FBC2 and so it also has FBC watermarks
* max FIFO mode for primary plane only (cursor is allowed, sprite is not)
* g4x has no manual FIFO repartitioning
* some TLB miss related workarounds are needed for the watermarks

Actually the hardware is quite similar to ILK+ in many ways. The
most visible differences are in the actual watermakr register
layout. ILK revamped that part quite heavily whereas g4x is still
using the layout inherited from earlier platforms.

Note that we didn't previously enable the HPLL SR on g4x. So in order
to not introduce too many functional changes in this patch I've not
actually enabled it here either, even though the code is now fully
ready for it. We'll enable it separately later on.

Signed-off-by: Ville Syrjälä <ville.syrjala@linux.intel.com>
Link: http://patchwork.freedesktop.org/patch/msgid/20170421181432.15216-13-ville.syrjala@linux.intel.com
Reviewed-by: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
drivers/gpu/drm/i915/i915_debugfs.c
drivers/gpu/drm/i915/i915_drv.h
drivers/gpu/drm/i915/intel_display.c
drivers/gpu/drm/i915/intel_drv.h
drivers/gpu/drm/i915/intel_pm.c

index 34785fb..b302c7b 100644 (file)
@@ -3892,6 +3892,8 @@ static void wm_latency_show(struct seq_file *m, const uint16_t wm[8])
                num_levels = 3;
        else if (IS_VALLEYVIEW(dev_priv))
                num_levels = 1;
+       else if (IS_G4X(dev_priv))
+               num_levels = 3;
        else
                num_levels = ilk_wm_max_level(dev_priv) + 1;
 
@@ -3904,8 +3906,10 @@ static void wm_latency_show(struct seq_file *m, const uint16_t wm[8])
                 * - WM1+ latency values in 0.5us units
                 * - latencies are in us on gen9/vlv/chv
                 */
-               if (INTEL_GEN(dev_priv) >= 9 || IS_VALLEYVIEW(dev_priv) ||
-                   IS_CHERRYVIEW(dev_priv))
+               if (INTEL_GEN(dev_priv) >= 9 ||
+                   IS_VALLEYVIEW(dev_priv) ||
+                   IS_CHERRYVIEW(dev_priv) ||
+                   IS_G4X(dev_priv))
                        latency *= 10;
                else if (level > 0)
                        latency *= 5;
@@ -3966,7 +3970,7 @@ static int pri_wm_latency_open(struct inode *inode, struct file *file)
 {
        struct drm_i915_private *dev_priv = inode->i_private;
 
-       if (INTEL_GEN(dev_priv) < 5)
+       if (INTEL_GEN(dev_priv) < 5 && !IS_G4X(dev_priv))
                return -ENODEV;
 
        return single_open(file, pri_wm_latency_show, dev_priv);
@@ -4008,6 +4012,8 @@ static ssize_t wm_latency_write(struct file *file, const char __user *ubuf,
                num_levels = 3;
        else if (IS_VALLEYVIEW(dev_priv))
                num_levels = 1;
+       else if (IS_G4X(dev_priv))
+               num_levels = 3;
        else
                num_levels = ilk_wm_max_level(dev_priv) + 1;
 
index 7e37fb9..74dffbe 100644 (file)
@@ -1654,11 +1654,13 @@ struct ilk_wm_values {
 
 struct g4x_pipe_wm {
        uint16_t plane[I915_MAX_PLANES];
+       uint16_t fbc;
 };
 
 struct g4x_sr_wm {
        uint16_t plane;
        uint16_t cursor;
+       uint16_t fbc;
 };
 
 struct vlv_wm_ddl_values {
@@ -1673,6 +1675,15 @@ struct vlv_wm_values {
        bool cxsr;
 };
 
+struct g4x_wm_values {
+       struct g4x_pipe_wm pipe[2];
+       struct g4x_sr_wm sr;
+       struct g4x_sr_wm hpll;
+       bool cxsr;
+       bool hpll_en;
+       bool fbc_en;
+};
+
 struct skl_ddb_entry {
        uint16_t start, end;    /* in number of blocks, 'end' is exclusive */
 };
@@ -2301,6 +2312,7 @@ struct drm_i915_private {
                        struct ilk_wm_values hw;
                        struct skl_wm_values skl_hw;
                        struct vlv_wm_values vlv;
+                       struct g4x_wm_values g4x;
                };
 
                uint8_t max_level;
index 19a7a1e..99f8dc8 100644 (file)
@@ -5719,6 +5719,8 @@ static void i9xx_set_pll_dividers(struct intel_crtc *crtc)
 static void i9xx_crtc_enable(struct intel_crtc_state *pipe_config,
                             struct drm_atomic_state *old_state)
 {
+       struct intel_atomic_state *old_intel_state =
+               to_intel_atomic_state(old_state);
        struct drm_crtc *crtc = pipe_config->base.crtc;
        struct drm_device *dev = crtc->dev;
        struct drm_i915_private *dev_priv = to_i915(dev);
@@ -5751,7 +5753,11 @@ static void i9xx_crtc_enable(struct intel_crtc_state *pipe_config,
 
        intel_color_load_luts(&pipe_config->base);
 
-       intel_update_watermarks(intel_crtc);
+       if (dev_priv->display.initial_watermarks != NULL)
+               dev_priv->display.initial_watermarks(old_intel_state,
+                                                    intel_crtc->config);
+       else
+               intel_update_watermarks(intel_crtc);
        intel_enable_pipe(intel_crtc);
 
        assert_vblank_disabled(crtc);
@@ -10852,21 +10858,21 @@ int intel_plane_atomic_calc_changes(struct drm_crtc_state *crtc_state,
                         turn_off, turn_on, mode_changed);
 
        if (turn_on) {
-               if (INTEL_GEN(dev_priv) < 5)
+               if (INTEL_GEN(dev_priv) < 5 && !IS_G4X(dev_priv))
                        pipe_config->update_wm_pre = true;
 
                /* must disable cxsr around plane enable/disable */
                if (plane->id != PLANE_CURSOR)
                        pipe_config->disable_cxsr = true;
        } else if (turn_off) {
-               if (INTEL_GEN(dev_priv) < 5)
+               if (INTEL_GEN(dev_priv) < 5 && !IS_G4X(dev_priv))
                        pipe_config->update_wm_post = true;
 
                /* must disable cxsr around plane enable/disable */
                if (plane->id != PLANE_CURSOR)
                        pipe_config->disable_cxsr = true;
        } else if (intel_wm_need_update(&plane->base, plane_state)) {
-               if (INTEL_GEN(dev_priv) < 5) {
+               if (INTEL_GEN(dev_priv) < 5 && !IS_G4X(dev_priv)) {
                        /* FIXME bollocks */
                        pipe_config->update_wm_pre = true;
                        pipe_config->update_wm_post = true;
@@ -11290,7 +11296,8 @@ clear_intel_crtc_state(struct intel_crtc_state *crtc_state)
        shared_dpll = crtc_state->shared_dpll;
        dpll_hw_state = crtc_state->dpll_hw_state;
        force_thru = crtc_state->pch_pfit.force_thru;
-       if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv))
+       if (IS_G4X(dev_priv) ||
+           IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv))
                wm_state = crtc_state->wm;
 
        /* Keep base drm_crtc_state intact, only clear our extended struct */
@@ -11302,7 +11309,8 @@ clear_intel_crtc_state(struct intel_crtc_state *crtc_state)
        crtc_state->shared_dpll = shared_dpll;
        crtc_state->dpll_hw_state = dpll_hw_state;
        crtc_state->pch_pfit.force_thru = force_thru;
-       if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv))
+       if (IS_G4X(dev_priv) ||
+           IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv))
                crtc_state->wm = wm_state;
 }
 
@@ -15527,7 +15535,10 @@ intel_modeset_setup_hw_state(struct drm_device *dev)
                pll->on = false;
        }
 
-       if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) {
+       if (IS_G4X(dev_priv)) {
+               g4x_wm_get_hw_state(dev);
+               g4x_wm_sanitize(dev_priv);
+       } else if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) {
                vlv_wm_get_hw_state(dev);
                vlv_wm_sanitize(dev_priv);
        } else if (IS_GEN9(dev_priv)) {
index d1cdd10..f530df7 100644 (file)
@@ -522,6 +522,22 @@ struct vlv_fifo_state {
        u16 plane[I915_MAX_PLANES];
 };
 
+enum g4x_wm_level {
+       G4X_WM_LEVEL_NORMAL,
+       G4X_WM_LEVEL_SR,
+       G4X_WM_LEVEL_HPLL,
+       NUM_G4X_WM_LEVELS,
+};
+
+struct g4x_wm_state {
+       struct g4x_pipe_wm wm;
+       struct g4x_sr_wm sr;
+       struct g4x_sr_wm hpll;
+       bool cxsr;
+       bool hpll_en;
+       bool fbc_en;
+};
+
 struct intel_crtc_wm_state {
        union {
                struct {
@@ -557,6 +573,15 @@ struct intel_crtc_wm_state {
                        /* display FIFO split */
                        struct vlv_fifo_state fifo_state;
                } vlv;
+
+               struct {
+                       /* "raw" watermarks */
+                       struct g4x_pipe_wm raw[NUM_G4X_WM_LEVELS];
+                       /* intermediate watermarks */
+                       struct g4x_wm_state intermediate;
+                       /* optimal watermarks */
+                       struct g4x_wm_state optimal;
+               } g4x;
        };
 
        /*
@@ -794,6 +819,7 @@ struct intel_crtc {
                union {
                        struct intel_pipe_wm ilk;
                        struct vlv_wm_state vlv;
+                       struct g4x_wm_state g4x;
                } active;
        } wm;
 
@@ -1841,6 +1867,7 @@ void gen6_rps_boost(struct drm_i915_private *dev_priv,
                    struct intel_rps_client *rps,
                    unsigned long submitted);
 void intel_queue_rps_boost_for_request(struct drm_i915_gem_request *req);
+void g4x_wm_get_hw_state(struct drm_device *dev);
 void vlv_wm_get_hw_state(struct drm_device *dev);
 void ilk_wm_get_hw_state(struct drm_device *dev);
 void skl_wm_get_hw_state(struct drm_device *dev);
@@ -1848,6 +1875,7 @@ void skl_ddb_get_hw_state(struct drm_i915_private *dev_priv,
                          struct skl_ddb_allocation *ddb /* out */);
 void skl_pipe_wm_get_hw_state(struct drm_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 drm_atomic_state *state);
 int intel_enable_sagv(struct drm_i915_private *dev_priv);
index 61b6799..9b0a6a4 100644 (file)
@@ -429,7 +429,10 @@ bool intel_set_memory_cxsr(struct drm_i915_private *dev_priv, bool enable)
 
        mutex_lock(&dev_priv->wm.wm_mutex);
        ret = _intel_set_memory_cxsr(dev_priv, enable);
-       dev_priv->wm.vlv.cxsr = enable;
+       if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv))
+               dev_priv->wm.vlv.cxsr = enable;
+       else if (IS_G4X(dev_priv))
+               dev_priv->wm.g4x.cxsr = enable;
        mutex_unlock(&dev_priv->wm.wm_mutex);
 
        return ret;
@@ -568,20 +571,6 @@ static const struct intel_watermark_params pineview_cursor_hplloff_wm = {
        .guard_size = PINEVIEW_CURSOR_GUARD_WM,
        .cacheline_size = PINEVIEW_FIFO_LINE_SIZE,
 };
-static const struct intel_watermark_params g4x_wm_info = {
-       .fifo_size = G4X_FIFO_SIZE,
-       .max_wm = G4X_MAX_WM,
-       .default_wm = G4X_MAX_WM,
-       .guard_size = 2,
-       .cacheline_size = G4X_FIFO_LINE_SIZE,
-};
-static const struct intel_watermark_params g4x_cursor_wm_info = {
-       .fifo_size = I965_CURSOR_FIFO,
-       .max_wm = I965_CURSOR_MAX_WM,
-       .default_wm = I965_CURSOR_DFT_WM,
-       .guard_size = 2,
-       .cacheline_size = G4X_FIFO_LINE_SIZE,
-};
 static const struct intel_watermark_params i965_cursor_wm_info = {
        .fifo_size = I965_CURSOR_FIFO,
        .max_wm = I965_CURSOR_MAX_WM,
@@ -780,6 +769,16 @@ static unsigned int intel_calculate_wm(int pixel_rate,
        return wm_size;
 }
 
+static bool is_disabling(int old, int new, int threshold)
+{
+       return old >= threshold && new < threshold;
+}
+
+static bool is_enabling(int old, int new, int threshold)
+{
+       return old < threshold && new >= threshold;
+}
+
 static int intel_wm_num_levels(struct drm_i915_private *dev_priv)
 {
        return dev_priv->wm.max_level + 1;
@@ -911,138 +910,28 @@ static int g4x_tlb_miss_wa(int fifo_size, int width, int cpp)
        return max(0, tlb_miss);
 }
 
-static bool g4x_compute_wm0(struct drm_i915_private *dev_priv,
-                           int plane,
-                           const struct intel_watermark_params *display,
-                           int display_latency_ns,
-                           const struct intel_watermark_params *cursor,
-                           int cursor_latency_ns,
-                           int *plane_wm,
-                           int *cursor_wm)
-{
-       struct intel_crtc *crtc;
-       const struct drm_display_mode *adjusted_mode;
-       const struct drm_framebuffer *fb;
-       int htotal, plane_width, cursor_width, clock, cpp;
-       int entries;
-
-       crtc = intel_get_crtc_for_plane(dev_priv, plane);
-       if (!intel_crtc_active(crtc)) {
-               *cursor_wm = cursor->guard_size;
-               *plane_wm = display->guard_size;
-               return false;
-       }
-
-       adjusted_mode = &crtc->config->base.adjusted_mode;
-       fb = crtc->base.primary->state->fb;
-       clock = adjusted_mode->crtc_clock;
-       htotal = adjusted_mode->crtc_htotal;
-       plane_width = crtc->config->pipe_src_w;
-       cursor_width = crtc->base.cursor->state->crtc_w;
-       cpp = fb->format->cpp[0];
-
-       /* Use the small buffer method to calculate plane watermark */
-       entries = intel_wm_method1(clock, cpp, display_latency_ns / 100);
-       entries += g4x_tlb_miss_wa(display->fifo_size, plane_width, cpp);
-       entries = DIV_ROUND_UP(entries, display->cacheline_size);
-       *plane_wm = entries + display->guard_size;
-       if (*plane_wm > (int)display->max_wm)
-               *plane_wm = display->max_wm;
-
-       /* Use the large buffer method to calculate cursor watermark */
-       entries = intel_wm_method2(clock, htotal, cursor_width, 4,
-                                  cursor_latency_ns / 100);
-       entries += g4x_tlb_miss_wa(cursor->fifo_size, cursor_width, 4);
-       entries = DIV_ROUND_UP(entries, cursor->cacheline_size);
-       *cursor_wm = entries + cursor->guard_size;
-       if (*cursor_wm > (int)cursor->max_wm)
-               *cursor_wm = (int)cursor->max_wm;
-
-       return true;
-}
-
-/*
- * Check the wm result.
- *
- * If any calculated watermark values is larger than the maximum value that
- * can be programmed into the associated watermark register, that watermark
- * must be disabled.
- */
-static bool g4x_check_srwm(struct drm_i915_private *dev_priv,
-                          int display_wm, int cursor_wm,
-                          const struct intel_watermark_params *display,
-                          const struct intel_watermark_params *cursor)
-{
-       DRM_DEBUG_KMS("SR watermark: display plane %d, cursor %d\n",
-                     display_wm, cursor_wm);
-
-       if (display_wm > display->max_wm) {
-               DRM_DEBUG_KMS("display watermark is too large(%d/%u), disabling\n",
-                             display_wm, display->max_wm);
-               return false;
-       }
-
-       if (cursor_wm > cursor->max_wm) {
-               DRM_DEBUG_KMS("cursor watermark is too large(%d/%u), disabling\n",
-                             cursor_wm, cursor->max_wm);
-               return false;
-       }
-
-       if (!(display_wm || cursor_wm)) {
-               DRM_DEBUG_KMS("SR latency is 0, disabling\n");
-               return false;
-       }
-
-       return true;
-}
-
-static bool g4x_compute_srwm(struct drm_i915_private *dev_priv,
-                            int plane,
-                            int latency_ns,
-                            const struct intel_watermark_params *display,
-                            const struct intel_watermark_params *cursor,
-                            int *display_wm, int *cursor_wm)
+static void g4x_write_wm_values(struct drm_i915_private *dev_priv,
+                               const struct g4x_wm_values *wm)
 {
-       struct intel_crtc *crtc;
-       const struct drm_display_mode *adjusted_mode;
-       const struct drm_framebuffer *fb;
-       int plane_width, cursor_width, htotal, cpp, clock;
-       int small, large;
-       int entries;
-
-       if (!latency_ns) {
-               *display_wm = *cursor_wm = 0;
-               return false;
-       }
-
-       crtc = intel_get_crtc_for_plane(dev_priv, plane);
-       adjusted_mode = &crtc->config->base.adjusted_mode;
-       fb = crtc->base.primary->state->fb;
-       clock = adjusted_mode->crtc_clock;
-       htotal = adjusted_mode->crtc_htotal;
-       plane_width = crtc->config->pipe_src_w;
-       cursor_width = crtc->base.cursor->state->crtc_w;
-       cpp = fb->format->cpp[0];
-
-       /* Use the minimum of the small and large buffer method for primary */
-       small = intel_wm_method1(clock, cpp, latency_ns / 100);
-       large = intel_wm_method2(clock, htotal, plane_width, cpp,
-                                latency_ns / 100);
-       entries = min(small, large);
-       entries += g4x_tlb_miss_wa(display->fifo_size, plane_width, cpp);
-       entries = DIV_ROUND_UP(entries, display->cacheline_size);
-       *display_wm = entries + display->guard_size;
-
-       /* calculate the self-refresh watermark for display cursor */
-       entries = intel_wm_method2(clock, htotal, cursor_width, 4,
-                                  latency_ns / 100);
-       entries += g4x_tlb_miss_wa(cursor->fifo_size, cursor_width, 4);
-       entries = DIV_ROUND_UP(entries, cursor->cacheline_size);
-       *cursor_wm = entries + cursor->guard_size;
+       I915_WRITE(DSPFW1,
+                  FW_WM(wm->sr.plane, SR) |
+                  FW_WM(wm->pipe[PIPE_B].plane[PLANE_CURSOR], CURSORB) |
+                  FW_WM(wm->pipe[PIPE_B].plane[PLANE_PRIMARY], PLANEB) |
+                  FW_WM(wm->pipe[PIPE_A].plane[PLANE_PRIMARY], PLANEA));
+       I915_WRITE(DSPFW2,
+                  (wm->fbc_en ? DSPFW_FBC_SR_EN : 0) |
+                  FW_WM(wm->sr.fbc, FBC_SR) |
+                  FW_WM(wm->hpll.fbc, FBC_HPLL_SR) |
+                  FW_WM(wm->pipe[PIPE_B].plane[PLANE_SPRITE0], SPRITEB) |
+                  FW_WM(wm->pipe[PIPE_A].plane[PLANE_CURSOR], CURSORA) |
+                  FW_WM(wm->pipe[PIPE_A].plane[PLANE_SPRITE0], SPRITEA));
+       I915_WRITE(DSPFW3,
+                  (wm->hpll_en ? DSPFW_HPLL_SR_EN : 0) |
+                  FW_WM(wm->sr.cursor, CURSOR_SR) |
+                  FW_WM(wm->hpll.cursor, HPLL_CURSOR) |
+                  FW_WM(wm->hpll.plane, HPLL_SR));
 
-       return g4x_check_srwm(dev_priv,
-                             *display_wm, *cursor_wm,
-                             display, cursor);
+       POSTING_READ(DSPFW1);
 }
 
 #define FW_WM_VLV(value, plane) \
@@ -1126,6 +1015,523 @@ static void vlv_write_wm_values(struct drm_i915_private *dev_priv,
 
 #undef FW_WM_VLV
 
+static void g4x_setup_wm_latency(struct drm_i915_private *dev_priv)
+{
+       /* all latencies in usec */
+       dev_priv->wm.pri_latency[G4X_WM_LEVEL_NORMAL] = 5;
+       dev_priv->wm.pri_latency[G4X_WM_LEVEL_SR] = 12;
+
+       dev_priv->wm.max_level = G4X_WM_LEVEL_SR;
+}
+
+static int g4x_plane_fifo_size(enum plane_id plane_id, int level)
+{
+       /*
+        * DSPCNTR[13] supposedly controls whether the
+        * primary plane can use the FIFO space otherwise
+        * reserved for the sprite plane. It's not 100% clear
+        * what the actual FIFO size is, but it looks like we
+        * can happily set both primary and sprite watermarks
+        * up to 127 cachelines. So that would seem to mean
+        * that either DSPCNTR[13] doesn't do anything, or that
+        * the total FIFO is >= 256 cachelines in size. Either
+        * way, we don't seem to have to worry about this
+        * repartitioning as the maximum watermark value the
+        * register can hold for each plane is lower than the
+        * minimum FIFO size.
+        */
+       switch (plane_id) {
+       case PLANE_CURSOR:
+               return 63;
+       case PLANE_PRIMARY:
+               return level == G4X_WM_LEVEL_NORMAL ? 127 : 511;
+       case PLANE_SPRITE0:
+               return level == G4X_WM_LEVEL_NORMAL ? 127 : 0;
+       default:
+               MISSING_CASE(plane_id);
+               return 0;
+       }
+}
+
+static int g4x_fbc_fifo_size(int level)
+{
+       switch (level) {
+       case G4X_WM_LEVEL_SR:
+               return 7;
+       case G4X_WM_LEVEL_HPLL:
+               return 15;
+       default:
+               MISSING_CASE(level);
+               return 0;
+       }
+}
+
+static uint16_t g4x_compute_wm(const struct intel_crtc_state *crtc_state,
+                              const struct intel_plane_state *plane_state,
+                              int level)
+{
+       struct intel_plane *plane = to_intel_plane(plane_state->base.plane);
+       struct drm_i915_private *dev_priv = to_i915(plane->base.dev);
+       const struct drm_display_mode *adjusted_mode =
+               &crtc_state->base.adjusted_mode;
+       int clock, htotal, cpp, width, wm;
+       int latency = dev_priv->wm.pri_latency[level] * 10;
+
+       if (latency == 0)
+               return USHRT_MAX;
+
+       if (!intel_wm_plane_visible(crtc_state, plane_state))
+               return 0;
+
+       /*
+        * Not 100% sure which way ELK should go here as the
+        * spec only says CL/CTG should assume 32bpp and BW
+        * doesn't need to. But as these things followed the
+        * mobile vs. desktop lines on gen3 as well, let's
+        * assume ELK doesn't need this.
+        *
+        * The spec also fails to list such a restriction for
+        * the HPLL watermark, which seems a little strange.
+        * Let's use 32bpp for the HPLL watermark as well.
+        */
+       if (IS_GM45(dev_priv) && plane->id == PLANE_PRIMARY &&
+           level != G4X_WM_LEVEL_NORMAL)
+               cpp = 4;
+       else
+               cpp = plane_state->base.fb->format->cpp[0];
+
+       clock = adjusted_mode->crtc_clock;
+       htotal = adjusted_mode->crtc_htotal;
+
+       if (plane->id == PLANE_CURSOR)
+               width = plane_state->base.crtc_w;
+       else
+               width = drm_rect_width(&plane_state->base.dst);
+
+       if (plane->id == PLANE_CURSOR) {
+               wm = intel_wm_method2(clock, htotal, width, cpp, latency);
+       } else if (plane->id == PLANE_PRIMARY &&
+                  level == G4X_WM_LEVEL_NORMAL) {
+               wm = intel_wm_method1(clock, cpp, latency);
+       } else {
+               int small, large;
+
+               small = intel_wm_method1(clock, cpp, latency);
+               large = intel_wm_method2(clock, htotal, width, cpp, latency);
+
+               wm = min(small, large);
+       }
+
+       wm += g4x_tlb_miss_wa(g4x_plane_fifo_size(plane->id, level),
+                             width, cpp);
+
+       wm = DIV_ROUND_UP(wm, 64) + 2;
+
+       return min_t(int, wm, USHRT_MAX);
+}
+
+static bool g4x_raw_plane_wm_set(struct intel_crtc_state *crtc_state,
+                                int level, enum plane_id plane_id, u16 value)
+{
+       struct drm_i915_private *dev_priv = to_i915(crtc_state->base.crtc->dev);
+       bool dirty = false;
+
+       for (; level < intel_wm_num_levels(dev_priv); level++) {
+               struct g4x_pipe_wm *raw = &crtc_state->wm.g4x.raw[level];
+
+               dirty |= raw->plane[plane_id] != value;
+               raw->plane[plane_id] = value;
+       }
+
+       return dirty;
+}
+
+static bool g4x_raw_fbc_wm_set(struct intel_crtc_state *crtc_state,
+                              int level, u16 value)
+{
+       struct drm_i915_private *dev_priv = to_i915(crtc_state->base.crtc->dev);
+       bool dirty = false;
+
+       /* NORMAL level doesn't have an FBC watermark */
+       level = max(level, G4X_WM_LEVEL_SR);
+
+       for (; level < intel_wm_num_levels(dev_priv); level++) {
+               struct g4x_pipe_wm *raw = &crtc_state->wm.g4x.raw[level];
+
+               dirty |= raw->fbc != value;
+               raw->fbc = value;
+       }
+
+       return dirty;
+}
+
+static uint32_t ilk_compute_fbc_wm(const struct intel_crtc_state *cstate,
+                                  const struct intel_plane_state *pstate,
+                                  uint32_t pri_val);
+
+static bool g4x_raw_plane_wm_compute(struct intel_crtc_state *crtc_state,
+                                    const struct intel_plane_state *plane_state)
+{
+       struct intel_plane *plane = to_intel_plane(plane_state->base.plane);
+       int num_levels = intel_wm_num_levels(to_i915(plane->base.dev));
+       enum plane_id plane_id = plane->id;
+       bool dirty = false;
+       int level;
+
+       if (!intel_wm_plane_visible(crtc_state, plane_state)) {
+               dirty |= g4x_raw_plane_wm_set(crtc_state, 0, plane_id, 0);
+               if (plane_id == PLANE_PRIMARY)
+                       dirty |= g4x_raw_fbc_wm_set(crtc_state, 0, 0);
+               goto out;
+       }
+
+       for (level = 0; level < num_levels; level++) {
+               struct g4x_pipe_wm *raw = &crtc_state->wm.g4x.raw[level];
+               int wm, max_wm;
+
+               wm = g4x_compute_wm(crtc_state, plane_state, level);
+               max_wm = g4x_plane_fifo_size(plane_id, level);
+
+               if (wm > max_wm)
+                       break;
+
+               dirty |= raw->plane[plane_id] != wm;
+               raw->plane[plane_id] = wm;
+
+               if (plane_id != PLANE_PRIMARY ||
+                   level == G4X_WM_LEVEL_NORMAL)
+                       continue;
+
+               wm = ilk_compute_fbc_wm(crtc_state, plane_state,
+                                       raw->plane[plane_id]);
+               max_wm = g4x_fbc_fifo_size(level);
+
+               /*
+                * FBC wm is not mandatory as we
+                * can always just disable its use.
+                */
+               if (wm > max_wm)
+                       wm = USHRT_MAX;
+
+               dirty |= raw->fbc != wm;
+               raw->fbc = wm;
+       }
+
+       /* mark watermarks as invalid */
+       dirty |= g4x_raw_plane_wm_set(crtc_state, level, plane_id, USHRT_MAX);
+
+       if (plane_id == PLANE_PRIMARY)
+               dirty |= g4x_raw_fbc_wm_set(crtc_state, level, USHRT_MAX);
+
+ out:
+       if (dirty) {
+               DRM_DEBUG_KMS("%s watermarks: normal=%d, SR=%d, HPLL=%d\n",
+                             plane->base.name,
+                             crtc_state->wm.g4x.raw[G4X_WM_LEVEL_NORMAL].plane[plane_id],
+                             crtc_state->wm.g4x.raw[G4X_WM_LEVEL_SR].plane[plane_id],
+                             crtc_state->wm.g4x.raw[G4X_WM_LEVEL_HPLL].plane[plane_id]);
+
+               if (plane_id == PLANE_PRIMARY)
+                       DRM_DEBUG_KMS("FBC watermarks: SR=%d, HPLL=%d\n",
+                                     crtc_state->wm.g4x.raw[G4X_WM_LEVEL_SR].fbc,
+                                     crtc_state->wm.g4x.raw[G4X_WM_LEVEL_HPLL].fbc);
+       }
+
+       return dirty;
+}
+
+static bool g4x_raw_plane_wm_is_valid(const struct intel_crtc_state *crtc_state,
+                                     enum plane_id plane_id, int level)
+{
+       const struct g4x_pipe_wm *raw = &crtc_state->wm.g4x.raw[level];
+
+       return raw->plane[plane_id] <= g4x_plane_fifo_size(plane_id, level);
+}
+
+static bool g4x_raw_crtc_wm_is_valid(const struct intel_crtc_state *crtc_state,
+                                    int level)
+{
+       struct drm_i915_private *dev_priv = to_i915(crtc_state->base.crtc->dev);
+
+       if (level > dev_priv->wm.max_level)
+               return false;
+
+       return g4x_raw_plane_wm_is_valid(crtc_state, PLANE_PRIMARY, level) &&
+               g4x_raw_plane_wm_is_valid(crtc_state, PLANE_SPRITE0, level) &&
+               g4x_raw_plane_wm_is_valid(crtc_state, PLANE_CURSOR, level);
+}
+
+/* mark all levels starting from 'level' as invalid */
+static void g4x_invalidate_wms(struct intel_crtc *crtc,
+                              struct g4x_wm_state *wm_state, int level)
+{
+       if (level <= G4X_WM_LEVEL_NORMAL) {
+               enum plane_id plane_id;
+
+               for_each_plane_id_on_crtc(crtc, plane_id)
+                       wm_state->wm.plane[plane_id] = USHRT_MAX;
+       }
+
+       if (level <= G4X_WM_LEVEL_SR) {
+               wm_state->cxsr = false;
+               wm_state->sr.cursor = USHRT_MAX;
+               wm_state->sr.plane = USHRT_MAX;
+               wm_state->sr.fbc = USHRT_MAX;
+       }
+
+       if (level <= G4X_WM_LEVEL_HPLL) {
+               wm_state->hpll_en = false;
+               wm_state->hpll.cursor = USHRT_MAX;
+               wm_state->hpll.plane = USHRT_MAX;
+               wm_state->hpll.fbc = USHRT_MAX;
+       }
+}
+
+static int g4x_compute_pipe_wm(struct intel_crtc_state *crtc_state)
+{
+       struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc);
+       struct intel_atomic_state *state =
+               to_intel_atomic_state(crtc_state->base.state);
+       struct g4x_wm_state *wm_state = &crtc_state->wm.g4x.optimal;
+       int num_active_planes = hweight32(crtc_state->active_planes &
+                                         ~BIT(PLANE_CURSOR));
+       const struct g4x_pipe_wm *raw;
+       struct intel_plane_state *plane_state;
+       struct intel_plane *plane;
+       enum plane_id plane_id;
+       int i, level;
+       unsigned int dirty = 0;
+
+       for_each_intel_plane_in_state(state, plane, plane_state, i) {
+               const struct intel_plane_state *old_plane_state =
+                       to_intel_plane_state(plane->base.state);
+
+               if (plane_state->base.crtc != &crtc->base &&
+                   old_plane_state->base.crtc != &crtc->base)
+                       continue;
+
+               if (g4x_raw_plane_wm_compute(crtc_state, plane_state))
+                       dirty |= BIT(plane->id);
+       }
+
+       if (!dirty)
+               return 0;
+
+       level = G4X_WM_LEVEL_NORMAL;
+       if (!g4x_raw_crtc_wm_is_valid(crtc_state, level))
+               goto out;
+
+       raw = &crtc_state->wm.g4x.raw[level];
+       for_each_plane_id_on_crtc(crtc, plane_id)
+               wm_state->wm.plane[plane_id] = raw->plane[plane_id];
+
+       level = G4X_WM_LEVEL_SR;
+
+       if (!g4x_raw_crtc_wm_is_valid(crtc_state, level))
+               goto out;
+
+       raw = &crtc_state->wm.g4x.raw[level];
+       wm_state->sr.plane = raw->plane[PLANE_PRIMARY];
+       wm_state->sr.cursor = raw->plane[PLANE_CURSOR];
+       wm_state->sr.fbc = raw->fbc;
+
+       wm_state->cxsr = num_active_planes == BIT(PLANE_PRIMARY);
+
+       level = G4X_WM_LEVEL_HPLL;
+
+       if (!g4x_raw_crtc_wm_is_valid(crtc_state, level))
+               goto out;
+
+       raw = &crtc_state->wm.g4x.raw[level];
+       wm_state->hpll.plane = raw->plane[PLANE_PRIMARY];
+       wm_state->hpll.cursor = raw->plane[PLANE_CURSOR];
+       wm_state->hpll.fbc = raw->fbc;
+
+       wm_state->hpll_en = wm_state->cxsr;
+
+       level++;
+
+ out:
+       if (level == G4X_WM_LEVEL_NORMAL)
+               return -EINVAL;
+
+       /* invalidate the higher levels */
+       g4x_invalidate_wms(crtc, wm_state, level);
+
+       /*
+        * Determine if the FBC watermark(s) can be used. IF
+        * this isn't the case we prefer to disable the FBC
+        ( watermark(s) rather than disable the SR/HPLL
+        * level(s) entirely.
+        */
+       wm_state->fbc_en = level > G4X_WM_LEVEL_NORMAL;
+
+       if (level >= G4X_WM_LEVEL_SR &&
+           wm_state->sr.fbc > g4x_fbc_fifo_size(G4X_WM_LEVEL_SR))
+               wm_state->fbc_en = false;
+       else if (level >= G4X_WM_LEVEL_HPLL &&
+                wm_state->hpll.fbc > g4x_fbc_fifo_size(G4X_WM_LEVEL_HPLL))
+               wm_state->fbc_en = false;
+
+       return 0;
+}
+
+static int g4x_compute_intermediate_wm(struct drm_device *dev,
+                                      struct intel_crtc *crtc,
+                                      struct intel_crtc_state *crtc_state)
+{
+       struct g4x_wm_state *intermediate = &crtc_state->wm.g4x.intermediate;
+       const struct g4x_wm_state *optimal = &crtc_state->wm.g4x.optimal;
+       const struct g4x_wm_state *active = &crtc->wm.active.g4x;
+       enum plane_id plane_id;
+
+       intermediate->cxsr = optimal->cxsr && active->cxsr &&
+               !crtc_state->disable_cxsr;
+       intermediate->hpll_en = optimal->hpll_en && active->hpll_en &&
+               !crtc_state->disable_cxsr;
+       intermediate->fbc_en = optimal->fbc_en && active->fbc_en;
+
+       for_each_plane_id_on_crtc(crtc, plane_id) {
+               intermediate->wm.plane[plane_id] =
+                       max(optimal->wm.plane[plane_id],
+                           active->wm.plane[plane_id]);
+
+               WARN_ON(intermediate->wm.plane[plane_id] >
+                       g4x_plane_fifo_size(plane_id, G4X_WM_LEVEL_NORMAL));
+       }
+
+       intermediate->sr.plane = max(optimal->sr.plane,
+                                    active->sr.plane);
+       intermediate->sr.cursor = max(optimal->sr.cursor,
+                                     active->sr.cursor);
+       intermediate->sr.fbc = max(optimal->sr.fbc,
+                                  active->sr.fbc);
+
+       intermediate->hpll.plane = max(optimal->hpll.plane,
+                                      active->hpll.plane);
+       intermediate->hpll.cursor = max(optimal->hpll.cursor,
+                                       active->hpll.cursor);
+       intermediate->hpll.fbc = max(optimal->hpll.fbc,
+                                    active->hpll.fbc);
+
+       WARN_ON((intermediate->sr.plane >
+                g4x_plane_fifo_size(PLANE_PRIMARY, G4X_WM_LEVEL_SR) ||
+                intermediate->sr.cursor >
+                g4x_plane_fifo_size(PLANE_CURSOR, G4X_WM_LEVEL_SR)) &&
+               intermediate->cxsr);
+       WARN_ON((intermediate->sr.plane >
+                g4x_plane_fifo_size(PLANE_PRIMARY, G4X_WM_LEVEL_HPLL) ||
+                intermediate->sr.cursor >
+                g4x_plane_fifo_size(PLANE_CURSOR, G4X_WM_LEVEL_HPLL)) &&
+               intermediate->hpll_en);
+
+       WARN_ON(intermediate->sr.fbc > g4x_fbc_fifo_size(1) &&
+               intermediate->fbc_en && intermediate->cxsr);
+       WARN_ON(intermediate->hpll.fbc > g4x_fbc_fifo_size(2) &&
+               intermediate->fbc_en && intermediate->hpll_en);
+
+       /*
+        * If our intermediate WM are identical to the final WM, then we can
+        * omit the post-vblank programming; only update if it's different.
+        */
+       if (memcmp(intermediate, optimal, sizeof(*intermediate)) != 0)
+               crtc_state->wm.need_postvbl_update = true;
+
+       return 0;
+}
+
+static void g4x_merge_wm(struct drm_i915_private *dev_priv,
+                        struct g4x_wm_values *wm)
+{
+       struct intel_crtc *crtc;
+       int num_active_crtcs = 0;
+
+       wm->cxsr = true;
+       wm->hpll_en = true;
+       wm->fbc_en = true;
+
+       for_each_intel_crtc(&dev_priv->drm, crtc) {
+               const struct g4x_wm_state *wm_state = &crtc->wm.active.g4x;
+
+               if (!crtc->active)
+                       continue;
+
+               if (!wm_state->cxsr)
+                       wm->cxsr = false;
+               if (!wm_state->hpll_en)
+                       wm->hpll_en = false;
+               if (!wm_state->fbc_en)
+                       wm->fbc_en = false;
+
+               num_active_crtcs++;
+       }
+
+       if (num_active_crtcs != 1) {
+               wm->cxsr = false;
+               wm->hpll_en = false;
+               wm->fbc_en = false;
+       }
+
+       for_each_intel_crtc(&dev_priv->drm, crtc) {
+               const struct g4x_wm_state *wm_state = &crtc->wm.active.g4x;
+               enum pipe pipe = crtc->pipe;
+
+               wm->pipe[pipe] = wm_state->wm;
+               if (crtc->active && wm->cxsr)
+                       wm->sr = wm_state->sr;
+               if (crtc->active && wm->hpll_en)
+                       wm->hpll = wm_state->hpll;
+       }
+}
+
+static void g4x_program_watermarks(struct drm_i915_private *dev_priv)
+{
+       struct g4x_wm_values *old_wm = &dev_priv->wm.g4x;
+       struct g4x_wm_values new_wm = {};
+
+       g4x_merge_wm(dev_priv, &new_wm);
+
+       if (memcmp(old_wm, &new_wm, sizeof(new_wm)) == 0)
+               return;
+
+       if (is_disabling(old_wm->cxsr, new_wm.cxsr, true))
+               _intel_set_memory_cxsr(dev_priv, false);
+
+       g4x_write_wm_values(dev_priv, &new_wm);
+
+       if (is_enabling(old_wm->cxsr, new_wm.cxsr, true))
+               _intel_set_memory_cxsr(dev_priv, true);
+
+       *old_wm = new_wm;
+}
+
+static void g4x_initial_watermarks(struct intel_atomic_state *state,
+                                  struct intel_crtc_state *crtc_state)
+{
+       struct drm_i915_private *dev_priv = to_i915(crtc_state->base.crtc->dev);
+       struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc);
+
+       mutex_lock(&dev_priv->wm.wm_mutex);
+       crtc->wm.active.g4x = crtc_state->wm.g4x.intermediate;
+       g4x_program_watermarks(dev_priv);
+       mutex_unlock(&dev_priv->wm.wm_mutex);
+}
+
+static void g4x_optimize_watermarks(struct intel_atomic_state *state,
+                                   struct intel_crtc_state *crtc_state)
+{
+       struct drm_i915_private *dev_priv = to_i915(crtc_state->base.crtc->dev);
+       struct intel_crtc *intel_crtc = to_intel_crtc(crtc_state->base.crtc);
+
+       if (!crtc_state->wm.need_postvbl_update)
+               return;
+
+       mutex_lock(&dev_priv->wm.wm_mutex);
+       intel_crtc->wm.active.g4x = crtc_state->wm.g4x.optimal;
+       g4x_program_watermarks(dev_priv);
+       mutex_unlock(&dev_priv->wm.wm_mutex);
+}
+
 /* latency must be in 0.1us units. */
 static unsigned int vlv_wm_method2(unsigned int pixel_rate,
                                   unsigned int htotal,
@@ -1673,16 +2079,6 @@ static void vlv_merge_wm(struct drm_i915_private *dev_priv,
        }
 }
 
-static bool is_disabling(int old, int new, int threshold)
-{
-       return old >= threshold && new < threshold;
-}
-
-static bool is_enabling(int old, int new, int threshold)
-{
-       return old < threshold && new >= threshold;
-}
-
 static void vlv_program_watermarks(struct drm_i915_private *dev_priv)
 {
        struct vlv_wm_values *old_wm = &dev_priv->wm.vlv;
@@ -1743,65 +2139,6 @@ static void vlv_optimize_watermarks(struct intel_atomic_state *state,
        mutex_unlock(&dev_priv->wm.wm_mutex);
 }
 
-#define single_plane_enabled(mask) is_power_of_2(mask)
-
-static void g4x_update_wm(struct intel_crtc *crtc)
-{
-       struct drm_i915_private *dev_priv = to_i915(crtc->base.dev);
-       static const int sr_latency_ns = 12000;
-       int planea_wm, planeb_wm, cursora_wm, cursorb_wm;
-       int plane_sr, cursor_sr;
-       unsigned int enabled = 0;
-       bool cxsr_enabled;
-
-       if (g4x_compute_wm0(dev_priv, PIPE_A,
-                           &g4x_wm_info, pessimal_latency_ns,
-                           &g4x_cursor_wm_info, pessimal_latency_ns,
-                           &planea_wm, &cursora_wm))
-               enabled |= 1 << PIPE_A;
-
-       if (g4x_compute_wm0(dev_priv, PIPE_B,
-                           &g4x_wm_info, pessimal_latency_ns,
-                           &g4x_cursor_wm_info, pessimal_latency_ns,
-                           &planeb_wm, &cursorb_wm))
-               enabled |= 1 << PIPE_B;
-
-       if (single_plane_enabled(enabled) &&
-           g4x_compute_srwm(dev_priv, ffs(enabled) - 1,
-                            sr_latency_ns,
-                            &g4x_wm_info,
-                            &g4x_cursor_wm_info,
-                            &plane_sr, &cursor_sr)) {
-               cxsr_enabled = true;
-       } else {
-               cxsr_enabled = false;
-               intel_set_memory_cxsr(dev_priv, false);
-               plane_sr = cursor_sr = 0;
-       }
-
-       DRM_DEBUG_KMS("Setting FIFO watermarks - A: plane=%d, cursor=%d, "
-                     "B: plane=%d, cursor=%d, SR: plane=%d, cursor=%d\n",
-                     planea_wm, cursora_wm,
-                     planeb_wm, cursorb_wm,
-                     plane_sr, cursor_sr);
-
-       I915_WRITE(DSPFW1,
-                  FW_WM(plane_sr, SR) |
-                  FW_WM(cursorb_wm, CURSORB) |
-                  FW_WM(planeb_wm, PLANEB) |
-                  FW_WM(planea_wm, PLANEA));
-       I915_WRITE(DSPFW2,
-                  (I915_READ(DSPFW2) & ~DSPFW_CURSORA_MASK) |
-                  FW_WM(cursora_wm, CURSORA));
-       /* HPLL off in SR has some issues on G4x... disable it */
-       I915_WRITE(DSPFW3,
-                  (I915_READ(DSPFW3) & ~(DSPFW_HPLL_SR_EN | DSPFW_CURSOR_SR_MASK)) |
-                  FW_WM(cursor_sr, CURSOR_SR));
-
-       if (cxsr_enabled)
-               intel_set_memory_cxsr(dev_priv, true);
-}
-
 static void i965_update_wm(struct intel_crtc *unused_crtc)
 {
        struct drm_i915_private *dev_priv = to_i915(unused_crtc->base.dev);
@@ -4778,6 +5115,32 @@ static void ilk_pipe_wm_get_hw_state(struct drm_crtc *crtc)
 #define _FW_WM_VLV(value, plane) \
        (((value) & DSPFW_ ## plane ## _MASK_VLV) >> DSPFW_ ## plane ## _SHIFT)
 
+static void g4x_read_wm_values(struct drm_i915_private *dev_priv,
+                              struct g4x_wm_values *wm)
+{
+       uint32_t tmp;
+
+       tmp = I915_READ(DSPFW1);
+       wm->sr.plane = _FW_WM(tmp, SR);
+       wm->pipe[PIPE_B].plane[PLANE_CURSOR] = _FW_WM(tmp, CURSORB);
+       wm->pipe[PIPE_B].plane[PLANE_PRIMARY] = _FW_WM(tmp, PLANEB);
+       wm->pipe[PIPE_A].plane[PLANE_PRIMARY] = _FW_WM(tmp, PLANEA);
+
+       tmp = I915_READ(DSPFW2);
+       wm->fbc_en = tmp & DSPFW_FBC_SR_EN;
+       wm->sr.fbc = _FW_WM(tmp, FBC_SR);
+       wm->hpll.fbc = _FW_WM(tmp, FBC_HPLL_SR);
+       wm->pipe[PIPE_B].plane[PLANE_SPRITE0] = _FW_WM(tmp, SPRITEB);
+       wm->pipe[PIPE_A].plane[PLANE_CURSOR] = _FW_WM(tmp, CURSORA);
+       wm->pipe[PIPE_A].plane[PLANE_SPRITE0] = _FW_WM(tmp, SPRITEA);
+
+       tmp = I915_READ(DSPFW3);
+       wm->hpll_en = tmp & DSPFW_HPLL_SR_EN;
+       wm->sr.cursor = _FW_WM(tmp, CURSOR_SR);
+       wm->hpll.cursor = _FW_WM(tmp, HPLL_CURSOR);
+       wm->hpll.plane = _FW_WM(tmp, HPLL_SR);
+}
+
 static void vlv_read_wm_values(struct drm_i915_private *dev_priv,
                               struct vlv_wm_values *wm)
 {
@@ -4854,6 +5217,147 @@ static void vlv_read_wm_values(struct drm_i915_private *dev_priv,
 #undef _FW_WM
 #undef _FW_WM_VLV
 
+void g4x_wm_get_hw_state(struct drm_device *dev)
+{
+       struct drm_i915_private *dev_priv = to_i915(dev);
+       struct g4x_wm_values *wm = &dev_priv->wm.g4x;
+       struct intel_crtc *crtc;
+
+       g4x_read_wm_values(dev_priv, wm);
+
+       wm->cxsr = I915_READ(FW_BLC_SELF) & FW_BLC_SELF_EN;
+
+       for_each_intel_crtc(dev, crtc) {
+               struct intel_crtc_state *crtc_state =
+                       to_intel_crtc_state(crtc->base.state);
+               struct g4x_wm_state *active = &crtc->wm.active.g4x;
+               struct g4x_pipe_wm *raw;
+               enum pipe pipe = crtc->pipe;
+               enum plane_id plane_id;
+               int level, max_level;
+
+               active->cxsr = wm->cxsr;
+               active->hpll_en = wm->hpll_en;
+               active->fbc_en = wm->fbc_en;
+
+               active->sr = wm->sr;
+               active->hpll = wm->hpll;
+
+               for_each_plane_id_on_crtc(crtc, plane_id) {
+                       active->wm.plane[plane_id] =
+                               wm->pipe[pipe].plane[plane_id];
+               }
+
+               if (wm->cxsr && wm->hpll_en)
+                       max_level = G4X_WM_LEVEL_HPLL;
+               else if (wm->cxsr)
+                       max_level = G4X_WM_LEVEL_SR;
+               else
+                       max_level = G4X_WM_LEVEL_NORMAL;
+
+               level = G4X_WM_LEVEL_NORMAL;
+               raw = &crtc_state->wm.g4x.raw[level];
+               for_each_plane_id_on_crtc(crtc, plane_id)
+                       raw->plane[plane_id] = active->wm.plane[plane_id];
+
+               if (++level > max_level)
+                       goto out;
+
+               raw = &crtc_state->wm.g4x.raw[level];
+               raw->plane[PLANE_PRIMARY] = active->sr.plane;
+               raw->plane[PLANE_CURSOR] = active->sr.cursor;
+               raw->plane[PLANE_SPRITE0] = 0;
+               raw->fbc = active->sr.fbc;
+
+               if (++level > max_level)
+                       goto out;
+
+               raw = &crtc_state->wm.g4x.raw[level];
+               raw->plane[PLANE_PRIMARY] = active->hpll.plane;
+               raw->plane[PLANE_CURSOR] = active->hpll.cursor;
+               raw->plane[PLANE_SPRITE0] = 0;
+               raw->fbc = active->hpll.fbc;
+
+       out:
+               for_each_plane_id_on_crtc(crtc, plane_id)
+                       g4x_raw_plane_wm_set(crtc_state, level,
+                                            plane_id, USHRT_MAX);
+               g4x_raw_fbc_wm_set(crtc_state, level, USHRT_MAX);
+
+               crtc_state->wm.g4x.optimal = *active;
+               crtc_state->wm.g4x.intermediate = *active;
+
+               DRM_DEBUG_KMS("Initial watermarks: pipe %c, plane=%d, cursor=%d, sprite=%d\n",
+                             pipe_name(pipe),
+                             wm->pipe[pipe].plane[PLANE_PRIMARY],
+                             wm->pipe[pipe].plane[PLANE_CURSOR],
+                             wm->pipe[pipe].plane[PLANE_SPRITE0]);
+       }
+
+       DRM_DEBUG_KMS("Initial SR watermarks: plane=%d, cursor=%d fbc=%d\n",
+                     wm->sr.plane, wm->sr.cursor, wm->sr.fbc);
+       DRM_DEBUG_KMS("Initial HPLL watermarks: plane=%d, SR cursor=%d fbc=%d\n",
+                     wm->hpll.plane, wm->hpll.cursor, wm->hpll.fbc);
+       DRM_DEBUG_KMS("Initial SR=%s HPLL=%s FBC=%s\n",
+                     yesno(wm->cxsr), yesno(wm->hpll_en), yesno(wm->fbc_en));
+}
+
+void g4x_wm_sanitize(struct drm_i915_private *dev_priv)
+{
+       struct intel_plane *plane;
+       struct intel_crtc *crtc;
+
+       mutex_lock(&dev_priv->wm.wm_mutex);
+
+       for_each_intel_plane(&dev_priv->drm, plane) {
+               struct intel_crtc *crtc =
+                       intel_get_crtc_for_pipe(dev_priv, plane->pipe);
+               struct intel_crtc_state *crtc_state =
+                       to_intel_crtc_state(crtc->base.state);
+               struct intel_plane_state *plane_state =
+                       to_intel_plane_state(plane->base.state);
+               struct g4x_wm_state *wm_state = &crtc_state->wm.g4x.optimal;
+               enum plane_id plane_id = plane->id;
+               int level;
+
+               if (plane_state->base.visible)
+                       continue;
+
+               for (level = 0; level < 3; level++) {
+                       struct g4x_pipe_wm *raw =
+                               &crtc_state->wm.g4x.raw[level];
+
+                       raw->plane[plane_id] = 0;
+                       wm_state->wm.plane[plane_id] = 0;
+               }
+
+               if (plane_id == PLANE_PRIMARY) {
+                       for (level = 0; level < 3; level++) {
+                               struct g4x_pipe_wm *raw =
+                                       &crtc_state->wm.g4x.raw[level];
+                               raw->fbc = 0;
+                       }
+
+                       wm_state->sr.fbc = 0;
+                       wm_state->hpll.fbc = 0;
+                       wm_state->fbc_en = false;
+               }
+       }
+
+       for_each_intel_crtc(&dev_priv->drm, crtc) {
+               struct intel_crtc_state *crtc_state =
+                       to_intel_crtc_state(crtc->base.state);
+
+               crtc_state->wm.g4x.intermediate =
+                       crtc_state->wm.g4x.optimal;
+               crtc->wm.active.g4x = crtc_state->wm.g4x.optimal;
+       }
+
+       g4x_program_watermarks(dev_priv);
+
+       mutex_unlock(&dev_priv->wm.wm_mutex);
+}
+
 void vlv_wm_get_hw_state(struct drm_device *dev)
 {
        struct drm_i915_private *dev_priv = to_i915(dev);
@@ -8160,6 +8664,12 @@ void intel_init_pm(struct drm_i915_private *dev_priv)
                dev_priv->display.initial_watermarks = vlv_initial_watermarks;
                dev_priv->display.optimize_watermarks = vlv_optimize_watermarks;
                dev_priv->display.atomic_update_watermarks = vlv_atomic_update_fifo;
+       } else if (IS_G4X(dev_priv)) {
+               g4x_setup_wm_latency(dev_priv);
+               dev_priv->display.compute_pipe_wm = g4x_compute_pipe_wm;
+               dev_priv->display.compute_intermediate_wm = g4x_compute_intermediate_wm;
+               dev_priv->display.initial_watermarks = g4x_initial_watermarks;
+               dev_priv->display.optimize_watermarks = g4x_optimize_watermarks;
        } else if (IS_PINEVIEW(dev_priv)) {
                if (!intel_get_cxsr_latency(IS_PINEVIEW_G(dev_priv),
                                            dev_priv->is_ddr3,
@@ -8175,8 +8685,6 @@ void intel_init_pm(struct drm_i915_private *dev_priv)
                        dev_priv->display.update_wm = NULL;
                } else
                        dev_priv->display.update_wm = pineview_update_wm;
-       } else if (IS_G4X(dev_priv)) {
-               dev_priv->display.update_wm = g4x_update_wm;
        } else if (IS_GEN4(dev_priv)) {
                dev_priv->display.update_wm = i965_update_wm;
        } else if (IS_GEN3(dev_priv)) {