Add a new general DRM property named "color format" which can be used by
userspace to request the display driver to output a particular color
format.

Possible string values for the new enum property are:
 - "AUTO" (setup by default, driver internally picks the color format)
 - "RGB"
 - "YUV 4:4:4"
 - "YUV 4:2:2"
 - "YUV 4:2:0"

Drivers should advertise from this list the formats they support in an
optimistic best-case scenario. EDID data from the sink can then be used
in the kernel's atomic check phase to restrict this set of formats, as
well as by userspace to make a correct choice in the first place.

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]>
Signed-off-by: Marius Vlad <[email protected]>
Reviewed-by: Maxime Ripard <[email protected]>
Signed-off-by: Nicolas Frattaroli <[email protected]>
---
 Documentation/gpu/drm-kms.rst       |   6 ++
 drivers/gpu/drm/drm_atomic_helper.c |   5 ++
 drivers/gpu/drm/drm_atomic_uapi.c   |  11 +++
 drivers/gpu/drm/drm_connector.c     | 155 ++++++++++++++++++++++++++++++++++++
 include/drm/drm_connector.h         | 108 +++++++++++++++++++++++++
 5 files changed, 285 insertions(+)

diff --git a/Documentation/gpu/drm-kms.rst b/Documentation/gpu/drm-kms.rst
index 2292e65f044c..da5133b672d3 100644
--- a/Documentation/gpu/drm-kms.rst
+++ b/Documentation/gpu/drm-kms.rst
@@ -599,6 +599,12 @@ Color Management Properties
 .. kernel-doc:: drivers/gpu/drm/drm_color_mgmt.c
    :doc: overview
 
+Color Format Property
+---------------------
+
+.. kernel-doc:: drivers/gpu/drm/drm_connector.c
+   :doc: Color format
+
 Tile Group Property
 -------------------
 
diff --git a/drivers/gpu/drm/drm_atomic_helper.c 
b/drivers/gpu/drm/drm_atomic_helper.c
index 2e7e8ed8ad7f..0de9748a82c6 100644
--- a/drivers/gpu/drm/drm_atomic_helper.c
+++ b/drivers/gpu/drm/drm_atomic_helper.c
@@ -737,6 +737,11 @@ drm_atomic_helper_check_modeset(struct drm_device *dev,
                        if (old_connector_state->max_requested_bpc !=
                            new_connector_state->max_requested_bpc)
                                new_crtc_state->connectors_changed = true;
+
+                       if (old_connector_state->color_format !=
+                           new_connector_state->color_format)
+                               new_crtc_state->connectors_changed = true;
+
                }
 
                if (funcs->atomic_check)
