diff mbox

[08/15] drm/i915: Implement PHY lane power gating for CHV

Message ID 1436388361-11130-9-git-send-email-ville.syrjala@linux.intel.com (mailing list archive)
State New, archived
Headers show

Commit Message

Ville Syrjälä July 8, 2015, 8:45 p.m. UTC
From: Ville Syrjälä <ville.syrjala@linux.intel.com>

Powergate the PHY lanes when they're not needed. For HDMI all four lanes
are needed always, but for DP we can enable only the needed lanes. To
power down the unused lanes we use some power down override bits in the
DISPLAY_PHY_CONTROL register. Without the overrides it appears that the
hardware always powers on all the lanes. When the port is disabled the
power down override is not needed and the lanes will shut off on their
own. That also means the override is critical to actually be able to
access the DPIO registers before the port is actually enabled.

Additionally the common lanes will power down when not needed. CL1
remains on as long as anything else is on, CL2 will shut down when
all the lanes in the same channel will shut down. There is one exception
for CL2 that will be dealt in a separate patch for clarity.

With potentially some lanes powered down, the DP code now has to check
the number of active lanes before accessing PCS/TX registers. All
registers in powered down blocks will reads as 0xffffffff, and soe we
would drown in warnings from vlv_dpio_read() if we allowed the code
to access all those registers.

Another important detail in the DP code is the "TX latency optimal"
setting. Normally the second TX lane acts as some kind of reset master,
with the other lanes as slaves. But when only a single lane is enabled,
that single lane obviously has to be the master.

A bit of extra care is needed to reconstruct the initial state of the
DISPLAY_PHY_CONTROL register since it can't be read safely. So instead
read the actual lane status from the DPLL/PHY_STATUS registers and
use that to determine which lanes ought to be powergated initially.

We also need to switch the PHY power modes to "deep PSR" to avoid
a hard system hang when powering down the single channel PHY.

Also sprinkle a few debug prints around so that we can monitor the
DISPLAY_PHY_STATUS changes without having to read it and risk
corrupting it.

v2: Add locking to chv_powergate_phy_lanes()
v3: Actually enable dynamic powerdown in the PHY and deal with the
    fallout

Signed-off-by: Ville Syrjälä <ville.syrjala@linux.intel.com>
---
 drivers/gpu/drm/i915/i915_reg.h         |   8 ++
 drivers/gpu/drm/i915/intel_dp.c         | 141 +++++++++++++++++++++-----------
 drivers/gpu/drm/i915/intel_drv.h        |   4 +
 drivers/gpu/drm/i915/intel_hdmi.c       |   4 +
 drivers/gpu/drm/i915/intel_runtime_pm.c | 123 ++++++++++++++++++++++++++--
 5 files changed, 221 insertions(+), 59 deletions(-)

Comments

