diff --git a/drivers/gpu/drm/i915/i915_reg.h b/drivers/gpu/drm/i915/i915_reg.h
index f238b7b33cd9..44eac5ffc2ce 100644
--- a/drivers/gpu/drm/i915/i915_reg.h
+++ b/drivers/gpu/drm/i915/i915_reg.h
@@ -6265,6 +6265,8 @@ enum {
 #define   SP_ROTATE_180			(1<<15)
 #define   SP_TILED			(1<<10)
 #define   SP_MIRROR			(1<<8) /* CHV pipe B */
+#define   SP_BOTTOM			(1<<2)
+#define   SP_ZORDER			(1<<0)
 #define _SPALINOFF		(VLV_DISPLAY_BASE + 0x72184)
 #define _SPASTRIDE		(VLV_DISPLAY_BASE + 0x72188)
 #define _SPAPOS			(VLV_DISPLAY_BASE + 0x7218c)
diff --git a/drivers/gpu/drm/i915/intel_atomic_plane.c b/drivers/gpu/drm/i915/intel_atomic_plane.c
index 6d068786eb41..a5a3123589e7 100644
--- a/drivers/gpu/drm/i915/intel_atomic_plane.c
+++ b/drivers/gpu/drm/i915/intel_atomic_plane.c
@@ -193,6 +193,14 @@ int intel_plane_atomic_check_with_state(const struct intel_crtc_state *old_crtc_
 	else
 		crtc_state->nv12_planes &= ~BIT(intel_plane->id);
 
+	/*
+	 * Copy over the zpos to the crtc state so that we don't
+	 * need to add every plane on the crtc to the state to
+	 * recompute the final zpos.
+	 */
+	crtc_state->raw_zpos[intel_plane->id] =
+		intel_plane_raw_zpos(crtc_state, intel_state);
+
 	return intel_plane_atomic_calc_changes(old_crtc_state,
 					       &crtc_state->base,
 					       old_plane_state,
diff --git a/drivers/gpu/drm/i915/intel_display.c b/drivers/gpu/drm/i915/intel_display.c
index bc3de1b5bb89..111cb0c1da54 100644
--- a/drivers/gpu/drm/i915/intel_display.c
+++ b/drivers/gpu/drm/i915/intel_display.c
@@ -30,6 +30,7 @@
 #include <linux/i2c.h>
 #include <linux/kernel.h>
 #include <linux/slab.h>
+#include <linux/sort.h>
 #include <linux/vgaarb.h>
 #include <drm/drm_edid.h>
 #include <drm/drmP.h>
@@ -2760,6 +2761,8 @@ intel_set_plane_visible(struct intel_crtc_state *crtc_state,
 		crtc_state->active_planes &= ~BIT(plane->id);
 	}
 
+	crtc_state->raw_zpos[plane->id] = intel_plane_raw_zpos(crtc_state, plane_state);
+
 	DRM_DEBUG_KMS("%s active planes 0x%x\n",
 		      crtc_state->base.crtc->name,
 		      crtc_state->active_planes);
@@ -3208,8 +3211,8 @@ int skl_check_plane_surface(const struct intel_crtc_state *crtc_state,
 static u32 i9xx_plane_ctl(const struct intel_crtc_state *crtc_state,
 			  const struct intel_plane_state *plane_state)
 {
-	struct drm_i915_private *dev_priv =
-		to_i915(plane_state->base.plane->dev);
+	struct intel_plane *plane = to_intel_plane(plane_state->base.plane);
+	struct drm_i915_private *dev_priv = to_i915(plane->base.dev);
 	struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc);
 	const struct drm_framebuffer *fb = plane_state->base.fb;
 	unsigned int rotation = plane_state->base.rotation;
@@ -10932,6 +10935,7 @@ clear_intel_crtc_state(struct intel_crtc_state *crtc_state)
 	struct intel_dpll_hw_state dpll_hw_state;
 	struct intel_shared_dpll *shared_dpll;
 	struct intel_crtc_wm_state wm_state;
+	u8 raw_zpos[I915_MAX_PLANES];
 	bool force_thru, ips_force_disable;
 
 	/* FIXME: before the switch to atomic started, a new pipe_config was
@@ -10947,6 +10951,7 @@ clear_intel_crtc_state(struct intel_crtc_state *crtc_state)
 	if (IS_G4X(dev_priv) ||
 	    IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv))
 		wm_state = crtc_state->wm;
+	memcpy(raw_zpos, crtc_state->raw_zpos, sizeof(raw_zpos));
 
 	/* Keep base drm_crtc_state intact, only clear our extended struct */
 	BUILD_BUG_ON(offsetof(struct intel_crtc_state, base));
@@ -10961,6 +10966,7 @@ clear_intel_crtc_state(struct intel_crtc_state *crtc_state)
 	if (IS_G4X(dev_priv) ||
 	    IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv))
 		crtc_state->wm = wm_state;
+	memcpy(crtc_state->raw_zpos, raw_zpos, sizeof(raw_zpos));
 }
 
 static int
@@ -12161,6 +12167,130 @@ static int calc_watermark_data(struct drm_atomic_state *state)
 	return 0;
 }
 
+int intel_plane_raw_zpos(const struct intel_crtc_state *crtc_state,
+			 const struct intel_plane_state *plane_state)
+{
+	struct intel_plane *plane = to_intel_plane(plane_state->base.plane);
+
+	/*
+	 * We'll keep the cursor always at the top regardless of
+	 * whether it's enabled or not. This avoids changes to the
+	 * sorted zpos of other planes when turning the cursor on/off,
+	 * and thus we can easily avoid having to add the other
+	 * planes to the atomic state.
+	 */
+	if (plane->id == PLANE_CURSOR)
+		return 0x20;
+
+	/*
+	 * For other planes we stack the disabled planes on top
+	 * of any enabled planes. This simplifies our life by
+	 * allowing us to keep the SP_BOTTOM and SP_ZORDER bits
+	 * cleared for any disabled planes on VLV/CHV. Thus we
+	 * don't have to worry about zpos in .disable_plane().
+	 */
+	if (plane_state->base.visible)
+		return plane_state->base.zpos;
+	else
+		return 0x10;
+}
+
+struct intel_plane_zpos {
+	u32 obj_id;
+	enum plane_id plane_id;
+	u8 zpos;
+};
+
+static int intel_zpos_cmp(const void *a, const void *b)
+{
+	const struct intel_plane_zpos *za = (const struct intel_plane_zpos *)a;
+	const struct intel_plane_zpos *zb = (const struct intel_plane_zpos *)b;
+
+	if (za->zpos != zb->zpos)
+		return za->zpos - zb->zpos;
+	else
+		return za->obj_id - zb->obj_id;
+}
+
+static void vlv_normalize_zpos(struct intel_crtc_state *crtc_state)
+{
+	struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc);
+	struct drm_i915_private *dev_priv = to_i915(crtc->base.dev);
+	struct intel_plane_zpos zpos[I915_MAX_PLANES] = {};
+	struct intel_plane *plane;
+	int i, num_planes;
+
+	i = 0;
+	for_each_intel_plane_on_crtc(&dev_priv->drm, crtc, plane) {
+		zpos[i].obj_id = plane->base.base.id;
+		zpos[i].plane_id = plane->id;
+		zpos[i].zpos = crtc_state->raw_zpos[plane->id];
+		i++;
+	}
+
+	num_planes = i;
+
+	/* sort according to zpos/plane obj id */
+	sort(zpos, num_planes, sizeof(zpos[0]),
+	     intel_zpos_cmp, NULL);
+
+	/* copy over sorted zpos */
+	for (i = 0; i < num_planes; i++) {
+		enum plane_id plane_id = zpos[i].plane_id;
+
+		crtc_state->zpos[plane_id] = i;
+	}
+}
+
+static int vlv_update_zpos(struct drm_i915_private *dev_priv,
+			   struct intel_atomic_state *state)
+{
+	struct intel_crtc *crtc;
+	struct intel_crtc_state *old_crtc_state, *new_crtc_state;
+	int i;
+
+	for_each_oldnew_intel_crtc_in_state(state, crtc, old_crtc_state, new_crtc_state, i) {
+		struct drm_plane *plane;
+		bool sprites_changed;
+
+		vlv_normalize_zpos(new_crtc_state);
+
+		sprites_changed =
+			memcmp(new_crtc_state->zpos,
+			       old_crtc_state->zpos,
+			       sizeof(new_crtc_state->zpos)) != 0;
+
+		if (!sprites_changed)
+			continue;
+
+		drm_for_each_plane_mask(plane, &dev_priv->drm,
+					new_crtc_state->base.plane_mask) {
+			struct drm_plane_state *plane_state;
+			enum plane_id plane_id = to_intel_plane(plane)->id;
+
+			if (plane_id == PLANE_CURSOR ||
+			    plane_id == PLANE_PRIMARY)
+				continue;
+
+			/*
+			 * zpos is configured via sprite registers, so must
+			 * add them to the state if anything significant
+			 * changed.
+			 */
+			plane_state = drm_atomic_get_plane_state(&state->base, plane);
+			if (IS_ERR(plane_state))
+				return PTR_ERR(plane_state);
+		}
+
+		DRM_DEBUG_KMS("pipe %c zpos sprite %c: %d, sprite %c: %d\n",
+			      pipe_name(crtc->pipe),
+			      sprite_name(crtc->pipe, 0), new_crtc_state->zpos[PLANE_SPRITE0],
+			      sprite_name(crtc->pipe, 1), new_crtc_state->zpos[PLANE_SPRITE1]);
+	}
+
+	return 0;
+}
+
 /**
  * intel_atomic_check - validate state object
  * @dev: drm device
@@ -12236,7 +12366,14 @@ static int intel_atomic_check(struct drm_device *dev,
 	if (ret)
 		return ret;
 
+	if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) {
+		ret = vlv_update_zpos(dev_priv, intel_state);
+		if (ret)
+			return ret;
+	}
+
 	intel_fbc_choose_crtc(dev_priv, intel_state);
+
 	return calc_watermark_data(state);
 }
 
@@ -13592,7 +13729,10 @@ intel_primary_plane_create(struct drm_i915_private *dev_priv, enum pipe pipe)
 						  DRM_COLOR_YCBCR_LIMITED_RANGE);
 
 	zpos = 0;
-	drm_plane_create_zpos_immutable_property(&primary->base, zpos);
+	if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv))
+		drm_plane_create_zpos_property(&primary->base, zpos, 0, 2);
+	else
+		drm_plane_create_zpos_immutable_property(&primary->base, zpos);
 
 	drm_plane_helper_add(&primary->base, &intel_plane_helper_funcs);
 
diff --git a/drivers/gpu/drm/i915/intel_display.h b/drivers/gpu/drm/i915/intel_display.h
index 2ef31617614a..09bbd9a3efd6 100644
--- a/drivers/gpu/drm/i915/intel_display.h
+++ b/drivers/gpu/drm/i915/intel_display.h
@@ -333,6 +333,15 @@ struct intel_link_m_n {
 	     (__i)++) \
 		for_each_if(plane)
 
+#define for_each_oldnew_intel_crtc_in_state(__state, crtc, old_crtc_state, new_crtc_state, __i) \
+	for ((__i) = 0; \
+	     (__i) < (__state)->base.dev->mode_config.num_crtc && \
+		     ((crtc) = to_intel_crtc((__state)->base.crtcs[__i].ptr), \
+		      (old_crtc_state) = to_intel_crtc_state((__state)->base.crtcs[__i].old_state), \
+		      (new_crtc_state) = to_intel_crtc_state((__state)->base.crtcs[__i].new_state), 1); \
+	     (__i)++) \
+		for_each_if(crtc)
+
 void intel_link_compute_m_n(int bpp, int nlanes,
 			    int pixel_clock, int link_clock,
 			    struct intel_link_m_n *m_n,
diff --git a/drivers/gpu/drm/i915/intel_drv.h b/drivers/gpu/drm/i915/intel_drv.h
index fd6256632482..994b9473b817 100644
--- a/drivers/gpu/drm/i915/intel_drv.h
+++ b/drivers/gpu/drm/i915/intel_drv.h
@@ -890,6 +890,10 @@ struct intel_crtc_state {
 	u8 active_planes;
 	u8 nv12_planes;
 
+	/* VLV/CHV zpos for each plane */
+	u8 raw_zpos[I915_MAX_PLANES]; /* raw property value (or 0xff for disabled planes) */
+	u8 zpos[I915_MAX_PLANES]; /* final zpos for each plane */
+
 	/* HDMI scrambling status */
 	bool hdmi_scrambling;
 
