[PATCH RFC 1/5] drm: Support post-blend color pipeline API

Nícolas F. R. A. Prado posted 5 patches 1 month, 1 week ago
There is a newer version of this series
[PATCH RFC 1/5] drm: Support post-blend color pipeline API
Posted by Nícolas F. R. A. Prado 1 month, 1 week ago
Introduce support for a post-blend color pipeline API analogous to the
pre-blend color pipeline API. While the pre-blend color pipeline was
configured through a COLOR_PIPELINE property attached to a drm_plane,
the post-blend color pipeline is configured through a COLOR_PIPELINE
property on the drm_crtc.

Since colorops can now be attached to either a drm_plane or a drm_crtc,
rework the helpers to account for both cases.

Also introduce a new cap, DRM_CLIENT_CAP_POST_BLEND_COLOR_PIPELINE, to
enable support for post-blend color pipelines, and prevent the now
legacy GAMMA_LUT, DEGAMMA_LUT, GAMMA_LUT_SIZE and CTM properties from
being exposed.

Signed-off-by: Nícolas F. R. A. Prado <nfraprado@collabora.com>
---
 drivers/gpu/drm/drm_atomic.c        |  32 ++++++--
 drivers/gpu/drm/drm_atomic_uapi.c   |  50 ++++++++++++-
 drivers/gpu/drm/drm_colorop.c       | 144 +++++++++++++++++++++++++++++-------
 drivers/gpu/drm/drm_connector.c     |   1 +
 drivers/gpu/drm/drm_crtc.c          |  77 +++++++++++++++++++
 drivers/gpu/drm/drm_crtc_internal.h |   6 ++
 drivers/gpu/drm/drm_ioctl.c         |   7 ++
 drivers/gpu/drm/drm_mode_object.c   |  20 +++++
 drivers/gpu/drm/drm_plane.c         |  36 ++-------
 include/drm/drm_atomic.h            |  20 +++++
 include/drm/drm_atomic_uapi.h       |   2 +
 include/drm/drm_colorop.h           |  16 +++-
 include/drm/drm_crtc.h              |  19 +++++
 include/drm/drm_file.h              |   7 ++
 include/uapi/drm/drm.h              |  16 ++++
 15 files changed, 383 insertions(+), 70 deletions(-)

diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
index 3ab32fe7fe1cbf9057c3763d979638dce013d82b..558d389d59d9a44d3cd1048ed365848f62b4d62d 100644
--- a/drivers/gpu/drm/drm_atomic.c
+++ b/drivers/gpu/drm/drm_atomic.c
@@ -472,6 +472,8 @@ static void drm_atomic_crtc_print_state(struct drm_printer *p,
 	drm_printf(p, "\tplane_mask=%x\n", state->plane_mask);
 	drm_printf(p, "\tconnector_mask=%x\n", state->connector_mask);
 	drm_printf(p, "\tencoder_mask=%x\n", state->encoder_mask);
+	drm_printf(p, "\tcolor-pipeline=%d\n",
+		   state->color_pipeline ? state->color_pipeline->base.id : 0);
 	drm_printf(p, "\tmode: " DRM_MODE_FMT "\n", DRM_MODE_ARG(&state->mode));
 
 	if (crtc->funcs->atomic_print_state)
@@ -617,9 +619,15 @@ drm_atomic_get_colorop_state(struct drm_atomic_state *state,
 	if (colorop_state)
 		return colorop_state;
 
-	ret = drm_modeset_lock(&colorop->plane->mutex, state->acquire_ctx);
-	if (ret)
-		return ERR_PTR(ret);
+	if (colorop->plane) {
+		ret = drm_modeset_lock(&colorop->plane->mutex, state->acquire_ctx);
+		if (ret)
+			return ERR_PTR(ret);
+	} else {
+		ret = drm_modeset_lock(&colorop->crtc->mutex, state->acquire_ctx);
+		if (ret)
+			return ERR_PTR(ret);
+	}
 
 	colorop_state = drm_atomic_helper_colorop_duplicate_state(colorop);
 	if (!colorop_state)
@@ -2003,11 +2011,21 @@ static void __drm_state_dump(struct drm_device *dev, struct drm_printer *p,
 		return;
 
 	list_for_each_entry(colorop, &config->colorop_list, head) {
-		if (take_locks)
-			drm_modeset_lock(&colorop->plane->mutex, NULL);
+		if (take_locks) {
+			if (colorop->plane)
+				drm_modeset_lock(&colorop->plane->mutex, NULL);
+			else
+				drm_modeset_lock(&colorop->crtc->mutex, NULL);
+
+		}
 		drm_atomic_colorop_print_state(p, colorop->state);
-		if (take_locks)
-			drm_modeset_unlock(&colorop->plane->mutex);
+		if (take_locks) {
+			if (colorop->plane)
+				drm_modeset_unlock(&colorop->plane->mutex);
+			else
+				drm_modeset_unlock(&colorop->crtc->mutex);
+
+		}
 	}
 
 	list_for_each_entry(plane, &config->plane_list, head) {
diff --git a/drivers/gpu/drm/drm_atomic_uapi.c b/drivers/gpu/drm/drm_atomic_uapi.c
index b7cc6945864274bedd21dd5b73494f9aae216888..a826758cf0b6205e2ba49734070bc83ffb7c08df 100644
--- a/drivers/gpu/drm/drm_atomic_uapi.c
+++ b/drivers/gpu/drm/drm_atomic_uapi.c
@@ -287,6 +287,33 @@ drm_atomic_set_colorop_for_plane(struct drm_plane_state *plane_state,
 }
 EXPORT_SYMBOL(drm_atomic_set_colorop_for_plane);
 
+/**
+ * drm_atomic_set_colorop_for_crtc - set colorop for crtc
+ * @crtc_state: atomic state object for the crtc
+ * @colorop: colorop to use for the crtc
+ *
+ * Helper function to select the color pipeline on a crtc by setting
+ * it to the first drm_colorop element of the pipeline.
+ */
+void
+drm_atomic_set_colorop_for_crtc(struct drm_crtc_state *crtc_state,
+				 struct drm_colorop *colorop)
+{
+	struct drm_crtc *crtc = crtc_state->crtc;
+
+	if (colorop)
+		drm_dbg_atomic(crtc->dev,
+			       "Set [COLOROP:%d] for [CRTC:%d:%s] state %p\n",
+			       colorop->base.id, crtc->base.id, crtc->name,
+			       crtc_state);
+	else
+		drm_dbg_atomic(crtc->dev,
+			       "Set [NOCOLOROP] for [CRTC:%d:%s] state %p\n",
+			       crtc->base.id, crtc->name, crtc_state);
+
+	crtc_state->color_pipeline = colorop;
+}
+EXPORT_SYMBOL(drm_atomic_set_colorop_for_crtc);
 
 /**
  * drm_atomic_set_crtc_for_connector - set CRTC for connector
@@ -396,8 +423,8 @@ static s32 __user *get_out_fence_for_connector(struct drm_atomic_state *state,
 }
 
 static int drm_atomic_crtc_set_property(struct drm_crtc *crtc,
-		struct drm_crtc_state *state, struct drm_property *property,
-		uint64_t val)
+		struct drm_crtc_state *state, struct drm_file *file_priv,
+		struct drm_property *property, uint64_t val)
 {
 	struct drm_device *dev = crtc->dev;
 	struct drm_mode_config *config = &dev->mode_config;
@@ -406,7 +433,17 @@ static int drm_atomic_crtc_set_property(struct drm_crtc *crtc,
 
 	if (property == config->prop_active)
 		state->active = val;
-	else if (property == config->prop_mode_id) {
+	else if (property == crtc->color_pipeline_property) {
+		/* find DRM colorop object */
+		struct drm_colorop *colorop = NULL;
+
+		colorop = drm_colorop_find(dev, file_priv, val);
+
+		if (val && !colorop)
+			return -EACCES;
+
+		drm_atomic_set_colorop_for_crtc(state, colorop);
+	} else if (property == config->prop_mode_id) {
 		struct drm_property_blob *mode =
 			drm_property_lookup_blob(dev, val);
 		ret = drm_atomic_set_mode_prop_for_crtc(state, mode);
@@ -487,6 +524,8 @@ drm_atomic_crtc_get_property(struct drm_crtc *crtc,
 		*val = 0;
 	else if (property == crtc->scaling_filter_property)
 		*val = state->scaling_filter;
+	else if (property == crtc->color_pipeline_property)
+		*val = (state->color_pipeline) ? state->color_pipeline->base.id : 0;
 	else if (crtc->funcs->atomic_get_property)
 		return crtc->funcs->atomic_get_property(crtc, state, property, val);
 	else {
@@ -1047,6 +1086,8 @@ int drm_atomic_get_property(struct drm_mode_object *obj,
 
 		if (colorop->plane)
 			WARN_ON(!drm_modeset_is_locked(&colorop->plane->mutex));
+		else
+			WARN_ON(!drm_modeset_is_locked(&colorop->crtc->mutex));
 
 		ret = drm_atomic_colorop_get_property(colorop,
 				colorop->state, property, val);
@@ -1204,7 +1245,7 @@ int drm_atomic_set_property(struct drm_atomic_state *state,
 		}
 
 		ret = drm_atomic_crtc_set_property(crtc,
-				crtc_state, prop, prop_value);
+				crtc_state, file_priv, prop, prop_value);
 		break;
 	}
 	case DRM_MODE_OBJECT_PLANE: {
@@ -1604,6 +1645,7 @@ int drm_mode_atomic_ioctl(struct drm_device *dev,
 	state->acquire_ctx = &ctx;
 	state->allow_modeset = !!(arg->flags & DRM_MODE_ATOMIC_ALLOW_MODESET);
 	state->plane_color_pipeline = file_priv->plane_color_pipeline;
+	state->post_blend_color_pipeline = file_priv->post_blend_color_pipeline;
 
 retry:
 	copied_objs = 0;
diff --git a/drivers/gpu/drm/drm_colorop.c b/drivers/gpu/drm/drm_colorop.c
index a1b36cd488f0a014425a9192ffe5fcc4d2c1afaa..d53de1438d23def74a77730cacd3651131e82cbe 100644
--- a/drivers/gpu/drm/drm_colorop.c
+++ b/drivers/gpu/drm/drm_colorop.c
@@ -90,8 +90,9 @@ static const struct drm_prop_enum_list drm_colorop_lut3d_interpolation_list[] =
 
 /* Init Helpers */
 
-static int drm_plane_colorop_init(struct drm_device *dev, struct drm_colorop *colorop,
-			    struct drm_plane *plane, enum drm_colorop_type type, uint32_t flags)
+static int drm_common_colorop_init(struct drm_device *dev,
+				   struct drm_colorop *colorop,
+				   enum drm_colorop_type type, uint32_t flags)
 {
 	struct drm_mode_config *config = &dev->mode_config;
 	struct drm_property *prop;
@@ -104,7 +105,6 @@ static int drm_plane_colorop_init(struct drm_device *dev, struct drm_colorop *co
 	colorop->base.properties = &colorop->properties;
 	colorop->dev = dev;
 	colorop->type = type;
-	colorop->plane = plane;
 	colorop->next = NULL;
 
 	list_add_tail(&colorop->head, &config->colorop_list);
@@ -153,6 +153,34 @@ static int drm_plane_colorop_init(struct drm_device *dev, struct drm_colorop *co
 	return ret;
 }
 
+static int drm_crtc_colorop_init(struct drm_device *dev,
+				 struct drm_colorop *colorop,
+				 struct drm_crtc *crtc,
+				 enum drm_colorop_type type, uint32_t flags)
+{
+	int ret;
+
+	ret = drm_common_colorop_init(dev, colorop, type, flags);
+
+	colorop->crtc = crtc;
+
+	return ret;
+}
+
+static int drm_plane_colorop_init(struct drm_device *dev,
+				  struct drm_colorop *colorop,
+				  struct drm_plane *plane,
+				  enum drm_colorop_type type, uint32_t flags)
+{
+	int ret;
+
+	ret = drm_common_colorop_init(dev, colorop, type, flags);
+
+	colorop->plane = plane;
+
+	return ret;
+}
+
 /**
  * drm_colorop_cleanup - Cleanup a drm_colorop object in color_pipeline
  *
@@ -279,29 +307,16 @@ static int drm_colorop_create_data_prop(struct drm_device *dev, struct drm_color
 	return 0;
 }
 
-/**
- * drm_plane_colorop_curve_1d_lut_init - Initialize a DRM_COLOROP_1D_LUT
- *
- * @dev: DRM device
- * @colorop: The drm_colorop object to initialize
- * @plane: The associated drm_plane
- * @lut_size: LUT size supported by driver
- * @lut1d_interpolation: 1D LUT interpolation type
- * @flags: bitmask of misc, see DRM_COLOROP_FLAG_* defines.
- * @return zero on success, -E value on failure
- */
-int drm_plane_colorop_curve_1d_lut_init(struct drm_device *dev, struct drm_colorop *colorop,
-					struct drm_plane *plane, uint32_t lut_size,
-					enum drm_colorop_lut1d_interpolation_type lut1d_interpolation,
-					uint32_t flags)
+static int
+drm_common_colorop_curve_1d_lut_init(struct drm_device *dev,
+				     struct drm_colorop *colorop,
+				     uint32_t lut_size,
+				     enum drm_colorop_lut1d_interpolation_type lut1d_interpolation,
+				     uint32_t flags)
 {
 	struct drm_property *prop;
 	int ret;
 
-	ret = drm_plane_colorop_init(dev, colorop, plane, DRM_COLOROP_1D_LUT, flags);
-	if (ret)
-		return ret;
-
 	/* initialize 1D LUT only attribute */
 	/* LUT size */
 	prop = drm_property_create_range(dev, DRM_MODE_PROP_IMMUTABLE | DRM_MODE_PROP_ATOMIC,
@@ -333,17 +348,69 @@ int drm_plane_colorop_curve_1d_lut_init(struct drm_device *dev, struct drm_color
 
 	return 0;
 }
-EXPORT_SYMBOL(drm_plane_colorop_curve_1d_lut_init);
 
-int drm_plane_colorop_ctm_3x4_init(struct drm_device *dev, struct drm_colorop *colorop,
-				   struct drm_plane *plane, uint32_t flags)
+/**
+ * drm_crtc_colorop_curve_1d_lut_init - Initialize a DRM_COLOROP_1D_LUT
+ *
+ * @dev: DRM device
+ * @colorop: The drm_colorop object to initialize
+ * @crtc: The associated drm_crtc
+ * @lut_size: LUT size supported by driver
+ * @lut1d_interpolation: 1D LUT interpolation type
+ * @flags: bitmask of misc, see DRM_COLOROP_FLAG_* defines.
+ * @return zero on success, -E value on failure
+ */
+int
+drm_crtc_colorop_curve_1d_lut_init(struct drm_device *dev,
+				   struct drm_colorop *colorop,
+				   struct drm_crtc *crtc, uint32_t lut_size,
+				   enum drm_colorop_lut1d_interpolation_type lut1d_interpolation,
+				   uint32_t flags)
 {
 	int ret;
 
-	ret = drm_plane_colorop_init(dev, colorop, plane, DRM_COLOROP_CTM_3X4, flags);
+	ret = drm_crtc_colorop_init(dev, colorop, crtc, DRM_COLOROP_1D_LUT, flags);
 	if (ret)
 		return ret;
 
+	return drm_common_colorop_curve_1d_lut_init(dev, colorop, lut_size,
+						    lut1d_interpolation, flags);
+}
+EXPORT_SYMBOL(drm_crtc_colorop_curve_1d_lut_init);
+
+/**
+ * drm_plane_colorop_curve_1d_lut_init - Initialize a DRM_COLOROP_1D_LUT
+ *
+ * @dev: DRM device
+ * @colorop: The drm_colorop object to initialize
+ * @plane: The associated drm_plane
+ * @lut_size: LUT size supported by driver
+ * @lut1d_interpolation: 1D LUT interpolation type
+ * @flags: bitmask of misc, see DRM_COLOROP_FLAG_* defines.
+ * @return zero on success, -E value on failure
+ */
+int
+drm_plane_colorop_curve_1d_lut_init(struct drm_device *dev, struct drm_colorop *colorop,
+				    struct drm_plane *plane, uint32_t lut_size,
+				    enum drm_colorop_lut1d_interpolation_type lut1d_interpolation,
+				    uint32_t flags)
+{
+	int ret;
+
+	ret = drm_plane_colorop_init(dev, colorop, plane, DRM_COLOROP_1D_LUT, flags);
+	if (ret)
+		return ret;
+
+	return drm_common_colorop_curve_1d_lut_init(dev, colorop, lut_size,
+						    lut1d_interpolation, flags);
+}
+EXPORT_SYMBOL(drm_plane_colorop_curve_1d_lut_init);
+
+static int drm_common_colorop_ctm_3x4_init(struct drm_device *dev, struct drm_colorop *colorop,
+					   uint32_t flags)
+{
+	int ret;
+
 	ret = drm_colorop_create_data_prop(dev, colorop);
 	if (ret)
 		return ret;
@@ -352,6 +419,31 @@ int drm_plane_colorop_ctm_3x4_init(struct drm_device *dev, struct drm_colorop *c
 
 	return 0;
 }
+
+int drm_crtc_colorop_ctm_3x4_init(struct drm_device *dev, struct drm_colorop *colorop,
+				   struct drm_crtc *crtc, uint32_t flags)
+{
+	int ret;
+
+	ret = drm_crtc_colorop_init(dev, colorop, crtc, DRM_COLOROP_CTM_3X4, flags);
+	if (ret)
+		return ret;
+
+	return drm_common_colorop_ctm_3x4_init(dev, colorop, flags);
+}
+EXPORT_SYMBOL(drm_crtc_colorop_ctm_3x4_init);
+
+int drm_plane_colorop_ctm_3x4_init(struct drm_device *dev, struct drm_colorop *colorop,
+				   struct drm_plane *plane, uint32_t flags)
+{
+	int ret;
+
+	ret = drm_plane_colorop_init(dev, colorop, plane, DRM_COLOROP_CTM_3X4, flags);
+	if (ret)
+		return ret;
+
+	return drm_common_colorop_ctm_3x4_init(dev, colorop, flags);
+}
 EXPORT_SYMBOL(drm_plane_colorop_ctm_3x4_init);
 
 /**
diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c
index 4d6dc9ebfdb5bc730b1aff7a184448af7b93f078..f58cfd2131139ff3e613adc4dbb9ddbedf724dc7 100644
--- a/drivers/gpu/drm/drm_connector.c
+++ b/drivers/gpu/drm/drm_connector.c
@@ -3440,6 +3440,7 @@ int drm_mode_getconnector(struct drm_device *dev, void *data,
 	 */
 	ret = drm_mode_object_get_properties(&connector->base, file_priv->atomic,
 			file_priv->plane_color_pipeline,
+			file_priv->post_blend_color_pipeline,
 			(uint32_t __user *)(unsigned long)(out_resp->props_ptr),
 			(uint64_t __user *)(unsigned long)(out_resp->prop_values_ptr),
 			&out_resp->count_props);
diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c
index 46655339003db2a1b43441434839e26f61d79b4e..94238163ff1254c721df39c030bc99a31d3bb92a 100644
--- a/drivers/gpu/drm/drm_crtc.c
+++ b/drivers/gpu/drm/drm_crtc.c
@@ -959,3 +959,80 @@ bool drm_crtc_in_clone_mode(struct drm_crtc_state *crtc_state)
 	return hweight32(crtc_state->encoder_mask) > 1;
 }
 EXPORT_SYMBOL(drm_crtc_in_clone_mode);
+
+struct drm_property *
+drm_common_create_color_pipeline_property(struct drm_device *dev, struct drm_mode_object *obj,
+					  const struct drm_prop_enum_list *pipelines,
+					  int num_pipelines)
+{
+	struct drm_prop_enum_list *all_pipelines;
+	struct drm_property *prop;
+	int len = 0;
+	int i;
+
+	all_pipelines = kcalloc(num_pipelines + 1,
+				sizeof(*all_pipelines),
+				GFP_KERNEL);
+
+	if (!all_pipelines) {
+		drm_err(dev, "failed to allocate color pipeline\n");
+		return ERR_PTR(-ENOMEM);
+	}
+
+	/* Create default Bypass color pipeline */
+	all_pipelines[len].type = 0;
+	all_pipelines[len].name = "Bypass";
+	len++;
+
+	/* Add all other color pipelines */
+	for (i = 0; i < num_pipelines; i++, len++) {
+		all_pipelines[len].type = pipelines[i].type;
+		all_pipelines[len].name = pipelines[i].name;
+	}
+
+	prop = drm_property_create_enum(dev, DRM_MODE_PROP_ATOMIC,
+					"COLOR_PIPELINE",
+					all_pipelines, len);
+	if (IS_ERR(prop)) {
+		kfree(all_pipelines);
+		return prop;
+	}
+
+	drm_object_attach_property(obj, prop, 0);
+
+	kfree(all_pipelines);
+	return prop;
+}
+
+/**
+ * drm_crtc_create_color_pipeline_property - create a new color pipeline
+ * property
+ *
+ * @crtc: drm CRTC
+ * @pipelines: list of pipelines
+ * @num_pipelines: number of pipelines
+ *
+ * Create the COLOR_PIPELINE CRTC property to specify color pipelines on
+ * the CRTC.
+ *
+ * RETURNS:
+ * Zero for success or -errno
+ */
+int drm_crtc_create_color_pipeline_property(struct drm_crtc *crtc,
+					    const struct drm_prop_enum_list *pipelines,
+					    int num_pipelines)
+{
+	struct drm_property *prop;
+
+	prop = drm_common_create_color_pipeline_property(crtc->dev,
+							 &crtc->base,
+							 pipelines,
+							 num_pipelines);
+	if (IS_ERR(prop))
+		return PTR_ERR(prop);
+
+	crtc->color_pipeline_property = prop;
+
+	return 0;
+}
+EXPORT_SYMBOL(drm_crtc_create_color_pipeline_property);
diff --git a/drivers/gpu/drm/drm_crtc_internal.h b/drivers/gpu/drm/drm_crtc_internal.h
index c094092296448093c5cd192ecdc8ea9a50769c90..c53f154e5392a10c326c844b7321666275f9ac02 100644
--- a/drivers/gpu/drm/drm_crtc_internal.h
+++ b/drivers/gpu/drm/drm_crtc_internal.h
@@ -35,6 +35,7 @@
 #ifndef __DRM_CRTC_INTERNAL_H__
 #define __DRM_CRTC_INTERNAL_H__
 
+#include <drm/drm_property.h>
 #include <linux/err.h>
 #include <linux/types.h>
 
@@ -79,6 +80,10 @@ int drm_crtc_check_viewport(const struct drm_crtc *crtc,
 int drm_crtc_register_all(struct drm_device *dev);
 void drm_crtc_unregister_all(struct drm_device *dev);
 int drm_crtc_force_disable(struct drm_crtc *crtc);
+struct drm_property *
+drm_common_create_color_pipeline_property(struct drm_device *dev, struct drm_mode_object *obj,
+					  const struct drm_prop_enum_list *pipelines,
+					  int num_pipelines);
 
 struct dma_fence *drm_crtc_create_fence(struct drm_crtc *crtc);
 
@@ -164,6 +169,7 @@ void drm_mode_object_unregister(struct drm_device *dev,
 				struct drm_mode_object *object);
 int drm_mode_object_get_properties(struct drm_mode_object *obj, bool atomic,
 				   bool plane_color_pipeline,
+				   bool post_blend_color_pipeline,
 				   uint32_t __user *prop_ptr,
 				   uint64_t __user *prop_values,
 				   uint32_t *arg_count_props);
diff --git a/drivers/gpu/drm/drm_ioctl.c b/drivers/gpu/drm/drm_ioctl.c
index ff193155129e7e863888d8958458978566b144f8..2c81f63fee428ca85f3c626d892ea6097b964e88 100644
--- a/drivers/gpu/drm/drm_ioctl.c
+++ b/drivers/gpu/drm/drm_ioctl.c
@@ -380,6 +380,13 @@ drm_setclientcap(struct drm_device *dev, void *data, struct drm_file *file_priv)
 			return -EINVAL;
 		file_priv->plane_color_pipeline = req->value;
 		break;
+	case DRM_CLIENT_CAP_POST_BLEND_COLOR_PIPELINE:
+		if (!file_priv->atomic)
+			return -EINVAL;
+		if (req->value > 1)
+			return -EINVAL;
+		file_priv->post_blend_color_pipeline = req->value;
+		break;
 	default:
 		return -EINVAL;
 	}
diff --git a/drivers/gpu/drm/drm_mode_object.c b/drivers/gpu/drm/drm_mode_object.c
index b45d501b10c868c6d9b7a5a8760eadbd7b372a6a..c9e20d12e8fd311f71b9d6bc8d575624751d33ad 100644
--- a/drivers/gpu/drm/drm_mode_object.c
+++ b/drivers/gpu/drm/drm_mode_object.c
@@ -388,6 +388,7 @@ EXPORT_SYMBOL(drm_object_property_get_default_value);
 /* helper for getconnector and getproperties ioctls */
 int drm_mode_object_get_properties(struct drm_mode_object *obj, bool atomic,
 				   bool plane_color_pipeline,
+				   bool post_blend_color_pipeline,
 				   uint32_t __user *prop_ptr,
 				   uint64_t __user *prop_values,
 				   uint32_t *arg_count_props)
@@ -416,6 +417,24 @@ int drm_mode_object_get_properties(struct drm_mode_object *obj, bool atomic,
 				continue;
 		}
 
+		if (post_blend_color_pipeline && obj->type == DRM_MODE_OBJECT_CRTC) {
+			struct drm_crtc *crtc = obj_to_crtc(obj);
+			struct drm_mode_config mode_config = crtc->dev->mode_config;
+
+			if (prop == mode_config.gamma_lut_property ||
+			    prop == mode_config.degamma_lut_property ||
+			    prop == mode_config.gamma_lut_size_property ||
+			    prop == mode_config.ctm_property)
+				continue;
+		}
+
+		if (!post_blend_color_pipeline && obj->type == DRM_MODE_OBJECT_CRTC) {
+			struct drm_crtc *crtc = obj_to_crtc(obj);
+
+			if (prop == crtc->color_pipeline_property)
+				continue;
+		}
+
 		if (*arg_count_props > count) {
 			ret = __drm_object_property_get_value(obj, prop, &val);
 			if (ret)
@@ -475,6 +494,7 @@ int drm_mode_obj_get_properties_ioctl(struct drm_device *dev, void *data,
 
 	ret = drm_mode_object_get_properties(obj, file_priv->atomic,
 			file_priv->plane_color_pipeline,
+			file_priv->post_blend_color_pipeline,
 			(uint32_t __user *)(unsigned long)(arg->props_ptr),
 			(uint64_t __user *)(unsigned long)(arg->prop_values_ptr),
 			&arg->count_props);
diff --git a/drivers/gpu/drm/drm_plane.c b/drivers/gpu/drm/drm_plane.c
index f6cfa8ac090c7bc49c7f276993bba7e9800da140..60dbfcab495600dd44c15260a1fa6135db59c6e2 100644
--- a/drivers/gpu/drm/drm_plane.c
+++ b/drivers/gpu/drm/drm_plane.c
@@ -1839,43 +1839,17 @@ int drm_plane_create_color_pipeline_property(struct drm_plane *plane,
 					     const struct drm_prop_enum_list *pipelines,
 					     int num_pipelines)
 {
-	struct drm_prop_enum_list *all_pipelines;
 	struct drm_property *prop;
-	int len = 0;
-	int i;
-
-	all_pipelines = kcalloc(num_pipelines + 1,
-				sizeof(*all_pipelines),
-				GFP_KERNEL);
-
-	if (!all_pipelines) {
-		drm_err(plane->dev, "failed to allocate color pipeline\n");
-		return -ENOMEM;
-	}
 
-	/* Create default Bypass color pipeline */
-	all_pipelines[len].type = 0;
-	all_pipelines[len].name = "Bypass";
-	len++;
-
-	/* Add all other color pipelines */
-	for (i = 0; i < num_pipelines; i++, len++) {
-		all_pipelines[len].type = pipelines[i].type;
-		all_pipelines[len].name = pipelines[i].name;
-	}
-
-	prop = drm_property_create_enum(plane->dev, DRM_MODE_PROP_ATOMIC,
-					"COLOR_PIPELINE",
-					all_pipelines, len);
-	if (IS_ERR(prop)) {
-		kfree(all_pipelines);
+	prop = drm_common_create_color_pipeline_property(plane->dev,
+							 &plane->base,
+							 pipelines,
+							 num_pipelines);
+	if (IS_ERR(prop))
 		return PTR_ERR(prop);
-	}
 
-	drm_object_attach_property(&plane->base, prop, 0);
 	plane->color_pipeline_property = prop;
 
-	kfree(all_pipelines);
 	return 0;
 }
 EXPORT_SYMBOL(drm_plane_create_color_pipeline_property);
diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h
index 678708df9cdb90b4266127193a92183069f18688..8c42c584aefbf0034b2163d90538e80099b0dadb 100644
--- a/include/drm/drm_atomic.h
+++ b/include/drm/drm_atomic.h
@@ -482,6 +482,26 @@ struct drm_atomic_state {
 	 */
 	bool plane_color_pipeline : 1;
 
+	/**
+	 * @post_blend_color_pipeline:
+	 *
+	 * Indicates whether this atomic state originated with a client that
+	 * set the DRM_CLIENT_CAP_POST_BLEND_COLOR_PIPELINE.
+	 *
+	 * Drivers and helper functions should use this to ignore legacy
+	 * properties that are incompatible with the drm_crtc COLOR_PIPELINE
+	 * behavior, such as:
+	 *
+	 *  - GAMMA_LUT
+	 *  - DEGAMMA_LUT
+	 *  - GAMMA_LUT_SIZE
+	 *  - CTM
+	 *
+	 * or any other driver-specific properties that might affect pixel
+	 * values.
+	 */
+	bool post_blend_color_pipeline : 1;
+
 	/**
 	 * @colorops:
 	 *
diff --git a/include/drm/drm_atomic_uapi.h b/include/drm/drm_atomic_uapi.h
index 4363155233267b93767c895fa6085544e2277442..4dc191f6f929d73deee9812025c48275a33cf770 100644
--- a/include/drm/drm_atomic_uapi.h
+++ b/include/drm/drm_atomic_uapi.h
@@ -52,6 +52,8 @@ void drm_atomic_set_fb_for_plane(struct drm_plane_state *plane_state,
 				 struct drm_framebuffer *fb);
 void drm_atomic_set_colorop_for_plane(struct drm_plane_state *plane_state,
 				      struct drm_colorop *colorop);
+void drm_atomic_set_colorop_for_crtc(struct drm_crtc_state *crtc_state,
+				     struct drm_colorop *colorop);
 int __must_check
 drm_atomic_set_crtc_for_connector(struct drm_connector_state *conn_state,
 				  struct drm_crtc *crtc);
diff --git a/include/drm/drm_colorop.h b/include/drm/drm_colorop.h
index d61c6c40e47162cb8b1e7db58b6746c43ac5d202..710a6199ebc5498a3f664de39ea07dbc95944eb7 100644
--- a/include/drm/drm_colorop.h
+++ b/include/drm/drm_colorop.h
@@ -206,10 +206,16 @@ struct drm_colorop {
 	/**
 	 * @plane:
 	 *
-	 * The plane on which the colorop sits. A drm_colorop is always unique
-	 * to a plane.
+	 * The plane on which the colorop sits if it is a pre-blend colorop.
+	 * In this case it is unique to the plane.
+	 *
+	 * @crtc:
+	 *
+	 * The CRTC on which the colorop sits if it is a post-blend colorop.
+	 * In this case it is unique to the CRTC.
 	 */
 	struct drm_plane *plane;
+	struct drm_crtc *crtc;
 
 	/**
 	 * @state:
@@ -370,6 +376,10 @@ static inline struct drm_colorop *drm_colorop_find(struct drm_device *dev,
 
 void drm_colorop_pipeline_destroy(struct drm_device *dev);
 
+int drm_crtc_colorop_curve_1d_lut_init(struct drm_device *dev, struct drm_colorop *colorop,
+				       struct drm_crtc *crtc, uint32_t lut_size,
+				       enum drm_colorop_lut1d_interpolation_type lut1d_interpolation,
+				       uint32_t flags);
 int drm_plane_colorop_curve_1d_init(struct drm_device *dev, struct drm_colorop *colorop,
 				    struct drm_plane *plane, u64 supported_tfs, uint32_t flags);
 int drm_plane_colorop_curve_1d_lut_init(struct drm_device *dev, struct drm_colorop *colorop,
@@ -378,6 +388,8 @@ int drm_plane_colorop_curve_1d_lut_init(struct drm_device *dev, struct drm_color
 					uint32_t flags);
 int drm_plane_colorop_ctm_3x4_init(struct drm_device *dev, struct drm_colorop *colorop,
 				   struct drm_plane *plane, uint32_t flags);
+int drm_crtc_colorop_ctm_3x4_init(struct drm_device *dev, struct drm_colorop *colorop,
+				   struct drm_crtc *crtc, uint32_t flags);
 int drm_plane_colorop_mult_init(struct drm_device *dev, struct drm_colorop *colorop,
 				struct drm_plane *plane, uint32_t flags);
 int drm_plane_colorop_3dlut_init(struct drm_device *dev, struct drm_colorop *colorop,
diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h
index caa56e039da2a748cf40ebf45b37158acda439d9..df03637ca25abd45e96b5944229297f776fd046d 100644
--- a/include/drm/drm_crtc.h
+++ b/include/drm/drm_crtc.h
@@ -274,6 +274,14 @@ struct drm_crtc_state {
 	 */
 	struct drm_property_blob *gamma_lut;
 
+	/**
+	 * @color_pipeline:
+	 *
+	 * The first colorop of the active color pipeline, or NULL, if no
+	 * color pipeline is active.
+	 */
+	struct drm_colorop *color_pipeline;
+
 	/**
 	 * @target_vblank:
 	 *
@@ -1088,6 +1096,14 @@ struct drm_crtc {
 	 */
 	struct drm_property *scaling_filter_property;
 
+	/**
+	 * @color_pipeline_property:
+	 *
+	 * Optional "COLOR_PIPELINE" enum property for specifying
+	 * a color pipeline to use on the CRTC.
+	 */
+	struct drm_property *color_pipeline_property;
+
 	/**
 	 * @state:
 	 *
@@ -1323,5 +1339,8 @@ static inline struct drm_crtc *drm_crtc_find(struct drm_device *dev,
 
 int drm_crtc_create_scaling_filter_property(struct drm_crtc *crtc,
 					    unsigned int supported_filters);
+int drm_crtc_create_color_pipeline_property(struct drm_crtc *crtc,
+					     const struct drm_prop_enum_list *pipelines,
+					     int num_pipelines);
 bool drm_crtc_in_clone_mode(struct drm_crtc_state *crtc_state);
 #endif /* __DRM_CRTC_H__ */
diff --git a/include/drm/drm_file.h b/include/drm/drm_file.h
index 1a3018e4a537b3341acb50187d47371f6b781b9d..42b9a43baa18079af8ec2ea5b1484b23c497beb0 100644
--- a/include/drm/drm_file.h
+++ b/include/drm/drm_file.h
@@ -213,6 +213,13 @@ struct drm_file {
 	 */
 	bool plane_color_pipeline;
 
+	/**
+	 * @post_blend_color_pipeline:
+	 *
+	 * True if client understands post-blend color pipelines
+	 */
+	bool post_blend_color_pipeline;
+
 	/**
 	 * @was_master:
 	 *
diff --git a/include/uapi/drm/drm.h b/include/uapi/drm/drm.h
index 27cc159c1d275c7a7fe057840ef792f30a582bb7..1191b142ebaa5478376308f169f9ce8591580d9d 100644
--- a/include/uapi/drm/drm.h
+++ b/include/uapi/drm/drm.h
@@ -921,6 +921,22 @@ struct drm_get_cap {
  */
 #define DRM_CLIENT_CAP_PLANE_COLOR_PIPELINE	7
 
+/**
+ * DRM_CLIENT_CAP_POST_BLEND_COLOR_PIPELINE
+ *
+ * If set to 1 the DRM core will allow setting the COLOR_PIPELINE
+ * property on a &drm_crtc, as well as drm_colorop properties.
+ *
+ * Setting of these crtc properties will be rejected when this client
+ * cap is set:
+ * - GAMMA_LUT
+ * - DEGAMMA_LUT
+ * - CTM
+ *
+ * The client must enable &DRM_CLIENT_CAP_ATOMIC first.
+ */
+#define DRM_CLIENT_CAP_POST_BLEND_COLOR_PIPELINE	8
+
 /* DRM_IOCTL_SET_CLIENT_CAP ioctl argument type */
 struct drm_set_client_cap {
 	__u64 capability;

-- 
2.47.2

Re: [PATCH RFC 1/5] drm: Support post-blend color pipeline API
Posted by Louis Chauvet 4 weeks ago

Le 22/08/2025 à 20:36, Nícolas F. R. A. Prado a écrit :
> Introduce support for a post-blend color pipeline API analogous to the
> pre-blend color pipeline API. While the pre-blend color pipeline was
> configured through a COLOR_PIPELINE property attached to a drm_plane,
> the post-blend color pipeline is configured through a COLOR_PIPELINE
> property on the drm_crtc.
> 
> Since colorops can now be attached to either a drm_plane or a drm_crtc,
> rework the helpers to account for both cases.
> 
> Also introduce a new cap, DRM_CLIENT_CAP_POST_BLEND_COLOR_PIPELINE, to
> enable support for post-blend color pipelines, and prevent the now
> legacy GAMMA_LUT, DEGAMMA_LUT, GAMMA_LUT_SIZE and CTM properties from
> being exposed.
> 
> Signed-off-by: Nícolas F. R. A. Prado <nfraprado@collabora.com>
> ---
>   drivers/gpu/drm/drm_atomic.c        |  32 ++++++--
>   drivers/gpu/drm/drm_atomic_uapi.c   |  50 ++++++++++++-
>   drivers/gpu/drm/drm_colorop.c       | 144 +++++++++++++++++++++++++++++-------
>   drivers/gpu/drm/drm_connector.c     |   1 +
>   drivers/gpu/drm/drm_crtc.c          |  77 +++++++++++++++++++
>   drivers/gpu/drm/drm_crtc_internal.h |   6 ++
>   drivers/gpu/drm/drm_ioctl.c         |   7 ++
>   drivers/gpu/drm/drm_mode_object.c   |  20 +++++
>   drivers/gpu/drm/drm_plane.c         |  36 ++-------
>   include/drm/drm_atomic.h            |  20 +++++
>   include/drm/drm_atomic_uapi.h       |   2 +
>   include/drm/drm_colorop.h           |  16 +++-
>   include/drm/drm_crtc.h              |  19 +++++
>   include/drm/drm_file.h              |   7 ++
>   include/uapi/drm/drm.h              |  16 ++++
>   15 files changed, 383 insertions(+), 70 deletions(-)
> 
> diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
> index 3ab32fe7fe1cbf9057c3763d979638dce013d82b..558d389d59d9a44d3cd1048ed365848f62b4d62d 100644
> --- a/drivers/gpu/drm/drm_atomic.c
> +++ b/drivers/gpu/drm/drm_atomic.c
> @@ -472,6 +472,8 @@ static void drm_atomic_crtc_print_state(struct drm_printer *p,
>   	drm_printf(p, "\tplane_mask=%x\n", state->plane_mask);
>   	drm_printf(p, "\tconnector_mask=%x\n", state->connector_mask);
>   	drm_printf(p, "\tencoder_mask=%x\n", state->encoder_mask);
> +	drm_printf(p, "\tcolor-pipeline=%d\n",
> +		   state->color_pipeline ? state->color_pipeline->base.id : 0);

This could be in a separate patch / suggested to the initial series.

>   	drm_printf(p, "\tmode: " DRM_MODE_FMT "\n", DRM_MODE_ARG(&state->mode));
>   
>   	if (crtc->funcs->atomic_print_state)
> @@ -617,9 +619,15 @@ drm_atomic_get_colorop_state(struct drm_atomic_state *state,
>   	if (colorop_state)
>   		return colorop_state;
>   
> -	ret = drm_modeset_lock(&colorop->plane->mutex, state->acquire_ctx);
> -	if (ret)
> -		return ERR_PTR(ret);
> +	if (colorop->plane) {
> +		ret = drm_modeset_lock(&colorop->plane->mutex, state->acquire_ctx);
> +		if (ret)
> +			return ERR_PTR(ret);
> +	} else {
> +		ret = drm_modeset_lock(&colorop->crtc->mutex, state->acquire_ctx);
> +		if (ret)
> +			return ERR_PTR(ret);
> +	}

Two suggestions here:

Maybe you can create `colorop_modeset_lock/unlock` helpers to avoid code 
repetition.

Can you also change it to

	if (colorop->plane)
		...
	else if (colorop->crtc)
		...
	else
		drm_err("Dangling colorop, it must be attached to a plane or a CRTC")
		return ERR_PTR

?

This way it will avoid issues if someone add support to attach colorop 
to other drm parts and forgot to update locking in some places.

>   
>   	colorop_state = drm_atomic_helper_colorop_duplicate_state(colorop);
>   	if (!colorop_state)
> @@ -2003,11 +2011,21 @@ static void __drm_state_dump(struct drm_device *dev, struct drm_printer *p,
>   		return;
>   
>   	list_for_each_entry(colorop, &config->colorop_list, head) {
> -		if (take_locks)
> -			drm_modeset_lock(&colorop->plane->mutex, NULL);
> +		if (take_locks) {
> +			if (colorop->plane)
> +				drm_modeset_lock(&colorop->plane->mutex, NULL);
> +			else
> +				drm_modeset_lock(&colorop->crtc->mutex, NULL);> +
> +		}
>   		drm_atomic_colorop_print_state(p, colorop->state);
> -		if (take_locks)
> -			drm_modeset_unlock(&colorop->plane->mutex);
> +		if (take_locks) {
> +			if (colorop->plane)
> +				drm_modeset_unlock(&colorop->plane->mutex);
> +			else
> +				drm_modeset_unlock(&colorop->crtc->mutex);
> +
> +		}
>   	}
>   
>   	list_for_each_entry(plane, &config->plane_list, head) {
> diff --git a/drivers/gpu/drm/drm_atomic_uapi.c b/drivers/gpu/drm/drm_atomic_uapi.c
> index b7cc6945864274bedd21dd5b73494f9aae216888..a826758cf0b6205e2ba49734070bc83ffb7c08df 100644
> --- a/drivers/gpu/drm/drm_atomic_uapi.c
> +++ b/drivers/gpu/drm/drm_atomic_uapi.c
> @@ -287,6 +287,33 @@ drm_atomic_set_colorop_for_plane(struct drm_plane_state *plane_state,
>   }
>   EXPORT_SYMBOL(drm_atomic_set_colorop_for_plane);
>   
> +/**
> + * drm_atomic_set_colorop_for_crtc - set colorop for crtc
> + * @crtc_state: atomic state object for the crtc
> + * @colorop: colorop to use for the crtc
> + *
> + * Helper function to select the color pipeline on a crtc by setting
> + * it to the first drm_colorop element of the pipeline.
> + */
> +void
> +drm_atomic_set_colorop_for_crtc(struct drm_crtc_state *crtc_state,
> +				 struct drm_colorop *colorop)
> +{
> +	struct drm_crtc *crtc = crtc_state->crtc;
> +
> +	if (colorop)
> +		drm_dbg_atomic(crtc->dev,
> +			       "Set [COLOROP:%d] for [CRTC:%d:%s] state %p\n",
> +			       colorop->base.id, crtc->base.id, crtc->name,
> +			       crtc_state);
> +	else
> +		drm_dbg_atomic(crtc->dev,
> +			       "Set [NOCOLOROP] for [CRTC:%d:%s] state %p\n",
> +			       crtc->base.id, crtc->name, crtc_state);
> +
> +	crtc_state->color_pipeline = colorop;
> +}
> +EXPORT_SYMBOL(drm_atomic_set_colorop_for_crtc);
>   
>   /**
>    * drm_atomic_set_crtc_for_connector - set CRTC for connector
> @@ -396,8 +423,8 @@ static s32 __user *get_out_fence_for_connector(struct drm_atomic_state *state,
>   }
>   
>   static int drm_atomic_crtc_set_property(struct drm_crtc *crtc,
> -		struct drm_crtc_state *state, struct drm_property *property,
> -		uint64_t val)
> +		struct drm_crtc_state *state, struct drm_file *file_priv,
> +		struct drm_property *property, uint64_t val)
>   {
>   	struct drm_device *dev = crtc->dev;
>   	struct drm_mode_config *config = &dev->mode_config;
> @@ -406,7 +433,17 @@ static int drm_atomic_crtc_set_property(struct drm_crtc *crtc,
>   
>   	if (property == config->prop_active)
>   		state->active = val;
> -	else if (property == config->prop_mode_id) {
> +	else if (property == crtc->color_pipeline_property) {
> +		/* find DRM colorop object */
> +		struct drm_colorop *colorop = NULL;
> +
> +		colorop = drm_colorop_find(dev, file_priv, val);
> +
> +		if (val && !colorop)
> +			return -EACCES;
> +
> +		drm_atomic_set_colorop_for_crtc(state, colorop);
> +	} else if (property == config->prop_mode_id) {
>   		struct drm_property_blob *mode =
>   			drm_property_lookup_blob(dev, val);
>   		ret = drm_atomic_set_mode_prop_for_crtc(state, mode);
> @@ -487,6 +524,8 @@ drm_atomic_crtc_get_property(struct drm_crtc *crtc,
>   		*val = 0;
>   	else if (property == crtc->scaling_filter_property)
>   		*val = state->scaling_filter;
> +	else if (property == crtc->color_pipeline_property)
> +		*val = (state->color_pipeline) ? state->color_pipeline->base.id : 0;
>   	else if (crtc->funcs->atomic_get_property)
>   		return crtc->funcs->atomic_get_property(crtc, state, property, val);
>   	else {
> @@ -1047,6 +1086,8 @@ int drm_atomic_get_property(struct drm_mode_object *obj,
>   
>   		if (colorop->plane)
>   			WARN_ON(!drm_modeset_is_locked(&colorop->plane->mutex));
> +		else
> +			WARN_ON(!drm_modeset_is_locked(&colorop->crtc->mutex));
>   
>   		ret = drm_atomic_colorop_get_property(colorop,
>   				colorop->state, property, val);
> @@ -1204,7 +1245,7 @@ int drm_atomic_set_property(struct drm_atomic_state *state,
>   		}
>   
>   		ret = drm_atomic_crtc_set_property(crtc,
> -				crtc_state, prop, prop_value);
> +				crtc_state, file_priv, prop, prop_value);
>   		break;
>   	}
>   	case DRM_MODE_OBJECT_PLANE: {
> @@ -1604,6 +1645,7 @@ int drm_mode_atomic_ioctl(struct drm_device *dev,
>   	state->acquire_ctx = &ctx;
>   	state->allow_modeset = !!(arg->flags & DRM_MODE_ATOMIC_ALLOW_MODESET);
>   	state->plane_color_pipeline = file_priv->plane_color_pipeline;
> +	state->post_blend_color_pipeline = file_priv->post_blend_color_pipeline;
>   
>   retry:
>   	copied_objs = 0;
> diff --git a/drivers/gpu/drm/drm_colorop.c b/drivers/gpu/drm/drm_colorop.c
> index a1b36cd488f0a014425a9192ffe5fcc4d2c1afaa..d53de1438d23def74a77730cacd3651131e82cbe 100644
> --- a/drivers/gpu/drm/drm_colorop.c
> +++ b/drivers/gpu/drm/drm_colorop.c
> @@ -90,8 +90,9 @@ static const struct drm_prop_enum_list drm_colorop_lut3d_interpolation_list[] =
>   
>   /* Init Helpers */
>   
> -static int drm_plane_colorop_init(struct drm_device *dev, struct drm_colorop *colorop,
> -			    struct drm_plane *plane, enum drm_colorop_type type, uint32_t flags)
> +static int drm_common_colorop_init(struct drm_device *dev,
> +				   struct drm_colorop *colorop,
> +				   enum drm_colorop_type type, uint32_t flags)
>   {
>   	struct drm_mode_config *config = &dev->mode_config;
>   	struct drm_property *prop;
> @@ -104,7 +105,6 @@ static int drm_plane_colorop_init(struct drm_device *dev, struct drm_colorop *co
>   	colorop->base.properties = &colorop->properties;
>   	colorop->dev = dev;
>   	colorop->type = type;
> -	colorop->plane = plane;
>   	colorop->next = NULL;
>   
>   	list_add_tail(&colorop->head, &config->colorop_list);
> @@ -153,6 +153,34 @@ static int drm_plane_colorop_init(struct drm_device *dev, struct drm_colorop *co
>   	return ret;
>   }
>   
> +static int drm_crtc_colorop_init(struct drm_device *dev,
> +				 struct drm_colorop *colorop,
> +				 struct drm_crtc *crtc,
> +				 enum drm_colorop_type type, uint32_t flags)
> +{
> +	int ret;
> +
> +	ret = drm_common_colorop_init(dev, colorop, type, flags);
> +
> +	colorop->crtc = crtc;
> +
> +	return ret;
> +}
> +
> +static int drm_plane_colorop_init(struct drm_device *dev,
> +				  struct drm_colorop *colorop,
> +				  struct drm_plane *plane,
> +				  enum drm_colorop_type type, uint32_t flags)
> +{
> +	int ret;
> +
> +	ret = drm_common_colorop_init(dev, colorop, type, flags);
> +
> +	colorop->plane = plane;
> +
> +	return ret;
> +}
> +
>   /**
>    * drm_colorop_cleanup - Cleanup a drm_colorop object in color_pipeline
>    *
> @@ -279,29 +307,16 @@ static int drm_colorop_create_data_prop(struct drm_device *dev, struct drm_color
>   	return 0;
>   }
>   
> -/**
> - * drm_plane_colorop_curve_1d_lut_init - Initialize a DRM_COLOROP_1D_LUT
> - *
> - * @dev: DRM device
> - * @colorop: The drm_colorop object to initialize
> - * @plane: The associated drm_plane
> - * @lut_size: LUT size supported by driver
> - * @lut1d_interpolation: 1D LUT interpolation type
> - * @flags: bitmask of misc, see DRM_COLOROP_FLAG_* defines.
> - * @return zero on success, -E value on failure
> - */
> -int drm_plane_colorop_curve_1d_lut_init(struct drm_device *dev, struct drm_colorop *colorop,
> -					struct drm_plane *plane, uint32_t lut_size,
> -					enum drm_colorop_lut1d_interpolation_type lut1d_interpolation,
> -					uint32_t flags)
> +static int
> +drm_common_colorop_curve_1d_lut_init(struct drm_device *dev,
> +				     struct drm_colorop *colorop,
> +				     uint32_t lut_size,
> +				     enum drm_colorop_lut1d_interpolation_type lut1d_interpolation,
> +				     uint32_t flags)
>   {
>   	struct drm_property *prop;
>   	int ret;
>   
> -	ret = drm_plane_colorop_init(dev, colorop, plane, DRM_COLOROP_1D_LUT, flags);
> -	if (ret)
> -		return ret;
> -
>   	/* initialize 1D LUT only attribute */
>   	/* LUT size */
>   	prop = drm_property_create_range(dev, DRM_MODE_PROP_IMMUTABLE | DRM_MODE_PROP_ATOMIC,
> @@ -333,17 +348,69 @@ int drm_plane_colorop_curve_1d_lut_init(struct drm_device *dev, struct drm_color
>   
>   	return 0;
>   }
> -EXPORT_SYMBOL(drm_plane_colorop_curve_1d_lut_init);
>   
> -int drm_plane_colorop_ctm_3x4_init(struct drm_device *dev, struct drm_colorop *colorop,
> -				   struct drm_plane *plane, uint32_t flags)
> +/**
> + * drm_crtc_colorop_curve_1d_lut_init - Initialize a DRM_COLOROP_1D_LUT
> + *
> + * @dev: DRM device
> + * @colorop: The drm_colorop object to initialize
> + * @crtc: The associated drm_crtc
> + * @lut_size: LUT size supported by driver
> + * @lut1d_interpolation: 1D LUT interpolation type
> + * @flags: bitmask of misc, see DRM_COLOROP_FLAG_* defines.
> + * @return zero on success, -E value on failure
> + */
> +int
> +drm_crtc_colorop_curve_1d_lut_init(struct drm_device *dev,
> +				   struct drm_colorop *colorop,
> +				   struct drm_crtc *crtc, uint32_t lut_size,
> +				   enum drm_colorop_lut1d_interpolation_type lut1d_interpolation,
> +				   uint32_t flags)
>   {
>   	int ret;
>   
> -	ret = drm_plane_colorop_init(dev, colorop, plane, DRM_COLOROP_CTM_3X4, flags);
> +	ret = drm_crtc_colorop_init(dev, colorop, crtc, DRM_COLOROP_1D_LUT, flags);
>   	if (ret)
>   		return ret;
>   
> +	return drm_common_colorop_curve_1d_lut_init(dev, colorop, lut_size,
> +						    lut1d_interpolation, flags);
> +}
> +EXPORT_SYMBOL(drm_crtc_colorop_curve_1d_lut_init);
> +
> +/**
> + * drm_plane_colorop_curve_1d_lut_init - Initialize a DRM_COLOROP_1D_LUT
> + *
> + * @dev: DRM device
> + * @colorop: The drm_colorop object to initialize
> + * @plane: The associated drm_plane
> + * @lut_size: LUT size supported by driver
> + * @lut1d_interpolation: 1D LUT interpolation type
> + * @flags: bitmask of misc, see DRM_COLOROP_FLAG_* defines.
> + * @return zero on success, -E value on failure
> + */
> +int
> +drm_plane_colorop_curve_1d_lut_init(struct drm_device *dev, struct drm_colorop *colorop,
> +				    struct drm_plane *plane, uint32_t lut_size,
> +				    enum drm_colorop_lut1d_interpolation_type lut1d_interpolation,
> +				    uint32_t flags)
> +{
> +	int ret;
> +
> +	ret = drm_plane_colorop_init(dev, colorop, plane, DRM_COLOROP_1D_LUT, flags);
> +	if (ret)
> +		return ret;
> +
> +	return drm_common_colorop_curve_1d_lut_init(dev, colorop, lut_size,
> +						    lut1d_interpolation, flags);
> +}
> +EXPORT_SYMBOL(drm_plane_colorop_curve_1d_lut_init);
> +
> +static int drm_common_colorop_ctm_3x4_init(struct drm_device *dev, struct drm_colorop *colorop,
> +					   uint32_t flags)
> +{
> +	int ret;
> +
>   	ret = drm_colorop_create_data_prop(dev, colorop);
>   	if (ret)
>   		return ret;
> @@ -352,6 +419,31 @@ int drm_plane_colorop_ctm_3x4_init(struct drm_device *dev, struct drm_colorop *c
>   
>   	return 0;
>   }
> +
> +int drm_crtc_colorop_ctm_3x4_init(struct drm_device *dev, struct drm_colorop *colorop,
> +				   struct drm_crtc *crtc, uint32_t flags)
> +{
> +	int ret;
> +
> +	ret = drm_crtc_colorop_init(dev, colorop, crtc, DRM_COLOROP_CTM_3X4, flags);
> +	if (ret)
> +		return ret;
> +
> +	return drm_common_colorop_ctm_3x4_init(dev, colorop, flags);
> +}
> +EXPORT_SYMBOL(drm_crtc_colorop_ctm_3x4_init);
> +
> +int drm_plane_colorop_ctm_3x4_init(struct drm_device *dev, struct drm_colorop *colorop,
> +				   struct drm_plane *plane, uint32_t flags)
> +{
> +	int ret;
> +
> +	ret = drm_plane_colorop_init(dev, colorop, plane, DRM_COLOROP_CTM_3X4, flags);
> +	if (ret)
> +		return ret;
> +
> +	return drm_common_colorop_ctm_3x4_init(dev, colorop, flags);
> +}
>   EXPORT_SYMBOL(drm_plane_colorop_ctm_3x4_init);
>   
>   /**
> diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c
> index 4d6dc9ebfdb5bc730b1aff7a184448af7b93f078..f58cfd2131139ff3e613adc4dbb9ddbedf724dc7 100644
> --- a/drivers/gpu/drm/drm_connector.c
> +++ b/drivers/gpu/drm/drm_connector.c
> @@ -3440,6 +3440,7 @@ int drm_mode_getconnector(struct drm_device *dev, void *data,
>   	 */
>   	ret = drm_mode_object_get_properties(&connector->base, file_priv->atomic,
>   			file_priv->plane_color_pipeline,
> +			file_priv->post_blend_color_pipeline,
>   			(uint32_t __user *)(unsigned long)(out_resp->props_ptr),
>   			(uint64_t __user *)(unsigned long)(out_resp->prop_values_ptr),
>   			&out_resp->count_props);
> diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c
> index 46655339003db2a1b43441434839e26f61d79b4e..94238163ff1254c721df39c030bc99a31d3bb92a 100644
> --- a/drivers/gpu/drm/drm_crtc.c
> +++ b/drivers/gpu/drm/drm_crtc.c
> @@ -959,3 +959,80 @@ bool drm_crtc_in_clone_mode(struct drm_crtc_state *crtc_state)
>   	return hweight32(crtc_state->encoder_mask) > 1;
>   }
>   EXPORT_SYMBOL(drm_crtc_in_clone_mode);
> +
> +struct drm_property *
> +drm_common_create_color_pipeline_property(struct drm_device *dev, struct drm_mode_object *obj,
> +					  const struct drm_prop_enum_list *pipelines,
> +					  int num_pipelines)
> +{
> +	struct drm_prop_enum_list *all_pipelines;
> +	struct drm_property *prop;
> +	int len = 0;
> +	int i;
> +
> +	all_pipelines = kcalloc(num_pipelines + 1,
> +				sizeof(*all_pipelines),
> +				GFP_KERNEL);
> +
> +	if (!all_pipelines) {
> +		drm_err(dev, "failed to allocate color pipeline\n");
> +		return ERR_PTR(-ENOMEM);
> +	}
> +
> +	/* Create default Bypass color pipeline */
> +	all_pipelines[len].type = 0;
> +	all_pipelines[len].name = "Bypass";
> +	len++;
> +
> +	/* Add all other color pipelines */
> +	for (i = 0; i < num_pipelines; i++, len++) {
> +		all_pipelines[len].type = pipelines[i].type;
> +		all_pipelines[len].name = pipelines[i].name;
> +	}
> +
> +	prop = drm_property_create_enum(dev, DRM_MODE_PROP_ATOMIC,
> +					"COLOR_PIPELINE",
> +					all_pipelines, len);
> +	if (IS_ERR(prop)) {
> +		kfree(all_pipelines);
> +		return prop;
> +	}
> +
> +	drm_object_attach_property(obj, prop, 0);
> +
> +	kfree(all_pipelines);
> +	return prop;
> +}
> +
> +/**
> + * drm_crtc_create_color_pipeline_property - create a new color pipeline
> + * property
> + *
> + * @crtc: drm CRTC
> + * @pipelines: list of pipelines
> + * @num_pipelines: number of pipelines
> + *
> + * Create the COLOR_PIPELINE CRTC property to specify color pipelines on
> + * the CRTC.
> + *
> + * RETURNS:
> + * Zero for success or -errno
> + */
> +int drm_crtc_create_color_pipeline_property(struct drm_crtc *crtc,
> +					    const struct drm_prop_enum_list *pipelines,
> +					    int num_pipelines)
> +{
> +	struct drm_property *prop;
> +
> +	prop = drm_common_create_color_pipeline_property(crtc->dev,
> +							 &crtc->base,
> +							 pipelines,
> +							 num_pipelines);
> +	if (IS_ERR(prop))
> +		return PTR_ERR(prop);
> +
> +	crtc->color_pipeline_property = prop;
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL(drm_crtc_create_color_pipeline_property);
> diff --git a/drivers/gpu/drm/drm_crtc_internal.h b/drivers/gpu/drm/drm_crtc_internal.h
> index c094092296448093c5cd192ecdc8ea9a50769c90..c53f154e5392a10c326c844b7321666275f9ac02 100644
> --- a/drivers/gpu/drm/drm_crtc_internal.h
> +++ b/drivers/gpu/drm/drm_crtc_internal.h
> @@ -35,6 +35,7 @@
>   #ifndef __DRM_CRTC_INTERNAL_H__
>   #define __DRM_CRTC_INTERNAL_H__
>   
> +#include <drm/drm_property.h>
>   #include <linux/err.h>
>   #include <linux/types.h>
>   
> @@ -79,6 +80,10 @@ int drm_crtc_check_viewport(const struct drm_crtc *crtc,
>   int drm_crtc_register_all(struct drm_device *dev);
>   void drm_crtc_unregister_all(struct drm_device *dev);
>   int drm_crtc_force_disable(struct drm_crtc *crtc);
> +struct drm_property *
> +drm_common_create_color_pipeline_property(struct drm_device *dev, struct drm_mode_object *obj,
> +					  const struct drm_prop_enum_list *pipelines,
> +					  int num_pipelines);
>   
>   struct dma_fence *drm_crtc_create_fence(struct drm_crtc *crtc);
>   
> @@ -164,6 +169,7 @@ void drm_mode_object_unregister(struct drm_device *dev,
>   				struct drm_mode_object *object);
>   int drm_mode_object_get_properties(struct drm_mode_object *obj, bool atomic,
>   				   bool plane_color_pipeline,
> +				   bool post_blend_color_pipeline,
>   				   uint32_t __user *prop_ptr,
>   				   uint64_t __user *prop_values,
>   				   uint32_t *arg_count_props);
> diff --git a/drivers/gpu/drm/drm_ioctl.c b/drivers/gpu/drm/drm_ioctl.c
> index ff193155129e7e863888d8958458978566b144f8..2c81f63fee428ca85f3c626d892ea6097b964e88 100644
> --- a/drivers/gpu/drm/drm_ioctl.c
> +++ b/drivers/gpu/drm/drm_ioctl.c
> @@ -380,6 +380,13 @@ drm_setclientcap(struct drm_device *dev, void *data, struct drm_file *file_priv)
>   			return -EINVAL;
>   		file_priv->plane_color_pipeline = req->value;
>   		break;
> +	case DRM_CLIENT_CAP_POST_BLEND_COLOR_PIPELINE:
> +		if (!file_priv->atomic)
> +			return -EINVAL;
> +		if (req->value > 1)
> +			return -EINVAL;
> +		file_priv->post_blend_color_pipeline = req->value;
> +		break;
>   	default:
>   		return -EINVAL;
>   	}
> diff --git a/drivers/gpu/drm/drm_mode_object.c b/drivers/gpu/drm/drm_mode_object.c
> index b45d501b10c868c6d9b7a5a8760eadbd7b372a6a..c9e20d12e8fd311f71b9d6bc8d575624751d33ad 100644
> --- a/drivers/gpu/drm/drm_mode_object.c
> +++ b/drivers/gpu/drm/drm_mode_object.c
> @@ -388,6 +388,7 @@ EXPORT_SYMBOL(drm_object_property_get_default_value);
>   /* helper for getconnector and getproperties ioctls */
>   int drm_mode_object_get_properties(struct drm_mode_object *obj, bool atomic,
>   				   bool plane_color_pipeline,
> +				   bool post_blend_color_pipeline,
>   				   uint32_t __user *prop_ptr,
>   				   uint64_t __user *prop_values,
>   				   uint32_t *arg_count_props)
> @@ -416,6 +417,24 @@ int drm_mode_object_get_properties(struct drm_mode_object *obj, bool atomic,
>   				continue;
>   		}
>   
> +		if (post_blend_color_pipeline && obj->type == DRM_MODE_OBJECT_CRTC) {
> +			struct drm_crtc *crtc = obj_to_crtc(obj);
> +			struct drm_mode_config mode_config = crtc->dev->mode_config;
> +
> +			if (prop == mode_config.gamma_lut_property ||
> +			    prop == mode_config.degamma_lut_property ||
> +			    prop == mode_config.gamma_lut_size_property ||
> +			    prop == mode_config.ctm_property)
> +				continue;
> +		}
> +
> +		if (!post_blend_color_pipeline && obj->type == DRM_MODE_OBJECT_CRTC) {
> +			struct drm_crtc *crtc = obj_to_crtc(obj);
> +
> +			if (prop == crtc->color_pipeline_property)
> +				continue;
> +		}
> +
>   		if (*arg_count_props > count) {
>   			ret = __drm_object_property_get_value(obj, prop, &val);
>   			if (ret)
> @@ -475,6 +494,7 @@ int drm_mode_obj_get_properties_ioctl(struct drm_device *dev, void *data,
>   
>   	ret = drm_mode_object_get_properties(obj, file_priv->atomic,
>   			file_priv->plane_color_pipeline,
> +			file_priv->post_blend_color_pipeline,
>   			(uint32_t __user *)(unsigned long)(arg->props_ptr),
>   			(uint64_t __user *)(unsigned long)(arg->prop_values_ptr),
>   			&arg->count_props);
> diff --git a/drivers/gpu/drm/drm_plane.c b/drivers/gpu/drm/drm_plane.c
> index f6cfa8ac090c7bc49c7f276993bba7e9800da140..60dbfcab495600dd44c15260a1fa6135db59c6e2 100644
> --- a/drivers/gpu/drm/drm_plane.c
> +++ b/drivers/gpu/drm/drm_plane.c
> @@ -1839,43 +1839,17 @@ int drm_plane_create_color_pipeline_property(struct drm_plane *plane,
>   					     const struct drm_prop_enum_list *pipelines,
>   					     int num_pipelines)
>   {
> -	struct drm_prop_enum_list *all_pipelines;
>   	struct drm_property *prop;
> -	int len = 0;
> -	int i;
> -
> -	all_pipelines = kcalloc(num_pipelines + 1,
> -				sizeof(*all_pipelines),
> -				GFP_KERNEL);
> -
> -	if (!all_pipelines) {
> -		drm_err(plane->dev, "failed to allocate color pipeline\n");
> -		return -ENOMEM;
> -	}
>   
> -	/* Create default Bypass color pipeline */
> -	all_pipelines[len].type = 0;
> -	all_pipelines[len].name = "Bypass";
> -	len++;
> -
> -	/* Add all other color pipelines */
> -	for (i = 0; i < num_pipelines; i++, len++) {
> -		all_pipelines[len].type = pipelines[i].type;
> -		all_pipelines[len].name = pipelines[i].name;
> -	}
> -
> -	prop = drm_property_create_enum(plane->dev, DRM_MODE_PROP_ATOMIC,
> -					"COLOR_PIPELINE",
> -					all_pipelines, len);
> -	if (IS_ERR(prop)) {
> -		kfree(all_pipelines);
> +	prop = drm_common_create_color_pipeline_property(plane->dev,
> +							 &plane->base,
> +							 pipelines,
> +							 num_pipelines);
> +	if (IS_ERR(prop))
>   		return PTR_ERR(prop);
> -	}
>   
> -	drm_object_attach_property(&plane->base, prop, 0);
>   	plane->color_pipeline_property = prop;
>   
> -	kfree(all_pipelines);
>   	return 0;
>   }
>   EXPORT_SYMBOL(drm_plane_create_color_pipeline_property);
> diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h
> index 678708df9cdb90b4266127193a92183069f18688..8c42c584aefbf0034b2163d90538e80099b0dadb 100644
> --- a/include/drm/drm_atomic.h
> +++ b/include/drm/drm_atomic.h
> @@ -482,6 +482,26 @@ struct drm_atomic_state {
>   	 */
>   	bool plane_color_pipeline : 1;
>   
> +	/**
> +	 * @post_blend_color_pipeline:
> +	 *
> +	 * Indicates whether this atomic state originated with a client that
> +	 * set the DRM_CLIENT_CAP_POST_BLEND_COLOR_PIPELINE.
> +	 *
> +	 * Drivers and helper functions should use this to ignore legacy
> +	 * properties that are incompatible with the drm_crtc COLOR_PIPELINE
> +	 * behavior, such as:
> +	 *
> +	 *  - GAMMA_LUT
> +	 *  - DEGAMMA_LUT
> +	 *  - GAMMA_LUT_SIZE
> +	 *  - CTM
> +	 *
> +	 * or any other driver-specific properties that might affect pixel
> +	 * values.
> +	 */
> +	bool post_blend_color_pipeline : 1;
> +
>   	/**
>   	 * @colorops:
>   	 *
> diff --git a/include/drm/drm_atomic_uapi.h b/include/drm/drm_atomic_uapi.h
> index 4363155233267b93767c895fa6085544e2277442..4dc191f6f929d73deee9812025c48275a33cf770 100644
> --- a/include/drm/drm_atomic_uapi.h
> +++ b/include/drm/drm_atomic_uapi.h
> @@ -52,6 +52,8 @@ void drm_atomic_set_fb_for_plane(struct drm_plane_state *plane_state,
>   				 struct drm_framebuffer *fb);
>   void drm_atomic_set_colorop_for_plane(struct drm_plane_state *plane_state,
>   				      struct drm_colorop *colorop);
> +void drm_atomic_set_colorop_for_crtc(struct drm_crtc_state *crtc_state,
> +				     struct drm_colorop *colorop);
>   int __must_check
>   drm_atomic_set_crtc_for_connector(struct drm_connector_state *conn_state,
>   				  struct drm_crtc *crtc);
> diff --git a/include/drm/drm_colorop.h b/include/drm/drm_colorop.h
> index d61c6c40e47162cb8b1e7db58b6746c43ac5d202..710a6199ebc5498a3f664de39ea07dbc95944eb7 100644
> --- a/include/drm/drm_colorop.h
> +++ b/include/drm/drm_colorop.h
> @@ -206,10 +206,16 @@ struct drm_colorop {
>   	/**
>   	 * @plane:
>   	 *
> -	 * The plane on which the colorop sits. A drm_colorop is always unique
> -	 * to a plane.
> +	 * The plane on which the colorop sits if it is a pre-blend colorop.
> +	 * In this case it is unique to the plane.
> +	 *
> +	 * @crtc:
> +	 *
> +	 * The CRTC on which the colorop sits if it is a post-blend colorop.
> +	 * In this case it is unique to the CRTC.
>   	 */
>   	struct drm_plane *plane;
> +	struct drm_crtc *crtc;
>   
>   	/**
>   	 * @state:
> @@ -370,6 +376,10 @@ static inline struct drm_colorop *drm_colorop_find(struct drm_device *dev,
>   
>   void drm_colorop_pipeline_destroy(struct drm_device *dev);
>   
> +int drm_crtc_colorop_curve_1d_lut_init(struct drm_device *dev, struct drm_colorop *colorop,
> +				       struct drm_crtc *crtc, uint32_t lut_size,
> +				       enum drm_colorop_lut1d_interpolation_type lut1d_interpolation,
> +				       uint32_t flags);
>   int drm_plane_colorop_curve_1d_init(struct drm_device *dev, struct drm_colorop *colorop,
>   				    struct drm_plane *plane, u64 supported_tfs, uint32_t flags);
>   int drm_plane_colorop_curve_1d_lut_init(struct drm_device *dev, struct drm_colorop *colorop,
> @@ -378,6 +388,8 @@ int drm_plane_colorop_curve_1d_lut_init(struct drm_device *dev, struct drm_color
>   					uint32_t flags);
>   int drm_plane_colorop_ctm_3x4_init(struct drm_device *dev, struct drm_colorop *colorop,
>   				   struct drm_plane *plane, uint32_t flags);
> +int drm_crtc_colorop_ctm_3x4_init(struct drm_device *dev, struct drm_colorop *colorop,
> +				   struct drm_crtc *crtc, uint32_t flags);
>   int drm_plane_colorop_mult_init(struct drm_device *dev, struct drm_colorop *colorop,
>   				struct drm_plane *plane, uint32_t flags);
>   int drm_plane_colorop_3dlut_init(struct drm_device *dev, struct drm_colorop *colorop,
> diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h
> index caa56e039da2a748cf40ebf45b37158acda439d9..df03637ca25abd45e96b5944229297f776fd046d 100644
> --- a/include/drm/drm_crtc.h
> +++ b/include/drm/drm_crtc.h
> @@ -274,6 +274,14 @@ struct drm_crtc_state {
>   	 */
>   	struct drm_property_blob *gamma_lut;
>   
> +	/**
> +	 * @color_pipeline:
> +	 *
> +	 * The first colorop of the active color pipeline, or NULL, if no
> +	 * color pipeline is active.
> +	 */
> +	struct drm_colorop *color_pipeline;
> +
>   	/**
>   	 * @target_vblank:
>   	 *
> @@ -1088,6 +1096,14 @@ struct drm_crtc {
>   	 */
>   	struct drm_property *scaling_filter_property;
>   
> +	/**
> +	 * @color_pipeline_property:
> +	 *
> +	 * Optional "COLOR_PIPELINE" enum property for specifying
> +	 * a color pipeline to use on the CRTC.
> +	 */
> +	struct drm_property *color_pipeline_property;
> +
>   	/**
>   	 * @state:
>   	 *
> @@ -1323,5 +1339,8 @@ static inline struct drm_crtc *drm_crtc_find(struct drm_device *dev,
>   
>   int drm_crtc_create_scaling_filter_property(struct drm_crtc *crtc,
>   					    unsigned int supported_filters);
> +int drm_crtc_create_color_pipeline_property(struct drm_crtc *crtc,
> +					     const struct drm_prop_enum_list *pipelines,
> +					     int num_pipelines);
>   bool drm_crtc_in_clone_mode(struct drm_crtc_state *crtc_state);
>   #endif /* __DRM_CRTC_H__ */
> diff --git a/include/drm/drm_file.h b/include/drm/drm_file.h
> index 1a3018e4a537b3341acb50187d47371f6b781b9d..42b9a43baa18079af8ec2ea5b1484b23c497beb0 100644
> --- a/include/drm/drm_file.h
> +++ b/include/drm/drm_file.h
> @@ -213,6 +213,13 @@ struct drm_file {
>   	 */
>   	bool plane_color_pipeline;
>   
> +	/**
> +	 * @post_blend_color_pipeline:
> +	 *
> +	 * True if client understands post-blend color pipelines
> +	 */
> +	bool post_blend_color_pipeline;
> +
>   	/**
>   	 * @was_master:
>   	 *
> diff --git a/include/uapi/drm/drm.h b/include/uapi/drm/drm.h
> index 27cc159c1d275c7a7fe057840ef792f30a582bb7..1191b142ebaa5478376308f169f9ce8591580d9d 100644
> --- a/include/uapi/drm/drm.h
> +++ b/include/uapi/drm/drm.h
> @@ -921,6 +921,22 @@ struct drm_get_cap {
>    */
>   #define DRM_CLIENT_CAP_PLANE_COLOR_PIPELINE	7
>   
> +/**
> + * DRM_CLIENT_CAP_POST_BLEND_COLOR_PIPELINE
> + *
> + * If set to 1 the DRM core will allow setting the COLOR_PIPELINE
> + * property on a &drm_crtc, as well as drm_colorop properties.
> + *
> + * Setting of these crtc properties will be rejected when this client
> + * cap is set:

I don't know enough the uAPI of DRM, but if I understand your patch 
correctly, it will not reject GAMMA_LUT/DEGAMMA_LUT/CTM, only unlist 
them from the get_properties syscall. Did I overlooked something?

> + * - GAMMA_LUT
> + * - DEGAMMA_LUT
> + * - CTM
> + *
> + * The client must enable &DRM_CLIENT_CAP_ATOMIC first.
> + */
> +#define DRM_CLIENT_CAP_POST_BLEND_COLOR_PIPELINE	8
> +
>   /* DRM_IOCTL_SET_CLIENT_CAP ioctl argument type */
>   struct drm_set_client_cap {
>   	__u64 capability;
> 

-- 
Louis Chauvet, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com

Re: [PATCH RFC 1/5] drm: Support post-blend color pipeline API
Posted by Nícolas F. R. A. Prado 3 weeks, 3 days ago
On Fri, 2025-09-05 at 19:55 +0200, Louis Chauvet wrote:
> 
> 
> Le 22/08/2025 à 20:36, Nícolas F. R. A. Prado a écrit :
> > Introduce support for a post-blend color pipeline API analogous to
> > the
> > pre-blend color pipeline API. While the pre-blend color pipeline
> > was
> > configured through a COLOR_PIPELINE property attached to a
> > drm_plane,
> > the post-blend color pipeline is configured through a
> > COLOR_PIPELINE
> > property on the drm_crtc.
> > 
> > Since colorops can now be attached to either a drm_plane or a
> > drm_crtc,
> > rework the helpers to account for both cases.
> > 
> > Also introduce a new cap, DRM_CLIENT_CAP_POST_BLEND_COLOR_PIPELINE,
> > to
> > enable support for post-blend color pipelines, and prevent the now
> > legacy GAMMA_LUT, DEGAMMA_LUT, GAMMA_LUT_SIZE and CTM properties
> > from
> > being exposed.
> > 
> > Signed-off-by: Nícolas F. R. A. Prado <nfraprado@collabora.com>
> > ---
> >   drivers/gpu/drm/drm_atomic.c        |  32 ++++++--
> >   drivers/gpu/drm/drm_atomic_uapi.c   |  50 ++++++++++++-
> >   drivers/gpu/drm/drm_colorop.c       | 144
> > +++++++++++++++++++++++++++++-------
> >   drivers/gpu/drm/drm_connector.c     |   1 +
> >   drivers/gpu/drm/drm_crtc.c          |  77 +++++++++++++++++++
> >   drivers/gpu/drm/drm_crtc_internal.h |   6 ++
> >   drivers/gpu/drm/drm_ioctl.c         |   7 ++
> >   drivers/gpu/drm/drm_mode_object.c   |  20 +++++
> >   drivers/gpu/drm/drm_plane.c         |  36 ++-------
> >   include/drm/drm_atomic.h            |  20 +++++
> >   include/drm/drm_atomic_uapi.h       |   2 +
> >   include/drm/drm_colorop.h           |  16 +++-
> >   include/drm/drm_crtc.h              |  19 +++++
> >   include/drm/drm_file.h              |   7 ++
> >   include/uapi/drm/drm.h              |  16 ++++
> >   15 files changed, 383 insertions(+), 70 deletions(-)
> > 
> > diff --git a/drivers/gpu/drm/drm_atomic.c
> > b/drivers/gpu/drm/drm_atomic.c
> > index
> > 3ab32fe7fe1cbf9057c3763d979638dce013d82b..558d389d59d9a44d3cd1048ed
> > 365848f62b4d62d 100644
> > --- a/drivers/gpu/drm/drm_atomic.c
> > +++ b/drivers/gpu/drm/drm_atomic.c
> > @@ -472,6 +472,8 @@ static void drm_atomic_crtc_print_state(struct
> > drm_printer *p,
> >   	drm_printf(p, "\tplane_mask=%x\n", state->plane_mask);
> >   	drm_printf(p, "\tconnector_mask=%x\n", state-
> > >connector_mask);
> >   	drm_printf(p, "\tencoder_mask=%x\n", state->encoder_mask);
> > +	drm_printf(p, "\tcolor-pipeline=%d\n",
> > +		   state->color_pipeline ? state->color_pipeline-
> > >base.id : 0);
> 
> This could be in a separate patch / suggested to the initial series.

Right, I'll make sure to split the changes in this commit further for
v2.

> 
> >   	drm_printf(p, "\tmode: " DRM_MODE_FMT "\n",
> > DRM_MODE_ARG(&state->mode));
> >   
> >   	if (crtc->funcs->atomic_print_state)
> > @@ -617,9 +619,15 @@ drm_atomic_get_colorop_state(struct
> > drm_atomic_state *state,
> >   	if (colorop_state)
> >   		return colorop_state;
> >   
> > -	ret = drm_modeset_lock(&colorop->plane->mutex, state-
> > >acquire_ctx);
> > -	if (ret)
> > -		return ERR_PTR(ret);
> > +	if (colorop->plane) {
> > +		ret = drm_modeset_lock(&colorop->plane->mutex,
> > state->acquire_ctx);
> > +		if (ret)
> > +			return ERR_PTR(ret);
> > +	} else {
> > +		ret = drm_modeset_lock(&colorop->crtc->mutex,
> > state->acquire_ctx);
> > +		if (ret)
> > +			return ERR_PTR(ret);
> > +	}
> 
> Two suggestions here:
> 
> Maybe you can create `colorop_modeset_lock/unlock` helpers to avoid
> code 
> repetition.
> 
> Can you also change it to
> 
> 	if (colorop->plane)
> 		...
> 	else if (colorop->crtc)
> 		...
> 	else
> 		drm_err("Dangling colorop, it must be attached to a
> plane or a CRTC")
> 		return ERR_PTR
> 
> ?
> 
> This way it will avoid issues if someone add support to attach
> colorop 
> to other drm parts and forgot to update locking in some places.

Yes, both suggestions sound good, I'll apply them for v2.

> > diff --git a/include/uapi/drm/drm.h b/include/uapi/drm/drm.h
> > index
> > 27cc159c1d275c7a7fe057840ef792f30a582bb7..1191b142ebaa5478376308f16
> > 9f9ce8591580d9d 100644
> > --- a/include/uapi/drm/drm.h
> > +++ b/include/uapi/drm/drm.h
> > @@ -921,6 +921,22 @@ struct drm_get_cap {
> >    */
> >   #define DRM_CLIENT_CAP_PLANE_COLOR_PIPELINE	7
> >   
> > +/**
> > + * DRM_CLIENT_CAP_POST_BLEND_COLOR_PIPELINE
> > + *
> > + * If set to 1 the DRM core will allow setting the COLOR_PIPELINE
> > + * property on a &drm_crtc, as well as drm_colorop properties.
> > + *
> > + * Setting of these crtc properties will be rejected when this
> > client
> > + * cap is set:
> 
> I don't know enough the uAPI of DRM, but if I understand your patch 
> correctly, it will not reject GAMMA_LUT/DEGAMMA_LUT/CTM, only unlist 
> them from the get_properties syscall. Did I overlooked something?

You're right. This was originally based on v10 of the pre-blend series
(https://lore.kernel.org/dri-devel/20250617041746.2884343-12-alex.hung@amd.com/
), which did in fact return an error when the properties were set with
the cap enabled, but v11
(https://lore.kernel.org/all/20250815035047.3319284-12-alex.hung@amd.com/
) switched to just unlisting the properties, so I also updated the code
here accordingly for this RFC, but forgot to update this text, and
apparently so did the original series.

From the discussion in another thread
(https://lore.kernel.org/dri-devel/20250822-mtk-post-blend-color-pipeline-v1-0-a9446d4aca82@collabora.com/T/#m1e8ba77ef54453b26ae70e1f1699ed17afec91e3
) we might make these properties read-only, and in which case we'll
have to make them listed but error when set again, so I'll wait for
that discussion to be resolved before updating this and forwarding that
feedback to the pre-blend series. But thanks for pointing out the
inconsistency anyway.

> 
> > + * - GAMMA_LUT
> > + * - DEGAMMA_LUT
> > + * - CTM
> > + *
> > + * The client must enable &DRM_CLIENT_CAP_ATOMIC first.
> > + */
> > +#define DRM_CLIENT_CAP_POST_BLEND_COLOR_PIPELINE	8
> > +
> >   /* DRM_IOCTL_SET_CLIENT_CAP ioctl argument type */
> >   struct drm_set_client_cap {
> >   	__u64 capability;
> > 

-- 
Thanks,

Nícolas
Re: [PATCH RFC 1/5] drm: Support post-blend color pipeline API
Posted by Sebastian Wick 1 month, 1 week ago
On Fri Aug 22, 2025 at 8:36 PM CEST, Nícolas F. R. A. Prado wrote:
> Introduce support for a post-blend color pipeline API analogous to the
> pre-blend color pipeline API. While the pre-blend color pipeline was
> configured through a COLOR_PIPELINE property attached to a drm_plane,
> the post-blend color pipeline is configured through a COLOR_PIPELINE
> property on the drm_crtc.
>
> Since colorops can now be attached to either a drm_plane or a drm_crtc,
> rework the helpers to account for both cases.
>
> Also introduce a new cap, DRM_CLIENT_CAP_POST_BLEND_COLOR_PIPELINE, to
> enable support for post-blend color pipelines, and prevent the now
> legacy GAMMA_LUT, DEGAMMA_LUT, GAMMA_LUT_SIZE and CTM properties from
> being exposed.

Hey,

Please note that you'll also have to deprecate the semi-standard
Broadcast RGB property. It serves two purposes at once: it changes the
values between the color range (similar to COLOR_RANGE but at the other
end) and informats the sink of the range as well.

So the post blending color pipeline will need something like an inverse
COLOR_RANGE op.

We will also need a new connector property where user space can select
the color range, which does not change the pixel values, and only
exposes options that can be achieved (default sink behavior, full range
infoframe, limited range infoframe).

>
> Signed-off-by: Nícolas F. R. A. Prado <nfraprado@collabora.com>
> ---
>  drivers/gpu/drm/drm_atomic.c        |  32 ++++++--
>  drivers/gpu/drm/drm_atomic_uapi.c   |  50 ++++++++++++-
>  drivers/gpu/drm/drm_colorop.c       | 144 +++++++++++++++++++++++++++++-------
>  drivers/gpu/drm/drm_connector.c     |   1 +
>  drivers/gpu/drm/drm_crtc.c          |  77 +++++++++++++++++++
>  drivers/gpu/drm/drm_crtc_internal.h |   6 ++
>  drivers/gpu/drm/drm_ioctl.c         |   7 ++
>  drivers/gpu/drm/drm_mode_object.c   |  20 +++++
>  drivers/gpu/drm/drm_plane.c         |  36 ++-------
>  include/drm/drm_atomic.h            |  20 +++++
>  include/drm/drm_atomic_uapi.h       |   2 +
>  include/drm/drm_colorop.h           |  16 +++-
>  include/drm/drm_crtc.h              |  19 +++++
>  include/drm/drm_file.h              |   7 ++
>  include/uapi/drm/drm.h              |  16 ++++
>  15 files changed, 383 insertions(+), 70 deletions(-)
>
> diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
> index 3ab32fe7fe1cbf9057c3763d979638dce013d82b..558d389d59d9a44d3cd1048ed365848f62b4d62d 100644
> --- a/drivers/gpu/drm/drm_atomic.c
> +++ b/drivers/gpu/drm/drm_atomic.c
> @@ -472,6 +472,8 @@ static void drm_atomic_crtc_print_state(struct drm_printer *p,
>  	drm_printf(p, "\tplane_mask=%x\n", state->plane_mask);
>  	drm_printf(p, "\tconnector_mask=%x\n", state->connector_mask);
>  	drm_printf(p, "\tencoder_mask=%x\n", state->encoder_mask);
> +	drm_printf(p, "\tcolor-pipeline=%d\n",
> +		   state->color_pipeline ? state->color_pipeline->base.id : 0);
>  	drm_printf(p, "\tmode: " DRM_MODE_FMT "\n", DRM_MODE_ARG(&state->mode));
>  
>  	if (crtc->funcs->atomic_print_state)
> @@ -617,9 +619,15 @@ drm_atomic_get_colorop_state(struct drm_atomic_state *state,
>  	if (colorop_state)
>  		return colorop_state;
>  
> -	ret = drm_modeset_lock(&colorop->plane->mutex, state->acquire_ctx);
> -	if (ret)
> -		return ERR_PTR(ret);
> +	if (colorop->plane) {
> +		ret = drm_modeset_lock(&colorop->plane->mutex, state->acquire_ctx);
> +		if (ret)
> +			return ERR_PTR(ret);
> +	} else {
> +		ret = drm_modeset_lock(&colorop->crtc->mutex, state->acquire_ctx);
> +		if (ret)
> +			return ERR_PTR(ret);
> +	}
>  
>  	colorop_state = drm_atomic_helper_colorop_duplicate_state(colorop);
>  	if (!colorop_state)
> @@ -2003,11 +2011,21 @@ static void __drm_state_dump(struct drm_device *dev, struct drm_printer *p,
>  		return;
>  
>  	list_for_each_entry(colorop, &config->colorop_list, head) {
> -		if (take_locks)
> -			drm_modeset_lock(&colorop->plane->mutex, NULL);
> +		if (take_locks) {
> +			if (colorop->plane)
> +				drm_modeset_lock(&colorop->plane->mutex, NULL);
> +			else
> +				drm_modeset_lock(&colorop->crtc->mutex, NULL);
> +
> +		}
>  		drm_atomic_colorop_print_state(p, colorop->state);
> -		if (take_locks)
> -			drm_modeset_unlock(&colorop->plane->mutex);
> +		if (take_locks) {
> +			if (colorop->plane)
> +				drm_modeset_unlock(&colorop->plane->mutex);
> +			else
> +				drm_modeset_unlock(&colorop->crtc->mutex);
> +
> +		}
>  	}
>  
>  	list_for_each_entry(plane, &config->plane_list, head) {
> diff --git a/drivers/gpu/drm/drm_atomic_uapi.c b/drivers/gpu/drm/drm_atomic_uapi.c
> index b7cc6945864274bedd21dd5b73494f9aae216888..a826758cf0b6205e2ba49734070bc83ffb7c08df 100644
> --- a/drivers/gpu/drm/drm_atomic_uapi.c
> +++ b/drivers/gpu/drm/drm_atomic_uapi.c
> @@ -287,6 +287,33 @@ drm_atomic_set_colorop_for_plane(struct drm_plane_state *plane_state,
>  }
>  EXPORT_SYMBOL(drm_atomic_set_colorop_for_plane);
>  
> +/**
> + * drm_atomic_set_colorop_for_crtc - set colorop for crtc
> + * @crtc_state: atomic state object for the crtc
> + * @colorop: colorop to use for the crtc
> + *
> + * Helper function to select the color pipeline on a crtc by setting
> + * it to the first drm_colorop element of the pipeline.
> + */
> +void
> +drm_atomic_set_colorop_for_crtc(struct drm_crtc_state *crtc_state,
> +				 struct drm_colorop *colorop)
> +{
> +	struct drm_crtc *crtc = crtc_state->crtc;
> +
> +	if (colorop)
> +		drm_dbg_atomic(crtc->dev,
> +			       "Set [COLOROP:%d] for [CRTC:%d:%s] state %p\n",
> +			       colorop->base.id, crtc->base.id, crtc->name,
> +			       crtc_state);
> +	else
> +		drm_dbg_atomic(crtc->dev,
> +			       "Set [NOCOLOROP] for [CRTC:%d:%s] state %p\n",
> +			       crtc->base.id, crtc->name, crtc_state);
> +
> +	crtc_state->color_pipeline = colorop;
> +}
> +EXPORT_SYMBOL(drm_atomic_set_colorop_for_crtc);
>  
>  /**
>   * drm_atomic_set_crtc_for_connector - set CRTC for connector
> @@ -396,8 +423,8 @@ static s32 __user *get_out_fence_for_connector(struct drm_atomic_state *state,
>  }
>  
>  static int drm_atomic_crtc_set_property(struct drm_crtc *crtc,
> -		struct drm_crtc_state *state, struct drm_property *property,
> -		uint64_t val)
> +		struct drm_crtc_state *state, struct drm_file *file_priv,
> +		struct drm_property *property, uint64_t val)
>  {
>  	struct drm_device *dev = crtc->dev;
>  	struct drm_mode_config *config = &dev->mode_config;
> @@ -406,7 +433,17 @@ static int drm_atomic_crtc_set_property(struct drm_crtc *crtc,
>  
>  	if (property == config->prop_active)
>  		state->active = val;
> -	else if (property == config->prop_mode_id) {
> +	else if (property == crtc->color_pipeline_property) {
> +		/* find DRM colorop object */
> +		struct drm_colorop *colorop = NULL;
> +
> +		colorop = drm_colorop_find(dev, file_priv, val);
> +
> +		if (val && !colorop)
> +			return -EACCES;
> +
> +		drm_atomic_set_colorop_for_crtc(state, colorop);
> +	} else if (property == config->prop_mode_id) {
>  		struct drm_property_blob *mode =
>  			drm_property_lookup_blob(dev, val);
>  		ret = drm_atomic_set_mode_prop_for_crtc(state, mode);
> @@ -487,6 +524,8 @@ drm_atomic_crtc_get_property(struct drm_crtc *crtc,
>  		*val = 0;
>  	else if (property == crtc->scaling_filter_property)
>  		*val = state->scaling_filter;
> +	else if (property == crtc->color_pipeline_property)
> +		*val = (state->color_pipeline) ? state->color_pipeline->base.id : 0;
>  	else if (crtc->funcs->atomic_get_property)
>  		return crtc->funcs->atomic_get_property(crtc, state, property, val);
>  	else {
> @@ -1047,6 +1086,8 @@ int drm_atomic_get_property(struct drm_mode_object *obj,
>  
>  		if (colorop->plane)
>  			WARN_ON(!drm_modeset_is_locked(&colorop->plane->mutex));
> +		else
> +			WARN_ON(!drm_modeset_is_locked(&colorop->crtc->mutex));
>  
>  		ret = drm_atomic_colorop_get_property(colorop,
>  				colorop->state, property, val);
> @@ -1204,7 +1245,7 @@ int drm_atomic_set_property(struct drm_atomic_state *state,
>  		}
>  
>  		ret = drm_atomic_crtc_set_property(crtc,
> -				crtc_state, prop, prop_value);
> +				crtc_state, file_priv, prop, prop_value);
>  		break;
>  	}
>  	case DRM_MODE_OBJECT_PLANE: {
> @@ -1604,6 +1645,7 @@ int drm_mode_atomic_ioctl(struct drm_device *dev,
>  	state->acquire_ctx = &ctx;
>  	state->allow_modeset = !!(arg->flags & DRM_MODE_ATOMIC_ALLOW_MODESET);
>  	state->plane_color_pipeline = file_priv->plane_color_pipeline;
> +	state->post_blend_color_pipeline = file_priv->post_blend_color_pipeline;
>  
>  retry:
>  	copied_objs = 0;
> diff --git a/drivers/gpu/drm/drm_colorop.c b/drivers/gpu/drm/drm_colorop.c
> index a1b36cd488f0a014425a9192ffe5fcc4d2c1afaa..d53de1438d23def74a77730cacd3651131e82cbe 100644
> --- a/drivers/gpu/drm/drm_colorop.c
> +++ b/drivers/gpu/drm/drm_colorop.c
> @@ -90,8 +90,9 @@ static const struct drm_prop_enum_list drm_colorop_lut3d_interpolation_list[] =
>  
>  /* Init Helpers */
>  
> -static int drm_plane_colorop_init(struct drm_device *dev, struct drm_colorop *colorop,
> -			    struct drm_plane *plane, enum drm_colorop_type type, uint32_t flags)
> +static int drm_common_colorop_init(struct drm_device *dev,
> +				   struct drm_colorop *colorop,
> +				   enum drm_colorop_type type, uint32_t flags)
>  {
>  	struct drm_mode_config *config = &dev->mode_config;
>  	struct drm_property *prop;
> @@ -104,7 +105,6 @@ static int drm_plane_colorop_init(struct drm_device *dev, struct drm_colorop *co
>  	colorop->base.properties = &colorop->properties;
>  	colorop->dev = dev;
>  	colorop->type = type;
> -	colorop->plane = plane;
>  	colorop->next = NULL;
>  
>  	list_add_tail(&colorop->head, &config->colorop_list);
> @@ -153,6 +153,34 @@ static int drm_plane_colorop_init(struct drm_device *dev, struct drm_colorop *co
>  	return ret;
>  }
>  
> +static int drm_crtc_colorop_init(struct drm_device *dev,
> +				 struct drm_colorop *colorop,
> +				 struct drm_crtc *crtc,
> +				 enum drm_colorop_type type, uint32_t flags)
> +{
> +	int ret;
> +
> +	ret = drm_common_colorop_init(dev, colorop, type, flags);
> +
> +	colorop->crtc = crtc;
> +
> +	return ret;
> +}
> +
> +static int drm_plane_colorop_init(struct drm_device *dev,
> +				  struct drm_colorop *colorop,
> +				  struct drm_plane *plane,
> +				  enum drm_colorop_type type, uint32_t flags)
> +{
> +	int ret;
> +
> +	ret = drm_common_colorop_init(dev, colorop, type, flags);
> +
> +	colorop->plane = plane;
> +
> +	return ret;
> +}
> +
>  /**
>   * drm_colorop_cleanup - Cleanup a drm_colorop object in color_pipeline
>   *
> @@ -279,29 +307,16 @@ static int drm_colorop_create_data_prop(struct drm_device *dev, struct drm_color
>  	return 0;
>  }
>  
> -/**
> - * drm_plane_colorop_curve_1d_lut_init - Initialize a DRM_COLOROP_1D_LUT
> - *
> - * @dev: DRM device
> - * @colorop: The drm_colorop object to initialize
> - * @plane: The associated drm_plane
> - * @lut_size: LUT size supported by driver
> - * @lut1d_interpolation: 1D LUT interpolation type
> - * @flags: bitmask of misc, see DRM_COLOROP_FLAG_* defines.
> - * @return zero on success, -E value on failure
> - */
> -int drm_plane_colorop_curve_1d_lut_init(struct drm_device *dev, struct drm_colorop *colorop,
> -					struct drm_plane *plane, uint32_t lut_size,
> -					enum drm_colorop_lut1d_interpolation_type lut1d_interpolation,
> -					uint32_t flags)
> +static int
> +drm_common_colorop_curve_1d_lut_init(struct drm_device *dev,
> +				     struct drm_colorop *colorop,
> +				     uint32_t lut_size,
> +				     enum drm_colorop_lut1d_interpolation_type lut1d_interpolation,
> +				     uint32_t flags)
>  {
>  	struct drm_property *prop;
>  	int ret;
>  
> -	ret = drm_plane_colorop_init(dev, colorop, plane, DRM_COLOROP_1D_LUT, flags);
> -	if (ret)
> -		return ret;
> -
>  	/* initialize 1D LUT only attribute */
>  	/* LUT size */
>  	prop = drm_property_create_range(dev, DRM_MODE_PROP_IMMUTABLE | DRM_MODE_PROP_ATOMIC,
> @@ -333,17 +348,69 @@ int drm_plane_colorop_curve_1d_lut_init(struct drm_device *dev, struct drm_color
>  
>  	return 0;
>  }
> -EXPORT_SYMBOL(drm_plane_colorop_curve_1d_lut_init);
>  
> -int drm_plane_colorop_ctm_3x4_init(struct drm_device *dev, struct drm_colorop *colorop,
> -				   struct drm_plane *plane, uint32_t flags)
> +/**
> + * drm_crtc_colorop_curve_1d_lut_init - Initialize a DRM_COLOROP_1D_LUT
> + *
> + * @dev: DRM device
> + * @colorop: The drm_colorop object to initialize
> + * @crtc: The associated drm_crtc
> + * @lut_size: LUT size supported by driver
> + * @lut1d_interpolation: 1D LUT interpolation type
> + * @flags: bitmask of misc, see DRM_COLOROP_FLAG_* defines.
> + * @return zero on success, -E value on failure
> + */
> +int
> +drm_crtc_colorop_curve_1d_lut_init(struct drm_device *dev,
> +				   struct drm_colorop *colorop,
> +				   struct drm_crtc *crtc, uint32_t lut_size,
> +				   enum drm_colorop_lut1d_interpolation_type lut1d_interpolation,
> +				   uint32_t flags)
>  {
>  	int ret;
>  
> -	ret = drm_plane_colorop_init(dev, colorop, plane, DRM_COLOROP_CTM_3X4, flags);
> +	ret = drm_crtc_colorop_init(dev, colorop, crtc, DRM_COLOROP_1D_LUT, flags);
>  	if (ret)
>  		return ret;
>  
> +	return drm_common_colorop_curve_1d_lut_init(dev, colorop, lut_size,
> +						    lut1d_interpolation, flags);
> +}
> +EXPORT_SYMBOL(drm_crtc_colorop_curve_1d_lut_init);
> +
> +/**
> + * drm_plane_colorop_curve_1d_lut_init - Initialize a DRM_COLOROP_1D_LUT
> + *
> + * @dev: DRM device
> + * @colorop: The drm_colorop object to initialize
> + * @plane: The associated drm_plane
> + * @lut_size: LUT size supported by driver
> + * @lut1d_interpolation: 1D LUT interpolation type
> + * @flags: bitmask of misc, see DRM_COLOROP_FLAG_* defines.
> + * @return zero on success, -E value on failure
> + */
> +int
> +drm_plane_colorop_curve_1d_lut_init(struct drm_device *dev, struct drm_colorop *colorop,
> +				    struct drm_plane *plane, uint32_t lut_size,
> +				    enum drm_colorop_lut1d_interpolation_type lut1d_interpolation,
> +				    uint32_t flags)
> +{
> +	int ret;
> +
> +	ret = drm_plane_colorop_init(dev, colorop, plane, DRM_COLOROP_1D_LUT, flags);
> +	if (ret)
> +		return ret;
> +
> +	return drm_common_colorop_curve_1d_lut_init(dev, colorop, lut_size,
> +						    lut1d_interpolation, flags);
> +}
> +EXPORT_SYMBOL(drm_plane_colorop_curve_1d_lut_init);
> +
> +static int drm_common_colorop_ctm_3x4_init(struct drm_device *dev, struct drm_colorop *colorop,
> +					   uint32_t flags)
> +{
> +	int ret;
> +
>  	ret = drm_colorop_create_data_prop(dev, colorop);
>  	if (ret)
>  		return ret;
> @@ -352,6 +419,31 @@ int drm_plane_colorop_ctm_3x4_init(struct drm_device *dev, struct drm_colorop *c
>  
>  	return 0;
>  }
> +
> +int drm_crtc_colorop_ctm_3x4_init(struct drm_device *dev, struct drm_colorop *colorop,
> +				   struct drm_crtc *crtc, uint32_t flags)
> +{
> +	int ret;
> +
> +	ret = drm_crtc_colorop_init(dev, colorop, crtc, DRM_COLOROP_CTM_3X4, flags);
> +	if (ret)
> +		return ret;
> +
> +	return drm_common_colorop_ctm_3x4_init(dev, colorop, flags);
> +}
> +EXPORT_SYMBOL(drm_crtc_colorop_ctm_3x4_init);
> +
> +int drm_plane_colorop_ctm_3x4_init(struct drm_device *dev, struct drm_colorop *colorop,
> +				   struct drm_plane *plane, uint32_t flags)
> +{
> +	int ret;
> +
> +	ret = drm_plane_colorop_init(dev, colorop, plane, DRM_COLOROP_CTM_3X4, flags);
> +	if (ret)
> +		return ret;
> +
> +	return drm_common_colorop_ctm_3x4_init(dev, colorop, flags);
> +}
>  EXPORT_SYMBOL(drm_plane_colorop_ctm_3x4_init);
>  
>  /**
> diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c
> index 4d6dc9ebfdb5bc730b1aff7a184448af7b93f078..f58cfd2131139ff3e613adc4dbb9ddbedf724dc7 100644
> --- a/drivers/gpu/drm/drm_connector.c
> +++ b/drivers/gpu/drm/drm_connector.c
> @@ -3440,6 +3440,7 @@ int drm_mode_getconnector(struct drm_device *dev, void *data,
>  	 */
>  	ret = drm_mode_object_get_properties(&connector->base, file_priv->atomic,
>  			file_priv->plane_color_pipeline,
> +			file_priv->post_blend_color_pipeline,
>  			(uint32_t __user *)(unsigned long)(out_resp->props_ptr),
>  			(uint64_t __user *)(unsigned long)(out_resp->prop_values_ptr),
>  			&out_resp->count_props);
> diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c
> index 46655339003db2a1b43441434839e26f61d79b4e..94238163ff1254c721df39c030bc99a31d3bb92a 100644
> --- a/drivers/gpu/drm/drm_crtc.c
> +++ b/drivers/gpu/drm/drm_crtc.c
> @@ -959,3 +959,80 @@ bool drm_crtc_in_clone_mode(struct drm_crtc_state *crtc_state)
>  	return hweight32(crtc_state->encoder_mask) > 1;
>  }
>  EXPORT_SYMBOL(drm_crtc_in_clone_mode);
> +
> +struct drm_property *
> +drm_common_create_color_pipeline_property(struct drm_device *dev, struct drm_mode_object *obj,
> +					  const struct drm_prop_enum_list *pipelines,
> +					  int num_pipelines)
> +{
> +	struct drm_prop_enum_list *all_pipelines;
> +	struct drm_property *prop;
> +	int len = 0;
> +	int i;
> +
> +	all_pipelines = kcalloc(num_pipelines + 1,
> +				sizeof(*all_pipelines),
> +				GFP_KERNEL);
> +
> +	if (!all_pipelines) {
> +		drm_err(dev, "failed to allocate color pipeline\n");
> +		return ERR_PTR(-ENOMEM);
> +	}
> +
> +	/* Create default Bypass color pipeline */
> +	all_pipelines[len].type = 0;
> +	all_pipelines[len].name = "Bypass";
> +	len++;
> +
> +	/* Add all other color pipelines */
> +	for (i = 0; i < num_pipelines; i++, len++) {
> +		all_pipelines[len].type = pipelines[i].type;
> +		all_pipelines[len].name = pipelines[i].name;
> +	}
> +
> +	prop = drm_property_create_enum(dev, DRM_MODE_PROP_ATOMIC,
> +					"COLOR_PIPELINE",
> +					all_pipelines, len);
> +	if (IS_ERR(prop)) {
> +		kfree(all_pipelines);
> +		return prop;
> +	}
> +
> +	drm_object_attach_property(obj, prop, 0);
> +
> +	kfree(all_pipelines);
> +	return prop;
> +}
> +
> +/**
> + * drm_crtc_create_color_pipeline_property - create a new color pipeline
> + * property
> + *
> + * @crtc: drm CRTC
> + * @pipelines: list of pipelines
> + * @num_pipelines: number of pipelines
> + *
> + * Create the COLOR_PIPELINE CRTC property to specify color pipelines on
> + * the CRTC.
> + *
> + * RETURNS:
> + * Zero for success or -errno
> + */
> +int drm_crtc_create_color_pipeline_property(struct drm_crtc *crtc,
> +					    const struct drm_prop_enum_list *pipelines,
> +					    int num_pipelines)
> +{
> +	struct drm_property *prop;
> +
> +	prop = drm_common_create_color_pipeline_property(crtc->dev,
> +							 &crtc->base,
> +							 pipelines,
> +							 num_pipelines);
> +	if (IS_ERR(prop))
> +		return PTR_ERR(prop);
> +
> +	crtc->color_pipeline_property = prop;
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL(drm_crtc_create_color_pipeline_property);
> diff --git a/drivers/gpu/drm/drm_crtc_internal.h b/drivers/gpu/drm/drm_crtc_internal.h
> index c094092296448093c5cd192ecdc8ea9a50769c90..c53f154e5392a10c326c844b7321666275f9ac02 100644
> --- a/drivers/gpu/drm/drm_crtc_internal.h
> +++ b/drivers/gpu/drm/drm_crtc_internal.h
> @@ -35,6 +35,7 @@
>  #ifndef __DRM_CRTC_INTERNAL_H__
>  #define __DRM_CRTC_INTERNAL_H__
>  
> +#include <drm/drm_property.h>
>  #include <linux/err.h>
>  #include <linux/types.h>
>  
> @@ -79,6 +80,10 @@ int drm_crtc_check_viewport(const struct drm_crtc *crtc,
>  int drm_crtc_register_all(struct drm_device *dev);
>  void drm_crtc_unregister_all(struct drm_device *dev);
>  int drm_crtc_force_disable(struct drm_crtc *crtc);
> +struct drm_property *
> +drm_common_create_color_pipeline_property(struct drm_device *dev, struct drm_mode_object *obj,
> +					  const struct drm_prop_enum_list *pipelines,
> +					  int num_pipelines);
>  
>  struct dma_fence *drm_crtc_create_fence(struct drm_crtc *crtc);
>  
> @@ -164,6 +169,7 @@ void drm_mode_object_unregister(struct drm_device *dev,
>  				struct drm_mode_object *object);
>  int drm_mode_object_get_properties(struct drm_mode_object *obj, bool atomic,
>  				   bool plane_color_pipeline,
> +				   bool post_blend_color_pipeline,
>  				   uint32_t __user *prop_ptr,
>  				   uint64_t __user *prop_values,
>  				   uint32_t *arg_count_props);
> diff --git a/drivers/gpu/drm/drm_ioctl.c b/drivers/gpu/drm/drm_ioctl.c
> index ff193155129e7e863888d8958458978566b144f8..2c81f63fee428ca85f3c626d892ea6097b964e88 100644
> --- a/drivers/gpu/drm/drm_ioctl.c
> +++ b/drivers/gpu/drm/drm_ioctl.c
> @@ -380,6 +380,13 @@ drm_setclientcap(struct drm_device *dev, void *data, struct drm_file *file_priv)
>  			return -EINVAL;
>  		file_priv->plane_color_pipeline = req->value;
>  		break;
> +	case DRM_CLIENT_CAP_POST_BLEND_COLOR_PIPELINE:
> +		if (!file_priv->atomic)
> +			return -EINVAL;
> +		if (req->value > 1)
> +			return -EINVAL;
> +		file_priv->post_blend_color_pipeline = req->value;
> +		break;
>  	default:
>  		return -EINVAL;
>  	}
> diff --git a/drivers/gpu/drm/drm_mode_object.c b/drivers/gpu/drm/drm_mode_object.c
> index b45d501b10c868c6d9b7a5a8760eadbd7b372a6a..c9e20d12e8fd311f71b9d6bc8d575624751d33ad 100644
> --- a/drivers/gpu/drm/drm_mode_object.c
> +++ b/drivers/gpu/drm/drm_mode_object.c
> @@ -388,6 +388,7 @@ EXPORT_SYMBOL(drm_object_property_get_default_value);
>  /* helper for getconnector and getproperties ioctls */
>  int drm_mode_object_get_properties(struct drm_mode_object *obj, bool atomic,
>  				   bool plane_color_pipeline,
> +				   bool post_blend_color_pipeline,
>  				   uint32_t __user *prop_ptr,
>  				   uint64_t __user *prop_values,
>  				   uint32_t *arg_count_props)
> @@ -416,6 +417,24 @@ int drm_mode_object_get_properties(struct drm_mode_object *obj, bool atomic,
>  				continue;
>  		}
>  
> +		if (post_blend_color_pipeline && obj->type == DRM_MODE_OBJECT_CRTC) {
> +			struct drm_crtc *crtc = obj_to_crtc(obj);
> +			struct drm_mode_config mode_config = crtc->dev->mode_config;
> +
> +			if (prop == mode_config.gamma_lut_property ||
> +			    prop == mode_config.degamma_lut_property ||
> +			    prop == mode_config.gamma_lut_size_property ||
> +			    prop == mode_config.ctm_property)
> +				continue;
> +		}
> +
> +		if (!post_blend_color_pipeline && obj->type == DRM_MODE_OBJECT_CRTC) {
> +			struct drm_crtc *crtc = obj_to_crtc(obj);
> +
> +			if (prop == crtc->color_pipeline_property)
> +				continue;
> +		}
> +
>  		if (*arg_count_props > count) {
>  			ret = __drm_object_property_get_value(obj, prop, &val);
>  			if (ret)
> @@ -475,6 +494,7 @@ int drm_mode_obj_get_properties_ioctl(struct drm_device *dev, void *data,
>  
>  	ret = drm_mode_object_get_properties(obj, file_priv->atomic,
>  			file_priv->plane_color_pipeline,
> +			file_priv->post_blend_color_pipeline,
>  			(uint32_t __user *)(unsigned long)(arg->props_ptr),
>  			(uint64_t __user *)(unsigned long)(arg->prop_values_ptr),
>  			&arg->count_props);
> diff --git a/drivers/gpu/drm/drm_plane.c b/drivers/gpu/drm/drm_plane.c
> index f6cfa8ac090c7bc49c7f276993bba7e9800da140..60dbfcab495600dd44c15260a1fa6135db59c6e2 100644
> --- a/drivers/gpu/drm/drm_plane.c
> +++ b/drivers/gpu/drm/drm_plane.c
> @@ -1839,43 +1839,17 @@ int drm_plane_create_color_pipeline_property(struct drm_plane *plane,
>  					     const struct drm_prop_enum_list *pipelines,
>  					     int num_pipelines)
>  {
> -	struct drm_prop_enum_list *all_pipelines;
>  	struct drm_property *prop;
> -	int len = 0;
> -	int i;
> -
> -	all_pipelines = kcalloc(num_pipelines + 1,
> -				sizeof(*all_pipelines),
> -				GFP_KERNEL);
> -
> -	if (!all_pipelines) {
> -		drm_err(plane->dev, "failed to allocate color pipeline\n");
> -		return -ENOMEM;
> -	}
>  
> -	/* Create default Bypass color pipeline */
> -	all_pipelines[len].type = 0;
> -	all_pipelines[len].name = "Bypass";
> -	len++;
> -
> -	/* Add all other color pipelines */
> -	for (i = 0; i < num_pipelines; i++, len++) {
> -		all_pipelines[len].type = pipelines[i].type;
> -		all_pipelines[len].name = pipelines[i].name;
> -	}
> -
> -	prop = drm_property_create_enum(plane->dev, DRM_MODE_PROP_ATOMIC,
> -					"COLOR_PIPELINE",
> -					all_pipelines, len);
> -	if (IS_ERR(prop)) {
> -		kfree(all_pipelines);
> +	prop = drm_common_create_color_pipeline_property(plane->dev,
> +							 &plane->base,
> +							 pipelines,
> +							 num_pipelines);
> +	if (IS_ERR(prop))
>  		return PTR_ERR(prop);
> -	}
>  
> -	drm_object_attach_property(&plane->base, prop, 0);
>  	plane->color_pipeline_property = prop;
>  
> -	kfree(all_pipelines);
>  	return 0;
>  }
>  EXPORT_SYMBOL(drm_plane_create_color_pipeline_property);
> diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h
> index 678708df9cdb90b4266127193a92183069f18688..8c42c584aefbf0034b2163d90538e80099b0dadb 100644
> --- a/include/drm/drm_atomic.h
> +++ b/include/drm/drm_atomic.h
> @@ -482,6 +482,26 @@ struct drm_atomic_state {
>  	 */
>  	bool plane_color_pipeline : 1;
>  
> +	/**
> +	 * @post_blend_color_pipeline:
> +	 *
> +	 * Indicates whether this atomic state originated with a client that
> +	 * set the DRM_CLIENT_CAP_POST_BLEND_COLOR_PIPELINE.
> +	 *
> +	 * Drivers and helper functions should use this to ignore legacy
> +	 * properties that are incompatible with the drm_crtc COLOR_PIPELINE
> +	 * behavior, such as:
> +	 *
> +	 *  - GAMMA_LUT
> +	 *  - DEGAMMA_LUT
> +	 *  - GAMMA_LUT_SIZE
> +	 *  - CTM
> +	 *
> +	 * or any other driver-specific properties that might affect pixel
> +	 * values.
> +	 */
> +	bool post_blend_color_pipeline : 1;
> +
>  	/**
>  	 * @colorops:
>  	 *
> diff --git a/include/drm/drm_atomic_uapi.h b/include/drm/drm_atomic_uapi.h
> index 4363155233267b93767c895fa6085544e2277442..4dc191f6f929d73deee9812025c48275a33cf770 100644
> --- a/include/drm/drm_atomic_uapi.h
> +++ b/include/drm/drm_atomic_uapi.h
> @@ -52,6 +52,8 @@ void drm_atomic_set_fb_for_plane(struct drm_plane_state *plane_state,
>  				 struct drm_framebuffer *fb);
>  void drm_atomic_set_colorop_for_plane(struct drm_plane_state *plane_state,
>  				      struct drm_colorop *colorop);
> +void drm_atomic_set_colorop_for_crtc(struct drm_crtc_state *crtc_state,
> +				     struct drm_colorop *colorop);
>  int __must_check
>  drm_atomic_set_crtc_for_connector(struct drm_connector_state *conn_state,
>  				  struct drm_crtc *crtc);
> diff --git a/include/drm/drm_colorop.h b/include/drm/drm_colorop.h
> index d61c6c40e47162cb8b1e7db58b6746c43ac5d202..710a6199ebc5498a3f664de39ea07dbc95944eb7 100644
> --- a/include/drm/drm_colorop.h
> +++ b/include/drm/drm_colorop.h
> @@ -206,10 +206,16 @@ struct drm_colorop {
>  	/**
>  	 * @plane:
>  	 *
> -	 * The plane on which the colorop sits. A drm_colorop is always unique
> -	 * to a plane.
> +	 * The plane on which the colorop sits if it is a pre-blend colorop.
> +	 * In this case it is unique to the plane.
> +	 *
> +	 * @crtc:
> +	 *
> +	 * The CRTC on which the colorop sits if it is a post-blend colorop.
> +	 * In this case it is unique to the CRTC.
>  	 */
>  	struct drm_plane *plane;
> +	struct drm_crtc *crtc;
>  
>  	/**
>  	 * @state:
> @@ -370,6 +376,10 @@ static inline struct drm_colorop *drm_colorop_find(struct drm_device *dev,
>  
>  void drm_colorop_pipeline_destroy(struct drm_device *dev);
>  
> +int drm_crtc_colorop_curve_1d_lut_init(struct drm_device *dev, struct drm_colorop *colorop,
> +				       struct drm_crtc *crtc, uint32_t lut_size,
> +				       enum drm_colorop_lut1d_interpolation_type lut1d_interpolation,
> +				       uint32_t flags);
>  int drm_plane_colorop_curve_1d_init(struct drm_device *dev, struct drm_colorop *colorop,
>  				    struct drm_plane *plane, u64 supported_tfs, uint32_t flags);
>  int drm_plane_colorop_curve_1d_lut_init(struct drm_device *dev, struct drm_colorop *colorop,
> @@ -378,6 +388,8 @@ int drm_plane_colorop_curve_1d_lut_init(struct drm_device *dev, struct drm_color
>  					uint32_t flags);
>  int drm_plane_colorop_ctm_3x4_init(struct drm_device *dev, struct drm_colorop *colorop,
>  				   struct drm_plane *plane, uint32_t flags);
> +int drm_crtc_colorop_ctm_3x4_init(struct drm_device *dev, struct drm_colorop *colorop,
> +				   struct drm_crtc *crtc, uint32_t flags);
>  int drm_plane_colorop_mult_init(struct drm_device *dev, struct drm_colorop *colorop,
>  				struct drm_plane *plane, uint32_t flags);
>  int drm_plane_colorop_3dlut_init(struct drm_device *dev, struct drm_colorop *colorop,
> diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h
> index caa56e039da2a748cf40ebf45b37158acda439d9..df03637ca25abd45e96b5944229297f776fd046d 100644
> --- a/include/drm/drm_crtc.h
> +++ b/include/drm/drm_crtc.h
> @@ -274,6 +274,14 @@ struct drm_crtc_state {
>  	 */
>  	struct drm_property_blob *gamma_lut;
>  
> +	/**
> +	 * @color_pipeline:
> +	 *
> +	 * The first colorop of the active color pipeline, or NULL, if no
> +	 * color pipeline is active.
> +	 */
> +	struct drm_colorop *color_pipeline;
> +
>  	/**
>  	 * @target_vblank:
>  	 *
> @@ -1088,6 +1096,14 @@ struct drm_crtc {
>  	 */
>  	struct drm_property *scaling_filter_property;
>  
> +	/**
> +	 * @color_pipeline_property:
> +	 *
> +	 * Optional "COLOR_PIPELINE" enum property for specifying
> +	 * a color pipeline to use on the CRTC.
> +	 */
> +	struct drm_property *color_pipeline_property;
> +
>  	/**
>  	 * @state:
>  	 *
> @@ -1323,5 +1339,8 @@ static inline struct drm_crtc *drm_crtc_find(struct drm_device *dev,
>  
>  int drm_crtc_create_scaling_filter_property(struct drm_crtc *crtc,
>  					    unsigned int supported_filters);
> +int drm_crtc_create_color_pipeline_property(struct drm_crtc *crtc,
> +					     const struct drm_prop_enum_list *pipelines,
> +					     int num_pipelines);
>  bool drm_crtc_in_clone_mode(struct drm_crtc_state *crtc_state);
>  #endif /* __DRM_CRTC_H__ */
> diff --git a/include/drm/drm_file.h b/include/drm/drm_file.h
> index 1a3018e4a537b3341acb50187d47371f6b781b9d..42b9a43baa18079af8ec2ea5b1484b23c497beb0 100644
> --- a/include/drm/drm_file.h
> +++ b/include/drm/drm_file.h
> @@ -213,6 +213,13 @@ struct drm_file {
>  	 */
>  	bool plane_color_pipeline;
>  
> +	/**
> +	 * @post_blend_color_pipeline:
> +	 *
> +	 * True if client understands post-blend color pipelines
> +	 */
> +	bool post_blend_color_pipeline;
> +
>  	/**
>  	 * @was_master:
>  	 *
> diff --git a/include/uapi/drm/drm.h b/include/uapi/drm/drm.h
> index 27cc159c1d275c7a7fe057840ef792f30a582bb7..1191b142ebaa5478376308f169f9ce8591580d9d 100644
> --- a/include/uapi/drm/drm.h
> +++ b/include/uapi/drm/drm.h
> @@ -921,6 +921,22 @@ struct drm_get_cap {
>   */
>  #define DRM_CLIENT_CAP_PLANE_COLOR_PIPELINE	7
>  
> +/**
> + * DRM_CLIENT_CAP_POST_BLEND_COLOR_PIPELINE
> + *
> + * If set to 1 the DRM core will allow setting the COLOR_PIPELINE
> + * property on a &drm_crtc, as well as drm_colorop properties.
> + *
> + * Setting of these crtc properties will be rejected when this client
> + * cap is set:
> + * - GAMMA_LUT
> + * - DEGAMMA_LUT
> + * - CTM
> + *
> + * The client must enable &DRM_CLIENT_CAP_ATOMIC first.
> + */
> +#define DRM_CLIENT_CAP_POST_BLEND_COLOR_PIPELINE	8
> +
>  /* DRM_IOCTL_SET_CLIENT_CAP ioctl argument type */
>  struct drm_set_client_cap {
>  	__u64 capability;
Re: [PATCH RFC 1/5] drm: Support post-blend color pipeline API
Posted by Daniel Stone 1 month, 1 week ago
Hey,

On Wed, 27 Aug 2025 at 12:17, Sebastian Wick <sebastian.wick@redhat.com> wrote:
> On Fri Aug 22, 2025 at 8:36 PM CEST, Nícolas F. R. A. Prado wrote:
> > Introduce support for a post-blend color pipeline API analogous to the
> > pre-blend color pipeline API. While the pre-blend color pipeline was
> > configured through a COLOR_PIPELINE property attached to a drm_plane,
> > the post-blend color pipeline is configured through a COLOR_PIPELINE
> > property on the drm_crtc.
> >
> > Since colorops can now be attached to either a drm_plane or a drm_crtc,
> > rework the helpers to account for both cases.
> >
> > Also introduce a new cap, DRM_CLIENT_CAP_POST_BLEND_COLOR_PIPELINE, to
> > enable support for post-blend color pipelines, and prevent the now
> > legacy GAMMA_LUT, DEGAMMA_LUT, GAMMA_LUT_SIZE and CTM properties from
> > being exposed.
>
> Please note that you'll also have to deprecate the semi-standard
> Broadcast RGB property. It serves two purposes at once: it changes the
> values between the color range (similar to COLOR_RANGE but at the other
> end) and informats the sink of the range as well.
>
> So the post blending color pipeline will need something like an inverse
> COLOR_RANGE op.
>
> We will also need a new connector property where user space can select
> the color range, which does not change the pixel values, and only
> exposes options that can be achieved (default sink behavior, full range
> infoframe, limited range infoframe).

As a note to others, the follow-up is on the 'pixel_encoding' property
thread here:
https://lore.kernel.org/all/DCD5VIFRKFB9.1KHIZI3ASID2I@redhat.com/

I think we should keep discussion of those properties there. :)

Also strongly related is the proposal to add range/encoding properties
to writeback connectors; analagous to the inbound properties:
https://lore.kernel.org/all/20250813170542.331206-1-robert.mader@collabora.com/

I've talked myself around into thinking that the writeback-connector
property is better than trying to use colorops to do the transform. On
the hardware I've seen, whilst the CRTC output pipeline does have
colour-transform properties, the final yard of the encoding (YUV/RGB,
full/limited range, primaries, etc) is in fact a per-connector
property, so I think it makes more sense there, as that allows
usecases like RGB display output whilst streaming to YUV writeback so
you can push it directly into an encoder without an intermediate
RGB->YUV conversion step. But again, I think conversation about that
would be better on that thread rather than here.

Thanks!

Cheers,
Daniel
Re: [PATCH RFC 1/5] drm: Support post-blend color pipeline API
Posted by Daniel Stone 1 month, 1 week ago
Hi Nicolas,
Thanks for doing this! It's great to see.

On Fri, 22 Aug 2025 at 19:36, Nícolas F. R. A. Prado
<nfraprado@collabora.com> wrote:
> -/**
> - * drm_plane_colorop_curve_1d_lut_init - Initialize a DRM_COLOROP_1D_LUT
> - *
> - * @dev: DRM device
> - * @colorop: The drm_colorop object to initialize
> - * @plane: The associated drm_plane
> - * @lut_size: LUT size supported by driver
> - * @lut1d_interpolation: 1D LUT interpolation type
> - * @flags: bitmask of misc, see DRM_COLOROP_FLAG_* defines.
> - * @return zero on success, -E value on failure
> - */
> -int drm_plane_colorop_curve_1d_lut_init(struct drm_device *dev, struct drm_colorop *colorop,
> -                                       struct drm_plane *plane, uint32_t lut_size,
> -                                       enum drm_colorop_lut1d_interpolation_type lut1d_interpolation,
> -                                       uint32_t flags)
> +static int
> +drm_common_colorop_curve_1d_lut_init(struct drm_device *dev,
> +                                    struct drm_colorop *colorop,
> +                                    uint32_t lut_size,
> +                                    enum drm_colorop_lut1d_interpolation_type lut1d_interpolation,
> +                                    uint32_t flags)

I think these would be better in a prior commit which only moved the
plane init around.

> @@ -416,6 +417,24 @@ int drm_mode_object_get_properties(struct drm_mode_object *obj, bool atomic,
>                                 continue;
>                 }
>
> +               if (post_blend_color_pipeline && obj->type == DRM_MODE_OBJECT_CRTC) {
> +                       struct drm_crtc *crtc = obj_to_crtc(obj);
> +                       struct drm_mode_config mode_config = crtc->dev->mode_config;
> +
> +                       if (prop == mode_config.gamma_lut_property ||
> +                           prop == mode_config.degamma_lut_property ||
> +                           prop == mode_config.gamma_lut_size_property ||
> +                           prop == mode_config.ctm_property)
> +                               continue;
> +               }
> +
> +               if (!post_blend_color_pipeline && obj->type == DRM_MODE_OBJECT_CRTC) {
> +                       struct drm_crtc *crtc = obj_to_crtc(obj);
> +
> +                       if (prop == crtc->color_pipeline_property)
> +                               continue;
> +               }

Hmmm. One issue with this is that it makes things like drm_info
harder: if drm_info opted into the client cap, it would no longer be
able to see any GAMMA_LUT/etc programmed by the prior userspace. So I
think allowing at least read-only access would be reasonable here.

Having a client cap without a driver cap also puts userspace in a
difficult position. If the driver doesn't support post-blend colorops,
then enabling the client cap strictly removes support without a
replacement. And without a driver cap, the client doesn't have a way
to know which is better.

Cheers,
Daniel
Re: [PATCH RFC 1/5] drm: Support post-blend color pipeline API
Posted by Xaver Hugl 1 month, 1 week ago
> > @@ -416,6 +417,24 @@ int drm_mode_object_get_properties(struct drm_mode_object *obj, bool atomic,
> >                                 continue;
> >                 }
> >
> > +               if (post_blend_color_pipeline && obj->type == DRM_MODE_OBJECT_CRTC) {
> > +                       struct drm_crtc *crtc = obj_to_crtc(obj);
> > +                       struct drm_mode_config mode_config = crtc->dev->mode_config;
> > +
> > +                       if (prop == mode_config.gamma_lut_property ||
> > +                           prop == mode_config.degamma_lut_property ||
> > +                           prop == mode_config.gamma_lut_size_property ||
> > +                           prop == mode_config.ctm_property)
> > +                               continue;
> > +               }
> > +
> > +               if (!post_blend_color_pipeline && obj->type == DRM_MODE_OBJECT_CRTC) {
> > +                       struct drm_crtc *crtc = obj_to_crtc(obj);
> > +
> > +                       if (prop == crtc->color_pipeline_property)
> > +                               continue;
> > +               }
>
> Hmmm. One issue with this is that it makes things like drm_info
> harder: if drm_info opted into the client cap, it would no longer be
> able to see any GAMMA_LUT/etc programmed by the prior userspace. So I
> think allowing at least read-only access would be reasonable here.
FWIW the cap for per-plane pipelines also hides COLOR_RANGE and
COLOR_ENCODING properties from the client.

From a compositor POV, I slightly prefer hiding the properties
entirely, but ignoring them on the compositor side when a color
pipeline is available would also be trivial.

> Having a client cap without a driver cap also puts userspace in a
> difficult position. If the driver doesn't support post-blend colorops,
> then enabling the client cap strictly removes support without a
> replacement. And without a driver cap, the client doesn't have a way
> to know which is better.
>
> Cheers,
> Daniel
Re: [PATCH RFC 1/5] drm: Support post-blend color pipeline API
Posted by Daniel Stone 1 month, 1 week ago
Hi,

On Mon, 25 Aug 2025 at 19:45, Xaver Hugl <xaver.hugl@gmail.com> wrote:
> > > @@ -416,6 +417,24 @@ int drm_mode_object_get_properties(struct drm_mode_object *obj, bool atomic,
> > > +               if (post_blend_color_pipeline && obj->type == DRM_MODE_OBJECT_CRTC) {
> > > +                       struct drm_crtc *crtc = obj_to_crtc(obj);
> > > +                       struct drm_mode_config mode_config = crtc->dev->mode_config;
> > > +
> > > +                       if (prop == mode_config.gamma_lut_property ||
> > > +                           prop == mode_config.degamma_lut_property ||
> > > +                           prop == mode_config.gamma_lut_size_property ||
> > > +                           prop == mode_config.ctm_property)
> > > +                               continue;
> > > +               }
> > > +
> > > +               if (!post_blend_color_pipeline && obj->type == DRM_MODE_OBJECT_CRTC) {
> > > +                       struct drm_crtc *crtc = obj_to_crtc(obj);
> > > +
> > > +                       if (prop == crtc->color_pipeline_property)
> > > +                               continue;
> > > +               }
> >
> > Hmmm. One issue with this is that it makes things like drm_info
> > harder: if drm_info opted into the client cap, it would no longer be
> > able to see any GAMMA_LUT/etc programmed by the prior userspace. So I
> > think allowing at least read-only access would be reasonable here.
>
> FWIW the cap for per-plane pipelines also hides COLOR_RANGE and
> COLOR_ENCODING properties from the client.
>
> From a compositor POV, I slightly prefer hiding the properties
> entirely, but ignoring them on the compositor side when a color
> pipeline is available would also be trivial.

It makes it impossible to do smooth transitions from legacy clients,
as the old (current) properties can't be read back.

I assume the atomic state would also carry the old values (even if the
drivers are specified to have to ignore them), so there would be an
odd transition:
* pre-colorop userspace sets GAMMA_LUT to invert brightness
* colorop userspace takes over, does not set any colorops on the CRTC,
brightness is no longer inverted (presumably? depends on what the
default set of colorops is? and what the drivers do?), but the atomic
state still carries the old gamma_lut blob
* pre-colorop userspace takes over, does not touch GAMMA_LUT,
brightness is inverted as the colorop from the previous atomic state
is ignored and the pre-atomic one now takes precedence

This isn't necessarily wrong per se, but does seem kind of janky and
error-prone: like should the old state be reset to zero/bypass for
commits from colorop-aware clients? Or should we explicitly allow 0
but no other value?

Cheers,
Daniel
Re: [PATCH RFC 1/5] drm: Support post-blend color pipeline API
Posted by Nícolas F. R. A. Prado 1 month ago
On Tue, 2025-08-26 at 13:25 +0100, Daniel Stone wrote:
> Hi,
> 
> On Mon, 25 Aug 2025 at 19:45, Xaver Hugl <xaver.hugl@gmail.com>
> wrote:
> > > > @@ -416,6 +417,24 @@ int drm_mode_object_get_properties(struct
> > > > drm_mode_object *obj, bool atomic,
> > > > +               if (post_blend_color_pipeline && obj->type ==
> > > > DRM_MODE_OBJECT_CRTC) {
> > > > +                       struct drm_crtc *crtc =
> > > > obj_to_crtc(obj);
> > > > +                       struct drm_mode_config mode_config =
> > > > crtc->dev->mode_config;
> > > > +
> > > > +                       if (prop ==
> > > > mode_config.gamma_lut_property ||
> > > > +                           prop ==
> > > > mode_config.degamma_lut_property ||
> > > > +                           prop ==
> > > > mode_config.gamma_lut_size_property ||
> > > > +                           prop == mode_config.ctm_property)
> > > > +                               continue;
> > > > +               }
> > > > +
> > > > +               if (!post_blend_color_pipeline && obj->type ==
> > > > DRM_MODE_OBJECT_CRTC) {
> > > > +                       struct drm_crtc *crtc =
> > > > obj_to_crtc(obj);
> > > > +
> > > > +                       if (prop == crtc-
> > > > >color_pipeline_property)
> > > > +                               continue;
> > > > +               }
> > > 
> > > Hmmm. One issue with this is that it makes things like drm_info
> > > harder: if drm_info opted into the client cap, it would no longer
> > > be
> > > able to see any GAMMA_LUT/etc programmed by the prior userspace.
> > > So I
> > > think allowing at least read-only access would be reasonable
> > > here.
> > 
> > FWIW the cap for per-plane pipelines also hides COLOR_RANGE and
> > COLOR_ENCODING properties from the client.
> > 
> > From a compositor POV, I slightly prefer hiding the properties
> > entirely, but ignoring them on the compositor side when a color
> > pipeline is available would also be trivial.
> 
> It makes it impossible to do smooth transitions from legacy clients,
> as the old (current) properties can't be read back.
> 
> I assume the atomic state would also carry the old values (even if
> the
> drivers are specified to have to ignore them), so there would be an
> odd transition:
> * pre-colorop userspace sets GAMMA_LUT to invert brightness
> * colorop userspace takes over, does not set any colorops on the
> CRTC,
> brightness is no longer inverted (presumably? depends on what the
> default set of colorops is? and what the drivers do?), but the atomic
> state still carries the old gamma_lut blob
> * pre-colorop userspace takes over, does not touch GAMMA_LUT,
> brightness is inverted as the colorop from the previous atomic state
> is ignored and the pre-atomic one now takes precedence
> 
> This isn't necessarily wrong per se, but does seem kind of janky and
> error-prone: like should the old state be reset to zero/bypass for
> commits from colorop-aware clients? Or should we explicitly allow 0
> but no other value?

Hi, thanks for the feedback!

Based on this discussion, this is my understanding for the changes
desired on the series and their reasonings:

1. Add a driver cap, DRM_CAP_POST_BLEND_COLOR_PIPELINE, which drivers
will use to signal they support post-blend color pipelines.
  - Reason: Allow userspace to figure out that the driver doesn't
support post-blend color pipelines and choose to not set the client
cap, DRM_CLIENT_CAP_POST_BLEND_COLOR_PIPELINE, so it can use legacy
color management instead.
2. Make it so setting the client cap,
DRM_CLIENT_CAP_POST_BLEND_COLOR_PIPELINE, fails if the driver cap,
DRM_CAP_POST_BLEND_COLOR_PIPELINE, isn't set
  - Reason: Prevent userspace from making color management unusable if
the driver doesn't support post-blend color pipelines, as the legacy
color-management properties (GAMMA_LUT, DEGAMMA_LUT, CTM) would be
unwriteable with the client cap set.
3. Make legacy color-management properties (GAMMA_LUT, DEGAMMA_LUT,
CTM) read-only if the client cap,
DRM_CLIENT_CAP_POST_BLEND_COLOR_PIPELINE, is set
  - Reason: Allow drm_info to print legacy color management
configuration while still enabling post-blend color pipelines through
the client cap. Also to allow smooth handover from pre-colorop
userspace client to colorop-ready userspace client, as the latter can
now replicate the legacy color configuration through the colorops.

Side note: Smooth handover back to pre-colorop userspace after tweaking
the colorops to something else would not be possible without making the
legacy properties writable too, so that the client could update them to
match the colorops setting before switching back. I don't imagine this
would be a common use case, and colorops are a superset of the legacy
properties so there are cases where it wouldn't even be possible to
replicate the colorop setting on the legacy properties, but thought I'd
mention this limitation for completeness' sake.

Also, as Xaver noted, this feedback also applies to pre-blend pipelines
and its legacy color-management properties (COLOR_ENCODING,
COLOR_RANGE), so the same changes would be desirable there for the same
reasons. So we should share this feedback on that series as well.


Does this sound right?

-- 
Thanks,

Nícolas
Re: [PATCH RFC 1/5] drm: Support post-blend color pipeline API
Posted by Daniel Stone 2 weeks, 4 days ago
Hi Nícolas,

On Wed, 3 Sept 2025 at 19:43, Nícolas F. R. A. Prado
<nfraprado@collabora.com> wrote:
> On Tue, 2025-08-26 at 13:25 +0100, Daniel Stone wrote:
> Based on this discussion, this is my understanding for the changes
> desired on the series and their reasonings:
>
> 1. Add a driver cap, DRM_CAP_POST_BLEND_COLOR_PIPELINE, which drivers
> will use to signal they support post-blend color pipelines.
>   - Reason: Allow userspace to figure out that the driver doesn't
> support post-blend color pipelines and choose to not set the client
> cap, DRM_CLIENT_CAP_POST_BLEND_COLOR_PIPELINE, so it can use legacy
> color management instead.
> 2. Make it so setting the client cap,
> DRM_CLIENT_CAP_POST_BLEND_COLOR_PIPELINE, fails if the driver cap,
> DRM_CAP_POST_BLEND_COLOR_PIPELINE, isn't set
>   - Reason: Prevent userspace from making color management unusable if
> the driver doesn't support post-blend color pipelines, as the legacy
> color-management properties (GAMMA_LUT, DEGAMMA_LUT, CTM) would be
> unwriteable with the client cap set.

Definitely.

> 3. Make legacy color-management properties (GAMMA_LUT, DEGAMMA_LUT,
> CTM) read-only if the client cap,
> DRM_CLIENT_CAP_POST_BLEND_COLOR_PIPELINE, is set
>   - Reason: Allow drm_info to print legacy color management
> configuration while still enabling post-blend color pipelines through
> the client cap. Also to allow smooth handover from pre-colorop
> userspace client to colorop-ready userspace client, as the latter can
> now replicate the legacy color configuration through the colorops.

I think yes, but I don't really feel strongly about this. If others
involved have stronger opinions, I'm happy to yield.

> Side note: Smooth handover back to pre-colorop userspace after tweaking
> the colorops to something else would not be possible without making the
> legacy properties writable too, so that the client could update them to
> match the colorops setting before switching back. I don't imagine this
> would be a common use case, and colorops are a superset of the legacy
> properties so there are cases where it wouldn't even be possible to
> replicate the colorop setting on the legacy properties, but thought I'd
> mention this limitation for completeness' sake.

That's a totally acceptable tradeoff. We don't have a standard
inter-client capability handshake, so if downgrading from a
newer/more-capable to an older/less-capable client is a bit janky,
that's OK. There's only so much we can do given the original design
decision for the KMS core to not be opinionated about a 'golden state'
that could be used as a reference for userspace to work from as a
base.

> Also, as Xaver noted, this feedback also applies to pre-blend pipelines
> and its legacy color-management properties (COLOR_ENCODING,
> COLOR_RANGE), so the same changes would be desirable there for the same
> reasons. So we should share this feedback on that series as well.

Yep.

Cheers,
Daniel