drm/i915: Disable eDP VDD in a delayed work proc instead of synchronously
authorKeith Packard <keithp@keithp.com>
Mon, 19 Sep 2011 06:09:52 +0000 (23:09 -0700)
committerKeith Packard <keithp@keithp.com>
Thu, 6 Oct 2011 15:57:02 +0000 (08:57 -0700)
There's no good reason to turn off the eDP force VDD bit synchronously
while probing devices; that just sticks a huge delay into all mode
setting paths. Instead, queue a delayed work proc to disable the VDD
force bit and then remember when that fires to ensure that the
appropriate delay is respected before trying to turn it back on.

Signed-off-by: Keith Packard <keithp@keithp.com>
drivers/gpu/drm/i915/intel_dp.c

index ecb976451a21f090b29fc1c90667b12f3a98b517..e880a04c722f74d64c2e2c5046806524ca638c5b 100644 (file)
@@ -65,6 +65,9 @@ struct intel_dp {
        int backlight_on_delay;
        int backlight_off_delay;
        struct drm_display_mode *panel_fixed_mode;  /* for eDP */
+       struct delayed_work panel_vdd_work;
+       bool want_panel_vdd;
+       unsigned long panel_off_jiffies;
 };
 
 /**
@@ -614,7 +617,7 @@ intel_dp_i2c_aux_ch(struct i2c_adapter *adapter, int mode,
 }
 
 static void ironlake_edp_panel_vdd_on(struct intel_dp *intel_dp);
-static void ironlake_edp_panel_vdd_off(struct intel_dp *intel_dp);
+static void ironlake_edp_panel_vdd_off(struct intel_dp *intel_dp, bool sync);
 
 static int
 intel_dp_i2c_init(struct intel_dp *intel_dp,
@@ -637,7 +640,7 @@ intel_dp_i2c_init(struct intel_dp *intel_dp,
 
        ironlake_edp_panel_vdd_on(intel_dp);
        ret = i2c_dp_aux_add_bus(&intel_dp->adapter);
-       ironlake_edp_panel_vdd_off(intel_dp);
+       ironlake_edp_panel_vdd_off(intel_dp, false);
        return ret;
 }
 
@@ -862,6 +865,23 @@ intel_dp_mode_set(struct drm_encoder *encoder, struct drm_display_mode *mode,
        }
 }
 
+static void ironlake_wait_panel_off(struct intel_dp *intel_dp)
+{
+       unsigned long   off_time;
+       unsigned long   delay;
+       DRM_DEBUG_KMS("Wait for panel power off time\n");
+       off_time = intel_dp->panel_off_jiffies + msecs_to_jiffies(intel_dp->panel_power_down_delay);
+       if (time_after(jiffies, off_time)) {
+               DRM_DEBUG_KMS("Time already passed");
+               return;
+       }
+       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_edp_panel_vdd_on(struct intel_dp *intel_dp)
 {
        struct drm_device *dev = intel_dp->base.base.dev;
@@ -872,6 +892,16 @@ static void ironlake_edp_panel_vdd_on(struct intel_dp *intel_dp)
                return;
        DRM_DEBUG_KMS("Turn eDP VDD on\n");
 
+       WARN(intel_dp->want_panel_vdd,
+            "eDP VDD already requested on\n");
+
+       intel_dp->want_panel_vdd = true;
+       if (ironlake_edp_have_panel_vdd(intel_dp)) {
+               DRM_DEBUG_KMS("eDP VDD already on\n");
+               return;
+       }
+
+       ironlake_wait_panel_off(intel_dp);
        pp = I915_READ(PCH_PP_CONTROL);
        pp &= ~PANEL_UNLOCK_MASK;
        pp |= PANEL_UNLOCK_REGS;
@@ -885,31 +915,64 @@ static void ironlake_edp_panel_vdd_on(struct intel_dp *intel_dp)
         * If the panel wasn't on, delay before accessing aux channel
         */
        if (!ironlake_edp_have_panel_power(intel_dp)) {
+               DRM_DEBUG_KMS("eDP was not running\n");
                msleep(intel_dp->panel_power_up_delay);
-               DRM_DEBUG_KMS("eDP VDD was not on\n");
        }
 }
 
