diff options
Diffstat (limited to 'drivers/gpu/drm/drm_probe_helper.c')
| -rw-r--r-- | drivers/gpu/drm/drm_probe_helper.c | 241 | 
1 files changed, 180 insertions, 61 deletions
| diff --git a/drivers/gpu/drm/drm_probe_helper.c b/drivers/gpu/drm/drm_probe_helper.c index 682359512996..bb427c5a4f1f 100644 --- a/drivers/gpu/drm/drm_probe_helper.c +++ b/drivers/gpu/drm/drm_probe_helper.c @@ -354,6 +354,79 @@ drm_helper_probe_detect(struct drm_connector *connector,  }  EXPORT_SYMBOL(drm_helper_probe_detect); +static int drm_helper_probe_get_modes(struct drm_connector *connector) +{ +	const struct drm_connector_helper_funcs *connector_funcs = +		connector->helper_private; +	int count; + +	count = connector_funcs->get_modes(connector); + +	/* +	 * Fallback for when DDC probe failed in drm_get_edid() and thus skipped +	 * override/firmware EDID. +	 */ +	if (count == 0 && connector->status == connector_status_connected) +		count = drm_add_override_edid_modes(connector); + +	return count; +} + +static int __drm_helper_update_and_validate(struct drm_connector *connector, +					    uint32_t maxX, uint32_t maxY, +					    struct drm_modeset_acquire_ctx *ctx) +{ +	struct drm_device *dev = connector->dev; +	struct drm_display_mode *mode; +	int mode_flags = 0; +	int ret; + +	drm_connector_list_update(connector); + +	if (connector->interlace_allowed) +		mode_flags |= DRM_MODE_FLAG_INTERLACE; +	if (connector->doublescan_allowed) +		mode_flags |= DRM_MODE_FLAG_DBLSCAN; +	if (connector->stereo_allowed) +		mode_flags |= DRM_MODE_FLAG_3D_MASK; + +	list_for_each_entry(mode, &connector->modes, head) { +		if (mode->status != MODE_OK) +			continue; + +		mode->status = drm_mode_validate_driver(dev, mode); +		if (mode->status != MODE_OK) +			continue; + +		mode->status = drm_mode_validate_size(mode, maxX, maxY); +		if (mode->status != MODE_OK) +			continue; + +		mode->status = drm_mode_validate_flag(mode, mode_flags); +		if (mode->status != MODE_OK) +			continue; + +		ret = drm_mode_validate_pipeline(mode, connector, ctx, +						 &mode->status); +		if (ret) { +			drm_dbg_kms(dev, +				    "drm_mode_validate_pipeline failed: %d\n", +				    ret); + +			if (drm_WARN_ON_ONCE(dev, ret != -EDEADLK)) +				mode->status = MODE_ERROR; +			else +				return -EDEADLK; +		} + +		if (mode->status != MODE_OK) +			continue; +		mode->status = drm_mode_validate_ycbcr420(mode, connector); +	} + +	return 0; +} +  /**   * drm_helper_probe_single_connector_modes - get complete set of display modes   * @connector: connector to probe @@ -418,11 +491,7 @@ int drm_helper_probe_single_connector_modes(struct drm_connector *connector,  {  	struct drm_device *dev = connector->dev;  	struct drm_display_mode *mode; -	const struct drm_connector_helper_funcs *connector_funcs = -		connector->helper_private;  	int count = 0, ret; -	int mode_flags = 0; -	bool verbose_prune = true;  	enum drm_connector_status old_status;  	struct drm_modeset_acquire_ctx ctx; @@ -502,74 +571,54 @@ retry:  		DRM_DEBUG_KMS("[CONNECTOR:%d:%s] disconnected\n",  			connector->base.id, connector->name);  		drm_connector_update_edid_property(connector, NULL); -		verbose_prune = false; -		goto prune; +		drm_mode_prune_invalid(dev, &connector->modes, false); +		goto exit;  	} -	count = (*connector_funcs->get_modes)(connector); - -	/* -	 * Fallback for when DDC probe failed in drm_get_edid() and thus skipped -	 * override/firmware EDID. -	 */ -	if (count == 0 && connector->status == connector_status_connected) -		count = drm_add_override_edid_modes(connector); +	count = drm_helper_probe_get_modes(connector);  	if (count == 0 && (connector->status == connector_status_connected || -			   connector->status == connector_status_unknown)) +			   connector->status == connector_status_unknown)) {  		count = drm_add_modes_noedid(connector, 1024, 768); -	count += drm_helper_probe_add_cmdline_mode(connector); -	if (count == 0) -		goto prune; -	drm_connector_list_update(connector); - -	if (connector->interlace_allowed) -		mode_flags |= DRM_MODE_FLAG_INTERLACE; -	if (connector->doublescan_allowed) -		mode_flags |= DRM_MODE_FLAG_DBLSCAN; -	if (connector->stereo_allowed) -		mode_flags |= DRM_MODE_FLAG_3D_MASK; - -	list_for_each_entry(mode, &connector->modes, head) { -		if (mode->status != MODE_OK) -			continue; - -		mode->status = drm_mode_validate_driver(dev, mode); -		if (mode->status != MODE_OK) -			continue; - -		mode->status = drm_mode_validate_size(mode, maxX, maxY); -		if (mode->status != MODE_OK) -			continue; - -		mode->status = drm_mode_validate_flag(mode, mode_flags); -		if (mode->status != MODE_OK) -			continue; +		/* +		 * Section 4.2.2.6 (EDID Corruption Detection) of the DP 1.4a +		 * Link CTS specifies that 640x480 (the official "failsafe" +		 * mode) needs to be the default if there's no EDID. +		 */ +		if (connector->connector_type == DRM_MODE_CONNECTOR_DisplayPort) +			drm_set_preferred_mode(connector, 640, 480); +	} +	count += drm_helper_probe_add_cmdline_mode(connector); +	if (count != 0) { +		ret = __drm_helper_update_and_validate(connector, maxX, maxY, &ctx); +		if (ret == -EDEADLK) { +			drm_modeset_backoff(&ctx); +			goto retry; +		} +	} -		ret = drm_mode_validate_pipeline(mode, connector, &ctx, -						 &mode->status); -		if (ret) { -			drm_dbg_kms(dev, -				    "drm_mode_validate_pipeline failed: %d\n", -				    ret); +	drm_mode_prune_invalid(dev, &connector->modes, true); -			if (drm_WARN_ON_ONCE(dev, ret != -EDEADLK)) { -				mode->status = MODE_ERROR; -			} else { -				drm_modeset_backoff(&ctx); -				goto retry; -			} +	/* +	 * Displayport spec section 5.2.1.2 ("Video Timing Format") says that +	 * all detachable sinks shall support 640x480 @60Hz as a fail safe +	 * mode. If all modes were pruned, perhaps because they need more +	 * lanes or a higher pixel clock than available, at least try to add +	 * in 640x480. +	 */ +	if (list_empty(&connector->modes) && +	    connector->connector_type == DRM_MODE_CONNECTOR_DisplayPort) { +		count = drm_add_modes_noedid(connector, 640, 480); +		ret = __drm_helper_update_and_validate(connector, maxX, maxY, &ctx); +		if (ret == -EDEADLK) { +			drm_modeset_backoff(&ctx); +			goto retry;  		} - -		if (mode->status != MODE_OK) -			continue; -		mode->status = drm_mode_validate_ycbcr420(mode, connector); +		drm_mode_prune_invalid(dev, &connector->modes, true);  	} -prune: -	drm_mode_prune_invalid(dev, &connector->modes, verbose_prune); - +exit:  	drm_modeset_drop_locks(&ctx);  	drm_modeset_acquire_fini(&ctx); @@ -964,3 +1013,73 @@ bool drm_helper_hpd_irq_event(struct drm_device *dev)  	return changed;  }  EXPORT_SYMBOL(drm_helper_hpd_irq_event); + +/** + * drm_connector_helper_get_modes_from_ddc - Updates the connector's EDID + *                                           property from the connector's + *                                           DDC channel + * @connector: The connector + * + * Returns: + * The number of detected display modes. + * + * Uses a connector's DDC channel to retrieve EDID data and update the + * connector's EDID property and display modes. Drivers can use this + * function to implement struct &drm_connector_helper_funcs.get_modes + * for connectors with a DDC channel. + */ +int drm_connector_helper_get_modes_from_ddc(struct drm_connector *connector) +{ +	struct edid *edid; +	int count = 0; + +	if (!connector->ddc) +		return 0; + +	edid = drm_get_edid(connector, connector->ddc); + +	// clears property if EDID is NULL +	drm_connector_update_edid_property(connector, edid); + +	if (edid) { +		count = drm_add_edid_modes(connector, edid); +		kfree(edid); +	} + +	return count; +} +EXPORT_SYMBOL(drm_connector_helper_get_modes_from_ddc); + +/** + * drm_connector_helper_get_modes - Read EDID and update connector. + * @connector: The connector + * + * Read the EDID using drm_edid_read() (which requires that connector->ddc is + * set), and update the connector using the EDID. + * + * This can be used as the "default" connector helper .get_modes() hook if the + * driver does not need any special processing. This is sets the example what + * custom .get_modes() hooks should do regarding EDID read and connector update. + * + * Returns: Number of modes. + */ +int drm_connector_helper_get_modes(struct drm_connector *connector) +{ +	const struct drm_edid *drm_edid; +	int count; + +	drm_edid = drm_edid_read(connector); + +	/* +	 * Unconditionally update the connector. If the EDID was read +	 * successfully, fill in the connector information derived from the +	 * EDID. Otherwise, if the EDID is NULL, clear the connector +	 * information. +	 */ +	count = drm_edid_connector_update(connector, drm_edid); + +	drm_edid_free(drm_edid); + +	return count; +} +EXPORT_SYMBOL(drm_connector_helper_get_modes); | 