deepak.s@linux.intel.com Aug. 19, 2015, 1:48 a.m. UTC | #1
On 07/09/2015 02:15 AM, ville.syrjala@linux.intel.com wrote:
> From: Ville Syrjälä <ville.syrjala@linux.intel.com>
>
> Powergate the PHY lanes when they're not needed. For HDMI all four lanes
> are needed always, but for DP we can enable only the needed lanes. To
> power down the unused lanes we use some power down override bits in the
> DISPLAY_PHY_CONTROL register. Without the overrides it appears that the
> hardware always powers on all the lanes. When the port is disabled the
> power down override is not needed and the lanes will shut off on their
> own. That also means the override is critical to actually be able to
> access the DPIO registers before the port is actually enabled.
>
> Additionally the common lanes will power down when not needed. CL1
> remains on as long as anything else is on, CL2 will shut down when
> all the lanes in the same channel will shut down. There is one exception
> for CL2 that will be dealt in a separate patch for clarity.
>
> With potentially some lanes powered down, the DP code now has to check
> the number of active lanes before accessing PCS/TX registers. All
> registers in powered down blocks will reads as 0xffffffff, and soe we
> would drown in warnings from vlv_dpio_read() if we allowed the code
> to access all those registers.
>
> Another important detail in the DP code is the "TX latency optimal"
> setting. Normally the second TX lane acts as some kind of reset master,
> with the other lanes as slaves. But when only a single lane is enabled,
> that single lane obviously has to be the master.
>
> A bit of extra care is needed to reconstruct the initial state of the
> DISPLAY_PHY_CONTROL register since it can't be read safely. So instead
> read the actual lane status from the DPLL/PHY_STATUS registers and
> use that to determine which lanes ought to be powergated initially.
>
> We also need to switch the PHY power modes to "deep PSR" to avoid
> a hard system hang when powering down the single channel PHY.
>
> Also sprinkle a few debug prints around so that we can monitor the
> DISPLAY_PHY_STATUS changes without having to read it and risk
> corrupting it.
>
> v2: Add locking to chv_powergate_phy_lanes()
> v3: Actually enable dynamic powerdown in the PHY and deal with the
>      fallout
>
> Signed-off-by: Ville Syrjälä <ville.syrjala@linux.intel.com>
> ---
>   drivers/gpu/drm/i915/i915_reg.h         |   8 ++
>   drivers/gpu/drm/i915/intel_dp.c         | 141 +++++++++++++++++++++-----------
>   drivers/gpu/drm/i915/intel_drv.h        |   4 +
>   drivers/gpu/drm/i915/intel_hdmi.c       |   4 +
>   drivers/gpu/drm/i915/intel_runtime_pm.c | 123 ++++++++++++++++++++++++++--
>   5 files changed, 221 insertions(+), 59 deletions(-)
>
> diff --git a/drivers/gpu/drm/i915/i915_reg.h b/drivers/gpu/drm/i915/i915_reg.h
> index 17cb7e5..bcfcbb62 100644
> --- a/drivers/gpu/drm/i915/i915_reg.h
> +++ b/drivers/gpu/drm/i915/i915_reg.h
> @@ -1125,9 +1125,15 @@ enum skl_disp_power_wells {
>   
>   #define _CHV_CMN_DW19_CH0		0x814c
>   #define _CHV_CMN_DW6_CH1		0x8098
> +#define   DPIO_DYNPWRDOWNEN_CH1		(1 << 28) /* CL2 DW6 only */
>   #define   CHV_CMN_USEDCLKCHANNEL	(1 << 13)
> +
>   #define CHV_CMN_DW19(ch) _PIPE(ch, _CHV_CMN_DW19_CH0, _CHV_CMN_DW6_CH1)
>   
> +#define CHV_CMN_DW28			0x8170
> +#define   DPIO_CL1POWERDOWNEN		(1 << 23)
> +#define   DPIO_DYNPWRDOWNEN_CH0		(1 << 22)
> +
>   #define CHV_CMN_DW30			0x8178
>   #define   DPIO_LRC_BYPASS		(1 << 3)
>   
> @@ -2175,10 +2181,12 @@ enum skl_disp_power_wells {
>   #define DPIO_PHY_STATUS			(VLV_DISPLAY_BASE + 0x6240)
>   #define   DPLL_PORTD_READY_MASK		(0xf)
>   #define DISPLAY_PHY_CONTROL (VLV_DISPLAY_BASE + 0x60100)
> +#define   PHY_CH_POWER_DOWN_OVRD_EN(phy, ch)	(1 << (2*(phy)+(ch)+27))
>   #define   PHY_LDO_DELAY_0NS			0x0
>   #define   PHY_LDO_DELAY_200NS			0x1
>   #define   PHY_LDO_DELAY_600NS			0x2
>   #define   PHY_LDO_SEQ_DELAY(delay, phy)		((delay) << (2*(phy)+23))
> +#define   PHY_CH_POWER_DOWN_OVRD(mask, phy, ch)	((mask) << (8*(phy)+4*(ch)+11))
>   #define   PHY_CH_SU_PSR				0x1
>   #define   PHY_CH_DEEP_PSR			0x7
>   #define   PHY_CH_POWER_MODE(mode, phy, ch)	((mode) << (6*(phy)+3*(ch)+2))
> diff --git a/drivers/gpu/drm/i915/intel_dp.c b/drivers/gpu/drm/i915/intel_dp.c
> index 40b8430..6058129 100644
> --- a/drivers/gpu/drm/i915/intel_dp.c
> +++ b/drivers/gpu/drm/i915/intel_dp.c
> @@ -133,6 +133,11 @@ static void vlv_init_panel_power_sequencer(struct intel_dp *intel_dp);
>   static void vlv_steal_power_sequencer(struct drm_device *dev,
>   				      enum pipe pipe);
>   
> +static unsigned int intel_dp_unused_lane_mask(int lane_count)
> +{
> +	return ~((1 << lane_count) - 1) & 0xf;
> +}
> +
>   static int
>   intel_dp_max_link_bw(struct intel_dp  *intel_dp)
>   {
> @@ -2395,17 +2400,21 @@ static void chv_post_disable_dp(struct intel_encoder *encoder)
>   	val |= CHV_PCS_REQ_SOFTRESET_EN;
>   	vlv_dpio_write(dev_priv, pipe, VLV_PCS01_DW1(ch), val);
>   
> -	val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW1(ch));
> -	val |= CHV_PCS_REQ_SOFTRESET_EN;
> -	vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW1(ch), val);
> +	if (intel_crtc->config->lane_count > 2) {
> +		val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW1(ch));
> +		val |= CHV_PCS_REQ_SOFTRESET_EN;
> +		vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW1(ch), val);
> +	}
>   
>   	val = vlv_dpio_read(dev_priv, pipe, VLV_PCS01_DW0(ch));
>   	val &= ~(DPIO_PCS_TX_LANE2_RESET | DPIO_PCS_TX_LANE1_RESET);
>   	vlv_dpio_write(dev_priv, pipe, VLV_PCS01_DW0(ch), val);
>   
> -	val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW0(ch));
> -	val &= ~(DPIO_PCS_TX_LANE2_RESET | DPIO_PCS_TX_LANE1_RESET);
> -	vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW0(ch), val);
> +	if (intel_crtc->config->lane_count > 2) {
> +		val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW0(ch));
> +		val &= ~(DPIO_PCS_TX_LANE2_RESET | DPIO_PCS_TX_LANE1_RESET);
> +		vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW0(ch), val);
> +	}
>   
>   	mutex_unlock(&dev_priv->sb_lock);
>   }
> @@ -2525,7 +2534,6 @@ static void intel_enable_dp(struct intel_encoder *encoder)
>   	struct drm_i915_private *dev_priv = dev->dev_private;
>   	struct intel_crtc *crtc = to_intel_crtc(encoder->base.crtc);
>   	uint32_t dp_reg = I915_READ(intel_dp->output_reg);
> -	unsigned int lane_mask = 0x0;
>   
>   	if (WARN_ON(dp_reg & DP_PORT_EN))
>   		return;
> @@ -2543,9 +2551,15 @@ static void intel_enable_dp(struct intel_encoder *encoder)
>   
>   	pps_unlock(intel_dp);
>   
> -	if (IS_VALLEYVIEW(dev))
> +	if (IS_VALLEYVIEW(dev)) {
> +		unsigned int lane_mask = 0x0;
> +
> +		if (IS_CHERRYVIEW(dev))
> +			lane_mask = intel_dp_unused_lane_mask(crtc->config->lane_count);
> +
>   		vlv_wait_port_ready(dev_priv, dp_to_dig_port(intel_dp),
>   				    lane_mask);
> +	}
>   
>   	intel_dp_sink_dpms(intel_dp, DRM_MODE_DPMS_ON);
>   	intel_dp_start_link_train(intel_dp);
> @@ -2772,31 +2786,40 @@ static void chv_pre_enable_dp(struct intel_encoder *encoder)
>   	val &= ~DPIO_LANEDESKEW_STRAP_OVRD;
>   	vlv_dpio_write(dev_priv, pipe, VLV_PCS01_DW11(ch), val);
>   
> -	val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW11(ch));
> -	val &= ~DPIO_LANEDESKEW_STRAP_OVRD;
> -	vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW11(ch), val);
> +	if (intel_crtc->config->lane_count > 2) {
> +		val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW11(ch));
> +		val &= ~DPIO_LANEDESKEW_STRAP_OVRD;
> +		vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW11(ch), val);
> +	}
>   
>   	/* Deassert soft data lane reset*/
>   	val = vlv_dpio_read(dev_priv, pipe, VLV_PCS01_DW1(ch));
>   	val |= CHV_PCS_REQ_SOFTRESET_EN;
>   	vlv_dpio_write(dev_priv, pipe, VLV_PCS01_DW1(ch), val);
>   
> -	val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW1(ch));
> -	val |= CHV_PCS_REQ_SOFTRESET_EN;
> -	vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW1(ch), val);
> +	if (intel_crtc->config->lane_count > 2) {
> +		val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW1(ch));
> +		val |= CHV_PCS_REQ_SOFTRESET_EN;
> +		vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW1(ch), val);
> +	}
>   
>   	val = vlv_dpio_read(dev_priv, pipe, VLV_PCS01_DW0(ch));
>   	val |= (DPIO_PCS_TX_LANE2_RESET | DPIO_PCS_TX_LANE1_RESET);
>   	vlv_dpio_write(dev_priv, pipe, VLV_PCS01_DW0(ch), val);
>   
> -	val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW0(ch));
> -	val |= (DPIO_PCS_TX_LANE2_RESET | DPIO_PCS_TX_LANE1_RESET);
> -	vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW0(ch), val);
> +	if (intel_crtc->config->lane_count > 2) {
> +		val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW0(ch));
> +		val |= (DPIO_PCS_TX_LANE2_RESET | DPIO_PCS_TX_LANE1_RESET);
> +		vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW0(ch), val);
> +	}
>   
>   	/* Program Tx lane latency optimal setting*/
> -	for (i = 0; i < 4; i++) {
> +	for (i = 0; i < intel_crtc->config->lane_count; i++) {
>   		/* Set the upar bit */
> -		data = (i == 1) ? 0x0 : 0x1;
> +		if (intel_crtc->config->lane_count == 1)
> +			data = 0x0;
> +		else
> +			data = (i == 1) ? 0x0 : 0x1;
>   		vlv_dpio_write(dev_priv, pipe, CHV_TX_DW14(ch, i),
>   				data << DPIO_UPAR_SHIFT);
>   	}
> @@ -2817,9 +2840,11 @@ static void chv_pre_enable_dp(struct intel_encoder *encoder)
>   	val |= DPIO_TX2_STAGGER_MASK(0x1f);
>   	vlv_dpio_write(dev_priv, pipe, VLV_PCS01_DW11(ch), val);
>   
> -	val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW11(ch));
> -	val |= DPIO_TX2_STAGGER_MASK(0x1f);
> -	vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW11(ch), val);
> +	if (intel_crtc->config->lane_count > 2) {
> +		val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW11(ch));
> +		val |= DPIO_TX2_STAGGER_MASK(0x1f);
> +		vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW11(ch), val);
> +	}
>   
>   	vlv_dpio_write(dev_priv, pipe, VLV_PCS01_DW12(ch),
>   		       DPIO_LANESTAGGER_STRAP(stagger) |
> @@ -2828,12 +2853,14 @@ static void chv_pre_enable_dp(struct intel_encoder *encoder)
>   		       DPIO_TX1_STAGGER_MULT(6) |
>   		       DPIO_TX2_STAGGER_MULT(0));
>   
> -	vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW12(ch),
> -		       DPIO_LANESTAGGER_STRAP(stagger) |
> -		       DPIO_LANESTAGGER_STRAP_OVRD |
> -		       DPIO_TX1_STAGGER_MASK(0x1f) |
> -		       DPIO_TX1_STAGGER_MULT(7) |
> -		       DPIO_TX2_STAGGER_MULT(5));
> +	if (intel_crtc->config->lane_count > 2) {
> +		vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW12(ch),
> +			       DPIO_LANESTAGGER_STRAP(stagger) |
> +			       DPIO_LANESTAGGER_STRAP_OVRD |
> +			       DPIO_TX1_STAGGER_MASK(0x1f) |
> +			       DPIO_TX1_STAGGER_MULT(7) |
> +			       DPIO_TX2_STAGGER_MULT(5));
> +	}
>   
>   	mutex_unlock(&dev_priv->sb_lock);
>   
> @@ -2849,10 +2876,14 @@ static void chv_dp_pre_pll_enable(struct intel_encoder *encoder)
>   		to_intel_crtc(encoder->base.crtc);
>   	enum dpio_channel ch = vlv_dport_to_channel(dport);
>   	enum pipe pipe = intel_crtc->pipe;
> +	unsigned int lane_mask =
> +		intel_dp_unused_lane_mask(intel_crtc->config->lane_count);
>   	u32 val;
>   
>   	intel_dp_prepare(encoder);
>   
> +	chv_phy_powergate_lanes(encoder, true, lane_mask);
> +
>   	mutex_lock(&dev_priv->sb_lock);
>   
>   	/* program left/right clock distribution */
> @@ -2883,13 +2914,15 @@ static void chv_dp_pre_pll_enable(struct intel_encoder *encoder)
>   		val |= CHV_PCS_USEDCLKCHANNEL;
>   	vlv_dpio_write(dev_priv, pipe, VLV_PCS01_DW8(ch), val);
>   
> -	val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW8(ch));
> -	val |= CHV_PCS_USEDCLKCHANNEL_OVRRIDE;
> -	if (pipe != PIPE_B)
> -		val &= ~CHV_PCS_USEDCLKCHANNEL;
> -	else
> -		val |= CHV_PCS_USEDCLKCHANNEL;
> -	vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW8(ch), val);
> +	if (intel_crtc->config->lane_count > 2) {
> +		val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW8(ch));
> +		val |= CHV_PCS_USEDCLKCHANNEL_OVRRIDE;
> +		if (pipe != PIPE_B)
> +			val &= ~CHV_PCS_USEDCLKCHANNEL;
> +		else
> +			val |= CHV_PCS_USEDCLKCHANNEL;
> +		vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW8(ch), val);
> +	}
>   
>   	/*
>   	 * This a a bit weird since generally CL
> @@ -2926,6 +2959,8 @@ static void chv_dp_post_pll_disable(struct intel_encoder *encoder)
>   	}
>   
>   	mutex_unlock(&dev_priv->sb_lock);
> +
> +	chv_phy_powergate_lanes(encoder, false, 0x0);
>   }
>   
>   /*
> @@ -3261,24 +3296,28 @@ static uint32_t chv_signal_levels(struct intel_dp *intel_dp)
>   	val |= DPIO_PCS_TX1DEEMP_9P5 | DPIO_PCS_TX2DEEMP_9P5;
>   	vlv_dpio_write(dev_priv, pipe, VLV_PCS01_DW10(ch), val);
>   
> -	val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW10(ch));
> -	val &= ~(DPIO_PCS_SWING_CALC_TX0_TX2 | DPIO_PCS_SWING_CALC_TX1_TX3);
> -	val &= ~(DPIO_PCS_TX1DEEMP_MASK | DPIO_PCS_TX2DEEMP_MASK);
> -	val |= DPIO_PCS_TX1DEEMP_9P5 | DPIO_PCS_TX2DEEMP_9P5;
> -	vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW10(ch), val);
> +	if (intel_crtc->config->lane_count > 2) {
> +		val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW10(ch));
> +		val &= ~(DPIO_PCS_SWING_CALC_TX0_TX2 | DPIO_PCS_SWING_CALC_TX1_TX3);
> +		val &= ~(DPIO_PCS_TX1DEEMP_MASK | DPIO_PCS_TX2DEEMP_MASK);
> +		val |= DPIO_PCS_TX1DEEMP_9P5 | DPIO_PCS_TX2DEEMP_9P5;
> +		vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW10(ch), val);
> +	}
>   
>   	val = vlv_dpio_read(dev_priv, pipe, VLV_PCS01_DW9(ch));
>   	val &= ~(DPIO_PCS_TX1MARGIN_MASK | DPIO_PCS_TX2MARGIN_MASK);
>   	val |= DPIO_PCS_TX1MARGIN_000 | DPIO_PCS_TX2MARGIN_000;
>   	vlv_dpio_write(dev_priv, pipe, VLV_PCS01_DW9(ch), val);
>   
> -	val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW9(ch));
> -	val &= ~(DPIO_PCS_TX1MARGIN_MASK | DPIO_PCS_TX2MARGIN_MASK);
> -	val |= DPIO_PCS_TX1MARGIN_000 | DPIO_PCS_TX2MARGIN_000;
> -	vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW9(ch), val);
> +	if (intel_crtc->config->lane_count > 2) {
> +		val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW9(ch));
> +		val &= ~(DPIO_PCS_TX1MARGIN_MASK | DPIO_PCS_TX2MARGIN_MASK);
> +		val |= DPIO_PCS_TX1MARGIN_000 | DPIO_PCS_TX2MARGIN_000;
> +		vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW9(ch), val);
> +	}
>   
>   	/* Program swing deemph */
> -	for (i = 0; i < 4; i++) {
> +	for (i = 0; i < intel_crtc->config->lane_count; i++) {
>   		val = vlv_dpio_read(dev_priv, pipe, CHV_TX_DW4(ch, i));
>   		val &= ~DPIO_SWING_DEEMPH9P5_MASK;
>   		val |= deemph_reg_value << DPIO_SWING_DEEMPH9P5_SHIFT;
> @@ -3286,7 +3325,7 @@ static uint32_t chv_signal_levels(struct intel_dp *intel_dp)
>   	}
>   
>   	/* Program swing margin */
> -	for (i = 0; i < 4; i++) {
> +	for (i = 0; i < intel_crtc->config->lane_count; i++) {
>   		val = vlv_dpio_read(dev_priv, pipe, CHV_TX_DW2(ch, i));
>   
>   		val &= ~DPIO_SWING_MARGIN000_MASK;
> @@ -3309,7 +3348,7 @@ static uint32_t chv_signal_levels(struct intel_dp *intel_dp)
>   	 * For now, for this unique transition scale selection, set bit
>   	 * 27 for ch0 and ch1.
>   	 */
> -	for (i = 0; i < 4; i++) {
> +	for (i = 0; i < intel_crtc->config->lane_count; i++) {
>   		val = vlv_dpio_read(dev_priv, pipe, CHV_TX_DW3(ch, i));
>   		if (chv_need_uniq_trans_scale(train_set))
>   			val |= DPIO_TX_UNIQ_TRANS_SCALE_EN;
> @@ -3323,9 +3362,11 @@ static uint32_t chv_signal_levels(struct intel_dp *intel_dp)
>   	val |= DPIO_PCS_SWING_CALC_TX0_TX2 | DPIO_PCS_SWING_CALC_TX1_TX3;
>   	vlv_dpio_write(dev_priv, pipe, VLV_PCS01_DW10(ch), val);
>   
> -	val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW10(ch));
> -	val |= DPIO_PCS_SWING_CALC_TX0_TX2 | DPIO_PCS_SWING_CALC_TX1_TX3;
> -	vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW10(ch), val);
> +	if (intel_crtc->config->lane_count > 2) {
> +		val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW10(ch));
> +		val |= DPIO_PCS_SWING_CALC_TX0_TX2 | DPIO_PCS_SWING_CALC_TX1_TX3;
> +		vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW10(ch), val);
> +	}
>   
>   	/* LRC Bypass */
>   	val = vlv_dpio_read(dev_priv, pipe, CHV_CMN_DW30);
> diff --git a/drivers/gpu/drm/i915/intel_drv.h b/drivers/gpu/drm/i915/intel_drv.h
> index 23d5e46..f8a16dc 100644
> --- a/drivers/gpu/drm/i915/intel_drv.h
> +++ b/drivers/gpu/drm/i915/intel_drv.h
> @@ -1370,6 +1370,10 @@ void intel_runtime_pm_put(struct drm_i915_private *dev_priv);
>   
>   void intel_display_set_init_power(struct drm_i915_private *dev, bool enable);
>   
> +void chv_phy_powergate_lanes(struct intel_encoder *encoder,
> +			     bool override, unsigned int mask);
> +
> +
>   /* intel_pm.c */
>   void intel_init_clock_gating(struct drm_device *dev);
>   void intel_suspend_hw(struct drm_device *dev);
> diff --git a/drivers/gpu/drm/i915/intel_hdmi.c b/drivers/gpu/drm/i915/intel_hdmi.c
> index 86b1a2c..b3f6c9f 100644
> --- a/drivers/gpu/drm/i915/intel_hdmi.c
> +++ b/drivers/gpu/drm/i915/intel_hdmi.c
> @@ -1625,6 +1625,8 @@ static void chv_hdmi_pre_pll_enable(struct intel_encoder *encoder)
>   
>   	intel_hdmi_prepare(encoder);
>   
> +	chv_phy_powergate_lanes(encoder, true, 0x0);
> +
>   	mutex_lock(&dev_priv->sb_lock);
>   
>   	/* program left/right clock distribution */
> @@ -1698,6 +1700,8 @@ static void chv_hdmi_post_pll_disable(struct intel_encoder *encoder)
>   	}
>   
>   	mutex_unlock(&dev_priv->sb_lock);
> +
> +	chv_phy_powergate_lanes(encoder, false, 0x0);
>   }
>   
>   static void vlv_hdmi_post_disable(struct intel_encoder *encoder)
> diff --git a/drivers/gpu/drm/i915/intel_runtime_pm.c b/drivers/gpu/drm/i915/intel_runtime_pm.c
> index dab1da9..506a8cc 100644
> --- a/drivers/gpu/drm/i915/intel_runtime_pm.c
> +++ b/drivers/gpu/drm/i915/intel_runtime_pm.c
> @@ -946,14 +946,19 @@ static void chv_dpio_cmn_power_well_enable(struct drm_i915_private *dev_priv,
>   					   struct i915_power_well *power_well)
>   {
>   	enum dpio_phy phy;
> +	enum pipe pipe;
> +	uint32_t tmp;
>   
>   	WARN_ON_ONCE(power_well->data != PUNIT_POWER_WELL_DPIO_CMN_BC &&
>   		     power_well->data != PUNIT_POWER_WELL_DPIO_CMN_D);
>   
> -	if (power_well->data == PUNIT_POWER_WELL_DPIO_CMN_BC)
> +	if (power_well->data == PUNIT_POWER_WELL_DPIO_CMN_BC) {
> +		pipe = PIPE_A;
>   		phy = DPIO_PHY0;
> -	else
> +	} else {
> +		pipe = PIPE_C;
>   		phy = DPIO_PHY1;
> +	}
>   
>   	/* since ref/cri clock was enabled */
>   	udelay(1); /* >10ns for cmnreset, >0ns for sidereset */
> @@ -963,8 +968,26 @@ static void chv_dpio_cmn_power_well_enable(struct drm_i915_private *dev_priv,
>   	if (wait_for(I915_READ(DISPLAY_PHY_STATUS) & PHY_POWERGOOD(phy), 1))
>   		DRM_ERROR("Display PHY %d is not power up\n", phy);
>   
> +	mutex_lock(&dev_priv->sb_lock);
> +
> +	/* Enable dynamic power down */
> +	tmp = vlv_dpio_read(dev_priv, pipe, CHV_CMN_DW28);
> +	tmp |= DPIO_DYNPWRDOWNEN_CH0 | DPIO_CL1POWERDOWNEN;
> +	vlv_dpio_write(dev_priv, pipe, CHV_CMN_DW28, tmp);
> +
> +	if (power_well->data == PUNIT_POWER_WELL_DPIO_CMN_BC) {
> +		tmp = vlv_dpio_read(dev_priv, pipe, _CHV_CMN_DW6_CH1);
> +		tmp |= DPIO_DYNPWRDOWNEN_CH1;
> +		vlv_dpio_write(dev_priv, pipe, _CHV_CMN_DW6_CH1, tmp);
> +	}
> +
> +	mutex_unlock(&dev_priv->sb_lock);
> +
>   	dev_priv->chv_phy_control |= PHY_COM_LANE_RESET_DEASSERT(phy);
>   	I915_WRITE(DISPLAY_PHY_CONTROL, dev_priv->chv_phy_control);
> +
> +	DRM_DEBUG_KMS("Enabled DPIO PHY%d (PHY_CONTROL=0x%08x)\n",
> +		      phy, dev_priv->chv_phy_control);
>   }
>   
>   static void chv_dpio_cmn_power_well_disable(struct drm_i915_private *dev_priv,
> @@ -988,6 +1011,35 @@ static void chv_dpio_cmn_power_well_disable(struct drm_i915_private *dev_priv,
>   	I915_WRITE(DISPLAY_PHY_CONTROL, dev_priv->chv_phy_control);
>   
>   	vlv_set_power_well(dev_priv, power_well, false);
> +
> +	DRM_DEBUG_KMS("Disabled DPIO PHY%d (PHY_CONTROL=0x%08x)\n",
> +		      phy, dev_priv->chv_phy_control);
> +}
> +
> +void chv_phy_powergate_lanes(struct intel_encoder *encoder,
> +			     bool override, unsigned int mask)
> +{
> +	struct drm_i915_private *dev_priv = to_i915(encoder->base.dev);
> +	struct i915_power_domains *power_domains = &dev_priv->power_domains;
> +	enum dpio_phy phy = vlv_dport_to_phy(enc_to_dig_port(&encoder->base));
> +	enum dpio_channel ch = vlv_dport_to_channel(enc_to_dig_port(&encoder->base));
> +
> +	mutex_lock(&power_domains->lock);
> +
> +	dev_priv->chv_phy_control &= ~PHY_CH_POWER_DOWN_OVRD(0xf, phy, ch);
> +	dev_priv->chv_phy_control |= PHY_CH_POWER_DOWN_OVRD(mask, phy, ch);
> +
> +	if (override)
> +		dev_priv->chv_phy_control |= PHY_CH_POWER_DOWN_OVRD_EN(phy, ch);
> +	else
> +		dev_priv->chv_phy_control &= ~PHY_CH_POWER_DOWN_OVRD_EN(phy, ch);
> +
> +	I915_WRITE(DISPLAY_PHY_CONTROL, dev_priv->chv_phy_control);
> +
> +	DRM_DEBUG_KMS("Power gating DPIO PHY%d CH%d lanes 0x%x (PHY_CONTROL=0x%08x)\n",
> +		      phy, ch, mask, dev_priv->chv_phy_control);
> +
> +	mutex_unlock(&power_domains->lock);
>   }
>   
>   static bool chv_pipe_power_well_enabled(struct drm_i915_private *dev_priv,
> @@ -1620,19 +1672,72 @@ static void chv_phy_control_init(struct drm_i915_private *dev_priv)
>   	 * DISPLAY_PHY_CONTROL can get corrupted if read. As a
>   	 * workaround never ever read DISPLAY_PHY_CONTROL, and
>   	 * instead maintain a shadow copy ourselves. Use the actual
> -	 * power well state to reconstruct the expected initial
> -	 * value.
> +	 * power well state and lane status to reconstruct the
> +	 * expected initial value.
>   	 */
>   	dev_priv->chv_phy_control =
>   		PHY_LDO_SEQ_DELAY(PHY_LDO_DELAY_600NS, DPIO_PHY0) |
>   		PHY_LDO_SEQ_DELAY(PHY_LDO_DELAY_600NS, DPIO_PHY1) |
> -		PHY_CH_POWER_MODE(PHY_CH_SU_PSR, DPIO_PHY0, DPIO_CH0) |
> -		PHY_CH_POWER_MODE(PHY_CH_SU_PSR, DPIO_PHY0, DPIO_CH1) |
> -		PHY_CH_POWER_MODE(PHY_CH_SU_PSR, DPIO_PHY1, DPIO_CH0);
> -	if (cmn_bc->ops->is_enabled(dev_priv, cmn_bc))
> +		PHY_CH_POWER_MODE(PHY_CH_DEEP_PSR, DPIO_PHY0, DPIO_CH0) |
> +		PHY_CH_POWER_MODE(PHY_CH_DEEP_PSR, DPIO_PHY0, DPIO_CH1) |
> +		PHY_CH_POWER_MODE(PHY_CH_DEEP_PSR, DPIO_PHY1, DPIO_CH0);
> +
> +	/*
> +	 * If all lanes are disabled we leave the override disabled
> +	 * with all power down bits cleared to match the state we
> +	 * would use after disabling the port. Otherwise enable the
> +	 * override and set the lane powerdown bits accding to the
> +	 * current lane status.
> +	 */
> +	if (cmn_bc->ops->is_enabled(dev_priv, cmn_bc)) {
> +		uint32_t status = I915_READ(DPLL(PIPE_A));
> +		unsigned int mask;
> +
> +		mask = status & DPLL_PORTB_READY_MASK;
> +		if (mask == 0xf)
> +			mask = 0x0;
> +		else
> +			dev_priv->chv_phy_control |=
> +				PHY_CH_POWER_DOWN_OVRD_EN(DPIO_PHY0, DPIO_CH0);
> +
> +		dev_priv->chv_phy_control |=
> +			PHY_CH_POWER_DOWN_OVRD(mask, DPIO_PHY0, DPIO_CH0);
> +
> +		mask = (status & DPLL_PORTC_READY_MASK) >> 4;
> +		if (mask == 0xf)
> +			mask = 0x0;
> +		else
> +			dev_priv->chv_phy_control |=
> +				PHY_CH_POWER_DOWN_OVRD_EN(DPIO_PHY0, DPIO_CH1);
> +
> +		dev_priv->chv_phy_control |=
> +			PHY_CH_POWER_DOWN_OVRD(mask, DPIO_PHY0, DPIO_CH1);
> +
>   		dev_priv->chv_phy_control |= PHY_COM_LANE_RESET_DEASSERT(DPIO_PHY0);
> -	if (cmn_d->ops->is_enabled(dev_priv, cmn_d))
> +	}
> +
> +	if (cmn_d->ops->is_enabled(dev_priv, cmn_d)) {
> +		uint32_t status = I915_READ(DPIO_PHY_STATUS);
> +		unsigned int mask;
> +
> +		mask = status & DPLL_PORTD_READY_MASK;
> +
> +		if (mask == 0xf)
> +			mask = 0x0;
> +		else
> +			dev_priv->chv_phy_control |=
> +				PHY_CH_POWER_DOWN_OVRD_EN(DPIO_PHY1, DPIO_CH0);
> +
> +		dev_priv->chv_phy_control |=
> +			PHY_CH_POWER_DOWN_OVRD(mask, DPIO_PHY1, DPIO_CH0);
> +
>   		dev_priv->chv_phy_control |= PHY_COM_LANE_RESET_DEASSERT(DPIO_PHY1);
> +	}
> +
> +	I915_WRITE(DISPLAY_PHY_CONTROL, dev_priv->chv_phy_control);
> +
> +	DRM_DEBUG_KMS("Initial PHY_CONTROL=0x%08x\n",
> +		      dev_priv->chv_phy_control);
>   }
>   
>   static void vlv_cmnlane_wa(struct drm_i915_private *dev_priv)
Looks fine i think we have covered all the scenarios.
Reviewed-by: Deepak S <deepak.s@linux.intel.com>
Daniel Vetter Aug. 26, 2015, 8:27 a.m. UTC | #2
On Wed, Jul 08, 2015 at 11:45:54PM +0300, ville.syrjala@linux.intel.com wrote:
> diff --git a/drivers/gpu/drm/i915/intel_runtime_pm.c b/drivers/gpu/drm/i915/intel_runtime_pm.c
> index dab1da9..506a8cc 100644
> --- a/drivers/gpu/drm/i915/intel_runtime_pm.c
> +++ b/drivers/gpu/drm/i915/intel_runtime_pm.c
> @@ -946,14 +946,19 @@ static void chv_dpio_cmn_power_well_enable(struct drm_i915_private *dev_priv,
>  					   struct i915_power_well *power_well)
>  {
>  	enum dpio_phy phy;
> +	enum pipe pipe;
> +	uint32_t tmp;
>  
>  	WARN_ON_ONCE(power_well->data != PUNIT_POWER_WELL_DPIO_CMN_BC &&
>  		     power_well->data != PUNIT_POWER_WELL_DPIO_CMN_D);
>  
> -	if (power_well->data == PUNIT_POWER_WELL_DPIO_CMN_BC)
> +	if (power_well->data == PUNIT_POWER_WELL_DPIO_CMN_BC) {
> +		pipe = PIPE_A;
>  		phy = DPIO_PHY0;
> -	else
> +	} else {
> +		pipe = PIPE_C;
>  		phy = DPIO_PHY1;
> +	}
>  
>  	/* since ref/cri clock was enabled */
>  	udelay(1); /* >10ns for cmnreset, >0ns for sidereset */

I have some minor context conflicts in this hunk and the next one and it
looks like I'm missing a patch. Since I'm not sure what's going on here
please either rebase or tell me which series I'm missing.

Merged the first 7 patches from this series meanwhile, thanks.
-Daniel

> @@ -963,8 +968,26 @@ static void chv_dpio_cmn_power_well_enable(struct drm_i915_private *dev_priv,
>  	if (wait_for(I915_READ(DISPLAY_PHY_STATUS) & PHY_POWERGOOD(phy), 1))
>  		DRM_ERROR("Display PHY %d is not power up\n", phy);
>  
> +	mutex_lock(&dev_priv->sb_lock);
> +
> +	/* Enable dynamic power down */
> +	tmp = vlv_dpio_read(dev_priv, pipe, CHV_CMN_DW28);
> +	tmp |= DPIO_DYNPWRDOWNEN_CH0 | DPIO_CL1POWERDOWNEN;
> +	vlv_dpio_write(dev_priv, pipe, CHV_CMN_DW28, tmp);
> +
> +	if (power_well->data == PUNIT_POWER_WELL_DPIO_CMN_BC) {
> +		tmp = vlv_dpio_read(dev_priv, pipe, _CHV_CMN_DW6_CH1);
> +		tmp |= DPIO_DYNPWRDOWNEN_CH1;
> +		vlv_dpio_write(dev_priv, pipe, _CHV_CMN_DW6_CH1, tmp);
> +	}
> +
> +	mutex_unlock(&dev_priv->sb_lock);
> +
>  	dev_priv->chv_phy_control |= PHY_COM_LANE_RESET_DEASSERT(phy);
>  	I915_WRITE(DISPLAY_PHY_CONTROL, dev_priv->chv_phy_control);
> +
> +	DRM_DEBUG_KMS("Enabled DPIO PHY%d (PHY_CONTROL=0x%08x)\n",
> +		      phy, dev_priv->chv_phy_control);
>  }
>  
>  static void chv_dpio_cmn_power_well_disable(struct drm_i915_private *dev_priv,
> @@ -988,6 +1011,35 @@ static void chv_dpio_cmn_power_well_disable(struct drm_i915_private *dev_priv,
>  	I915_WRITE(DISPLAY_PHY_CONTROL, dev_priv->chv_phy_control);
>  
>  	vlv_set_power_well(dev_priv, power_well, false);
> +
> +	DRM_DEBUG_KMS("Disabled DPIO PHY%d (PHY_CONTROL=0x%08x)\n",
> +		      phy, dev_priv->chv_phy_control);
> +}
> +
> +void chv_phy_powergate_lanes(struct intel_encoder *encoder,
> +			     bool override, unsigned int mask)
> +{
> +	struct drm_i915_private *dev_priv = to_i915(encoder->base.dev);
> +	struct i915_power_domains *power_domains = &dev_priv->power_domains;
> +	enum dpio_phy phy = vlv_dport_to_phy(enc_to_dig_port(&encoder->base));
> +	enum dpio_channel ch = vlv_dport_to_channel(enc_to_dig_port(&encoder->base));
> +
> +	mutex_lock(&power_domains->lock);
> +
> +	dev_priv->chv_phy_control &= ~PHY_CH_POWER_DOWN_OVRD(0xf, phy, ch);
> +	dev_priv->chv_phy_control |= PHY_CH_POWER_DOWN_OVRD(mask, phy, ch);
> +
> +	if (override)
> +		dev_priv->chv_phy_control |= PHY_CH_POWER_DOWN_OVRD_EN(phy, ch);
> +	else
> +		dev_priv->chv_phy_control &= ~PHY_CH_POWER_DOWN_OVRD_EN(phy, ch);
> +
> +	I915_WRITE(DISPLAY_PHY_CONTROL, dev_priv->chv_phy_control);
> +
> +	DRM_DEBUG_KMS("Power gating DPIO PHY%d CH%d lanes 0x%x (PHY_CONTROL=0x%08x)\n",
> +		      phy, ch, mask, dev_priv->chv_phy_control);
> +
> +	mutex_unlock(&power_domains->lock);
>  }
>  
>  static bool chv_pipe_power_well_enabled(struct drm_i915_private *dev_priv,
> @@ -1620,19 +1672,72 @@ static void chv_phy_control_init(struct drm_i915_private *dev_priv)
>  	 * DISPLAY_PHY_CONTROL can get corrupted if read. As a
>  	 * workaround never ever read DISPLAY_PHY_CONTROL, and
>  	 * instead maintain a shadow copy ourselves. Use the actual
> -	 * power well state to reconstruct the expected initial
> -	 * value.
> +	 * power well state and lane status to reconstruct the
> +	 * expected initial value.
>  	 */
>  	dev_priv->chv_phy_control =
>  		PHY_LDO_SEQ_DELAY(PHY_LDO_DELAY_600NS, DPIO_PHY0) |
>  		PHY_LDO_SEQ_DELAY(PHY_LDO_DELAY_600NS, DPIO_PHY1) |
> -		PHY_CH_POWER_MODE(PHY_CH_SU_PSR, DPIO_PHY0, DPIO_CH0) |
> -		PHY_CH_POWER_MODE(PHY_CH_SU_PSR, DPIO_PHY0, DPIO_CH1) |
> -		PHY_CH_POWER_MODE(PHY_CH_SU_PSR, DPIO_PHY1, DPIO_CH0);
> -	if (cmn_bc->ops->is_enabled(dev_priv, cmn_bc))
> +		PHY_CH_POWER_MODE(PHY_CH_DEEP_PSR, DPIO_PHY0, DPIO_CH0) |
> +		PHY_CH_POWER_MODE(PHY_CH_DEEP_PSR, DPIO_PHY0, DPIO_CH1) |
> +		PHY_CH_POWER_MODE(PHY_CH_DEEP_PSR, DPIO_PHY1, DPIO_CH0);
> +
> +	/*
> +	 * If all lanes are disabled we leave the override disabled
> +	 * with all power down bits cleared to match the state we
> +	 * would use after disabling the port. Otherwise enable the
> +	 * override and set the lane powerdown bits accding to the
> +	 * current lane status.
> +	 */
> +	if (cmn_bc->ops->is_enabled(dev_priv, cmn_bc)) {
> +		uint32_t status = I915_READ(DPLL(PIPE_A));
> +		unsigned int mask;
> +
> +		mask = status & DPLL_PORTB_READY_MASK;
> +		if (mask == 0xf)
> +			mask = 0x0;
> +		else
> +			dev_priv->chv_phy_control |=
> +				PHY_CH_POWER_DOWN_OVRD_EN(DPIO_PHY0, DPIO_CH0);
> +
> +		dev_priv->chv_phy_control |=
> +			PHY_CH_POWER_DOWN_OVRD(mask, DPIO_PHY0, DPIO_CH0);
> +
> +		mask = (status & DPLL_PORTC_READY_MASK) >> 4;
> +		if (mask == 0xf)
> +			mask = 0x0;
> +		else
> +			dev_priv->chv_phy_control |=
> +				PHY_CH_POWER_DOWN_OVRD_EN(DPIO_PHY0, DPIO_CH1);
> +
> +		dev_priv->chv_phy_control |=
> +			PHY_CH_POWER_DOWN_OVRD(mask, DPIO_PHY0, DPIO_CH1);
> +
>  		dev_priv->chv_phy_control |= PHY_COM_LANE_RESET_DEASSERT(DPIO_PHY0);
> -	if (cmn_d->ops->is_enabled(dev_priv, cmn_d))
> +	}
> +
> +	if (cmn_d->ops->is_enabled(dev_priv, cmn_d)) {
> +		uint32_t status = I915_READ(DPIO_PHY_STATUS);
> +		unsigned int mask;
> +
> +		mask = status & DPLL_PORTD_READY_MASK;
> +
> +		if (mask == 0xf)
> +			mask = 0x0;
> +		else
> +			dev_priv->chv_phy_control |=
> +				PHY_CH_POWER_DOWN_OVRD_EN(DPIO_PHY1, DPIO_CH0);
> +
> +		dev_priv->chv_phy_control |=
> +			PHY_CH_POWER_DOWN_OVRD(mask, DPIO_PHY1, DPIO_CH0);
> +
>  		dev_priv->chv_phy_control |= PHY_COM_LANE_RESET_DEASSERT(DPIO_PHY1);
> +	}
> +
> +	I915_WRITE(DISPLAY_PHY_CONTROL, dev_priv->chv_phy_control);
> +
> +	DRM_DEBUG_KMS("Initial PHY_CONTROL=0x%08x\n",
> +		      dev_priv->chv_phy_control);
>  }
>  
>  static void vlv_cmnlane_wa(struct drm_i915_private *dev_priv)
> -- 
> 2.3.6
> 
> _______________________________________________
> Intel-gfx mailing list
> Intel-gfx@lists.freedesktop.org
> http://lists.freedesktop.org/mailman/listinfo/intel-gfx
diff mbox

Patch

diff --git a/drivers/gpu/drm/i915/i915_reg.h b/drivers/gpu/drm/i915/i915_reg.h
index 17cb7e5..bcfcbb62 100644
--- a/drivers/gpu/drm/i915/i915_reg.h
+++ b/drivers/gpu/drm/i915/i915_reg.h
@@ -1125,9 +1125,15 @@  enum skl_disp_power_wells {
 
 #define _CHV_CMN_DW19_CH0		0x814c
 #define _CHV_CMN_DW6_CH1		0x8098
+#define   DPIO_DYNPWRDOWNEN_CH1		(1 << 28) /* CL2 DW6 only */
 #define   CHV_CMN_USEDCLKCHANNEL	(1 << 13)
+
 #define CHV_CMN_DW19(ch) _PIPE(ch, _CHV_CMN_DW19_CH0, _CHV_CMN_DW6_CH1)
 
+#define CHV_CMN_DW28			0x8170
+#define   DPIO_CL1POWERDOWNEN		(1 << 23)
+#define   DPIO_DYNPWRDOWNEN_CH0		(1 << 22)
+
 #define CHV_CMN_DW30			0x8178
 #define   DPIO_LRC_BYPASS		(1 << 3)
 
@@ -2175,10 +2181,12 @@  enum skl_disp_power_wells {
 #define DPIO_PHY_STATUS			(VLV_DISPLAY_BASE + 0x6240)
 #define   DPLL_PORTD_READY_MASK		(0xf)
 #define DISPLAY_PHY_CONTROL (VLV_DISPLAY_BASE + 0x60100)
+#define   PHY_CH_POWER_DOWN_OVRD_EN(phy, ch)	(1 << (2*(phy)+(ch)+27))
 #define   PHY_LDO_DELAY_0NS			0x0
 #define   PHY_LDO_DELAY_200NS			0x1
 #define   PHY_LDO_DELAY_600NS			0x2
 #define   PHY_LDO_SEQ_DELAY(delay, phy)		((delay) << (2*(phy)+23))
+#define   PHY_CH_POWER_DOWN_OVRD(mask, phy, ch)	((mask) << (8*(phy)+4*(ch)+11))
 #define   PHY_CH_SU_PSR				0x1
 #define   PHY_CH_DEEP_PSR			0x7
 #define   PHY_CH_POWER_MODE(mode, phy, ch)	((mode) << (6*(phy)+3*(ch)+2))
diff --git a/drivers/gpu/drm/i915/intel_dp.c b/drivers/gpu/drm/i915/intel_dp.c
index 40b8430..6058129 100644
--- a/drivers/gpu/drm/i915/intel_dp.c
+++ b/drivers/gpu/drm/i915/intel_dp.c
@@ -133,6 +133,11 @@  static void vlv_init_panel_power_sequencer(struct intel_dp *intel_dp);
 static void vlv_steal_power_sequencer(struct drm_device *dev,
 				      enum pipe pipe);
 
+static unsigned int intel_dp_unused_lane_mask(int lane_count)
+{
+	return ~((1 << lane_count) - 1) & 0xf;
+}
+
 static int
 intel_dp_max_link_bw(struct intel_dp  *intel_dp)
 {
@@ -2395,17 +2400,21 @@  static void chv_post_disable_dp(struct intel_encoder *encoder)
 	val |= CHV_PCS_REQ_SOFTRESET_EN;
 	vlv_dpio_write(dev_priv, pipe, VLV_PCS01_DW1(ch), val);
 
-	val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW1(ch));
-	val |= CHV_PCS_REQ_SOFTRESET_EN;
-	vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW1(ch), val);
+	if (intel_crtc->config->lane_count > 2) {
+		val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW1(ch));
+		val |= CHV_PCS_REQ_SOFTRESET_EN;
+		vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW1(ch), val);
+	}
 
 	val = vlv_dpio_read(dev_priv, pipe, VLV_PCS01_DW0(ch));
 	val &= ~(DPIO_PCS_TX_LANE2_RESET | DPIO_PCS_TX_LANE1_RESET);
 	vlv_dpio_write(dev_priv, pipe, VLV_PCS01_DW0(ch), val);
 
-	val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW0(ch));
-	val &= ~(DPIO_PCS_TX_LANE2_RESET | DPIO_PCS_TX_LANE1_RESET);
-	vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW0(ch), val);
+	if (intel_crtc->config->lane_count > 2) {
+		val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW0(ch));
+		val &= ~(DPIO_PCS_TX_LANE2_RESET | DPIO_PCS_TX_LANE1_RESET);
+		vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW0(ch), val);
+	}
 
 	mutex_unlock(&dev_priv->sb_lock);
 }
