diff mbox series

[v2,08/22] drm/i915/fdi: Improve FDI BW sharing between pipe B and C

Message ID 20230824080517.693621-9-imre.deak@intel.com (mailing list archive)
State New, archived
Headers show
Series drm/i915: Improve BW management on shared display links | expand

Commit Message

Imre Deak Aug. 24, 2023, 8:05 a.m. UTC
At the moment modesetting pipe C on IVB will fail if pipe B uses 4 FDI
lanes. Make the BW sharing more dynamic by trying to reduce pipe B's
link bpp in this case, until pipe B uses only up to 2 FDI lanes.

For this instead of the encoder compute config retry loop - which
reduced link bpp only for the encoder's pipe - check the overall BW
limits after all CRTC states have been computed and if the check fails
reduce the maximum link bpp for a selected pipe (for FDI pipe B or C as
required) and recompute all the CRTC states. Retry this sequence until
either the overall BW limit check passes, or further bpp reduction is
not possible (because all pipes/encoders sharing the link BW reached
their minimum link bpp).

This change also prepares for an upcoming patch resolving BW limits in
a similar way on MST links as well.

v2:
- Rename intel_crtc_state::max_link_bpp to max_link_bpp_x16 and
  intel_link_bw_limits::max_bpp to max_bpp_x16. (Jani)
- Increase back pipe B's link bpp if earlier it was limited due to
  pipe C and pipe C gets later disabled.
- Don't assume that a CRTC is already in the atomic state, while
  reducing its link bpp.
- Add DocBook description to intel_fdi_atomic_check_link().

Cc: Jani Nikula <jani.nikula@linux.intel.com>
Signed-off-by: Imre Deak <imre.deak@intel.com>
---
 drivers/gpu/drm/i915/display/g4x_hdmi.c       |   5 +-
 drivers/gpu/drm/i915/display/intel_atomic.c   | 207 ++++++++++++++++++
 drivers/gpu/drm/i915/display/intel_atomic.h   |   8 +
 drivers/gpu/drm/i915/display/intel_crt.c      |   7 +
 drivers/gpu/drm/i915/display/intel_crtc.c     |   1 +
 drivers/gpu/drm/i915/display/intel_display.c  |  44 ++--
 drivers/gpu/drm/i915/display/intel_display.h  |   4 +
 .../drm/i915/display/intel_display_types.h    |   9 +-
 drivers/gpu/drm/i915/display/intel_dp.c       |   3 +-
 drivers/gpu/drm/i915/display/intel_fdi.c      | 137 ++++++++++--
 drivers/gpu/drm/i915/display/intel_fdi.h      |   5 +
 drivers/gpu/drm/i915/display/intel_lvds.c     |   9 +-
 drivers/gpu/drm/i915/display/intel_sdvo.c     |   9 +-
 13 files changed, 404 insertions(+), 44 deletions(-)

Comments

Ville Syrjala Sept. 11, 2023, 6:59 p.m. UTC | #1
On Thu, Aug 24, 2023 at 11:05:03AM +0300, Imre Deak wrote:
> At the moment modesetting pipe C on IVB will fail if pipe B uses 4 FDI
> lanes. Make the BW sharing more dynamic by trying to reduce pipe B's
> link bpp in this case, until pipe B uses only up to 2 FDI lanes.
> 
> For this instead of the encoder compute config retry loop - which
> reduced link bpp only for the encoder's pipe - check the overall BW
> limits after all CRTC states have been computed and if the check fails
> reduce the maximum link bpp for a selected pipe (for FDI pipe B or C as
> required) and recompute all the CRTC states. Retry this sequence until
> either the overall BW limit check passes, or further bpp reduction is
> not possible (because all pipes/encoders sharing the link BW reached
> their minimum link bpp).
> 
> This change also prepares for an upcoming patch resolving BW limits in
> a similar way on MST links as well.
> 
> v2:
> - Rename intel_crtc_state::max_link_bpp to max_link_bpp_x16 and
>   intel_link_bw_limits::max_bpp to max_bpp_x16. (Jani)
> - Increase back pipe B's link bpp if earlier it was limited due to
>   pipe C and pipe C gets later disabled.
> - Don't assume that a CRTC is already in the atomic state, while
>   reducing its link bpp.
> - Add DocBook description to intel_fdi_atomic_check_link().
> 
> Cc: Jani Nikula <jani.nikula@linux.intel.com>
> Signed-off-by: Imre Deak <imre.deak@intel.com>
> ---
>  drivers/gpu/drm/i915/display/g4x_hdmi.c       |   5 +-
>  drivers/gpu/drm/i915/display/intel_atomic.c   | 207 ++++++++++++++++++
>  drivers/gpu/drm/i915/display/intel_atomic.h   |   8 +
>  drivers/gpu/drm/i915/display/intel_crt.c      |   7 +
>  drivers/gpu/drm/i915/display/intel_crtc.c     |   1 +
>  drivers/gpu/drm/i915/display/intel_display.c  |  44 ++--
>  drivers/gpu/drm/i915/display/intel_display.h  |   4 +
>  .../drm/i915/display/intel_display_types.h    |   9 +-
>  drivers/gpu/drm/i915/display/intel_dp.c       |   3 +-
>  drivers/gpu/drm/i915/display/intel_fdi.c      | 137 ++++++++++--
>  drivers/gpu/drm/i915/display/intel_fdi.h      |   5 +
>  drivers/gpu/drm/i915/display/intel_lvds.c     |   9 +-
>  drivers/gpu/drm/i915/display/intel_sdvo.c     |   9 +-
>  13 files changed, 404 insertions(+), 44 deletions(-)
> 
> diff --git a/drivers/gpu/drm/i915/display/g4x_hdmi.c b/drivers/gpu/drm/i915/display/g4x_hdmi.c
> index 634b14116d9dd..ebbceddc13259 100644
> --- a/drivers/gpu/drm/i915/display/g4x_hdmi.c
> +++ b/drivers/gpu/drm/i915/display/g4x_hdmi.c
> @@ -133,8 +133,11 @@ static int g4x_hdmi_compute_config(struct intel_encoder *encoder,
>  	struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc);
>  	struct drm_i915_private *i915 = to_i915(encoder->base.dev);
>  
> -	if (HAS_PCH_SPLIT(i915))
> +	if (HAS_PCH_SPLIT(i915)) {
>  		crtc_state->has_pch_encoder = true;
> +		if (!intel_atomic_compute_pipe_bpp(crtc_state))
> +			return -EINVAL;
> +	}
>  
>  	if (IS_G4X(i915))
>  		crtc_state->has_hdmi_sink = g4x_compute_has_hdmi_sink(state, crtc);
> diff --git a/drivers/gpu/drm/i915/display/intel_atomic.c b/drivers/gpu/drm/i915/display/intel_atomic.c
> index 7cf51dd8c0567..ee4cbf80ddb55 100644
> --- a/drivers/gpu/drm/i915/display/intel_atomic.c
> +++ b/drivers/gpu/drm/i915/display/intel_atomic.c

Not convinced we should put anything there. I think I mostly managed to
move a bunch or semi-random stuff out now, so all it more or less has is
the struct intel_atomic_state alloc/free stuff.

You have intel_atomic_check() in intel_display.c and the lower level
intel_atomic_check_config() is also there, but now the middle man
intel_atomic_check_config_and_link() ended up in intel_atomic.c for
whatever reason. I'd just shove all of it into intel_display.c.

The patch is also rather massive. Can we try to chunk it up to
eg. something like:
1) add the new infrastructure
2) convert the fdi retry stuff to use the new infrastructure
3) add intel_fdi_add_affected_crtcs() (which seems more or less
   independent from the rest of the fdi conversion?)
?