@@ -1457,6 +1461,9 @@ void intel_dump_cdclk_state(const struct intel_cdclk_state *cdclk_state,
 			    const char *context);
 
 /* intel_display.c */
+int intel_plane_raw_zpos(const struct intel_crtc_state *crtc_state,
+			 const struct intel_plane_state *plane_state);
+void intel__enable_pipe(struct drm_i915_private *dev_priv, enum pipe pipe);
 void i830_enable_pipe(struct drm_i915_private *dev_priv, enum pipe pipe);
 void i830_disable_pipe(struct drm_i915_private *dev_priv, enum pipe pipe);
 enum pipe intel_crtc_pch_transcoder(struct intel_crtc *crtc);
diff --git a/drivers/gpu/drm/i915/intel_sprite.c b/drivers/gpu/drm/i915/intel_sprite.c
index a6f4524adff4..228eba582d44 100644
--- a/drivers/gpu/drm/i915/intel_sprite.c
+++ b/drivers/gpu/drm/i915/intel_sprite.c
@@ -511,6 +511,22 @@ static u32 vlv_sprite_ctl(const struct intel_crtc_state *crtc_state,
 	return sprctl;
 }
 
+static u32 vlv_sprctl_zpos(struct intel_plane *plane,
+			   const struct intel_crtc_state *crtc_state)
+{
+	enum plane_id plane_id = plane->id;
+	enum plane_id other_plane_id = plane_id == PLANE_SPRITE0 ?
+		PLANE_SPRITE1 : PLANE_SPRITE0;
+
+	if (crtc_state->zpos[plane_id] == 0)
+		return SP_BOTTOM;
+	else if (crtc_state->zpos[plane_id] ==
+		 crtc_state->zpos[other_plane_id] + 1)
+		return SP_ZORDER;
+
+	return 0;
+}
+
 static void
 vlv_update_plane(struct intel_plane *plane,
 		 const struct intel_crtc_state *crtc_state,
@@ -538,6 +554,9 @@ vlv_update_plane(struct intel_plane *plane,
 
 	linear_offset = intel_fb_xy_to_linear(x, y, plane_state, 0);
 
+	/* final zpos value is computed after vlv_sprite_ctl() */
+	sprctl |= vlv_sprctl_zpos(plane, crtc_state);
+
 	spin_lock_irqsave(&dev_priv->uncore.lock, irqflags);
 
 	vlv_update_clrc(plane_state);
@@ -1553,7 +1572,10 @@ intel_sprite_plane_create(struct drm_i915_private *dev_priv,
 					  DRM_COLOR_YCBCR_LIMITED_RANGE);
 
 	zpos = plane + 1;
-	drm_plane_create_zpos_immutable_property(&intel_plane->base, zpos);
+	if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv))
+		drm_plane_create_zpos_property(&intel_plane->base, zpos, 0, 2);
+	else
+		drm_plane_create_zpos_immutable_property(&intel_plane->base, zpos);
 
 	drm_plane_helper_add(&intel_plane->base, &intel_plane_helper_funcs);
 