@@ -2525,7 +2534,6 @@  static void intel_enable_dp(struct intel_encoder *encoder)
 	struct drm_i915_private *dev_priv = dev->dev_private;
 	struct intel_crtc *crtc = to_intel_crtc(encoder->base.crtc);
 	uint32_t dp_reg = I915_READ(intel_dp->output_reg);
-	unsigned int lane_mask = 0x0;
 
 	if (WARN_ON(dp_reg & DP_PORT_EN))
 		return;
@@ -2543,9 +2551,15 @@  static void intel_enable_dp(struct intel_encoder *encoder)
 
 	pps_unlock(intel_dp);
 
-	if (IS_VALLEYVIEW(dev))
+	if (IS_VALLEYVIEW(dev)) {
+		unsigned int lane_mask = 0x0;
+
+		if (IS_CHERRYVIEW(dev))
+			lane_mask = intel_dp_unused_lane_mask(crtc->config->lane_count);
+
 		vlv_wait_port_ready(dev_priv, dp_to_dig_port(intel_dp),
 				    lane_mask);
+	}
 
 	intel_dp_sink_dpms(intel_dp, DRM_MODE_DPMS_ON);
 	intel_dp_start_link_train(intel_dp);
@@ -2772,31 +2786,40 @@  static void chv_pre_enable_dp(struct intel_encoder *encoder)
 	val &= ~DPIO_LANEDESKEW_STRAP_OVRD;
 	vlv_dpio_write(dev_priv, pipe, VLV_PCS01_DW11(ch), val);
 