> @@ -38,6 +38,7 @@
>  #include "intel_atomic.h"
>  #include "intel_cdclk.h"
>  #include "intel_display_types.h"
> +#include "intel_fdi.h"
>  #include "intel_global_state.h"
>  #include "intel_hdcp.h"
>  #include "intel_psr.h"
> @@ -358,3 +359,209 @@ intel_atomic_get_crtc_state(struct drm_atomic_state *state,
>  
>  	return to_intel_crtc_state(crtc_state);
>  }
> +
> +/**
> + * intel_atomic_compute_pipe_bpp - compute pipe bpp limited by max link bpp
> + * @crtc_state: the crtc state
> + *
> + * Compute the pipe bpp limited by the CRTC's maximum link bpp. Encoders can
> + * call this function during state computation in the simple case where the
> + * link bpp will always match the pipe bpp. This is the case for all non-DP
> + * encoders, while DP encoders will use a link bpp lower than pipe bpp in case
> + * of DSC compression.
> + *
> + * Returns %true in case of success, %false if pipe bpp would need to be
> + * reduced below its valid range.
> + */
> +bool intel_atomic_compute_pipe_bpp(struct intel_crtc_state *crtc_state)
> +{
> +	int pipe_bpp = min(crtc_state->pipe_bpp,
> +			   to_bpp_int(crtc_state->max_link_bpp_x16));
> +
> +	pipe_bpp = rounddown(pipe_bpp, 2 * 3);
> +
> +	if (pipe_bpp < 6 * 3)
> +		return false;
> +
> +	crtc_state->pipe_bpp = pipe_bpp;
> +
> +	return true;
> +}
> +
> +/**
> + * intel_atomic_reduce_link_bpp - reduce maximum link bpp for a selected pipe
> + * @state: atomic state
> + * @limits: link BW limits
> + * @pipe_mask: mask of pipes to select from
> + * @reason: explanation of why bpp reduction is needed
> + *
> + * Select the pipe from @pipe_mask with the biggest link bpp value and set the
> + * maximum of link bpp in @limits below this value. Modeset the selected pipe,
> + * so that its state will get recomputed.
> + *
> + * This function can be called to resolve a link's BW overallocation by reducing
> + * the link bpp of one pipe on the link and hence reducing the total link BW.
> + *
> + * Returns
> + *   - 0 in case of success
> + *   - %-EINVAL if no pipe can further reduce its link bpp
> + *   - Other negative error, if modesetting the selected pipe failed
> + */
> +int intel_atomic_reduce_link_bpp(struct intel_atomic_state *state,
> +				 struct intel_link_bw_limits *limits,
> +				 u8 pipe_mask,
> +				 const char *reason)
> +{
> +	struct drm_i915_private *i915 = to_i915(state->base.dev);
> +	enum pipe max_bpp_pipe = INVALID_PIPE;
> +	struct intel_crtc *crtc;
> +	int max_bpp = 0;
> +
> +	for_each_intel_crtc_in_pipe_mask(&i915->drm, crtc, pipe_mask) {
> +		struct intel_crtc_state *crtc_state;
> +		int pipe_bpp;
> +
> +		if (limits->min_bpp_pipes & BIT(crtc->pipe))
> +			continue;
> +
> +		crtc_state = intel_atomic_get_crtc_state(&state->base,
> +							 crtc);
> +		if (IS_ERR(crtc_state))
> +			return PTR_ERR(crtc_state);
> +
> +		if (crtc_state->dsc.compression_enable)
> +			pipe_bpp = crtc_state->dsc.compressed_bpp;
> +		else
> +			pipe_bpp = crtc_state->pipe_bpp;
> +
> +		if (pipe_bpp > max_bpp) {
> +			max_bpp = pipe_bpp;
> +			max_bpp_pipe = crtc->pipe;
> +		}
> +	}
> +
> +	if (max_bpp_pipe == INVALID_PIPE)
> +		return -EINVAL;
> +
> +	limits->max_bpp_x16[max_bpp_pipe] = to_bpp_x16(max_bpp) - 1;
> +
> +	return intel_modeset_pipes_in_mask(state, reason,
> +					   BIT(max_bpp_pipe), false);
> +}
> +
> +static int intel_atomic_check_link(struct intel_atomic_state *state,
> +				   struct intel_link_bw_limits *limits)
> +{
> +	int ret;
> +
> +	ret = intel_fdi_atomic_check_link(state, limits);
> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static bool
> +assert_link_limit_change_valid(struct drm_i915_private *i915,
> +			       const struct intel_link_bw_limits *old_limits,
> +			       const struct intel_link_bw_limits *new_limits)
> +{
> +	bool bpps_changed = false;
> +	enum pipe pipe;
> +
> +	for_each_pipe(i915, pipe) {
> +		/* The bpp limit can only decrease. */
> +		if (drm_WARN_ON(&i915->drm,
> +				new_limits->max_bpp_x16[pipe] >
> +				old_limits->max_bpp_x16[pipe]))
> +			return false;
> +
> +		if (new_limits->max_bpp_x16[pipe] <
> +		    old_limits->max_bpp_x16[pipe])
> +			bpps_changed = true;
> +	}
> +
> +	if (drm_WARN_ON(&i915->drm,
> +			!bpps_changed))
> +		return false;
> +
> +	return true;
> +}
> +
> +static bool
> +reset_link_bpp_limit_to_min(struct intel_atomic_state *state,
> +			    const struct intel_link_bw_limits *old_limits,
> +			    struct intel_link_bw_limits *new_limits,
> +			    enum pipe failed_pipe)
> +{
> +	if (failed_pipe == INVALID_PIPE)
> +		return false;
> +
> +	if (new_limits->min_bpp_pipes & BIT(failed_pipe))
> +		return false;
> +
> +	if (new_limits->max_bpp_x16[failed_pipe] ==
> +	    old_limits->max_bpp_x16[failed_pipe])
> +		return false;
> +
> +	new_limits->max_bpp_x16[failed_pipe] =
> +		old_limits->max_bpp_x16[failed_pipe];
> +	new_limits->min_bpp_pipes |= BIT(failed_pipe);
> +
> +	return true;
> +}
> +
> +/**
> + * intel_atomic_check_config_and_link - compute CRTC configs, resolving any BW limits
> + * @state: atomic state
> + *
> + * Compute the configuration of all CRTCs in @state and resolve any BW
> + * limitations on links shared by these CRTCs.
> + *
> + * Return 0 in case of success, or a negative error code otherwise.
> + */
> +int intel_atomic_check_config_and_link(struct intel_atomic_state *state)
> +{
> +	struct drm_i915_private *i915 = to_i915(state->base.dev);
> +	struct intel_link_bw_limits new_limits = {};
> +	struct intel_link_bw_limits old_limits;
> +	enum pipe pipe;
> +	int ret;
> +
> +	for_each_pipe(i915, pipe)
> +		new_limits.max_bpp_x16[pipe] = INT_MAX;
> +
> +	old_limits = new_limits;
> +
> +	while (true) {
> +		enum pipe failed_pipe;
> +
> +		ret = intel_atomic_check_config(state, &new_limits,
> +						&failed_pipe);
> +		if (ret) {
> +			if (ret == -EINVAL &&
> +			    reset_link_bpp_limit_to_min(state,
> +							&old_limits,
> +							&new_limits,
> +							failed_pipe))
> +				continue;
> +
> +			break;
> +		}
> +
> +		old_limits = new_limits;
> +
> +		ret = intel_atomic_check_link(state, &new_limits);
> +		if (ret != -EAGAIN)
> +			break;
> +
> +		if (!assert_link_limit_change_valid(i915,
> +						    &old_limits,
> +						    &new_limits)) {
> +			ret = -EINVAL;
> +			break;
> +		}
> +	}
> +
> +	return ret;
> +}
> diff --git a/drivers/gpu/drm/i915/display/intel_atomic.h b/drivers/gpu/drm/i915/display/intel_atomic.h
> index e506f6a873447..bbf3595d52c41 100644
> --- a/drivers/gpu/drm/i915/display/intel_atomic.h
> +++ b/drivers/gpu/drm/i915/display/intel_atomic.h
> @@ -20,6 +20,7 @@ struct intel_atomic_state;
>  struct intel_connector;
>  struct intel_crtc;
>  struct intel_crtc_state;
> +struct intel_link_bw_limits;
>  
>  int intel_digital_connector_atomic_get_property(struct drm_connector *connector,
>  						const struct drm_connector_state *state,
> @@ -52,4 +53,11 @@ struct intel_crtc_state *
>  intel_atomic_get_crtc_state(struct drm_atomic_state *state,
>  			    struct intel_crtc *crtc);
>  
> +int intel_atomic_reduce_link_bpp(struct intel_atomic_state *state,
> +				 struct intel_link_bw_limits *limits,
> +				 u8 pipe_mask,
> +				 const char *reason);
> +bool intel_atomic_compute_pipe_bpp(struct intel_crtc_state *crtc_state);
> +int intel_atomic_check_config_and_link(struct intel_atomic_state *state);
> +
>  #endif /* __INTEL_ATOMIC_H__ */
> diff --git a/drivers/gpu/drm/i915/display/intel_crt.c b/drivers/gpu/drm/i915/display/intel_crt.c
> index f66340b4caf0f..3322080a574e8 100644
> --- a/drivers/gpu/drm/i915/display/intel_crt.c
> +++ b/drivers/gpu/drm/i915/display/intel_crt.c
> @@ -36,6 +36,7 @@
>  #include "i915_drv.h"
>  #include "i915_irq.h"
>  #include "i915_reg.h"
> +#include "intel_atomic.h"
>  #include "intel_connector.h"
>  #include "intel_crt.h"
>  #include "intel_crtc.h"
> @@ -413,6 +414,9 @@ static int pch_crt_compute_config(struct intel_encoder *encoder,
>  		return -EINVAL;
>  
>  	pipe_config->has_pch_encoder = true;
> +	if (!intel_atomic_compute_pipe_bpp(pipe_config))
> +		return -EINVAL;
> +
>  	pipe_config->output_format = INTEL_OUTPUT_FORMAT_RGB;
>  
>  	return 0;
> @@ -435,6 +439,9 @@ static int hsw_crt_compute_config(struct intel_encoder *encoder,
>  		return -EINVAL;
>  
>  	pipe_config->has_pch_encoder = true;
> +	if (!intel_atomic_compute_pipe_bpp(pipe_config))
> +		return -EINVAL;
> +
>  	pipe_config->output_format = INTEL_OUTPUT_FORMAT_RGB;
>  
>  	/* LPT FDI RX only supports 8bpc. */
> diff --git a/drivers/gpu/drm/i915/display/intel_crtc.c b/drivers/gpu/drm/i915/display/intel_crtc.c
> index 182c6dd64f47c..1eda6a9f19aa8 100644
> --- a/drivers/gpu/drm/i915/display/intel_crtc.c
> +++ b/drivers/gpu/drm/i915/display/intel_crtc.c
> @@ -175,6 +175,7 @@ void intel_crtc_state_reset(struct intel_crtc_state *crtc_state,
>  	crtc_state->hsw_workaround_pipe = INVALID_PIPE;
>  	crtc_state->scaler_state.scaler_id = -1;
>  	crtc_state->mst_master_transcoder = INVALID_TRANSCODER;
> +	crtc_state->max_link_bpp_x16 = INT_MAX;
>  }
>  
>  static struct intel_crtc *intel_crtc_alloc(void)
> diff --git a/drivers/gpu/drm/i915/display/intel_display.c b/drivers/gpu/drm/i915/display/intel_display.c
> index dbf109a2e738f..32778bd01bb05 100644
> --- a/drivers/gpu/drm/i915/display/intel_display.c
> +++ b/drivers/gpu/drm/i915/display/intel_display.c
> @@ -4641,7 +4641,8 @@ intel_crtc_prepare_cleared_state(struct intel_atomic_state *state,
>  
>  static int
>  intel_modeset_pipe_config(struct intel_atomic_state *state,
> -			  struct intel_crtc *crtc)
> +			  struct intel_crtc *crtc,
> +			  const struct intel_link_bw_limits *limits)
>  {
>  	struct drm_i915_private *i915 = to_i915(crtc->base.dev);
>  	struct intel_crtc_state *crtc_state =
> @@ -4650,7 +4651,6 @@ intel_modeset_pipe_config(struct intel_atomic_state *state,
>  	struct drm_connector_state *connector_state;
>  	int pipe_src_w, pipe_src_h;
>  	int base_bpp, ret, i;
> -	bool retry = true;
>  
>  	crtc_state->cpu_transcoder = (enum transcoder) crtc->pipe;
>  
> @@ -4673,6 +4673,17 @@ intel_modeset_pipe_config(struct intel_atomic_state *state,
>  	if (ret)
>  		return ret;
>  
> +	crtc_state->max_link_bpp_x16 = limits->max_bpp_x16[crtc->pipe];
> +
> +	if (crtc_state->pipe_bpp > to_bpp_int(crtc_state->max_link_bpp_x16)) {
> +		drm_dbg_kms(&i915->drm,
> +			    "[CRTC:%d:%s] Link bpp limited to %d.%04d\n",
> +			    crtc->base.base.id, crtc->base.name,
> +			    to_bpp_int(crtc_state->max_link_bpp_x16),
> +			    to_bpp_frac_dec(crtc_state->max_link_bpp_x16));
> +		crtc_state->bw_constrained = true;
> +	}
> +
>  	base_bpp = crtc_state->pipe_bpp;
>  
>  	/*
> @@ -4714,7 +4725,6 @@ intel_modeset_pipe_config(struct intel_atomic_state *state,
>  			crtc_state->output_types |= BIT(encoder->type);
>  	}
>  
> -encoder_retry:
>  	/* Ensure the port clock defaults are reset when retrying. */
>  	crtc_state->port_clock = 0;
>  	crtc_state->pixel_multiplier = 1;
> @@ -4754,17 +4764,6 @@ intel_modeset_pipe_config(struct intel_atomic_state *state,
>  	ret = intel_crtc_compute_config(state, crtc);
>  	if (ret == -EDEADLK)
>  		return ret;
> -	if (ret == -EAGAIN) {
> -		if (drm_WARN(&i915->drm, !retry,
> -			     "[CRTC:%d:%s] loop in pipe configuration computation\n",
> -			     crtc->base.base.id, crtc->base.name))
> -			return -EINVAL;
> -
> -		drm_dbg_kms(&i915->drm, "[CRTC:%d:%s] bw constrained, retrying\n",
> -			    crtc->base.base.id, crtc->base.name);
> -		retry = false;
> -		goto encoder_retry;
> -	}
>  	if (ret < 0) {
>  		drm_dbg_kms(&i915->drm, "[CRTC:%d:%s] config failure: %d\n",
>  			    crtc->base.base.id, crtc->base.name, ret);
> @@ -6206,7 +6205,9 @@ static int intel_bigjoiner_add_affected_crtcs(struct intel_atomic_state *state)
>  	return 0;
>  }
>  
> -static int intel_atomic_check_config(struct intel_atomic_state *state)
> +int intel_atomic_check_config(struct intel_atomic_state *state,
> +			      struct intel_link_bw_limits *limits,
> +			      enum pipe *failed_pipe)
>  {
>  	struct drm_i915_private *i915 = to_i915(state->base.dev);
>  	struct intel_crtc_state *new_crtc_state;
> @@ -6214,10 +6215,16 @@ static int intel_atomic_check_config(struct intel_atomic_state *state)
>  	int ret;
>  	int i;
>  
> +	*failed_pipe = INVALID_PIPE;
> +
>  	ret = intel_bigjoiner_add_affected_crtcs(state);
>  	if (ret)
>  		return ret;
>  
> +	ret = intel_fdi_add_affected_crtcs(state);
> +	if (ret)
> +		return ret;
> +
>  	for_each_new_intel_crtc_in_state(state, crtc, new_crtc_state, i) {
>  		if (!intel_crtc_needs_modeset(new_crtc_state)) {
>  			if (intel_crtc_is_bigjoiner_slave(new_crtc_state))
> @@ -6239,7 +6246,7 @@ static int intel_atomic_check_config(struct intel_atomic_state *state)
>  		if (!new_crtc_state->hw.enable)
>  			continue;
>  
> -		ret = intel_modeset_pipe_config(state, crtc);
> +		ret = intel_modeset_pipe_config(state, crtc, limits);
>  		if (ret)
>  			break;
>  
> @@ -6248,6 +6255,9 @@ static int intel_atomic_check_config(struct intel_atomic_state *state)
>  			break;
>  	}
>  
> +	if (ret)
> +		*failed_pipe = crtc->pipe;
> +
>  	return ret;
>  }
>  
> @@ -6295,7 +6305,7 @@ int intel_atomic_check(struct drm_device *dev,
>  			return ret;
>  	}
>  
> -	ret = intel_atomic_check_config(state);
> +	ret = intel_atomic_check_config_and_link(state);
>  	if (ret)
>  		goto fail;
>  
> diff --git a/drivers/gpu/drm/i915/display/intel_display.h b/drivers/gpu/drm/i915/display/intel_display.h
> index d9a54610d9d5e..2e0535739bd70 100644
> --- a/drivers/gpu/drm/i915/display/intel_display.h
> +++ b/drivers/gpu/drm/i915/display/intel_display.h
> @@ -55,6 +55,7 @@ struct intel_digital_port;
>  struct intel_dp;
>  struct intel_encoder;
>  struct intel_initial_plane_config;
> +struct intel_link_bw_limits;
>  struct intel_link_m_n;
>  struct intel_plane;
>  struct intel_plane_state;
> @@ -391,6 +392,9 @@ enum phy_fia {
>  			     (new_connector_state) = to_intel_digital_connector_state((__state)->base.connectors[__i].new_state), 1))
>  
>  int intel_atomic_check(struct drm_device *dev, struct drm_atomic_state *state);
> +int intel_atomic_check_config(struct intel_atomic_state *state,
> +			      struct intel_link_bw_limits *limits,
> +			      enum pipe *failed_pipe);
>  int intel_atomic_add_affected_planes(struct intel_atomic_state *state,
>  				     struct intel_crtc *crtc);
>  u8 intel_calc_active_pipes(struct intel_atomic_state *state,
> diff --git a/drivers/gpu/drm/i915/display/intel_display_types.h b/drivers/gpu/drm/i915/display/intel_display_types.h
> index b143085b399eb..6f4f46658df22 100644
> --- a/drivers/gpu/drm/i915/display/intel_display_types.h
> +++ b/drivers/gpu/drm/i915/display/intel_display_types.h
> @@ -66,6 +66,12 @@ struct intel_tc_port;
>   * Display related stuff
>   */
>  
> +struct intel_link_bw_limits {
> +	u8 min_bpp_pipes;
> +	/* in 1/16 bpp units */
> +	int max_bpp_x16[I915_MAX_PIPES];
> +};
> +
>  /* these are outputs from the chip - integrated only
>     external chips are via DVO or SDVO output */
>  enum intel_output_type {
> @@ -1189,7 +1195,8 @@ struct intel_crtc_state {
>  		u32 ctrl, div;
>  	} dsi_pll;
>  
> -	int pipe_bpp;
> +	int max_link_bpp_x16;	/* in 1/16 bpp units */
> +	int pipe_bpp;		/* in 1 bpp units */
>  	struct intel_link_m_n dp_m_n;
>  
>  	/* m2_n2 for eDP downclock */
> diff --git a/drivers/gpu/drm/i915/display/intel_dp.c b/drivers/gpu/drm/i915/display/intel_dp.c
> index 6637bd4768bf7..48f005932ad8b 100644
> --- a/drivers/gpu/drm/i915/display/intel_dp.c
> +++ b/drivers/gpu/drm/i915/display/intel_dp.c
> @@ -2218,7 +2218,8 @@ intel_dp_compute_config_link_bpp_limits(struct intel_dp *intel_dp,
>  	const struct intel_encoder *encoder = &dp_to_dig_port(intel_dp)->base;
>  	int max_link_bpp_x16;
>  
> -	max_link_bpp_x16 = to_bpp_x16(limits->pipe.max_bpp);
> +	max_link_bpp_x16 = min(crtc_state->max_link_bpp_x16,
> +			       to_bpp_x16(limits->pipe.max_bpp));
>  
>  	if (!dsc) {
>  		max_link_bpp_x16 = rounddown(max_link_bpp_x16, to_bpp_x16(2 * 3));
> diff --git a/drivers/gpu/drm/i915/display/intel_fdi.c b/drivers/gpu/drm/i915/display/intel_fdi.c
> index e12b46a84fa11..123ba67f68791 100644
> --- a/drivers/gpu/drm/i915/display/intel_fdi.c
> +++ b/drivers/gpu/drm/i915/display/intel_fdi.c
> @@ -119,6 +119,59 @@ void intel_fdi_link_train(struct intel_crtc *crtc,
>  	dev_priv->display.funcs.fdi->fdi_link_train(crtc, crtc_state);
>  }
>  
> +/**
> + * intel_fdi_add_affected_crtcs - add CRTCs on FDI affected by other modeset CRTCs
> + * @state: intel atomic state
> + *
> + * Add a CRTC using FDI to @state if changing another CRTC's FDI BW usage is
> + * known to affect the available FDI BW for the former CRTC. In practice this
> + * means adding CRTC B on IVYBRIDGE if its use of FDI lanes is limited (by
> + * CRTC C) and CRTC C is getting disabled.
> + *
> + * Returns 0 in case of success, or a negative error code otherwise.
> + */
> +int intel_fdi_add_affected_crtcs(struct intel_atomic_state *state)
> +{
> +	struct drm_i915_private *i915 = to_i915(state->base.dev);
> +	struct intel_crtc_state *old_crtc_state;
> +	struct intel_crtc_state *new_crtc_state;
> +	struct intel_crtc *crtc;
> +
> +	if (!IS_IVYBRIDGE(i915))
> +		return 0;
> +
> +	crtc = intel_crtc_for_pipe(i915, PIPE_C);
> +	new_crtc_state = intel_atomic_get_new_crtc_state(state, crtc);
> +
> +	if (!new_crtc_state)
> +		return 0;
> +
> +	old_crtc_state = intel_atomic_get_new_crtc_state(state, crtc);
> +
> +	if (!old_crtc_state->fdi_lanes)
> +		return 0;
> +
> +	if (!intel_crtc_needs_modeset(new_crtc_state))
> +		return 0;
> +
> +	if (new_crtc_state->uapi.enable)
> +		return 0;
> +
> +	crtc = intel_crtc_for_pipe(i915, PIPE_B);
> +	new_crtc_state = intel_atomic_get_crtc_state(&state->base, crtc);
> +
> +	if (IS_ERR(new_crtc_state))
> +		return PTR_ERR(old_crtc_state);
> +
> +	old_crtc_state = intel_atomic_get_old_crtc_state(state, crtc);
> +	if (!old_crtc_state->fdi_lanes)
> +		return 0;
> +
> +	return intel_modeset_pipes_in_mask(state,
> +					   "FDI link BW decrease on pipe C",
> +					   BIT(PIPE_B), false);
> +}
> +
>  /* units of 100MHz */
>  static int pipe_required_fdi_lanes(struct intel_crtc_state *crtc_state)
>  {
> @@ -129,13 +182,16 @@ static int pipe_required_fdi_lanes(struct intel_crtc_state *crtc_state)
>  }
>  
>  static int ilk_check_fdi_lanes(struct drm_device *dev, enum pipe pipe,
> -			       struct intel_crtc_state *pipe_config)
> +			       struct intel_crtc_state *pipe_config,
> +			       enum pipe *pipe_to_reduce)
>  {
>  	struct drm_i915_private *dev_priv = to_i915(dev);
>  	struct drm_atomic_state *state = pipe_config->uapi.state;
>  	struct intel_crtc *other_crtc;
>  	struct intel_crtc_state *other_crtc_state;
>  
> +	*pipe_to_reduce = pipe;
> +
>  	drm_dbg_kms(&dev_priv->drm,
>  		    "checking fdi config on pipe %c, lanes %i\n",
>  		    pipe_name(pipe), pipe_config->fdi_lanes);
> @@ -198,6 +254,9 @@ static int ilk_check_fdi_lanes(struct drm_device *dev, enum pipe pipe,
>  		if (pipe_required_fdi_lanes(other_crtc_state) > 2) {
>  			drm_dbg_kms(&dev_priv->drm,
>  				    "fdi link B uses too many lanes to enable link C\n");
> +
> +			*pipe_to_reduce = PIPE_B;
> +
>  			return -EINVAL;
>  		}
>  		return 0;
> @@ -238,10 +297,8 @@ int ilk_fdi_compute_config(struct intel_crtc *crtc,
>  	struct drm_device *dev = crtc->base.dev;
>  	struct drm_i915_private *i915 = to_i915(dev);
>  	const struct drm_display_mode *adjusted_mode = &pipe_config->hw.adjusted_mode;
> -	int lane, link_bw, fdi_dotclock, ret;
> -	bool needs_recompute = false;
> +	int lane, link_bw, fdi_dotclock;
>  
> -retry:
>  	/* FDI is a binary signal running at ~2.7GHz, encoding
>  	 * each output octet as 10 bits. The actual frequency
>  	 * is stored as a divider into a 100MHz clock, and the
> @@ -261,25 +318,69 @@ int ilk_fdi_compute_config(struct intel_crtc *crtc,
>  	intel_link_compute_m_n(pipe_config->pipe_bpp, lane, fdi_dotclock,
>  			       link_bw, &pipe_config->fdi_m_n, false);
>  
> -	ret = ilk_check_fdi_lanes(dev, crtc->pipe, pipe_config);
> -	if (ret == -EDEADLK)
> +	return 0;
> +}
> +
> +static int intel_fdi_atomic_check_bw(struct intel_atomic_state *state,
> +				     struct intel_crtc *crtc,
> +				     struct intel_crtc_state *pipe_config,
> +				     struct intel_link_bw_limits *limits)
> +{
> +	struct drm_i915_private *i915 = to_i915(crtc->base.dev);
> +	enum pipe pipe_to_reduce;
> +	int ret;
> +
> +	ret = ilk_check_fdi_lanes(&i915->drm, crtc->pipe, pipe_config,
> +				  &pipe_to_reduce);
> +	if (ret != -EINVAL)
>  		return ret;
>  
> -	if (ret == -EINVAL && pipe_config->pipe_bpp > 6*3) {
> -		pipe_config->pipe_bpp -= 2*3;
> -		drm_dbg_kms(&i915->drm,
> -			    "fdi link bw constraint, reducing pipe bpp to %i\n",
> -			    pipe_config->pipe_bpp);
> -		needs_recompute = true;
> -		pipe_config->bw_constrained = true;
> +	ret = intel_atomic_reduce_link_bpp(state, limits,
> +					   BIT(pipe_to_reduce),
> +					   "FDI link BW");
>  
> -		goto retry;
> -	}
> +	return ret ? : -EAGAIN;
> +}
>  
> -	if (needs_recompute)
> -		return -EAGAIN;
> +/**
> + * intel_fdi_atomic_check_link - check all modeset FDI link configuration
> + * @state: intel atomic state
> + * @limits: link BW limits
> + *
> + * Check the link configuration for all modeset FDI outputs. If the
> + * configuration is invalid @limits will be updated if possible to
> + * reduce the total BW, after which the configuration for all CRTCs in
> + * @state must be recomputed with the updated @limits.
> + *
> + * Returns:
> + *   - 0 if the confugration is valid
> + *   - %-EAGAIN, if the configuration is invalid and @limits got updated
> + *     with fallback values with which the configuration of all CRTCs
> + *     in @state must be recomputed
> + *   - Other negative error, if the configuration is invalid without a
> + *     fallback possibility, or the check failed for another reason
> + */
> +int intel_fdi_atomic_check_link(struct intel_atomic_state *state,
> +				struct intel_link_bw_limits *limits)
> +{
> +	struct intel_crtc *crtc;
> +	struct intel_crtc_state *crtc_state;
> +	int i;
> +
> +	for_each_new_intel_crtc_in_state(state, crtc, crtc_state, i) {
> +		int ret;
>  
> -	return ret;
> +		if (!crtc_state->has_pch_encoder ||
> +		    !intel_crtc_needs_modeset(crtc_state) ||
> +		    !crtc_state->hw.enable)
> +			continue;
> +
> +		ret = intel_fdi_atomic_check_bw(state, crtc, crtc_state, limits);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return 0;
>  }
>  
>  static void cpt_set_fdi_bc_bifurcation(struct drm_i915_private *dev_priv, bool enable)
> diff --git a/drivers/gpu/drm/i915/display/intel_fdi.h b/drivers/gpu/drm/i915/display/intel_fdi.h
> index 1cdb86172702f..eb02b967bb440 100644
> --- a/drivers/gpu/drm/i915/display/intel_fdi.h
> +++ b/drivers/gpu/drm/i915/display/intel_fdi.h
> @@ -8,14 +8,19 @@
>  
>  enum pipe;
>  struct drm_i915_private;
> +struct intel_atomic_state;
>  struct intel_crtc;
>  struct intel_crtc_state;
>  struct intel_encoder;
> +struct intel_link_bw_limits;
>  
> +int intel_fdi_add_affected_crtcs(struct intel_atomic_state *state);
>  int intel_fdi_link_freq(struct drm_i915_private *i915,
>  			const struct intel_crtc_state *pipe_config);
>  int ilk_fdi_compute_config(struct intel_crtc *intel_crtc,
>  			   struct intel_crtc_state *pipe_config);
> +int intel_fdi_atomic_check_link(struct intel_atomic_state *state,
> +				struct intel_link_bw_limits *limits);
>  void intel_fdi_normal_train(struct intel_crtc *crtc);
>  void ilk_fdi_disable(struct intel_crtc *crtc);
>  void ilk_fdi_pll_disable(struct intel_crtc *intel_crtc);
> diff --git a/drivers/gpu/drm/i915/display/intel_lvds.c b/drivers/gpu/drm/i915/display/intel_lvds.c
> index 3ace56979b70e..08dcc2d10a2c1 100644
> --- a/drivers/gpu/drm/i915/display/intel_lvds.c
> +++ b/drivers/gpu/drm/i915/display/intel_lvds.c
> @@ -425,6 +425,12 @@ static int intel_lvds_compute_config(struct intel_encoder *encoder,
>  		return -EINVAL;
>  	}
>  
> +	if (HAS_PCH_SPLIT(i915)) {
> +		crtc_state->has_pch_encoder = true;
> +		if (!intel_atomic_compute_pipe_bpp(crtc_state))
> +			return -EINVAL;
> +	}
> +
>  	if (lvds_encoder->a3_power == LVDS_A3_POWER_UP)
>  		lvds_bpp = 8*3;
>  	else
> @@ -453,9 +459,6 @@ static int intel_lvds_compute_config(struct intel_encoder *encoder,
>  	if (adjusted_mode->flags & DRM_MODE_FLAG_DBLSCAN)
>  		return -EINVAL;
>  
> -	if (HAS_PCH_SPLIT(i915))
> -		crtc_state->has_pch_encoder = true;
> -
>  	ret = intel_panel_fitting(crtc_state, conn_state);
>  	if (ret)
>  		return ret;
> diff --git a/drivers/gpu/drm/i915/display/intel_sdvo.c b/drivers/gpu/drm/i915/display/intel_sdvo.c
> index 7d25a64698e2f..1c6330151f4d6 100644
> --- a/drivers/gpu/drm/i915/display/intel_sdvo.c
> +++ b/drivers/gpu/drm/i915/display/intel_sdvo.c
> @@ -1352,14 +1352,17 @@ static int intel_sdvo_compute_config(struct intel_encoder *encoder,
>  	struct drm_display_mode *adjusted_mode = &pipe_config->hw.adjusted_mode;
>  	struct drm_display_mode *mode = &pipe_config->hw.mode;
>  
> +	if (HAS_PCH_SPLIT(to_i915(encoder->base.dev))) {
> +		pipe_config->has_pch_encoder = true;
> +		if (!intel_atomic_compute_pipe_bpp(pipe_config))
> +			return -EINVAL;
> +	}
> +
>  	DRM_DEBUG_KMS("forcing bpc to 8 for SDVO\n");
>  	pipe_config->pipe_bpp = 8*3;
>  	pipe_config->sink_format = INTEL_OUTPUT_FORMAT_RGB;
>  	pipe_config->output_format = INTEL_OUTPUT_FORMAT_RGB;
>  
> -	if (HAS_PCH_SPLIT(to_i915(encoder->base.dev)))
> -		pipe_config->has_pch_encoder = true;
> -
>  	/*
>  	 * We need to construct preferred input timings based on our
>  	 * output timings.  To do that, we have to set the output
> -- 
> 2.37.2
Imre Deak Sept. 11, 2023, 8:42 p.m. UTC | #2
On Mon, Sep 11, 2023 at 09:59:34PM +0300, Ville Syrjälä wrote:
> On Thu, Aug 24, 2023 at 11:05:03AM +0300, Imre Deak wrote:
> > At the moment modesetting pipe C on IVB will fail if pipe B uses 4 FDI
> > lanes. Make the BW sharing more dynamic by trying to reduce pipe B's
> > link bpp in this case, until pipe B uses only up to 2 FDI lanes.
> > 
> > For this instead of the encoder compute config retry loop - which
> > reduced link bpp only for the encoder's pipe - check the overall BW
> > limits after all CRTC states have been computed and if the check fails
> > reduce the maximum link bpp for a selected pipe (for FDI pipe B or C as
> > required) and recompute all the CRTC states. Retry this sequence until
> > either the overall BW limit check passes, or further bpp reduction is
> > not possible (because all pipes/encoders sharing the link BW reached
> > their minimum link bpp).
> > 
> > This change also prepares for an upcoming patch resolving BW limits in
> > a similar way on MST links as well.
> > 
> > v2:
> > - Rename intel_crtc_state::max_link_bpp to max_link_bpp_x16 and
> >   intel_link_bw_limits::max_bpp to max_bpp_x16. (Jani)
> > - Increase back pipe B's link bpp if earlier it was limited due to
> >   pipe C and pipe C gets later disabled.
> > - Don't assume that a CRTC is already in the atomic state, while
> >   reducing its link bpp.
> > - Add DocBook description to intel_fdi_atomic_check_link().
> > 
> > Cc: Jani Nikula <jani.nikula@linux.intel.com>
> > Signed-off-by: Imre Deak <imre.deak@intel.com>
> > ---
> >  drivers/gpu/drm/i915/display/g4x_hdmi.c       |   5 +-
> >  drivers/gpu/drm/i915/display/intel_atomic.c   | 207 ++++++++++++++++++
> >  drivers/gpu/drm/i915/display/intel_atomic.h   |   8 +
> >  drivers/gpu/drm/i915/display/intel_crt.c      |   7 +
> >  drivers/gpu/drm/i915/display/intel_crtc.c     |   1 +
> >  drivers/gpu/drm/i915/display/intel_display.c  |  44 ++--
> >  drivers/gpu/drm/i915/display/intel_display.h  |   4 +
> >  .../drm/i915/display/intel_display_types.h    |   9 +-
> >  drivers/gpu/drm/i915/display/intel_dp.c       |   3 +-
> >  drivers/gpu/drm/i915/display/intel_fdi.c      | 137 ++++++++++--
> >  drivers/gpu/drm/i915/display/intel_fdi.h      |   5 +
> >  drivers/gpu/drm/i915/display/intel_lvds.c     |   9 +-
> >  drivers/gpu/drm/i915/display/intel_sdvo.c     |   9 +-
> >  13 files changed, 404 insertions(+), 44 deletions(-)
> > 
> > diff --git a/drivers/gpu/drm/i915/display/g4x_hdmi.c b/drivers/gpu/drm/i915/display/g4x_hdmi.c
> > index 634b14116d9dd..ebbceddc13259 100644
> > --- a/drivers/gpu/drm/i915/display/g4x_hdmi.c
> > +++ b/drivers/gpu/drm/i915/display/g4x_hdmi.c
> > @@ -133,8 +133,11 @@ static int g4x_hdmi_compute_config(struct intel_encoder *encoder,
> >  	struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc);
> >  	struct drm_i915_private *i915 = to_i915(encoder->base.dev);
> >  
> > -	if (HAS_PCH_SPLIT(i915))
> > +	if (HAS_PCH_SPLIT(i915)) {
> >  		crtc_state->has_pch_encoder = true;
> > +		if (!intel_atomic_compute_pipe_bpp(crtc_state))
> > +			return -EINVAL;
> > +	}
> >  
> >  	if (IS_G4X(i915))
> >  		crtc_state->has_hdmi_sink = g4x_compute_has_hdmi_sink(state, crtc);
> > diff --git a/drivers/gpu/drm/i915/display/intel_atomic.c b/drivers/gpu/drm/i915/display/intel_atomic.c
> > index 7cf51dd8c0567..ee4cbf80ddb55 100644
> > --- a/drivers/gpu/drm/i915/display/intel_atomic.c
> > +++ b/drivers/gpu/drm/i915/display/intel_atomic.c
> 
> Not convinced we should put anything there. I think I mostly managed to
> move a bunch or semi-random stuff out now, so all it more or less has is
> the struct intel_atomic_state alloc/free stuff.
> 
> You have intel_atomic_check() in intel_display.c and the lower level
> intel_atomic_check_config() is also there, but now the middle man
> intel_atomic_check_config_and_link() ended up in intel_atomic.c for
> whatever reason. I'd just shove all of it into intel_display.c.

I didn't want to add more stuff to intel_display.c, but yes
intel_atomic.c is not the best place for these. How about moving the
link BW config/check functions instead to intel_link_bw.c as in
https://github.com/ideak/linux/commit/9fd9d456da53 ?

> The patch is also rather massive.

I thought it's easier to understand if new function definitions / their
use is in one patch, but I guess here the amount of changes vs. just
additions makes it less clear.

> Can we try to chunk it up to eg. something like:
> 1) add the new infrastructure
> 2) convert the fdi retry stuff to use the new infrastructure
> 3) add intel_fdi_add_affected_crtcs() (which seems more or less
>    independent from the rest of the fdi conversion?)

Yes, 3) can be regarded as an additional functionality.

> ?

Ok, will split the changes based on the above points.

> > @@ -38,6 +38,7 @@
> >  #include "intel_atomic.h"
> >  #include "intel_cdclk.h"
> >  #include "intel_display_types.h"
> > +#include "intel_fdi.h"
> >  #include "intel_global_state.h"
> >  #include "intel_hdcp.h"
> >  #include "intel_psr.h"
> > @@ -358,3 +359,209 @@ intel_atomic_get_crtc_state(struct drm_atomic_state *state,
> >  
> >  	return to_intel_crtc_state(crtc_state);
> >  }
> > +
> > +/**
> > + * intel_atomic_compute_pipe_bpp - compute pipe bpp limited by max link bpp
> > + * @crtc_state: the crtc state
> > + *
> > + * Compute the pipe bpp limited by the CRTC's maximum link bpp. Encoders can
> > + * call this function during state computation in the simple case where the
> > + * link bpp will always match the pipe bpp. This is the case for all non-DP
> > + * encoders, while DP encoders will use a link bpp lower than pipe bpp in case
> > + * of DSC compression.
> > + *
> > + * Returns %true in case of success, %false if pipe bpp would need to be
> > + * reduced below its valid range.
> > + */
> > +bool intel_atomic_compute_pipe_bpp(struct intel_crtc_state *crtc_state)
> > +{
> > +	int pipe_bpp = min(crtc_state->pipe_bpp,
> > +			   to_bpp_int(crtc_state->max_link_bpp_x16));
> > +
> > +	pipe_bpp = rounddown(pipe_bpp, 2 * 3);
> > +
> > +	if (pipe_bpp < 6 * 3)
> > +		return false;
> > +
> > +	crtc_state->pipe_bpp = pipe_bpp;
> > +
> > +	return true;
> > +}
> > +
> > +/**
> > + * intel_atomic_reduce_link_bpp - reduce maximum link bpp for a selected pipe
> > + * @state: atomic state
> > + * @limits: link BW limits
> > + * @pipe_mask: mask of pipes to select from
> > + * @reason: explanation of why bpp reduction is needed
> > + *
> > + * Select the pipe from @pipe_mask with the biggest link bpp value and set the
> > + * maximum of link bpp in @limits below this value. Modeset the selected pipe,
> > + * so that its state will get recomputed.
> > + *
> > + * This function can be called to resolve a link's BW overallocation by reducing
> > + * the link bpp of one pipe on the link and hence reducing the total link BW.
> > + *
> > + * Returns
> > + *   - 0 in case of success
> > + *   - %-EINVAL if no pipe can further reduce its link bpp
> > + *   - Other negative error, if modesetting the selected pipe failed
> > + */
> > +int intel_atomic_reduce_link_bpp(struct intel_atomic_state *state,
> > +				 struct intel_link_bw_limits *limits,
> > +				 u8 pipe_mask,
> > +				 const char *reason)
> > +{
> > +	struct drm_i915_private *i915 = to_i915(state->base.dev);
> > +	enum pipe max_bpp_pipe = INVALID_PIPE;
> > +	struct intel_crtc *crtc;
> > +	int max_bpp = 0;
> > +
> > +	for_each_intel_crtc_in_pipe_mask(&i915->drm, crtc, pipe_mask) {
> > +		struct intel_crtc_state *crtc_state;
> > +		int pipe_bpp;
> > +
> > +		if (limits->min_bpp_pipes & BIT(crtc->pipe))
> > +			continue;
> > +
> > +		crtc_state = intel_atomic_get_crtc_state(&state->base,
> > +							 crtc);
> > +		if (IS_ERR(crtc_state))
> > +			return PTR_ERR(crtc_state);
> > +
> > +		if (crtc_state->dsc.compression_enable)
> > +			pipe_bpp = crtc_state->dsc.compressed_bpp;
> > +		else
> > +			pipe_bpp = crtc_state->pipe_bpp;
> > +
> > +		if (pipe_bpp > max_bpp) {
> > +			max_bpp = pipe_bpp;
> > +			max_bpp_pipe = crtc->pipe;
> > +		}
> > +	}
> > +
> > +	if (max_bpp_pipe == INVALID_PIPE)
> > +		return -EINVAL;
> > +
> > +	limits->max_bpp_x16[max_bpp_pipe] = to_bpp_x16(max_bpp) - 1;
> > +
> > +	return intel_modeset_pipes_in_mask(state, reason,
> > +					   BIT(max_bpp_pipe), false);
> > +}
> > +
> > +static int intel_atomic_check_link(struct intel_atomic_state *state,
> > +				   struct intel_link_bw_limits *limits)
> > +{
> > +	int ret;
> > +
> > +	ret = intel_fdi_atomic_check_link(state, limits);
> > +	if (ret)
> > +		return ret;
> > +
> > +	return 0;
> > +}
> > +
> > +static bool
> > +assert_link_limit_change_valid(struct drm_i915_private *i915,
> > +			       const struct intel_link_bw_limits *old_limits,
> > +			       const struct intel_link_bw_limits *new_limits)
> > +{
> > +	bool bpps_changed = false;
> > +	enum pipe pipe;
> > +
> > +	for_each_pipe(i915, pipe) {
> > +		/* The bpp limit can only decrease. */
> > +		if (drm_WARN_ON(&i915->drm,
> > +				new_limits->max_bpp_x16[pipe] >
> > +				old_limits->max_bpp_x16[pipe]))
> > +			return false;
> > +
> > +		if (new_limits->max_bpp_x16[pipe] <
> > +		    old_limits->max_bpp_x16[pipe])
> > +			bpps_changed = true;
> > +	}
> > +
> > +	if (drm_WARN_ON(&i915->drm,
> > +			!bpps_changed))
> > +		return false;
> > +
> > +	return true;
> > +}
> > +
> > +static bool
> > +reset_link_bpp_limit_to_min(struct intel_atomic_state *state,
> > +			    const struct intel_link_bw_limits *old_limits,
> > +			    struct intel_link_bw_limits *new_limits,
> > +			    enum pipe failed_pipe)
> > +{
> > +	if (failed_pipe == INVALID_PIPE)
> > +		return false;
> > +
> > +	if (new_limits->min_bpp_pipes & BIT(failed_pipe))
> > +		return false;
> > +
> > +	if (new_limits->max_bpp_x16[failed_pipe] ==
> > +	    old_limits->max_bpp_x16[failed_pipe])
> > +		return false;
> > +
> > +	new_limits->max_bpp_x16[failed_pipe] =
> > +		old_limits->max_bpp_x16[failed_pipe];
> > +	new_limits->min_bpp_pipes |= BIT(failed_pipe);
> > +
> > +	return true;
> > +}
> > +
> > +/**
> > + * intel_atomic_check_config_and_link - compute CRTC configs, resolving any BW limits
> > + * @state: atomic state
> > + *
> > + * Compute the configuration of all CRTCs in @state and resolve any BW
> > + * limitations on links shared by these CRTCs.
> > + *
> > + * Return 0 in case of success, or a negative error code otherwise.
> > + */
> > +int intel_atomic_check_config_and_link(struct intel_atomic_state *state)
> > +{
> > +	struct drm_i915_private *i915 = to_i915(state->base.dev);
> > +	struct intel_link_bw_limits new_limits = {};
> > +	struct intel_link_bw_limits old_limits;
> > +	enum pipe pipe;
> > +	int ret;
> > +
> > +	for_each_pipe(i915, pipe)
> > +		new_limits.max_bpp_x16[pipe] = INT_MAX;
> > +
> > +	old_limits = new_limits;
> > +
> > +	while (true) {
> > +		enum pipe failed_pipe;
> > +
> > +		ret = intel_atomic_check_config(state, &new_limits,
> > +						&failed_pipe);
> > +		if (ret) {
> > +			if (ret == -EINVAL &&
> > +			    reset_link_bpp_limit_to_min(state,
> > +							&old_limits,
> > +							&new_limits,
> > +							failed_pipe))
> > +				continue;
> > +
> > +			break;
> > +		}
> > +
> > +		old_limits = new_limits;
> > +
> > +		ret = intel_atomic_check_link(state, &new_limits);
> > +		if (ret != -EAGAIN)
> > +			break;
> > +
> > +		if (!assert_link_limit_change_valid(i915,
> > +						    &old_limits,
> > +						    &new_limits)) {
> > +			ret = -EINVAL;
> > +			break;
> > +		}
> > +	}
> > +
> > +	return ret;
> > +}
> > diff --git a/drivers/gpu/drm/i915/display/intel_atomic.h b/drivers/gpu/drm/i915/display/intel_atomic.h
> > index e506f6a873447..bbf3595d52c41 100644
> > --- a/drivers/gpu/drm/i915/display/intel_atomic.h
> > +++ b/drivers/gpu/drm/i915/display/intel_atomic.h
> > @@ -20,6 +20,7 @@ struct intel_atomic_state;
> >  struct intel_connector;
> >  struct intel_crtc;
> >  struct intel_crtc_state;
> > +struct intel_link_bw_limits;
> >  
> >  int intel_digital_connector_atomic_get_property(struct drm_connector *connector,
> >  						const struct drm_connector_state *state,
> > @@ -52,4 +53,11 @@ struct intel_crtc_state *
> >  intel_atomic_get_crtc_state(struct drm_atomic_state *state,
> >  			    struct intel_crtc *crtc);
> >  
> > +int intel_atomic_reduce_link_bpp(struct intel_atomic_state *state,
> > +				 struct intel_link_bw_limits *limits,
> > +				 u8 pipe_mask,
> > +				 const char *reason);
> > +bool intel_atomic_compute_pipe_bpp(struct intel_crtc_state *crtc_state);
> > +int intel_atomic_check_config_and_link(struct intel_atomic_state *state);
> > +
> >  #endif /* __INTEL_ATOMIC_H__ */
> > diff --git a/drivers/gpu/drm/i915/display/intel_crt.c b/drivers/gpu/drm/i915/display/intel_crt.c
> > index f66340b4caf0f..3322080a574e8 100644
> > --- a/drivers/gpu/drm/i915/display/intel_crt.c
> > +++ b/drivers/gpu/drm/i915/display/intel_crt.c
> > @@ -36,6 +36,7 @@
> >  #include "i915_drv.h"
> >  #include "i915_irq.h"
> >  #include "i915_reg.h"
> > +#include "intel_atomic.h"
> >  #include "intel_connector.h"
> >  #include "intel_crt.h"
> >  #include "intel_crtc.h"
> > @@ -413,6 +414,9 @@ static int pch_crt_compute_config(struct intel_encoder *encoder,
> >  		return -EINVAL;
> >  
> >  	pipe_config->has_pch_encoder = true;
> > +	if (!intel_atomic_compute_pipe_bpp(pipe_config))
> > +		return -EINVAL;
> > +
> >  	pipe_config->output_format = INTEL_OUTPUT_FORMAT_RGB;
> >  
> >  	return 0;
> > @@ -435,6 +439,9 @@ static int hsw_crt_compute_config(struct intel_encoder *encoder,
> >  		return -EINVAL;
> >  
> >  	pipe_config->has_pch_encoder = true;
> > +	if (!intel_atomic_compute_pipe_bpp(pipe_config))
> > +		return -EINVAL;
> > +
> >  	pipe_config->output_format = INTEL_OUTPUT_FORMAT_RGB;
> >  
> >  	/* LPT FDI RX only supports 8bpc. */
> > diff --git a/drivers/gpu/drm/i915/display/intel_crtc.c b/drivers/gpu/drm/i915/display/intel_crtc.c
> > index 182c6dd64f47c..1eda6a9f19aa8 100644
> > --- a/drivers/gpu/drm/i915/display/intel_crtc.c
> > +++ b/drivers/gpu/drm/i915/display/intel_crtc.c
> > @@ -175,6 +175,7 @@ void intel_crtc_state_reset(struct intel_crtc_state *crtc_state,
> >  	crtc_state->hsw_workaround_pipe = INVALID_PIPE;
> >  	crtc_state->scaler_state.scaler_id = -1;
> >  	crtc_state->mst_master_transcoder = INVALID_TRANSCODER;
> > +	crtc_state->max_link_bpp_x16 = INT_MAX;
> >  }
> >  
> >  static struct intel_crtc *intel_crtc_alloc(void)
> > diff --git a/drivers/gpu/drm/i915/display/intel_display.c b/drivers/gpu/drm/i915/display/intel_display.c
> > index dbf109a2e738f..32778bd01bb05 100644
> > --- a/drivers/gpu/drm/i915/display/intel_display.c
> > +++ b/drivers/gpu/drm/i915/display/intel_display.c
> > @@ -4641,7 +4641,8 @@ intel_crtc_prepare_cleared_state(struct intel_atomic_state *state,
> >  
> >  static int
> >  intel_modeset_pipe_config(struct intel_atomic_state *state,
> > -			  struct intel_crtc *crtc)
> > +			  struct intel_crtc *crtc,
> > +			  const struct intel_link_bw_limits *limits)
> >  {
> >  	struct drm_i915_private *i915 = to_i915(crtc->base.dev);
> >  	struct intel_crtc_state *crtc_state =
> > @@ -4650,7 +4651,6 @@ intel_modeset_pipe_config(struct intel_atomic_state *state,
> >  	struct drm_connector_state *connector_state;
> >  	int pipe_src_w, pipe_src_h;
> >  	int base_bpp, ret, i;
> > -	bool retry = true;
> >  
> >  	crtc_state->cpu_transcoder = (enum transcoder) crtc->pipe;
> >  
> > @@ -4673,6 +4673,17 @@ intel_modeset_pipe_config(struct intel_atomic_state *state,
> >  	if (ret)
> >  		return ret;
> >  
> > +	crtc_state->max_link_bpp_x16 = limits->max_bpp_x16[crtc->pipe];
> > +
> > +	if (crtc_state->pipe_bpp > to_bpp_int(crtc_state->max_link_bpp_x16)) {
> > +		drm_dbg_kms(&i915->drm,
> > +			    "[CRTC:%d:%s] Link bpp limited to %d.%04d\n",
> > +			    crtc->base.base.id, crtc->base.name,
> > +			    to_bpp_int(crtc_state->max_link_bpp_x16),
> > +			    to_bpp_frac_dec(crtc_state->max_link_bpp_x16));
> > +		crtc_state->bw_constrained = true;
> > +	}
> > +
> >  	base_bpp = crtc_state->pipe_bpp;
> >  
> >  	/*
> > @@ -4714,7 +4725,6 @@ intel_modeset_pipe_config(struct intel_atomic_state *state,
> >  			crtc_state->output_types |= BIT(encoder->type);
> >  	}
> >  
> > -encoder_retry:
> >  	/* Ensure the port clock defaults are reset when retrying. */
> >  	crtc_state->port_clock = 0;
> >  	crtc_state->pixel_multiplier = 1;
> > @@ -4754,17 +4764,6 @@ intel_modeset_pipe_config(struct intel_atomic_state *state,
> >  	ret = intel_crtc_compute_config(state, crtc);
> >  	if (ret == -EDEADLK)
> >  		return ret;
> > -	if (ret == -EAGAIN) {
> > -		if (drm_WARN(&i915->drm, !retry,
> > -			     "[CRTC:%d:%s] loop in pipe configuration computation\n",
> > -			     crtc->base.base.id, crtc->base.name))
> > -			return -EINVAL;
> > -
> > -		drm_dbg_kms(&i915->drm, "[CRTC:%d:%s] bw constrained, retrying\n",
> > -			    crtc->base.base.id, crtc->base.name);
> > -		retry = false;
> > -		goto encoder_retry;
> > -	}
> >  	if (ret < 0) {
> >  		drm_dbg_kms(&i915->drm, "[CRTC:%d:%s] config failure: %d\n",
> >  			    crtc->base.base.id, crtc->base.name, ret);
> > @@ -6206,7 +6205,9 @@ static int intel_bigjoiner_add_affected_crtcs(struct intel_atomic_state *state)
> >  	return 0;
> >  }
> >  
> > -static int intel_atomic_check_config(struct intel_atomic_state *state)
> > +int intel_atomic_check_config(struct intel_atomic_state *state,
> > +			      struct intel_link_bw_limits *limits,
> > +			      enum pipe *failed_pipe)
> >  {
> >  	struct drm_i915_private *i915 = to_i915(state->base.dev);
> >  	struct intel_crtc_state *new_crtc_state;
> > @@ -6214,10 +6215,16 @@ static int intel_atomic_check_config(struct intel_atomic_state *state)
> >  	int ret;
> >  	int i;
> >  
> > +	*failed_pipe = INVALID_PIPE;
> > +
> >  	ret = intel_bigjoiner_add_affected_crtcs(state);
> >  	if (ret)
> >  		return ret;
> >  
> > +	ret = intel_fdi_add_affected_crtcs(state);
> > +	if (ret)
> > +		return ret;
> > +
> >  	for_each_new_intel_crtc_in_state(state, crtc, new_crtc_state, i) {
> >  		if (!intel_crtc_needs_modeset(new_crtc_state)) {
> >  			if (intel_crtc_is_bigjoiner_slave(new_crtc_state))
> > @@ -6239,7 +6246,7 @@ static int intel_atomic_check_config(struct intel_atomic_state *state)
> >  		if (!new_crtc_state->hw.enable)
> >  			continue;
> >  
> > -		ret = intel_modeset_pipe_config(state, crtc);
> > +		ret = intel_modeset_pipe_config(state, crtc, limits);
> >  		if (ret)
> >  			break;
> >  
> > @@ -6248,6 +6255,9 @@ static int intel_atomic_check_config(struct intel_atomic_state *state)
> >  			break;
> >  	}
> >  
> > +	if (ret)
> > +		*failed_pipe = crtc->pipe;
> > +
> >  	return ret;
> >  }
> >  
> > @@ -6295,7 +6305,7 @@ int intel_atomic_check(struct drm_device *dev,
> >  			return ret;
> >  	}
> >  
> > -	ret = intel_atomic_check_config(state);
> > +	ret = intel_atomic_check_config_and_link(state);
> >  	if (ret)
> >  		goto fail;
> >  
> > diff --git a/drivers/gpu/drm/i915/display/intel_display.h b/drivers/gpu/drm/i915/display/intel_display.h
> > index d9a54610d9d5e..2e0535739bd70 100644
> > --- a/drivers/gpu/drm/i915/display/intel_display.h
> > +++ b/drivers/gpu/drm/i915/display/intel_display.h
> > @@ -55,6 +55,7 @@ struct intel_digital_port;
> >  struct intel_dp;
> >  struct intel_encoder;
> >  struct intel_initial_plane_config;
> > +struct intel_link_bw_limits;
> >  struct intel_link_m_n;
> >  struct intel_plane;
> >  struct intel_plane_state;
> > @@ -391,6 +392,9 @@ enum phy_fia {
> >  			     (new_connector_state) = to_intel_digital_connector_state((__state)->base.connectors[__i].new_state), 1))
> >  
> >  int intel_atomic_check(struct drm_device *dev, struct drm_atomic_state *state);
> > +int intel_atomic_check_config(struct intel_atomic_state *state,
> > +			      struct intel_link_bw_limits *limits,
> > +			      enum pipe *failed_pipe);
> >  int intel_atomic_add_affected_planes(struct intel_atomic_state *state,
> >  				     struct intel_crtc *crtc);
> >  u8 intel_calc_active_pipes(struct intel_atomic_state *state,
> > diff --git a/drivers/gpu/drm/i915/display/intel_display_types.h b/drivers/gpu/drm/i915/display/intel_display_types.h
> > index b143085b399eb..6f4f46658df22 100644
> > --- a/drivers/gpu/drm/i915/display/intel_display_types.h
> > +++ b/drivers/gpu/drm/i915/display/intel_display_types.h
> > @@ -66,6 +66,12 @@ struct intel_tc_port;
> >   * Display related stuff
> >   */
> >  
> > +struct intel_link_bw_limits {
> > +	u8 min_bpp_pipes;
> > +	/* in 1/16 bpp units */
> > +	int max_bpp_x16[I915_MAX_PIPES];
> > +};
> > +
> >  /* these are outputs from the chip - integrated only
> >     external chips are via DVO or SDVO output */
> >  enum intel_output_type {
> > @@ -1189,7 +1195,8 @@ struct intel_crtc_state {
> >  		u32 ctrl, div;
> >  	} dsi_pll;
> >  
> > -	int pipe_bpp;
> > +	int max_link_bpp_x16;	/* in 1/16 bpp units */
> > +	int pipe_bpp;		/* in 1 bpp units */
> >  	struct intel_link_m_n dp_m_n;
> >  
> >  	/* m2_n2 for eDP downclock */
> > diff --git a/drivers/gpu/drm/i915/display/intel_dp.c b/drivers/gpu/drm/i915/display/intel_dp.c
> > index 6637bd4768bf7..48f005932ad8b 100644
> > --- a/drivers/gpu/drm/i915/display/intel_dp.c
> > +++ b/drivers/gpu/drm/i915/display/intel_dp.c
> > @@ -2218,7 +2218,8 @@ intel_dp_compute_config_link_bpp_limits(struct intel_dp *intel_dp,
> >  	const struct intel_encoder *encoder = &dp_to_dig_port(intel_dp)->base;
> >  	int max_link_bpp_x16;
> >  
> > -	max_link_bpp_x16 = to_bpp_x16(limits->pipe.max_bpp);
> > +	max_link_bpp_x16 = min(crtc_state->max_link_bpp_x16,
> > +			       to_bpp_x16(limits->pipe.max_bpp));
> >  
> >  	if (!dsc) {
> >  		max_link_bpp_x16 = rounddown(max_link_bpp_x16, to_bpp_x16(2 * 3));
> > diff --git a/drivers/gpu/drm/i915/display/intel_fdi.c b/drivers/gpu/drm/i915/display/intel_fdi.c
> > index e12b46a84fa11..123ba67f68791 100644
> > --- a/drivers/gpu/drm/i915/display/intel_fdi.c
> > +++ b/drivers/gpu/drm/i915/display/intel_fdi.c
> > @@ -119,6 +119,59 @@ void intel_fdi_link_train(struct intel_crtc *crtc,
> >  	dev_priv->display.funcs.fdi->fdi_link_train(crtc, crtc_state);
> >  }
> >  
> > +/**
> > + * intel_fdi_add_affected_crtcs - add CRTCs on FDI affected by other modeset CRTCs
> > + * @state: intel atomic state
> > + *
> > + * Add a CRTC using FDI to @state if changing another CRTC's FDI BW usage is
> > + * known to affect the available FDI BW for the former CRTC. In practice this
> > + * means adding CRTC B on IVYBRIDGE if its use of FDI lanes is limited (by
> > + * CRTC C) and CRTC C is getting disabled.
> > + *
> > + * Returns 0 in case of success, or a negative error code otherwise.
> > + */
> > +int intel_fdi_add_affected_crtcs(struct intel_atomic_state *state)
> > +{
> > +	struct drm_i915_private *i915 = to_i915(state->base.dev);
> > +	struct intel_crtc_state *old_crtc_state;
> > +	struct intel_crtc_state *new_crtc_state;
> > +	struct intel_crtc *crtc;
> > +
> > +	if (!IS_IVYBRIDGE(i915))
> > +		return 0;
> > +
> > +	crtc = intel_crtc_for_pipe(i915, PIPE_C);
> > +	new_crtc_state = intel_atomic_get_new_crtc_state(state, crtc);
> > +
> > +	if (!new_crtc_state)
> > +		return 0;
> > +
> > +	old_crtc_state = intel_atomic_get_new_crtc_state(state, crtc);
> > +
> > +	if (!old_crtc_state->fdi_lanes)
> > +		return 0;
> > +
> > +	if (!intel_crtc_needs_modeset(new_crtc_state))
> > +		return 0;
> > +
> > +	if (new_crtc_state->uapi.enable)
> > +		return 0;
> > +
> > +	crtc = intel_crtc_for_pipe(i915, PIPE_B);
> > +	new_crtc_state = intel_atomic_get_crtc_state(&state->base, crtc);
> > +
> > +	if (IS_ERR(new_crtc_state))
> > +		return PTR_ERR(old_crtc_state);
> > +
> > +	old_crtc_state = intel_atomic_get_old_crtc_state(state, crtc);
> > +	if (!old_crtc_state->fdi_lanes)
> > +		return 0;
> > +
> > +	return intel_modeset_pipes_in_mask(state,
> > +					   "FDI link BW decrease on pipe C",
> > +					   BIT(PIPE_B), false);
> > +}
> > +
> >  /* units of 100MHz */
> >  static int pipe_required_fdi_lanes(struct intel_crtc_state *crtc_state)
> >  {
> > @@ -129,13 +182,16 @@ static int pipe_required_fdi_lanes(struct intel_crtc_state *crtc_state)
> >  }
> >  
> >  static int ilk_check_fdi_lanes(struct drm_device *dev, enum pipe pipe,
> > -			       struct intel_crtc_state *pipe_config)
> > +			       struct intel_crtc_state *pipe_config,
> > +			       enum pipe *pipe_to_reduce)
> >  {
> >  	struct drm_i915_private *dev_priv = to_i915(dev);
> >  	struct drm_atomic_state *state = pipe_config->uapi.state;
> >  	struct intel_crtc *other_crtc;
> >  	struct intel_crtc_state *other_crtc_state;
> >  
> > +	*pipe_to_reduce = pipe;
> > +
> >  	drm_dbg_kms(&dev_priv->drm,
> >  		    "checking fdi config on pipe %c, lanes %i\n",
> >  		    pipe_name(pipe), pipe_config->fdi_lanes);
> > @@ -198,6 +254,9 @@ static int ilk_check_fdi_lanes(struct drm_device *dev, enum pipe pipe,
> >  		if (pipe_required_fdi_lanes(other_crtc_state) > 2) {
> >  			drm_dbg_kms(&dev_priv->drm,
> >  				    "fdi link B uses too many lanes to enable link C\n");
> > +
> > +			*pipe_to_reduce = PIPE_B;
> > +
> >  			return -EINVAL;
> >  		}
> >  		return 0;
> > @@ -238,10 +297,8 @@ int ilk_fdi_compute_config(struct intel_crtc *crtc,
> >  	struct drm_device *dev = crtc->base.dev;
> >  	struct drm_i915_private *i915 = to_i915(dev);
> >  	const struct drm_display_mode *adjusted_mode = &pipe_config->hw.adjusted_mode;
> > -	int lane, link_bw, fdi_dotclock, ret;
> > -	bool needs_recompute = false;
> > +	int lane, link_bw, fdi_dotclock;
> >  
> > -retry:
> >  	/* FDI is a binary signal running at ~2.7GHz, encoding
> >  	 * each output octet as 10 bits. The actual frequency
> >  	 * is stored as a divider into a 100MHz clock, and the
> > @@ -261,25 +318,69 @@ int ilk_fdi_compute_config(struct intel_crtc *crtc,
> >  	intel_link_compute_m_n(pipe_config->pipe_bpp, lane, fdi_dotclock,
> >  			       link_bw, &pipe_config->fdi_m_n, false);
> >  
> > -	ret = ilk_check_fdi_lanes(dev, crtc->pipe, pipe_config);
> > -	if (ret == -EDEADLK)
> > +	return 0;
> > +}
> > +
> > +static int intel_fdi_atomic_check_bw(struct intel_atomic_state *state,
> > +				     struct intel_crtc *crtc,
> > +				     struct intel_crtc_state *pipe_config,
> > +				     struct intel_link_bw_limits *limits)
> > +{
> > +	struct drm_i915_private *i915 = to_i915(crtc->base.dev);
> > +	enum pipe pipe_to_reduce;
> > +	int ret;
> > +
> > +	ret = ilk_check_fdi_lanes(&i915->drm, crtc->pipe, pipe_config,
> > +				  &pipe_to_reduce);
> > +	if (ret != -EINVAL)
> >  		return ret;
> >  
> > -	if (ret == -EINVAL && pipe_config->pipe_bpp > 6*3) {
> > -		pipe_config->pipe_bpp -= 2*3;
> > -		drm_dbg_kms(&i915->drm,
> > -			    "fdi link bw constraint, reducing pipe bpp to %i\n",
> > -			    pipe_config->pipe_bpp);
> > -		needs_recompute = true;
> > -		pipe_config->bw_constrained = true;
> > +	ret = intel_atomic_reduce_link_bpp(state, limits,
> > +					   BIT(pipe_to_reduce),
> > +					   "FDI link BW");
> >  
> > -		goto retry;
> > -	}
> > +	return ret ? : -EAGAIN;
> > +}
> >  
> > -	if (needs_recompute)
> > -		return -EAGAIN;
> > +/**
> > + * intel_fdi_atomic_check_link - check all modeset FDI link configuration
> > + * @state: intel atomic state
> > + * @limits: link BW limits
> > + *
> > + * Check the link configuration for all modeset FDI outputs. If the
> > + * configuration is invalid @limits will be updated if possible to
> > + * reduce the total BW, after which the configuration for all CRTCs in
> > + * @state must be recomputed with the updated @limits.
> > + *
> > + * Returns:
> > + *   - 0 if the confugration is valid
> > + *   - %-EAGAIN, if the configuration is invalid and @limits got updated
> > + *     with fallback values with which the configuration of all CRTCs
> > + *     in @state must be recomputed
> > + *   - Other negative error, if the configuration is invalid without a
> > + *     fallback possibility, or the check failed for another reason
> > + */
> > +int intel_fdi_atomic_check_link(struct intel_atomic_state *state,
> > +				struct intel_link_bw_limits *limits)
> > +{
> > +	struct intel_crtc *crtc;
> > +	struct intel_crtc_state *crtc_state;
> > +	int i;
> > +
> > +	for_each_new_intel_crtc_in_state(state, crtc, crtc_state, i) {
> > +		int ret;
> >  
> > -	return ret;
> > +		if (!crtc_state->has_pch_encoder ||
> > +		    !intel_crtc_needs_modeset(crtc_state) ||
> > +		    !crtc_state->hw.enable)
> > +			continue;
> > +
> > +		ret = intel_fdi_atomic_check_bw(state, crtc, crtc_state, limits);
> > +		if (ret)
> > +			return ret;
> > +	}
> > +
> > +	return 0;
> >  }
> >  
> >  static void cpt_set_fdi_bc_bifurcation(struct drm_i915_private *dev_priv, bool enable)
> > diff --git a/drivers/gpu/drm/i915/display/intel_fdi.h b/drivers/gpu/drm/i915/display/intel_fdi.h
> > index 1cdb86172702f..eb02b967bb440 100644
> > --- a/drivers/gpu/drm/i915/display/intel_fdi.h
> > +++ b/drivers/gpu/drm/i915/display/intel_fdi.h
> > @@ -8,14 +8,19 @@
> >  
> >  enum pipe;
> >  struct drm_i915_private;
> > +struct intel_atomic_state;
> >  struct intel_crtc;
> >  struct intel_crtc_state;
> >  struct intel_encoder;
> > +struct intel_link_bw_limits;
> >  
> > +int intel_fdi_add_affected_crtcs(struct intel_atomic_state *state);
> >  int intel_fdi_link_freq(struct drm_i915_private *i915,
> >  			const struct intel_crtc_state *pipe_config);
> >  int ilk_fdi_compute_config(struct intel_crtc *intel_crtc,
> >  			   struct intel_crtc_state *pipe_config);
> > +int intel_fdi_atomic_check_link(struct intel_atomic_state *state,
> > +				struct intel_link_bw_limits *limits);
> >  void intel_fdi_normal_train(struct intel_crtc *crtc);
> >  void ilk_fdi_disable(struct intel_crtc *crtc);
> >  void ilk_fdi_pll_disable(struct intel_crtc *intel_crtc);
> > diff --git a/drivers/gpu/drm/i915/display/intel_lvds.c b/drivers/gpu/drm/i915/display/intel_lvds.c
> > index 3ace56979b70e..08dcc2d10a2c1 100644
> > --- a/drivers/gpu/drm/i915/display/intel_lvds.c
> > +++ b/drivers/gpu/drm/i915/display/intel_lvds.c
> > @@ -425,6 +425,12 @@ static int intel_lvds_compute_config(struct intel_encoder *encoder,
> >  		return -EINVAL;
> >  	}
> >  
> > +	if (HAS_PCH_SPLIT(i915)) {
> > +		crtc_state->has_pch_encoder = true;
> > +		if (!intel_atomic_compute_pipe_bpp(crtc_state))
> > +			return -EINVAL;
> > +	}
> > +
> >  	if (lvds_encoder->a3_power == LVDS_A3_POWER_UP)
> >  		lvds_bpp = 8*3;
> >  	else
> > @@ -453,9 +459,6 @@ static int intel_lvds_compute_config(struct intel_encoder *encoder,
> >  	if (adjusted_mode->flags & DRM_MODE_FLAG_DBLSCAN)
> >  		return -EINVAL;
> >  
> > -	if (HAS_PCH_SPLIT(i915))
> > -		crtc_state->has_pch_encoder = true;
> > -
> >  	ret = intel_panel_fitting(crtc_state, conn_state);
> >  	if (ret)
> >  		return ret;
> > diff --git a/drivers/gpu/drm/i915/display/intel_sdvo.c b/drivers/gpu/drm/i915/display/intel_sdvo.c
> > index 7d25a64698e2f..1c6330151f4d6 100644
> > --- a/drivers/gpu/drm/i915/display/intel_sdvo.c
> > +++ b/drivers/gpu/drm/i915/display/intel_sdvo.c
> > @@ -1352,14 +1352,17 @@ static int intel_sdvo_compute_config(struct intel_encoder *encoder,
> >  	struct drm_display_mode *adjusted_mode = &pipe_config->hw.adjusted_mode;
> >  	struct drm_display_mode *mode = &pipe_config->hw.mode;
> >  
> > +	if (HAS_PCH_SPLIT(to_i915(encoder->base.dev))) {
> > +		pipe_config->has_pch_encoder = true;
> > +		if (!intel_atomic_compute_pipe_bpp(pipe_config))
> > +			return -EINVAL;
> > +	}
> > +
> >  	DRM_DEBUG_KMS("forcing bpc to 8 for SDVO\n");
> >  	pipe_config->pipe_bpp = 8*3;
> >  	pipe_config->sink_format = INTEL_OUTPUT_FORMAT_RGB;
> >  	pipe_config->output_format = INTEL_OUTPUT_FORMAT_RGB;
> >  
> > -	if (HAS_PCH_SPLIT(to_i915(encoder->base.dev)))
> > -		pipe_config->has_pch_encoder = true;
> > -
> >  	/*
> >  	 * We need to construct preferred input timings based on our
> >  	 * output timings.  To do that, we have to set the output
> > -- 
> > 2.37.2
> 
> -- 
> Ville Syrjälä
> Intel
diff mbox series

Patch

diff --git a/drivers/gpu/drm/i915/display/g4x_hdmi.c b/drivers/gpu/drm/i915/display/g4x_hdmi.c
index 634b14116d9dd..ebbceddc13259 100644
--- a/drivers/gpu/drm/i915/display/g4x_hdmi.c
+++ b/drivers/gpu/drm/i915/display/g4x_hdmi.c
@@ -133,8 +133,11 @@  static int g4x_hdmi_compute_config(struct intel_encoder *encoder,
 	struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc);
 	struct drm_i915_private *i915 = to_i915(encoder->base.dev);
 
-	if (HAS_PCH_SPLIT(i915))
+	if (HAS_PCH_SPLIT(i915)) {
 		crtc_state->has_pch_encoder = true;
+		if (!intel_atomic_compute_pipe_bpp(crtc_state))
+			return -EINVAL;
+	}
 
 	if (IS_G4X(i915))
 		crtc_state->has_hdmi_sink = g4x_compute_has_hdmi_sink(state, crtc);
diff --git a/drivers/gpu/drm/i915/display/intel_atomic.c b/drivers/gpu/drm/i915/display/intel_atomic.c
index 7cf51dd8c0567..ee4cbf80ddb55 100644
--- a/drivers/gpu/drm/i915/display/intel_atomic.c
+++ b/drivers/gpu/drm/i915/display/intel_atomic.c
@@ -38,6 +38,7 @@ 
 #include "intel_atomic.h"
 #include "intel_cdclk.h"
 #include "intel_display_types.h"
+#include "intel_fdi.h"
 #include "intel_global_state.h"
 #include "intel_hdcp.h"
 #include "intel_psr.h"
@@ -358,3 +359,209 @@  intel_atomic_get_crtc_state(struct drm_atomic_state *state,
 
 	return to_intel_crtc_state(crtc_state);
 }
+
+/**
+ * intel_atomic_compute_pipe_bpp - compute pipe bpp limited by max link bpp
+ * @crtc_state: the crtc state
+ *
+ * Compute the pipe bpp limited by the CRTC's maximum link bpp. Encoders can
+ * call this function during state computation in the simple case where the
+ * link bpp will always match the pipe bpp. This is the case for all non-DP
+ * encoders, while DP encoders will use a link bpp lower than pipe bpp in case
+ * of DSC compression.
+ *
+ * Returns %true in case of success, %false if pipe bpp would need to be
+ * reduced below its valid range.
+ */
+bool intel_atomic_compute_pipe_bpp(struct intel_crtc_state *crtc_state)
+{
+	int pipe_bpp = min(crtc_state->pipe_bpp,
+			   to_bpp_int(crtc_state->max_link_bpp_x16));
+
+	pipe_bpp = rounddown(pipe_bpp, 2 * 3);
+
+	if (pipe_bpp < 6 * 3)
+		return false;
+
+	crtc_state->pipe_bpp = pipe_bpp;
+
+	return true;
+}
+
+/**
+ * intel_atomic_reduce_link_bpp - reduce maximum link bpp for a selected pipe
+ * @state: atomic state
+ * @limits: link BW limits
+ * @pipe_mask: mask of pipes to select from
+ * @reason: explanation of why bpp reduction is needed
+ *
+ * Select the pipe from @pipe_mask with the biggest link bpp value and set the
+ * maximum of link bpp in @limits below this value. Modeset the selected pipe,
+ * so that its state will get recomputed.
+ *
+ * This function can be called to resolve a link's BW overallocation by reducing
+ * the link bpp of one pipe on the link and hence reducing the total link BW.
+ *
+ * Returns
+ *   - 0 in case of success
+ *   - %-EINVAL if no pipe can further reduce its link bpp
+ *   - Other negative error, if modesetting the selected pipe failed
+ */
+int intel_atomic_reduce_link_bpp(struct intel_atomic_state *state,
+				 struct intel_link_bw_limits *limits,
+				 u8 pipe_mask,
+				 const char *reason)
+{
+	struct drm_i915_private *i915 = to_i915(state->base.dev);
+	enum pipe max_bpp_pipe = INVALID_PIPE;
+	struct intel_crtc *crtc;
+	int max_bpp = 0;
+
+	for_each_intel_crtc_in_pipe_mask(&i915->drm, crtc, pipe_mask) {
+		struct intel_crtc_state *crtc_state;
+		int pipe_bpp;
+
+		if (limits->min_bpp_pipes & BIT(crtc->pipe))
+			continue;
+
+		crtc_state = intel_atomic_get_crtc_state(&state->base,
+							 crtc);
+		if (IS_ERR(crtc_state))
+			return PTR_ERR(crtc_state);
+
+		if (crtc_state->dsc.compression_enable)
+			pipe_bpp = crtc_state->dsc.compressed_bpp;
+		else
+			pipe_bpp = crtc_state->pipe_bpp;
+
+		if (pipe_bpp > max_bpp) {
+			max_bpp = pipe_bpp;
+			max_bpp_pipe = crtc->pipe;
+		}
+	}
+
+	if (max_bpp_pipe == INVALID_PIPE)
+		return -EINVAL;
+
+	limits->max_bpp_x16[max_bpp_pipe] = to_bpp_x16(max_bpp) - 1;
+
+	return intel_modeset_pipes_in_mask(state, reason,
+					   BIT(max_bpp_pipe), false);
+}
+
+static int intel_atomic_check_link(struct intel_atomic_state *state,
+				   struct intel_link_bw_limits *limits)
+{
+	int ret;
+
+	ret = intel_fdi_atomic_check_link(state, limits);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static bool
+assert_link_limit_change_valid(struct drm_i915_private *i915,
+			       const struct intel_link_bw_limits *old_limits,
+			       const struct intel_link_bw_limits *new_limits)
+{
+	bool bpps_changed = false;
+	enum pipe pipe;
+
+	for_each_pipe(i915, pipe) {
+		/* The bpp limit can only decrease. */
+		if (drm_WARN_ON(&i915->drm,
+				new_limits->max_bpp_x16[pipe] >
+				old_limits->max_bpp_x16[pipe]))
+			return false;
+
+		if (new_limits->max_bpp_x16[pipe] <
+		    old_limits->max_bpp_x16[pipe])
+			bpps_changed = true;
+	}
+
+	if (drm_WARN_ON(&i915->drm,
+			!bpps_changed))
+		return false;
+
+	return true;
+}
+
+static bool
+reset_link_bpp_limit_to_min(struct intel_atomic_state *state,
+			    const struct intel_link_bw_limits *old_limits,
+			    struct intel_link_bw_limits *new_limits,
+			    enum pipe failed_pipe)
+{
+	if (failed_pipe == INVALID_PIPE)
+		return false;
+
+	if (new_limits->min_bpp_pipes & BIT(failed_pipe))
+		return false;
+
+	if (new_limits->max_bpp_x16[failed_pipe] ==
+	    old_limits->max_bpp_x16[failed_pipe])
+		return false;
+
+	new_limits->max_bpp_x16[failed_pipe] =
+		old_limits->max_bpp_x16[failed_pipe];
+	new_limits->min_bpp_pipes |= BIT(failed_pipe);
+
+	return true;
+}
+
+/**
+ * intel_atomic_check_config_and_link - compute CRTC configs, resolving any BW limits
+ * @state: atomic state
+ *
+ * Compute the configuration of all CRTCs in @state and resolve any BW
+ * limitations on links shared by these CRTCs.
+ *
+ * Return 0 in case of success, or a negative error code otherwise.
+ */
+int intel_atomic_check_config_and_link(struct intel_atomic_state *state)
+{
+	struct drm_i915_private *i915 = to_i915(state->base.dev);
+	struct intel_link_bw_limits new_limits = {};
+	struct intel_link_bw_limits old_limits;
+	enum pipe pipe;
+	int ret;
+
+	for_each_pipe(i915, pipe)
+		new_limits.max_bpp_x16[pipe] = INT_MAX;
+
+	old_limits = new_limits;
+
+	while (true) {
+		enum pipe failed_pipe;
+
+		ret = intel_atomic_check_config(state, &new_limits,
+						&failed_pipe);
+		if (ret) {
+			if (ret == -EINVAL &&
+			    reset_link_bpp_limit_to_min(state,
+							&old_limits,
+							&new_limits,
+							failed_pipe))
+				continue;
+
+			break;
+		}
+
+		old_limits = new_limits;
+
+		ret = intel_atomic_check_link(state, &new_limits);
+		if (ret != -EAGAIN)
+			break;
+
+		if (!assert_link_limit_change_valid(i915,
+						    &old_limits,
+						    &new_limits)) {
+			ret = -EINVAL;
+			break;
+		}
+	}
+
+	return ret;
+}
diff --git a/drivers/gpu/drm/i915/display/intel_atomic.h b/drivers/gpu/drm/i915/display/intel_atomic.h
index e506f6a873447..bbf3595d52c41 100644
--- a/drivers/gpu/drm/i915/display/intel_atomic.h
+++ b/drivers/gpu/drm/i915/display/intel_atomic.h
@@ -20,6 +20,7 @@  struct intel_atomic_state;
 struct intel_connector;
 struct intel_crtc;
 struct intel_crtc_state;
+struct intel_link_bw_limits;
 
 int intel_digital_connector_atomic_get_property(struct drm_connector *connector,
 						const struct drm_connector_state *state,
@@ -52,4 +53,11 @@  struct intel_crtc_state *
 intel_atomic_get_crtc_state(struct drm_atomic_state *state,
 			    struct intel_crtc *crtc);
 
+int intel_atomic_reduce_link_bpp(struct intel_atomic_state *state,
+				 struct intel_link_bw_limits *limits,
+				 u8 pipe_mask,
+				 const char *reason);
+bool intel_atomic_compute_pipe_bpp(struct intel_crtc_state *crtc_state);
+int intel_atomic_check_config_and_link(struct intel_atomic_state *state);
+
 #endif /* __INTEL_ATOMIC_H__ */
diff --git a/drivers/gpu/drm/i915/display/intel_crt.c b/drivers/gpu/drm/i915/display/intel_crt.c
index f66340b4caf0f..3322080a574e8 100644
--- a/drivers/gpu/drm/i915/display/intel_crt.c
+++ b/drivers/gpu/drm/i915/display/intel_crt.c
@@ -36,6 +36,7 @@ 
 #include "i915_drv.h"
 #include "i915_irq.h"
 #include "i915_reg.h"
+#include "intel_atomic.h"
 #include "intel_connector.h"
 #include "intel_crt.h"
 #include "intel_crtc.h"
@@ -413,6 +414,9 @@  static int pch_crt_compute_config(struct intel_encoder *encoder,
 		return -EINVAL;
 
 	pipe_config->has_pch_encoder = true;
+	if (!intel_atomic_compute_pipe_bpp(pipe_config))
+		return -EINVAL;
+
 	pipe_config->output_format = INTEL_OUTPUT_FORMAT_RGB;
 
 	return 0;
@@ -435,6 +439,9 @@  static int hsw_crt_compute_config(struct intel_encoder *encoder,
 		return -EINVAL;
 
 	pipe_config->has_pch_encoder = true;
+	if (!intel_atomic_compute_pipe_bpp(pipe_config))
+		return -EINVAL;
+
 	pipe_config->output_format = INTEL_OUTPUT_FORMAT_RGB;
 
 	/* LPT FDI RX only supports 8bpc. */
diff --git a/drivers/gpu/drm/i915/display/intel_crtc.c b/drivers/gpu/drm/i915/display/intel_crtc.c
index 182c6dd64f47c..1eda6a9f19aa8 100644
--- a/drivers/gpu/drm/i915/display/intel_crtc.c
+++ b/drivers/gpu/drm/i915/display/intel_crtc.c
@@ -175,6 +175,7 @@  void intel_crtc_state_reset(struct intel_crtc_state *crtc_state,
 	crtc_state->hsw_workaround_pipe = INVALID_PIPE;
 	crtc_state->scaler_state.scaler_id = -1;
 	crtc_state->mst_master_transcoder = INVALID_TRANSCODER;
+	crtc_state->max_link_bpp_x16 = INT_MAX;
 }
 
 static struct intel_crtc *intel_crtc_alloc(void)
diff --git a/drivers/gpu/drm/i915/display/intel_display.c b/drivers/gpu/drm/i915/display/intel_display.c
index dbf109a2e738f..32778bd01bb05 100644
--- a/drivers/gpu/drm/i915/display/intel_display.c
+++ b/drivers/gpu/drm/i915/display/intel_display.c
@@ -4641,7 +4641,8 @@  intel_crtc_prepare_cleared_state(struct intel_atomic_state *state,
 
 static int
 intel_modeset_pipe_config(struct intel_atomic_state *state,
-			  struct intel_crtc *crtc)
+			  struct intel_crtc *crtc,
+			  const struct intel_link_bw_limits *limits)
 {
 	struct drm_i915_private *i915 = to_i915(crtc->base.dev);
 	struct intel_crtc_state *crtc_state =
@@ -4650,7 +4651,6 @@  intel_modeset_pipe_config(struct intel_atomic_state *state,
 	struct drm_connector_state *connector_state;
 	int pipe_src_w, pipe_src_h;
 	int base_bpp, ret, i;
-	bool retry = true;
 
 	crtc_state->cpu_transcoder = (enum transcoder) crtc->pipe;
 
@@ -4673,6 +4673,17 @@  intel_modeset_pipe_config(struct intel_atomic_state *state,
 	if (ret)
 		return ret;
 
+	crtc_state->max_link_bpp_x16 = limits->max_bpp_x16[crtc->pipe];
+
+	if (crtc_state->pipe_bpp > to_bpp_int(crtc_state->max_link_bpp_x16)) {
+		drm_dbg_kms(&i915->drm,
+			    "[CRTC:%d:%s] Link bpp limited to %d.%04d\n",
+			    crtc->base.base.id, crtc->base.name,
+			    to_bpp_int(crtc_state->max_link_bpp_x16),
+			    to_bpp_frac_dec(crtc_state->max_link_bpp_x16));
+		crtc_state->bw_constrained = true;
+	}
+
 	base_bpp = crtc_state->pipe_bpp;
 
 	/*
@@ -4714,7 +4725,6 @@  intel_modeset_pipe_config(struct intel_atomic_state *state,
 			crtc_state->output_types |= BIT(encoder->type);
 	}
 
-encoder_retry:
 	/* Ensure the port clock defaults are reset when retrying. */
 	crtc_state->port_clock = 0;
 	crtc_state->pixel_multiplier = 1;
@@ -4754,17 +4764,6 @@  intel_modeset_pipe_config(struct intel_atomic_state *state,
 	ret = intel_crtc_compute_config(state, crtc);
 	if (ret == -EDEADLK)
 		return ret;
-	if (ret == -EAGAIN) {
-		if (drm_WARN(&i915->drm, !retry,
-			     "[CRTC:%d:%s] loop in pipe configuration computation\n",
-			     crtc->base.base.id, crtc->base.name))
-			return -EINVAL;
-
-		drm_dbg_kms(&i915->drm, "[CRTC:%d:%s] bw constrained, retrying\n",
-			    crtc->base.base.id, crtc->base.name);
-		retry = false;
-		goto encoder_retry;
-	}
 	if (ret < 0) {
 		drm_dbg_kms(&i915->drm, "[CRTC:%d:%s] config failure: %d\n",
 			    crtc->base.base.id, crtc->base.name, ret);
@@ -6206,7 +6205,9 @@  static int intel_bigjoiner_add_affected_crtcs(struct intel_atomic_state *state)
 	return 0;
 }
 
-static int intel_atomic_check_config(struct intel_atomic_state *state)
+int intel_atomic_check_config(struct intel_atomic_state *state,
+			      struct intel_link_bw_limits *limits,
+			      enum pipe *failed_pipe)
 {
 	struct drm_i915_private *i915 = to_i915(state->base.dev);
 	struct intel_crtc_state *new_crtc_state;
@@ -6214,10 +6215,16 @@  static int intel_atomic_check_config(struct intel_atomic_state *state)
 	int ret;
 	int i;
 
+	*failed_pipe = INVALID_PIPE;
+
 	ret = intel_bigjoiner_add_affected_crtcs(state);
 	if (ret)
 		return ret;
 
+	ret = intel_fdi_add_affected_crtcs(state);
+	if (ret)
+		return ret;
+
 	for_each_new_intel_crtc_in_state(state, crtc, new_crtc_state, i) {
 		if (!intel_crtc_needs_modeset(new_crtc_state)) {
 			if (intel_crtc_is_bigjoiner_slave(new_crtc_state))
@@ -6239,7 +6246,7 @@  static int intel_atomic_check_config(struct intel_atomic_state *state)
 		if (!new_crtc_state->hw.enable)
 			continue;
 
-		ret = intel_modeset_pipe_config(state, crtc);
+		ret = intel_modeset_pipe_config(state, crtc, limits);
 		if (ret)
 			break;
 
@@ -6248,6 +6255,9 @@  static int intel_atomic_check_config(struct intel_atomic_state *state)
 			break;
 	}
 
+	if (ret)
+		*failed_pipe = crtc->pipe;
+
 	return ret;
 }
 
@@ -6295,7 +6305,7 @@  int intel_atomic_check(struct drm_device *dev,
 			return ret;
 	}
 
-	ret = intel_atomic_check_config(state);
+	ret = intel_atomic_check_config_and_link(state);
 	if (ret)
 		goto fail;
 
diff --git a/drivers/gpu/drm/i915/display/intel_display.h b/drivers/gpu/drm/i915/display/intel_display.h
index d9a54610d9d5e..2e0535739bd70 100644
--- a/drivers/gpu/drm/i915/display/intel_display.h
+++ b/drivers/gpu/drm/i915/display/intel_display.h
@@ -55,6 +55,7 @@  struct intel_digital_port;
 struct intel_dp;
 struct intel_encoder;
 struct intel_initial_plane_config;
+struct intel_link_bw_limits;
 struct intel_link_m_n;
 struct intel_plane;
 struct intel_plane_state;
@@ -391,6 +392,9 @@  enum phy_fia {
 			     (new_connector_state) = to_intel_digital_connector_state((__state)->base.connectors[__i].new_state), 1))
 
 int intel_atomic_check(struct drm_device *dev, struct drm_atomic_state *state);
+int intel_atomic_check_config(struct intel_atomic_state *state,
+			      struct intel_link_bw_limits *limits,
+			      enum pipe *failed_pipe);
 int intel_atomic_add_affected_planes(struct intel_atomic_state *state,
 				     struct intel_crtc *crtc);
 u8 intel_calc_active_pipes(struct intel_atomic_state *state,
diff --git a/drivers/gpu/drm/i915/display/intel_display_types.h b/drivers/gpu/drm/i915/display/intel_display_types.h
index b143085b399eb..6f4f46658df22 100644
--- a/drivers/gpu/drm/i915/display/intel_display_types.h
+++ b/drivers/gpu/drm/i915/display/intel_display_types.h
@@ -66,6 +66,12 @@  struct intel_tc_port;
  * Display related stuff
  */
 
+struct intel_link_bw_limits {
+	u8 min_bpp_pipes;
+	/* in 1/16 bpp units */
+	int max_bpp_x16[I915_MAX_PIPES];
+};
+
 /* these are outputs from the chip - integrated only
    external chips are via DVO or SDVO output */
 enum intel_output_type {
@@ -1189,7 +1195,8 @@  struct intel_crtc_state {
 		u32 ctrl, div;
 	} dsi_pll;
 
-	int pipe_bpp;
+	int max_link_bpp_x16;	/* in 1/16 bpp units */
+	int pipe_bpp;		/* in 1 bpp units */
 	struct intel_link_m_n dp_m_n;
 
 	/* m2_n2 for eDP downclock */
diff --git a/drivers/gpu/drm/i915/display/intel_dp.c b/drivers/gpu/drm/i915/display/intel_dp.c
index 6637bd4768bf7..48f005932ad8b 100644
--- a/drivers/gpu/drm/i915/display/intel_dp.c
+++ b/drivers/gpu/drm/i915/display/intel_dp.c
@@ -2218,7 +2218,8 @@  intel_dp_compute_config_link_bpp_limits(struct intel_dp *intel_dp,
 	const struct intel_encoder *encoder = &dp_to_dig_port(intel_dp)->base;
 	int max_link_bpp_x16;
 
-	max_link_bpp_x16 = to_bpp_x16(limits->pipe.max_bpp);
+	max_link_bpp_x16 = min(crtc_state->max_link_bpp_x16,
+			       to_bpp_x16(limits->pipe.max_bpp));
 
 	if (!dsc) {
 		max_link_bpp_x16 = rounddown(max_link_bpp_x16, to_bpp_x16(2 * 3));
diff --git a/drivers/gpu/drm/i915/display/intel_fdi.c b/drivers/gpu/drm/i915/display/intel_fdi.c
index e12b46a84fa11..123ba67f68791 100644
--- a/drivers/gpu/drm/i915/display/intel_fdi.c
+++ b/drivers/gpu/drm/i915/display/intel_fdi.c
@@ -119,6 +119,59 @@  void intel_fdi_link_train(struct intel_crtc *crtc,
 	dev_priv->display.funcs.fdi->fdi_link_train(crtc, crtc_state);
 }
 
+/**
+ * intel_fdi_add_affected_crtcs - add CRTCs on FDI affected by other modeset CRTCs
+ * @state: intel atomic state
+ *
+ * Add a CRTC using FDI to @state if changing another CRTC's FDI BW usage is
+ * known to affect the available FDI BW for the former CRTC. In practice this
+ * means adding CRTC B on IVYBRIDGE if its use of FDI lanes is limited (by
+ * CRTC C) and CRTC C is getting disabled.
+ *
+ * Returns 0 in case of success, or a negative error code otherwise.
+ */
+int intel_fdi_add_affected_crtcs(struct intel_atomic_state *state)
+{
+	struct drm_i915_private *i915 = to_i915(state->base.dev);
+	struct intel_crtc_state *old_crtc_state;
+	struct intel_crtc_state *new_crtc_state;
+	struct intel_crtc *crtc;
+
+	if (!IS_IVYBRIDGE(i915))
+		return 0;
+
+	crtc = intel_crtc_for_pipe(i915, PIPE_C);
+	new_crtc_state = intel_atomic_get_new_crtc_state(state, crtc);
+
+	if (!new_crtc_state)
+		return 0;
+
+	old_crtc_state = intel_atomic_get_new_crtc_state(state, crtc);
+
+	if (!old_crtc_state->fdi_lanes)
+		return 0;
+
+	if (!intel_crtc_needs_modeset(new_crtc_state))
+		return 0;
+
+	if (new_crtc_state->uapi.enable)
+		return 0;
+
+	crtc = intel_crtc_for_pipe(i915, PIPE_B);
+	new_crtc_state = intel_atomic_get_crtc_state(&state->base, crtc);
+
+	if (IS_ERR(new_crtc_state))
+		return PTR_ERR(old_crtc_state);
+
+	old_crtc_state = intel_atomic_get_old_crtc_state(state, crtc);
+	if (!old_crtc_state->fdi_lanes)
+		return 0;
+
+	return intel_modeset_pipes_in_mask(state,
+					   "FDI link BW decrease on pipe C",
+					   BIT(PIPE_B), false);
+}
+
 /* units of 100MHz */
 static int pipe_required_fdi_lanes(struct intel_crtc_state *crtc_state)
 {
@@ -129,13 +182,16 @@  static int pipe_required_fdi_lanes(struct intel_crtc_state *crtc_state)
 }
 
 static int ilk_check_fdi_lanes(struct drm_device *dev, enum pipe pipe,
-			       struct intel_crtc_state *pipe_config)
+			       struct intel_crtc_state *pipe_config,
+			       enum pipe *pipe_to_reduce)
 {
 	struct drm_i915_private *dev_priv = to_i915(dev);
 	struct drm_atomic_state *state = pipe_config->uapi.state;
 	struct intel_crtc *other_crtc;
 	struct intel_crtc_state *other_crtc_state;
 
+	*pipe_to_reduce = pipe;
+
 	drm_dbg_kms(&dev_priv->drm,
 		    "checking fdi config on pipe %c, lanes %i\n",
 		    pipe_name(pipe), pipe_config->fdi_lanes);
@@ -198,6 +254,9 @@  static int ilk_check_fdi_lanes(struct drm_device *dev, enum pipe pipe,
 		if (pipe_required_fdi_lanes(other_crtc_state) > 2) {
 			drm_dbg_kms(&dev_priv->drm,
 				    "fdi link B uses too many lanes to enable link C\n");
+
+			*pipe_to_reduce = PIPE_B;
+
 			return -EINVAL;
 		}
 		return 0;
@@ -238,10 +297,8 @@  int ilk_fdi_compute_config(struct intel_crtc *crtc,
 	struct drm_device *dev = crtc->base.dev;
 	struct drm_i915_private *i915 = to_i915(dev);
 	const struct drm_display_mode *adjusted_mode = &pipe_config->hw.adjusted_mode;
-	int lane, link_bw, fdi_dotclock, ret;
-	bool needs_recompute = false;
+	int lane, link_bw, fdi_dotclock;
 
-retry:
 	/* FDI is a binary signal running at ~2.7GHz, encoding
 	 * each output octet as 10 bits. The actual frequency
 	 * is stored as a divider into a 100MHz clock, and the
@@ -261,25 +318,69 @@  int ilk_fdi_compute_config(struct intel_crtc *crtc,
 	intel_link_compute_m_n(pipe_config->pipe_bpp, lane, fdi_dotclock,
 			       link_bw, &pipe_config->fdi_m_n, false);
 
-	ret = ilk_check_fdi_lanes(dev, crtc->pipe, pipe_config);
-	if (ret == -EDEADLK)
+	return 0;
+}
+
+static int intel_fdi_atomic_check_bw(struct intel_atomic_state *state,
+				     struct intel_crtc *crtc,
+				     struct intel_crtc_state *pipe_config,
+				     struct intel_link_bw_limits *limits)
+{
+	struct drm_i915_private *i915 = to_i915(crtc->base.dev);
+	enum pipe pipe_to_reduce;
+	int ret;
+
+	ret = ilk_check_fdi_lanes(&i915->drm, crtc->pipe, pipe_config,
+				  &pipe_to_reduce);
+	if (ret != -EINVAL)
 		return ret;
 
-	if (ret == -EINVAL && pipe_config->pipe_bpp > 6*3) {
-		pipe_config->pipe_bpp -= 2*3;
-		drm_dbg_kms(&i915->drm,
-			    "fdi link bw constraint, reducing pipe bpp to %i\n",
-			    pipe_config->pipe_bpp);
-		needs_recompute = true;
-		pipe_config->bw_constrained = true;
+	ret = intel_atomic_reduce_link_bpp(state, limits,
+					   BIT(pipe_to_reduce),
+					   "FDI link BW");
 
-		goto retry;
-	}
+	return ret ? : -EAGAIN;
+}
 
-	if (needs_recompute)
-		return -EAGAIN;
+/**
+ * intel_fdi_atomic_check_link - check all modeset FDI link configuration
+ * @state: intel atomic state
+ * @limits: link BW limits
+ *
+ * Check the link configuration for all modeset FDI outputs. If the
+ * configuration is invalid @limits will be updated if possible to
+ * reduce the total BW, after which the configuration for all CRTCs in
+ * @state must be recomputed with the updated @limits.
+ *
+ * Returns:
+ *   - 0 if the confugration is valid
+ *   - %-EAGAIN, if the configuration is invalid and @limits got updated
+ *     with fallback values with which the configuration of all CRTCs
+ *     in @state must be recomputed
+ *   - Other negative error, if the configuration is invalid without a
+ *     fallback possibility, or the check failed for another reason
+ */
+int intel_fdi_atomic_check_link(struct intel_atomic_state *state,
+				struct intel_link_bw_limits *limits)
+{
+	struct intel_crtc *crtc;
+	struct intel_crtc_state *crtc_state;
+	int i;
+
+	for_each_new_intel_crtc_in_state(state, crtc, crtc_state, i) {
+		int ret;
 
-	return ret;
+		if (!crtc_state->has_pch_encoder ||
+		    !intel_crtc_needs_modeset(crtc_state) ||
+		    !crtc_state->hw.enable)
+			continue;
+
+		ret = intel_fdi_atomic_check_bw(state, crtc, crtc_state, limits);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
 }
 
 static void cpt_set_fdi_bc_bifurcation(struct drm_i915_private *dev_priv, bool enable)
diff --git a/drivers/gpu/drm/i915/display/intel_fdi.h b/drivers/gpu/drm/i915/display/intel_fdi.h
index 1cdb86172702f..eb02b967bb440 100644
--- a/drivers/gpu/drm/i915/display/intel_fdi.h
+++ b/drivers/gpu/drm/i915/display/intel_fdi.h
@@ -8,14 +8,19 @@ 
 
 enum pipe;
 struct drm_i915_private;
+struct intel_atomic_state;
 struct intel_crtc;
 struct intel_crtc_state;
 struct intel_encoder;
+struct intel_link_bw_limits;
 
+int intel_fdi_add_affected_crtcs(struct intel_atomic_state *state);
 int intel_fdi_link_freq(struct drm_i915_private *i915,
 			const struct intel_crtc_state *pipe_config);
 int ilk_fdi_compute_config(struct intel_crtc *intel_crtc,
 			   struct intel_crtc_state *pipe_config);
+int intel_fdi_atomic_check_link(struct intel_atomic_state *state,
+				struct intel_link_bw_limits *limits);
 void intel_fdi_normal_train(struct intel_crtc *crtc);
 void ilk_fdi_disable(struct intel_crtc *crtc);
 void ilk_fdi_pll_disable(struct intel_crtc *intel_crtc);
diff --git a/drivers/gpu/drm/i915/display/intel_lvds.c b/drivers/gpu/drm/i915/display/intel_lvds.c
index 3ace56979b70e..08dcc2d10a2c1 100644
--- a/drivers/gpu/drm/i915/display/intel_lvds.c
+++ b/drivers/gpu/drm/i915/display/intel_lvds.c
@@ -425,6 +425,12 @@  static int intel_lvds_compute_config(struct intel_encoder *encoder,
 		return -EINVAL;
 	}
 
+	if (HAS_PCH_SPLIT(i915)) {
+		crtc_state->has_pch_encoder = true;
+		if (!intel_atomic_compute_pipe_bpp(crtc_state))
+			return -EINVAL;
+	}
+
 	if (lvds_encoder->a3_power == LVDS_A3_POWER_UP)
 		lvds_bpp = 8*3;
 	else
@@ -453,9 +459,6 @@  static int intel_lvds_compute_config(struct intel_encoder *encoder,
 	if (adjusted_mode->flags & DRM_MODE_FLAG_DBLSCAN)
 		return -EINVAL;
 
-	if (HAS_PCH_SPLIT(i915))
-		crtc_state->has_pch_encoder = true;
-
 	ret = intel_panel_fitting(crtc_state, conn_state);
 	if (ret)
 		return ret;
diff --git a/drivers/gpu/drm/i915/display/intel_sdvo.c b/drivers/gpu/drm/i915/display/intel_sdvo.c
index 7d25a64698e2f..1c6330151f4d6 100644
--- a/drivers/gpu/drm/i915/display/intel_sdvo.c
+++ b/drivers/gpu/drm/i915/display/intel_sdvo.c
@@ -1352,14 +1352,17 @@  static int intel_sdvo_compute_config(struct intel_encoder *encoder,
 	struct drm_display_mode *adjusted_mode = &pipe_config->hw.adjusted_mode;
 	struct drm_display_mode *mode = &pipe_config->hw.mode;
 
+	if (HAS_PCH_SPLIT(to_i915(encoder->base.dev))) {
+		pipe_config->has_pch_encoder = true;
+		if (!intel_atomic_compute_pipe_bpp(pipe_config))
+			return -EINVAL;
+	}
+
 	DRM_DEBUG_KMS("forcing bpc to 8 for SDVO\n");
 	pipe_config->pipe_bpp = 8*3;
 	pipe_config->sink_format = INTEL_OUTPUT_FORMAT_RGB;
 	pipe_config->output_format = INTEL_OUTPUT_FORMAT_RGB;
 
-	if (HAS_PCH_SPLIT(to_i915(encoder->base.dev)))
-		pipe_config->has_pch_encoder = true;
-
 	/*
 	 * We need to construct preferred input timings based on our
 	 * output timings.  To do that, we have to set the output