//*****************************************************************************
//
//  SPDX-FileCopyrightText: Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
//  SPDX-License-Identifier: MIT
//
//  Permission is hereby granted, free of charge, to any person obtaining a
//  copy of this software and associated documentation files (the "Software"),
//  to deal in the Software without restriction, including without limitation
//  the rights to use, copy, modify, merge, publish, distribute, sublicense,
//  and/or sell copies of the Software, and to permit persons to whom the
//  Software is furnished to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
//  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
//  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
//  DEALINGS IN THE SOFTWARE.
//
//  File:       nvt_displayid20.c
//
//  Purpose:    the provide displayID 2.0 related services
//
//*****************************************************************************

#include "nvBinSegment.h"
#include "nvmisc.h"

#include "nvtiming_pvt.h"
#include "displayid20.h"

PUSH_SEGMENTS

// Helper function
static NVT_STATUS getPrimaryUseCase(NvU8 product_type, NVT_DISPLAYID_PRODUCT_PRIMARY_USE_CASE *primary_use_case);
static NvU32      greatestCommonDenominator(NvU32 x, NvU32 y);
static NvU8       getExistedTimingSeqNumber(NVT_DISPLAYID_2_0_INFO *pDisplayIdInfo, enum NVT_TIMING_TYPE);

/*
 *  The Second-generation version of VESA DisplayID Standard
 *                    DisplayID v2.0
 *
 *  @brief Parses a displayID20 section
 *
 *  @param pDisplayId     The DisplayId20 Section Block ()
 *  @param length         Size of the displayId section Block
 *  @param pDisplayIdInfo Need to parse the raw data to store as NV structure
 *
 */
CODE_SEGMENT(PAGE_DD_CODE)
NVT_STATUS NV_STDCALL
NvTiming_parseDisplayId20Info(
    const NvU8 *pDisplayId,
    NvU32 length,
    NVT_DISPLAYID_2_0_INFO *pDisplayIdInfo)
{
    NVT_STATUS             status = NVT_STATUS_SUCCESS;
    const DISPLAYID_2_0_SECTION *pSection = NULL;
    NvU32                  offset = 0;
    NvU32                  extensionIndex = 0;
    NvU32                  idx = 0;

    // parameter check
    if ((pDisplayId == NULL) ||
        (pDisplayIdInfo == NULL))
    {
        return NVT_STATUS_ERR;
    }

    pSection = (const DISPLAYID_2_0_SECTION *)pDisplayId;

    if ((pSection->header.version < DISPLAYID_2_0_VERSION) ||
        (DISPLAYID_2_0_SECTION_SIZE_TOTAL(pSection->header) > length))
    {
        return NVT_STATUS_ERR;
    }

    NVMISC_MEMSET(pDisplayIdInfo, 0, sizeof(NVT_DISPLAYID_2_0_INFO));

    status = parseDisplayId20BaseSection(pSection, pDisplayIdInfo);
    if (status != NVT_STATUS_SUCCESS)
    {
        return status;
    }

    pDisplayIdInfo->extension_count = pSection->header.extension_count;
    for (extensionIndex = 0; extensionIndex < pDisplayIdInfo->extension_count; extensionIndex++)
    {
        // Get offset to the next section.
        offset += DISPLAYID_2_0_SECTION_SIZE_TOTAL(pSection->header);

        // validate the next section buffer is valid
        pSection = (const DISPLAYID_2_0_SECTION *)(pDisplayId + offset);
        if ((offset + DISPLAYID_2_0_SECTION_SIZE_TOTAL(pSection->header)) > length)
        {
            return NVT_STATUS_ERR;
        }

        // process the section
        status = parseDisplayId20ExtensionSection(pSection, pDisplayIdInfo);
        if (status != NVT_STATUS_SUCCESS)
        {
            return status;
        }
    }

    for (idx = 0; idx < pDisplayIdInfo->total_timings; idx++)
    {
        updateColorFormatForDisplayId20Timings(pDisplayIdInfo, idx);
    }

    return status;
}

CODE_SEGMENT(PAGE_DD_CODE)
NvU32
NvTiming_DisplayID2ValidationMask(
    NVT_DISPLAYID_2_0_INFO *pDisplayId20Info,
    NvBool bIsStrongValidation)
{
    NvU32 j;
    NvU32 ret = 0;

    // check the DisplayId2 version and signature
    if (pDisplayId20Info->version != 0x2)
    {
        ret |= NVT_DID2_VALIDATION_ERR_MASK(NVT_DID2_VALIDATION_ERR_VERSION);
    }

    if (!pDisplayId20Info->valid_data_blocks.product_id_present)
    {
        ret |= NVT_DID2_VALIDATION_ERR_MASK(NVT_DID2_VALIDATION_ERR_PRODUCT_IDENTIFY);
    }

    if (pDisplayId20Info->primary_use_case >= PRODUCT_PRIMARY_USE_GENERIC_DISPLAY &&
        pDisplayId20Info->primary_use_case <= PRODUCT_PRIMARY_USE_HEAD_MOUNT_AUGMENTED_REALITY)
    {
        if (!(pDisplayId20Info->valid_data_blocks.parameters_present        &&
              pDisplayId20Info->valid_data_blocks.interface_feature_present &&
              pDisplayId20Info->valid_data_blocks.type7Timing_present       &&
              pDisplayId20Info->total_timings))
        {
            ret |= NVT_DID2_VALIDATION_ERR_MASK(NVT_DID2_VALIDATION_ERR_NO_DATA_BLOCK);
        }
    }

    // Strong validation to follow
    if (bIsStrongValidation == NV_TRUE)
    {
        // TODO : For each of the Data Block limitation
        // Type 7 Timings data block
        for (j = 0; j <= pDisplayId20Info->total_timings; j++)
        {
            if ( NVT_PREFERRED_TIMING_IS_DISPLAYID(pDisplayId20Info->timing[j].etc.flag) &&
                 (pDisplayId20Info->display_param.h_pixels != 0) &&
                 (pDisplayId20Info->display_param.v_pixels != 0))
            {
                if ( pDisplayId20Info->timing[j].HVisible != pDisplayId20Info->display_param.h_pixels ||
                     pDisplayId20Info->timing[j].VVisible != pDisplayId20Info->display_param.v_pixels )
                {
                    ret |= NVT_DID2_VALIDATION_ERR_MASK(NVT_DID2_VALIDATION_ERR_NO_DATA_BLOCK);
                    break;
                }
            }
        }
        // TODO : go on the next data block validation if it existed.
        // TODO : validate extension blocks
    }

    return ret;
}

CODE_SEGMENT(PAGE_DD_CODE)
NVT_STATUS
NvTiming_DisplayID2ValidationDataBlocks(
    NVT_DISPLAYID_2_0_INFO *pDisplayIdInfo,
    NvBool bIsStrongValidation)
{
    if (NvTiming_DisplayID2ValidationMask(pDisplayIdInfo, bIsStrongValidation) != 0)
    {
        return NVT_STATUS_ERR;
    }
    else
    {
        return NVT_STATUS_SUCCESS;
    }
}

//  DisplayID20 Entry point functions
CODE_SEGMENT(PAGE_DD_CODE)
NVT_STATUS
parseDisplayId20BaseSection(
    const DISPLAYID_2_0_SECTION *pSection,
    NVT_DISPLAYID_2_0_INFO *pDisplayIdInfo)
{
    NVT_STATUS    status = NVT_STATUS_SUCCESS;

    // validate for section checksum before processing the data block
    if (computeDisplayId20SectionCheckSum((const NvU8 *)pSection, DISPLAYID_2_0_SECTION_SIZE_TOTAL(pSection->header)) != 0)
    {
        status |= NVT_DID2_VALIDATION_ERR_MASK(NVT_DID2_VALIDATION_ERR_CHECKSUM);
        return status;
    }

    pDisplayIdInfo->revision = pSection->header.revision;
    pDisplayIdInfo->version = pSection->header.version;

    status = getPrimaryUseCase(pSection->header.product_type,
        &pDisplayIdInfo->primary_use_case);
    if (status != NVT_STATUS_SUCCESS)
    {
        return status;
    }

    status = parseDisplayId20SectionDataBlocks(pSection, pDisplayIdInfo);

    return status;
}

CODE_SEGMENT(PAGE_DD_CODE)
NVT_STATUS
parseDisplayId20ExtensionSection(
    const DISPLAYID_2_0_SECTION *pSection,
    NVT_DISPLAYID_2_0_INFO *pDisplayIdInfo)
{
    NVT_STATUS    status = NVT_STATUS_SUCCESS;

    // validate for section checksum before processing the data block
    if (computeDisplayId20SectionCheckSum((const NvU8 *)pSection, DISPLAYID_2_0_SECTION_SIZE_TOTAL(pSection->header)) != 0)
    {
        status |= NVT_DID2_VALIDATION_ERR_MASK(NVT_DID2_VALIDATION_ERR_CHECKSUM);
        return status;
    }

    nvt_assert(pSection->header.version >= DISPLAYID_2_0_VERSION);
    nvt_assert(pSection->header.extension_count == 0);
    nvt_assert(pSection->header.product_type == DISPLAYID_2_0_PROD_EXTENSION);

    status = parseDisplayId20SectionDataBlocks(pSection, pDisplayIdInfo);

    return status;
}