-	val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW11(ch));
-	val &= ~DPIO_LANEDESKEW_STRAP_OVRD;
-	vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW11(ch), val);
+	if (intel_crtc->config->lane_count > 2) {
+		val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW11(ch));
+		val &= ~DPIO_LANEDESKEW_STRAP_OVRD;
+		vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW11(ch), val);
+	}
 
 	/* Deassert soft data lane reset*/
 	val = vlv_dpio_read(dev_priv, pipe, VLV_PCS01_DW1(ch));
 	val |= CHV_PCS_REQ_SOFTRESET_EN;
 	vlv_dpio_write(dev_priv, pipe, VLV_PCS01_DW1(ch), val);
 
-	val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW1(ch));
-	val |= CHV_PCS_REQ_SOFTRESET_EN;
-	vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW1(ch), val);
+	if (intel_crtc->config->lane_count > 2) {
+		val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW1(ch));
+		val |= CHV_PCS_REQ_SOFTRESET_EN;
+		vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW1(ch), val);
+	}
 
 	val = vlv_dpio_read(dev_priv, pipe, VLV_PCS01_DW0(ch));
 	val |= (DPIO_PCS_TX_LANE2_RESET | DPIO_PCS_TX_LANE1_RESET);
 	vlv_dpio_write(dev_priv, pipe, VLV_PCS01_DW0(ch), val);
 
