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