[Intel-gfx] [PATCH 2/2] drm/i915: Lane count change detection
Daniel Vetter
daniel at ffwll.ch
Tue May 17 10:17:41 UTC 2016
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 at intel.com>
> Signed-off-by: Shubhangi Shrivastava <shubhangi.shrivastava at 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 at lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/intel-gfx
--
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
More information about the Intel-gfx
mailing list