Commit 99ea7127 authored by Keith Packard's avatar Keith Packard

drm/i915: Let panel power sequencing hardware do its job

The panel power sequencing hardware tracks the stages of panel power
sequencing and signals when the panel is completely on or off. Instead
of blindly assuming the panel timings will work, poll the panel power
status register until it shows the correct values.
Signed-off-by: default avatarKeith Packard <keithp@keithp.com>
Reviewed-by: default avatarJesse Barnes <jbarnes@virtuousgeek.org>
parent 417e822d
...@@ -1553,12 +1553,21 @@ ...@@ -1553,12 +1553,21 @@
*/ */
#define PP_READY (1 << 30) #define PP_READY (1 << 30)
#define PP_SEQUENCE_NONE (0 << 28) #define PP_SEQUENCE_NONE (0 << 28)
#define PP_SEQUENCE_ON (1 << 28) #define PP_SEQUENCE_POWER_UP (1 << 28)
#define PP_SEQUENCE_OFF (2 << 28) #define PP_SEQUENCE_POWER_DOWN (2 << 28)
#define PP_SEQUENCE_MASK 0x30000000 #define PP_SEQUENCE_MASK (3 << 28)
#define PP_SEQUENCE_SHIFT 28
#define PP_CYCLE_DELAY_ACTIVE (1 << 27) #define PP_CYCLE_DELAY_ACTIVE (1 << 27)
#define PP_SEQUENCE_STATE_ON_IDLE (1 << 3)
#define PP_SEQUENCE_STATE_MASK 0x0000000f #define PP_SEQUENCE_STATE_MASK 0x0000000f
#define PP_SEQUENCE_STATE_OFF_IDLE (0x0 << 0)
#define PP_SEQUENCE_STATE_OFF_S0_1 (0x1 << 0)
#define PP_SEQUENCE_STATE_OFF_S0_2 (0x2 << 0)
#define PP_SEQUENCE_STATE_OFF_S0_3 (0x3 << 0)
#define PP_SEQUENCE_STATE_ON_IDLE (0x8 << 0)
#define PP_SEQUENCE_STATE_ON_S1_0 (0x9 << 0)
#define PP_SEQUENCE_STATE_ON_S1_2 (0xa << 0)
#define PP_SEQUENCE_STATE_ON_S1_3 (0xb << 0)
#define PP_SEQUENCE_STATE_RESET (0xf << 0)
#define PP_CONTROL 0x61204 #define PP_CONTROL 0x61204
#define POWER_TARGET_ON (1 << 0) #define POWER_TARGET_ON (1 << 0)
#define PP_ON_DELAYS 0x61208 #define PP_ON_DELAYS 0x61208
......
...@@ -66,7 +66,6 @@ struct intel_dp { ...@@ -66,7 +66,6 @@ struct intel_dp {
struct drm_display_mode *panel_fixed_mode; /* for eDP */ struct drm_display_mode *panel_fixed_mode; /* for eDP */
struct delayed_work panel_vdd_work; struct delayed_work panel_vdd_work;
bool want_panel_vdd; bool want_panel_vdd;
unsigned long panel_off_jiffies;
}; };
/** /**
...@@ -906,32 +905,53 @@ intel_dp_mode_set(struct drm_encoder *encoder, struct drm_display_mode *mode, ...@@ -906,32 +905,53 @@ intel_dp_mode_set(struct drm_encoder *encoder, struct drm_display_mode *mode,
} }
} }
static void ironlake_wait_panel_off(struct intel_dp *intel_dp) #define IDLE_ON_MASK (PP_ON | 0 | PP_SEQUENCE_MASK | 0 | PP_SEQUENCE_STATE_MASK)
#define IDLE_ON_VALUE (PP_ON | 0 | PP_SEQUENCE_NONE | 0 | PP_SEQUENCE_STATE_ON_IDLE)
#define IDLE_OFF_MASK (PP_ON | 0 | PP_SEQUENCE_MASK | 0 | PP_SEQUENCE_STATE_MASK)
#define IDLE_OFF_VALUE (0 | 0 | PP_SEQUENCE_NONE | 0 | PP_SEQUENCE_STATE_OFF_IDLE)
#define IDLE_CYCLE_MASK (PP_ON | 0 | PP_SEQUENCE_MASK | PP_CYCLE_DELAY_ACTIVE | PP_SEQUENCE_STATE_MASK)
#define IDLE_CYCLE_VALUE (0 | 0 | PP_SEQUENCE_NONE | 0 | PP_SEQUENCE_STATE_OFF_IDLE)
static void ironlake_wait_panel_status(struct intel_dp *intel_dp,
u32 mask,
u32 value)
{ {
unsigned long off_time; struct drm_device *dev = intel_dp->base.base.dev;
unsigned long delay; struct drm_i915_private *dev_priv = dev->dev_private;
DRM_DEBUG_KMS("Wait for panel power off time\n"); DRM_DEBUG_KMS("mask %08x value %08x status %08x control %08x\n",
mask, value,
I915_READ(PCH_PP_STATUS),
I915_READ(PCH_PP_CONTROL));
if (ironlake_edp_have_panel_power(intel_dp) || if (_wait_for((I915_READ(PCH_PP_STATUS) & mask) == value, 5000, 10)) {
ironlake_edp_have_panel_vdd(intel_dp)) DRM_ERROR("Panel status timeout: status %08x control %08x\n",
{ I915_READ(PCH_PP_STATUS),
DRM_DEBUG_KMS("Panel still on, no delay needed\n"); I915_READ(PCH_PP_CONTROL));
return;
} }
}
off_time = intel_dp->panel_off_jiffies + msecs_to_jiffies(intel_dp->panel_power_down_delay); static void ironlake_wait_panel_on(struct intel_dp *intel_dp)
if (time_after(jiffies, off_time)) { {
DRM_DEBUG_KMS("Time already passed"); DRM_DEBUG_KMS("Wait for panel power on\n");
return; ironlake_wait_panel_status(intel_dp, IDLE_ON_MASK, IDLE_ON_VALUE);
}
delay = jiffies_to_msecs(off_time - jiffies);
if (delay > intel_dp->panel_power_down_delay)
delay = intel_dp->panel_power_down_delay;
DRM_DEBUG_KMS("Waiting an additional %ld ms\n", delay);
msleep(delay);
} }
static void ironlake_wait_panel_off(struct intel_dp *intel_dp)
{
DRM_DEBUG_KMS("Wait for panel power off time\n");
ironlake_wait_panel_status(intel_dp, IDLE_OFF_MASK, IDLE_OFF_VALUE);
}
static void ironlake_wait_panel_power_cycle(struct intel_dp *intel_dp)
{
DRM_DEBUG_KMS("Wait for panel power cycle\n");
ironlake_wait_panel_status(intel_dp, IDLE_CYCLE_MASK, IDLE_CYCLE_VALUE);
}
/* Read the current pp_control value, unlocking the register if it /* Read the current pp_control value, unlocking the register if it
* is locked * is locked
*/ */
...@@ -959,12 +979,15 @@ static void ironlake_edp_panel_vdd_on(struct intel_dp *intel_dp) ...@@ -959,12 +979,15 @@ static void ironlake_edp_panel_vdd_on(struct intel_dp *intel_dp)
"eDP VDD already requested on\n"); "eDP VDD already requested on\n");
intel_dp->want_panel_vdd = true; intel_dp->want_panel_vdd = true;
if (ironlake_edp_have_panel_vdd(intel_dp)) { if (ironlake_edp_have_panel_vdd(intel_dp)) {
DRM_DEBUG_KMS("eDP VDD already on\n"); DRM_DEBUG_KMS("eDP VDD already on\n");
return; return;
} }
ironlake_wait_panel_off(intel_dp); if (!ironlake_edp_have_panel_power(intel_dp))
ironlake_wait_panel_power_cycle(intel_dp);
pp = ironlake_get_pp_control(dev_priv); pp = ironlake_get_pp_control(dev_priv);
pp |= EDP_FORCE_VDD; pp |= EDP_FORCE_VDD;
I915_WRITE(PCH_PP_CONTROL, pp); I915_WRITE(PCH_PP_CONTROL, pp);
...@@ -996,7 +1019,8 @@ static void ironlake_panel_vdd_off_sync(struct intel_dp *intel_dp) ...@@ -996,7 +1019,8 @@ static void ironlake_panel_vdd_off_sync(struct intel_dp *intel_dp)
/* Make sure sequencer is idle before allowing subsequent activity */ /* Make sure sequencer is idle before allowing subsequent activity */
DRM_DEBUG_KMS("PCH_PP_STATUS: 0x%08x PCH_PP_CONTROL: 0x%08x\n", DRM_DEBUG_KMS("PCH_PP_STATUS: 0x%08x PCH_PP_CONTROL: 0x%08x\n",
I915_READ(PCH_PP_STATUS), I915_READ(PCH_PP_CONTROL)); I915_READ(PCH_PP_STATUS), I915_READ(PCH_PP_CONTROL));
intel_dp->panel_off_jiffies = jiffies;
msleep(intel_dp->panel_power_down_delay);
} }
} }
...@@ -1034,21 +1058,25 @@ static void ironlake_edp_panel_vdd_off(struct intel_dp *intel_dp, bool sync) ...@@ -1034,21 +1058,25 @@ static void ironlake_edp_panel_vdd_off(struct intel_dp *intel_dp, bool sync)
} }
} }
/* Returns true if the panel was already on when called */
static void ironlake_edp_panel_on(struct intel_dp *intel_dp) static void ironlake_edp_panel_on(struct intel_dp *intel_dp)
{ {
struct drm_device *dev = intel_dp->base.base.dev; struct drm_device *dev = intel_dp->base.base.dev;
struct drm_i915_private *dev_priv = dev->dev_private; struct drm_i915_private *dev_priv = dev->dev_private;
u32 pp, idle_on_mask = PP_ON | PP_SEQUENCE_STATE_ON_IDLE; u32 pp;
if (!is_edp(intel_dp)) if (!is_edp(intel_dp))
return; return;
if (ironlake_edp_have_panel_power(intel_dp))
DRM_DEBUG_KMS("Turn eDP power on\n");
if (ironlake_edp_have_panel_power(intel_dp)) {
DRM_DEBUG_KMS("eDP power already on\n");
return; return;
}
ironlake_wait_panel_off(intel_dp); ironlake_wait_panel_power_cycle(intel_dp);
pp = ironlake_get_pp_control(dev_priv);
pp = ironlake_get_pp_control(dev_priv);
if (IS_GEN5(dev)) { if (IS_GEN5(dev)) {
/* ILK workaround: disable reset around power sequence */ /* ILK workaround: disable reset around power sequence */
pp &= ~PANEL_POWER_RESET; pp &= ~PANEL_POWER_RESET;
...@@ -1057,13 +1085,13 @@ static void ironlake_edp_panel_on(struct intel_dp *intel_dp) ...@@ -1057,13 +1085,13 @@ static void ironlake_edp_panel_on(struct intel_dp *intel_dp)
} }
pp |= POWER_TARGET_ON; pp |= POWER_TARGET_ON;
if (!IS_GEN5(dev))
pp |= PANEL_POWER_RESET;
I915_WRITE(PCH_PP_CONTROL, pp); I915_WRITE(PCH_PP_CONTROL, pp);
POSTING_READ(PCH_PP_CONTROL); POSTING_READ(PCH_PP_CONTROL);
if (wait_for((I915_READ(PCH_PP_STATUS) & idle_on_mask) == idle_on_mask, ironlake_wait_panel_on(intel_dp);
5000))
DRM_ERROR("panel on wait timed out: 0x%08x\n",
I915_READ(PCH_PP_STATUS));
if (IS_GEN5(dev)) { if (IS_GEN5(dev)) {
pp |= PANEL_POWER_RESET; /* restore panel reset bit */ pp |= PANEL_POWER_RESET; /* restore panel reset bit */
...@@ -1072,44 +1100,25 @@ static void ironlake_edp_panel_on(struct intel_dp *intel_dp) ...@@ -1072,44 +1100,25 @@ static void ironlake_edp_panel_on(struct intel_dp *intel_dp)
} }
} }
static void ironlake_edp_panel_off(struct drm_encoder *encoder) static void ironlake_edp_panel_off(struct intel_dp *intel_dp)
{ {
struct intel_dp *intel_dp = enc_to_intel_dp(encoder); struct drm_device *dev = intel_dp->base.base.dev;
struct drm_device *dev = encoder->dev;
struct drm_i915_private *dev_priv = dev->dev_private; struct drm_i915_private *dev_priv = dev->dev_private;
u32 pp, idle_off_mask = PP_ON | PP_SEQUENCE_MASK | u32 pp;
PP_CYCLE_DELAY_ACTIVE | PP_SEQUENCE_STATE_MASK;
if (!is_edp(intel_dp)) if (!is_edp(intel_dp))
return; return;
pp = ironlake_get_pp_control(dev_priv);
if (IS_GEN5(dev)) { DRM_DEBUG_KMS("Turn eDP power off\n");
/* ILK workaround: disable reset around power sequence */
pp &= ~PANEL_POWER_RESET;
I915_WRITE(PCH_PP_CONTROL, pp);
POSTING_READ(PCH_PP_CONTROL);
}
intel_dp->panel_off_jiffies = jiffies; WARN(intel_dp->want_panel_vdd, "Cannot turn power off while VDD is on\n");
if (IS_GEN5(dev)) { pp = ironlake_get_pp_control(dev_priv);
pp &= ~POWER_TARGET_ON; pp &= ~(POWER_TARGET_ON | EDP_FORCE_VDD | PANEL_POWER_RESET | EDP_BLC_ENABLE);
I915_WRITE(PCH_PP_CONTROL, pp);
POSTING_READ(PCH_PP_CONTROL);
pp &= ~POWER_TARGET_ON;
I915_WRITE(PCH_PP_CONTROL, pp); I915_WRITE(PCH_PP_CONTROL, pp);
POSTING_READ(PCH_PP_CONTROL); POSTING_READ(PCH_PP_CONTROL);
msleep(intel_dp->panel_power_cycle_delay);
if (wait_for((I915_READ(PCH_PP_STATUS) & idle_off_mask) == 0, 5000)) ironlake_wait_panel_off(intel_dp);
DRM_ERROR("panel off wait timed out: 0x%08x\n",
I915_READ(PCH_PP_STATUS));
pp |= PANEL_POWER_RESET; /* restore panel reset bit */
I915_WRITE(PCH_PP_CONTROL, pp);
POSTING_READ(PCH_PP_CONTROL);
}
} }
static void ironlake_edp_backlight_on(struct intel_dp *intel_dp) static void ironlake_edp_backlight_on(struct intel_dp *intel_dp)
...@@ -1223,7 +1232,7 @@ static void intel_dp_prepare(struct drm_encoder *encoder) ...@@ -1223,7 +1232,7 @@ static void intel_dp_prepare(struct drm_encoder *encoder)
*/ */
ironlake_edp_backlight_off(intel_dp); ironlake_edp_backlight_off(intel_dp);
intel_dp_link_down(intel_dp); intel_dp_link_down(intel_dp);
ironlake_edp_panel_off(encoder); ironlake_edp_panel_off(intel_dp);
} }
static void intel_dp_commit(struct drm_encoder *encoder) static void intel_dp_commit(struct drm_encoder *encoder)
...@@ -1237,7 +1246,6 @@ static void intel_dp_commit(struct drm_encoder *encoder) ...@@ -1237,7 +1246,6 @@ static void intel_dp_commit(struct drm_encoder *encoder)
intel_dp_start_link_train(intel_dp); intel_dp_start_link_train(intel_dp);
ironlake_edp_panel_on(intel_dp); ironlake_edp_panel_on(intel_dp);
ironlake_edp_panel_vdd_off(intel_dp, true); ironlake_edp_panel_vdd_off(intel_dp, true);
intel_dp_complete_link_train(intel_dp); intel_dp_complete_link_train(intel_dp);
ironlake_edp_backlight_on(intel_dp); ironlake_edp_backlight_on(intel_dp);
...@@ -1261,7 +1269,7 @@ intel_dp_dpms(struct drm_encoder *encoder, int mode) ...@@ -1261,7 +1269,7 @@ intel_dp_dpms(struct drm_encoder *encoder, int mode)
ironlake_edp_backlight_off(intel_dp); ironlake_edp_backlight_off(intel_dp);
intel_dp_sink_dpms(intel_dp, mode); intel_dp_sink_dpms(intel_dp, mode);
intel_dp_link_down(intel_dp); intel_dp_link_down(intel_dp);
ironlake_edp_panel_off(encoder); ironlake_edp_panel_off(intel_dp);
if (is_edp(intel_dp) && !is_pch_edp(intel_dp)) if (is_edp(intel_dp) && !is_pch_edp(intel_dp))
ironlake_edp_pll_off(encoder); ironlake_edp_pll_off(encoder);
ironlake_edp_panel_vdd_off(intel_dp, false); ironlake_edp_panel_vdd_off(intel_dp, false);
...@@ -2398,11 +2406,10 @@ intel_dp_init(struct drm_device *dev, int output_reg) ...@@ -2398,11 +2406,10 @@ intel_dp_init(struct drm_device *dev, int output_reg)
DRM_DEBUG_KMS("backlight on delay %d, off delay %d\n", DRM_DEBUG_KMS("backlight on delay %d, off delay %d\n",
intel_dp->backlight_on_delay, intel_dp->backlight_off_delay); intel_dp->backlight_on_delay, intel_dp->backlight_off_delay);
intel_dp->panel_off_jiffies = jiffies - intel_dp->panel_power_down_delay;
ironlake_edp_panel_vdd_on(intel_dp); ironlake_edp_panel_vdd_on(intel_dp);
ret = intel_dp_get_dpcd(intel_dp); ret = intel_dp_get_dpcd(intel_dp);
ironlake_edp_panel_vdd_off(intel_dp, false); ironlake_edp_panel_vdd_off(intel_dp, false);
if (ret) { if (ret) {
if (intel_dp->dpcd[DP_DPCD_REV] >= 0x11) if (intel_dp->dpcd[DP_DPCD_REV] >= 0x11)
dev_priv->no_aux_handshake = dev_priv->no_aux_handshake =
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment