1 /* $NetBSD: amdgpu_dm_color.c,v 1.2 2021/12/18 23:45:00 riastradh Exp $ */
2
3 /*
4 * Copyright 2018 Advanced Micro Devices, Inc.
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19 * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 * OTHER DEALINGS IN THE SOFTWARE.
23 *
24 * Authors: AMD
25 *
26 */
27 #include <sys/cdefs.h>
28 __KERNEL_RCSID(0, "$NetBSD: amdgpu_dm_color.c,v 1.2 2021/12/18 23:45:00 riastradh Exp $");
29
30 #include "amdgpu.h"
31 #include "amdgpu_mode.h"
32 #include "amdgpu_dm.h"
33 #include "dc.h"
34 #include "modules/color/color_gamma.h"
35 #include "basics/conversion.h"
36
37 /*
38 * The DC interface to HW gives us the following color management blocks
39 * per pipe (surface):
40 *
41 * - Input gamma LUT (de-normalized)
42 * - Input CSC (normalized)
43 * - Surface degamma LUT (normalized)
44 * - Surface CSC (normalized)
45 * - Surface regamma LUT (normalized)
46 * - Output CSC (normalized)
47 *
48 * But these aren't a direct mapping to DRM color properties. The current DRM
49 * interface exposes CRTC degamma, CRTC CTM and CRTC regamma while our hardware
50 * is essentially giving:
51 *
52 * Plane CTM -> Plane degamma -> Plane CTM -> Plane regamma -> Plane CTM
53 *
54 * The input gamma LUT block isn't really applicable here since it operates
55 * on the actual input data itself rather than the HW fp representation. The
56 * input and output CSC blocks are technically available to use as part of
57 * the DC interface but are typically used internally by DC for conversions
58 * between color spaces. These could be blended together with user
59 * adjustments in the future but for now these should remain untouched.
60 *
61 * The pipe blending also happens after these blocks so we don't actually
62 * support any CRTC props with correct blending with multiple planes - but we
63 * can still support CRTC color management properties in DM in most single
64 * plane cases correctly with clever management of the DC interface in DM.
65 *
66 * As per DRM documentation, blocks should be in hardware bypass when their
67 * respective property is set to NULL. A linear DGM/RGM LUT should also
68 * considered as putting the respective block into bypass mode.
69 *
70 * This means that the following
71 * configuration is assumed to be the default:
72 *
73 * Plane DGM Bypass -> Plane CTM Bypass -> Plane RGM Bypass -> ...
74 * CRTC DGM Bypass -> CRTC CTM Bypass -> CRTC RGM Bypass
75 */
76
77 #define MAX_DRM_LUT_VALUE 0xFFFF
78
79 /*
80 * Initialize the color module.
81 *
82 * We're not using the full color module, only certain components.
83 * Only call setup functions for components that we need.
84 */
amdgpu_dm_init_color_mod(void)85 void amdgpu_dm_init_color_mod(void)
86 {
87 setup_x_points_distribution();
88 }
89
90 /* Extracts the DRM lut and lut size from a blob. */
91 static const struct drm_color_lut *
__extract_blob_lut(const struct drm_property_blob * blob,uint32_t * size)92 __extract_blob_lut(const struct drm_property_blob *blob, uint32_t *size)
93 {
94 *size = blob ? drm_color_lut_size(blob) : 0;
95 return blob ? (struct drm_color_lut *)blob->data : NULL;
96 }
97
98 /*
99 * Return true if the given lut is a linear mapping of values, i.e. it acts
100 * like a bypass LUT.
101 *
102 * It is considered linear if the lut represents:
103 * f(a) = (0xFF00/MAX_COLOR_LUT_ENTRIES-1)a; for integer a in
104 * [0, MAX_COLOR_LUT_ENTRIES)
105 */
__is_lut_linear(const struct drm_color_lut * lut,uint32_t size)106 static bool __is_lut_linear(const struct drm_color_lut *lut, uint32_t size)
107 {
108 int i;
109 uint32_t expected;
110 int delta;
111
112 for (i = 0; i < size; i++) {
113 /* All color values should equal */
114 if ((lut[i].red != lut[i].green) || (lut[i].green != lut[i].blue))
115 return false;
116
117 expected = i * MAX_DRM_LUT_VALUE / (size-1);
118
119 /* Allow a +/-1 error. */
120 delta = lut[i].red - expected;
121 if (delta < -1 || 1 < delta)
122 return false;
123 }
124 return true;
125 }
126
127 /**
128 * Convert the drm_color_lut to dc_gamma. The conversion depends on the size
129 * of the lut - whether or not it's legacy.
130 */
__drm_lut_to_dc_gamma(const struct drm_color_lut * lut,struct dc_gamma * gamma,bool is_legacy)131 static void __drm_lut_to_dc_gamma(const struct drm_color_lut *lut,
132 struct dc_gamma *gamma, bool is_legacy)
133 {
134 uint32_t r, g, b;
135 int i;
136
137 if (is_legacy) {
138 for (i = 0; i < MAX_COLOR_LEGACY_LUT_ENTRIES; i++) {
139 r = drm_color_lut_extract(lut[i].red, 16);
140 g = drm_color_lut_extract(lut[i].green, 16);
141 b = drm_color_lut_extract(lut[i].blue, 16);
142
143 gamma->entries.red[i] = dc_fixpt_from_int(r);
144 gamma->entries.green[i] = dc_fixpt_from_int(g);
145 gamma->entries.blue[i] = dc_fixpt_from_int(b);
146 }
147 return;
148 }
149
150 /* else */
151 for (i = 0; i < MAX_COLOR_LUT_ENTRIES; i++) {
152 r = drm_color_lut_extract(lut[i].red, 16);
153 g = drm_color_lut_extract(lut[i].green, 16);
154 b = drm_color_lut_extract(lut[i].blue, 16);
155
156 gamma->entries.red[i] = dc_fixpt_from_fraction(r, MAX_DRM_LUT_VALUE);
157 gamma->entries.green[i] = dc_fixpt_from_fraction(g, MAX_DRM_LUT_VALUE);
158 gamma->entries.blue[i] = dc_fixpt_from_fraction(b, MAX_DRM_LUT_VALUE);
159 }
160 }
161
162 /*
163 * Converts a DRM CTM to a DC CSC float matrix.
164 * The matrix needs to be a 3x4 (12 entry) matrix.
165 */
__drm_ctm_to_dc_matrix(const struct drm_color_ctm * ctm,struct fixed31_32 * matrix)166 static void __drm_ctm_to_dc_matrix(const struct drm_color_ctm *ctm,
167 struct fixed31_32 *matrix)
168 {
169 int64_t val;
170 int i;
171
172 /*
173 * DRM gives a 3x3 matrix, but DC wants 3x4. Assuming we're operating
174 * with homogeneous coordinates, augment the matrix with 0's.
175 *
176 * The format provided is S31.32, using signed-magnitude representation.
177 * Our fixed31_32 is also S31.32, but is using 2's complement. We have
178 * to convert from signed-magnitude to 2's complement.
179 */
180 for (i = 0; i < 12; i++) {
181 /* Skip 4th element */
182 if (i % 4 == 3) {
183 matrix[i] = dc_fixpt_zero;
184 continue;
185 }
186
187 /* gamut_remap_matrix[i] = ctm[i - floor(i/4)] */
188 val = ctm->matrix[i - (i / 4)];
189 /* If negative, convert to 2's complement. */
190 if (val & (1ULL << 63))
191 val = -(val & ~(1ULL << 63));
192
193 matrix[i].value = val;
194 }
195 }
196
197 /* Calculates the legacy transfer function - only for sRGB input space. */
__set_legacy_tf(struct dc_transfer_func * func,const struct drm_color_lut * lut,uint32_t lut_size,bool has_rom)198 static int __set_legacy_tf(struct dc_transfer_func *func,
199 const struct drm_color_lut *lut, uint32_t lut_size,
200 bool has_rom)
201 {
202 struct dc_gamma *gamma = NULL;
203 bool res;
204
205 ASSERT(lut && lut_size == MAX_COLOR_LEGACY_LUT_ENTRIES);
206
207 gamma = dc_create_gamma();
208 if (!gamma)
209 return -ENOMEM;
210
211 gamma->type = GAMMA_RGB_256;
212 gamma->num_entries = lut_size;
213 __drm_lut_to_dc_gamma(lut, gamma, true);
214
215 res = mod_color_calculate_regamma_params(func, gamma, true, has_rom,
216 NULL);
217
218 dc_gamma_release(&gamma);
219
220 return res ? 0 : -ENOMEM;
221 }
222
223 /* Calculates the output transfer function based on expected input space. */
__set_output_tf(struct dc_transfer_func * func,const struct drm_color_lut * lut,uint32_t lut_size,bool has_rom)224 static int __set_output_tf(struct dc_transfer_func *func,
225 const struct drm_color_lut *lut, uint32_t lut_size,
226 bool has_rom)
227 {
228 struct dc_gamma *gamma = NULL;
229 bool res;
230
231 ASSERT(lut && lut_size == MAX_COLOR_LUT_ENTRIES);
232
233 gamma = dc_create_gamma();
234 if (!gamma)
235 return -ENOMEM;
236
237 gamma->num_entries = lut_size;
238 __drm_lut_to_dc_gamma(lut, gamma, false);
239
240 if (func->tf == TRANSFER_FUNCTION_LINEAR) {
241 /*
242 * Color module doesn't like calculating regamma params
243 * on top of a linear input. But degamma params can be used
244 * instead to simulate this.
245 */
246 gamma->type = GAMMA_CUSTOM;
247 res = mod_color_calculate_degamma_params(func, gamma, true);
248 } else {
249 /*
250 * Assume sRGB. The actual mapping will depend on whether the
251 * input was legacy or not.
252 */
253 gamma->type = GAMMA_CS_TFM_1D;
254 res = mod_color_calculate_regamma_params(func, gamma, false,
255 has_rom, NULL);
256 }
257
258 dc_gamma_release(&gamma);
259
260 return res ? 0 : -ENOMEM;
261 }
262
263 /* Caculates the input transfer function based on expected input space. */
__set_input_tf(struct dc_transfer_func * func,const struct drm_color_lut * lut,uint32_t lut_size)264 static int __set_input_tf(struct dc_transfer_func *func,
265 const struct drm_color_lut *lut, uint32_t lut_size)
266 {
267 struct dc_gamma *gamma = NULL;
268 bool res;
269
270 gamma = dc_create_gamma();
271 if (!gamma)
272 return -ENOMEM;
273
274 gamma->type = GAMMA_CUSTOM;
275 gamma->num_entries = lut_size;
276
277 __drm_lut_to_dc_gamma(lut, gamma, false);
278
279 res = mod_color_calculate_degamma_params(func, gamma, true);
280 dc_gamma_release(&gamma);
281
282 return res ? 0 : -ENOMEM;
283 }
284
285 /**
286 * amdgpu_dm_update_crtc_color_mgmt: Maps DRM color management to DC stream.
287 * @crtc: amdgpu_dm crtc state
288 *
289 * With no plane level color management properties we're free to use any
290 * of the HW blocks as long as the CRTC CTM always comes before the
291 * CRTC RGM and after the CRTC DGM.
292 *
293 * The CRTC RGM block will be placed in the RGM LUT block if it is non-linear.
294 * The CRTC DGM block will be placed in the DGM LUT block if it is non-linear.
295 * The CRTC CTM will be placed in the gamut remap block if it is non-linear.
296 *
297 * The RGM block is typically more fully featured and accurate across
298 * all ASICs - DCE can't support a custom non-linear CRTC DGM.
299 *
300 * For supporting both plane level color management and CRTC level color
301 * management at once we have to either restrict the usage of CRTC properties
302 * or blend adjustments together.
303 *
304 * Returns 0 on success.
305 */
amdgpu_dm_update_crtc_color_mgmt(struct dm_crtc_state * crtc)306 int amdgpu_dm_update_crtc_color_mgmt(struct dm_crtc_state *crtc)
307 {
308 struct dc_stream_state *stream = crtc->stream;
309 struct amdgpu_device *adev =
310 (struct amdgpu_device *)crtc->base.state->dev->dev_private;
311 bool has_rom = adev->asic_type <= CHIP_RAVEN;
312 struct drm_color_ctm *ctm = NULL;
313 const struct drm_color_lut *degamma_lut, *regamma_lut;
314 uint32_t degamma_size, regamma_size;
315 bool has_regamma, has_degamma;
316 bool is_legacy;
317 int r;
318
319 degamma_lut = __extract_blob_lut(crtc->base.degamma_lut, °amma_size);
320 if (degamma_lut && degamma_size != MAX_COLOR_LUT_ENTRIES)
321 return -EINVAL;
322
323 regamma_lut = __extract_blob_lut(crtc->base.gamma_lut, ®amma_size);
324 if (regamma_lut && regamma_size != MAX_COLOR_LUT_ENTRIES &&
325 regamma_size != MAX_COLOR_LEGACY_LUT_ENTRIES)
326 return -EINVAL;
327
328 has_degamma =
329 degamma_lut && !__is_lut_linear(degamma_lut, degamma_size);
330
331 has_regamma =
332 regamma_lut && !__is_lut_linear(regamma_lut, regamma_size);
333
334 is_legacy = regamma_size == MAX_COLOR_LEGACY_LUT_ENTRIES;
335
336 /* Reset all adjustments. */
337 crtc->cm_has_degamma = false;
338 crtc->cm_is_degamma_srgb = false;
339
340 /* Setup regamma and degamma. */
341 if (is_legacy) {
342 /*
343 * Legacy regamma forces us to use the sRGB RGM as a base.
344 * This also means we can't use linear DGM since DGM needs
345 * to use sRGB as a base as well, resulting in incorrect CRTC
346 * DGM and CRTC CTM.
347 *
348 * TODO: Just map this to the standard regamma interface
349 * instead since this isn't really right. One of the cases
350 * where this setup currently fails is trying to do an
351 * inverse color ramp in legacy userspace.
352 */
353 crtc->cm_is_degamma_srgb = true;
354 stream->out_transfer_func->type = TF_TYPE_DISTRIBUTED_POINTS;
355 stream->out_transfer_func->tf = TRANSFER_FUNCTION_SRGB;
356
357 r = __set_legacy_tf(stream->out_transfer_func, regamma_lut,
358 regamma_size, has_rom);
359 if (r)
360 return r;
361 } else if (has_regamma) {
362 /* CRTC RGM goes into RGM LUT. */
363 stream->out_transfer_func->type = TF_TYPE_DISTRIBUTED_POINTS;
364 stream->out_transfer_func->tf = TRANSFER_FUNCTION_LINEAR;
365
366 r = __set_output_tf(stream->out_transfer_func, regamma_lut,
367 regamma_size, has_rom);
368 if (r)
369 return r;
370 } else {
371 /*
372 * No CRTC RGM means we can just put the block into bypass
373 * since we don't have any plane level adjustments using it.
374 */
375 stream->out_transfer_func->type = TF_TYPE_BYPASS;
376 stream->out_transfer_func->tf = TRANSFER_FUNCTION_LINEAR;
377 }
378
379 /*
380 * CRTC DGM goes into DGM LUT. It would be nice to place it
381 * into the RGM since it's a more featured block but we'd
382 * have to place the CTM in the OCSC in that case.
383 */
384 crtc->cm_has_degamma = has_degamma;
385
386 /* Setup CRTC CTM. */
387 if (crtc->base.ctm) {
388 ctm = (struct drm_color_ctm *)crtc->base.ctm->data;
389
390 /*
391 * Gamut remapping must be used for gamma correction
392 * since it comes before the regamma correction.
393 *
394 * OCSC could be used for gamma correction, but we'd need to
395 * blend the adjustments together with the required output
396 * conversion matrix - so just use the gamut remap block
397 * for now.
398 */
399 __drm_ctm_to_dc_matrix(ctm, stream->gamut_remap_matrix.matrix);
400
401 stream->gamut_remap_matrix.enable_remap = true;
402 stream->csc_color_matrix.enable_adjustment = false;
403 } else {
404 /* Bypass CTM. */
405 stream->gamut_remap_matrix.enable_remap = false;
406 stream->csc_color_matrix.enable_adjustment = false;
407 }
408
409 return 0;
410 }
411
412 /**
413 * amdgpu_dm_update_plane_color_mgmt: Maps DRM color management to DC plane.
414 * @crtc: amdgpu_dm crtc state
415 * @ dc_plane_state: target DC surface
416 *
417 * Update the underlying dc_stream_state's input transfer function (ITF) in
418 * preparation for hardware commit. The transfer function used depends on
419 * the prepartion done on the stream for color management.
420 *
421 * Returns 0 on success.
422 */
amdgpu_dm_update_plane_color_mgmt(struct dm_crtc_state * crtc,struct dc_plane_state * dc_plane_state)423 int amdgpu_dm_update_plane_color_mgmt(struct dm_crtc_state *crtc,
424 struct dc_plane_state *dc_plane_state)
425 {
426 const struct drm_color_lut *degamma_lut;
427 uint32_t degamma_size;
428 int r;
429
430 if (crtc->cm_has_degamma) {
431 degamma_lut = __extract_blob_lut(crtc->base.degamma_lut,
432 °amma_size);
433 ASSERT(degamma_size == MAX_COLOR_LUT_ENTRIES);
434
435 dc_plane_state->in_transfer_func->type =
436 TF_TYPE_DISTRIBUTED_POINTS;
437
438 /*
439 * This case isn't fully correct, but also fairly
440 * uncommon. This is userspace trying to use a
441 * legacy gamma LUT + atomic degamma LUT
442 * at the same time.
443 *
444 * Legacy gamma requires the input to be in linear
445 * space, so that means we need to apply an sRGB
446 * degamma. But color module also doesn't support
447 * a user ramp in this case so the degamma will
448 * be lost.
449 *
450 * Even if we did support it, it's still not right:
451 *
452 * Input -> CRTC DGM -> sRGB DGM -> CRTC CTM ->
453 * sRGB RGM -> CRTC RGM -> Output
454 *
455 * The CSC will be done in the wrong space since
456 * we're applying an sRGB DGM on top of the CRTC
457 * DGM.
458 *
459 * TODO: Don't use the legacy gamma interface and just
460 * map these to the atomic one instead.
461 */
462 if (crtc->cm_is_degamma_srgb)
463 dc_plane_state->in_transfer_func->tf =
464 TRANSFER_FUNCTION_SRGB;
465 else
466 dc_plane_state->in_transfer_func->tf =
467 TRANSFER_FUNCTION_LINEAR;
468
469 r = __set_input_tf(dc_plane_state->in_transfer_func,
470 degamma_lut, degamma_size);
471 if (r)
472 return r;
473 } else if (crtc->cm_is_degamma_srgb) {
474 /*
475 * For legacy gamma support we need the regamma input
476 * in linear space. Assume that the input is sRGB.
477 */
478 dc_plane_state->in_transfer_func->type = TF_TYPE_PREDEFINED;
479 dc_plane_state->in_transfer_func->tf = TRANSFER_FUNCTION_SRGB;
480 } else {
481 /* ...Otherwise we can just bypass the DGM block. */
482 dc_plane_state->in_transfer_func->type = TF_TYPE_BYPASS;
483 dc_plane_state->in_transfer_func->tf = TRANSFER_FUNCTION_LINEAR;
484 }
485
486 return 0;
487 }
488