aboutsummaryrefslogtreecommitdiff
path: root/SOURCES/0001-HDR.patch
diff options
context:
space:
mode:
Diffstat (limited to 'SOURCES/0001-HDR.patch')
-rw-r--r--SOURCES/0001-HDR.patch2310
1 files changed, 2310 insertions, 0 deletions
diff --git a/SOURCES/0001-HDR.patch b/SOURCES/0001-HDR.patch
new file mode 100644
index 0000000..6df422b
--- /dev/null
+++ b/SOURCES/0001-HDR.patch
@@ -0,0 +1,2310 @@
+From 03248cc1991679d1025ea5bdf30ee324cdebf622 Mon Sep 17 00:00:00 2001
+From: Peter Jung <admin@ptr1337.dev>
+Date: Wed, 23 Aug 2023 18:54:42 +0200
+Subject: [PATCH] AMD-HDR
+
+Hi all,
+
+Here is the next version of our work to enable AMD driver-specific color
+management properties [1][2]. This series is a collection of
+contributions from Joshua, Harry, and me to enhance the AMD KMS color
+pipeline for Steam Deck/SteamOS by exposing additional pre-blending and
+post-blending color capabilities from those available in the current DRM
+KMS API[3].
+
+The userspace case here is Gamescope which is the compositor for
+SteamOS. Gamescope is already using these features to implement its
+color management pipeline [4].
+
+In this version, I try to address all concerns shared in the previous
+one, i.e.:
+- Replace DRM_ by AMDGPU_ prefix for transfer function enumeration;
+- Explicitly define EOTFs and inverse EOTFs and set props accordingly;
+- Document pre-defined transfer functions;
+- Remove misleading comments;
+- Remove post-blending/MPC shaper and 3D LUT support;
+- Move driver-specific property operations from amdgpu_display.c to
+ amdgpu_dm_color.c;
+- Reset planes if any color props change;
+- Nits/small fixes;
+
+Bearing in mind the complexity of color concepts, I believe there is a
+high chance of some misunderstanding from my side when defining EOTFs
+and documenting pre-defined TFs. So, reviews are very important and
+welcome (thanks in advance). FWIW, I added Harry as a co-developer of
+this TF documentation since I based on his description of EOTF/inv_EOTF
+and previous documentation work [5]. Let me know if there is a better
+way for credits.
+
+Two DC patches were already applied and, therefore, removed from the
+series. I added r-b according to previous feedback. We also add plane
+CTM to driver-specific properties. As a result, this is the updated list
+of all driver-specific color properties exposed by this series:
+
+- plane degamma LUT and pre-defined TF;
+- plane HDR multiplier;
+- plane CTM 3x4;
+- plane shaper LUT and pre-defined TF;
+- plane 3D LUT;
+- plane blend LUT and pre-defined TF;
+- CRTC gamma pre-defined TF;
+
+Remember you can find the AMD HW color capabilities documented here:
+https://dri.freedesktop.org/docs/drm/gpu/amdgpu/display/display-manager.html#color-management-properties
+
+Worth mentioning that the pre-blending degamma block can use ROM curves
+for some pre-defined TFs, but the other blocks use the AMD color module
+to calculate this curve considering pre-defined coefficients.
+
+We need changes on DC gamut remap matrix to support the plane and CRTC
+CTM on drivers that support both. I've sent a previous patch to apply
+these changes to all DCN3+ families [6]. Here I use the same changes but
+limited to DCN301. Just let me know if you prefer the previous/expanded
+version.
+
+Finally, this is the Linux/AMD color management API before and after
+blending with the driver-specific properties:
+
++----------------------+
+| PLANE |
+| |
+| +----------------+ |
+| | AMD Degamma | |
+| | | |
+| | EOTF | 1D LUT | |
+| +--------+-------+ |
+| | |
+| +--------v-------+ |
+| | AMD HDR | |
+| | Multiply | |
+| +--------+-------+ |
+| | |
+| +--------v-------+ |
+| | AMD CTM (3x4) | |
+| +--------+-------+ |
+| | |
+| +--------v-------+ |
+| | AMD Shaper | |
+| | | |
+| | inv_EOTF | | |
+| | Custom 1D LUT | |
+| +--------+-------+ |
+| | |
+| +--------v-------+ |
+| | AMD 3D LUT | |
+| | 17^3/12-bit | |
+| +--------+-------+ |
+| | |
+| +--------v-------+ |
+| | AMD Blend | |
+| | | |
+| | EOTF | 1D LUT | |
+| +--------+-------+ |
+| | |
+++----------v---------++
+|| Blending ||
+++----------+---------++
+| CRTC | |
+| | |
+| +-------v-------+ |
+| | DRM Degamma | |
+| | | |
+| | Custom 1D LUT | |
+| +-------+-------+ |
+| | |
+| +-------v-------+ |
+| | DRM CTM (3x3) | |
+| +-------+-------+ |
+| | |
+| +-------v-------+ |
+| | DRM Gamma | |
+| | | |
+| | Custom 1D LUT | |
+| +---------------+ |
+| | *AMD Gamma | |
+| | inv_EOTF | |
+| +---------------+ |
+| |
++----------------------+
+
+Let me know your thoughts.
+
+Best Regards,
+
+Melissa Wen
+
+[1] https://lore.kernel.org/dri-devel/20230423141051.702990-1-mwen@igalia.com
+[2] https://lore.kernel.org/dri-devel/20230523221520.3115570-1-mwen@igalia.com
+[3] https://github.com/ValveSoftware/gamescope/blob/master/src/docs/Steam%20Deck%20Display%20Pipeline.png
+[4] https://github.com/ValveSoftware/gamescope
+[5] https://lore.kernel.org/dri-devel/20210730204134.21769-1-harry.wentland@amd.com
+[6] https://lore.kernel.org/dri-devel/20230721132431.692158-1-mwen@igalia.com
+
+Harry Wentland (1):
+ drm/amd/display: fix segment distribution for linear LUTs
+
+Joshua Ashton (14):
+ drm/amd/display: add plane degamma TF driver-specific property
+ drm/amd/display: add plane HDR multiplier driver-specific property
+ drm/amd/display: add plane blend LUT and TF driver-specific properties
+ drm/amd/display: add CRTC gamma TF support
+ drm/amd/display: set sdr_ref_white_level to 80 for out_transfer_func
+ drm/amd/display: mark plane as needing reset if color props change
+ drm/amd/display: add plane degamma TF and LUT support
+ drm/amd/display: add dc_fixpt_from_s3132 helper
+ drm/amd/display: add HDR multiplier support
+ drm/amd/display: handle empty LUTs in __set_input_tf
+ drm/amd/display: add plane blend LUT and TF support
+ drm/amd/display: allow newer DC hardware to use degamma ROM for PQ/HLG
+ drm/amd/display: copy 3D LUT settings from crtc state to stream_update
+ drm/amd/display: Use 3x4 CTM for plane CTM
+
+Melissa Wen (19):
+ drm/drm_mode_object: increase max objects to accommodate new color
+ props
+ drm/drm_property: make replace_property_blob_from_id a DRM helper
+ drm/drm_plane: track color mgmt changes per plane
+ drm/amd/display: add driver-specific property for plane degamma LUT
+ drm/amd/display: explicitly define EOTF and inverse EOTF
+ drm/amd/display: document AMDGPU pre-defined transfer functions
+ drm/amd/display: add plane 3D LUT driver-specific properties
+ drm/amd/display: add plane shaper LUT and TF driver-specific
+ properties
+ drm/amd/display: add CRTC gamma TF driver-specific property
+ drm/amd/display: add comments to describe DM crtc color mgmt behavior
+ drm/amd/display: encapsulate atomic regamma operation
+ drm/amd/display: decouple steps for mapping CRTC degamma to DC plane
+ drm/amd/display: reject atomic commit if setting both plane and CRTC
+ degamma
+ drm/amd/display: add plane shaper LUT support
+ drm/amd/display: add plane shaper TF support
+ drm/amd/display: add plane 3D LUT support
+ drm/amd/display: set stream gamut remap matrix to MPC for DCN301
+ drm/amd/display: add plane CTM driver-specific property
+ drm/amd/display: add plane CTM support
+
+Signed-off-by: Peter Jung <admin@ptr1337.dev>
+---
+ drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h | 71 ++
+ .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 34 +-
+ .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 100 +++
+ .../amd/display/amdgpu_dm/amdgpu_dm_color.c | 805 ++++++++++++++++--
+ .../amd/display/amdgpu_dm/amdgpu_dm_crtc.c | 72 ++
+ .../amd/display/amdgpu_dm/amdgpu_dm_plane.c | 224 ++++-
+ .../amd/display/dc/dcn10/dcn10_cm_common.c | 95 ++-
+ .../drm/amd/display/dc/dcn30/dcn30_hwseq.c | 37 +
+ .../drm/amd/display/dc/dcn30/dcn30_hwseq.h | 3 +
+ .../drm/amd/display/dc/dcn301/dcn301_init.c | 2 +-
+ .../gpu/drm/amd/display/include/fixed31_32.h | 12 +
+ drivers/gpu/drm/arm/malidp_crtc.c | 2 +-
+ drivers/gpu/drm/drm_atomic.c | 1 +
+ drivers/gpu/drm/drm_atomic_state_helper.c | 1 +
+ drivers/gpu/drm/drm_atomic_uapi.c | 43 +-
+ drivers/gpu/drm/drm_property.c | 49 ++
+ include/drm/drm_mode_object.h | 2 +-
+ include/drm/drm_plane.h | 7 +
+ include/drm/drm_property.h | 6 +
+ include/uapi/drm/drm_mode.h | 8 +
+ 20 files changed, 1446 insertions(+), 128 deletions(-)
+
+diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h
+index 32fe05c810c6fc..84bf501b02f4c2 100644
+--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h
++++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h
+@@ -343,6 +343,77 @@ struct amdgpu_mode_info {
+ int disp_priority;
+ const struct amdgpu_display_funcs *funcs;
+ const enum drm_plane_type *plane_type;
++
++ /* Driver-private color mgmt props */
++
++ /* @plane_degamma_lut_property: Plane property to set a degamma LUT to
++ * convert input space before blending.
++ */
++ struct drm_property *plane_degamma_lut_property;
++ /* @plane_degamma_lut_size_property: Plane property to define the max
++ * size of degamma LUT as supported by the driver (read-only).
++ */
++ struct drm_property *plane_degamma_lut_size_property;
++ /**
++ * @plane_degamma_tf_property: Plane pre-defined transfer function to
++ * to go from scanout/encoded values to linear values.
++ */
++ struct drm_property *plane_degamma_tf_property;
++ /**
++ * @plane_hdr_mult_property:
++ */
++ struct drm_property *plane_hdr_mult_property;
++
++ struct drm_property *plane_ctm_property;
++ /**
++ * @shaper_lut_property: Plane property to set pre-blending shaper LUT
++ * that converts color content before 3D LUT.
++ */
++ struct drm_property *plane_shaper_lut_property;
++ /**
++ * @shaper_lut_size_property: Plane property for the size of
++ * pre-blending shaper LUT as supported by the driver (read-only).
++ */
++ struct drm_property *plane_shaper_lut_size_property;
++ /**
++ * @plane_shaper_tf_property: Plane property to set a predefined
++ * transfer function for pre-blending shaper (before applying 3D LUT)
++ * with or without LUT.
++ */
++ struct drm_property *plane_shaper_tf_property;
++ /**
++ * @plane_lut3d_property: Plane property for gamma correction using a
++ * 3D LUT (pre-blending).
++ */
++ struct drm_property *plane_lut3d_property;
++ /**
++ * @plane_degamma_lut_size_property: Plane property to define the max
++ * size of 3D LUT as supported by the driver (read-only).
++ */
++ struct drm_property *plane_lut3d_size_property;
++ /**
++ * @plane_blend_lut_property: Plane property for output gamma before
++ * blending. Userspace set a blend LUT to convert colors after 3D LUT
++ * conversion. It works as a post-3D LUT 1D LUT, with shaper LUT, they
++ * are sandwiching 3D LUT with two 1D LUT.
++ */
++ struct drm_property *plane_blend_lut_property;
++ /**
++ * @plane_blend_lut_size_property: Plane property to define the max
++ * size of blend LUT as supported by the driver (read-only).
++ */
++ struct drm_property *plane_blend_lut_size_property;
++ /**
++ * @plane_blend_tf_property: Plane property to set a predefined
++ * transfer function for pre-blending blend (before applying 3D LUT)
++ * with or without LUT.
++ */
++ struct drm_property *plane_blend_tf_property;
++ /* @regamma_tf_property: Transfer function for CRTC regamma
++ * (post-blending). Possible values are defined by `enum
++ * amdgpu_transfer_function`.
++ */
++ struct drm_property *regamma_tf_property;
+ };
+
+ #define AMDGPU_MAX_BL_LEVEL 0xFF
+diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
+index e5554a36e8c8b2..43ef0e5f97ae1a 100644
+--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
+@@ -3943,6 +3943,11 @@ static int amdgpu_dm_mode_config_init(struct amdgpu_device *adev)
+ return r;
+ }
+
++#ifdef AMD_PRIVATE_COLOR
++ if (amdgpu_dm_create_color_properties(adev))
++ return -ENOMEM;
++#endif
++
+ r = amdgpu_dm_audio_init(adev);
+ if (r) {
+ dc_release_state(state->context);
+@@ -4992,7 +4997,9 @@ static int fill_dc_plane_attributes(struct amdgpu_device *adev,
+ * Always set input transfer function, since plane state is refreshed
+ * every time.
+ */
+- ret = amdgpu_dm_update_plane_color_mgmt(dm_crtc_state, dc_plane_state);
++ ret = amdgpu_dm_update_plane_color_mgmt(dm_crtc_state,
++ plane_state,
++ dc_plane_state);
+ if (ret)
+ return ret;
+
+@@ -8007,6 +8014,10 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_state *state,
+ bundle->surface_updates[planes_count].gamma = dc_plane->gamma_correction;
+ bundle->surface_updates[planes_count].in_transfer_func = dc_plane->in_transfer_func;
+ bundle->surface_updates[planes_count].gamut_remap_matrix = &dc_plane->gamut_remap_matrix;
++ bundle->surface_updates[planes_count].hdr_mult = dc_plane->hdr_mult;
++ bundle->surface_updates[planes_count].func_shaper = dc_plane->in_shaper_func;
++ bundle->surface_updates[planes_count].lut3d_func = dc_plane->lut3d_func;
++ bundle->surface_updates[planes_count].blend_tf = dc_plane->blend_tf;
+ }
+
+ amdgpu_dm_plane_fill_dc_scaling_info(dm->adev, new_plane_state,
+@@ -8215,6 +8226,10 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_state *state,
+ &acrtc_state->stream->csc_color_matrix;
+ bundle->stream_update.out_transfer_func =
+ acrtc_state->stream->out_transfer_func;
++ bundle->stream_update.lut3d_func =
++ (struct dc_3dlut *) acrtc_state->stream->lut3d_func;
++ bundle->stream_update.func_shaper =
++ (struct dc_transfer_func *) acrtc_state->stream->func_shaper;
+ }
+
+ acrtc_state->stream->abm_level = acrtc_state->abm_level;
+@@ -9405,6 +9420,7 @@ static int dm_update_crtc_state(struct amdgpu_display_manager *dm,
+ * when a modeset is needed, to ensure it gets reprogrammed.
+ */
+ if (dm_new_crtc_state->base.color_mgmt_changed ||
++ dm_old_crtc_state->regamma_tf != dm_new_crtc_state->regamma_tf ||
+ drm_atomic_crtc_needs_modeset(new_crtc_state)) {
+ ret = amdgpu_dm_update_crtc_color_mgmt(dm_new_crtc_state);
+ if (ret)
+@@ -9472,6 +9488,10 @@ static bool should_reset_plane(struct drm_atomic_state *state,
+ */
+ for_each_oldnew_plane_in_state(state, other, old_other_state, new_other_state, i) {
+ struct amdgpu_framebuffer *old_afb, *new_afb;
++ struct dm_plane_state *dm_new_other_state, *dm_old_other_state;
++
++ dm_new_other_state = to_dm_plane_state(new_other_state);
++ dm_old_other_state = to_dm_plane_state(old_other_state);
+
+ if (other->type == DRM_PLANE_TYPE_CURSOR)
+ continue;
+@@ -9508,6 +9528,18 @@ static bool should_reset_plane(struct drm_atomic_state *state,
+ old_other_state->color_encoding != new_other_state->color_encoding)
+ return true;
+
++ /* HDR/Transfer Function changes. */
++ if (dm_old_other_state->degamma_tf != dm_new_other_state->degamma_tf ||
++ dm_old_other_state->degamma_lut != dm_new_other_state->degamma_lut ||
++ dm_old_other_state->hdr_mult != dm_new_other_state->hdr_mult ||
++ dm_old_other_state->ctm != dm_new_other_state->ctm ||
++ dm_old_other_state->shaper_lut != dm_new_other_state->shaper_lut ||
++ dm_old_other_state->shaper_tf != dm_new_other_state->shaper_tf ||
++ dm_old_other_state->lut3d != dm_new_other_state->lut3d ||
++ dm_old_other_state->blend_lut != dm_new_other_state->blend_lut ||
++ dm_old_other_state->blend_tf != dm_new_other_state->blend_tf)
++ return true;
++
+ /* Framebuffer checks fall at the end. */
+ if (!old_other_state->fb || !new_other_state->fb)
+ continue;
+diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
+index 9fb5bb3a75a777..f92bbd7ed867b0 100644
+--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
+@@ -51,6 +51,8 @@
+
+ #define AMDGPU_DMUB_NOTIFICATION_MAX 5
+
++#define AMDGPU_HDR_MULT_DEFAULT (0x100000000LL)
++
+ /*
+ #include "include/amdgpu_dal_power_if.h"
+ #include "amdgpu_dm_irq.h"
+@@ -702,9 +704,91 @@ static inline void amdgpu_dm_set_mst_status(uint8_t *status,
+
+ extern const struct amdgpu_ip_block_version dm_ip_block;
+
++enum amdgpu_transfer_function {
++ AMDGPU_TRANSFER_FUNCTION_DEFAULT,
++ AMDGPU_TRANSFER_FUNCTION_SRGB_EOTF,
++ AMDGPU_TRANSFER_FUNCTION_BT709_EOTF,
++ AMDGPU_TRANSFER_FUNCTION_PQ_EOTF,
++ AMDGPU_TRANSFER_FUNCTION_LINEAR,
++ AMDGPU_TRANSFER_FUNCTION_UNITY,
++ AMDGPU_TRANSFER_FUNCTION_GAMMA22_EOTF,
++ AMDGPU_TRANSFER_FUNCTION_GAMMA24_EOTF,
++ AMDGPU_TRANSFER_FUNCTION_GAMMA26_EOTF,
++ AMDGPU_TRANSFER_FUNCTION_SRGB_INV_EOTF,
++ AMDGPU_TRANSFER_FUNCTION_BT709_INV_EOTF,
++ AMDGPU_TRANSFER_FUNCTION_PQ_INV_EOTF,
++ AMDGPU_TRANSFER_FUNCTION_GAMMA22_INV_EOTF,
++ AMDGPU_TRANSFER_FUNCTION_GAMMA24_INV_EOTF,
++ AMDGPU_TRANSFER_FUNCTION_GAMMA26_INV_EOTF,
++ AMDGPU_TRANSFER_FUNCTION_COUNT
++};
++
+ struct dm_plane_state {
+ struct drm_plane_state base;
+ struct dc_plane_state *dc_state;
++
++ /* Plane color mgmt */
++ /**
++ * @degamma_lut:
++ *
++ * 1D LUT for mapping framebuffer/plane pixel data before sampling or
++ * blending operations. It's usually applied to linearize input space.
++ * The blob (if not NULL) is an array of &struct drm_color_lut.
++ */
++ struct drm_property_blob *degamma_lut;
++ /**
++ * @degamma_tf:
++ *
++ * Predefined transfer function to tell DC driver the input space to
++ * linearize.
++ */
++ enum amdgpu_transfer_function degamma_tf;
++ /**
++ * @hdr_mult:
++ *
++ * Multiplier to 'gain' the plane. When PQ is decoded using the fixed
++ * func transfer function to the internal FP16 fb, 1.0 -> 80 nits (on
++ * AMD at least). When sRGB is decoded, 1.0 -> 1.0, obviously.
++ * Therefore, 1.0 multiplier = 80 nits for SDR content. So if you
++ * want, 203 nits for SDR content, pass in (203.0 / 80.0). Format is
++ * S31.32 sign-magnitude.
++ */
++ __u64 hdr_mult;
++ /**
++ * @ctm:
++ *
++ * Color transformation matrix. See drm_crtc_enable_color_mgmt(). The
++ * blob (if not NULL) is a &struct drm_color_ctm.
++ */
++ struct drm_property_blob *ctm;
++ /**
++ * @shaper_lut: shaper lookup table blob. The blob (if not NULL) is an
++ * array of &struct drm_color_lut.
++ */
++ struct drm_property_blob *shaper_lut;
++ /**
++ * @shaper_tf:
++ *
++ * Predefined transfer function to delinearize color space.
++ */
++ enum amdgpu_transfer_function shaper_tf;
++ /**
++ * @lut3d: 3D lookup table blob. The blob (if not NULL) is an array of
++ * &struct drm_color_lut.
++ */
++ struct drm_property_blob *lut3d;
++ /**
++ * @blend_lut: blend lut lookup table blob. The blob (if not NULL) is an
++ * array of &struct drm_color_lut.
++ */
++ struct drm_property_blob *blend_lut;
++ /**
++ * @blend_tf:
++ *
++ * Pre-defined transfer function for converting plane pixel data before
++ * applying blend LUT.
++ */
++ enum amdgpu_transfer_function blend_tf;
+ };
+
+ struct dm_crtc_state {
+@@ -729,6 +813,14 @@ struct dm_crtc_state {
+ struct dc_info_packet vrr_infopacket;
+
+ int abm_level;
++
++ /**
++ * @regamma_tf:
++ *
++ * Pre-defined transfer function for converting internal FB -> wire
++ * encoding.
++ */
++ enum amdgpu_transfer_function regamma_tf;
+ };
+
+ #define to_dm_crtc_state(x) container_of(x, struct dm_crtc_state, base)
+@@ -790,14 +882,22 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector,
+
+ void amdgpu_dm_trigger_timing_sync(struct drm_device *dev);
+
++/* 3D LUT max size is 17x17x17 */
++#define MAX_COLOR_3DLUT_ENTRIES 4913
++#define MAX_COLOR_3DLUT_BITDEPTH 12
++int amdgpu_dm_verify_lut3d_size(struct amdgpu_device *adev,
++ struct drm_plane_state *plane_state);
++/* 1D LUT size */
+ #define MAX_COLOR_LUT_ENTRIES 4096
+ /* Legacy gamm LUT users such as X doesn't like large LUT sizes */
+ #define MAX_COLOR_LEGACY_LUT_ENTRIES 256
+
+ void amdgpu_dm_init_color_mod(void);
++int amdgpu_dm_create_color_properties(struct amdgpu_device *adev);
+ int amdgpu_dm_verify_lut_sizes(const struct drm_crtc_state *crtc_state);
+ int amdgpu_dm_update_crtc_color_mgmt(struct dm_crtc_state *crtc);
+ int amdgpu_dm_update_plane_color_mgmt(struct dm_crtc_state *crtc,
++ struct drm_plane_state *plane_state,
+ struct dc_plane_state *dc_plane_state);
+
+ void amdgpu_dm_update_connector_after_detect(
+diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c
+index a4cb23d059bd6a..0a51af44efd5f7 100644
+--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c
++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c
+@@ -72,6 +72,7 @@
+ */
+
+ #define MAX_DRM_LUT_VALUE 0xFFFF
++#define SDR_WHITE_LEVEL_INIT_VALUE 80
+
+ /**
+ * amdgpu_dm_init_color_mod - Initialize the color module.
+@@ -84,6 +85,213 @@ void amdgpu_dm_init_color_mod(void)
+ setup_x_points_distribution();
+ }
+
++#ifdef AMD_PRIVATE_COLOR
++/* Pre-defined Transfer Functions (TF)
++ *
++ * AMD driver supports pre-defined mathematical functions for transferring
++ * between encoded values and optical/linear space. Depending on HW color caps,
++ * ROMs and curves built by the AMD color module support these transforms.
++ *
++ * The driver-specific color implementation exposes properties for pre-blending
++ * degamma TF, shaper TF (before 3D LUT), and blend(dpp.ogam) TF and
++ * post-blending regamma (mpc.ogam) TF. However, only pre-blending degamma
++ * supports ROM curves. AMD color module uses pre-defined coefficients to build
++ * curves for the other blocks. What can be done by each color block is
++ * described by struct dpp_color_capsand struct mpc_color_caps.
++ *
++ * AMD driver-specific color API exposes the following pre-defined transfer
++ * functions:
++ *
++ * - Linear/Unity: linear/identity relationship between pixel value and
++ * luminance value;
++ * - Gamma 2.2, Gamma 2.4, Gamma 2.6: pure gamma functions;
++ * - sRGB: 2.4 gamma with small initial linear section as standardized by IEC
++ * 61966-2-1:1999;
++ * - BT.709 (BT.1886): 2.4 gamma with differences in the dark end of the scale.
++ * Used in HD-TV and standardized by ITU-R BT.1886;
++ * - PQ (Perceptual Quantizer): used for HDR display, allows luminance range
++ * capability of 0 to 10,000 nits; standardized by SMPTE ST 2084.
++ *
++ * In the driver-specific API, color block names attached to TF properties
++ * suggest the intention regarding non-linear encoding pixel's luminance
++ * values. As some newer encodings don't use gamma curve, we make encoding and
++ * decoding explicit by defining an enum list of transfer functions supported
++ * in terms of EOTF and inverse EOTF, where:
++ *
++ * - EOTF (electro-optical transfer function): is the transfer function to go
++ * from the encoded value to an optical (linear) value. De-gamma functions
++ * traditionally do this.
++ * - Inverse EOTF (simply the inverse of the EOTF): is usually intended to go
++ * from an optical/linear space (which might have been used for blending)
++ * back to the encoded values. Gamma functions traditionally do this.
++ */
++static const char * const
++amdgpu_transfer_function_names[] = {
++ [AMDGPU_TRANSFER_FUNCTION_DEFAULT] = "Default",
++ [AMDGPU_TRANSFER_FUNCTION_LINEAR] = "Linear",
++ [AMDGPU_TRANSFER_FUNCTION_UNITY] = "Unity",
++ [AMDGPU_TRANSFER_FUNCTION_SRGB_EOTF] = "sRGB EOTF",
++ [AMDGPU_TRANSFER_FUNCTION_BT709_EOTF] = "BT.709 EOTF",
++ [AMDGPU_TRANSFER_FUNCTION_PQ_EOTF] = "PQ EOTF",
++ [AMDGPU_TRANSFER_FUNCTION_GAMMA22_EOTF] = "Gamma 2.2 EOTF",
++ [AMDGPU_TRANSFER_FUNCTION_GAMMA24_EOTF] = "Gamma 2.4 EOTF",
++ [AMDGPU_TRANSFER_FUNCTION_GAMMA26_EOTF] = "Gamma 2.6 EOTF",
++ [AMDGPU_TRANSFER_FUNCTION_SRGB_INV_EOTF] = "sRGB inv_EOTF",
++ [AMDGPU_TRANSFER_FUNCTION_BT709_INV_EOTF] = "BT.709 inv_EOTF",
++ [AMDGPU_TRANSFER_FUNCTION_PQ_INV_EOTF] = "PQ inv_EOTF",
++ [AMDGPU_TRANSFER_FUNCTION_GAMMA22_INV_EOTF] = "Gamma 2.2 inv_EOTF",
++ [AMDGPU_TRANSFER_FUNCTION_GAMMA24_INV_EOTF] = "Gamma 2.4 inv_EOTF",
++ [AMDGPU_TRANSFER_FUNCTION_GAMMA26_INV_EOTF] = "Gamma 2.6 inv_EOTF",
++};
++
++static const u32 amdgpu_eotf =
++ BIT(AMDGPU_TRANSFER_FUNCTION_SRGB_EOTF) |
++ BIT(AMDGPU_TRANSFER_FUNCTION_BT709_EOTF) |
++ BIT(AMDGPU_TRANSFER_FUNCTION_PQ_EOTF) |
++ BIT(AMDGPU_TRANSFER_FUNCTION_GAMMA22_EOTF) |
++ BIT(AMDGPU_TRANSFER_FUNCTION_GAMMA24_EOTF) |
++ BIT(AMDGPU_TRANSFER_FUNCTION_GAMMA26_EOTF);
++
++static const u32 amdgpu_inv_eotf =
++ BIT(AMDGPU_TRANSFER_FUNCTION_SRGB_INV_EOTF) |
++ BIT(AMDGPU_TRANSFER_FUNCTION_BT709_INV_EOTF) |
++ BIT(AMDGPU_TRANSFER_FUNCTION_PQ_INV_EOTF) |
++ BIT(AMDGPU_TRANSFER_FUNCTION_GAMMA22_INV_EOTF) |
++ BIT(AMDGPU_TRANSFER_FUNCTION_GAMMA24_INV_EOTF) |
++ BIT(AMDGPU_TRANSFER_FUNCTION_GAMMA26_INV_EOTF);
++
++static struct drm_property *
++amdgpu_create_tf_property(struct drm_device *dev,
++ const char *name,
++ u32 supported_tf)
++{
++ u32 transfer_functions = supported_tf |
++ BIT(AMDGPU_TRANSFER_FUNCTION_DEFAULT) |
++ BIT(AMDGPU_TRANSFER_FUNCTION_LINEAR) |
++ BIT(AMDGPU_TRANSFER_FUNCTION_UNITY);
++ struct drm_prop_enum_list enum_list[AMDGPU_TRANSFER_FUNCTION_COUNT];
++ int i, len;
++
++ len = 0;
++ for (i = 0; i < AMDGPU_TRANSFER_FUNCTION_COUNT; i++) {
++ if ((transfer_functions & BIT(i)) == 0)
++ continue;
++
++ enum_list[len].type = i;
++ enum_list[len].name = amdgpu_transfer_function_names[i];
++ len++;
++ }
++
++ return drm_property_create_enum(dev, DRM_MODE_PROP_ENUM,
++ name, enum_list, len);
++}
++
++int
++amdgpu_dm_create_color_properties(struct amdgpu_device *adev)
++{
++ struct drm_property *prop;
++
++ prop = drm_property_create(adev_to_drm(adev),
++ DRM_MODE_PROP_BLOB,
++ "AMD_PLANE_DEGAMMA_LUT", 0);
++ if (!prop)
++ return -ENOMEM;
++ adev->mode_info.plane_degamma_lut_property = prop;
++
++ prop = drm_property_create_range(adev_to_drm(adev),
++ DRM_MODE_PROP_IMMUTABLE,
++ "AMD_PLANE_DEGAMMA_LUT_SIZE", 0, UINT_MAX);
++ if (!prop)
++ return -ENOMEM;
++ adev->mode_info.plane_degamma_lut_size_property = prop;
++
++ prop = amdgpu_create_tf_property(adev_to_drm(adev),
++ "AMD_PLANE_DEGAMMA_TF",
++ amdgpu_eotf);
++ if (!prop)
++ return -ENOMEM;
++ adev->mode_info.plane_degamma_tf_property = prop;
++
++ prop = drm_property_create_range(adev_to_drm(adev),
++ 0, "AMD_PLANE_HDR_MULT", 0, U64_MAX);
++ if (!prop)
++ return -ENOMEM;
++ adev->mode_info.plane_hdr_mult_property = prop;
++
++ prop = drm_property_create(adev_to_drm(adev),
++ DRM_MODE_PROP_BLOB,
++ "AMD_PLANE_CTM", 0);
++ if (!prop)
++ return -ENOMEM;
++ adev->mode_info.plane_ctm_property = prop;
++
++ prop = drm_property_create(adev_to_drm(adev),
++ DRM_MODE_PROP_BLOB,
++ "AMD_PLANE_SHAPER_LUT", 0);
++ if (!prop)
++ return -ENOMEM;
++ adev->mode_info.plane_shaper_lut_property = prop;
++
++ prop = drm_property_create_range(adev_to_drm(adev),
++ DRM_MODE_PROP_IMMUTABLE,
++ "AMD_PLANE_SHAPER_LUT_SIZE", 0, UINT_MAX);
++ if (!prop)
++ return -ENOMEM;
++ adev->mode_info.plane_shaper_lut_size_property = prop;
++
++ prop = amdgpu_create_tf_property(adev_to_drm(adev),
++ "AMD_PLANE_SHAPER_TF",
++ amdgpu_inv_eotf);
++ if (!prop)
++ return -ENOMEM;
++ adev->mode_info.plane_shaper_tf_property = prop;
++
++ prop = drm_property_create(adev_to_drm(adev),
++ DRM_MODE_PROP_BLOB,
++ "AMD_PLANE_LUT3D", 0);
++ if (!prop)
++ return -ENOMEM;
++ adev->mode_info.plane_lut3d_property = prop;
++
++ prop = drm_property_create_range(adev_to_drm(adev),
++ DRM_MODE_PROP_IMMUTABLE,
++ "AMD_PLANE_LUT3D_SIZE", 0, UINT_MAX);
++ if (!prop)
++ return -ENOMEM;
++ adev->mode_info.plane_lut3d_size_property = prop;
++
++ prop = drm_property_create(adev_to_drm(adev),
++ DRM_MODE_PROP_BLOB,
++ "AMD_PLANE_BLEND_LUT", 0);
++ if (!prop)
++ return -ENOMEM;
++ adev->mode_info.plane_blend_lut_property = prop;
++
++ prop = drm_property_create_range(adev_to_drm(adev),
++ DRM_MODE_PROP_IMMUTABLE,
++ "AMD_PLANE_BLEND_LUT_SIZE", 0, UINT_MAX);
++ if (!prop)
++ return -ENOMEM;
++ adev->mode_info.plane_blend_lut_size_property = prop;
++
++ prop = amdgpu_create_tf_property(adev_to_drm(adev),
++ "AMD_PLANE_BLEND_TF",
++ amdgpu_eotf);
++ if (!prop)
++ return -ENOMEM;
++ adev->mode_info.plane_blend_tf_property = prop;
++
++ prop = amdgpu_create_tf_property(adev_to_drm(adev),
++ "AMD_CRTC_REGAMMA_TF",
++ amdgpu_inv_eotf);
++ if (!prop)
++ return -ENOMEM;
++ adev->mode_info.regamma_tf_property = prop;
++
++ return 0;
++}
++#endif
++
+ /**
+ * __extract_blob_lut - Extracts the DRM lut and lut size from a blob.
+ * @blob: DRM color mgmt property blob
+@@ -182,7 +390,6 @@ static void __drm_lut_to_dc_gamma(const struct drm_color_lut *lut,
+ static void __drm_ctm_to_dc_matrix(const struct drm_color_ctm *ctm,
+ struct fixed31_32 *matrix)
+ {
+- int64_t val;
+ int i;
+
+ /*
+@@ -201,12 +408,33 @@ static void __drm_ctm_to_dc_matrix(const struct drm_color_ctm *ctm,
+ }
+
+ /* gamut_remap_matrix[i] = ctm[i - floor(i/4)] */
+- val = ctm->matrix[i - (i / 4)];
+- /* If negative, convert to 2's complement. */
+- if (val & (1ULL << 63))
+- val = -(val & ~(1ULL << 63));
++ matrix[i] = dc_fixpt_from_s3132(ctm->matrix[i - (i / 4)]);
++ }
++}
+
+- matrix[i].value = val;
++/**
++ * __drm_ctm2_to_dc_matrix - converts a DRM CTM2 to a DC CSC float matrix
++ * @ctm: DRM color transformation matrix
++ * @matrix: DC CSC float matrix
++ *
++ * The matrix needs to be a 3x4 (12 entry) matrix.
++ */
++static void __drm_ctm2_to_dc_matrix(const struct drm_color_ctm2 *ctm,
++ struct fixed31_32 *matrix)
++{
++ int i;
++
++ /*
++ * DRM gives a 3x3 matrix, but DC wants 3x4. Assuming we're operating
++ * with homogeneous coordinates, augment the matrix with 0's.
++ *
++ * The format provided is S31.32, using signed-magnitude representation.
++ * Our fixed31_32 is also S31.32, but is using 2's complement. We have
++ * to convert from signed-magnitude to 2's complement.
++ */
++ for (i = 0; i < 12; i++) {
++ /* gamut_remap_matrix[i] = ctm[i - floor(i/4)] */
++ matrix[i] = dc_fixpt_from_s3132(ctm->matrix[i]);
+ }
+ }
+
+@@ -268,16 +496,18 @@ static int __set_output_tf(struct dc_transfer_func *func,
+ struct calculate_buffer cal_buffer = {0};
+ bool res;
+
+- ASSERT(lut && lut_size == MAX_COLOR_LUT_ENTRIES);
+-
+ cal_buffer.buffer_index = -1;
+
+- gamma = dc_create_gamma();
+- if (!gamma)
+- return -ENOMEM;
++ if (lut_size) {
++ ASSERT(lut && lut_size == MAX_COLOR_LUT_ENTRIES);
+
+- gamma->num_entries = lut_size;
+- __drm_lut_to_dc_gamma(lut, gamma, false);
++ gamma = dc_create_gamma();
++ if (!gamma)
++ return -ENOMEM;
++
++ gamma->num_entries = lut_size;
++ __drm_lut_to_dc_gamma(lut, gamma, false);
++ }
+
+ if (func->tf == TRANSFER_FUNCTION_LINEAR) {
+ /*
+@@ -285,27 +515,63 @@ static int __set_output_tf(struct dc_transfer_func *func,
+ * on top of a linear input. But degamma params can be used
+ * instead to simulate this.
+ */
+- gamma->type = GAMMA_CUSTOM;
++ if (gamma)
++ gamma->type = GAMMA_CUSTOM;
+ res = mod_color_calculate_degamma_params(NULL, func,
+- gamma, true);
++ gamma, gamma != NULL);
+ } else {
+ /*
+ * Assume sRGB. The actual mapping will depend on whether the
+ * input was legacy or not.
+ */
+- gamma->type = GAMMA_CS_TFM_1D;
+- res = mod_color_calculate_regamma_params(func, gamma, false,
++ if (gamma)
++ gamma->type = GAMMA_CS_TFM_1D;
++ res = mod_color_calculate_regamma_params(func, gamma, gamma != NULL,
+ has_rom, NULL, &cal_buffer);
+ }
+
+- dc_gamma_release(&gamma);
++ if (gamma)
++ dc_gamma_release(&gamma);
+
+ return res ? 0 : -ENOMEM;
+ }
+
++static int amdgpu_dm_set_atomic_regamma(struct dc_stream_state *stream,
++ const struct drm_color_lut *regamma_lut,
++ uint32_t regamma_size, bool has_rom,
++ enum dc_transfer_func_predefined tf)
++{
++ struct dc_transfer_func *out_tf = stream->out_transfer_func;
++ int ret = 0;
++
++ if (regamma_size || tf != TRANSFER_FUNCTION_LINEAR) {
++ /* CRTC RGM goes into RGM LUT.
++ *
++ * Note: there is no implicit sRGB regamma here. We are using
++ * degamma calculation from color module to calculate the curve
++ * from a linear base.
++ */
++ out_tf->type = TF_TYPE_DISTRIBUTED_POINTS;
++ out_tf->tf = tf;
++ out_tf->sdr_ref_white_level = SDR_WHITE_LEVEL_INIT_VALUE;
++
++ ret = __set_output_tf(out_tf, regamma_lut, regamma_size, has_rom);
++ } else {
++ /*
++ * No CRTC RGM means we can just put the block into bypass
++ * since we don't have any plane level adjustments using it.
++ */
++ out_tf->type = TF_TYPE_BYPASS;
++ out_tf->tf = TRANSFER_FUNCTION_LINEAR;
++ }
++
++ return ret;
++}
++
+ /**
+ * __set_input_tf - calculates the input transfer function based on expected
+ * input space.
++ * @caps: dc color capabilities
+ * @func: transfer function
+ * @lut: lookup table that defines the color space
+ * @lut_size: size of respective lut.
+@@ -313,27 +579,249 @@ static int __set_output_tf(struct dc_transfer_func *func,
+ * Returns:
+ * 0 in case of success. -ENOMEM if fails.
+ */
+-static int __set_input_tf(struct dc_transfer_func *func,
++static int __set_input_tf(struct dc_color_caps *caps, struct dc_transfer_func *func,
+ const struct drm_color_lut *lut, uint32_t lut_size)
+ {
+ struct dc_gamma *gamma = NULL;
+ bool res;
+
+- gamma = dc_create_gamma();
+- if (!gamma)
+- return -ENOMEM;
++ if (lut_size) {
++ gamma = dc_create_gamma();
++ if (!gamma)
++ return -ENOMEM;
+
+- gamma->type = GAMMA_CUSTOM;
+- gamma->num_entries = lut_size;
++ gamma->type = GAMMA_CUSTOM;
++ gamma->num_entries = lut_size;
+
+- __drm_lut_to_dc_gamma(lut, gamma, false);
++ __drm_lut_to_dc_gamma(lut, gamma, false);
++ }
+
+- res = mod_color_calculate_degamma_params(NULL, func, gamma, true);
+- dc_gamma_release(&gamma);
++ res = mod_color_calculate_degamma_params(caps, func, gamma, gamma != NULL);
++
++ if (gamma)
++ dc_gamma_release(&gamma);
+
+ return res ? 0 : -ENOMEM;
+ }
+
++static enum dc_transfer_func_predefined
++amdgpu_tf_to_dc_tf(enum amdgpu_transfer_function tf)
++{
++ switch (tf)
++ {
++ default:
++ case AMDGPU_TRANSFER_FUNCTION_DEFAULT:
++ case AMDGPU_TRANSFER_FUNCTION_LINEAR:
++ return TRANSFER_FUNCTION_LINEAR;
++ case AMDGPU_TRANSFER_FUNCTION_SRGB_EOTF:
++ case AMDGPU_TRANSFER_FUNCTION_SRGB_INV_EOTF:
++ return TRANSFER_FUNCTION_SRGB;
++ case AMDGPU_TRANSFER_FUNCTION_BT709_EOTF:
++ case AMDGPU_TRANSFER_FUNCTION_BT709_INV_EOTF:
++ return TRANSFER_FUNCTION_BT709;
++ case AMDGPU_TRANSFER_FUNCTION_PQ_EOTF:
++ case AMDGPU_TRANSFER_FUNCTION_PQ_INV_EOTF:
++ return TRANSFER_FUNCTION_PQ;
++ case AMDGPU_TRANSFER_FUNCTION_UNITY:
++ return TRANSFER_FUNCTION_UNITY;
++ case AMDGPU_TRANSFER_FUNCTION_GAMMA22_EOTF:
++ case AMDGPU_TRANSFER_FUNCTION_GAMMA22_INV_EOTF:
++ return TRANSFER_FUNCTION_GAMMA22;
++ case AMDGPU_TRANSFER_FUNCTION_GAMMA24_EOTF:
++ case AMDGPU_TRANSFER_FUNCTION_GAMMA24_INV_EOTF:
++ return TRANSFER_FUNCTION_GAMMA24;
++ case AMDGPU_TRANSFER_FUNCTION_GAMMA26_EOTF:
++ case AMDGPU_TRANSFER_FUNCTION_GAMMA26_INV_EOTF:
++ return TRANSFER_FUNCTION_GAMMA26;
++ }
++}
++
++static void __to_dc_lut3d_color(struct dc_rgb *rgb,
++ const struct drm_color_lut lut,
++ int bit_precision)
++{
++ rgb->red = drm_color_lut_extract(lut.red, bit_precision);
++ rgb->green = drm_color_lut_extract(lut.green, bit_precision);
++ rgb->blue = drm_color_lut_extract(lut.blue, bit_precision);
++}
++
++static void __drm_3dlut_to_dc_3dlut(const struct drm_color_lut *lut,
++ uint32_t lut3d_size,
++ struct tetrahedral_params *params,
++ bool use_tetrahedral_9,
++ int bit_depth)
++{
++ struct dc_rgb *lut0;
++ struct dc_rgb *lut1;
++ struct dc_rgb *lut2;
++ struct dc_rgb *lut3;
++ int lut_i, i;
++
++
++ if (use_tetrahedral_9) {
++ lut0 = params->tetrahedral_9.lut0;
++ lut1 = params->tetrahedral_9.lut1;
++ lut2 = params->tetrahedral_9.lut2;
++ lut3 = params->tetrahedral_9.lut3;
++ } else {
++ lut0 = params->tetrahedral_17.lut0;
++ lut1 = params->tetrahedral_17.lut1;
++ lut2 = params->tetrahedral_17.lut2;
++ lut3 = params->tetrahedral_17.lut3;
++ }
++
++ for (lut_i = 0, i = 0; i < lut3d_size - 4; lut_i++, i += 4) {
++ /* We should consider the 3dlut RGB values are distributed
++ * along four arrays lut0-3 where the first sizes 1229 and the
++ * other 1228. The bit depth supported for 3dlut channel is
++ * 12-bit, but DC also supports 10-bit.
++ *
++ * TODO: improve color pipeline API to enable the userspace set
++ * bit depth and 3D LUT size/stride, as specified by VA-API.
++ */
++ __to_dc_lut3d_color(&lut0[lut_i], lut[i], bit_depth);
++ __to_dc_lut3d_color(&lut1[lut_i], lut[i + 1], bit_depth);
++ __to_dc_lut3d_color(&lut2[lut_i], lut[i + 2], bit_depth);
++ __to_dc_lut3d_color(&lut3[lut_i], lut[i + 3], bit_depth);
++ }
++ /* lut0 has 1229 points (lut_size/4 + 1) */
++ __to_dc_lut3d_color(&lut0[lut_i], lut[i], bit_depth);
++}
++
++/* amdgpu_dm_atomic_lut3d - set DRM 3D LUT to DC stream
++ * @drm_lut3d: DRM CRTC (user) 3D LUT
++ * @drm_lut3d_size: size of 3D LUT
++ * @lut3d: DC 3D LUT
++ *
++ * Map DRM CRTC 3D LUT to DC 3D LUT and all necessary bits to program it
++ * on DCN MPC accordingly.
++ */
++static void amdgpu_dm_atomic_lut3d(const struct drm_color_lut *drm_lut,
++ uint32_t drm_lut3d_size,
++ struct dc_3dlut *lut)
++{
++ if (!drm_lut3d_size) {
++ lut->state.bits.initialized = 0;
++ } else {
++ /* Stride and bit depth are not programmable by API yet.
++ * Therefore, only supports 17x17x17 3D LUT (12-bit).
++ */
++ lut->lut_3d.use_tetrahedral_9 = false;
++ lut->lut_3d.use_12bits = true;
++ lut->state.bits.initialized = 1;
++ __drm_3dlut_to_dc_3dlut(drm_lut, drm_lut3d_size, &lut->lut_3d,
++ lut->lut_3d.use_tetrahedral_9,
++ MAX_COLOR_3DLUT_BITDEPTH);
++ }
++}
++
++static int amdgpu_dm_atomic_shaper_lut(const struct drm_color_lut *shaper_lut,
++ bool has_rom,
++ enum dc_transfer_func_predefined tf,
++ uint32_t shaper_size,
++ struct dc_transfer_func *func_shaper)
++{
++ int ret = 0;
++
++ if (shaper_size || tf != TRANSFER_FUNCTION_LINEAR) {
++ /* If DRM shaper LUT is set, we assume a linear color space
++ * (linearized by DRM degamma 1D LUT or not)
++ */
++ func_shaper->type = TF_TYPE_DISTRIBUTED_POINTS;
++ func_shaper->tf = tf;
++ func_shaper->sdr_ref_white_level = SDR_WHITE_LEVEL_INIT_VALUE;
++
++ ret = __set_output_tf(func_shaper, shaper_lut, shaper_size, has_rom);
++ } else {
++ func_shaper->type = TF_TYPE_BYPASS;
++ func_shaper->tf = TRANSFER_FUNCTION_LINEAR;
++ }
++
++ return ret;
++}
++
++static int amdgpu_dm_atomic_blend_lut(const struct drm_color_lut *blend_lut,
++ bool has_rom,
++ enum dc_transfer_func_predefined tf,
++ uint32_t blend_size,
++ struct dc_transfer_func *func_blend)
++{
++ int ret = 0;
++
++ if (blend_size || tf != TRANSFER_FUNCTION_LINEAR) {
++ /* DRM plane gamma LUT or TF means we are linearizing color
++ * space before blending (similar to degamma programming). As
++ * we don't have hardcoded curve support, or we use AMD color
++ * module to fill the parameters that will be translated to HW
++ * points.
++ */
++ func_blend->type = TF_TYPE_DISTRIBUTED_POINTS;
++ func_blend->tf = tf;
++ func_blend->sdr_ref_white_level = SDR_WHITE_LEVEL_INIT_VALUE;
++
++ ret = __set_input_tf(NULL, func_blend, blend_lut, blend_size);
++ } else {
++ func_blend->type = TF_TYPE_BYPASS;
++ func_blend->tf = TRANSFER_FUNCTION_LINEAR;
++ }
++
++ return ret;
++}
++
++/* amdgpu_dm_lut3d_size - get expected size according to hw color caps
++ * @adev: amdgpu device
++ * @lut_size: default size
++ *
++ * Return:
++ * lut_size if DC 3D LUT is supported, zero otherwise.
++ */
++static uint32_t amdgpu_dm_get_lut3d_size(struct amdgpu_device *adev,
++ uint32_t lut_size)
++{
++ return adev->dm.dc->caps.color.dpp.hw_3d_lut ? lut_size : 0;
++}
++
++/**
++ * amdgpu_dm_verify_lut3d_size - verifies if 3D LUT is supported and if DRM 3D
++ * LUT matches the hw supported size
++ * @adev: amdgpu device
++ * @crtc_state: the DRM CRTC state
++ *
++ * Verifies if post-blending (MPC) 3D LUT is supported by the HW (DCN 3.0 or
++ * newer) and if the DRM 3D LUT matches the supported size.
++ *
++ * Returns:
++ * 0 on success. -EINVAL if lut size are invalid.
++ */
++int amdgpu_dm_verify_lut3d_size(struct amdgpu_device *adev,
++ struct drm_plane_state *plane_state)
++{
++ struct dm_plane_state *dm_plane_state = to_dm_plane_state(plane_state);
++ const struct drm_color_lut *shaper = NULL, *lut3d = NULL;
++ uint32_t exp_size, size;
++
++ /* shaper LUT is only available if 3D LUT color caps*/
++ exp_size = amdgpu_dm_get_lut3d_size(adev, MAX_COLOR_LUT_ENTRIES);
++ shaper = __extract_blob_lut(dm_plane_state->shaper_lut, &size);
++
++ if (shaper && size != exp_size) {
++ drm_dbg(&adev->ddev,
++ "Invalid Shaper LUT size. Should be %u but got %u.\n",
++ exp_size, size);
++ }
++
++ exp_size = amdgpu_dm_get_lut3d_size(adev, MAX_COLOR_3DLUT_ENTRIES);
++ lut3d = __extract_blob_lut(dm_plane_state->lut3d, &size);
++
++ if (lut3d && size != exp_size) {
++ drm_dbg(&adev->ddev, "Invalid 3D LUT size. Should be %u but got %u.\n",
++ exp_size, size);
++ return -EINVAL;
++ }
++
++ return 0;
++}
++
+ /**
+ * amdgpu_dm_verify_lut_sizes - verifies if DRM luts match the hw supported sizes
+ * @crtc_state: the DRM CRTC state
+@@ -401,9 +889,12 @@ int amdgpu_dm_update_crtc_color_mgmt(struct dm_crtc_state *crtc)
+ const struct drm_color_lut *degamma_lut, *regamma_lut;
+ uint32_t degamma_size, regamma_size;
+ bool has_regamma, has_degamma;
++ enum dc_transfer_func_predefined tf = TRANSFER_FUNCTION_LINEAR;
+ bool is_legacy;
+ int r;
+
++ tf = amdgpu_tf_to_dc_tf(crtc->regamma_tf);
++
+ r = amdgpu_dm_verify_lut_sizes(&crtc->base);
+ if (r)
+ return r;
+@@ -440,26 +931,22 @@ int amdgpu_dm_update_crtc_color_mgmt(struct dm_crtc_state *crtc)
+ stream->out_transfer_func->type = TF_TYPE_DISTRIBUTED_POINTS;
+ stream->out_transfer_func->tf = TRANSFER_FUNCTION_SRGB;
+
++ /* Note: although we pass has_rom as parameter here, we never
++ * actually use ROM because the color module only takes the ROM
++ * path if transfer_func->type == PREDEFINED.
++ *
++ * See more in mod_color_calculate_regamma_params()
++ */
+ r = __set_legacy_tf(stream->out_transfer_func, regamma_lut,
+ regamma_size, has_rom);
+ if (r)
+ return r;
+- } else if (has_regamma) {
+- /* If atomic regamma, CRTC RGM goes into RGM LUT. */
+- stream->out_transfer_func->type = TF_TYPE_DISTRIBUTED_POINTS;
+- stream->out_transfer_func->tf = TRANSFER_FUNCTION_LINEAR;
+-
+- r = __set_output_tf(stream->out_transfer_func, regamma_lut,
+- regamma_size, has_rom);
++ } else {
++ regamma_size = has_regamma ? regamma_size : 0;
++ r = amdgpu_dm_set_atomic_regamma(stream, regamma_lut,
++ regamma_size, has_rom, tf);
+ if (r)
+ return r;
+- } else {
+- /*
+- * No CRTC RGM means we can just put the block into bypass
+- * since we don't have any plane level adjustments using it.
+- */
+- stream->out_transfer_func->type = TF_TYPE_BYPASS;
+- stream->out_transfer_func->tf = TRANSFER_FUNCTION_LINEAR;
+ }
+
+ /*
+@@ -495,20 +982,10 @@ int amdgpu_dm_update_crtc_color_mgmt(struct dm_crtc_state *crtc)
+ return 0;
+ }
+
+-/**
+- * amdgpu_dm_update_plane_color_mgmt: Maps DRM color management to DC plane.
+- * @crtc: amdgpu_dm crtc state
+- * @dc_plane_state: target DC surface
+- *
+- * Update the underlying dc_stream_state's input transfer function (ITF) in
+- * preparation for hardware commit. The transfer function used depends on
+- * the preparation done on the stream for color management.
+- *
+- * Returns:
+- * 0 on success. -ENOMEM if mem allocation fails.
+- */
+-int amdgpu_dm_update_plane_color_mgmt(struct dm_crtc_state *crtc,
+- struct dc_plane_state *dc_plane_state)
++static int
++map_crtc_degamma_to_dc_plane(struct dm_crtc_state *crtc,
++ struct dc_plane_state *dc_plane_state,
++ struct dc_color_caps *caps)
+ {
+ const struct drm_color_lut *degamma_lut;
+ enum dc_transfer_func_predefined tf = TRANSFER_FUNCTION_SRGB;
+@@ -531,8 +1008,7 @@ int amdgpu_dm_update_plane_color_mgmt(struct dm_crtc_state *crtc,
+ &degamma_size);
+ ASSERT(degamma_size == MAX_COLOR_LUT_ENTRIES);
+
+- dc_plane_state->in_transfer_func->type =
+- TF_TYPE_DISTRIBUTED_POINTS;
++ dc_plane_state->in_transfer_func->type = TF_TYPE_DISTRIBUTED_POINTS;
+
+ /*
+ * This case isn't fully correct, but also fairly
+@@ -564,11 +1040,11 @@ int amdgpu_dm_update_plane_color_mgmt(struct dm_crtc_state *crtc,
+ dc_plane_state->in_transfer_func->tf =
+ TRANSFER_FUNCTION_LINEAR;
+
+- r = __set_input_tf(dc_plane_state->in_transfer_func,
++ r = __set_input_tf(caps, dc_plane_state->in_transfer_func,
+ degamma_lut, degamma_size);
+ if (r)
+ return r;
+- } else if (crtc->cm_is_degamma_srgb) {
++ } else {
+ /*
+ * For legacy gamma support we need the regamma input
+ * in linear space. Assume that the input is sRGB.
+@@ -577,14 +1053,213 @@ int amdgpu_dm_update_plane_color_mgmt(struct dm_crtc_state *crtc,
+ dc_plane_state->in_transfer_func->tf = tf;
+
+ if (tf != TRANSFER_FUNCTION_SRGB &&
+- !mod_color_calculate_degamma_params(NULL,
+- dc_plane_state->in_transfer_func, NULL, false))
++ !mod_color_calculate_degamma_params(caps,
++ dc_plane_state->in_transfer_func,
++ NULL, false))
++ return -ENOMEM;
++ }
++
++ return 0;
++}
++
++static int
++__set_dm_plane_degamma(struct drm_plane_state *plane_state,
++ struct dc_plane_state *dc_plane_state,
++ struct dc_color_caps *color_caps)
++{
++ struct dm_plane_state *dm_plane_state = to_dm_plane_state(plane_state);
++ const struct drm_color_lut *degamma_lut;
++ enum amdgpu_transfer_function tf = AMDGPU_TRANSFER_FUNCTION_DEFAULT;
++ uint32_t degamma_size;
++ bool has_degamma_lut;
++ int ret;
++
++ degamma_lut = __extract_blob_lut(dm_plane_state->degamma_lut,
++ &degamma_size);
++
++ has_degamma_lut = degamma_lut &&
++ !__is_lut_linear(degamma_lut, degamma_size);
++
++ tf = dm_plane_state->degamma_tf;
++
++ /* If we don't have plane degamma LUT nor TF to set on DC, we have
++ * nothing to do here, return.
++ */
++ if (!has_degamma_lut && tf == AMDGPU_TRANSFER_FUNCTION_DEFAULT)
++ return -EINVAL;
++
++ dc_plane_state->in_transfer_func->tf = amdgpu_tf_to_dc_tf(tf);
++
++ if (has_degamma_lut) {
++ ASSERT(degamma_size == MAX_COLOR_LUT_ENTRIES);
++
++ dc_plane_state->in_transfer_func->type =
++ TF_TYPE_DISTRIBUTED_POINTS;
++
++ ret = __set_input_tf(color_caps, dc_plane_state->in_transfer_func,
++ degamma_lut, degamma_size);
++ if (ret)
++ return ret;
++ } else {
++ dc_plane_state->in_transfer_func->type =
++ TF_TYPE_PREDEFINED;
++
++ if (!mod_color_calculate_degamma_params(color_caps,
++ dc_plane_state->in_transfer_func, NULL, false))
+ return -ENOMEM;
+- } else {
+- /* ...Otherwise we can just bypass the DGM block. */
+- dc_plane_state->in_transfer_func->type = TF_TYPE_BYPASS;
+- dc_plane_state->in_transfer_func->tf = TRANSFER_FUNCTION_LINEAR;
++ }
++ return 0;
++}
++
++static int
++amdgpu_dm_plane_set_color_properties(struct drm_plane_state *plane_state,
++ struct dc_plane_state *dc_plane_state,
++ struct dc_color_caps *color_caps)
++{
++ struct dm_plane_state *dm_plane_state = to_dm_plane_state(plane_state);
++ enum amdgpu_transfer_function shaper_tf = AMDGPU_TRANSFER_FUNCTION_DEFAULT;
++ enum amdgpu_transfer_function blend_tf = AMDGPU_TRANSFER_FUNCTION_DEFAULT;
++ const struct drm_color_lut *shaper_lut, *lut3d, *blend_lut;
++ uint32_t shaper_size, lut3d_size, blend_size;
++ int ret;
++
++ /* We have nothing to do here, return */
++ if (!plane_state->color_mgmt_changed)
++ return 0;
++
++ dc_plane_state->hdr_mult = dc_fixpt_from_s3132(dm_plane_state->hdr_mult);
++
++ shaper_lut = __extract_blob_lut(dm_plane_state->shaper_lut, &shaper_size);
++ shaper_size = shaper_lut != NULL ? shaper_size : 0;
++ shaper_tf = dm_plane_state->shaper_tf;
++ lut3d = __extract_blob_lut(dm_plane_state->lut3d, &lut3d_size);
++ lut3d_size = lut3d != NULL ? lut3d_size : 0;
++
++ amdgpu_dm_atomic_lut3d(lut3d, lut3d_size, dc_plane_state->lut3d_func);
++ ret = amdgpu_dm_atomic_shaper_lut(shaper_lut, false,
++ amdgpu_tf_to_dc_tf(shaper_tf),
++ shaper_size,
++ dc_plane_state->in_shaper_func);
++ if (ret) {
++ drm_dbg_kms(plane_state->plane->dev,
++ "setting plane %d shaper LUT failed.\n",
++ plane_state->plane->index);
++
++ return ret;
++ }
++
++ blend_tf = dm_plane_state->blend_tf;
++ blend_lut = __extract_blob_lut(dm_plane_state->blend_lut, &blend_size);
++ blend_size = blend_lut != NULL ? blend_size : 0;
++
++ ret = amdgpu_dm_atomic_blend_lut(blend_lut, false,
++ amdgpu_tf_to_dc_tf(blend_tf),
++ blend_size, dc_plane_state->blend_tf);
++ if (ret) {
++ drm_dbg_kms(plane_state->plane->dev,
++ "setting plane %d gamma lut failed.\n",
++ plane_state->plane->index);
++
++ return ret;
+ }
+
+ return 0;
+ }
++
++/**
++ * amdgpu_dm_update_plane_color_mgmt: Maps DRM color management to DC plane.
++ * @crtc: amdgpu_dm crtc state
++ * @plane_state: DRM plane state
++ * @dc_plane_state: target DC surface
++ *
++ * Update the underlying dc_stream_state's input transfer function (ITF) in
++ * preparation for hardware commit. The transfer function used depends on
++ * the preparation done on the stream for color management.
++ *
++ * Returns:
++ * 0 on success. -ENOMEM if mem allocation fails.
++ */
++int amdgpu_dm_update_plane_color_mgmt(struct dm_crtc_state *crtc,
++ struct drm_plane_state *plane_state,
++ struct dc_plane_state *dc_plane_state)
++{
++ struct amdgpu_device *adev = drm_to_adev(crtc->base.state->dev);
++ struct dm_plane_state *dm_plane_state = to_dm_plane_state(plane_state);
++ struct drm_color_ctm2 *ctm = NULL;
++ struct dc_color_caps *color_caps = NULL;
++ bool has_crtc_cm_degamma;
++ int ret;
++
++ ret = amdgpu_dm_verify_lut3d_size(adev, plane_state);
++ if (ret) {
++ drm_dbg_driver(&adev->ddev, "amdgpu_dm_verify_lut3d_size() failed\n");
++ return ret;
++ }
++
++ if (dc_plane_state->ctx && dc_plane_state->ctx->dc)
++ color_caps = &dc_plane_state->ctx->dc->caps.color;
++
++ /* Initially, we can just bypass the DGM block. */
++ dc_plane_state->in_transfer_func->type = TF_TYPE_BYPASS;
++ dc_plane_state->in_transfer_func->tf = TRANSFER_FUNCTION_LINEAR;
++
++ /* After, we start to update values according to color props */
++ has_crtc_cm_degamma = (crtc->cm_has_degamma || crtc->cm_is_degamma_srgb);
++
++ ret = __set_dm_plane_degamma(plane_state, dc_plane_state, color_caps);
++ if (ret == -ENOMEM)
++ return ret;
++
++ /* We only have one degamma block available (pre-blending) for the
++ * whole color correction pipeline, so that we can't actually perform
++ * plane and CRTC degamma at the same time. Explicitly reject atomic
++ * updates when userspace sets both plane and CRTC degamma properties.
++ */
++ if (has_crtc_cm_degamma && ret != -EINVAL){
++ drm_dbg_kms(crtc->base.crtc->dev,
++ "doesn't support plane and CRTC degamma at the same time\n");
++ return -EINVAL;
++ }
++
++ /* If we are here, it means we don't have plane degamma settings, check
++ * if we have CRTC degamma waiting for mapping to pre-blending degamma
++ * block
++ */
++ if (has_crtc_cm_degamma) {
++ /* AMD HW doesn't have post-blending degamma caps. When DRM
++ * CRTC atomic degamma is set, we maps it to DPP degamma block
++ * (pre-blending) or, on legacy gamma, we use DPP degamma to
++ * linearize (implicit degamma) from sRGB/BT709 according to
++ * the input space.
++ */
++ ret = map_crtc_degamma_to_dc_plane(crtc, dc_plane_state, color_caps);
++ if (ret)
++ return ret;
++ }
++
++ /* Setup CRTC CTM. */
++ if (dm_plane_state->ctm) {
++ ctm = (struct drm_color_ctm2 *)dm_plane_state->ctm->data;
++
++ /*
++ * So far, if we have both plane and CRTC CTM, plane CTM takes
++ * the priority and we discard data for CRTC CTM, as
++ * implemented in dcn10_program_gamut_remap(). However, we
++ * have MPC gamut_remap_matrix from DCN3 family, therefore we
++ * can remap MPC programing of the matrix to MPC block and
++ * provide support for both DPP and MPC matrix at the same
++ * time.
++ */
++ __drm_ctm2_to_dc_matrix(ctm, dc_plane_state->gamut_remap_matrix.matrix);
++
++ dc_plane_state->gamut_remap_matrix.enable_remap = true;
++ dc_plane_state->input_csc_color_matrix.enable_adjustment = false;
++ } else {
++ /* Bypass CTM. */
++ dc_plane_state->gamut_remap_matrix.enable_remap = false;
++ dc_plane_state->input_csc_color_matrix.enable_adjustment = false;
++ }
++
++ return amdgpu_dm_plane_set_color_properties(plane_state,
++ dc_plane_state, color_caps);
++}
+diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c
+index 30d4c6fd95f531..e7b38cce010cc8 100644
+--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c
++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c
+@@ -253,6 +253,7 @@ static struct drm_crtc_state *dm_crtc_duplicate_state(struct drm_crtc *crtc)
+ state->freesync_config = cur->freesync_config;
+ state->cm_has_degamma = cur->cm_has_degamma;
+ state->cm_is_degamma_srgb = cur->cm_is_degamma_srgb;
++ state->regamma_tf = cur->regamma_tf;
+ state->crc_skip_count = cur->crc_skip_count;
+ state->mpo_requested = cur->mpo_requested;
+ /* TODO Duplicate dc_stream after objects are stream object is flattened */
+@@ -289,6 +290,70 @@ static int amdgpu_dm_crtc_late_register(struct drm_crtc *crtc)
+ }
+ #endif
+
++#ifdef AMD_PRIVATE_COLOR
++/**
++ * drm_crtc_additional_color_mgmt - enable additional color properties
++ * @crtc: DRM CRTC
++ *
++ * This function lets the driver enable post-blending CRTC regamma transfer
++ * function property in addition to DRM CRTC gamma LUT. Default value means
++ * linear transfer function, which is the default CRTC gamma LUT behaviour
++ * without this property.
++ */
++static void
++dm_crtc_additional_color_mgmt(struct drm_crtc *crtc)
++{
++ struct amdgpu_device *adev = drm_to_adev(crtc->dev);
++
++ if(adev->dm.dc->caps.color.mpc.ogam_ram)
++ drm_object_attach_property(&crtc->base,
++ adev->mode_info.regamma_tf_property,
++ AMDGPU_TRANSFER_FUNCTION_DEFAULT);
++}
++
++static int
++amdgpu_dm_atomic_crtc_set_property(struct drm_crtc *crtc,
++ struct drm_crtc_state *state,
++ struct drm_property *property,
++ uint64_t val)
++{
++ struct amdgpu_device *adev = drm_to_adev(crtc->dev);
++ struct dm_crtc_state *acrtc_state = to_dm_crtc_state(state);
++
++ if (property == adev->mode_info.regamma_tf_property) {
++ if (acrtc_state->regamma_tf != val) {
++ acrtc_state->regamma_tf = val;
++ acrtc_state->base.color_mgmt_changed |= 1;
++ }
++ } else {
++ drm_dbg_atomic(crtc->dev,
++ "[CRTC:%d:%s] unknown property [PROP:%d:%s]]\n",
++ crtc->base.id, crtc->name,
++ property->base.id, property->name);
++ return -EINVAL;
++ }
++
++ return 0;
++}
++
++static int
++amdgpu_dm_atomic_crtc_get_property(struct drm_crtc *crtc,
++ const struct drm_crtc_state *state,
++ struct drm_property *property,
++ uint64_t *val)
++{
++ struct amdgpu_device *adev = drm_to_adev(crtc->dev);
++ struct dm_crtc_state *acrtc_state = to_dm_crtc_state(state);
++
++ if (property == adev->mode_info.regamma_tf_property)
++ *val = acrtc_state->regamma_tf;
++ else
++ return -EINVAL;
++
++ return 0;
++}
++#endif
++
+ /* Implemented only the options currently available for the driver */
+ static const struct drm_crtc_funcs amdgpu_dm_crtc_funcs = {
+ .reset = dm_crtc_reset_state,
+@@ -307,6 +372,10 @@ static const struct drm_crtc_funcs amdgpu_dm_crtc_funcs = {
+ #if defined(CONFIG_DEBUG_FS)
+ .late_register = amdgpu_dm_crtc_late_register,
+ #endif
++#ifdef AMD_PRIVATE_COLOR
++ .atomic_set_property = amdgpu_dm_atomic_crtc_set_property,
++ .atomic_get_property = amdgpu_dm_atomic_crtc_get_property,
++#endif
+ };
+
+ static void dm_crtc_helper_disable(struct drm_crtc *crtc)
+@@ -482,6 +551,9 @@ int amdgpu_dm_crtc_init(struct amdgpu_display_manager *dm,
+
+ drm_mode_crtc_set_gamma_size(&acrtc->base, MAX_COLOR_LEGACY_LUT_ENTRIES);
+
++#ifdef AMD_PRIVATE_COLOR
++ dm_crtc_additional_color_mgmt(&acrtc->base);
++#endif
+ return 0;
+
+ fail:
+diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c
+index 32266897374792..60e5ffb1863d74 100644
+--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c
++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c
+@@ -1317,8 +1317,14 @@ static void dm_drm_plane_reset(struct drm_plane *plane)
+ amdgpu_state = kzalloc(sizeof(*amdgpu_state), GFP_KERNEL);
+ WARN_ON(amdgpu_state == NULL);
+
+- if (amdgpu_state)
+- __drm_atomic_helper_plane_reset(plane, &amdgpu_state->base);
++ if (!amdgpu_state)
++ return;
++
++ __drm_atomic_helper_plane_reset(plane, &amdgpu_state->base);
++ amdgpu_state->degamma_tf = AMDGPU_TRANSFER_FUNCTION_DEFAULT;
++ amdgpu_state->hdr_mult = AMDGPU_HDR_MULT_DEFAULT;
++ amdgpu_state->shaper_tf = AMDGPU_TRANSFER_FUNCTION_DEFAULT;
++ amdgpu_state->blend_tf = AMDGPU_TRANSFER_FUNCTION_DEFAULT;
+ }
+
+ static struct drm_plane_state *
+@@ -1338,6 +1344,22 @@ dm_drm_plane_duplicate_state(struct drm_plane *plane)
+ dc_plane_state_retain(dm_plane_state->dc_state);
+ }
+
++ if (dm_plane_state->degamma_lut)
++ drm_property_blob_get(dm_plane_state->degamma_lut);
++ if (dm_plane_state->ctm)
++ drm_property_blob_get(dm_plane_state->ctm);
++ if (dm_plane_state->shaper_lut)
++ drm_property_blob_get(dm_plane_state->shaper_lut);
++ if (dm_plane_state->lut3d)
++ drm_property_blob_get(dm_plane_state->lut3d);
++ if (dm_plane_state->blend_lut)
++ drm_property_blob_get(dm_plane_state->blend_lut);
++
++ dm_plane_state->degamma_tf = old_dm_plane_state->degamma_tf;
++ dm_plane_state->hdr_mult = old_dm_plane_state->hdr_mult;
++ dm_plane_state->shaper_tf = old_dm_plane_state->shaper_tf;
++ dm_plane_state->blend_tf = old_dm_plane_state->blend_tf;
++
+ return &dm_plane_state->base;
+ }
+
+@@ -1405,12 +1427,203 @@ static void dm_drm_plane_destroy_state(struct drm_plane *plane,
+ {
+ struct dm_plane_state *dm_plane_state = to_dm_plane_state(state);
+
++ if (dm_plane_state->degamma_lut)
++ drm_property_blob_put(dm_plane_state->degamma_lut);
++ if (dm_plane_state->ctm)
++ drm_property_blob_put(dm_plane_state->ctm);
++ if (dm_plane_state->lut3d)
++ drm_property_blob_put(dm_plane_state->lut3d);
++ if (dm_plane_state->shaper_lut)
++ drm_property_blob_put(dm_plane_state->shaper_lut);
++ if (dm_plane_state->blend_lut)
++ drm_property_blob_put(dm_plane_state->blend_lut);
++
+ if (dm_plane_state->dc_state)
+ dc_plane_state_release(dm_plane_state->dc_state);
+
+ drm_atomic_helper_plane_destroy_state(plane, state);
+ }
+
++#ifdef AMD_PRIVATE_COLOR
++static void
++dm_atomic_plane_attach_color_mgmt_properties(struct amdgpu_display_manager *dm,
++ struct drm_plane *plane)
++{
++ struct amdgpu_mode_info mode_info = dm->adev->mode_info;
++ struct dpp_color_caps dpp_color_caps = dm->dc->caps.color.dpp;
++
++ /* Check HW color pipeline capabilities for DPP (pre-blending) before expose*/
++ if (dpp_color_caps.dgam_ram || dpp_color_caps.gamma_corr) {
++ drm_object_attach_property(&plane->base,
++ mode_info.plane_degamma_lut_property, 0);
++ drm_object_attach_property(&plane->base,
++ mode_info.plane_degamma_lut_size_property,
++ MAX_COLOR_LUT_ENTRIES);
++ drm_object_attach_property(&plane->base,
++ dm->adev->mode_info.plane_degamma_tf_property,
++ AMDGPU_TRANSFER_FUNCTION_DEFAULT);
++ }
++ /* HDR MULT is always available */
++ drm_object_attach_property(&plane->base,
++ dm->adev->mode_info.plane_hdr_mult_property,
++ AMDGPU_HDR_MULT_DEFAULT);
++
++ /* Only enable plane CTM if both DPP and MPC gamut remap is available. */
++ if (dm->dc->caps.color.mpc.gamut_remap)
++ drm_object_attach_property(&plane->base,
++ dm->adev->mode_info.plane_ctm_property, 0);
++
++ if (dpp_color_caps.hw_3d_lut) {
++ drm_object_attach_property(&plane->base,
++ mode_info.plane_shaper_lut_property, 0);
++ drm_object_attach_property(&plane->base,
++ mode_info.plane_shaper_lut_size_property,
++ MAX_COLOR_LUT_ENTRIES);
++ drm_object_attach_property(&plane->base,
++ mode_info.plane_shaper_tf_property,
++ AMDGPU_TRANSFER_FUNCTION_DEFAULT);
++ drm_object_attach_property(&plane->base,
++ mode_info.plane_lut3d_property, 0);
++ drm_object_attach_property(&plane->base,
++ mode_info.plane_lut3d_size_property,
++ MAX_COLOR_3DLUT_ENTRIES);
++ }
++
++ if (dpp_color_caps.ogam_ram) {
++ drm_object_attach_property(&plane->base,
++ mode_info.plane_blend_lut_property, 0);
++ drm_object_attach_property(&plane->base,
++ mode_info.plane_blend_lut_size_property,
++ MAX_COLOR_LUT_ENTRIES);
++ drm_object_attach_property(&plane->base,
++ mode_info.plane_blend_tf_property,
++ AMDGPU_TRANSFER_FUNCTION_DEFAULT);
++ }
++}
++
++static int
++dm_atomic_plane_set_property(struct drm_plane *plane,
++ struct drm_plane_state *state,
++ struct drm_property *property,
++ uint64_t val)
++{
++ struct dm_plane_state *dm_plane_state = to_dm_plane_state(state);
++ struct amdgpu_device *adev = drm_to_adev(plane->dev);
++ bool replaced = false;
++ int ret;
++
++ if (property == adev->mode_info.plane_degamma_lut_property) {
++ ret = drm_property_replace_blob_from_id(plane->dev,
++ &dm_plane_state->degamma_lut,
++ val,
++ -1, sizeof(struct drm_color_lut),
++ &replaced);
++ dm_plane_state->base.color_mgmt_changed |= replaced;
++ return ret;
++ } else if (property == adev->mode_info.plane_degamma_tf_property) {
++ if (dm_plane_state->degamma_tf != val) {
++ dm_plane_state->degamma_tf = val;
++ dm_plane_state->base.color_mgmt_changed = 1;
++ }
++ } else if (property == adev->mode_info.plane_hdr_mult_property) {
++ if (dm_plane_state->hdr_mult != val) {
++ dm_plane_state->hdr_mult = val;
++ dm_plane_state->base.color_mgmt_changed = 1;
++ }
++ } else if (property == adev->mode_info.plane_ctm_property) {
++ ret = drm_property_replace_blob_from_id(plane->dev,
++ &dm_plane_state->ctm,
++ val,
++ sizeof(struct drm_color_ctm2), -1,
++ &replaced);
++ dm_plane_state->base.color_mgmt_changed |= replaced;
++ return ret;
++ } else if (property == adev->mode_info.plane_shaper_lut_property) {
++ ret = drm_property_replace_blob_from_id(plane->dev,
++ &dm_plane_state->shaper_lut,
++ val, -1,
++ sizeof(struct drm_color_lut),
++ &replaced);
++ dm_plane_state->base.color_mgmt_changed |= replaced;
++ return ret;
++ } else if (property == adev->mode_info.plane_shaper_tf_property) {
++ if (dm_plane_state->shaper_tf != val) {
++ dm_plane_state->shaper_tf = val;
++ dm_plane_state->base.color_mgmt_changed = 1;
++ }
++ } else if (property == adev->mode_info.plane_lut3d_property) {
++ ret = drm_property_replace_blob_from_id(plane->dev,
++ &dm_plane_state->lut3d,
++ val, -1,
++ sizeof(struct drm_color_lut),
++ &replaced);
++ dm_plane_state->base.color_mgmt_changed |= replaced;
++ return ret;
++ } else if (property == adev->mode_info.plane_blend_lut_property) {
++ ret = drm_property_replace_blob_from_id(plane->dev,
++ &dm_plane_state->blend_lut,
++ val, -1,
++ sizeof(struct drm_color_lut),
++ &replaced);
++ dm_plane_state->base.color_mgmt_changed |= replaced;
++ return ret;
++ } else if (property == adev->mode_info.plane_blend_tf_property) {
++ if (dm_plane_state->blend_tf != val) {
++ dm_plane_state->blend_tf = val;
++ dm_plane_state->base.color_mgmt_changed = 1;
++ }
++ } else {
++ drm_dbg_atomic(plane->dev,
++ "[PLANE:%d:%s] unknown property [PROP:%d:%s]]\n",
++ plane->base.id, plane->name,
++ property->base.id, property->name);
++ return -EINVAL;
++ }
++
++ return 0;
++}
++
++static int
++dm_atomic_plane_get_property(struct drm_plane *plane,
++ const struct drm_plane_state *state,
++ struct drm_property *property,
++ uint64_t *val)
++{
++ struct dm_plane_state *dm_plane_state = to_dm_plane_state(state);
++ struct amdgpu_device *adev = drm_to_adev(plane->dev);
++
++ if (property == adev->mode_info.plane_degamma_lut_property) {
++ *val = (dm_plane_state->degamma_lut) ?
++ dm_plane_state->degamma_lut->base.id : 0;
++ } else if (property == adev->mode_info.plane_degamma_tf_property) {
++ *val = dm_plane_state->degamma_tf;
++ } else if (property == adev->mode_info.plane_hdr_mult_property) {
++ *val = dm_plane_state->hdr_mult;
++ } else if (property == adev->mode_info.plane_ctm_property) {
++ *val = (dm_plane_state->ctm) ?
++ dm_plane_state->ctm->base.id : 0;
++ } else if (property == adev->mode_info.plane_shaper_lut_property) {
++ *val = (dm_plane_state->shaper_lut) ?
++ dm_plane_state->shaper_lut->base.id : 0;
++ } else if (property == adev->mode_info.plane_shaper_tf_property) {
++ *val = dm_plane_state->shaper_tf;
++ } else if (property == adev->mode_info.plane_lut3d_property) {
++ *val = (dm_plane_state->lut3d) ?
++ dm_plane_state->lut3d->base.id : 0;
++ } else if (property == adev->mode_info.plane_blend_lut_property) {
++ *val = (dm_plane_state->blend_lut) ?
++ dm_plane_state->blend_lut->base.id : 0;
++ } else if (property == adev->mode_info.plane_blend_tf_property) {
++ *val = dm_plane_state->blend_tf;
++
++ } else {
++ return -EINVAL;
++ }
++
++ return 0;
++}
++#endif
++
+ static const struct drm_plane_funcs dm_plane_funcs = {
+ .update_plane = drm_atomic_helper_update_plane,
+ .disable_plane = drm_atomic_helper_disable_plane,
+@@ -1419,6 +1632,10 @@ static const struct drm_plane_funcs dm_plane_funcs = {
+ .atomic_duplicate_state = dm_drm_plane_duplicate_state,
+ .atomic_destroy_state = dm_drm_plane_destroy_state,
+ .format_mod_supported = dm_plane_format_mod_supported,
++#ifdef AMD_PRIVATE_COLOR
++ .atomic_set_property = dm_atomic_plane_set_property,
++ .atomic_get_property = dm_atomic_plane_get_property,
++#endif
+ };
+
+ int amdgpu_dm_plane_init(struct amdgpu_display_manager *dm,
+@@ -1489,6 +1706,9 @@ int amdgpu_dm_plane_init(struct amdgpu_display_manager *dm,
+
+ drm_plane_helper_add(plane, &dm_plane_helper_funcs);
+
++#ifdef AMD_PRIVATE_COLOR
++ dm_atomic_plane_attach_color_mgmt_properties(dm, plane);
++#endif
+ /* Create (reset) the plane state */
+ if (plane->funcs->reset)
+ plane->funcs->reset(plane);
+diff --git a/drivers/gpu/drm/amd/display/dc/dcn10/dcn10_cm_common.c b/drivers/gpu/drm/amd/display/dc/dcn10/dcn10_cm_common.c
+index 3538973bd0c6cb..04b2e04b68f33b 100644
+--- a/drivers/gpu/drm/amd/display/dc/dcn10/dcn10_cm_common.c
++++ b/drivers/gpu/drm/amd/display/dc/dcn10/dcn10_cm_common.c
+@@ -349,20 +349,37 @@ bool cm_helper_translate_curve_to_hw_format(struct dc_context *ctx,
+ * segment is from 2^-10 to 2^1
+ * There are less than 256 points, for optimization
+ */
+- seg_distr[0] = 3;
+- seg_distr[1] = 4;
+- seg_distr[2] = 4;
+- seg_distr[3] = 4;
+- seg_distr[4] = 4;
+- seg_distr[5] = 4;
+- seg_distr[6] = 4;
+- seg_distr[7] = 4;
+- seg_distr[8] = 4;
+- seg_distr[9] = 4;
+- seg_distr[10] = 1;
+-
+- region_start = -10;
+- region_end = 1;
++ if (output_tf->tf == TRANSFER_FUNCTION_LINEAR) {
++ seg_distr[0] = 0; /* 2 */
++ seg_distr[1] = 1; /* 4 */
++ seg_distr[2] = 2; /* 4 */
++ seg_distr[3] = 3; /* 8 */
++ seg_distr[4] = 4; /* 16 */
++ seg_distr[5] = 5; /* 32 */
++ seg_distr[6] = 6; /* 64 */
++ seg_distr[7] = 7; /* 128 */
++
++ region_start = -8;
++ region_end = 1;
++ } else {
++ seg_distr[0] = 3; /* 8 */
++ seg_distr[1] = 4; /* 16 */
++ seg_distr[2] = 4;
++ seg_distr[3] = 4;
++ seg_distr[4] = 4;
++ seg_distr[5] = 4;
++ seg_distr[6] = 4;
++ seg_distr[7] = 4;
++ seg_distr[8] = 4;
++ seg_distr[9] = 4;
++ seg_distr[10] = 1; /* 2 */
++ /* total = 8*16 + 8 + 64 + 2 = */
++
++ region_start = -10;
++ region_end = 1;
++ }
++
++
+ }
+
+ for (i = region_end - region_start; i < MAX_REGIONS_NUMBER ; i++)
+@@ -375,16 +392,56 @@ bool cm_helper_translate_curve_to_hw_format(struct dc_context *ctx,
+
+ j = 0;
+ for (k = 0; k < (region_end - region_start); k++) {
+- increment = NUMBER_SW_SEGMENTS / (1 << seg_distr[k]);
++ /*
++ * We're using an ugly-ish hack here. Our HW allows for
++ * 256 segments per region but SW_SEGMENTS is 16.
++ * SW_SEGMENTS has some undocumented relationship to
++ * the number of points in the tf_pts struct, which
++ * is 512, unlike what's suggested TRANSFER_FUNC_POINTS.
++ *
++ * In order to work past this dilemma we'll scale our
++ * increment by (1 << 4) and then do the inverse (1 >> 4)
++ * when accessing the elements in tf_pts.
++ *
++ * TODO: find a better way using SW_SEGMENTS and
++ * TRANSFER_FUNC_POINTS definitions
++ */
++ increment = (NUMBER_SW_SEGMENTS << 4) / (1 << seg_distr[k]);
+ start_index = (region_start + k + MAX_LOW_POINT) *
+ NUMBER_SW_SEGMENTS;
+- for (i = start_index; i < start_index + NUMBER_SW_SEGMENTS;
++ for (i = (start_index << 4); i < (start_index << 4) + (NUMBER_SW_SEGMENTS << 4);
+ i += increment) {
++ struct fixed31_32 in_plus_one, in;
++ struct fixed31_32 value, red_value, green_value, blue_value;
++ uint32_t t = i & 0xf;
++
+ if (j == hw_points - 1)
+ break;
+- rgb_resulted[j].red = output_tf->tf_pts.red[i];
+- rgb_resulted[j].green = output_tf->tf_pts.green[i];
+- rgb_resulted[j].blue = output_tf->tf_pts.blue[i];
++
++ in_plus_one = output_tf->tf_pts.red[(i >> 4) + 1];
++ in = output_tf->tf_pts.red[i >> 4];
++ value = dc_fixpt_sub(in_plus_one, in);
++ value = dc_fixpt_shr(dc_fixpt_mul_int(value, t), 4);
++ value = dc_fixpt_add(in, value);
++ red_value = value;
++
++ in_plus_one = output_tf->tf_pts.green[(i >> 4) + 1];
++ in = output_tf->tf_pts.green[i >> 4];
++ value = dc_fixpt_sub(in_plus_one, in);
++ value = dc_fixpt_shr(dc_fixpt_mul_int(value, t), 4);
++ value = dc_fixpt_add(in, value);
++ green_value = value;
++
++ in_plus_one = output_tf->tf_pts.blue[(i >> 4) + 1];
++ in = output_tf->tf_pts.blue[i >> 4];
++ value = dc_fixpt_sub(in_plus_one, in);
++ value = dc_fixpt_shr(dc_fixpt_mul_int(value, t), 4);
++ value = dc_fixpt_add(in, value);
++ blue_value = value;
++
++ rgb_resulted[j].red = red_value;
++ rgb_resulted[j].green = green_value;
++ rgb_resulted[j].blue = blue_value;
+ j++;
+ }
+ }
+diff --git a/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_hwseq.c b/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_hwseq.c
+index bf8864bc8a99ee..72558eb877dc65 100644
+--- a/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_hwseq.c
++++ b/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_hwseq.c
+@@ -186,6 +186,43 @@ bool dcn30_set_input_transfer_func(struct dc *dc,
+ return result;
+ }
+
++void dcn30_program_gamut_remap(struct pipe_ctx *pipe_ctx)
++{
++ int i = 0;
++ struct dpp_grph_csc_adjustment dpp_adjust;
++ struct mpc_grph_gamut_adjustment mpc_adjust;
++ int mpcc_id = pipe_ctx->plane_res.hubp->inst;
++ struct mpc *mpc = pipe_ctx->stream_res.opp->ctx->dc->res_pool->mpc;
++
++ memset(&dpp_adjust, 0, sizeof(dpp_adjust));
++ dpp_adjust.gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_BYPASS;
++
++ if (pipe_ctx->plane_state &&
++ pipe_ctx->plane_state->gamut_remap_matrix.enable_remap == true) {
++ dpp_adjust.gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_SW;
++ for (i = 0; i < CSC_TEMPERATURE_MATRIX_SIZE; i++)
++ dpp_adjust.temperature_matrix[i] =
++ pipe_ctx->plane_state->gamut_remap_matrix.matrix[i];
++ }
++
++ pipe_ctx->plane_res.dpp->funcs->dpp_set_gamut_remap(pipe_ctx->plane_res.dpp,
++ &dpp_adjust);
++
++ memset(&mpc_adjust, 0, sizeof(mpc_adjust));
++ mpc_adjust.gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_BYPASS;
++
++ if (pipe_ctx->top_pipe == NULL) {
++ if (pipe_ctx->stream->gamut_remap_matrix.enable_remap == true) {
++ mpc_adjust.gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_SW;
++ for (i = 0; i < CSC_TEMPERATURE_MATRIX_SIZE; i++)
++ mpc_adjust.temperature_matrix[i] =
++ pipe_ctx->stream->gamut_remap_matrix.matrix[i];
++ }
++ }
++
++ mpc->funcs->set_gamut_remap(mpc, mpcc_id, &mpc_adjust);
++}
++
+ bool dcn30_set_output_transfer_func(struct dc *dc,
+ struct pipe_ctx *pipe_ctx,
+ const struct dc_stream_state *stream)
+diff --git a/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_hwseq.h b/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_hwseq.h
+index a24a8e33a3d289..cb34ca932a5ff8 100644
+--- a/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_hwseq.h
++++ b/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_hwseq.h
+@@ -58,6 +58,9 @@ bool dcn30_set_blend_lut(struct pipe_ctx *pipe_ctx,
+ bool dcn30_set_input_transfer_func(struct dc *dc,
+ struct pipe_ctx *pipe_ctx,
+ const struct dc_plane_state *plane_state);
++
++void dcn30_program_gamut_remap(struct pipe_ctx *pipe_ctx);
++
+ bool dcn30_set_output_transfer_func(struct dc *dc,
+ struct pipe_ctx *pipe_ctx,
+ const struct dc_stream_state *stream);
+diff --git a/drivers/gpu/drm/amd/display/dc/dcn301/dcn301_init.c b/drivers/gpu/drm/amd/display/dc/dcn301/dcn301_init.c
+index 257df8660b4caf..81fd50ee97c307 100644
+--- a/drivers/gpu/drm/amd/display/dc/dcn301/dcn301_init.c
++++ b/drivers/gpu/drm/amd/display/dc/dcn301/dcn301_init.c
+@@ -33,7 +33,7 @@
+ #include "dcn301_init.h"
+
+ static const struct hw_sequencer_funcs dcn301_funcs = {
+- .program_gamut_remap = dcn10_program_gamut_remap,
++ .program_gamut_remap = dcn30_program_gamut_remap,
+ .init_hw = dcn10_init_hw,
+ .power_down_on_boot = dcn10_power_down_on_boot,
+ .apply_ctx_to_hw = dce110_apply_ctx_to_hw,
+diff --git a/drivers/gpu/drm/amd/display/include/fixed31_32.h b/drivers/gpu/drm/amd/display/include/fixed31_32.h
+index d4cf7ead1d877e..84da1dd34efd18 100644
+--- a/drivers/gpu/drm/amd/display/include/fixed31_32.h
++++ b/drivers/gpu/drm/amd/display/include/fixed31_32.h
+@@ -69,6 +69,18 @@ static const struct fixed31_32 dc_fixpt_epsilon = { 1LL };
+ static const struct fixed31_32 dc_fixpt_half = { 0x80000000LL };
+ static const struct fixed31_32 dc_fixpt_one = { 0x100000000LL };
+
++static inline struct fixed31_32 dc_fixpt_from_s3132(__u64 x)
++{
++ struct fixed31_32 val;
++
++ /* If negative, convert to 2's complement. */
++ if (x & (1ULL << 63))
++ x = -(x & ~(1ULL << 63));
++
++ val.value = x;
++ return val;
++}
++
+ /*
+ * @brief
+ * Initialization routines
+diff --git a/drivers/gpu/drm/arm/malidp_crtc.c b/drivers/gpu/drm/arm/malidp_crtc.c
+index dc01c43f61930b..d72c22dcf6855a 100644
+--- a/drivers/gpu/drm/arm/malidp_crtc.c
++++ b/drivers/gpu/drm/arm/malidp_crtc.c
+@@ -221,7 +221,7 @@ static int malidp_crtc_atomic_check_ctm(struct drm_crtc *crtc,
+
+ /*
+ * The size of the ctm is checked in
+- * drm_atomic_replace_property_blob_from_id.
++ * drm_property_replace_blob_from_id.
+ */
+ ctm = (struct drm_color_ctm *)state->ctm->data;
+ for (i = 0; i < ARRAY_SIZE(ctm->matrix); ++i) {
+diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
+index c277b198fa3fa2..c3df45f901456b 100644
+--- a/drivers/gpu/drm/drm_atomic.c
++++ b/drivers/gpu/drm/drm_atomic.c
+@@ -733,6 +733,7 @@ static void drm_atomic_plane_print_state(struct drm_printer *p,
+ drm_get_color_encoding_name(state->color_encoding));
+ drm_printf(p, "\tcolor-range=%s\n",
+ drm_get_color_range_name(state->color_range));
++ drm_printf(p, "\tcolor_mgmt_changed=%d\n", state->color_mgmt_changed);
+
+ if (plane->funcs->atomic_print_state)
+ plane->funcs->atomic_print_state(p, state);
+diff --git a/drivers/gpu/drm/drm_atomic_state_helper.c b/drivers/gpu/drm/drm_atomic_state_helper.c
+index 784e63d70a421e..25bb0859fda74d 100644
+--- a/drivers/gpu/drm/drm_atomic_state_helper.c
++++ b/drivers/gpu/drm/drm_atomic_state_helper.c
+@@ -338,6 +338,7 @@ void __drm_atomic_helper_plane_duplicate_state(struct drm_plane *plane,
+ state->fence = NULL;
+ state->commit = NULL;
+ state->fb_damage_clips = NULL;
++ state->color_mgmt_changed = false;
+ }
+ EXPORT_SYMBOL(__drm_atomic_helper_plane_duplicate_state);
+
+diff --git a/drivers/gpu/drm/drm_atomic_uapi.c b/drivers/gpu/drm/drm_atomic_uapi.c
+index d867e7f9f2cd58..a6a9ee5086ddb1 100644
+--- a/drivers/gpu/drm/drm_atomic_uapi.c
++++ b/drivers/gpu/drm/drm_atomic_uapi.c
+@@ -362,39 +362,6 @@ static s32 __user *get_out_fence_for_connector(struct drm_atomic_state *state,
+ return fence_ptr;
+ }
+
+-static int
+-drm_atomic_replace_property_blob_from_id(struct drm_device *dev,
+- struct drm_property_blob **blob,
+- uint64_t blob_id,
+- ssize_t expected_size,
+- ssize_t expected_elem_size,
+- bool *replaced)
+-{
+- struct drm_property_blob *new_blob = NULL;
+-
+- if (blob_id != 0) {
+- new_blob = drm_property_lookup_blob(dev, blob_id);
+- if (new_blob == NULL)
+- return -EINVAL;
+-
+- if (expected_size > 0 &&
+- new_blob->length != expected_size) {
+- drm_property_blob_put(new_blob);
+- return -EINVAL;
+- }
+- if (expected_elem_size > 0 &&
+- new_blob->length % expected_elem_size != 0) {
+- drm_property_blob_put(new_blob);
+- return -EINVAL;
+- }
+- }
+-
+- *replaced |= drm_property_replace_blob(blob, new_blob);
+- drm_property_blob_put(new_blob);
+-
+- return 0;
+-}
+-
+ static int drm_atomic_crtc_set_property(struct drm_crtc *crtc,
+ struct drm_crtc_state *state, struct drm_property *property,
+ uint64_t val)
+@@ -415,7 +382,7 @@ static int drm_atomic_crtc_set_property(struct drm_crtc *crtc,
+ } else if (property == config->prop_vrr_enabled) {
+ state->vrr_enabled = val;
+ } else if (property == config->degamma_lut_property) {
+- ret = drm_atomic_replace_property_blob_from_id(dev,
++ ret = drm_property_replace_blob_from_id(dev,
+ &state->degamma_lut,
+ val,
+ -1, sizeof(struct drm_color_lut),
+@@ -423,7 +390,7 @@ static int drm_atomic_crtc_set_property(struct drm_crtc *crtc,
+ state->color_mgmt_changed |= replaced;
+ return ret;
+ } else if (property == config->ctm_property) {
+- ret = drm_atomic_replace_property_blob_from_id(dev,
++ ret = drm_property_replace_blob_from_id(dev,
+ &state->ctm,
+ val,
+ sizeof(struct drm_color_ctm), -1,
+@@ -431,7 +398,7 @@ static int drm_atomic_crtc_set_property(struct drm_crtc *crtc,
+ state->color_mgmt_changed |= replaced;
+ return ret;
+ } else if (property == config->gamma_lut_property) {
+- ret = drm_atomic_replace_property_blob_from_id(dev,
++ ret = drm_property_replace_blob_from_id(dev,
+ &state->gamma_lut,
+ val,
+ -1, sizeof(struct drm_color_lut),
+@@ -563,7 +530,7 @@ static int drm_atomic_plane_set_property(struct drm_plane *plane,
+ } else if (property == plane->color_range_property) {
+ state->color_range = val;
+ } else if (property == config->prop_fb_damage_clips) {
+- ret = drm_atomic_replace_property_blob_from_id(dev,
++ ret = drm_property_replace_blob_from_id(dev,
+ &state->fb_damage_clips,
+ val,
+ -1,
+@@ -729,7 +696,7 @@ static int drm_atomic_connector_set_property(struct drm_connector *connector,
+ if (state->link_status != DRM_LINK_STATUS_GOOD)
+ state->link_status = val;
+ } else if (property == config->hdr_output_metadata_property) {
+- ret = drm_atomic_replace_property_blob_from_id(dev,
++ ret = drm_property_replace_blob_from_id(dev,
+ &state->hdr_output_metadata,
+ val,
+ sizeof(struct hdr_output_metadata), -1,
+diff --git a/drivers/gpu/drm/drm_property.c b/drivers/gpu/drm/drm_property.c
+index dfec479830e496..f72ef6493340a7 100644
+--- a/drivers/gpu/drm/drm_property.c
++++ b/drivers/gpu/drm/drm_property.c
+@@ -751,6 +751,55 @@ bool drm_property_replace_blob(struct drm_property_blob **blob,
+ }
+ EXPORT_SYMBOL(drm_property_replace_blob);
+
++/**
++ * drm_property_replace_blob_from_id - replace a blob property taking a reference
++ * @dev: DRM device
++ * @blob: a pointer to the member blob to be replaced
++ * @blob_id: the id of the new blob to replace with
++ * @expected_size: expected size of the blob property
++ * @expected_elem_size: expected size of an element in the blob property
++ * @replaced: if the blob was in fact replaced
++ *
++ * Look up the new blob from id, take its reference, check expected sizes of
++ * the blob and its element and replace the old blob by the new one. Advertise
++ * if the replacement operation was successful.
++ *
++ * Return: true if the blob was in fact replaced. -EINVAL if the new blob was
++ * not found or sizes don't match.
++ */
++int drm_property_replace_blob_from_id(struct drm_device *dev,
++ struct drm_property_blob **blob,
++ uint64_t blob_id,
++ ssize_t expected_size,
++ ssize_t expected_elem_size,
++ bool *replaced)
++{
++ struct drm_property_blob *new_blob = NULL;
++
++ if (blob_id != 0) {
++ new_blob = drm_property_lookup_blob(dev, blob_id);
++ if (new_blob == NULL)
++ return -EINVAL;
++
++ if (expected_size > 0 &&
++ new_blob->length != expected_size) {
++ drm_property_blob_put(new_blob);
++ return -EINVAL;
++ }
++ if (expected_elem_size > 0 &&
++ new_blob->length % expected_elem_size != 0) {
++ drm_property_blob_put(new_blob);
++ return -EINVAL;
++ }
++ }
++
++ *replaced |= drm_property_replace_blob(blob, new_blob);
++ drm_property_blob_put(new_blob);
++
++ return 0;
++}
++EXPORT_SYMBOL(drm_property_replace_blob_from_id);
++
+ int drm_mode_getblob_ioctl(struct drm_device *dev,
+ void *data, struct drm_file *file_priv)
+ {
+diff --git a/include/drm/drm_mode_object.h b/include/drm/drm_mode_object.h
+index 912f1e4156853f..08d7a7f0188fea 100644
+--- a/include/drm/drm_mode_object.h
++++ b/include/drm/drm_mode_object.h
+@@ -60,7 +60,7 @@ struct drm_mode_object {
+ void (*free_cb)(struct kref *kref);
+ };
+
+-#define DRM_OBJECT_MAX_PROPERTY 24
++#define DRM_OBJECT_MAX_PROPERTY 64
+ /**
+ * struct drm_object_properties - property tracking for &drm_mode_object
+ */
+diff --git a/include/drm/drm_plane.h b/include/drm/drm_plane.h
+index 51291983ea445d..52c3287da0daad 100644
+--- a/include/drm/drm_plane.h
++++ b/include/drm/drm_plane.h
+@@ -237,6 +237,13 @@ struct drm_plane_state {
+
+ /** @state: backpointer to global drm_atomic_state */
+ struct drm_atomic_state *state;
++
++ /**
++ * @color_mgmt_changed: Color management properties have changed. Used
++ * by the atomic helpers and drivers to steer the atomic commit control
++ * flow.
++ */
++ bool color_mgmt_changed : 1;
+ };
+
+ static inline struct drm_rect
+diff --git a/include/drm/drm_property.h b/include/drm/drm_property.h
+index 65bc9710a47029..082f29156b3e3f 100644
+--- a/include/drm/drm_property.h
++++ b/include/drm/drm_property.h
+@@ -279,6 +279,12 @@ struct drm_property_blob *drm_property_create_blob(struct drm_device *dev,
+ const void *data);
+ struct drm_property_blob *drm_property_lookup_blob(struct drm_device *dev,
+ uint32_t id);
++int drm_property_replace_blob_from_id(struct drm_device *dev,
++ struct drm_property_blob **blob,
++ uint64_t blob_id,
++ ssize_t expected_size,
++ ssize_t expected_elem_size,
++ bool *replaced);
+ int drm_property_replace_global_blob(struct drm_device *dev,
+ struct drm_property_blob **replace,
+ size_t length,
+diff --git a/include/uapi/drm/drm_mode.h b/include/uapi/drm/drm_mode.h
+index 43691058d28fb8..23fc194009980f 100644
+--- a/include/uapi/drm/drm_mode.h
++++ b/include/uapi/drm/drm_mode.h
+@@ -843,6 +843,14 @@ struct drm_color_ctm {
+ __u64 matrix[9];
+ };
+
++struct drm_color_ctm2 {
++ /*
++ * Conversion matrix in S31.32 sign-magnitude
++ * (not two's complement!) format.
++ */
++ __u64 matrix[12];
++};
++
+ struct drm_color_lut {
+ /*
+ * Values are mapped linearly to 0.0 - 1.0 range, with 0x0 == 0.0 and
+