CODE_SEGMENT(PAGE_DD_CODE)
NVT_STATUS
parseDisplayId20SectionDataBlocks(
    const DISPLAYID_2_0_SECTION *pSection,
    NVT_DISPLAYID_2_0_INFO *pDisplayIdInfo)
{
    NvU32                            i = 0;
    NvU32                            offset = 0;
    const DISPLAYID_2_0_DATA_BLOCK_HEADER *pDataBlock = NULL;
    NVT_STATUS                       status = NVT_STATUS_SUCCESS;

    while (offset < pSection->header.section_bytes)
    {
        // Get current block
        pDataBlock = (const DISPLAYID_2_0_DATA_BLOCK_HEADER *)(pSection->data + offset);

        // detected zero padding
        if (pDataBlock->type == 0)
        {
            for (i = offset; i < pSection->header.section_bytes; i++)
            {
                // validate that all paddings are zeros
                nvt_assert(pSection->data[i] == 0);
            }
            break;
        }

        // check data block is valid.
        if ((offset + DISPLAYID_2_0_DATA_BLOCK_SIZE_TOTAL(pDataBlock)) > pSection->header.section_bytes)
        {
            return NVT_STATUS_ERR;
        }

        // parse the data block
        status = parseDisplayId20DataBlock(pDataBlock, pDisplayIdInfo);
        if (status != NVT_STATUS_SUCCESS)
        {
            return status;
        }

        switch (pDataBlock->type)
        {
            case DISPLAYID_2_0_BLOCK_TYPE_PRODUCT_IDENTITY:
                pDisplayIdInfo->valid_data_blocks.product_id_present             = NV_TRUE;
                break;
            case DISPLAYID_2_0_BLOCK_TYPE_DISPLAY_PARAM:
                pDisplayIdInfo->valid_data_blocks.parameters_present             = NV_TRUE;
                if (pDisplayIdInfo->display_param.audio_speakers_integrated == AUDIO_SPEAKER_INTEGRATED_SUPPORTED)
                {
                    pDisplayIdInfo->basic_caps |= NVT_DISPLAY_2_0_CAP_BASIC_AUDIO;
                }
                break;
            case DISPLAYID_2_0_BLOCK_TYPE_TIMING_7:
                pDisplayIdInfo->valid_data_blocks.type7Timing_present            = NV_TRUE;
                break;
            case DISPLAYID_2_0_BLOCK_TYPE_TIMING_8:
                pDisplayIdInfo->valid_data_blocks.type8Timing_present            = NV_TRUE;
                break;
            case DISPLAYID_2_0_BLOCK_TYPE_TIMING_9:
                pDisplayIdInfo->valid_data_blocks.type9Timing_present            = NV_TRUE;
                break;
            case DISPLAYID_2_0_BLOCK_TYPE_RANGE_LIMITS:
                pDisplayIdInfo->valid_data_blocks.dynamic_range_limit_present    = NV_TRUE;
                break;
            case DISPLAYID_2_0_BLOCK_TYPE_INTERFACE_FEATURES:
                pDisplayIdInfo->valid_data_blocks.interface_feature_present      = NV_TRUE;

                // Supported - Color depth is supported for all supported timings.  Supported timing includes all Display-ID exposed timings
                // (that is timing exposed using DisplayID timing types and CTA VICs)
                if (IS_BPC_SUPPORTED_COLORFORMAT(pDisplayIdInfo->interface_features.yuv444.bpcs))
                {
                    pDisplayIdInfo->basic_caps |= NVT_DISPLAY_2_0_CAP_YCbCr_444;
                }

                if (IS_BPC_SUPPORTED_COLORFORMAT(pDisplayIdInfo->interface_features.yuv422.bpcs))
                {
                    pDisplayIdInfo->basic_caps |= NVT_DISPLAY_2_0_CAP_YCbCr_422;
                }

                if (pDisplayIdInfo->interface_features.audio_capability.support_48khz   ||
                    pDisplayIdInfo->interface_features.audio_capability.support_44_1khz ||
                    pDisplayIdInfo->interface_features.audio_capability.support_32khz)
                {
                    pDisplayIdInfo->basic_caps |= NVT_DISPLAY_2_0_CAP_BASIC_AUDIO;
                }
                break;
            case DISPLAYID_2_0_BLOCK_TYPE_STEREO:
                pDisplayIdInfo->valid_data_blocks.stereo_interface_present           = NV_TRUE;
                break;
            case DISPLAYID_2_0_BLOCK_TYPE_TILED_DISPLAY:
                pDisplayIdInfo->valid_data_blocks.tiled_display_present              = NV_TRUE;
                break;
            case DISPLAYID_2_0_BLOCK_TYPE_CONTAINER_ID:
                pDisplayIdInfo->valid_data_blocks.container_id_present               = NV_TRUE;
                break;
            case DISPLAYID_2_0_BLOCK_TYPE_TIMING_10:
                pDisplayIdInfo->valid_data_blocks.type10Timing_present               = NV_TRUE;
                break;
            case DISPLAYID_2_0_BLOCK_TYPE_ADAPTIVE_SYNC:
                pDisplayIdInfo->valid_data_blocks.adaptive_sync_present              = NV_TRUE;
                break;
            case DISPLAYID_2_0_BLOCK_TYPE_ARVR_HMD:
                pDisplayIdInfo->valid_data_blocks.arvr_hmd_present                   = NV_TRUE;
                break;
            case DISPLAYID_2_0_BLOCK_TYPE_ARVR_LAYER:
                pDisplayIdInfo->valid_data_blocks.arvr_layer_present                 = NV_TRUE;
                break;
            case DISPLAYID_2_0_BLOCK_TYPE_BRIGHTNESS_LUMINANCE_RANGE:
                pDisplayIdInfo->valid_data_blocks.brightness_luminance_range_present = NV_TRUE;
                break;
            case DISPLAYID_2_0_BLOCK_TYPE_VENDOR_SPEC:
                pDisplayIdInfo->valid_data_blocks.vendor_specific_present            = NV_TRUE;
                break;
            case DISPLAYID_2_0_BLOCK_TYPE_CTA_DATA:
                pDisplayIdInfo->valid_data_blocks.cta_data_present                   = NV_TRUE;
                break;
            default:
                status = NVT_STATUS_ERR;
        }

        // advance to the next block
        offset += DISPLAYID_2_0_DATA_BLOCK_SIZE_TOTAL(pDataBlock);
    }

    return status;
}

CODE_SEGMENT(PAGE_DD_CODE)
NVT_STATUS
parseDisplayId20DataBlock(
    const DISPLAYID_2_0_DATA_BLOCK_HEADER *pDataBlock,
    NVT_DISPLAYID_2_0_INFO *pDisplayIdInfo)
{
    NVT_STATUS  status = NVT_STATUS_SUCCESS;

    switch (pDataBlock->type)
    {
    case DISPLAYID_2_0_BLOCK_TYPE_PRODUCT_IDENTITY:
        status = parseDisplayId20ProductIdentity(pDataBlock, pDisplayIdInfo);
        break;
    case DISPLAYID_2_0_BLOCK_TYPE_DISPLAY_PARAM:
        status = parseDisplayId20DisplayParam(pDataBlock, pDisplayIdInfo);
        break;
    case DISPLAYID_2_0_BLOCK_TYPE_TIMING_7:
        status = parseDisplayId20Timing7(pDataBlock, pDisplayIdInfo);
        break;
    case DISPLAYID_2_0_BLOCK_TYPE_TIMING_8:
        status = parseDisplayId20Timing8(pDataBlock, pDisplayIdInfo);
        break;
    case DISPLAYID_2_0_BLOCK_TYPE_TIMING_9:
        status = parseDisplayId20Timing9(pDataBlock, pDisplayIdInfo);
        break;
    case DISPLAYID_2_0_BLOCK_TYPE_TIMING_10:
        status = parseDisplayId20Timing10(pDataBlock, pDisplayIdInfo);
        break;
    case DISPLAYID_2_0_BLOCK_TYPE_RANGE_LIMITS:
        status = parseDisplayId20RangeLimit(pDataBlock, pDisplayIdInfo);
        break;
    case DISPLAYID_2_0_BLOCK_TYPE_INTERFACE_FEATURES:
        status = parseDisplayId20DisplayInterfaceFeatures(pDataBlock, pDisplayIdInfo);
        break;
    case DISPLAYID_2_0_BLOCK_TYPE_STEREO:
        status = parseDisplayId20Stereo(pDataBlock, pDisplayIdInfo);
        break;
    case DISPLAYID_2_0_BLOCK_TYPE_TILED_DISPLAY:
        status = parseDisplayId20TiledDisplay(pDataBlock, pDisplayIdInfo);
        break;
    case DISPLAYID_2_0_BLOCK_TYPE_CONTAINER_ID:
        status = parseDisplayId20ContainerId(pDataBlock, pDisplayIdInfo);
        break;
    case DISPLAYID_2_0_BLOCK_TYPE_ADAPTIVE_SYNC:
        status = parseDisplayId20AdaptiveSync(pDataBlock, pDisplayIdInfo);
        break;
    case DISPLAYID_2_0_BLOCK_TYPE_ARVR_HMD:
        status = parseDisplayId20ARVRHMD(pDataBlock, pDisplayIdInfo);
        break;
    case DISPLAYID_2_0_BLOCK_TYPE_ARVR_LAYER:
        status = parseDisplayId20ARVRLayer(pDataBlock, pDisplayIdInfo);
        break;
    case DISPLAYID_2_0_BLOCK_TYPE_BRIGHTNESS_LUMINANCE_RANGE:
        status = parseDisplayId20BrightnessLuminanceRange(pDataBlock, pDisplayIdInfo);
        break;
    case DISPLAYID_2_0_BLOCK_TYPE_VENDOR_SPEC:
        status = parseDisplayId20VendorSpecific(pDataBlock, pDisplayIdInfo);
        break;
    case DISPLAYID_2_0_BLOCK_TYPE_CTA_DATA:
        status = parseDisplayId20CtaData(pDataBlock, pDisplayIdInfo);
        break;
    default:
        status = NVT_STATUS_ERR;
    }
    return status;
}

// All Data Blocks Parsing
CODE_SEGMENT(PAGE_DD_CODE)
NVT_STATUS
parseDisplayId20ProductIdentity(
    const DISPLAYID_2_0_DATA_BLOCK_HEADER *pDataBlock,
    NVT_DISPLAYID_2_0_INFO *pDisplayIdInfo)
{
    NVT_STATUS status = NVT_STATUS_SUCCESS;
    NVT_DISPLAYID_PRODUCT_IDENTITY *pProductIdentity = NULL;
    const DISPLAYID_2_0_PROD_IDENTIFICATION_BLOCK *pProductIdBlock = NULL;

    pProductIdBlock = (const DISPLAYID_2_0_PROD_IDENTIFICATION_BLOCK *)pDataBlock;

    // add more validation if needed

    if (pDisplayIdInfo == NULL) return status;

    pProductIdentity = &pDisplayIdInfo->product_identity;

    pProductIdentity->vendor_id = (pProductIdBlock->vendor[0] << 16) |
        (pProductIdBlock->vendor[1] << 8) |
        (pProductIdBlock->vendor[2]);
    pProductIdentity->product_id = (pProductIdBlock->product_code[0]) |
        (pProductIdBlock->product_code[1] << 8);
    pProductIdentity->serial_number = (pProductIdBlock->serial_number[0]) |
        (pProductIdBlock->serial_number[1] << 8) |
        (pProductIdBlock->serial_number[2] << 16) |
        (pProductIdBlock->serial_number[3] << 24);
    pProductIdentity->week = (pProductIdBlock->model_tag >= 1 && pProductIdBlock->model_tag <= 52) ?
        pProductIdBlock->model_tag : 0;
    pProductIdentity->year = (pProductIdBlock->model_tag == 0xFF) ?
        pProductIdBlock->model_year :
        pProductIdBlock->model_year + 2000;

    if (pProductIdBlock->product_name_string_size != 0)
    {
        NVMISC_STRNCPY((char *)pProductIdentity->product_string,
            (const char *)pProductIdBlock->product_name_string,
            pProductIdBlock->product_name_string_size);
    }
    pProductIdentity->product_string[pProductIdBlock->product_name_string_size] = '\0';

    return status;
}