-static void ironlake_edp_panel_vdd_off(struct intel_dp *intel_dp)
+static void ironlake_panel_vdd_off_sync(struct intel_dp *intel_dp)
 {
        struct drm_device *dev = intel_dp->base.base.dev;
        struct drm_i915_private *dev_priv = dev->dev_private;
        u32 pp;
 
+       if (!intel_dp->want_panel_vdd && ironlake_edp_have_panel_vdd(intel_dp)) {
+               pp = I915_READ(PCH_PP_CONTROL);
+               pp &= ~PANEL_UNLOCK_MASK;
+               pp |= PANEL_UNLOCK_REGS;
+               pp &= ~EDP_FORCE_VDD;
+               I915_WRITE(PCH_PP_CONTROL, pp);
+               POSTING_READ(PCH_PP_CONTROL);
+
+               /* Make sure sequencer is idle before allowing subsequent activity */
+               DRM_DEBUG_KMS("PCH_PP_STATUS: 0x%08x PCH_PP_CONTROL: 0x%08x\n",
+                             I915_READ(PCH_PP_STATUS), I915_READ(PCH_PP_CONTROL));
+               intel_dp->panel_off_jiffies = jiffies;
+       }
+}
+
+static void ironlake_panel_vdd_work(struct work_struct *__work)
+{
+       struct intel_dp *intel_dp = container_of(to_delayed_work(__work),
+                                                struct intel_dp, panel_vdd_work);
+       struct drm_device *dev = intel_dp->base.base.dev;
+
+       mutex_lock(&dev->struct_mutex);
+       ironlake_panel_vdd_off_sync(intel_dp);
+       mutex_unlock(&dev->struct_mutex);
+}
+
+static void ironlake_edp_panel_vdd_off(struct intel_dp *intel_dp, bool sync)
+{
        if (!is_edp(intel_dp))
                return;
-       DRM_DEBUG_KMS("Turn eDP VDD off\n");
-       pp = I915_READ(PCH_PP_CONTROL);
-       pp &= ~PANEL_UNLOCK_MASK;
-       pp |= PANEL_UNLOCK_REGS;
-       pp &= ~EDP_FORCE_VDD;
-       I915_WRITE(PCH_PP_CONTROL, pp);
-       POSTING_READ(PCH_PP_CONTROL);
 
-       /* Make sure sequencer is idle before allowing subsequent activity */
-       DRM_DEBUG_KMS("PCH_PP_STATUS: 0x%08x PCH_PP_CONTROL: 0x%08x\n",
-                     I915_READ(PCH_PP_STATUS), I915_READ(PCH_PP_CONTROL));
-       msleep(intel_dp->panel_power_cycle_delay);
+       DRM_DEBUG_KMS("Turn eDP VDD off %d\n", intel_dp->want_panel_vdd);
+       WARN(!intel_dp->want_panel_vdd, "eDP VDD not forced on");
+       
+       intel_dp->want_panel_vdd = false;
+
+       if (sync) {
+               ironlake_panel_vdd_off_sync(intel_dp);
+       } else {
+               /*
+                * Queue the timer to fire a long
+                * time from now (relative to the power down delay)
+                * to keep the panel power up across a sequence of operations
+                */
+               schedule_delayed_work(&intel_dp->panel_vdd_work,
+                                     msecs_to_jiffies(intel_dp->panel_power_cycle_delay * 5));
+       }
 }
 
 /* Returns true if the panel was already on when called */
@@ -920,10 +983,11 @@ static void ironlake_edp_panel_on (struct intel_dp *intel_dp)
        u32 pp, idle_on_mask = PP_ON | PP_SEQUENCE_STATE_ON_IDLE;
 
        if (!is_edp(intel_dp))
-               return true;
+               return;
        if (ironlake_edp_have_panel_power(intel_dp))
                return;
 
+       ironlake_wait_panel_off(intel_dp);
        pp = I915_READ(PCH_PP_CONTROL);
        pp &= ~PANEL_UNLOCK_MASK;
        pp |= PANEL_UNLOCK_REGS;
@@ -978,6 +1042,7 @@ static void ironlake_edp_panel_off(struct drm_encoder *encoder)
        pp |= PANEL_POWER_RESET; /* restore panel reset bit */
        I915_WRITE(PCH_PP_CONTROL, pp);
        POSTING_READ(PCH_PP_CONTROL);
+       intel_dp->panel_off_jiffies = jiffies;
 }
 
 static void ironlake_edp_backlight_on (struct intel_dp *intel_dp)
@@ -1088,7 +1153,7 @@ static void intel_dp_prepare(struct drm_encoder *encoder)
        /* Wake up the sink first */
        ironlake_edp_panel_vdd_on(intel_dp);
        intel_dp_sink_dpms(intel_dp, DRM_MODE_DPMS_ON);
