Message ID | 1463120623-3585-2-git-send-email-shubhangi.shrivastava@intel.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
On Fri, May 13, 2016 at 11:53:43AM +0530, Shubhangi Shrivastava wrote: > DP panel can issue short pulse interrupts during which it can > modify the number of lanes supported by it. This will result in > change in capabilities of the display and limit the max > resoultion possible on it. This should be informed to the > user mode as well since HWC will issue the modeset assuming > old capabilities. > > This patch detects lane count change and simulates a detatch > and attach, so to the user mode or anyone else tracking the > display it will appear as unplug and replug of different > display. > > v2: moved queuing of delayed thread to intel_dp where we > set simulate_disconnect_connect flag. There is a chance > for short pulse to be hit even before long pulse detection > is complete, in such a scenario the delayed thread won't > be queued resulting in flag never getting cleared. > Since we set the flag and queue it immediately we can be > sure that the flag will be cleared. > > Signed-off-by: Sivakumar Thulasimani <sivakumar.thulasimani@intel.com> > Signed-off-by: Shubhangi Shrivastava <shubhangi.shrivastava@intel.com> This is nonsense, just send a uevent to userspace, and make sure that we re-validate all the modes. Userspace is supposed to ask for updated mode lists after every uevent, and then reset a suitable mode. If hwc can't do that, then we're not going to add a horrible hack to the kernel of temporarily unplugging something, since fundamentally that's just duct-tape and still racy. Note that right now we don't have support to generate uevents for when the connector status hasn't changed. But we need that in a lot of other cases too. I think the simplest option is to add a sink_lifetime counter to drm_connector, and a helper to increment that, e.g. drm_connector_sink_change(). Then we could call that from our probe/hotplug hooks every time something relevant changes (different sink dongle, different number of lanes, different edid). And the top-level probe helpers could also check for any changes in the sink_lifetime instead of just connect/disconnect. I think it'd be good if we also wire this up for edid changes, in the core function which updates the edid blob. Cheers, Daniel > --- > drivers/gpu/drm/i915/i915_drv.h | 1 + > drivers/gpu/drm/i915/i915_irq.c | 2 ++ > drivers/gpu/drm/i915/intel_dp.c | 47 ++++++++++++++++++++++++++++++--- > drivers/gpu/drm/i915/intel_drv.h | 4 +++ > drivers/gpu/drm/i915/intel_hotplug.c | 50 ++++++++++++++++++++++++++++++++++++ > 5 files changed, 100 insertions(+), 4 deletions(-) > > diff --git a/drivers/gpu/drm/i915/i915_drv.h b/drivers/gpu/drm/i915/i915_drv.h > index 7a0b513..30d3636 100644 > --- a/drivers/gpu/drm/i915/i915_drv.h > +++ b/drivers/gpu/drm/i915/i915_drv.h > @@ -280,6 +280,7 @@ struct i915_hotplug { > u32 long_port_mask; > u32 short_port_mask; > struct work_struct dig_port_work; > + struct delayed_work simulate_work; > > /* > * if we get a HPD irq from DP and a HPD irq from non-DP > diff --git a/drivers/gpu/drm/i915/i915_irq.c b/drivers/gpu/drm/i915/i915_irq.c > index f0d9414..74fe755 100644 > --- a/drivers/gpu/drm/i915/i915_irq.c > +++ b/drivers/gpu/drm/i915/i915_irq.c > @@ -4572,6 +4572,8 @@ void intel_irq_init(struct drm_i915_private *dev_priv) > > INIT_WORK(&dev_priv->rps.work, gen6_pm_rps_work); > INIT_WORK(&dev_priv->l3_parity.error_work, ivybridge_parity_work); > + INIT_DELAYED_WORK(&dev_priv->hotplug.simulate_work, > + intel_hpd_simulate_reconnect_work); > > /* Let's track the enabled rps events */ > if (IS_VALLEYVIEW(dev_priv)) > diff --git a/drivers/gpu/drm/i915/intel_dp.c b/drivers/gpu/drm/i915/intel_dp.c > index d5ed84f..ebd525e 100644 > --- a/drivers/gpu/drm/i915/intel_dp.c > +++ b/drivers/gpu/drm/i915/intel_dp.c > @@ -3783,6 +3783,26 @@ update_status: > } > } > > +static void intel_dp_update_simulate_detach_info(struct intel_dp *intel_dp) > +{ > + struct intel_connector *intel_connector = intel_dp->attached_connector; > + struct drm_device *dev = intel_dp_to_dev(intel_dp); > + struct drm_i915_private *dev_priv = dev->dev_private; > + > + /* > + * Queue thread only if it is not already done. > + * The flag is cleared inside the callback function > + * hence we can be sure that there is no race condition > + * under which it may remain set. > + */ > + if (!intel_connector->simulate_disconnect_connect) { > + intel_connector->simulate_disconnect_connect = true; > + DRM_DEBUG_KMS("Queue simulate work func\n"); > + mod_delayed_work(system_wq, &dev_priv->hotplug.simulate_work, > + msecs_to_jiffies(100)); > + } > +} > + > static int > intel_dp_check_mst_status(struct intel_dp *intel_dp) > { > @@ -3893,6 +3913,7 @@ intel_dp_short_pulse(struct intel_dp *intel_dp) > struct drm_device *dev = intel_dp_to_dev(intel_dp); > u8 sink_irq_vector; > u8 old_sink_count = intel_dp->sink_count; > + u8 old_lane_count = intel_dp->dpcd[DP_MAX_LANE_COUNT]; > bool ret; > > /* > @@ -3924,6 +3945,20 @@ intel_dp_short_pulse(struct intel_dp *intel_dp) > sink_irq_vector &= ~DP_AUTOMATED_TEST_REQUEST; > } > > + /* > + * If lane count has changed, we need to inform > + * user mode of new capablities, this is done by setting > + * our flag to do a fake disconnect and connect so it > + * will appear to the user mode that a new panel is > + * connected and will use the new capabilties of the > + * panel > + */ > + if (old_lane_count != intel_dp->dpcd[DP_MAX_LANE_COUNT]) { > + DRM_DEBUG_KMS("Lane count changed\n"); > + intel_dp_update_simulate_detach_info(intel_dp); > + return false; > + } > + > if (sink_irq_vector & (DP_CP_IRQ | DP_SINK_SPECIFIC_IRQ)) > DRM_DEBUG_DRIVER("CP or sink specific irq unhandled\n"); > > @@ -4213,13 +4248,17 @@ intel_dp_long_pulse(struct intel_connector *intel_connector) > intel_display_power_get(to_i915(dev), power_domain); > > /* Can't disconnect eDP, but you can close the lid... */ > - if (is_edp(intel_dp)) > + if (is_edp(intel_dp)) { > status = edp_detect(intel_dp); > - else if (intel_digital_port_connected(to_i915(dev), > - dp_to_dig_port(intel_dp))) > + } else if (intel_connector->simulate_disconnect_connect) { > + DRM_DEBUG_KMS("Simulating disconnect\n"); > + status = connector_status_disconnected; > + } else if (intel_digital_port_connected(to_i915(dev), > + dp_to_dig_port(intel_dp))) { > status = intel_dp_detect_dpcd(intel_dp); > - else > + } else { > status = connector_status_disconnected; > + } > > if (status != connector_status_connected) { > intel_dp->compliance_test_active = 0; > diff --git a/drivers/gpu/drm/i915/intel_drv.h b/drivers/gpu/drm/i915/intel_drv.h > index 8daadc5..995f0da 100644 > --- a/drivers/gpu/drm/i915/intel_drv.h > +++ b/drivers/gpu/drm/i915/intel_drv.h > @@ -257,6 +257,8 @@ struct intel_connector { > struct edid *edid; > struct edid *detect_edid; > > + bool simulate_disconnect_connect; > + > /* since POLL and HPD connectors may use the same HPD line keep the native > state of connector->polled in case hotplug storm detection changes it */ > u8 polled; > @@ -1420,6 +1422,8 @@ bool intel_hdmi_compute_config(struct intel_encoder *encoder, > struct intel_crtc_state *pipe_config); > void intel_dp_dual_mode_set_tmds_output(struct intel_hdmi *hdmi, bool enable); > > +/* intel_hotplug.c */ > +void intel_hpd_simulate_reconnect_work(struct work_struct *work); > > /* intel_lvds.c */ > void intel_lvds_init(struct drm_device *dev); > diff --git a/drivers/gpu/drm/i915/intel_hotplug.c b/drivers/gpu/drm/i915/intel_hotplug.c > index 38eeca7..7e346d7 100644 > --- a/drivers/gpu/drm/i915/intel_hotplug.c > +++ b/drivers/gpu/drm/i915/intel_hotplug.c > @@ -247,6 +247,55 @@ static bool intel_hpd_irq_event(struct drm_device *dev, > return true; > } > > +/* > + * This function is the second half of logic to perform fake > + * disconnect-connect. The first half was the connector setting > + * a flag, returning status as disconnected and queing this function. > + * > + * This function uses simulate_disconnect_connect flag to identify the > + * connector that should be detected again. Since this is executed after > + * a delay if the panel is still plugged in it will be reported as > + * connected to user mode > + */ > +void intel_hpd_simulate_reconnect_work(struct work_struct *work) > +{ > + struct drm_i915_private *dev_priv = > + container_of(work, struct drm_i915_private, > + hotplug.simulate_work.work); > + struct drm_device *dev = dev_priv->dev; > + struct intel_encoder *intel_encoder; > + struct intel_connector *intel_connector; > + struct drm_connector *connector; > + struct drm_mode_config *mode_config = &dev->mode_config; > + enum port port; > + > + DRM_DEBUG_KMS("\n"); > + mutex_lock(&mode_config->mutex); > + > + list_for_each_entry(connector, &mode_config->connector_list, head) { > + intel_connector = to_intel_connector(connector); > + if (!intel_connector->simulate_disconnect_connect) > + continue; > + > + intel_connector->simulate_disconnect_connect = false; > + > + if (!intel_connector->encoder) > + continue; > + > + intel_encoder = intel_connector->encoder; > + > + port = intel_ddi_get_encoder_port(intel_encoder); > + > + spin_lock_irq(&dev_priv->irq_lock); > + dev_priv->hotplug.long_port_mask |= (1<<port); > + spin_unlock_irq(&dev_priv->irq_lock); > + } > + > + mutex_unlock(&mode_config->mutex); > + > + queue_work(dev_priv->hotplug.dp_wq, &dev_priv->hotplug.dig_port_work); > +} > + > static void i915_digport_work_func(struct work_struct *work) > { > struct drm_i915_private *dev_priv = > @@ -509,4 +558,5 @@ void intel_hpd_cancel_work(struct drm_i915_private *dev_priv) > cancel_work_sync(&dev_priv->hotplug.dig_port_work); > cancel_work_sync(&dev_priv->hotplug.hotplug_work); > cancel_delayed_work_sync(&dev_priv->hotplug.reenable_work); > + cancel_delayed_work_sync(&dev_priv->hotplug.simulate_work); > } > -- > 2.6.1 > > _______________________________________________ > Intel-gfx mailing list > Intel-gfx@lists.freedesktop.org > https://lists.freedesktop.org/mailman/listinfo/intel-gfx
diff --git a/drivers/gpu/drm/i915/i915_drv.h b/drivers/gpu/drm/i915/i915_drv.h index 7a0b513..30d3636 100644 --- a/drivers/gpu/drm/i915/i915_drv.h +++ b/drivers/gpu/drm/i915/i915_drv.h @@ -280,6 +280,7 @@ struct i915_hotplug { u32 long_port_mask; u32 short_port_mask; struct work_struct dig_port_work; + struct delayed_work simulate_work; /* * if we get a HPD irq from DP and a HPD irq from non-DP diff --git a/drivers/gpu/drm/i915/i915_irq.c b/drivers/gpu/drm/i915/i915_irq.c index f0d9414..74fe755 100644 --- a/drivers/gpu/drm/i915/i915_irq.c +++ b/drivers/gpu/drm/i915/i915_irq.c @@ -4572,6 +4572,8 @@ void intel_irq_init(struct drm_i915_private *dev_priv) INIT_WORK(&dev_priv->rps.work, gen6_pm_rps_work); INIT_WORK(&dev_priv->l3_parity.error_work, ivybridge_parity_work); + INIT_DELAYED_WORK(&dev_priv->hotplug.simulate_work, + intel_hpd_simulate_reconnect_work); /* Let's track the enabled rps events */ if (IS_VALLEYVIEW(dev_priv)) diff --git a/drivers/gpu/drm/i915/intel_dp.c b/drivers/gpu/drm/i915/intel_dp.c index d5ed84f..ebd525e 100644 --- a/drivers/gpu/drm/i915/intel_dp.c +++ b/drivers/gpu/drm/i915/intel_dp.c @@ -3783,6 +3783,26 @@ update_status: } } +static void intel_dp_update_simulate_detach_info(struct intel_dp *intel_dp) +{ + struct intel_connector *intel_connector = intel_dp->attached_connector; + struct drm_device *dev = intel_dp_to_dev(intel_dp); + struct drm_i915_private *dev_priv = dev->dev_private; + + /* + * Queue thread only if it is not already done. + * The flag is cleared inside the callback function + * hence we can be sure that there is no race condition + * under which it may remain set. + */ + if (!intel_connector->simulate_disconnect_connect) { + intel_connector->simulate_disconnect_connect = true; + DRM_DEBUG_KMS("Queue simulate work func\n"); + mod_delayed_work(system_wq, &dev_priv->hotplug.simulate_work, + msecs_to_jiffies(100)); + } +} + static int intel_dp_check_mst_status(struct intel_dp *intel_dp) { @@ -3893,6 +3913,7 @@ intel_dp_short_pulse(struct intel_dp *intel_dp) struct drm_device *dev = intel_dp_to_dev(intel_dp); u8 sink_irq_vector; u8 old_sink_count = intel_dp->sink_count; + u8 old_lane_count = intel_dp->dpcd[DP_MAX_LANE_COUNT]; bool ret; /* @@ -3924,6 +3945,20 @@ intel_dp_short_pulse(struct intel_dp *intel_dp) sink_irq_vector &= ~DP_AUTOMATED_TEST_REQUEST; } + /* + * If lane count has changed, we need to inform + * user mode of new capablities, this is done by setting + * our flag to do a fake disconnect and connect so it + * will appear to the user mode that a new panel is + * connected and will use the new capabilties of the + * panel + */ + if (old_lane_count != intel_dp->dpcd[DP_MAX_LANE_COUNT]) { + DRM_DEBUG_KMS("Lane count changed\n"); + intel_dp_update_simulate_detach_info(intel_dp); + return false; + } + if (sink_irq_vector & (DP_CP_IRQ | DP_SINK_SPECIFIC_IRQ)) DRM_DEBUG_DRIVER("CP or sink specific irq unhandled\n"); @@ -4213,13 +4248,17 @@ intel_dp_long_pulse(struct intel_connector *intel_connector) intel_display_power_get(to_i915(dev), power_domain); /* Can't disconnect eDP, but you can close the lid... */ - if (is_edp(intel_dp)) + if (is_edp(intel_dp)) { status = edp_detect(intel_dp); - else if (intel_digital_port_connected(to_i915(dev), - dp_to_dig_port(intel_dp))) + } else if (intel_connector->simulate_disconnect_connect) { + DRM_DEBUG_KMS("Simulating disconnect\n"); + status = connector_status_disconnected; + } else if (intel_digital_port_connected(to_i915(dev), + dp_to_dig_port(intel_dp))) { status = intel_dp_detect_dpcd(intel_dp); - else + } else { status = connector_status_disconnected; + } if (status != connector_status_connected) { intel_dp->compliance_test_active = 0; diff --git a/drivers/gpu/drm/i915/intel_drv.h b/drivers/gpu/drm/i915/intel_drv.h index 8daadc5..995f0da 100644 --- a/drivers/gpu/drm/i915/intel_drv.h +++ b/drivers/gpu/drm/i915/intel_drv.h @@ -257,6 +257,8 @@ struct intel_connector { struct edid *edid; struct edid *detect_edid; + bool simulate_disconnect_connect; + /* since POLL and HPD connectors may use the same HPD line keep the native state of connector->polled in case hotplug storm detection changes it */ u8 polled; @@ -1420,6 +1422,8 @@ bool intel_hdmi_compute_config(struct intel_encoder *encoder, struct intel_crtc_state *pipe_config); void intel_dp_dual_mode_set_tmds_output(struct intel_hdmi *hdmi, bool enable); +/* intel_hotplug.c */ +void intel_hpd_simulate_reconnect_work(struct work_struct *work); /* intel_lvds.c */ void intel_lvds_init(struct drm_device *dev); diff --git a/drivers/gpu/drm/i915/intel_hotplug.c b/drivers/gpu/drm/i915/intel_hotplug.c index 38eeca7..7e346d7 100644 --- a/drivers/gpu/drm/i915/intel_hotplug.c +++ b/drivers/gpu/drm/i915/intel_hotplug.c @@ -247,6 +247,55 @@ static bool intel_hpd_irq_event(struct drm_device *dev, return true; } +/* + * This function is the second half of logic to perform fake + * disconnect-connect. The first half was the connector setting + * a flag, returning status as disconnected and queing this function. + * + * This function uses simulate_disconnect_connect flag to identify the + * connector that should be detected again. Since this is executed after + * a delay if the panel is still plugged in it will be reported as + * connected to user mode + */ +void intel_hpd_simulate_reconnect_work(struct work_struct *work) +{ + struct drm_i915_private *dev_priv = + container_of(work, struct drm_i915_private, + hotplug.simulate_work.work); + struct drm_device *dev = dev_priv->dev; + struct intel_encoder *intel_encoder; + struct intel_connector *intel_connector; + struct drm_connector *connector; + struct drm_mode_config *mode_config = &dev->mode_config; + enum port port; + + DRM_DEBUG_KMS("\n"); + mutex_lock(&mode_config->mutex); + + list_for_each_entry(connector, &mode_config->connector_list, head) { + intel_connector = to_intel_connector(connector); + if (!intel_connector->simulate_disconnect_connect) + continue; + + intel_connector->simulate_disconnect_connect = false; + + if (!intel_connector->encoder) + continue; + + intel_encoder = intel_connector->encoder; + + port = intel_ddi_get_encoder_port(intel_encoder); + + spin_lock_irq(&dev_priv->irq_lock); + dev_priv->hotplug.long_port_mask |= (1<<port); + spin_unlock_irq(&dev_priv->irq_lock); + } + + mutex_unlock(&mode_config->mutex); + + queue_work(dev_priv->hotplug.dp_wq, &dev_priv->hotplug.dig_port_work); +} + static void i915_digport_work_func(struct work_struct *work) { struct drm_i915_private *dev_priv = @@ -509,4 +558,5 @@ void intel_hpd_cancel_work(struct drm_i915_private *dev_priv) cancel_work_sync(&dev_priv->hotplug.dig_port_work); cancel_work_sync(&dev_priv->hotplug.hotplug_work); cancel_delayed_work_sync(&dev_priv->hotplug.reenable_work); + cancel_delayed_work_sync(&dev_priv->hotplug.simulate_work); }