-	val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW0(ch));
-	val |= (DPIO_PCS_TX_LANE2_RESET | DPIO_PCS_TX_LANE1_RESET);
-	vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW0(ch), val);
+	if (intel_crtc->config->lane_count > 2) {
+		val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW0(ch));
+		val |= (DPIO_PCS_TX_LANE2_RESET | DPIO_PCS_TX_LANE1_RESET);
+		vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW0(ch), val);
+	}
 
 	/* Program Tx lane latency optimal setting*/
-	for (i = 0; i < 4; i++) {
+	for (i = 0; i < intel_crtc->config->lane_count; i++) {
 		/* Set the upar bit */
-		data = (i == 1) ? 0x0 : 0x1;
+		if (intel_crtc->config->lane_count == 1)
+			data = 0x0;
+		else
+			data = (i == 1) ? 0x0 : 0x1;
 		vlv_dpio_write(dev_priv, pipe, CHV_TX_DW14(ch, i),
 				data << DPIO_UPAR_SHIFT);
 	}
@@ -2817,9 +2840,11 @@  static void chv_pre_enable_dp(struct intel_encoder *encoder)
 	val |= DPIO_TX2_STAGGER_MASK(0x1f);
 	vlv_dpio_write(dev_priv, pipe, VLV_PCS01_DW11(ch), val);
 
-	val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW11(ch));
-	val |= DPIO_TX2_STAGGER_MASK(0x1f);
-	vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW11(ch), val);
+	if (intel_crtc->config->lane_count > 2) {
+		val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW11(ch));
+		val |= DPIO_TX2_STAGGER_MASK(0x1f);
+		vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW11(ch), val);
+	}
 
 	vlv_dpio_write(dev_priv, pipe, VLV_PCS01_DW12(ch),
 		       DPIO_LANESTAGGER_STRAP(stagger) |
@@ -2828,12 +2853,14 @@  static void chv_pre_enable_dp(struct intel_encoder *encoder)
 		       DPIO_TX1_STAGGER_MULT(6) |
 		       DPIO_TX2_STAGGER_MULT(0));
 