CODE_SEGMENT(PAGE_DD_CODE)
NVT_STATUS
parseDisplayId20DisplayParam(
    const DISPLAYID_2_0_DATA_BLOCK_HEADER *pDataBlock,
    NVT_DISPLAYID_2_0_INFO *pDisplayIdInfo)
{
    NVT_STATUS status = NVT_STATUS_SUCCESS;
    const DISPLAYID_2_0_DISPLAY_PARAM_BLOCK *pDisplayParamBlock = NULL;
    NVT_DISPLAYID_DISPLAY_PARAMETERS *pDisplayParam = NULL;

    if (pDataBlock->data_bytes != DISPLAYID_2_0_DISPLAY_PARAM_BLOCK_PAYLOAD_LENGTH)
    {
        return NVT_STATUS_ERR;
    }

    // Add more validation here if needed

    if (pDisplayIdInfo == NULL) return status;

    pDisplayParamBlock = (const DISPLAYID_2_0_DISPLAY_PARAM_BLOCK *)pDataBlock;
    pDisplayParam = &pDisplayIdInfo->display_param;

    pDisplayParam->revision = pDisplayParamBlock->header.revision;
    pDisplayParam->h_image_size_micro_meter = (pDisplayParamBlock->horizontal_image_size[1] << 8 |
        pDisplayParamBlock->horizontal_image_size[0]) *
        (pDisplayParamBlock->header.image_size_multiplier ? 1000 : 100);
    pDisplayParam->v_image_size_micro_meter = (pDisplayParamBlock->vertical_image_size[1] << 8 |
        pDisplayParamBlock->vertical_image_size[0]) *
        (pDisplayParamBlock->header.image_size_multiplier ? 1000 : 100);
    pDisplayParam->h_pixels = pDisplayParamBlock->horizontal_pixel_count[1] << 8 |
        pDisplayParamBlock->horizontal_pixel_count[0];
    pDisplayParam->v_pixels = pDisplayParamBlock->vertical_pixel_count[1] << 8 |
        pDisplayParamBlock->vertical_pixel_count[0];

    pDisplayParam->scan_orientation          = pDisplayParamBlock->feature.scan_orientation;
    pDisplayParam->audio_speakers_integrated = pDisplayParamBlock->feature.audio_speaker_information ? AUDIO_SPEAKER_INTEGRATED_NOT_SUPPORTED : AUDIO_SPEAKER_INTEGRATED_SUPPORTED;
    pDisplayParam->color_map_standard        = pDisplayParamBlock->feature.color_information ? COLOR_MAP_CIE_1976 : COLOR_MAP_CIE_1931;

    // 12 bits Binary Fraction Representations
    pDisplayParam->primaries[0].x = pDisplayParamBlock->primary_color_1_chromaticity.color_bits_mid.color_x_bits_high << 8 |
        pDisplayParamBlock->primary_color_1_chromaticity.color_x_bits_low;
    pDisplayParam->primaries[0].y = pDisplayParamBlock->primary_color_1_chromaticity.color_y_bits_high << 4 |
        pDisplayParamBlock->primary_color_1_chromaticity.color_bits_mid.color_y_bits_low;
    pDisplayParam->primaries[1].x = pDisplayParamBlock->primary_color_2_chromaticity.color_bits_mid.color_x_bits_high << 8 |
        pDisplayParamBlock->primary_color_2_chromaticity.color_x_bits_low;
    pDisplayParam->primaries[1].y = pDisplayParamBlock->primary_color_2_chromaticity.color_y_bits_high << 4 |
        pDisplayParamBlock->primary_color_2_chromaticity.color_bits_mid.color_y_bits_low;
    pDisplayParam->primaries[2].x = pDisplayParamBlock->primary_color_3_chromaticity.color_bits_mid.color_x_bits_high << 8 |
        pDisplayParamBlock->primary_color_3_chromaticity.color_x_bits_low;
    pDisplayParam->primaries[2].y = pDisplayParamBlock->primary_color_3_chromaticity.color_y_bits_high << 4 |
        pDisplayParamBlock->primary_color_3_chromaticity.color_bits_mid.color_y_bits_low;
    pDisplayParam->white.x = pDisplayParamBlock->white_point_chromaticity.color_bits_mid.color_x_bits_high << 8 |
        pDisplayParamBlock->white_point_chromaticity.color_x_bits_low;
    pDisplayParam->white.y = pDisplayParamBlock->white_point_chromaticity.color_y_bits_high << 4 |
        pDisplayParamBlock->white_point_chromaticity.color_bits_mid.color_y_bits_low;

    // IEEE 754 half-precision binary floating-point format
    pDisplayParam->native_max_luminance_full_coverage = pDisplayParamBlock->max_luminance_full_coverage[1] << 8 |
        pDisplayParamBlock->max_luminance_full_coverage[0];
    pDisplayParam->native_max_luminance_10_percent_rect_coverage = pDisplayParamBlock->max_luminance_10_percent_rectangular_coverage[1] << 8 |
        pDisplayParamBlock->max_luminance_10_percent_rectangular_coverage[0];
    pDisplayParam->native_min_luminance = pDisplayParamBlock->min_luminance[1] << 8 |
        pDisplayParamBlock->min_luminance[0];

    if (pDisplayParamBlock->feature.luminance_information == 0)
    {
        pDisplayParam->native_luminance_info = NATIVE_LUMINANCE_INFO_MIN_GURANTEE_VALUE;
    }
    else if (pDisplayParamBlock->feature.luminance_information == 1)
    {
        pDisplayParam->native_luminance_info = NATIVE_LUMINANCE_INFO_SOURCE_DEVICE_GUIDANCE;
    }
    else
    {
        return NVT_STATUS_ERR;
    }

    UPDATE_BPC_FOR_COLORFORMAT(pDisplayParam->native_color_depth,
        pDisplayParamBlock->color_depth_and_device_technology.color_depth == NATIVE_COLOR_BPC_6,
        pDisplayParamBlock->color_depth_and_device_technology.color_depth == NATIVE_COLOR_BPC_8,
        pDisplayParamBlock->color_depth_and_device_technology.color_depth == NATIVE_COLOR_BPC_10,
        pDisplayParamBlock->color_depth_and_device_technology.color_depth == NATIVE_COLOR_BPC_12,
        0,
        pDisplayParamBlock->color_depth_and_device_technology.color_depth == NATIVE_COLOR_BPC_16);

    pDisplayParam->device_technology = pDisplayParamBlock->color_depth_and_device_technology.device_technology;
    if (pDisplayParam->revision == 1)
    {
        pDisplayParam->device_theme_Preference = pDisplayParamBlock->color_depth_and_device_technology.device_theme_preference;
    }
    pDisplayParam->gamma_x100 = (pDisplayParamBlock->gamma_EOTF + 100);

    return status;
}

CODE_SEGMENT(PAGE_DD_CODE)
NVT_STATUS
parseDisplayId20Timing7(
    const DISPLAYID_2_0_DATA_BLOCK_HEADER *pDataBlock,
    NVT_DISPLAYID_2_0_INFO *pDisplayIdInfo)
{
    NVT_STATUS                          status = NVT_STATUS_SUCCESS;
    const DISPLAYID_2_0_TIMING_7_BLOCK *pTiming7Block    = NULL;
    NvU32                               descriptorCount  = 0;
    NvU8                                revision         = 0;
    NvU8                                i                = 0;
    NvU8                                startSeqNumber   = 0;

    NVT_TIMING                          newTiming;

    pTiming7Block = (const DISPLAYID_2_0_TIMING_7_BLOCK *)pDataBlock;

    // Based on the DisplayID_2_0_E7 spec:
    // the Future descriptor can be defined with more than 20 Byte per descriptor without creating a new timing type
    if (pTiming7Block->header.payload_bytes_len == 0)
    {
        if (pDataBlock->data_bytes % sizeof(DISPLAYID_2_0_TIMING_7_DESCRIPTOR) != 0)
        {
            return NVT_STATUS_ERR;
        }

        revision = pTiming7Block->header.revision;
        descriptorCount = pDataBlock->data_bytes / (sizeof(DISPLAYID_2_0_TIMING_7_DESCRIPTOR) + pTiming7Block->header.payload_bytes_len);

        if (descriptorCount < 1 || descriptorCount > DISPLAYID_2_0_TIMING_7_MAX_DESCRIPTORS)
        {
            return NVT_STATUS_ERR;
        }

        if (pDisplayIdInfo != NULL)
        {
            startSeqNumber = getExistedTimingSeqNumber(pDisplayIdInfo, NVT_TYPE_DISPLAYID_7);
        }

        for (i = 0; i < descriptorCount; i++)
        {
            NVMISC_MEMSET(&newTiming, 0, sizeof(newTiming));
            if (parseDisplayId20Timing7Descriptor(&pTiming7Block->descriptors[i], &newTiming, startSeqNumber+i) == NVT_STATUS_SUCCESS)
            {
                newTiming.etc.flag |= (revision >= DISPLAYID_2_0_TYPE7_DSC_PASSTHRU_REVISION && pTiming7Block->header.dsc_passthrough == 1) ?
                                      NVT_FLAG_DISPLAYID_T7_DSC_PASSTHRU :
                                      0;

                if (revision >= DISPLAYID_2_0_TYPE7_YCC420_SUPPORT_REVISION)
                {
                    newTiming.etc.flag |= pTiming7Block->descriptors[i].options.is_preferred_or_ycc420 ? NVT_FLAG_DISPLAYID_T7_T8_EXPLICT_YUV420 : 0;

                    if (pTiming7Block->descriptors[i].options.is_preferred_or_ycc420) // YCC 420 support
                    {
                        UPDATE_BPC_FOR_COLORFORMAT(newTiming.etc.yuv420, 0, 1, 1, 1, 0, 1);
                    }
                }
                else
                {
                    newTiming.etc.flag |= pTiming7Block->descriptors[i].options.is_preferred_or_ycc420 ? NVT_FLAG_DISPLAYID_DTD_PREFERRED_TIMING : 0;
                }

                NVT_SNPRINTF((char *)newTiming.etc.name, sizeof(newTiming.etc.name), "DID20-Type7:#%2d:%dx%dx%3d.%03dHz/%s",
                                                                                     (int)NVT_GET_TIMING_STATUS_SEQ(newTiming.etc.status),
                                                                                     (int)newTiming.HVisible,
                                                                                     (int)((newTiming.interlaced ? 2 : 1)*newTiming.VVisible),
                                                                                     (int)newTiming.etc.rrx1k/1000,
                                                                                     (int)newTiming.etc.rrx1k%1000,
                                                                                     (newTiming.interlaced ? "I":"P"));
                newTiming.etc.name[sizeof(newTiming.etc.name) - 1] = '\0';
                newTiming.etc.rep = 0x1;

                if (!assignNextAvailableDisplayId20Timing(pDisplayIdInfo, &newTiming))
                {
                    break;
                }
            }
            else
            {
                if (pDisplayIdInfo == NULL) return NVT_STATUS_ERR;
            }
        }
    }

    return status;
}

CODE_SEGMENT(PAGE_DD_CODE)
NVT_STATUS
parseDisplayId20Timing8(
    const DISPLAYID_2_0_DATA_BLOCK_HEADER *pDataBlock,
    NVT_DISPLAYID_2_0_INFO *pDisplayIdInfo)
{
    NVT_STATUS  status = NVT_STATUS_SUCCESS;
    const DISPLAYID_2_0_TIMING_8_BLOCK *pTiming8Block = NULL;
    NVT_TIMING newTiming;

    NvU8       codeCount      = 0;
    NvU8       startSeqNumber = 0;
    NvU8       i;

    pTiming8Block = (const DISPLAYID_2_0_TIMING_8_BLOCK *)pDataBlock;
    codeCount = pDataBlock->data_bytes;

    if (codeCount == 0)
    {
        nvt_assert(0 && "No available byte code!");
        return NVT_STATUS_SUCCESS;
    }

    if (codeCount > DISPLAYID_2_0_TIMING_8_MAX_CODES)
    {
        nvt_assert(0 && "one byte code is out of range!");
        return NVT_STATUS_SUCCESS;
    }

    if (pDisplayIdInfo != NULL)
    {
        startSeqNumber = getExistedTimingSeqNumber(pDisplayIdInfo, NVT_TYPE_DISPLAYID_8);
    }

    for (i = 0; i < codeCount; i++)
    {
        NVMISC_MEMSET(&newTiming, 0, sizeof(newTiming));

        if (parseDisplayId20Timing8Descriptor(&pTiming8Block->timingCode, &newTiming,
                                              pTiming8Block->header.timing_code_type,
                                              pTiming8Block->header.timing_code_size, i, startSeqNumber+i) == NVT_STATUS_SUCCESS)
        {
            newTiming.etc.flag |= ((pTiming8Block->header.revision == 1) && pTiming8Block->header.is_support_yuv420) ?
                                    NVT_FLAG_DISPLAYID_T7_T8_EXPLICT_YUV420 :
                                    0;

            NVT_SNPRINTF((char *)newTiming.etc.name, sizeof(newTiming.etc.name), "DID20-Type8:#%3d:%dx%dx%3d.%03dHz/%s",
                                                                                    (int)NVT_GET_TIMING_STATUS_SEQ(newTiming.etc.status),
                                                                                    (int)newTiming.HVisible, (int)newTiming.VVisible,
                                                                                    (int)newTiming.etc.rrx1k/1000, (int)newTiming.etc.rrx1k%1000,
                                                                                    (newTiming.interlaced ? "I":"P"));
            newTiming.etc.name[sizeof(newTiming.etc.name) - 1] = '\0';
            newTiming.etc.rep = 0x1;

            if (!assignNextAvailableDisplayId20Timing(pDisplayIdInfo, &newTiming))
            {
                break;
            }
        }
    }

    return status;
}

