[v2,13/22] drm/i915: Reprobe eDP and LVDS connectors on hotplug event

Submitted by Lukas Wunner on April 19, 2015, 3:01 p.m.

Details

Message ID 2032c8103fc62d709f10be04a54b4df5c6302ad2.1439288957.git.lukas@wunner.de
State New
Headers show

Not browsing as part of any series.

Commit Message

Lukas Wunner April 19, 2015, 3:01 p.m.
The i915 driver probes eDP and LVDS connectors once on startup by
invoking intel_setup_outputs(). If no DPCD or EDID can be obtained,
it will remove the connectors from the device's mode configuration,
presuming they're ghost connectors. As a result, subsequent calls to
drm_fb_helper_hotplug_event() won't be able to pick up changes on
these connectors.

This is a problem on dual gpu laptops such as the MacBook Pro which
require either a handler to switch DDC lines, or the discrete gpu
to proxy the DDC/AUX communication: Both the handler and the discrete
gpu may initialize after the i915 driver, and consequently, eDP and
LVDS connectors which may seem disconnected at startup may later turn
out to be connected.

By contrast, nouveau will keep eDP and LVDS connectors for which no
modes were found in the device's mode configuration and thus is able
to reprobe these connectors in drm_fb_helper_hotplug_event().

Assimilate to nouveau's behaviour: Keep modeless eDP and LVDS connectors
in the mode configuration and change the ->output_poll_changed callback
to reprobe them on hotplug events.

In the case of LVDS, split intel_lvds_init() in half: The first portion
is executed once on startup. This consists of detecting, setting up and
registering the connector. The second portion is executed both on
startup and on every reprobe. This consists of reading the panel's EDID,
determining if dual channel LVDS is used, and initializing the reference
clock.

In the case of eDP, reprobe involves calling intel_edp_init_connector()
and initializing the reference clock.

Based (loosely) on a patch by Matthew Garrett <mjg59@srcf.ucam.org> who
duplicated intel_setup_outputs() and reduced it to just the eDP probing
portion (which is not sufficient since pre-retina MBPs used LVDS):
http://www.codon.org.uk/~mjg59/tmp/retina_patches/0024-i915-Add-support-for-reprobing-for-a-panel.patch

Bugzilla: https://bugzilla.kernel.org/show_bug.cgi?id=88861
Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=61115
Tested-by: Paul Hordiienko <pvt.gord@gmail.com>
    [MBP  6,2 2010  intel ILK + nvidia GT216  pre-retina]
Tested-by: William Brown <william@blackhats.net.au>
    [MBP  8,2 2011  intel SNB + amd turks     pre-retina]
Tested-by: Lukas Wunner <lukas@wunner.de>
    [MBP  9,1 2012  intel IVB + nvidia GK107  pre-retina]
Tested-by: Bruno Bierbaumer <bruno@bierbaumer.net>
    [MBP 11,3 2013  intel HSW + nvidia GK107  retina -- work in progress]

Signed-off-by: Lukas Wunner <lukas@wunner.de>
---
 drivers/gpu/drm/i915/intel_display.c | 41 ++++++++++++++++++----
 drivers/gpu/drm/i915/intel_dp.c      | 38 ++++++++++++--------
 drivers/gpu/drm/i915/intel_drv.h     |  6 ++++
 drivers/gpu/drm/i915/intel_lvds.c    | 67 +++++++++++++++++++++++++-----------
 drivers/gpu/drm/i915/intel_panel.c   |  4 +--
 5 files changed, 112 insertions(+), 44 deletions(-)

Patch hide | download patch | download mbox