-	vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW12(ch),
-		       DPIO_LANESTAGGER_STRAP(stagger) |
-		       DPIO_LANESTAGGER_STRAP_OVRD |
-		       DPIO_TX1_STAGGER_MASK(0x1f) |
-		       DPIO_TX1_STAGGER_MULT(7) |
-		       DPIO_TX2_STAGGER_MULT(5));
+	if (intel_crtc->config->lane_count > 2) {
+		vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW12(ch),
+			       DPIO_LANESTAGGER_STRAP(stagger) |
+			       DPIO_LANESTAGGER_STRAP_OVRD |
+			       DPIO_TX1_STAGGER_MASK(0x1f) |
+			       DPIO_TX1_STAGGER_MULT(7) |
+			       DPIO_TX2_STAGGER_MULT(5));
+	}
 
 	mutex_unlock(&dev_priv->sb_lock);
 
@@ -2849,10 +2876,14 @@  static void chv_dp_pre_pll_enable(struct intel_encoder *encoder)
 		to_intel_crtc(encoder->base.crtc);
 	enum dpio_channel ch = vlv_dport_to_channel(dport);
 	enum pipe pipe = intel_crtc->pipe;
+	unsigned int lane_mask =
+		intel_dp_unused_lane_mask(intel_crtc->config->lane_count);
 	u32 val;
 
 	intel_dp_prepare(encoder);
 
