From: Shashank Sharma <shashank.sharma@xxxxxxxxx> 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@xxxxxxxxx> --- 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 _______________________________________________ Intel-gfx mailing list Intel-gfx@xxxxxxxxxxxxxxxxxxxxx http://lists.freedesktop.org/mailman/listinfo/intel-gfx