diff --git a/drivers/gpu/drm/drm_atomic_uapi.c 
b/drivers/gpu/drm/drm_atomic_uapi.c
index 5bd5bf6661df..dee510c85e59 100644
--- a/drivers/gpu/drm/drm_atomic_uapi.c
+++ b/drivers/gpu/drm/drm_atomic_uapi.c
@@ -935,6 +935,15 @@ static int drm_atomic_connector_set_property(struct 
drm_connector *connector,
                state->privacy_screen_sw_state = val;
        } else if (property == connector->broadcast_rgb_property) {
                state->hdmi.broadcast_rgb = val;
+       } else if (property == connector->color_format_property) {
+               if (val > INT_MAX || !drm_connector_color_format_valid(val)) {
+                       drm_dbg_atomic(connector->dev,
+                                      "[CONNECTOR:%d:%s] unknown color format 
%llu\n",
+                                      connector->base.id, connector->name, 
val);
+                       return -EINVAL;
+               }
+
+               state->color_format = val;
        } else if (connector->funcs->atomic_set_property) {
                return connector->funcs->atomic_set_property(connector,
                                state, property, val);
@@ -1020,6 +1029,8 @@ drm_atomic_connector_get_property(struct drm_connector 
*connector,
                *val = state->privacy_screen_sw_state;
        } else if (property == connector->broadcast_rgb_property) {
                *val = state->hdmi.broadcast_rgb;
+       } else if (property == connector->color_format_property) {
+               *val = state->color_format;
        } else if (connector->funcs->atomic_get_property) {
                return connector->funcs->atomic_get_property(connector,
                                state, property, val);
diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c
index 47dc53c4a738..1c4f3ff7d84f 100644
--- a/drivers/gpu/drm/drm_connector.c
+++ b/drivers/gpu/drm/drm_connector.c
@@ -1388,6 +1388,18 @@ static const u32 hdmi_colorspaces =
        BIT(DRM_MODE_COLORIMETRY_DCI_P3_RGB_D65) |
        BIT(DRM_MODE_COLORIMETRY_DCI_P3_RGB_THEATER);
 
+static const u32 hdmi_colorformats =
+       BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444) |
+       BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR444) |
+       BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422) |
+       BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR420);
+
+static const u32 dp_colorformats =
+       BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444) |
+       BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR444) |
+       BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422) |
+       BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR420);
+
 /*
  * As per DP 1.4a spec, 2.2.5.7.5 VSC SDP Payload for Pixel 
Encoding/Colorimetry
  * Format Table 2-120
@@ -2940,6 +2952,149 @@ int drm_connector_attach_colorspace_property(struct 
drm_connector *connector)
 }
 EXPORT_SYMBOL(drm_connector_attach_colorspace_property);
 
+/**
+ * DOC: Color format
+ *
+ * The connector "color format" property allows userspace to request a specific
+ * color model on the output of the connector. Not all values listed by the
+ * property are guaranteed to work for every sink; rather, it is an optimistic
+ * listing of color formats that the source could output depending on
+ * circumstances.
+ *
+ * Whether it actually can output a certain color format is determined during
+ * the atomic check phase. Consequently, a userspace application that sets the
+ * color format to a value other than "AUTO" should check whether its atomic
+ * commit succeeded.
+ *
+ * Possible values for "color format":
+ *
+ * "AUTO":
+ *     The driver or display protocol helpers should pick a suitable color
+ *     format. All implementations of a specific display protocol will behave
+ *     the same way with "AUTO", but different display protocols do not
+ *     necessarily have the same "AUTO" semantics.
+ *
+ *     For HDMI connectors, "AUTO" picks RGB, but falls back to YUV 4:2:0 if
+ *     the bandwidth required for full-scale RGB is not available, or the mode
+ *     is YUV 4:2:0-only, as long as the mode, source, and sink all support
+ *     YUV 4:2:0.
+ * "RGB":
+ *     RGB output format. The quantization range (limited/full) depends on the
+ *     value of the "Broadcast RGB" property if it is present on the connector.
+ * "YUV 4:4:4":
+ *     YUV 4:4:4 (a.k.a. YCbCr 4:4:4) output format. Chroma is not subsampled.
+ *     The quantization range defaults to limited.
+ * "YUV 4:2:2":
+ *     YUV 4:2:2 (a.k.a. YCbCr 4:2:2) output format. Chroma has half the
+ *     horizontal resolution of Luma. The quantization range defaults to
+ *     limited.
+ * "YUV 4:2:0":
+ *     YUV 4:2:0 (a.k.a. YCbCr 4:2:0) output format. Chroma has half the
+ *     horizontal and vertical resolution of Luma. The quantization range
+ *     defaults to limited.
+ *
+ * A sink may only support some color formats in specific modes and at specific
+ * bit depths. The atomic modesetting API should be used to set a working
+ * configuration in one go, as an unsupported combination of parameters is
+ * rejected.
+ */
+
+/**
+ * drm_connector_attach_color_format_property - create and attach color format 
property
+ * @connector: connector to create the color format property on
+ * @supported_color_formats: bitmask of bit-shifted &enum 
drm_output_color_format
+ *                           values the connector supports
+ *
+ * Called by a driver to create a color format property. The property is
+ * attached to the connector automatically on success.
+ *
+ * @supported_color_formats should only include color formats the connector
+ * type can actually support.
+ *
+ * Returns:
+ * 0 on success, negative errno on error
+ */
+int drm_connector_attach_color_format_property(struct drm_connector *connector,
+                                              unsigned long 
supported_color_formats)
+{
+       struct drm_device *dev = connector->dev;
+       struct drm_prop_enum_list enum_list[DRM_CONNECTOR_COLOR_FORMAT_COUNT];
+       unsigned int i = 0;
+       unsigned long fmt;
+
+       if (connector->color_format_property)
+               return 0;
+
+       if (!supported_color_formats) {
+               drm_err(dev, "No supported color formats provided on 
[CONNECTOR:%d:%s]\n",
+                       connector->base.id, connector->name);
+               return -EINVAL;
+       }
+
+       if (supported_color_formats & ~GENMASK(DRM_OUTPUT_COLOR_FORMAT_COUNT - 
1, 0)) {
+               drm_err(dev, "Unknown color formats provided on 
[CONNECTOR:%d:%s]\n",
+                       connector->base.id, connector->name);
+               return -EINVAL;
+       }
+
+       switch (connector->connector_type) {
+       case DRM_MODE_CONNECTOR_HDMIA:
+       case DRM_MODE_CONNECTOR_HDMIB:
+               if (supported_color_formats & ~hdmi_colorformats) {
+                       drm_err(dev, "Color formats not allowed for HDMI on 
[CONNECTOR:%d:%s]\n",
+                               connector->base.id, connector->name);
+                       return -EINVAL;
+               }
+               break;
+       case DRM_MODE_CONNECTOR_DisplayPort:
+       case DRM_MODE_CONNECTOR_eDP:
+               if (supported_color_formats & ~dp_colorformats) {
+                       drm_err(dev, "Color formats not allowed for DP on 
[CONNECTOR:%d:%s]\n",
+                               connector->base.id, connector->name);
+                       return -EINVAL;
+               }
+               break;
+       }
+
+       enum_list[0].name = "AUTO";
+       enum_list[0].type = DRM_CONNECTOR_COLOR_FORMAT_AUTO;
+
+       for_each_set_bit(fmt, &supported_color_formats, 
DRM_OUTPUT_COLOR_FORMAT_COUNT) {
+               switch (fmt) {
+               case DRM_OUTPUT_COLOR_FORMAT_RGB444:
+                       enum_list[++i].type = DRM_CONNECTOR_COLOR_FORMAT_RGB444;
+                       break;
+               case DRM_OUTPUT_COLOR_FORMAT_YCBCR444:
+                       enum_list[++i].type = 
DRM_CONNECTOR_COLOR_FORMAT_YCBCR444;
+                       break;
+               case DRM_OUTPUT_COLOR_FORMAT_YCBCR422:
+                       enum_list[++i].type = 
DRM_CONNECTOR_COLOR_FORMAT_YCBCR422;
+                       break;
+               case DRM_OUTPUT_COLOR_FORMAT_YCBCR420:
+                       enum_list[++i].type = 
DRM_CONNECTOR_COLOR_FORMAT_YCBCR420;
+                       break;
+               default:
+                       drm_warn(dev, "Unknown supported format %ld on 
[CONNECTOR:%d:%s]\n",
+                                fmt, connector->base.id, connector->name);
+                       continue;
+               }
+               enum_list[i].name = 
drm_hdmi_connector_get_output_format_name(fmt);
+       }
+
+       connector->color_format_property =
+               drm_property_create_enum(dev, DRM_MODE_PROP_ENUM, "color 
format",
+                                        enum_list, i + 1);
+
+       if (!connector->color_format_property)
+               return -ENOMEM;
+
+       drm_object_attach_property(&connector->base, 
connector->color_format_property,
+                                  DRM_CONNECTOR_COLOR_FORMAT_AUTO);
+
+       return 0;
+}
+EXPORT_SYMBOL(drm_connector_attach_color_format_property);
+
 /**
  * drm_connector_atomic_hdr_metadata_equal - checks if the hdr metadata changed
  * @old_state: old connector state to compare
diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
index f83f28cae207..620f700fbe76 100644
--- a/include/drm/drm_connector.h
+++ b/include/drm/drm_connector.h
@@ -571,14 +571,106 @@ enum drm_colorspace {
  *   YCbCr 4:2:2 output format (ie. with horizontal subsampling)
  * @DRM_OUTPUT_COLOR_FORMAT_YCBCR420:
  *   YCbCr 4:2:0 output format (ie. with horizontal and vertical subsampling)
+ * @DRM_OUTPUT_COLOR_FORMAT_COUNT:
+ *   Number of valid output color format values in this enum
  */
 enum drm_output_color_format {
        DRM_OUTPUT_COLOR_FORMAT_RGB444 = 0,
        DRM_OUTPUT_COLOR_FORMAT_YCBCR444,
        DRM_OUTPUT_COLOR_FORMAT_YCBCR422,
        DRM_OUTPUT_COLOR_FORMAT_YCBCR420,
+       DRM_OUTPUT_COLOR_FORMAT_COUNT,
 };
 