CODE_SEGMENT(PAGE_DD_CODE)
NVT_STATUS
parseDisplayId20Timing9(
    const DISPLAYID_2_0_DATA_BLOCK_HEADER *pDataBlock,
    NVT_DISPLAYID_2_0_INFO *pDisplayIdInfo)
{
    NVT_STATUS                          status           = NVT_STATUS_SUCCESS;
    const DISPLAYID_2_0_TIMING_9_BLOCK *pTiming9Block    = NULL;
    NVT_TIMING                          newTiming;
    NvU32                               descriptorCount  = 0;
    NvU8                                startSeqNumber   = 0;
    NvU8                                i                = 0;

    descriptorCount = pDataBlock->data_bytes / sizeof(DISPLAYID_2_0_TIMING_9_DESCRIPTOR);
    if (descriptorCount < 1 || descriptorCount > DISPLAYID_2_0_TIMING_9_MAX_DESCRIPTORS)
    {
        return NVT_STATUS_ERR;
    }

    pTiming9Block = (const DISPLAYID_2_0_TIMING_9_BLOCK *)pDataBlock;

    if (pDisplayIdInfo != NULL)
    {
        startSeqNumber = getExistedTimingSeqNumber(pDisplayIdInfo, NVT_TYPE_DISPLAYID_9);
    }

    for (i = 0; i < descriptorCount; i++)
    {
        NVMISC_MEMSET(&newTiming, 0, sizeof(newTiming));

        if (parseDisplayId20Timing9Descriptor(&pTiming9Block->descriptors[i], &newTiming, startSeqNumber+i) == NVT_STATUS_SUCCESS)
        {
            if (!assignNextAvailableDisplayId20Timing(pDisplayIdInfo, &newTiming))
            {
                break;
            }
        }
        else
        {
            if (pDisplayIdInfo == NULL) return NVT_STATUS_ERR;
        }
    }

    return status;
}

CODE_SEGMENT(PAGE_DD_CODE)
NVT_STATUS
parseDisplayId20Timing10(
    const DISPLAYID_2_0_DATA_BLOCK_HEADER *pDataBlock,
    NVT_DISPLAYID_2_0_INFO *pDisplayIdInfo)
{
    NVT_STATUS  status = NVT_STATUS_SUCCESS;
    NvU32  descriptorCount       = 0;
    NvU8   startSeqNumber        = 0;
    NvU8   i                     = 0;
    NvU8   eachOfDescriptorsSize = sizeof(DISPLAYID_2_0_TIMING_10_6BYTES_DESCRIPTOR);

    const DISPLAYID_2_0_TIMING_10_BLOCK *pTiming10Block                = NULL;
    const DISPLAYID_2_0_TIMING_10_6BYTES_DESCRIPTOR *p6bytesDescriptor = NULL;

    NVT_TIMING   newTiming;

    pTiming10Block = (const DISPLAYID_2_0_TIMING_10_BLOCK *)pDataBlock;

    if (pTiming10Block->header.type != DISPLAYID_2_0_BLOCK_TYPE_TIMING_10)
    {
        return NVT_STATUS_ERR;
    }

    if (pTiming10Block->header.payload_bytes_len == DISPLAYID_2_0_TIMING_10_PAYLOAD_BYTES_6)
    {
        descriptorCount = pDataBlock->data_bytes / sizeof(DISPLAYID_2_0_TIMING_10_6BYTES_DESCRIPTOR);

        if (descriptorCount < 1 || descriptorCount > DISPLAYID_2_0_TIMING_10_MAX_6BYTES_DESCRIPTORS)
        {
            return NVT_STATUS_ERR;
        }
    }
    else if (pTiming10Block->header.payload_bytes_len == DISPLAYID_2_0_TIMING_10_PAYLOAD_BYTES_7)
    {
        descriptorCount = pDataBlock->data_bytes / sizeof(DISPLAYID_2_0_TIMING_10_7BYTES_DESCRIPTOR);

        if (descriptorCount < 1 || descriptorCount > DISPLAYID_2_0_TIMING_10_MAX_7BYTES_DESCRIPTORS)
        {
            return NVT_STATUS_ERR;
        }
    }

    eachOfDescriptorsSize += pTiming10Block->header.payload_bytes_len;

    if (pDisplayIdInfo != NULL)
    {
        startSeqNumber = getExistedTimingSeqNumber(pDisplayIdInfo, NVT_TYPE_DISPLAYID_10);
    }

    for (i = 0; i < descriptorCount; i++)
    {
        NVMISC_MEMSET(&newTiming, 0, sizeof(newTiming));
        if (NVT_STATUS_SUCCESS == parseDisplayId20Timing10Descriptor(&pTiming10Block->descriptors[i*eachOfDescriptorsSize],
                                                                     &newTiming,
                                                                     pTiming10Block->header.payload_bytes_len,
                                                                     startSeqNumber+i))
        {
            p6bytesDescriptor = (const DISPLAYID_2_0_TIMING_10_6BYTES_DESCRIPTOR *)&pTiming10Block->descriptors[i*eachOfDescriptorsSize];

            if (p6bytesDescriptor->options.ycc420_support)
            {
                UPDATE_BPC_FOR_COLORFORMAT(newTiming.etc.yuv420, 0, 1, 1, 1, 0, 1);
            }

            if (p6bytesDescriptor->options.timing_formula == DISPLAYID_2_0_TIMING_FORMULA_CVT_1_2_STANDARD)
            {
                NVT_SNPRINTF((char *)newTiming.etc.name, sizeof(newTiming.etc.name), "DID20-Type10:#%3d:%dx%dx%3d.%03dHz/%s",
                                                                                     (int)NVT_GET_TIMING_STATUS_SEQ(newTiming.etc.status),
                                                                                     (int)newTiming.HVisible,
                                                                                     (int)newTiming.VVisible,
                                                                                     (int)newTiming.etc.rrx1k/1000,
                                                                                     (int)newTiming.etc.rrx1k%1000,
                                                                                     (newTiming.interlaced ? "I":"P"));
            }
            else
            {
                NVT_SNPRINTF((char *)newTiming.etc.name, sizeof(newTiming.etc.name), "DID20-Type10RB%d:#%3d:%dx%dx%3d.%03dHz/%s",
                                                                                     p6bytesDescriptor->options.timing_formula,
                                                                                     (int)NVT_GET_TIMING_STATUS_SEQ(newTiming.etc.status),
                                                                                     (int)newTiming.HVisible,
                                                                                     (int)newTiming.VVisible,
                                                                                     (int)newTiming.etc.rrx1k/1000,
                                                                                     (int)newTiming.etc.rrx1k%1000,
                                                                                     (newTiming.interlaced ? "I":"P"));

            }
            newTiming.etc.name[sizeof(newTiming.etc.name) - 1] = '\0';
            newTiming.etc.rep = 0x1;

            if (!assignNextAvailableDisplayId20Timing(pDisplayIdInfo, &newTiming))
            {
                break;
            }
        }
        else
        {
            if (pDisplayIdInfo == NULL)
            {
                return NVT_STATUS_ERR;
            }
            continue;
        }
    }

    return status;
}

CODE_SEGMENT(PAGE_DD_CODE)
NVT_STATUS
parseDisplayId20RangeLimit(
    const DISPLAYID_2_0_DATA_BLOCK_HEADER *pDataBlock,
    NVT_DISPLAYID_2_0_INFO *pDisplayIdInfo)
{
    NVT_STATUS  status = NVT_STATUS_SUCCESS;
    const DISPLAYID_2_0_RANGE_LIMITS_BLOCK *pRangeLimitsBlock = NULL;
    NVT_DISPLAYID_RANGE_LIMITS rangeLimits = {0};

    // basic sanity check
    if (pDataBlock->data_bytes != DISPLAYID_2_0_RANGE_LIMITS_BLOCK_PAYLOAD_LENGTH)
    {
        return NVT_STATUS_ERR;
    }

    pRangeLimitsBlock = (const DISPLAYID_2_0_RANGE_LIMITS_BLOCK *)pDataBlock;

    rangeLimits.revision = pDataBlock->revision;

    rangeLimits.pclk_min = (pRangeLimitsBlock->pixel_clock_min[2] << 16 |
        pRangeLimitsBlock->pixel_clock_min[1] << 8 |
        pRangeLimitsBlock->pixel_clock_min[0]) + 1;
    rangeLimits.pclk_max = (pRangeLimitsBlock->pixel_clock_max[2] << 16 |
        pRangeLimitsBlock->pixel_clock_max[1] << 8 |
        pRangeLimitsBlock->pixel_clock_max[0]) + 1;
    rangeLimits.vfreq_min = pRangeLimitsBlock->vertical_frequency_min;
    if (rangeLimits.revision == 1)
    {
        rangeLimits.vfreq_max = pRangeLimitsBlock->dynamic_video_timing_range_support.vertical_frequency_max_9_8 << 8 |
                                  pRangeLimitsBlock->vertical_frequency_max_7_0;
    }
    else
    {
        rangeLimits.vfreq_max = pRangeLimitsBlock->vertical_frequency_max_7_0;
    }

    rangeLimits.seamless_dynamic_video_timing_change = pRangeLimitsBlock->dynamic_video_timing_range_support.seamless_dynamic_video_timing_change;

    if (pDisplayIdInfo == NULL)
    {
        if (rangeLimits.vfreq_min > rangeLimits.vfreq_max || rangeLimits.pclk_min > rangeLimits.pclk_max ||
            rangeLimits.vfreq_min == 0 || rangeLimits.vfreq_max == 0)
        {
            nvt_assert(0 && "wrong range limit");
            status = NVT_STATUS_ERR;
        }
        return status;
    }

    NVMISC_MEMCPY(&pDisplayIdInfo->range_limits, &rangeLimits, sizeof(NVT_DISPLAYID_RANGE_LIMITS));

    return status;
}

#define ADD_COLOR_SPACE_EOTF_COMBINATION(_pInterfaceFeatures, _color_space, _eotf) do {                                                        \
                    (_pInterfaceFeatures)->colorspace_eotf_combination[(_pInterfaceFeatures)->combination_count].color_space = (_color_space); \
                    (_pInterfaceFeatures)->colorspace_eotf_combination[(_pInterfaceFeatures)->combination_count].eotf        = (_eotf);        \
                    (_pInterfaceFeatures)->combination_count++;                                                                                \
                } while(0)

