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.

Signed-off-by: Imre Deak <[email protected]>
---
 .../drm/i915/display/intel_display_types.h    |   4 +
 drivers/gpu/drm/i915/display/intel_link_bw.c  | 204 +++++++++++++++++-
 drivers/gpu/drm/i915/display/intel_link_bw.h  |   2 +
 3 files changed, 203 insertions(+), 7 deletions(-)

diff --git a/drivers/gpu/drm/i915/display/intel_display_types.h 
b/drivers/gpu/drm/i915/display/intel_display_types.h
index 94468a9d2e0d3..6862cb4885b18 100644
--- a/drivers/gpu/drm/i915/display/intel_display_types.h
+++ b/drivers/gpu/drm/i915/display/intel_display_types.h
@@ -550,6 +550,10 @@ struct intel_connector {
                struct intel_dp *dp;
        } mst;
 
+       struct {
+               int force_bpp_x16;
+       } link;
+
        /* Work struct to schedule a uevent on link train failure */
        struct work_struct modeset_retry_work;
 
diff --git a/drivers/gpu/drm/i915/display/intel_link_bw.c 
b/drivers/gpu/drm/i915/display/intel_link_bw.c
index a10cd39926075..2a05fac53aa78 100644
--- a/drivers/gpu/drm/i915/display/intel_link_bw.c
+++ b/drivers/gpu/drm/i915/display/intel_link_bw.c
@@ -3,6 +3,9 @@
  * Copyright © 2023 Intel Corporation
  */
 
+#include <linux/debugfs.h>
+#include <linux/math.h>
+
 #include <drm/drm_fixed.h>
 #include <drm/drm_print.h>
 
@@ -10,11 +13,35 @@
 #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 drm_connector_state *conn_state;
+       struct drm_connector *_connector;
+       int force_bpp_x16 = INT_MAX;
+       int i;
+
+       for_each_new_connector_in_state(&state->base, _connector, conn_state, 
i) {
+               struct intel_connector *connector = 
to_intel_connector(_connector);
+
+               if (conn_state->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,141 @@ 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_uint(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(char *str, int *val_x16)
+{
+       const char *int_str;
+       char *frac_str;
+       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_uint(int_str, val_x16);
+       if (err)
+               return err;
+
+       if (!frac_str)
+               return 0;
+
+       if (*frac_str == '+')   /* otherwise valid in front of an unsigned 
integer */
+               return -EINVAL;
+
+       err = str_to_fxp_q4_uint(frac_str, &frac_val);
+       if (err)
+               return err;
+
+       *val_x16 += DIV_ROUND_CLOSEST(frac_val, int_pow(10, strlen(frac_str)));
+
+       return 0;
+}
+
+static int user_str_to_fxp_q4(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(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(ubuf, len, &bpp_x16);
+       if (err)
+               return err;
+
+       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 dentry *root = connector->base.debugfs_entry;
+
+       debugfs_create_file("i915_force_link_bpp", 0644, root,
+                           connector, &force_link_bpp_fops);
+}
diff --git a/drivers/gpu/drm/i915/display/intel_link_bw.h 
b/drivers/gpu/drm/i915/display/intel_link_bw.h
index e69049cf178f6..b499042e62b13 100644
--- a/drivers/gpu/drm/i915/display/intel_link_bw.h
+++ b/drivers/gpu/drm/i915/display/intel_link_bw.h
@@ -11,6 +11,7 @@
 #include "intel_display_limits.h"
 
 struct intel_atomic_state;
+struct intel_connector;
 struct intel_crtc_state;
 
 struct intel_link_bw_limits {
@@ -32,5 +33,6 @@ bool intel_link_bw_set_bpp_limit_for_pipe(struct 
intel_atomic_state *state,
                                          enum pipe pipe);
 int intel_link_bw_atomic_check(struct intel_atomic_state *state,
                               struct intel_link_bw_limits *new_limits);
+void intel_link_bw_connector_debugfs_add(struct intel_connector *connector);
 
 #endif
-- 
2.44.2

Reply via email to