+	chv_phy_powergate_lanes(encoder, true, lane_mask);
+
 	mutex_lock(&dev_priv->sb_lock);
 
 	/* program left/right clock distribution */
@@ -2883,13 +2914,15 @@  static void chv_dp_pre_pll_enable(struct intel_encoder *encoder)
 		val |= CHV_PCS_USEDCLKCHANNEL;
 	vlv_dpio_write(dev_priv, pipe, VLV_PCS01_DW8(ch), val);
 
-	val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW8(ch));
-	val |= CHV_PCS_USEDCLKCHANNEL_OVRRIDE;
-	if (pipe != PIPE_B)
-		val &= ~CHV_PCS_USEDCLKCHANNEL;
-	else
-		val |= CHV_PCS_USEDCLKCHANNEL;
-	vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW8(ch), val);
+	if (intel_crtc->config->lane_count > 2) {
+		val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW8(ch));
+		val |= CHV_PCS_USEDCLKCHANNEL_OVRRIDE;
+		if (pipe != PIPE_B)
+			val &= ~CHV_PCS_USEDCLKCHANNEL;
+		else
+			val |= CHV_PCS_USEDCLKCHANNEL;
+		vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW8(ch), val);
+	}
 
 	/*
 	 * This a a bit weird since generally CL
@@ -2926,6 +2959,8 @@  static void chv_dp_post_pll_disable(struct intel_encoder *encoder)
 	}
 
 	mutex_unlock(&dev_priv->sb_lock);
+
+	chv_phy_powergate_lanes(encoder, false, 0x0);
 }
 
 /*
@@ -3261,24 +3296,28 @@  static uint32_t chv_signal_levels(struct intel_dp *intel_dp)
 	val |= DPIO_PCS_TX1DEEMP_9P5 | DPIO_PCS_TX2DEEMP_9P5;
 	vlv_dpio_write(dev_priv, pipe, VLV_PCS01_DW10(ch), val);
 
-	val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW10(ch));
-	val &= ~(DPIO_PCS_SWING_CALC_TX0_TX2 | DPIO_PCS_SWING_CALC_TX1_TX3);
-	val &= ~(DPIO_PCS_TX1DEEMP_MASK | DPIO_PCS_TX2DEEMP_MASK);
-	val |= DPIO_PCS_TX1DEEMP_9P5 | DPIO_PCS_TX2DEEMP_9P5;
-	vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW10(ch), val);
+	if (intel_crtc->config->lane_count > 2) {
+		val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW10(ch));
+		val &= ~(DPIO_PCS_SWING_CALC_TX0_TX2 | DPIO_PCS_SWING_CALC_TX1_TX3);
+		val &= ~(DPIO_PCS_TX1DEEMP_MASK | DPIO_PCS_TX2DEEMP_MASK);
+		val |= DPIO_PCS_TX1DEEMP_9P5 | DPIO_PCS_TX2DEEMP_9P5;
+		vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW10(ch), val);
+	}
 
 	val = vlv_dpio_read(dev_priv, pipe, VLV_PCS01_DW9(ch));
 	val &= ~(DPIO_PCS_TX1MARGIN_MASK | DPIO_PCS_TX2MARGIN_MASK);
 	val |= DPIO_PCS_TX1MARGIN_000 | DPIO_PCS_TX2MARGIN_000;
 	vlv_dpio_write(dev_priv, pipe, VLV_PCS01_DW9(ch), val);
 
-	val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW9(ch));
-	val &= ~(DPIO_PCS_TX1MARGIN_MASK | DPIO_PCS_TX2MARGIN_MASK);
-	val |= DPIO_PCS_TX1MARGIN_000 | DPIO_PCS_TX2MARGIN_000;
-	vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW9(ch), val);
+	if (intel_crtc->config->lane_count > 2) {
+		val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW9(ch));
+		val &= ~(DPIO_PCS_TX1MARGIN_MASK | DPIO_PCS_TX2MARGIN_MASK);
+		val |= DPIO_PCS_TX1MARGIN_000 | DPIO_PCS_TX2MARGIN_000;
+		vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW9(ch), val);
+	}
 
 	/* Program swing deemph */
-	for (i = 0; i < 4; i++) {
+	for (i = 0; i < intel_crtc->config->lane_count; i++) {
 		val = vlv_dpio_read(dev_priv, pipe, CHV_TX_DW4(ch, i));
 		val &= ~DPIO_SWING_DEEMPH9P5_MASK;
 		val |= deemph_reg_value << DPIO_SWING_DEEMPH9P5_SHIFT;
@@ -3286,7 +3325,7 @@  static uint32_t chv_signal_levels(struct intel_dp *intel_dp)
 	}
 
 	/* Program swing margin */
-	for (i = 0; i < 4; i++) {
+	for (i = 0; i < intel_crtc->config->lane_count; i++) {
 		val = vlv_dpio_read(dev_priv, pipe, CHV_TX_DW2(ch, i));
 
 		val &= ~DPIO_SWING_MARGIN000_MASK;
@@ -3309,7 +3348,7 @@  static uint32_t chv_signal_levels(struct intel_dp *intel_dp)
 	 * For now, for this unique transition scale selection, set bit
 	 * 27 for ch0 and ch1.
 	 */
-	for (i = 0; i < 4; i++) {
+	for (i = 0; i < intel_crtc->config->lane_count; i++) {
 		val = vlv_dpio_read(dev_priv, pipe, CHV_TX_DW3(ch, i));
 		if (chv_need_uniq_trans_scale(train_set))
 			val |= DPIO_TX_UNIQ_TRANS_SCALE_EN;
@@ -3323,9 +3362,11 @@  static uint32_t chv_signal_levels(struct intel_dp *intel_dp)
 	val |= DPIO_PCS_SWING_CALC_TX0_TX2 | DPIO_PCS_SWING_CALC_TX1_TX3;
 	vlv_dpio_write(dev_priv, pipe, VLV_PCS01_DW10(ch), val);
 
-	val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW10(ch));
-	val |= DPIO_PCS_SWING_CALC_TX0_TX2 | DPIO_PCS_SWING_CALC_TX1_TX3;
-	vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW10(ch), val);
+	if (intel_crtc->config->lane_count > 2) {
+		val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW10(ch));
+		val |= DPIO_PCS_SWING_CALC_TX0_TX2 | DPIO_PCS_SWING_CALC_TX1_TX3;
+		vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW10(ch), val);
+	}
 
 	/* LRC Bypass */
 	val = vlv_dpio_read(dev_priv, pipe, CHV_CMN_DW30);
diff --git a/drivers/gpu/drm/i915/intel_drv.h b/drivers/gpu/drm/i915/intel_drv.h
index 23d5e46..f8a16dc 100644
--- a/drivers/gpu/drm/i915/intel_drv.h
+++ b/drivers/gpu/drm/i915/intel_drv.h
@@ -1370,6 +1370,10 @@  void intel_runtime_pm_put(struct drm_i915_private *dev_priv);
 
 void intel_display_set_init_power(struct drm_i915_private *dev, bool enable);
 
+void chv_phy_powergate_lanes(struct intel_encoder *encoder,
+			     bool override, unsigned int mask);
+
+
 /* intel_pm.c */
 void intel_init_clock_gating(struct drm_device *dev);
 void intel_suspend_hw(struct drm_device *dev);
diff --git a/drivers/gpu/drm/i915/intel_hdmi.c b/drivers/gpu/drm/i915/intel_hdmi.c
index 86b1a2c..b3f6c9f 100644
--- a/drivers/gpu/drm/i915/intel_hdmi.c
+++ b/drivers/gpu/drm/i915/intel_hdmi.c
@@ -1625,6 +1625,8 @@  static void chv_hdmi_pre_pll_enable(struct intel_encoder *encoder)
 
 	intel_hdmi_prepare(encoder);
 
+	chv_phy_powergate_lanes(encoder, true, 0x0);
+
 	mutex_lock(&dev_priv->sb_lock);
 
 	/* program left/right clock distribution */
@@ -1698,6 +1700,8 @@  static void chv_hdmi_post_pll_disable(struct intel_encoder *encoder)
 	}
 
 	mutex_unlock(&dev_priv->sb_lock);
+
+	chv_phy_powergate_lanes(encoder, false, 0x0);
 }
 
 static void vlv_hdmi_post_disable(struct intel_encoder *encoder)