+/**
+ * enum drm_connector_color_format - Connector Color Format Request
+ *
+ * This enum, unlike &enum drm_output_color_format, is used to specify requests
+ * for a specific color format on a connector through the DRM "color format"
+ * property. The difference is that it has an "AUTO" value to specify that
+ * no specific choice has been made.
+ */
+enum drm_connector_color_format {
+       /**
+        * @DRM_CONNECTOR_COLOR_FORMAT_AUTO: The driver or display protocol
+        * helpers should pick a suitable color format. All implementations of a
+        * specific display protocol must behave the same way with "AUTO", but
+        * different display protocols do not necessarily have the same "AUTO"
+        * semantics.
+        *
+        * For HDMI, "AUTO" picks RGB, but falls back to YCbCr 4:2:0 if the
+        * bandwidth required for full-scale RGB is not available, or the mode
+        * is YCbCr 4:2:0-only, as long as the mode and output both support
+        * YCbCr 4:2:0.
+        *
+        * For display protocols other than HDMI, the recursive bridge chain
+        * format selection picks the first chain of bridge formats that works,
+        * as has already been the case before the introduction of the "color
+        * format" property. Non-HDMI bridges should therefore either sort their
+        * bus output formats by preference, or agree on a unified auto format
+        * selection logic that's implemented in a common state helper (like
+        * how HDMI does it).
+        */
+       DRM_CONNECTOR_COLOR_FORMAT_AUTO = 0,
+
+       /**
+        * @DRM_CONNECTOR_COLOR_FORMAT_RGB444: RGB output format. The
+        * quantization range depends on the value of the "Broadcast RGB"
+        * property if it is present on the connector.
+        */
+       DRM_CONNECTOR_COLOR_FORMAT_RGB444,
+
+       /**
+        * @DRM_CONNECTOR_COLOR_FORMAT_YCBCR444: YCbCr 4:4:4 output format (ie.
+        * not subsampled). Quantization range is "Limited" by default.
+        */
+       DRM_CONNECTOR_COLOR_FORMAT_YCBCR444,
+
+       /**
+        * @DRM_CONNECTOR_COLOR_FORMAT_YCBCR422: YCbCr 4:2:2 output format (ie.
+        * with horizontal subsampling). Quantization range is "Limited" by
+        * default.
+        */
+       DRM_CONNECTOR_COLOR_FORMAT_YCBCR422,
+
+       /**
+        * @DRM_CONNECTOR_COLOR_FORMAT_YCBCR420: YCbCr 4:2:0 output format (ie.
+        * with horizontal and vertical subsampling). Quantization range is
+        * "Limited" by default.
+        */
+       DRM_CONNECTOR_COLOR_FORMAT_YCBCR420,
+
+       /**
+        * @DRM_CONNECTOR_COLOR_FORMAT_COUNT: Number of valid connector color
+        * format values in this enum
+        */
+       DRM_CONNECTOR_COLOR_FORMAT_COUNT,
+};
+
+/**
+ * drm_connector_color_format_valid - Validate drm_connector_color_format value
+ * @fmt: value to check against all values of &enum drm_connector_color_format
+ *
+ * Checks whether the passed in value of @fmt is one of the allowable values in
+ * &enum drm_connector_color_format.
+ *
+ * Returns: %true if it's a valid value for the enum, %false otherwise.
+ */
+static inline bool __pure
+drm_connector_color_format_valid(enum drm_connector_color_format fmt)
+{
+       switch (fmt) {
+       case DRM_CONNECTOR_COLOR_FORMAT_AUTO:
+       case DRM_CONNECTOR_COLOR_FORMAT_RGB444:
+       case DRM_CONNECTOR_COLOR_FORMAT_YCBCR444:
+       case DRM_CONNECTOR_COLOR_FORMAT_YCBCR422:
+       case DRM_CONNECTOR_COLOR_FORMAT_YCBCR420:
+               return true;
+       default:
+               return false;
+       }
+}
+
 const char *
 drm_hdmi_connector_get_output_format_name(enum drm_output_color_format fmt);
 
