diff options
Diffstat (limited to 'drivers/gpu/drm/i915/display/intel_link_bw.c')
-rw-r--r-- | drivers/gpu/drm/i915/display/intel_link_bw.c | 239 |
1 files changed, 232 insertions, 7 deletions
diff --git a/drivers/gpu/drm/i915/display/intel_link_bw.c b/drivers/gpu/drm/i915/display/intel_link_bw.c index a10cd3992607..3caef7f9c7c4 100644 --- a/drivers/gpu/drm/i915/display/intel_link_bw.c +++ b/drivers/gpu/drm/i915/display/intel_link_bw.c @@ -3,6 +3,11 @@ * 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> @@ -10,11 +15,33 @@ #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 @@ -31,9 +58,10 @@ void intel_link_bw_init_limits(struct intel_atomic_state *state, 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, - intel_crtc_for_pipe(display, pipe)); + 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; @@ -42,15 +70,19 @@ void intel_link_bw_init_limits(struct intel_atomic_state *state, } 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 + * __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, @@ -64,10 +96,11 @@ void intel_link_bw_init_limits(struct intel_atomic_state *state, * - %-ENOSPC if no pipe can further reduce its link bpp * - Other negative error, if modesetting the selected pipe failed */ -int intel_link_bw_reduce_bpp(struct intel_atomic_state *state, - struct intel_link_bw_limits *limits, - u8 pipe_mask, - const char *reason) +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; @@ -97,6 +130,10 @@ int intel_link_bw_reduce_bpp(struct intel_atomic_state *state, */ 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; @@ -112,6 +149,21 @@ int intel_link_bw_reduce_bpp(struct intel_atomic_state *state, 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 @@ -245,3 +297,176 @@ int intel_link_bw_atomic_check(struct intel_atomic_state *state, 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); +} |