This includes RGB, YUV420, YUV444 and Auto. Auto will pick RGB, unless
the mode being asked for is YUV420-only, in which case it picks YUV420.

Should the explicitly requested color format not be supported by the
sink, then an error is returned to userspace, so that it can make a
better choice.

Co-developed-by: Werner Sembach <[email protected]>
Signed-off-by: Werner Sembach <[email protected]>
Co-developed-by: Andri Yngvason <[email protected]>
Signed-off-by: Andri Yngvason <[email protected]>
Co-developed-by: Marius Vlad <[email protected]>
Signed-off-by: Marius Vlad <[email protected]>
Signed-off-by: Nicolas Frattaroli <[email protected]>
---
 drivers/gpu/drm/i915/display/intel_connector.c | 11 ++++
 drivers/gpu/drm/i915/display/intel_connector.h |  1 +
 drivers/gpu/drm/i915/display/intel_dp.c        | 71 ++++++++++++++++++++-----
 drivers/gpu/drm/i915/display/intel_dp.h        |  4 ++
 drivers/gpu/drm/i915/display/intel_dp_mst.c    | 55 +++++++++++++++++++-
 drivers/gpu/drm/i915/display/intel_hdmi.c      | 72 ++++++++++++++++++++------
 6 files changed, 185 insertions(+), 29 deletions(-)

diff --git a/drivers/gpu/drm/i915/display/intel_connector.c 
b/drivers/gpu/drm/i915/display/intel_connector.c
index 7ef9338d67ab..fb443aa7e0c0 100644
--- a/drivers/gpu/drm/i915/display/intel_connector.c
+++ b/drivers/gpu/drm/i915/display/intel_connector.c
@@ -38,6 +38,10 @@
 #include "intel_hdcp.h"
 #include "intel_panel.h"
 