CODE_SEGMENT(PAGE_DD_CODE)
NVT_STATUS
parseDisplayId20DisplayInterfaceFeatures(
    const DISPLAYID_2_0_DATA_BLOCK_HEADER *pDataBlock,
    NVT_DISPLAYID_2_0_INFO *pDisplayIdInfo)
{
    NVT_STATUS  status = NVT_STATUS_SUCCESS;
    NvU32       i = 0;
    const DISPLAYID_2_0_INTERFACE_FEATURES_BLOCK *pInterfaceFeaturesBlock = NULL;
    NVT_DISPLAYID_INTERFACE_FEATURES *pInterfaceFeatures = NULL;

    if (pDataBlock->data_bytes < DISPLAYID_2_0_INTERFACE_FEATURES_BLOCK_PAYLOAD_LENGTH_MIN)
    {
        return NVT_STATUS_ERR;
    }

    // Add more validation here if needed.

    if (pDisplayIdInfo == NULL) return status;

    pInterfaceFeatures = &pDisplayIdInfo->interface_features;

    pInterfaceFeaturesBlock = (const DISPLAYID_2_0_INTERFACE_FEATURES_BLOCK *)pDataBlock;
    pInterfaceFeatures->revision = pDataBlock->revision;

    UPDATE_BPC_FOR_COLORFORMAT(pInterfaceFeatures->rgb444,
        pInterfaceFeaturesBlock->interface_color_depth_rgb.bit_per_primary_6,
        pInterfaceFeaturesBlock->interface_color_depth_rgb.bit_per_primary_8,
        pInterfaceFeaturesBlock->interface_color_depth_rgb.bit_per_primary_10,
        pInterfaceFeaturesBlock->interface_color_depth_rgb.bit_per_primary_12,
        pInterfaceFeaturesBlock->interface_color_depth_rgb.bit_per_primary_14,
        pInterfaceFeaturesBlock->interface_color_depth_rgb.bit_per_primary_16);
    UPDATE_BPC_FOR_COLORFORMAT(pInterfaceFeatures->yuv444,
        pInterfaceFeaturesBlock->interface_color_depth_ycbcr444.bit_per_primary_6,
        pInterfaceFeaturesBlock->interface_color_depth_ycbcr444.bit_per_primary_8,
        pInterfaceFeaturesBlock->interface_color_depth_ycbcr444.bit_per_primary_10,
        pInterfaceFeaturesBlock->interface_color_depth_ycbcr444.bit_per_primary_12,
        pInterfaceFeaturesBlock->interface_color_depth_ycbcr444.bit_per_primary_14,
        pInterfaceFeaturesBlock->interface_color_depth_ycbcr444.bit_per_primary_16);
    UPDATE_BPC_FOR_COLORFORMAT(pInterfaceFeatures->yuv422,
        0,
        pInterfaceFeaturesBlock->interface_color_depth_ycbcr422.bit_per_primary_8,
        pInterfaceFeaturesBlock->interface_color_depth_ycbcr422.bit_per_primary_10,
        pInterfaceFeaturesBlock->interface_color_depth_ycbcr422.bit_per_primary_12,
        pInterfaceFeaturesBlock->interface_color_depth_ycbcr422.bit_per_primary_14,
        pInterfaceFeaturesBlock->interface_color_depth_ycbcr422.bit_per_primary_16);
    UPDATE_BPC_FOR_COLORFORMAT(pInterfaceFeatures->yuv420,
        0,
        pInterfaceFeaturesBlock->interface_color_depth_ycbcr420.bit_per_primary_8,
        pInterfaceFeaturesBlock->interface_color_depth_ycbcr420.bit_per_primary_10,
        pInterfaceFeaturesBlock->interface_color_depth_ycbcr420.bit_per_primary_12,
        pInterfaceFeaturesBlock->interface_color_depth_ycbcr420.bit_per_primary_14,
        pInterfaceFeaturesBlock->interface_color_depth_ycbcr420.bit_per_primary_16);

    // * 74.25MP/s
    pInterfaceFeatures->yuv420_min_pclk = pInterfaceFeaturesBlock->min_pixel_rate_ycbcr420 *
                                            7425;

    pInterfaceFeatures->audio_capability.support_48khz =
        pInterfaceFeaturesBlock->audio_capability.sample_rate_48_khz;
    pInterfaceFeatures->audio_capability.support_44_1khz =
        pInterfaceFeaturesBlock->audio_capability.sample_rate_44_1_khz;
    pInterfaceFeatures->audio_capability.support_32khz =
        pInterfaceFeaturesBlock->audio_capability.sample_rate_32_khz;

    if (pInterfaceFeaturesBlock->color_space_and_eotf_1.color_space_srgb_eotf_srgb)
    {
        ADD_COLOR_SPACE_EOTF_COMBINATION(pInterfaceFeatures,
            INTERFACE_COLOR_SPACE_SRGB,
            INTERFACE_EOTF_SRGB);
    }
    if (pInterfaceFeaturesBlock->color_space_and_eotf_1.color_space_bt601_eotf_bt601)
    {
        ADD_COLOR_SPACE_EOTF_COMBINATION(pInterfaceFeatures,
            INTERFACE_COLOR_SPACE_BT601,
            INTERFACE_EOTF_BT601);
    }
    if (pInterfaceFeaturesBlock->color_space_and_eotf_1.color_space_bt709_eotf_bt1886)
    {
        ADD_COLOR_SPACE_EOTF_COMBINATION(pInterfaceFeatures,
            INTERFACE_COLOR_SPACE_BT709,
            INTERFACE_EOTF_BT1886);
    }
    if (pInterfaceFeaturesBlock->color_space_and_eotf_1.color_space_adobe_rgb_eotf_adobe_rgb)
    {
        ADD_COLOR_SPACE_EOTF_COMBINATION(pInterfaceFeatures,
            INTERFACE_COLOR_SPACE_ADOBE_RGB,
            INTERFACE_EOTF_ADOBE_RGB);
    }
    if (pInterfaceFeaturesBlock->color_space_and_eotf_1.color_space_dci_p3_eotf_dci_p3)
    {
        ADD_COLOR_SPACE_EOTF_COMBINATION(pInterfaceFeatures,
            INTERFACE_COLOR_SPACE_DCI_P3,
            INTERFACE_EOTF_DCI_P3);
    }
    if (pInterfaceFeaturesBlock->color_space_and_eotf_1.color_space_bt2020_eotf_bt2020)
    {
        ADD_COLOR_SPACE_EOTF_COMBINATION(pInterfaceFeatures,
            INTERFACE_COLOR_SPACE_BT2020,
            INTERFACE_EOTF_BT2020);
    }
    if (pInterfaceFeaturesBlock->color_space_and_eotf_1.color_space_bt2020_eotf_smpte_st2084)
    {
        ADD_COLOR_SPACE_EOTF_COMBINATION(pInterfaceFeatures,
            INTERFACE_COLOR_SPACE_BT2020,
            INTERFACE_EOTF_SMPTE_ST2084);
    }

    for (i = 0; i < pInterfaceFeaturesBlock->additional_color_space_and_eotf_count.count; i++)
    {
        ADD_COLOR_SPACE_EOTF_COMBINATION(pInterfaceFeatures,
            pInterfaceFeaturesBlock->additional_color_space_and_eotf[i].color_space,
            pInterfaceFeaturesBlock->additional_color_space_and_eotf[i].eotf);
    }

    return status;
}

CODE_SEGMENT(PAGE_DD_CODE)
NVT_STATUS
parseDisplayId20Stereo(
    const DISPLAYID_2_0_DATA_BLOCK_HEADER *pDataBlock,
    NVT_DISPLAYID_2_0_INFO *pDisplayIdInfo)
{
    NVT_STATUS  status = NVT_STATUS_SUCCESS;

    if (pDisplayIdInfo == NULL) return status;

    // TODO: Implement the parsing here.

    return status;
}

CODE_SEGMENT(PAGE_DD_CODE)
NVT_STATUS
parseDisplayId20TiledDisplay(
    const DISPLAYID_2_0_DATA_BLOCK_HEADER *pDataBlock,
    NVT_DISPLAYID_2_0_INFO *pDisplayIdInfo)
{
    NVT_STATUS  status = NVT_STATUS_SUCCESS;
    const DISPLAYID_2_0_TILED_DISPLAY_BLOCK *pTiledDisplayBlock = NULL;
    NVT_DISPLAYID_TILED_DISPLAY_TOPOLOGY *pTileTopo = NULL;

    if (pDataBlock->data_bytes != DISPLAYID_2_0_TILED_DISPLAY_BLOCK_PAYLOAD_LENGTH)
    {
        return NVT_STATUS_ERR;
    }

    if (pDisplayIdInfo == NULL) return status;

    pTiledDisplayBlock = (const DISPLAYID_2_0_TILED_DISPLAY_BLOCK *)pDataBlock;
    pTileTopo = &pDisplayIdInfo->tile_topo;

    pTileTopo->revision = pDataBlock->revision;

    pTileTopo->capability.bSingleEnclosure = pTiledDisplayBlock->capability.single_enclosure;
    pTileTopo->capability.bHasBezelInfo = pTiledDisplayBlock->capability.has_bezel_info;
    pTileTopo->capability.multi_tile_behavior = pTiledDisplayBlock->capability.multi_tile_behavior;
    pTileTopo->capability.single_tile_behavior = pTiledDisplayBlock->capability.single_tile_behavior;

    pTileTopo->topology.row = ((pTiledDisplayBlock->topo_loc_high.row << 5) |
        (pTiledDisplayBlock->topo_low.row)) + 1;
    pTileTopo->topology.col = ((pTiledDisplayBlock->topo_loc_high.col << 5) |
        (pTiledDisplayBlock->topo_low.col)) + 1;
    pTileTopo->location.x = ((pTiledDisplayBlock->topo_loc_high.x << 5) |
        (pTiledDisplayBlock->loc_low.x));
    pTileTopo->location.y = ((pTiledDisplayBlock->topo_loc_high.y << 5) |
        (pTiledDisplayBlock->loc_low.y));

    pTileTopo->native_resolution.width = ((pTiledDisplayBlock->native_resolution.width_high << 8) |
        pTiledDisplayBlock->native_resolution.width_low) + 1;
    pTileTopo->native_resolution.height = ((pTiledDisplayBlock->native_resolution.height_high << 8) |
        pTiledDisplayBlock->native_resolution.height_low) + 1;

    pTileTopo->bezel_info.top = (pTiledDisplayBlock->bezel_info.top *
        pTiledDisplayBlock->bezel_info.pixel_density) / 10;
    pTileTopo->bezel_info.bottom = (pTiledDisplayBlock->bezel_info.bottom *
        pTiledDisplayBlock->bezel_info.pixel_density) / 10;
    pTileTopo->bezel_info.right = (pTiledDisplayBlock->bezel_info.right *
        pTiledDisplayBlock->bezel_info.pixel_density) / 10;
    pTileTopo->bezel_info.left = (pTiledDisplayBlock->bezel_info.left *
        pTiledDisplayBlock->bezel_info.pixel_density) / 10;

    pTileTopo->tile_topology_id.vendor_id = pTiledDisplayBlock->topo_id.vendor_id[0] << 16 |
        pTiledDisplayBlock->topo_id.vendor_id[1] << 8 |
        pTiledDisplayBlock->topo_id.vendor_id[2];
    pTileTopo->tile_topology_id.product_id = pTiledDisplayBlock->topo_id.product_id[1] << 8 |
        pTiledDisplayBlock->topo_id.product_id[0];
    pTileTopo->tile_topology_id.serial_number = pTiledDisplayBlock->topo_id.serial_number[3] << 24 |
        pTiledDisplayBlock->topo_id.serial_number[2] << 16 |
        pTiledDisplayBlock->topo_id.serial_number[1] << 8 |
        pTiledDisplayBlock->topo_id.serial_number[0];

    return status;
}

CODE_SEGMENT(PAGE_DD_CODE)
NVT_STATUS
parseDisplayId20ContainerId(
    const DISPLAYID_2_0_DATA_BLOCK_HEADER *pDataBlock,
    NVT_DISPLAYID_2_0_INFO *pDisplayIdInfo)
{
    NVT_STATUS status = NVT_STATUS_SUCCESS;
    const DISPLAYID_2_0_CONTAINERID_BLOCK  *pContainerIdBlock = NULL;
    NVT_DISPLAYID_CONTAINERID *pContainerId = NULL;

    if (pDataBlock->data_bytes != DISPLAYID_2_0_CONTAINERID_BLOCK_PAYLOAD_LENGTH)
    {
        return NVT_STATUS_ERR;
    }

    if (pDisplayIdInfo == NULL) return status;

    pContainerIdBlock = (const DISPLAYID_2_0_CONTAINERID_BLOCK *)pDataBlock;
    pContainerId      = &pDisplayIdInfo->container_id;

    pContainerId->revision = pDataBlock->revision;
    pContainerId->data1 = pContainerIdBlock->container_id[0] << 24 |
        pContainerIdBlock->container_id[1] << 16 |
        pContainerIdBlock->container_id[2] << 8 |
        pContainerIdBlock->container_id[3];
    pContainerId->data2 = pContainerIdBlock->container_id[4] << 8 |
        pContainerIdBlock->container_id[5];
    pContainerId->data3 = pContainerIdBlock->container_id[6] << 8 |
        pContainerIdBlock->container_id[7];
    pContainerId->data4 = pContainerIdBlock->container_id[8] << 8 |
        pContainerIdBlock->container_id[9];
    pContainerId->data5[0] = pContainerIdBlock->container_id[10];
    pContainerId->data5[1] = pContainerIdBlock->container_id[11];
    pContainerId->data5[2] = pContainerIdBlock->container_id[12];
    pContainerId->data5[3] = pContainerIdBlock->container_id[13];
    pContainerId->data5[4] = pContainerIdBlock->container_id[14];
    pContainerId->data5[5] = pContainerIdBlock->container_id[15];

    return status;
}