@@ -1167,6 +1259,13 @@ struct drm_connector_state {
         */
        enum drm_colorspace colorspace;
 
+       /**
+        * @color_format: State variable for Connector property to request
+        * color format change on Sink. This is most commonly used to switch
+        * between RGB to YUV and vice-versa.
+        */
+       enum drm_connector_color_format color_format;
+
        /**
         * @writeback_job: Writeback job for writeback connectors
         *
@@ -2165,6 +2264,12 @@ struct drm_connector {
         */
        struct drm_property *colorspace_property;
 
+       /**
+        * @color_format_property: Connector property to set the suitable
+        * color format supported by the sink.
+        */
+       struct drm_property *color_format_property;
+
        /**
         * @path_blob_ptr:
         *
@@ -2648,6 +2753,9 @@ bool drm_connector_has_possible_encoder(struct 
drm_connector *connector,
                                        struct drm_encoder *encoder);
 const char *drm_get_colorspace_name(enum drm_colorspace colorspace);
 
+int drm_connector_attach_color_format_property(struct drm_connector *connector,
+                                              unsigned long 
supported_color_formats);
+
 /**
  * drm_for_each_connector_iter - connector_list iterator macro
  * @connector: &struct drm_connector pointer used as cursor

-- 
2.53.0

Reply via email to