[Intel-gfx] [PATCH 1/2] drm/i915: Optimize HDMI EDID reads

shashank.sharma at intel.com shashank.sharma at intel.com
Tue Aug 12 14:38:20 CEST 2014


From: Shashank Sharma <shashank.sharma at intel.com>

The current hdmi_detect() function is getting called from
many places, few of these are:
1. HDMI hot plug interrupt bottom half
2. get_resources() IOCTL family
3. drm_helper_probe_single_connector_modes() family
4. output_poll_execute()
5. status_show() etc...

Every time this function is called, it goes and reads HDMI EDID over
DDC channel. Ideally, reading EDID is only required when there is a
real hot plug, and then for all IOCTL and userspace detect functions
can be answered using this same EDID.

The current patch adds EDID caching for a finite duration (1 minute)
This is how it works:
1. With in this caching duration, when usespace get_resource and other
   modeset_detect calls get called, we can use the cached EDID.
2. Even the get_mode function can use the cached EDID.
3. A delayed work will clear the cached EDID after the timeout.
4. If there is a real HDMI hotplug, within the caching duration, the
   cached EDID is updates, and a new delayed work is scheduled.

Signed-off-by: Shashank Sharma <shashank.sharma at intel.com>
---
 drivers/gpu/drm/i915/intel_drv.h  |  4 ++
 drivers/gpu/drm/i915/intel_hdmi.c | 92 ++++++++++++++++++++++++++++++++++++---
 2 files changed, 90 insertions(+), 6 deletions(-)

diff --git a/drivers/gpu/drm/i915/intel_drv.h b/drivers/gpu/drm/i915/intel_drv.h
index 28d185d..185a45a 100644
--- a/drivers/gpu/drm/i915/intel_drv.h
+++ b/drivers/gpu/drm/i915/intel_drv.h
@@ -110,6 +110,8 @@
 #define INTEL_DSI_VIDEO_MODE	0
 #define INTEL_DSI_COMMAND_MODE	1
 