CODE_SEGMENT(PAGE_DD_CODE)
NVT_STATUS
parseDisplayId20AdaptiveSync(
    const DISPLAYID_2_0_DATA_BLOCK_HEADER *pDataBlock,
    NVT_DISPLAYID_2_0_INFO *pDisplayIdInfo)
{
    NVT_STATUS                               status              = NVT_STATUS_SUCCESS;
    const DISPLAYID_2_0_ADAPTIVE_SYNC_BLOCK *pAdaptiveSyncBlock  = NULL;
    NvU32                                    descriptorCnt       = 0;
    NvU8                                     i                   = 0;
    NvU8                                     minRR               = 0;
    NvU16                                    maxRR               = 0;

    pAdaptiveSyncBlock = (const DISPLAYID_2_0_ADAPTIVE_SYNC_BLOCK *)pDataBlock;

    if (pAdaptiveSyncBlock->header.payload_bytes_adaptive_sync_len == 0)
    {
        // Sanity check
        if (pDataBlock->data_bytes % sizeof(DISPLAYID_2_0_ADAPTIVE_SYNC_DESCRIPTOR) != 0)
        {
            status = NVT_STATUS_ERR;
        }

        descriptorCnt = pDataBlock->data_bytes / sizeof(DISPLAYID_2_0_ADAPTIVE_SYNC_DESCRIPTOR);

        if (descriptorCnt < 1) return NVT_STATUS_ERR;

        if (pDisplayIdInfo == NULL)
        {
            for (i = 0; i < descriptorCnt; i++)
            {
                minRR = pAdaptiveSyncBlock->descriptors[i].min_refresh_rate;
                maxRR = (pAdaptiveSyncBlock->descriptors[i].max_refresh_rate.max_rr_9_8 << 8 |
                         pAdaptiveSyncBlock->descriptors[i].max_refresh_rate.max_rr_7_0) + 1;
                if (minRR > (maxRR + 1) || minRR == 0 || maxRR == 0)
                {
                    status = NVT_STATUS_ERR;
                }
            }
            return status;
        }

        pDisplayIdInfo->total_adaptive_sync_descriptor = descriptorCnt;

        for (i = 0; i < descriptorCnt; i++)
        {
            // Byte 0 Adaptive-Sync Operation and Range Information
            pDisplayIdInfo->adaptive_sync_descriptor[i].u.information.adaptive_sync_range       = pAdaptiveSyncBlock->descriptors[i].operation_range_info.range;
            pDisplayIdInfo->adaptive_sync_descriptor[i].u.information.duration_inc_flicker_perf = pAdaptiveSyncBlock->descriptors[i].operation_range_info.successive_frame_inc_tolerance;
            pDisplayIdInfo->adaptive_sync_descriptor[i].u.information.modes                     = pAdaptiveSyncBlock->descriptors[i].operation_range_info.modes;
            pDisplayIdInfo->adaptive_sync_descriptor[i].u.information.seamless_not_support      = pAdaptiveSyncBlock->descriptors[i].operation_range_info.seamless_transition_not_support;
            pDisplayIdInfo->adaptive_sync_descriptor[i].u.information.duration_dec_flicker_perf = pAdaptiveSyncBlock->descriptors[i].operation_range_info.successive_frame_dec_tolerance;

            // 6.2 format (six integer bits and two fractional bits) a value range of 0.00 to 63.75ms
            pDisplayIdInfo->adaptive_sync_descriptor[i].max_duration_inc = pAdaptiveSyncBlock->descriptors[i].max_single_frame_inc;
            pDisplayIdInfo->adaptive_sync_descriptor[i].min_rr           = pAdaptiveSyncBlock->descriptors[i].min_refresh_rate;
            pDisplayIdInfo->adaptive_sync_descriptor[i].max_rr           = (pAdaptiveSyncBlock->descriptors[i].max_refresh_rate.max_rr_9_8 << 8 |
                                                                            pAdaptiveSyncBlock->descriptors[i].max_refresh_rate.max_rr_7_0) + 1;
            // 6.2 format (six integer bits and two fractional bits) a value range of 0.00 to 63.75ms
            pDisplayIdInfo->adaptive_sync_descriptor[i].max_duration_dec = pAdaptiveSyncBlock->descriptors[i].max_single_frame_dec;
        }
    }
    else // all other values are RESERVED.
    {
        if (pDisplayIdInfo == NULL) return NVT_STATUS_ERR;
    }

    return status;
}

CODE_SEGMENT(PAGE_DD_CODE)
NVT_STATUS
parseDisplayId20ARVRHMD(
    const DISPLAYID_2_0_DATA_BLOCK_HEADER *pDataBlock,
    NVT_DISPLAYID_2_0_INFO *pDisplayIdInfo)
{
    NVT_STATUS  status = NVT_STATUS_SUCCESS;

    if (pDisplayIdInfo == NULL) return status;

    // TODO: Implement the parsing here.

    return status;
}

CODE_SEGMENT(PAGE_DD_CODE)
NVT_STATUS
parseDisplayId20ARVRLayer(
    const DISPLAYID_2_0_DATA_BLOCK_HEADER *pDataBlock,
    NVT_DISPLAYID_2_0_INFO *pDisplayIdInfo)
{
    NVT_STATUS  status = NVT_STATUS_SUCCESS;

    if (pDisplayIdInfo == NULL) return status;

    // TODO: Implement the parsing here.

    return status;
}

CODE_SEGMENT(PAGE_DD_CODE)
NVT_STATUS
parseDisplayId20BrightnessLuminanceRange(
    const DISPLAYID_2_0_DATA_BLOCK_HEADER *pDataBlock,
    NVT_DISPLAYID_2_0_INFO *pDisplayIdInfo)
{
    NVT_STATUS status = NVT_STATUS_SUCCESS;
    const DISPLAYID_2_0_BRIGHTNESS_LUMINANCE_RANGE_BLOCK  *pBrightnessLuminanceRangeBlock = NULL;
    NVT_DISPLAYID_BRIGHTNESS_LUMINANCE_RANGE *pluminanceRanges = NULL;

    if ((pDataBlock == NULL) || pDataBlock->data_bytes != DISPLAYID_2_0_BRIGHTNESS_LUMINANCE_RANGE_BLOCK_PAYLOAD_LENGTH)
    {
        return NVT_STATUS_ERR;
    }

    if (pDisplayIdInfo == NULL) return status;

    pBrightnessLuminanceRangeBlock = (const DISPLAYID_2_0_BRIGHTNESS_LUMINANCE_RANGE_BLOCK *)pDataBlock;
    pluminanceRanges      = &pDisplayIdInfo->luminance_ranges;

    pluminanceRanges->revision                = pDataBlock->revision;
    pluminanceRanges->min_sdr_luminance       = pBrightnessLuminanceRangeBlock->min_sdr_luminance;
    pluminanceRanges->max_sdr_luminance       = pBrightnessLuminanceRangeBlock->max_sdr_luminance;
    pluminanceRanges->max_boost_sdr_luminance = pBrightnessLuminanceRangeBlock->max_boost_sdr_luminance;

    return status;
}

CODE_SEGMENT(PAGE_DD_CODE)
NVT_STATUS
parseDisplayId20VendorSpecific(
    const DISPLAYID_2_0_DATA_BLOCK_HEADER *pDataBlock,
    NVT_DISPLAYID_2_0_INFO *pDisplayIdInfo)
{
    NVT_STATUS status = NVT_STATUS_SUCCESS;
    const DISPLAYID_2_0_VENDOR_SPECIFIC_BLOCK *block = NULL;
    NVT_DISPLAYID_VENDOR_SPECIFIC *pVendorSpecific = NULL;
    NvU32 ieee_oui = 0;

    // Add more validation here if needed

    if (pDisplayIdInfo == NULL) return status;

    block = (const DISPLAYID_2_0_VENDOR_SPECIFIC_BLOCK*)pDataBlock;
    pVendorSpecific =  &pDisplayIdInfo->vendor_specific;

    ieee_oui = (NvU32)((block->vendor_id[0] << 16) |
                        (block->vendor_id[1] << 8)  |
                        (block->vendor_id[2]));

    switch (ieee_oui)
    {
    case NVT_VESA_VENDOR_SPECIFIC_IEEE_ID:
        // TODO: below parser shall be updated if DID21 changed in the future
        if (pDataBlock->data_bytes == NVT_VESA_VENDOR_SPECIFIC_LENGTH)
        {
            pVendorSpecific->vesaVsdb.data_struct_type.type =
                block->vendor_specific_data[3] & NVT_VESA_ORG_VSDB_DATA_TYPE_MASK;
            pVendorSpecific->vesaVsdb.data_struct_type.color_space_and_eotf =
                (block->vendor_specific_data[3] & NVT_VESA_ORG_VSDB_COLOR_SPACE_AND_EOTF_MASK) >> NVT_VESA_ORG_VSDB_COLOR_SPACE_AND_EOTF_SHIFT;
            pVendorSpecific->vesaVsdb.overlapping.pixels_overlapping_count =
                block->vendor_specific_data[4] & NVT_VESA_ORG_VSDB_PIXELS_OVERLAPPING_MASK;
            pVendorSpecific->vesaVsdb.overlapping.multi_sst =
                (block->vendor_specific_data[4] & NVT_VESA_ORG_VSDB_MULTI_SST_MODE_MASK) >> NVT_VESA_ORG_VSDB_MULTI_SST_MODE_SHIFT;
            pVendorSpecific->vesaVsdb.pass_through_integer.pass_through_integer_dsc =
                block->vendor_specific_data[5] & NVT_VESA_ORG_VSDB_PASS_THROUGH_INTEGER_MASK;
            pVendorSpecific->vesaVsdb.pass_through_fractional.pass_through_fraction_dsc =
                block->vendor_specific_data[6] & NVT_VESA_ORG_VSDB_PASS_THROUGH_FRACTIOINAL_MASK;
        }
        else
        {
            status = NVT_STATUS_ERR;
        }
    break;

    default:
    break;
    }

    return status;
}

CODE_SEGMENT(PAGE_DD_CODE)
NVT_STATUS
parseDisplayId20CtaData(
    const DISPLAYID_2_0_DATA_BLOCK_HEADER *pDataBlock,
    NVT_DISPLAYID_2_0_INFO *pDisplayIdInfo)
{
    NVT_STATUS status = NVT_STATUS_SUCCESS;

    NVT_EDID_CEA861_INFO *p861Info = NULL;
    const DISPLAYID_2_0_CTA_BLOCK * ctaBlock = NULL;
    NvU8 *pcta_data = NULL;

    ctaBlock  = (const DISPLAYID_2_0_CTA_BLOCK *)pDataBlock;

    // WAR here to add a (size_t) cast for casting member from const to non-const in order to avoid Linux old compiler failed in DVS.
    pcta_data = (NvU8 *)(size_t)ctaBlock->cta_data;

    if (pDisplayIdInfo == NULL)
    {
        status = parseCta861DataBlockInfo(pcta_data, pDataBlock->data_bytes, NULL);
        return status;
    }
    else
    {
        status = parseCta861DataBlockInfo(pcta_data, pDataBlock->data_bytes, &pDisplayIdInfo->cta.cta861_info);
    }

    if (status != NVT_STATUS_SUCCESS)
    {
        return status;
    }

    p861Info = &(pDisplayIdInfo->cta.cta861_info);

    parseCta861VsdbBlocks(p861Info, pDisplayIdInfo, FROM_DISPLAYID_20_DATA_BLOCK);
    parseCta861VsvdbBlocks(p861Info, pDisplayIdInfo, FROM_DISPLAYID_20_DATA_BLOCK);

    parseCta861HfScdb(p861Info, pDisplayIdInfo, FROM_DISPLAYID_20_DATA_BLOCK);
    // This CTA 861 function to parse 861 part
    parse861bShortTiming(p861Info, pDisplayIdInfo, FROM_DISPLAYID_20_DATA_BLOCK);
    // yuv420-only video
    parse861bShortYuv420Timing(p861Info, pDisplayIdInfo, FROM_DISPLAYID_20_DATA_BLOCK);

    if (pDisplayIdInfo->cta.cta861_info.valid.hdr_static_metadata != 0)
    {
        parseCta861HdrStaticMetadataDataBlock(p861Info, pDisplayIdInfo, FROM_DISPLAYID_20_DATA_BLOCK);
    }

    // CEA861-F at 7.5.12 section about VFPDB block.
    if (p861Info->total_svr > 0)
    {
        parseCta861NativeOrPreferredTiming(p861Info, pDisplayIdInfo, FROM_DISPLAYID_20_DATA_BLOCK);
    }

    return status;
}