diff --git a/drivers/gpu/drm/i915/intel_runtime_pm.c b/drivers/gpu/drm/i915/intel_runtime_pm.c
index dab1da9..506a8cc 100644
--- a/drivers/gpu/drm/i915/intel_runtime_pm.c
+++ b/drivers/gpu/drm/i915/intel_runtime_pm.c
@@ -946,14 +946,19 @@  static void chv_dpio_cmn_power_well_enable(struct drm_i915_private *dev_priv,
 					   struct i915_power_well *power_well)
 {
 	enum dpio_phy phy;
+	enum pipe pipe;
+	uint32_t tmp;
 
 	WARN_ON_ONCE(power_well->data != PUNIT_POWER_WELL_DPIO_CMN_BC &&
 		     power_well->data != PUNIT_POWER_WELL_DPIO_CMN_D);
 
-	if (power_well->data == PUNIT_POWER_WELL_DPIO_CMN_BC)
+	if (power_well->data == PUNIT_POWER_WELL_DPIO_CMN_BC) {
+		pipe = PIPE_A;
 		phy = DPIO_PHY0;
-	else
+	} else {
+		pipe = PIPE_C;
 		phy = DPIO_PHY1;
+	}
 
 	/* since ref/cri clock was enabled */
 	udelay(1); /* >10ns for cmnreset, >0ns for sidereset */
@@ -963,8 +968,26 @@  static void chv_dpio_cmn_power_well_enable(struct drm_i915_private *dev_priv,
 	if (wait_for(I915_READ(DISPLAY_PHY_STATUS) & PHY_POWERGOOD(phy), 1))
 		DRM_ERROR("Display PHY %d is not power up\n", phy);
 
+	mutex_lock(&dev_priv->sb_lock);
+
+	/* Enable dynamic power down */
+	tmp = vlv_dpio_read(dev_priv, pipe, CHV_CMN_DW28);
+	tmp |= DPIO_DYNPWRDOWNEN_CH0 | DPIO_CL1POWERDOWNEN;
+	vlv_dpio_write(dev_priv, pipe, CHV_CMN_DW28, tmp);
+
+	if (power_well->data == PUNIT_POWER_WELL_DPIO_CMN_BC) {
+		tmp = vlv_dpio_read(dev_priv, pipe, _CHV_CMN_DW6_CH1);
+		tmp |= DPIO_DYNPWRDOWNEN_CH1;
+		vlv_dpio_write(dev_priv, pipe, _CHV_CMN_DW6_CH1, tmp);
+	}
+
+	mutex_unlock(&dev_priv->sb_lock);
+
 	dev_priv->chv_phy_control |= PHY_COM_LANE_RESET_DEASSERT(phy);
 	I915_WRITE(DISPLAY_PHY_CONTROL, dev_priv->chv_phy_control);
+
+	DRM_DEBUG_KMS("Enabled DPIO PHY%d (PHY_CONTROL=0x%08x)\n",
+		      phy, dev_priv->chv_phy_control);
 }
 
 static void chv_dpio_cmn_power_well_disable(struct drm_i915_private *dev_priv,
@@ -988,6 +1011,35 @@  static void chv_dpio_cmn_power_well_disable(struct drm_i915_private *dev_priv,
 	I915_WRITE(DISPLAY_PHY_CONTROL, dev_priv->chv_phy_control);
 
 	vlv_set_power_well(dev_priv, power_well, false);
+
+	DRM_DEBUG_KMS("Disabled DPIO PHY%d (PHY_CONTROL=0x%08x)\n",
+		      phy, dev_priv->chv_phy_control);
+}
+
+void chv_phy_powergate_lanes(struct intel_encoder *encoder,
+			     bool override, unsigned int mask)
+{
+	struct drm_i915_private *dev_priv = to_i915(encoder->base.dev);
+	struct i915_power_domains *power_domains = &dev_priv->power_domains;
+	enum dpio_phy phy = vlv_dport_to_phy(enc_to_dig_port(&encoder->base));
+	enum dpio_channel ch = vlv_dport_to_channel(enc_to_dig_port(&encoder->base));
+
+	mutex_lock(&power_domains->lock);
+
+	dev_priv->chv_phy_control &= ~PHY_CH_POWER_DOWN_OVRD(0xf, phy, ch);
+	dev_priv->chv_phy_control |= PHY_CH_POWER_DOWN_OVRD(mask, phy, ch);
+
+	if (override)
+		dev_priv->chv_phy_control |= PHY_CH_POWER_DOWN_OVRD_EN(phy, ch);
+	else
+		dev_priv->chv_phy_control &= ~PHY_CH_POWER_DOWN_OVRD_EN(phy, ch);
+
+	I915_WRITE(DISPLAY_PHY_CONTROL, dev_priv->chv_phy_control);
+
+	DRM_DEBUG_KMS("Power gating DPIO PHY%d CH%d lanes 0x%x (PHY_CONTROL=0x%08x)\n",
+		      phy, ch, mask, dev_priv->chv_phy_control);
+
+	mutex_unlock(&power_domains->lock);
 }
 
 static bool chv_pipe_power_well_enabled(struct drm_i915_private *dev_priv,
@@ -1620,19 +1672,72 @@  static void chv_phy_control_init(struct drm_i915_private *dev_priv)
 	 * DISPLAY_PHY_CONTROL can get corrupted if read. As a
 	 * workaround never ever read DISPLAY_PHY_CONTROL, and
 	 * instead maintain a shadow copy ourselves. Use the actual
-	 * power well state to reconstruct the expected initial
-	 * value.
+	 * power well state and lane status to reconstruct the
+	 * expected initial value.
 	 */
 	dev_priv->chv_phy_control =
 		PHY_LDO_SEQ_DELAY(PHY_LDO_DELAY_600NS, DPIO_PHY0) |
 		PHY_LDO_SEQ_DELAY(PHY_LDO_DELAY_600NS, DPIO_PHY1) |
-		PHY_CH_POWER_MODE(PHY_CH_SU_PSR, DPIO_PHY0, DPIO_CH0) |
-		PHY_CH_POWER_MODE(PHY_CH_SU_PSR, DPIO_PHY0, DPIO_CH1) |
-		PHY_CH_POWER_MODE(PHY_CH_SU_PSR, DPIO_PHY1, DPIO_CH0);
-	if (cmn_bc->ops->is_enabled(dev_priv, cmn_bc))
+		PHY_CH_POWER_MODE(PHY_CH_DEEP_PSR, DPIO_PHY0, DPIO_CH0) |
+		PHY_CH_POWER_MODE(PHY_CH_DEEP_PSR, DPIO_PHY0, DPIO_CH1) |
+		PHY_CH_POWER_MODE(PHY_CH_DEEP_PSR, DPIO_PHY1, DPIO_CH0);
+
+	/*
+	 * If all lanes are disabled we leave the override disabled
+	 * with all power down bits cleared to match the state we
+	 * would use after disabling the port. Otherwise enable the
+	 * override and set the lane powerdown bits accding to the
+	 * current lane status.
+	 */
+	if (cmn_bc->ops->is_enabled(dev_priv, cmn_bc)) {
+		uint32_t status = I915_READ(DPLL(PIPE_A));
+		unsigned int mask;
+
+		mask = status & DPLL_PORTB_READY_MASK;
+		if (mask == 0xf)
+			mask = 0x0;
+		else
+			dev_priv->chv_phy_control |=
+				PHY_CH_POWER_DOWN_OVRD_EN(DPIO_PHY0, DPIO_CH0);
+
+		dev_priv->chv_phy_control |=
+			PHY_CH_POWER_DOWN_OVRD(mask, DPIO_PHY0, DPIO_CH0);
+
+		mask = (status & DPLL_PORTC_READY_MASK) >> 4;
+		if (mask == 0xf)
+			mask = 0x0;
+		else
+			dev_priv->chv_phy_control |=
+				PHY_CH_POWER_DOWN_OVRD_EN(DPIO_PHY0, DPIO_CH1);
+
+		dev_priv->chv_phy_control |=
+			PHY_CH_POWER_DOWN_OVRD(mask, DPIO_PHY0, DPIO_CH1);
+
 		dev_priv->chv_phy_control |= PHY_COM_LANE_RESET_DEASSERT(DPIO_PHY0);
-	if (cmn_d->ops->is_enabled(dev_priv, cmn_d))
+	}
+
+	if (cmn_d->ops->is_enabled(dev_priv, cmn_d)) {
+		uint32_t status = I915_READ(DPIO_PHY_STATUS);
+		unsigned int mask;
+
+		mask = status & DPLL_PORTD_READY_MASK;
+
+		if (mask == 0xf)
+			mask = 0x0;
+		else
+			dev_priv->chv_phy_control |=
+				PHY_CH_POWER_DOWN_OVRD_EN(DPIO_PHY1, DPIO_CH0);
+
+		dev_priv->chv_phy_control |=
+			PHY_CH_POWER_DOWN_OVRD(mask, DPIO_PHY1, DPIO_CH0);
+
 		dev_priv->chv_phy_control |= PHY_COM_LANE_RESET_DEASSERT(DPIO_PHY1);
+	}
+
+	I915_WRITE(DISPLAY_PHY_CONTROL, dev_priv->chv_phy_control);
+
+	DRM_DEBUG_KMS("Initial PHY_CONTROL=0x%08x\n",
+		      dev_priv->chv_phy_control);
 }
 
 static void vlv_cmnlane_wa(struct drm_i915_private *dev_priv)