-       ironlake_edp_panel_vdd_off(intel_dp);
+       ironlake_edp_panel_vdd_off(intel_dp, false);
 
        /* Make sure the panel is off before trying to
         * change the mode
@@ -1106,7 +1171,8 @@ static void intel_dp_commit(struct drm_encoder *encoder)
        intel_dp_sink_dpms(intel_dp, DRM_MODE_DPMS_ON);
        intel_dp_start_link_train(intel_dp);
        ironlake_edp_panel_on(intel_dp);
-       ironlake_edp_panel_vdd_off(intel_dp);
+       ironlake_edp_panel_vdd_off(intel_dp, true);
+
        intel_dp_complete_link_train(intel_dp);
        ironlake_edp_backlight_on(intel_dp);
 
@@ -1130,18 +1196,19 @@ intel_dp_dpms(struct drm_encoder *encoder, int mode)
                ironlake_edp_panel_off(encoder);
                if (is_edp(intel_dp) && !is_pch_edp(intel_dp))
                        ironlake_edp_pll_off(encoder);
-               ironlake_edp_panel_vdd_off(intel_dp);
+               ironlake_edp_panel_vdd_off(intel_dp, false);
        } else {
                ironlake_edp_panel_vdd_on(intel_dp);
                intel_dp_sink_dpms(intel_dp, mode);
                if (!(dp_reg & DP_PORT_EN)) {
                        intel_dp_start_link_train(intel_dp);
                        ironlake_edp_panel_on(intel_dp);
-                       ironlake_edp_panel_vdd_off(intel_dp);
+                       ironlake_edp_panel_vdd_off(intel_dp, true);
                        intel_dp_complete_link_train(intel_dp);
                        ironlake_edp_backlight_on(intel_dp);
                } else
-                       ironlake_edp_panel_vdd_off(intel_dp);
+                       ironlake_edp_panel_vdd_off(intel_dp, false);
+               ironlake_edp_backlight_on(intel_dp);
        }
        intel_dp->dpms_mode = mode;
 }
@@ -1778,7 +1845,7 @@ intel_dp_get_edid(struct drm_connector *connector, struct i2c_adapter *adapter)
 
        ironlake_edp_panel_vdd_on(intel_dp);
        edid = drm_get_edid(connector, adapter);
-       ironlake_edp_panel_vdd_off(intel_dp);
+       ironlake_edp_panel_vdd_off(intel_dp, false);
        return edid;
 }
 
@@ -1790,7 +1857,7 @@ intel_dp_get_edid_modes(struct drm_connector *connector, struct i2c_adapter *ada
 
        ironlake_edp_panel_vdd_on(intel_dp);
        ret = intel_ddc_get_modes(connector, adapter);
-       ironlake_edp_panel_vdd_off(intel_dp);
+       ironlake_edp_panel_vdd_off(intel_dp, false);
        return ret;
 }
 
@@ -1977,6 +2044,10 @@ static void intel_dp_encoder_destroy(struct drm_encoder *encoder)
 
        i2c_del_adapter(&intel_dp->adapter);
        drm_encoder_cleanup(encoder);
+       if (is_edp(intel_dp)) {
+               cancel_delayed_work_sync(&intel_dp->panel_vdd_work);
+               ironlake_panel_vdd_off_sync(intel_dp);
+       }
        kfree(intel_dp);
 }
 
@@ -2113,8 +2184,11 @@ intel_dp_init(struct drm_device *dev, int output_reg)
        else if (output_reg == DP_D || output_reg == PCH_DP_D)
                intel_encoder->clone_mask = (1 << INTEL_DP_D_CLONE_BIT);
 
-       if (is_edp(intel_dp))
+       if (is_edp(intel_dp)) {
                intel_encoder->clone_mask = (1 << INTEL_EDP_CLONE_BIT);
+               INIT_DELAYED_WORK(&intel_dp->panel_vdd_work,
+                                 ironlake_panel_vdd_work);
+       }
 
        intel_encoder->crtc_mask = (1 << 0) | (1 << 1);
        connector->interlace_allowed = true;
@@ -2201,9 +2275,11 @@ intel_dp_init(struct drm_device *dev, int output_reg)
                DRM_DEBUG_KMS("backlight on delay %d, off delay %d\n",
                              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);
                ret = intel_dp_get_dpcd(intel_dp);
-               ironlake_edp_panel_vdd_off(intel_dp);
+               ironlake_edp_panel_vdd_off(intel_dp, false);
                if (ret) {
                        if (intel_dp->dpcd[DP_DPCD_REV] >= 0x11)
                                dev_priv->no_aux_handshake =