linux/drivers/gpu/drm/i915/display/intel_link_bw.c
Imre Deak f7f46a80fa drm/i915: Add support for forcing the link bpp on a connector
Add support for forcing the link bpp on a connector via a connector
debugfs entry. During reducing link bpps due to a link BW limit, keep
bpps close to their forced value.

Add the debugfs entry to all relevant connectors: all DP connectors and
on an FDI link CRT/SDVO/LVDS/HDMI connectors.

v2:
- Move adding the debugfs entries to this patch.
- Rename i915_force_link_bpp to intel_force_link_bpp. (Jani)
- Select the relevant connectors via platform checks. (Jani)
- Use for_each_new_intel_connector_in_state(). (Jani)
- Fix 64 bit division vs. 32 bit build when converting str to q4. (lkp)
- Avoid division and addition overflow when converting str to q4.

v3:
- Add TODO: to make the non-DSC min bpp value connector specific. (Ankit)

Cc: Jani Nikula <jani.nikula@intel.com>
Reviewed-by: Ankit Nautiyal <ankit.k.nautiyal@intel.com>
Signed-off-by: Imre Deak <imre.deak@intel.com>
Link: https://lore.kernel.org/r/20250509180340.554867-12-imre.deak@intel.com
2025-05-12 15:22:52 +03:00

472 lines
12 KiB
C