// Helper function
CODE_SEGMENT(PAGE_DD_CODE)
static NvU32
greatestCommonDenominator(
    NvU32 x,
    NvU32 y)
{
    NvU32 g = 0;

    while (x > 0)
    {
        g = x;
        x = y % x;
        y = g;
    }
    return g;
}

CODE_SEGMENT(PAGE_DD_CODE)
static NVT_STATUS
getPrimaryUseCase(
    NvU8 product_type,
    NVT_DISPLAYID_PRODUCT_PRIMARY_USE_CASE *primary_use_case)
{
    NVT_STATUS status = NVT_STATUS_SUCCESS;

    switch (product_type)
    {
    case DISPLAYID_2_0_PROD_TEST:
        *primary_use_case = PRODUCT_PRIMARY_USE_TEST_EQUIPMENT;
        break;
    case DISPLAYID_2_0_PROD_GENERIC_DISPLAY:
        *primary_use_case = PRODUCT_PRIMARY_USE_GENERIC_DISPLAY;
        break;
    case DISPLAYID_2_0_PROD_TELEVISION:
        *primary_use_case = PRODUCT_PRIMARY_USE_TELEVISION;
        break;
    case DISPLAYID_2_0_PROD_DESKTOP_PRODUCTIVITY_DISPLAY:
        *primary_use_case = PRODUCT_PRIMARY_USE_DESKTOP_PRODUCTIVITY;
        break;
    case DISPLAYID_2_0_PROD_DESKTOP_GAMING_DISPLAY:
        *primary_use_case = PRODUCT_PRIMARY_USE_DESKTOP_GAMING;
        break;
    case DISPLAYID_2_0_PROD_PRESENTATION_DISPLAY:
        *primary_use_case = PRODUCT_PRIMARY_USE_PRESENTATION;
        break;
    case DISPLAYID_2_0_PROD_HMD_VR:
        *primary_use_case = PRODUCT_PRIMARY_USE_HEAD_MOUNT_VIRTUAL_REALITY;
        break;
    case DISPLAYID_2_0_PROD_HMD_AR:
        *primary_use_case = PRODUCT_PRIMARY_USE_HEAD_MOUNT_AUGMENTED_REALITY;
        break;
    case DISPLAYID_2_0_PROD_EXTENSION:
        status = NVT_STATUS_ERR;
        break;
    default:
        status = NVT_STATUS_ERR;
    }

    return status;
}

// used in DID20 and DID20ext
CODE_SEGMENT(PAGE_DD_CODE)
NvU8
computeDisplayId20SectionCheckSum(
    const NvU8 *pSectionBytes,
    NvU32 length)
{

    NvU32 i = 0;
    NvU32 checkSum = 0;

    // Each DisplayID section composed of five mandatory bytes:
    // DisplayID Structure Version and Revision
    // Section Size
    // Product Primary Use Case
    // Extension Count
    // Checksum
    for (i = 0, checkSum = 0; i < length; i++)
    {
        checkSum += pSectionBytes[i];
    }

    return (checkSum & 0xFF);
}

CODE_SEGMENT(PAGE_DD_CODE)
NvBool
assignNextAvailableDisplayId20Timing(
    NVT_DISPLAYID_2_0_INFO *pDisplayIdInfo,
    const NVT_TIMING *pTiming)
{
    if (pDisplayIdInfo == NULL) return NV_TRUE;

    if (pDisplayIdInfo->total_timings >= COUNT(pDisplayIdInfo->timing))
    {
        return NV_FALSE;
    }

    pDisplayIdInfo->timing[pDisplayIdInfo->total_timings] = *pTiming;
    pDisplayIdInfo->total_timings++;

    return NV_TRUE;
}

CODE_SEGMENT(PAGE_DD_CODE)
NVT_STATUS
parseDisplayId20Timing7Descriptor(
    const void *pVoidDescriptor,
    NVT_TIMING *pTiming,
    NvU8 count)
{
    NVT_STATUS  status = NVT_STATUS_SUCCESS;
    NvU32       gdc = 0;

    const DISPLAYID_2_0_TIMING_7_DESCRIPTOR *pDescriptor = NULL;

    pDescriptor = (const DISPLAYID_2_0_TIMING_7_DESCRIPTOR *)pVoidDescriptor;

    // pclk is in 10Khz
    // pixel_clock is in kHz
    pTiming->pclk = ((pDescriptor->pixel_clock[2] << 16 |
        pDescriptor->pixel_clock[1] << 8 |
        pDescriptor->pixel_clock[0]) + 1) / 10;

    pTiming->HBorder = 0;
    pTiming->VBorder = 0;

    pTiming->HVisible = ((pDescriptor->horizontal.active_image_pixels[1] << 8) |
        (pDescriptor->horizontal.active_image_pixels[0])) + 1;
    pTiming->VVisible = ((pDescriptor->vertical.active_image_lines[1] << 8) |
        (pDescriptor->vertical.active_image_lines[0])) + 1;

    pTiming->HTotal = (((pDescriptor->horizontal.blank_pixels[1] << 8) |
        (pDescriptor->horizontal.blank_pixels[0])) + 1) +
        pTiming->HVisible;
    pTiming->VTotal = (((pDescriptor->vertical.blank_lines[1] << 8) |
        (pDescriptor->vertical.blank_lines[0])) + 1) +
        pTiming->VVisible;

    pTiming->HFrontPorch = ((pDescriptor->horizontal.front_porch_pixels_high << 8) |
        (pDescriptor->horizontal.front_porch_pixels_low)) + 1;
    pTiming->VFrontPorch = ((pDescriptor->vertical.front_porch_lines_high << 8) |
        (pDescriptor->vertical.front_porch_lines_low)) + 1;

    pTiming->HSyncWidth = ((pDescriptor->horizontal.sync_width_pixels[1] << 8) |
        (pDescriptor->horizontal.sync_width_pixels[0])) + 1;
    pTiming->VSyncWidth = ((pDescriptor->vertical.sync_width_lines[1] << 8) |
        (pDescriptor->vertical.sync_width_lines[0])) + 1;

    pTiming->HSyncPol = pDescriptor->horizontal.sync_polarity ? NVT_H_SYNC_POSITIVE :
        NVT_H_SYNC_NEGATIVE;
    pTiming->VSyncPol = pDescriptor->vertical.sync_polarity ? NVT_V_SYNC_POSITIVE :
        NVT_V_SYNC_NEGATIVE;

    // EDID used in DP1.4 Compliance test had incorrect HBlank listed, leading to wrong raster sizes being set by driver (bug 2714607)
    // Filter incorrect timings here. HTotal must cover sufficient blanking time
    if (pTiming->HTotal < (pTiming->HVisible + pTiming->HFrontPorch + pTiming->HSyncWidth))
    {
        return NVT_STATUS_ERR;
    }

    pTiming->interlaced = pDescriptor->options.interface_frame_scanning_type;

    switch (pDescriptor->options.aspect_ratio)
    {
    case DISPLAYID_2_0_TIMING_ASPECT_RATIO_1_1:
        pTiming->etc.aspect = (1 << 16) | 1;
        break;
    case DISPLAYID_2_0_TIMING_ASPECT_RATIO_5_4:
        pTiming->etc.aspect = (5 << 16) | 4;
        break;
    case DISPLAYID_2_0_TIMING_ASPECT_RATIO_4_3:
        pTiming->etc.aspect = (4 << 16) | 3;
        break;
    case DISPLAYID_2_0_TIMING_ASPECT_RATIO_15_9:
        pTiming->etc.aspect = (15 << 16) | 9;
        break;
    case DISPLAYID_2_0_TIMING_ASPECT_RATIO_16_9:
        pTiming->etc.aspect = (16 << 16) | 9;
        break;
    case DISPLAYID_2_0_TIMING_ASPECT_RATIO_16_10:
        pTiming->etc.aspect = (16 << 16) | 10;
        break;
    case DISPLAYID_2_0_TIMING_ASPECT_RATIO_64_27:
        pTiming->etc.aspect = (64 << 16) | 27;
        break;
    case DISPLAYID_2_0_TIMING_ASPECT_RATIO_256_135:
        pTiming->etc.aspect = (256 << 16) | 135;
        break;
    case DISPLAYID_2_0_TIMING_ASPECT_RATIO_CALCULATE:
        gdc = greatestCommonDenominator(pTiming->HVisible, pTiming->VVisible);
        if (gdc != 0)
        {
            pTiming->etc.aspect = ((pTiming->HVisible / gdc) << 16) |
                (pTiming->VVisible / gdc);
        }
        else
        {
            pTiming->etc.aspect = 0;
        }
        break;
    default:
        pTiming->etc.aspect = 0;
    }

    pTiming->etc.rr = NvTiming_CalcRR(pTiming->pclk,
        pTiming->interlaced,
        pTiming->HTotal,
        pTiming->VTotal);
    pTiming->etc.rrx1k = NvTiming_CalcRRx1k(pTiming->pclk,
        pTiming->interlaced,
        pTiming->HTotal,
        pTiming->VTotal);

    pTiming->etc.status = NVT_STATUS_DISPLAYID_7N(++count);

    return status;
}

CODE_SEGMENT(PAGE_DD_CODE)
NVT_STATUS
parseDisplayId20Timing8Descriptor(
    const void *pVoidDescriptor,
    NVT_TIMING *pTiming,
    NvU8 codeType,
    NvU8 codeSize,
    NvU8 idx,
    NvU8 count)
{
    NVT_STATUS  status = NVT_STATUS_SUCCESS;

    const NvU8 *pTimingCode = (const NvU8 *)pVoidDescriptor;
    const NvU16 *pTiming2ByteCode = (const NvU16 *)pVoidDescriptor;

    if (codeSize == DISPLAYID_2_0_TIMING_CODE_SIZE_1_BYTE)
    {
        switch (codeType)
        {
        case DISPLAYID_2_0_TIMING_CODE_DMT: //single-byte DMT ID Codes
            status = NvTiming_EnumDMT((NvU32)(pTimingCode[idx]), pTiming);
        break;
        case DISPLAYID_2_0_TIMING_CODE_CTA_VIC:
            status = NvTiming_EnumCEA861bTiming((NvU32)(pTimingCode[idx]), pTiming);
        break;
        case DISPLAYID_2_0_TIMING_CODE_HDMI_VIC:
            status = NvTiming_EnumHdmiVsdbExtendedTiming((NvU32)(pTimingCode[idx]), pTiming);
        break;
        default:
        {
            nvt_assert(0 && "RESERVED timing code type");
            status = NVT_STATUS_ERR;
        }
        break;
        }
    }
    else if (codeSize == DISPLAYID_2_0_TIMING_CODE_SIZE_2_BYTE)
    {
        // Standard two-byte codes
        if (codeType == DISPLAYID_2_0_TIMING_CODE_DMT)
        {
            status = NvTiming_EnumStdTwoBytesCode((NvU16)pTiming2ByteCode[idx], pTiming);
        }
    }

    if (status == NVT_STATUS_SUCCESS)
    {
        pTiming->etc.status = NVT_STATUS_DISPLAYID_8N(++count);
        return status;
    }

    return status;
}