+#define INTEL_HDMI_EDID_CACHING_SEC 60
+
 struct intel_framebuffer {
 	struct drm_framebuffer base;
 	struct drm_i915_gem_object *obj;
@@ -507,6 +509,8 @@ struct intel_hdmi {
 	enum hdmi_force_audio force_audio;
 	bool rgb_quant_range_selectable;
 	enum hdmi_picture_aspect aspect_ratio;
+	struct edid *edid;
+	struct delayed_work edid_work;
 	void (*write_infoframe)(struct drm_encoder *encoder,
 				enum hdmi_infoframe_type type,
 				const void *frame, ssize_t len);
diff --git a/drivers/gpu/drm/i915/intel_hdmi.c b/drivers/gpu/drm/i915/intel_hdmi.c
index 5f47d35..8dc3970 100644
--- a/drivers/gpu/drm/i915/intel_hdmi.c
+++ b/drivers/gpu/drm/i915/intel_hdmi.c
@@ -962,6 +962,22 @@ bool intel_hdmi_compute_config(struct intel_encoder *encoder,
 	return true;
 }
 
+/* Work function to invalidate EDID caching */
+static void intel_hdmi_invalidate_edid(struct work_struct *work)
+{
+	struct intel_hdmi *intel_hdmi = container_of(to_delayed_work(work),
+				struct intel_hdmi, edid_work);
+	struct drm_device *dev = intel_hdmi_to_dev(intel_hdmi);
+	struct drm_mode_config *mode_config = &dev->mode_config;
+
+	mutex_lock(&mode_config->mutex);
+	/* Checkpatch says kfree is NULL protected */
+	kfree(intel_hdmi->edid);
+	intel_hdmi->edid = NULL;
+	mutex_unlock(&mode_config->mutex);
+	DRM_DEBUG_DRIVER("cleaned up cached EDID\n");
+}
+
 static enum drm_connector_status
 intel_hdmi_detect(struct drm_connector *connector, bool force)
 {
@@ -978,15 +994,58 @@ intel_hdmi_detect(struct drm_connector *connector, bool force)
 	DRM_DEBUG_KMS("[CONNECTOR:%d:%s]\n",
 		      connector->base.id, connector->name);
 
+	/*
+	* hdmi_detect() gets called from both get_resource()
+	* and HDMI hpd bottom half work function.
+	* Its not required to read EDID for every detect call until it's is
+	* from a hot plug. Lets cache the EDID as soon as we get
+	* a HPD, and then try to re-use this for all the non hpd detact calls
+	* coming with in a finite duration.
+	*/
+	if (INTEL_INFO(dev)->gen < 6)
+		/* Do not break old platforms */
+		goto skip_optimization;
+
+	/* If call is from HPD, do check real status by reading EDID */
+	if (!force)
+		goto skip_optimization;
+
+	/* This is a non-hpd call, lets see if we can optimize this */
+	if (intel_hdmi->edid) {
+		/*
+		* So this is a non-hpd call, within the duration when
+		* EDID caching is valid. So previous status (valid)
+		* of connector is re-usable.
+		*/
+		if (connector->status != connector_status_unknown) {
+			DRM_DEBUG_DRIVER("Reporting force status\n");
+			return connector->status;
+		}
+	}
+
+skip_optimization:
 	power_domain = intel_display_port_power_domain(intel_encoder);
 	intel_display_power_get(dev_priv, power_domain);
 
 	intel_hdmi->has_hdmi_sink = false;
 	intel_hdmi->has_audio = false;
 	intel_hdmi->rgb_quant_range_selectable = false;
+
+	/*
+	* You are well deserving, dear code, as you have survived
+	* all the optimizations. Now go and enjoy reading EDID
+	*/
 	edid = drm_get_edid(connector,
-			    intel_gmbus_get_adapter(dev_priv,
-						    intel_hdmi->ddc_bus));
+			intel_gmbus_get_adapter(dev_priv,
+						intel_hdmi->ddc_bus));
+	/*
+	* Now when we have read new EDID, update cached EDID with
+	* latest (both NULL or non NULL). Cancel the delayed work
+	* which cleans up the cached EDID. Re-schedule if required.
+	*/
+	kfree(intel_hdmi->edid);
+	intel_hdmi->edid = edid;
+	cancel_delayed_work_sync(&intel_hdmi->edid_work);
 
 	if (edid) {
 		if (edid->input & DRM_EDID_INPUT_DIGITAL) {
@@ -997,8 +1056,17 @@ intel_hdmi_detect(struct drm_connector *connector, bool force)
 			intel_hdmi->has_audio = drm_detect_monitor_audio(edid);
 			intel_hdmi->rgb_quant_range_selectable =
 				drm_rgb_quant_range_selectable(edid);
+			/*
+			* Allow re-use of cached EDID for 60 sec, as
+			* userspace modeset should happen within this
+			* duration, and multiple detect calls will be
+			* handled using cached EDID.
+			*/
+			schedule_delayed_work(&intel_hdmi->edid_work,
+				msecs_to_jiffies(
+					INTEL_HDMI_EDID_CACHING_SEC
+							* 1000));
 		}
-		kfree(edid);
 	}
 
 	if (status == connector_status_connected) {
@@ -1027,13 +1095,22 @@ static int intel_hdmi_get_modes(struct drm_connector *connector)
 
 	power_domain = intel_display_port_power_domain(intel_encoder);
 	intel_display_power_get(dev_priv, power_domain);
-
-	ret = intel_ddc_get_modes(connector,
+	/*
+	* GEN6 and + have software support for EDID caching, so
+	* use cached_edid from detect call, if available.
+	*/
+	if (intel_hdmi->edid && (INTEL_INFO(connector->dev)->gen >= 6)) {
+		ret = intel_connector_update_modes(connector,
+				intel_hdmi->edid);
+		DRM_DEBUG_DRIVER("Using cached EDID, got %d modes\n", ret);
+	} else {
+		ret = intel_ddc_get_modes(connector,
 				   intel_gmbus_get_adapter(dev_priv,
 							   intel_hdmi->ddc_bus));
+		DRM_DEBUG_DRIVER("Read EDID, got %d modes\n", ret);
+	}
 
 	intel_display_power_put(dev_priv, power_domain);
-
 	return ret;
 }
 
@@ -1661,5 +1738,8 @@ void intel_hdmi_init(struct drm_device *dev, int hdmi_reg, enum port port)
 	intel_dig_port->hdmi.hdmi_reg = hdmi_reg;
 	intel_dig_port->dp.output_reg = 0;
 
+	/* Work function to invalidate cached EDID after timeout */
+	INIT_DELAYED_WORK(&(intel_dig_port->hdmi.edid_work),
+				intel_hdmi_invalidate_edid);
 	intel_hdmi_init_connector(intel_dig_port, intel_connector);
 }
-- 
1.9.1




More information about the Intel-gfx mailing list