// SPDX-License-Identifier: MIT
/*
* Copyright © 2023 Intel Corporation
*/
#include <linux/ctype.h>
#include <linux/debugfs.h>
#include <linux/int_log.h>
#include <linux/math.h>
#include <drm/drm_fixed.h>
#include <drm/drm_print.h>
#include "intel_atomic.h"
#include "intel_crtc.h"
#include "intel_display_core.h"
#include "intel_display_types.h"
#include "intel_dp.h"
#include "intel_dp_mst.h"
#include "intel_dp_tunnel.h"
#include "intel_fdi.h"
#include "intel_link_bw.h"
static int get_forced_link_bpp_x16(struct intel_atomic_state *state,
const struct intel_crtc *crtc)
{
struct intel_digital_connector_state *conn_state;
struct intel_connector *connector;
int force_bpp_x16 = INT_MAX;
int i;
for_each_new_intel_connector_in_state(state, connector, conn_state, i) {
if (conn_state->base.crtc != &crtc->base)
continue;
if (!connector->link.force_bpp_x16)
continue;
force_bpp_x16 = min(force_bpp_x16, connector->link.force_bpp_x16);
}
return force_bpp_x16 < INT_MAX ? force_bpp_x16 : 0;
}
/**
* intel_link_bw_init_limits - initialize BW limits
* @state: Atomic state
* @limits: link BW limits
*
* Initialize @limits.
*/
void intel_link_bw_init_limits(struct intel_atomic_state *state,
struct intel_link_bw_limits *limits)
{
struct intel_display *display = to_intel_display(state);
enum pipe pipe;
limits->force_fec_pipes = 0;
limits->bpp_limit_reached_pipes = 0;
for_each_pipe(display, pipe) {
struct intel_crtc *crtc = intel_crtc_for_pipe(display, pipe);
const struct intel_crtc_state *crtc_state =
intel_atomic_get_new_crtc_state(state, crtc);
int forced_bpp_x16 = get_forced_link_bpp_x16(state, crtc);
if (state->base.duplicated && crtc_state) {
limits->max_bpp_x16[pipe] = crtc_state->max_link_bpp_x16;
if (crtc_state->fec_enable)
limits->force_fec_pipes |= BIT(pipe);
} else {
limits->max_bpp_x16[pipe] = INT_MAX;
}
if (forced_bpp_x16)
limits->max_bpp_x16[pipe] = min(limits->max_bpp_x16[pipe], forced_bpp_x16);
}
}
/**
* __intel_link_bw_reduce_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
* @reduce_forced_bpp: allow reducing bpps below their forced link bpp
*
* 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
* - %-ENOSPC if no pipe can further reduce its link bpp
* - Other negative error, if modesetting the selected pipe failed
*/
static int __intel_link_bw_reduce_bpp(struct intel_atomic_state *state,
struct intel_link_bw_limits *limits,
u8 pipe_mask,
const char *reason,
bool reduce_forced_bpp)
{
struct intel_display *display = to_intel_display(state);
enum pipe max_bpp_pipe = INVALID_PIPE;
struct intel_crtc *crtc;
int max_bpp_x16 = 0;
for_each_intel_crtc_in_pipe_mask(display->drm, crtc, pipe_mask) {
struct intel_crtc_state *crtc_state;
int link_bpp_x16;
if (limits->bpp_limit_reached_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)
link_bpp_x16 = crtc_state->dsc.compressed_bpp_x16;
else
/*
* TODO: for YUV420 the actual link bpp is only half
* of the pipe bpp value. The MST encoder's BW allocation
* is based on the pipe bpp value, set the actual link bpp
* limit here once the MST BW allocation is fixed.
*/
link_bpp_x16 = fxp_q4_from_int(crtc_state->pipe_bpp);
if (!reduce_forced_bpp &&
link_bpp_x16 <= get_forced_link_bpp_x16(state, crtc))
continue;
if (link_bpp_x16 > max_bpp_x16) {
max_bpp_x16 = link_bpp_x16;
max_bpp_pipe = crtc->pipe;
}
}
if (max_bpp_pipe == INVALID_PIPE)
return -ENOSPC;
limits->max_bpp_x16[max_bpp_pipe] = max_bpp_x16 - 1;
return intel_modeset_pipes_in_mask_early(state, reason,
BIT(max_bpp_pipe));
}
int intel_link_bw_reduce_bpp(struct intel_atomic_state *state,
struct intel_link_bw_limits *limits,
u8 pipe_mask,
const char *reason)
{
int ret;
/* Try to keep any forced link BPP. */
ret = __intel_link_bw_reduce_bpp(state, limits, pipe_mask, reason, false);
if (ret == -ENOSPC)
ret = __intel_link_bw_reduce_bpp(state, limits, pipe_mask, reason, true);
return ret;
}
/**
* intel_link_bw_set_bpp_limit_for_pipe - set link bpp limit for a pipe to its minimum
* @state: atomic state
* @old_limits: link BW limits
* @new_limits: link BW limits
* @pipe: pipe
*
* Set the link bpp limit for @pipe in @new_limits to its value in
* @old_limits and mark this limit as the minimum. This function must be
* called after a pipe's compute config function failed, @old_limits
* containing the bpp limit with which compute config previously passed.
*
* The function will fail if setting a minimum is not possible, either
* because the old and new limits match (and so would lead to a pipe compute
* config failure) or the limit is already at the minimum.
*
* Returns %true in case of success.
*/
bool
intel_link_bw_set_bpp_limit_for_pipe(struct intel_atomic_state *state,
const struct intel_link_bw_limits *old_limits,
struct intel_link_bw_limits *new_limits,
enum pipe pipe)
{
struct intel_display *display = to_intel_display(state);
if (pipe == INVALID_PIPE)
return false;
if (new_limits->max_bpp_x16[pipe] ==
old_limits->max_bpp_x16[pipe])
return false;
if (drm_WARN_ON(display->drm,
new_limits->bpp_limit_reached_pipes & BIT(pipe)))
return false;
new_limits->max_bpp_x16[pipe] =
old_limits->max_bpp_x16[pipe];
new_limits->bpp_limit_reached_pipes |= BIT(pipe);
return true;
}
static int check_all_link_config(struct intel_atomic_state *state,
struct intel_link_bw_limits *limits)
{
/* TODO: Check additional shared display link configurations like MST */
int ret;
ret = intel_dp_mst_atomic_check_link(state, limits);
if (ret)
return ret;
ret = intel_dp_tunnel_atomic_check_link(state, limits);
if (ret)
return ret;
ret = intel_fdi_atomic_check_link(state, limits);
if (ret)
return ret;
return 0;
}
static bool
assert_link_limit_change_valid(struct intel_display *display,
const struct intel_link_bw_limits *old_limits,
const struct intel_link_bw_limits *new_limits)
{
bool bpps_changed = false;
enum pipe pipe;
/* FEC can't be forced off after it was forced on. */
if (drm_WARN_ON(display->drm,
(old_limits->force_fec_pipes & new_limits->force_fec_pipes) !=
old_limits->force_fec_pipes))
return false;
for_each_pipe(display, pipe) {
/* The bpp limit can only decrease. */
if (drm_WARN_ON(display->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;
}
/* At least one limit must change. */
if (drm_WARN_ON(display->drm,
!bpps_changed &&
new_limits->force_fec_pipes ==
old_limits->force_fec_pipes))
return false;
return true;
}
/**
* intel_link_bw_atomic_check - check display link states and set a fallback config if needed
* @state: atomic state
* @new_limits: link BW limits
*
* Check the configuration of all shared display links in @state and set new BW
* limits in @new_limits if there is a BW limitation.
*
* Returns:
* - 0 if the configuration is valid
* - %-EAGAIN, if the configuration is invalid and @new_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_link_bw_atomic_check(struct intel_atomic_state *state,
struct intel_link_bw_limits *new_limits)
{
struct intel_display *display = to_intel_display(state);
struct intel_link_bw_limits old_limits = *new_limits;
int ret;
ret = check_all_link_config(state, new_limits);
if (ret != -EAGAIN)
return ret;
if (!assert_link_limit_change_valid(display, &old_limits, new_limits))
return -EINVAL;
return -EAGAIN;
}
static int force_link_bpp_show(struct seq_file *m, void *data)
{
struct intel_connector *connector = m->private;
seq_printf(m, FXP_Q4_FMT "\n", FXP_Q4_ARGS(connector->link.force_bpp_x16));
return 0;
}
static int str_to_fxp_q4_nonneg_int(const char *str, int *val_x16)
{
unsigned int val;
int err;
err = kstrtouint(str, 10, &val);
if (err)
return err;
if (val > INT_MAX >> 4)
return -ERANGE;
*val_x16 = fxp_q4_from_int(val);
return 0;
}
/* modifies str */
static int str_to_fxp_q4_nonneg(char *str, int *val_x16)
{
const char *int_str;
char *frac_str;
int frac_digits;
int frac_val;
int err;
int_str = strim(str);
frac_str = strchr(int_str, '.');
if (frac_str)
*frac_str++ = '\0';
err = str_to_fxp_q4_nonneg_int(int_str, val_x16);
if (err)
return err;
if (!frac_str)
return 0;
/* prevent negative number/leading +- sign mark */
if (!isdigit(*frac_str))
return -EINVAL;
err = str_to_fxp_q4_nonneg_int(frac_str, &frac_val);
if (err)
return err;
frac_digits = strlen(frac_str);
if (frac_digits > intlog10(INT_MAX) >> 24 ||
frac_val > INT_MAX - int_pow(10, frac_digits) / 2)
return -ERANGE;
frac_val = DIV_ROUND_CLOSEST(frac_val, (int)int_pow(10, frac_digits));
if (*val_x16 > INT_MAX - frac_val)
return -ERANGE;
*val_x16 += frac_val;
return 0;
}
static int user_str_to_fxp_q4_nonneg(const char __user *ubuf, size_t len, int *val_x16)
{
char *kbuf;
int err;
kbuf = memdup_user_nul(ubuf, len);
if (IS_ERR(kbuf))
return PTR_ERR(kbuf);
err = str_to_fxp_q4_nonneg(kbuf, val_x16);
kfree(kbuf);
return err;
}
static bool connector_supports_dsc(struct intel_connector *connector)
{
struct intel_display *display = to_intel_display(connector);
switch (connector->base.connector_type) {
case DRM_MODE_CONNECTOR_eDP:
return intel_dp_has_dsc(connector);
case DRM_MODE_CONNECTOR_DisplayPort:
if (connector->mst.dp)
return HAS_DSC_MST(display);
return HAS_DSC(display);
default:
return false;
}
}
static ssize_t
force_link_bpp_write(struct file *file, const char __user *ubuf, size_t len, loff_t *offp)
{
struct seq_file *m = file->private_data;
struct intel_connector *connector = m->private;
struct intel_display *display = to_intel_display(connector);
int min_bpp;
int bpp_x16;
int err;
err = user_str_to_fxp_q4_nonneg(ubuf, len, &bpp_x16);
if (err)
return err;
/* TODO: Make the non-DSC min_bpp value connector specific. */
if (connector_supports_dsc(connector))
min_bpp = intel_dp_dsc_min_src_compressed_bpp();
else
min_bpp = intel_display_min_pipe_bpp();
if (bpp_x16 &&
(bpp_x16 < fxp_q4_from_int(min_bpp) ||
bpp_x16 > fxp_q4_from_int(intel_display_max_pipe_bpp(display))))
return -EINVAL;
err = drm_modeset_lock_single_interruptible(&display->drm->mode_config.connection_mutex);
if (err)
return err;
connector->link.force_bpp_x16 = bpp_x16;
drm_modeset_unlock(&display->drm->mode_config.connection_mutex);
*offp += len;
return len;
}
DEFINE_SHOW_STORE_ATTRIBUTE(force_link_bpp);
void intel_link_bw_connector_debugfs_add(struct intel_connector *connector)
{
struct intel_display *display = to_intel_display(connector);
struct dentry *root = connector->base.debugfs_entry;
switch (connector->base.connector_type) {
case DRM_MODE_CONNECTOR_DisplayPort:
case DRM_MODE_CONNECTOR_eDP:
break;
case DRM_MODE_CONNECTOR_VGA:
case DRM_MODE_CONNECTOR_SVIDEO:
case DRM_MODE_CONNECTOR_LVDS:
case DRM_MODE_CONNECTOR_DVID:
if (HAS_FDI(display))
break;
return;
case DRM_MODE_CONNECTOR_HDMIA:
if (HAS_FDI(display) && !HAS_DDI(display))
break;
return;
default:
return;
}
debugfs_create_file("intel_force_link_bpp", 0644, root,
connector, &force_link_bpp_fops);
}