CODE_SEGMENT(PAGE_DD_CODE)
NVT_STATUS
parseDisplayId20Timing9Descriptor(
    const void *pVoidDescriptor,
    NVT_TIMING *pTiming,
    NvU8 count)
{
    NVT_STATUS  status = NVT_STATUS_SUCCESS;
    const DISPLAYID_2_0_TIMING_9_DESCRIPTOR* pDescriptor = NULL;
    NvU32       width  = 0;
    NvU32       height = 0;
    NvU32       rr     = 0;

    pDescriptor = (const DISPLAYID_2_0_TIMING_9_DESCRIPTOR *)pVoidDescriptor;

    width  = (pDescriptor->horizontal_active_pixels[1] << 8 | pDescriptor->horizontal_active_pixels[0]) + 1;
    height = (pDescriptor->vertical_active_lines[1] << 8    | pDescriptor->vertical_active_lines[0]) + 1;
    rr     = pDescriptor->refresh_rate + 1;

    switch (pDescriptor->options.timing_formula)
    {
    case DISPLAYID_2_0_TIMING_FORMULA_CVT_1_2_STANDARD:
        status = NvTiming_CalcCVT(width, height, rr, NVT_PROGRESSIVE, pTiming);
    break;
    case DISPLAYID_2_0_TIMING_FORMULA_CVT_1_2_REDUCED_BLANKING_1:
        status = NvTiming_CalcCVT_RB(width, height, rr, NVT_PROGRESSIVE, pTiming);
    break;
    case DISPLAYID_2_0_TIMING_FORMULA_CVT_2_0_REDUCED_BLANKING_2:
        status = NvTiming_CalcCVT_RB2(width, height, rr, pDescriptor->options.rr_1000div1001_support, pTiming);
    break;
    default:
    {
        nvt_assert(0 && "Unknown timing formula");
        status = NVT_STATUS_ERR;
    }
    break;
    }

    if (status == NVT_STATUS_SUCCESS)
    {
        NVMISC_MEMSET(pTiming->etc.name, 0, sizeof(pTiming->etc.name));
        pTiming->etc.status = NVT_STATUS_DISPLAYID_9N(++count);

        if (pDescriptor->options.timing_formula ==  DISPLAYID_2_0_TIMING_FORMULA_CVT_1_2_STANDARD)
        {
            NVT_SNPRINTF((char *)pTiming->etc.name, sizeof(pTiming->etc.name), "DID20-Type9:#%3d:%dx%dx%3d.%03dHz/%s",
                                                                                (int)NVT_GET_TIMING_STATUS_SEQ(pTiming->etc.status),
                                                                                (int)pTiming->HVisible,
                                                                                (int)pTiming->VVisible,
                                                                                (int)pTiming->etc.rrx1k/1000,
                                                                                (int)pTiming->etc.rrx1k%1000,
                                                                                (pTiming->interlaced ? "I":"P"));
        }
        else
        {
            NVT_SNPRINTF((char *)pTiming->etc.name, sizeof(pTiming->etc.name), "DID20-Type9RB%d:#%3d:%dx%dx%3d.%03dHz/%s",
                                                                                pDescriptor->options.timing_formula,
                                                                                (int)NVT_GET_TIMING_STATUS_SEQ(pTiming->etc.status),
                                                                                (int)pTiming->HVisible,
                                                                                (int)pTiming->VVisible,
                                                                                (int)pTiming->etc.rrx1k/1000,
                                                                                (int)pTiming->etc.rrx1k%1000,
                                                                                (pTiming->interlaced ? "I":"P"));
        }
    }
    pTiming->etc.name[sizeof(pTiming->etc.name) - 1] = '\0';
    pTiming->etc.rep = 0x1;

    return status;
}

CODE_SEGMENT(PAGE_DD_CODE)
NVT_STATUS
parseDisplayId20Timing10Descriptor(
    const void *pDescriptor,
    NVT_TIMING *pTiming,
    NvU8 payloadBytes,
    NvU8 count)
{
    NVT_STATUS  status = NVT_STATUS_SUCCESS;
    const DISPLAYID_2_0_TIMING_10_6BYTES_DESCRIPTOR* p6bytesDescriptor = NULL;
    const DISPLAYID_2_0_TIMING_10_7BYTES_DESCRIPTOR* p7bytesDescriptor = NULL;
    NvU32 width  = 0;
    NvU32 height = 0;
    NvU32 rr     = 0;

    p6bytesDescriptor = (const DISPLAYID_2_0_TIMING_10_6BYTES_DESCRIPTOR *)pDescriptor;

    width  = (p6bytesDescriptor->horizontal_active_pixels[1] << 8 | p6bytesDescriptor->horizontal_active_pixels[0]) + 1;
    height = (p6bytesDescriptor->vertical_active_lines[1] << 8    | p6bytesDescriptor->vertical_active_lines[0]) + 1;
    rr     = p6bytesDescriptor->refresh_rate + 1;

    if (payloadBytes == DISPLAYID_2_0_TIMING_10_PAYLOAD_BYTES_7)
    {
        p7bytesDescriptor = (const DISPLAYID_2_0_TIMING_10_7BYTES_DESCRIPTOR *)pDescriptor;
                       rr = (p7bytesDescriptor->descriptor_6_bytes.refresh_rate | p7bytesDescriptor->refresh_rate_high << 8) + 1;
    }

    switch (p6bytesDescriptor->options.timing_formula)
    {
    case DISPLAYID_2_0_TIMING_FORMULA_CVT_1_2_STANDARD:
        status = NvTiming_CalcCVT(width, height, rr, NVT_PROGRESSIVE, pTiming);
    break;
    case DISPLAYID_2_0_TIMING_FORMULA_CVT_1_2_REDUCED_BLANKING_1:
        status = NvTiming_CalcCVT_RB(width, height, rr, NVT_PROGRESSIVE, pTiming);
    break;
    case DISPLAYID_2_0_TIMING_FORMULA_CVT_2_0_REDUCED_BLANKING_2:
        status = NvTiming_CalcCVT_RB2(width, height, rr, p6bytesDescriptor->options.rr1000div1001_or_hblank, pTiming);
    break;
    case DISPLAYID_2_0_TIMING_FORMULA_CVT_2_0_REDUCED_BLANKING_3:
    {
        NvU32 deltaHBlank = 0;
        if (p7bytesDescriptor != NULL)
        {
            if (p6bytesDescriptor->options.rr1000div1001_or_hblank == 0)     // Horizontal Blank in Pixels = [Field Value] * 8 + 80
            {
                deltaHBlank = p7bytesDescriptor->delta_hblank * 8;
            }
            else if (p6bytesDescriptor->options.rr1000div1001_or_hblank == 1)
            {
                if (p7bytesDescriptor->delta_hblank <= 5)
                    deltaHBlank = (p7bytesDescriptor->delta_hblank * 8 + 160) - 80;
                else // if 5 < Field Value <=7
                    deltaHBlank = (160 - ((p7bytesDescriptor->delta_hblank - 5) * 8)) - 80;
            }

            status = NvTiming_CalcCVT_RB3(width, height, rr, deltaHBlank, p7bytesDescriptor->additional_vblank_timing * 35, p6bytesDescriptor->options.early_vsync, pTiming);
        }
        else // 6 bytes descriptor
        {
            if (p6bytesDescriptor->options.rr1000div1001_or_hblank == 1)
                deltaHBlank = 80;

            status = NvTiming_CalcCVT_RB3(width, height, rr, deltaHBlank, 0, p6bytesDescriptor->options.early_vsync, pTiming);
        }
    }
    break;
    default:
    {
        nvt_assert(0 && "Unknown timing formula");
        status = NVT_STATUS_ERR;
    }
    break;
    }

    if (status == NVT_STATUS_SUCCESS)
    {
        pTiming->etc.status = NVT_STATUS_DISPLAYID_10N(++count);
        return status;
    }

    return status;
}

// get the existed stored timing sequence number
CODE_SEGMENT(PAGE_DD_CODE)
static NvU8
getExistedTimingSeqNumber(
    NVT_DISPLAYID_2_0_INFO *pDisplayIdInfo,
    enum NVT_TIMING_TYPE timingType)
{
    NvU8 count = 0;
    NvU8 i     = 0;

    switch (timingType)
    {
    case NVT_TYPE_DISPLAYID_7:
    case NVT_TYPE_DISPLAYID_8:
    case NVT_TYPE_DISPLAYID_9:
    case NVT_TYPE_DISPLAYID_10:
        break;
    default:
        return count;
    }

    for (i = 0; i< pDisplayIdInfo->total_timings; i++)
    {
        if (NVT_GET_TIMING_STATUS_TYPE(pDisplayIdInfo->timing[i].etc.status) == timingType)
        {
            ++count;
        }
    }

    return count;
}

// get the version
CODE_SEGMENT(PAGE_DD_CODE)
NvU32 getDID2Version(NvU8 *pData, NvU32 *pVer)
{
    const DISPLAYID_2_0_SECTION *pSection = (const DISPLAYID_2_0_SECTION*)pData;

    *pVer = 0;
    if (pSection->header.version == 0x2)
    {
        *pVer = (((NvU32)pSection->header.version) << 8) + ((NvU32)pSection->header.revision);
    }
    else
        return NVT_STATUS_ERR;          // un-recongnized DisplayID20 version

    return NVT_STATUS_SUCCESS;
}

CODE_SEGMENT(PAGE_DD_CODE)
void
updateColorFormatForDisplayId20Timings(
    NVT_DISPLAYID_2_0_INFO *pDisplayId20Info,
    NvU32 timingIdx)
{
    // pDisplayId20Info parsed displayID20 info
    NVT_TIMING *pT= &pDisplayId20Info->timing[timingIdx];

    nvt_assert(timingIdx <= COUNT(pDisplayId20Info->timing));

    // rgb444 (always support 6bpc and 8bpc as per DP spec 5.1.1.1.1 RGB Colorimetry)
    UPDATE_BPC_FOR_COLORFORMAT(pT->etc.rgb444, 1,
                                               1,
                                               pDisplayId20Info->interface_features.rgb444.bpc.bpc10,
                                               pDisplayId20Info->interface_features.rgb444.bpc.bpc12,
                                               pDisplayId20Info->interface_features.rgb444.bpc.bpc14,
                                               pDisplayId20Info->interface_features.rgb444.bpc.bpc16);

    // yuv444
    UPDATE_BPC_FOR_COLORFORMAT(pT->etc.yuv444, 0, // yuv444 does not support 6bpc
                                               pDisplayId20Info->interface_features.yuv444.bpc.bpc8,
                                               pDisplayId20Info->interface_features.yuv444.bpc.bpc10,
                                               pDisplayId20Info->interface_features.yuv444.bpc.bpc12,
                                               pDisplayId20Info->interface_features.yuv444.bpc.bpc14,
                                               pDisplayId20Info->interface_features.yuv444.bpc.bpc16);
    // yuv422
    UPDATE_BPC_FOR_COLORFORMAT(pT->etc.yuv422, 0, // yuv422 does not support 6bpc
                                               pDisplayId20Info->interface_features.yuv422.bpc.bpc8,
                                               pDisplayId20Info->interface_features.yuv422.bpc.bpc10,
                                               pDisplayId20Info->interface_features.yuv422.bpc.bpc12,
                                               pDisplayId20Info->interface_features.yuv422.bpc.bpc14,
                                               pDisplayId20Info->interface_features.yuv422.bpc.bpc16);

    if (!NVT_DID20_TIMING_IS_CTA861(pT->etc.flag, pT->etc.status))
    {
        // yuv420
        UPDATE_BPC_FOR_COLORFORMAT(pT->etc.yuv420, 0,  // yuv420 does not support 6bpc
                                                   pDisplayId20Info->interface_features.yuv420.bpc.bpc8,
                                                   pDisplayId20Info->interface_features.yuv420.bpc.bpc10,
                                                   pDisplayId20Info->interface_features.yuv420.bpc.bpc12,
                                                   pDisplayId20Info->interface_features.yuv420.bpc.bpc14,
                                                   pDisplayId20Info->interface_features.yuv420.bpc.bpc16);
    }
}
POP_SEGMENTS