diff --git a/drivers/gpu/drm/i915/intel_display.c b/drivers/gpu/drm/i915/intel_display.c
index 6335883..907b73e 100644
--- a/drivers/gpu/drm/i915/intel_display.c
+++ b/drivers/gpu/drm/i915/intel_display.c
@@ -8223,11 +8223,11 @@  static void ironlake_init_pch_refclk(struct drm_device *dev)
 	for_each_intel_encoder(dev, encoder) {
 		switch (encoder->type) {
 		case INTEL_OUTPUT_LVDS:
-			has_panel = true;
+			has_panel = intel_lvds_has_panel(encoder);
 			has_lvds = true;
 			break;
 		case INTEL_OUTPUT_EDP:
-			has_panel = true;
+			has_panel = intel_edp_has_panel(encoder);
 			if (enc_to_dig_port(&encoder->base)->port == PORT_A)
 				has_cpu_edp = true;
 			break;
@@ -14478,15 +14478,44 @@  intel_user_framebuffer_create(struct drm_device *dev,
 	return intel_framebuffer_create(dev, mode_cmd, obj);
 }
 
-#ifndef CONFIG_DRM_I915_FBDEV
-static inline void intel_fbdev_output_poll_changed(struct drm_device *dev)
+static void intel_output_poll_changed(struct drm_device *dev)
 {
-}
+	struct drm_i915_private *dev_priv = dev->dev_private;
+	struct intel_connector *intel_connector;
+
+	/* Reprobe LVDS and eDP as long as no EDID was retrieved from panel */
+	for_each_intel_connector(dev, intel_connector) {
+		struct drm_connector *connector = &intel_connector->base;
+
+		if ((connector->connector_type != DRM_MODE_CONNECTOR_LVDS &&
+		     connector->connector_type != DRM_MODE_CONNECTOR_eDP) ||
+		    !IS_ERR_OR_NULL(intel_connector->edid))
+			continue;
+
+		if ((connector->connector_type == DRM_MODE_CONNECTOR_LVDS &&
+		     !intel_lvds_probe_modes(connector)) ||
+		    (connector->connector_type == DRM_MODE_CONNECTOR_eDP &&
+		     !intel_edp_init_connector(
+			      enc_to_intel_dp(&intel_connector->encoder->base),
+			      intel_connector)))
+			continue;
+
+		intel_init_pch_refclk(dev);
+		drm_helper_move_panel_connectors_to_head(dev);
+		mutex_lock(&dev_priv->backlight_lock);
+		if (intel_connector->panel.backlight.device == NULL)
+			intel_backlight_device_register(intel_connector);
+		mutex_unlock(&dev_priv->backlight_lock);
+	}
+
+#ifdef CONFIG_DRM_I915_FBDEV
+	intel_fbdev_output_poll_changed(dev);
 #endif
+}
 
 static const struct drm_mode_config_funcs intel_mode_funcs = {
 	.fb_create = intel_user_framebuffer_create,
-	.output_poll_changed = intel_fbdev_output_poll_changed,
+	.output_poll_changed = intel_output_poll_changed,
 	.atomic_check = intel_atomic_check,
 	.atomic_commit = intel_atomic_commit,
 	.atomic_state_alloc = intel_atomic_state_alloc,
diff --git a/drivers/gpu/drm/i915/intel_dp.c b/drivers/gpu/drm/i915/intel_dp.c
index f1b9f93..23aa4ff 100644
--- a/drivers/gpu/drm/i915/intel_dp.c
+++ b/drivers/gpu/drm/i915/intel_dp.c
@@ -1998,6 +1998,15 @@  void intel_edp_panel_off(struct intel_dp *intel_dp)
 	pps_unlock(intel_dp);
 }
 
+bool intel_edp_has_panel(struct intel_encoder *intel_encoder)
+{
+	struct intel_dp *intel_dp = enc_to_intel_dp(&intel_encoder->base);
+	struct intel_connector *intel_connector = intel_dp->attached_connector;
+
+	return intel_dp->dpcd[DP_DPCD_REV] != 0 &&
+	       intel_connector->panel.fixed_mode != NULL;
+}
+
 /* Enable backlight in the panel power control. */
 static void _intel_edp_backlight_on(struct intel_dp *intel_dp)
 {
@@ -4338,6 +4347,9 @@  edp_detect(struct intel_dp *intel_dp)
 	struct drm_device *dev = intel_dp_to_dev(intel_dp);
 	enum drm_connector_status status;
 
+	if (!intel_edp_has_panel(intel_dp->attached_connector->encoder))
+		return connector_status_disconnected;
+
 	status = intel_panel_detect(dev);
 	if (status == connector_status_unknown)
 		status = connector_status_connected;
@@ -5599,8 +5611,8 @@  intel_dp_drrs_init(struct intel_connector *intel_connector,
 	return downclock_mode;
 }
 
-static bool intel_edp_init_connector(struct intel_dp *intel_dp,
-				     struct intel_connector *intel_connector)
+bool intel_edp_init_connector(struct intel_dp *intel_dp,
+			      struct intel_connector *intel_connector)
 {
 	struct drm_connector *connector = &intel_connector->base;
 	struct intel_digital_port *intel_dig_port = dp_to_dig_port(intel_dp);
@@ -5614,9 +5626,6 @@  static bool intel_edp_init_connector(struct intel_dp *intel_dp,
 	struct edid *edid;
 	enum pipe pipe = INVALID_PIPE;
 
-	if (!is_edp(intel_dp))
-		return true;
-
 	pps_lock(intel_dp);
 	intel_edp_panel_vdd_sanitize(intel_dp);
 	pps_unlock(intel_dp);
@@ -5630,8 +5639,7 @@  static bool intel_edp_init_connector(struct intel_dp *intel_dp,
 				intel_dp->dpcd[DP_MAX_DOWNSPREAD] &
 				DP_NO_AUX_HANDSHAKE_LINK_TRAINING;
 	} else {
-		/* if this fails, presume the device is a ghost */
-		DRM_INFO("failed to retrieve link info, disabling eDP\n");
+		DRM_INFO("failed to retrieve eDP link info\n");
 		return false;
 	}
 
@@ -5676,8 +5684,12 @@  static bool intel_edp_init_connector(struct intel_dp *intel_dp,
 	mutex_unlock(&dev->mode_config.mutex);
 
 	if (IS_VALLEYVIEW(dev)) {
-		intel_dp->edp_notifier.notifier_call = edp_notify_handler;
-		register_reboot_notifier(&intel_dp->edp_notifier);
+		if (intel_dp->edp_notifier.notifier_call == NULL) {
+			intel_dp->edp_notifier.notifier_call =
+				edp_notify_handler;
+			if (register_reboot_notifier(&intel_dp->edp_notifier))
+				intel_dp->edp_notifier.notifier_call = NULL;
+		}
 
 		/*
 		 * Figure out the current pipe for the initial backlight setup.
@@ -5817,9 +5829,8 @@  intel_dp_init_connector(struct intel_digital_port *intel_dig_port,
 		intel_dp_mst_encoder_init(intel_dig_port,
 					  intel_connector->base.base.id);
 
-	if (!intel_edp_init_connector(intel_dp, intel_connector)) {
-		drm_dp_aux_unregister(&intel_dp->aux);
-		if (is_edp(intel_dp)) {
+	if (is_edp(intel_dp)) {
+		if (!intel_edp_init_connector(intel_dp, intel_connector)) {
 			cancel_delayed_work_sync(&intel_dp->panel_vdd_work);
 			/*
 			 * vdd might still be enabled do to the delayed vdd off.
@@ -5829,9 +5840,6 @@  intel_dp_init_connector(struct intel_digital_port *intel_dig_port,
 			edp_panel_vdd_off_sync(intel_dp);
 			pps_unlock(intel_dp);
 		}
-		drm_connector_unregister(connector);
-		drm_connector_cleanup(connector);
-		return false;
 	}
 
 	intel_dp_add_properties(intel_dp, connector);
diff --git a/drivers/gpu/drm/i915/intel_drv.h b/drivers/gpu/drm/i915/intel_drv.h
index 47cef0e..361320b 100644
--- a/drivers/gpu/drm/i915/intel_drv.h
+++ b/drivers/gpu/drm/i915/intel_drv.h
@@ -1171,11 +1171,14 @@  bool intel_dp_compute_config(struct intel_encoder *encoder,
 bool intel_dp_is_edp(struct drm_device *dev, enum port port);
 enum irqreturn intel_dp_hpd_pulse(struct intel_digital_port *intel_dig_port,
 				  bool long_hpd);
+bool intel_edp_init_connector(struct intel_dp *intel_dp,
+			      struct intel_connector *intel_connector);
 void intel_edp_backlight_on(struct intel_dp *intel_dp);
 void intel_edp_backlight_off(struct intel_dp *intel_dp);
 void intel_edp_panel_vdd_on(struct intel_dp *intel_dp);
 void intel_edp_panel_on(struct intel_dp *intel_dp);
 void intel_edp_panel_off(struct intel_dp *intel_dp);
+bool intel_edp_has_panel(struct intel_encoder *intel_encoder);
 void intel_dp_add_properties(struct intel_dp *intel_dp, struct drm_connector *connector);
 void intel_dp_mst_suspend(struct drm_device *dev);
 void intel_dp_mst_resume(struct drm_device *dev);
@@ -1258,6 +1261,8 @@  bool intel_hdmi_compute_config(struct intel_encoder *encoder,
 
 /* intel_lvds.c */
 void intel_lvds_init(struct drm_device *dev);
+bool intel_lvds_probe_modes(struct drm_connector *connector);
+bool intel_lvds_has_panel(struct intel_encoder *intel_encoder);
 bool intel_is_dual_link_lvds(struct drm_device *dev);
 
 
@@ -1307,6 +1312,7 @@  extern struct drm_display_mode *intel_find_panel_downclock(
 				struct drm_connector *connector);
 void intel_backlight_register(struct drm_device *dev);
 void intel_backlight_unregister(struct drm_device *dev);
+int intel_backlight_device_register(struct intel_connector *connector);
 
 
 /* intel_psr.c */
diff --git a/drivers/gpu/drm/i915/intel_lvds.c b/drivers/gpu/drm/i915/intel_lvds.c
index cb634f4..4c7c8a2 100644
--- a/drivers/gpu/drm/i915/intel_lvds.c
+++ b/drivers/gpu/drm/i915/intel_lvds.c
@@ -359,7 +359,7 @@  static bool intel_lvds_compute_config(struct intel_encoder *intel_encoder,
 /**
  * Detect the LVDS connection.
  *
- * Since LVDS doesn't have hotlug, we use the lid as a proxy.  Open means
+ * Since LVDS doesn't have hotplug, we use the lid as a proxy.  Open means
  * connected and closed means disconnected.  We also send hotplug events as
  * needed, using lid status notification from the input layer.
  */
@@ -372,6 +372,9 @@  intel_lvds_detect(struct drm_connector *connector, bool force)
 	DRM_DEBUG_KMS("[CONNECTOR:%d:%s]\n",
 		      connector->base.id, connector->name);
 
+	if (!intel_lvds_has_panel(to_intel_connector(connector)->encoder))
+		return connector_status_disconnected;
+
 	status = intel_panel_detect(dev);
 	if (status != connector_status_unknown)
 		return status;
@@ -936,13 +939,6 @@  void intel_lvds_init(struct drm_device *dev)
 	struct intel_connector *intel_connector;
 	struct drm_connector *connector;
 	struct drm_encoder *encoder;
-	struct drm_display_mode *scan; /* *modes, *bios_mode; */
-	struct drm_display_mode *fixed_mode = NULL;
-	struct drm_display_mode *downclock_mode = NULL;
-	struct edid *edid;
-	struct drm_crtc *crtc;
-	u32 lvds;
-	int pipe;
 	u8 pin;
 
 	/*
@@ -1052,6 +1048,29 @@  void intel_lvds_init(struct drm_device *dev)
 				      dev->mode_config.scaling_mode_property,
 				      DRM_MODE_SCALE_ASPECT);
 	intel_connector->panel.fitting_mode = DRM_MODE_SCALE_ASPECT;
+
+	drm_connector_register(connector);
+	intel_lvds_probe_modes(connector);
+}
+
+bool intel_lvds_probe_modes(struct drm_connector *connector)
+{
+	struct drm_device *dev = connector->dev;
+	struct drm_i915_private *dev_priv = dev->dev_private;
+	struct intel_connector *intel_connector = to_intel_connector(connector);
+	struct intel_lvds_connector *lvds_connector = to_lvds_connector(connector);
+	struct intel_encoder *intel_encoder = intel_connector->encoder;
+	struct drm_encoder *encoder = &intel_encoder->base;
+	struct intel_lvds_encoder *lvds_encoder = to_lvds_encoder(encoder);
+	struct drm_display_mode *scan;
+	struct drm_display_mode *fixed_mode = NULL;
+	struct drm_display_mode *downclock_mode = NULL;
+	struct edid *edid;
+	struct drm_crtc *crtc;
+	u32 lvds;
+	int pipe;
+	u8 pin = GMBUS_PIN_PANEL;
+
 	/*
 	 * LVDS discovery:
 	 * 1) check for EDID on DDC
@@ -1079,7 +1098,7 @@  void intel_lvds_init(struct drm_device *dev)
 	} else {
 		edid = ERR_PTR(-ENOENT);
 	}
-	lvds_connector->base.edid = edid;
+	intel_connector->edid = edid;
 
 	if (IS_ERR_OR_NULL(edid)) {
 		/* Didn't get an EDID, so
@@ -1155,24 +1174,30 @@  out:
 	lvds_encoder->a3_power = I915_READ(lvds_encoder->reg) &
 				 LVDS_A3_POWER_MASK;
 
-	lvds_connector->lid_notifier.notifier_call = intel_lid_notify;
-	if (acpi_lid_notifier_register(&lvds_connector->lid_notifier)) {
-		DRM_DEBUG_KMS("lid notifier registration failed\n");
-		lvds_connector->lid_notifier.notifier_call = NULL;
+	if (lvds_connector->lid_notifier.notifier_call == NULL) {
+		lvds_connector->lid_notifier.notifier_call = intel_lid_notify;
+		if (acpi_lid_notifier_register(&lvds_connector->lid_notifier)) {
+			DRM_DEBUG_KMS("lid notifier registration failed\n");
+			lvds_connector->lid_notifier.notifier_call = NULL;
+		}
 	}
-	drm_connector_register(connector);
 
 	intel_panel_setup_backlight(connector, INVALID_PIPE);
 
-	return;
+	return true;
 
 failed:
 	mutex_unlock(&dev->mode_config.mutex);
 
-	DRM_DEBUG_KMS("No LVDS modes found, disabling.\n");
-	drm_connector_cleanup(connector);
-	drm_encoder_cleanup(encoder);
-	kfree(lvds_encoder);
-	kfree(lvds_connector);
-	return;
+	DRM_DEBUG_KMS("No LVDS modes found\n");
+	return false;
+}
+
+bool intel_lvds_has_panel(struct intel_encoder *intel_encoder)
+{
+	struct intel_lvds_connector *lvds_connector =
+		to_lvds_encoder(&intel_encoder->base)->attached_connector;
+	struct intel_connector *intel_connector = &lvds_connector->base;
+
+	return intel_connector->panel.fixed_mode != NULL;
 }
diff --git a/drivers/gpu/drm/i915/intel_panel.c b/drivers/gpu/drm/i915/intel_panel.c
index e2ab3f6..4dbfb3df 100644
--- a/drivers/gpu/drm/i915/intel_panel.c
+++ b/drivers/gpu/drm/i915/intel_panel.c
@@ -1139,7 +1139,7 @@  static const struct backlight_ops intel_backlight_device_ops = {
 	.get_brightness = intel_backlight_device_get_brightness,
 };
 
-static int intel_backlight_device_register(struct intel_connector *connector)
+int intel_backlight_device_register(struct intel_connector *connector)
 {
 	struct intel_panel *panel = &connector->panel;
 	struct backlight_properties props;
@@ -1202,7 +1202,7 @@  static void intel_backlight_device_unregister(struct intel_connector *connector)
 	}
 }
 #else /* CONFIG_BACKLIGHT_CLASS_DEVICE */
-static int intel_backlight_device_register(struct intel_connector *connector)
+int intel_backlight_device_register(struct intel_connector *connector)
 {
 	return 0;
 }