+static const u32 supported_colorformats = BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444) |
+                                         BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR444) 
|
+                                         BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR420);
+
 static void intel_connector_modeset_retry_work_fn(struct work_struct *work)
 {
        struct intel_connector *connector = container_of(work, 
typeof(*connector),
@@ -321,6 +325,13 @@ intel_attach_dp_colorspace_property(struct drm_connector 
*connector)
                drm_connector_attach_colorspace_property(connector);
 }
 
+void
+intel_attach_colorformat_property(struct drm_connector *connector)
+{
+       if (!drm_mode_create_color_format_property(connector, 
supported_colorformats))
+               drm_connector_attach_color_format_property(connector);
+}
+
 void
 intel_attach_scaling_mode_property(struct drm_connector *connector)
 {
diff --git a/drivers/gpu/drm/i915/display/intel_connector.h 
b/drivers/gpu/drm/i915/display/intel_connector.h
index 0aa86626e646..fe6149d1d559 100644
--- a/drivers/gpu/drm/i915/display/intel_connector.h
+++ b/drivers/gpu/drm/i915/display/intel_connector.h
@@ -31,6 +31,7 @@ void intel_attach_broadcast_rgb_property(struct drm_connector 
*connector);
 void intel_attach_aspect_ratio_property(struct drm_connector *connector);
 void intel_attach_hdmi_colorspace_property(struct drm_connector *connector);
 void intel_attach_dp_colorspace_property(struct drm_connector *connector);
+void intel_attach_colorformat_property(struct drm_connector *connector);
 void intel_attach_scaling_mode_property(struct drm_connector *connector);
 void intel_connector_queue_modeset_retry_work(struct intel_connector 
*connector);
 void intel_connector_cancel_modeset_retry_work(struct intel_connector 
*connector);
diff --git a/drivers/gpu/drm/i915/display/intel_dp.c 
b/drivers/gpu/drm/i915/display/intel_dp.c
index 025e906b63a9..926df8b56a67 100644
--- a/drivers/gpu/drm/i915/display/intel_dp.c
+++ b/drivers/gpu/drm/i915/display/intel_dp.c
@@ -1178,7 +1178,7 @@ dfp_can_convert(struct intel_dp *intel_dp,
        return false;
 }
 
-static enum intel_output_format
+enum intel_output_format
 intel_dp_output_format(struct intel_connector *connector,
                       enum intel_output_format sink_format)
 {
@@ -3324,17 +3324,24 @@ intel_dp_compute_output_format(struct intel_encoder 
*encoder,
        struct intel_connector *connector = intel_dp->attached_connector;
        const struct drm_display_info *info = &connector->base.display_info;
        const struct drm_display_mode *adjusted_mode = 
&crtc_state->hw.adjusted_mode;
-       bool ycbcr_420_only;
+       enum drm_connector_color_format actual_format;
        int ret;
 
-       ycbcr_420_only = drm_mode_is_420_only(info, adjusted_mode);
+       if ((conn_state->color_format == DRM_CONNECTOR_COLOR_FORMAT_YCBCR420 &&
+            drm_mode_is_420(info, adjusted_mode)) ||
+           (conn_state->color_format == DRM_CONNECTOR_COLOR_FORMAT_AUTO &&
+            drm_mode_is_420_only(info, adjusted_mode)))
+               crtc_state->sink_format = INTEL_OUTPUT_FORMAT_YCBCR420;
+       else if (conn_state->color_format == 
DRM_CONNECTOR_COLOR_FORMAT_YCBCR444)
+               crtc_state->sink_format = INTEL_OUTPUT_FORMAT_YCBCR444;
+       else
+               crtc_state->sink_format = INTEL_OUTPUT_FORMAT_RGB;
 
-       if (ycbcr_420_only && !connector->base.ycbcr_420_allowed) {
+       if (crtc_state->sink_format == INTEL_OUTPUT_FORMAT_YCBCR420 &&
+           !connector->base.ycbcr_420_allowed) {
                drm_dbg_kms(display->drm,
-                           "YCbCr 4:2:0 mode but YCbCr 4:2:0 output not 
possible. Falling back to RGB.\n");
-               crtc_state->sink_format = INTEL_OUTPUT_FORMAT_RGB;
-       } else {
-               crtc_state->sink_format = intel_dp_sink_format(connector, 
adjusted_mode);
+                       "YCbCr 4:2:0 mode requested but unsupported by 
connector.\n");
+               return -EINVAL;
        }
 
        crtc_state->output_format = intel_dp_output_format(connector, 
crtc_state->sink_format);
@@ -3342,9 +3349,20 @@ intel_dp_compute_output_format(struct intel_encoder 
*encoder,
        ret = intel_dp_compute_link_config(encoder, crtc_state, conn_state,
                                           respect_downstream_limits);
        if (ret) {
-               if (crtc_state->sink_format == INTEL_OUTPUT_FORMAT_YCBCR420 ||
-                   !connector->base.ycbcr_420_allowed ||
-                   !drm_mode_is_420_also(info, adjusted_mode))
+               /*
+                * If no valid link config can be found due to bandwidth 
constraints,
+                * degrade from RGB/YCbCr 4:4:4 to YCbCr 4:2:0 if permitted by
+                * the source and sink.
+                */
+               if (!connector->base.ycbcr_420_allowed)
+                       return ret;
+               /* No point in trying YCbCr420 a second time. */
+               if (crtc_state->sink_format == INTEL_OUTPUT_FORMAT_YCBCR420)
+                       return ret;
+               if (!drm_mode_is_420(info, adjusted_mode))
+                       return ret;
+               /* If a non-AUTO color format is chosen, don't fall back. */
+               if (conn_state->color_format)
                        return ret;
 
                crtc_state->sink_format = INTEL_OUTPUT_FORMAT_YCBCR420;
@@ -3352,9 +3370,36 @@ intel_dp_compute_output_format(struct intel_encoder 
*encoder,
                                                                   
crtc_state->sink_format);
                ret = intel_dp_compute_link_config(encoder, crtc_state, 
conn_state,
                                                   respect_downstream_limits);
+               if (ret)
+                       return ret;
        }
 
-       return ret;
+       if (conn_state->color_format == DRM_CONNECTOR_COLOR_FORMAT_AUTO)
+               return 0;
+
+       switch (crtc_state->sink_format) {
+       case INTEL_OUTPUT_FORMAT_RGB:
+               actual_format = DRM_CONNECTOR_COLOR_FORMAT_RGB444;
+               break;
+       case INTEL_OUTPUT_FORMAT_YCBCR444:
+               actual_format = DRM_CONNECTOR_COLOR_FORMAT_YCBCR444;
+               break;
+       case INTEL_OUTPUT_FORMAT_YCBCR420:
+               actual_format = DRM_CONNECTOR_COLOR_FORMAT_YCBCR420;
+               break;
+       default:
+               drm_dbg_kms(display->drm, "Intel format %d has no connector 
equivalent\n",
+                           crtc_state->sink_format);
+               return -EINVAL;
+       }
+
+       if (actual_format != conn_state->color_format) {
+               drm_dbg_kms(display->drm, "Requested connector color format %d, 
got %d\n",
+                           conn_state->color_format, actual_format);
+               return -EINVAL;
+       }
+
+       return 0;
 }
 
 void
@@ -6828,6 +6873,8 @@ intel_dp_add_properties(struct intel_dp *intel_dp, struct 
drm_connector *_connec
                intel_attach_dp_colorspace_property(&connector->base);
        }
 
+       intel_attach_colorformat_property(&connector->base);
+
        if (intel_dp_has_gamut_metadata_dip(&dp_to_dig_port(intel_dp)->base))
                
drm_connector_attach_hdr_output_metadata_property(&connector->base);
 
diff --git a/drivers/gpu/drm/i915/display/intel_dp.h 
b/drivers/gpu/drm/i915/display/intel_dp.h
index b0bbd5981f57..ebc63156e73f 100644
--- a/drivers/gpu/drm/i915/display/intel_dp.h
+++ b/drivers/gpu/drm/i915/display/intel_dp.h
@@ -203,6 +203,10 @@ bool intel_dp_compute_config_limits(struct intel_dp 
*intel_dp,
 void intel_dp_get_dsc_sink_cap(u8 dpcd_rev,
                               const struct drm_dp_desc *desc, bool is_branch,
                               struct intel_connector *connector);
+enum intel_output_format
+intel_dp_output_format(struct intel_connector *connector,
+                      enum intel_output_format sink_format);
+
 bool intel_dp_has_gamut_metadata_dip(struct intel_encoder *encoder);
 
 bool intel_dp_link_params_valid(struct intel_dp *intel_dp, int link_rate,
diff --git a/drivers/gpu/drm/i915/display/intel_dp_mst.c 
b/drivers/gpu/drm/i915/display/intel_dp_mst.c
index fb5396a46d1b..5c95d718b5b3 100644
--- a/drivers/gpu/drm/i915/display/intel_dp_mst.c
+++ b/drivers/gpu/drm/i915/display/intel_dp_mst.c
@@ -702,10 +702,13 @@ static int mst_stream_compute_config(struct intel_encoder 
*encoder,
        struct intel_atomic_state *state = 
to_intel_atomic_state(conn_state->state);
        struct intel_crtc *crtc = to_intel_crtc(pipe_config->uapi.crtc);
        struct intel_dp *intel_dp = to_primary_dp(encoder);
+       enum drm_connector_color_format actual_format;
        struct intel_connector *connector =
                to_intel_connector(conn_state->connector);
        const struct drm_display_mode *adjusted_mode =
                &pipe_config->hw.adjusted_mode;
+       const struct drm_display_info *info =
+               &connector->base.display_info;
        int num_joined_pipes;
        int ret = -EINVAL;
 
@@ -716,10 +719,53 @@ static int mst_stream_compute_config(struct intel_encoder 
*encoder,
        if (adjusted_mode->flags & DRM_MODE_FLAG_DBLSCAN)
                return -EINVAL;
 
-       pipe_config->sink_format = INTEL_OUTPUT_FORMAT_RGB;
-       pipe_config->output_format = INTEL_OUTPUT_FORMAT_RGB;
+       if ((conn_state->color_format == DRM_CONNECTOR_COLOR_FORMAT_YCBCR420 &&
+            drm_mode_is_420(info, adjusted_mode)) ||
+           (conn_state->color_format == DRM_CONNECTOR_COLOR_FORMAT_AUTO &&
+            drm_mode_is_420_only(info, adjusted_mode))) {
+               pipe_config->sink_format = INTEL_OUTPUT_FORMAT_YCBCR420;
+       } else if (conn_state->color_format == 
DRM_CONNECTOR_COLOR_FORMAT_YCBCR444) {
+               pipe_config->sink_format = INTEL_OUTPUT_FORMAT_YCBCR444;
+       } else if (conn_state->color_format == DRM_CONNECTOR_COLOR_FORMAT_AUTO 
||
+                  conn_state->color_format == 
DRM_CONNECTOR_COLOR_FORMAT_RGB444) {
+               pipe_config->sink_format = INTEL_OUTPUT_FORMAT_RGB;
+       } else {
+               drm_dbg_kms(display->drm, "Requested format %d unsupported.\n",
+                           conn_state->color_format);
+               return -EINVAL;
+       }
+
+       if (pipe_config->sink_format == INTEL_OUTPUT_FORMAT_YCBCR420 &&
+           !connector->base.ycbcr_420_allowed) {
+               drm_dbg_kms(display->drm,
+                           "YCbCr 4:2:0 mode requested but unsupported by 
connector.\n");
+               return -EINVAL;
+       }
+
+       pipe_config->output_format = intel_dp_output_format(connector, 
pipe_config->sink_format);
        pipe_config->has_pch_encoder = false;
 
+       /* Validate that the right format was picked if explicitly requested */
+       if (conn_state->color_format != DRM_CONNECTOR_COLOR_FORMAT_AUTO) {
+               switch (pipe_config->output_format) {
+               case INTEL_OUTPUT_FORMAT_RGB:
+                       actual_format = DRM_CONNECTOR_COLOR_FORMAT_RGB444;
+                       break;
+               case INTEL_OUTPUT_FORMAT_YCBCR444:
+                       actual_format = DRM_CONNECTOR_COLOR_FORMAT_YCBCR444;
+                       break;
+               case INTEL_OUTPUT_FORMAT_YCBCR420:
+                       actual_format = DRM_CONNECTOR_COLOR_FORMAT_YCBCR420;
+                       break;
+               }
+
+               if (actual_format != conn_state->color_format) {
+                       drm_dbg_kms(display->drm, "Requested connector color 
format %d, got %d\n",
+                                   conn_state->color_format, actual_format);
+                       return -EINVAL;
+               }
+       }
+
        for_each_joiner_candidate(connector, adjusted_mode, num_joined_pipes) {
                if (num_joined_pipes > 1)
                        pipe_config->joiner_pipes = GENMASK(crtc->pipe + 
num_joined_pipes - 1,
@@ -1690,6 +1736,11 @@ static int mst_topology_add_connector_properties(struct 
intel_dp *intel_dp,
        if (connector->base.max_bpc_property)
                drm_connector_attach_max_bpc_property(&connector->base, 6, 12);
 
+       connector->base.color_format_property =
+               intel_dp->attached_connector->base.color_format_property;
+       if (connector->base.color_format_property)
+               intel_attach_colorformat_property(&connector->base);
+
        return drm_connector_set_path_property(&connector->base, pathprop);
 }
 
diff --git a/drivers/gpu/drm/i915/display/intel_hdmi.c 
b/drivers/gpu/drm/i915/display/intel_hdmi.c
index 05e898d10a2b..6b685d9a458e 100644
--- a/drivers/gpu/drm/i915/display/intel_hdmi.c
+++ b/drivers/gpu/drm/i915/display/intel_hdmi.c
@@ -2270,33 +2270,73 @@ static int intel_hdmi_compute_output_format(struct 
intel_encoder *encoder,
        struct intel_connector *connector = 
to_intel_connector(conn_state->connector);
        const struct drm_display_mode *adjusted_mode = 
&crtc_state->hw.adjusted_mode;
        const struct drm_display_info *info = &connector->base.display_info;
-       bool ycbcr_420_only = drm_mode_is_420_only(info, adjusted_mode);
+       enum drm_connector_color_format req_fmt = conn_state->color_format;
+       enum drm_connector_color_format actual_fmt;
        int ret;
 
-       crtc_state->sink_format =
-               intel_hdmi_sink_format(crtc_state, connector, ycbcr_420_only);
-
-       if (ycbcr_420_only && crtc_state->sink_format != 
INTEL_OUTPUT_FORMAT_YCBCR420) {
-               drm_dbg_kms(display->drm,
-                           "YCbCr 4:2:0 mode but YCbCr 4:2:0 output not 
possible. Falling back to RGB.\n");
-               crtc_state->sink_format = INTEL_OUTPUT_FORMAT_RGB;
-       }
+       if (!req_fmt)
+               crtc_state->sink_format =
+                       intel_hdmi_sink_format(crtc_state, connector,
+                                              drm_mode_is_420_only(info, 
adjusted_mode));
+       else if (req_fmt == DRM_CONNECTOR_COLOR_FORMAT_YCBCR444)
+               crtc_state->sink_format = INTEL_OUTPUT_FORMAT_YCBCR444;
+       else
+               crtc_state->sink_format =
+                       intel_hdmi_sink_format(crtc_state, connector,
+                                              req_fmt == 
DRM_CONNECTOR_COLOR_FORMAT_YCBCR420);
 
        crtc_state->output_format = intel_hdmi_output_format(crtc_state);
        ret = intel_hdmi_compute_clock(encoder, crtc_state, 
respect_downstream_limits);
        if (ret) {
-               if (crtc_state->sink_format == INTEL_OUTPUT_FORMAT_YCBCR420 ||
-                   !crtc_state->has_hdmi_sink ||
-                   !connector->base.ycbcr_420_allowed ||
-                   !drm_mode_is_420_also(info, adjusted_mode))
+               /*
+                * If no valid link config can be found due to bandwidth 
constraints,
+                * degrade from RGB/YCbCr 4:4:4 to YCbCr 4:2:0 if permitted by
+                * the source and sink.
+                */
+               if (!connector->base.ycbcr_420_allowed)
+                       return ret;
+               /* No point in trying YCbCr420 a second time. */
+               if (crtc_state->sink_format == INTEL_OUTPUT_FORMAT_YCBCR420)
+                       return ret;
+               if (!drm_mode_is_420(info, adjusted_mode))
+                       return ret;
+               /* If a non-AUTO color format is chosen, don't fall back. */
+               if (req_fmt)
                        return ret;
 
                crtc_state->sink_format = INTEL_OUTPUT_FORMAT_YCBCR420;
                crtc_state->output_format = 
intel_hdmi_output_format(crtc_state);
                ret = intel_hdmi_compute_clock(encoder, crtc_state, 
respect_downstream_limits);
+               if (ret)
+                       return ret;
        }
 
-       return ret;
+       if (req_fmt == DRM_CONNECTOR_COLOR_FORMAT_AUTO)
+               return 0;
+
+       switch (crtc_state->sink_format) {
+       case INTEL_OUTPUT_FORMAT_RGB:
+               actual_fmt = DRM_CONNECTOR_COLOR_FORMAT_RGB444;
+               break;
+       case INTEL_OUTPUT_FORMAT_YCBCR444:
+               actual_fmt = DRM_CONNECTOR_COLOR_FORMAT_YCBCR444;
+               break;
+       case INTEL_OUTPUT_FORMAT_YCBCR420:
+               actual_fmt = DRM_CONNECTOR_COLOR_FORMAT_YCBCR420;
+               break;
+       default:
+               drm_dbg_kms(display->drm, "Intel format %d has no connector 
equivalent\n",
+                           crtc_state->sink_format);
+               return -EINVAL;
+       }
+
+       if (req_fmt != actual_fmt) {
+               drm_dbg_kms(display->drm, "Requested connector color format %d, 
got %d\n",
+                           req_fmt, actual_fmt);
+               return -EINVAL;
+       }
+
+       return 0;
 }
 
 static bool intel_hdmi_is_cloned(const struct intel_crtc_state *crtc_state)
@@ -2689,8 +2729,10 @@ intel_hdmi_add_properties(struct intel_hdmi *intel_hdmi, 
struct drm_connector *_
        if (DISPLAY_VER(display) >= 10)
                
drm_connector_attach_hdr_output_metadata_property(&connector->base);
 
-       if (!HAS_GMCH(display))
+       if (!HAS_GMCH(display)) {
                drm_connector_attach_max_bpc_property(&connector->base, 8, 12);
+               intel_attach_colorformat_property(&connector->base);
+       }
 }
 
 /*

-- 
2.53.0

Reply via email to