//***************************************************************************** // // 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_edid.c // // Purpose: the provide edid related services // //***************************************************************************** #include "nvBinSegment.h" #include "nvmisc.h" #include "edid.h" PUSH_SEGMENTS // Macro to declare a TIMING initializer for given parameters without border #define EST_TIMING(hv,hfp,hsw,ht,hsp,vv,vfp,vsw,vt,vsp,rr,pclk,format) \ {hv,0,hfp,hsw,ht,(hsp)=='-',vv,0,vfp,vsw,vt,(vsp)=='-',NVT_PROGRESSIVE,pclk,((pclk<<3)+(pclk<<1)),{0,rr,set_rrx1k(pclk,ht,vt),0,1,{0},{0},{0},{0},format,"VESA Established"}} DATA_SEGMENT(PAGE_DATA) #if !defined(NV_WSA) CONS_SEGMENT(PAGE_CONS) #endif // wsa // There is a large table of strings that translate 3-character PNP vendor IDs to a more user-friendly name in the following header. // Mark this constant table as pageable. #include "nvPNPVendorIds.h" static const NVT_TIMING EDID_EST[] = { EST_TIMING( 720, 0, 0, 720,'-', 400, 0,0, 400,'-',70, 0,NVT_STATUS_EDID_EST), // 720x400x70Hz (IBM, VGA) EST_TIMING( 720, 0, 0, 720,'-', 400, 0,0, 400,'-',88, 0,NVT_STATUS_EDID_EST), // 720x400x88Hz (IBM, XGA2) {640,0,16,96,800,NVT_H_SYNC_NEGATIVE,480,0,10,2,525,NVT_V_SYNC_NEGATIVE,NVT_PROGRESSIVE,2518,25175,{0,60,60000,0,1,{0},{0},{0},{0},NVT_STATUS_EDID_EST,"EDID_Established"}}, EST_TIMING( 640, 0, 0, 640,'-', 480, 0,0, 480,'-',67, 0,NVT_STATUS_EDID_EST), // 640x480x67Hz (Apple, Mac II) // 640x480x72Hz (VESA) - this entry have borders {640,8,16,40,832,NVT_H_SYNC_NEGATIVE,480,8,1,3,520,NVT_V_SYNC_NEGATIVE,NVT_PROGRESSIVE,3150,31500,{0,72,72000,0,1,{0},{0},{0},{0},NVT_STATUS_EDID_EST,"EDID_Established"}}, EST_TIMING( 640,16, 64, 840,'-', 480, 1,3, 500,'-',75, 3150,NVT_STATUS_EDID_EST), // 640x480x75Hz (VESA) EST_TIMING( 800,24, 72,1024,'+', 600, 1,2, 625,'+',56, 3600,NVT_STATUS_EDID_EST), // 800x600x56Hz (VESA) EST_TIMING( 800,40,128,1056,'+', 600, 1,4, 628,'+',60, 4000,NVT_STATUS_EDID_EST), // 800x600x60Hz (VESA) EST_TIMING( 800,56,120,1040,'+', 600,37,6, 666,'+',72, 5000,NVT_STATUS_EDID_EST), // 800x600x72Hz (VESA) EST_TIMING( 800,16, 80,1056,'+', 600, 1,3, 625,'+',75, 4950,NVT_STATUS_EDID_EST), // 800x600x75Hz (VESA) EST_TIMING( 832, 0, 0, 832,'-', 624, 0,0, 624,'-',75, 0,NVT_STATUS_EDID_EST), // 832x624x75Hz (Apple, Mac II) EST_TIMING(1024, 0, 0,1024,'-', 768, 0,0, 768,'-',87, 0,NVT_STATUS_EDID_EST), // 1024x768x87Hz (IBM, Interlaced) EST_TIMING(1024,24,136,1344,'-', 768, 3,6, 806,'-',60, 6500,NVT_STATUS_EDID_EST), // 1024x768x60Hz (VESA) EST_TIMING(1024,24,136,1328,'-', 768, 3,6, 806,'-',70, 7500,NVT_STATUS_EDID_EST), // 1024x768x70Hz (VESA) EST_TIMING(1024,16, 96,1312,'+', 768, 1,3, 800,'+',75, 7875,NVT_STATUS_EDID_EST), // 1024x768x75Hz (VESA) EST_TIMING(1280,16,144,1688,'+',1024, 1,3,1066,'+',75,13500,NVT_STATUS_EDID_EST), // 1280x1024x75Hz (VESA) // the end NVT_TIMING_SENTINEL }; static NvU32 MAX_EST_FORMAT = sizeof(EDID_EST)/sizeof(EDID_EST[0]) - 1; static const NVT_TIMING EDID_ESTIII[] = { EST_TIMING( 640, 32, 64, 832,'+', 350,32,3, 445,'-',85, 3150,NVT_STATUS_EDID_EST), // 640x350x85Hz EST_TIMING( 640, 32, 64, 832,'-', 400, 1,3, 445,'+',85, 3150,NVT_STATUS_EDID_EST), // 640x400x85Hz EST_TIMING( 720, 36, 72, 936,'-', 400, 1,3, 446,'+',85, 3550,NVT_STATUS_EDID_EST), // 720x400x85Hz EST_TIMING( 640, 56, 56, 832,'-', 480, 1,3, 509,'-',85, 3600,NVT_STATUS_EDID_EST), // 640x480x85Hz EST_TIMING( 848, 16,112,1088,'+', 480, 6,8, 517,'+',60, 3375,NVT_STATUS_EDID_EST), // 848x480x60HZ EST_TIMING( 800, 32, 64,1048,'+', 600, 1,3, 631,'+',85, 5625,NVT_STATUS_EDID_EST), // 800x600x85Hz EST_TIMING(1024, 48, 96,1376,'+', 768, 1,3, 808,'+',85, 9450,NVT_STATUS_EDID_EST), // 1024x768x85Hz EST_TIMING(1152, 64,128,1600,'+', 864, 1,3, 900,'+',75,10800,NVT_STATUS_EDID_EST), // 1152x864x75Hz EST_TIMING(1280, 48, 32,1440,'+', 768, 3,7, 790,'-',60, 6825,NVT_STATUS_EDID_EST), // 1280x768x60Hz (RB) EST_TIMING(1280, 64,128,1664,'-', 768, 3,7, 798,'+',60, 7950,NVT_STATUS_EDID_EST), // 1280x768x60Hz EST_TIMING(1280, 80,128,1696,'-', 768, 3,7, 805,'+',75,10225,NVT_STATUS_EDID_EST), // 1280x768x75Hz EST_TIMING(1280, 80,136,1712,'-', 768, 3,7, 809,'+',85,11750,NVT_STATUS_EDID_EST), // 1280x768x85Hz EST_TIMING(1280, 96,112,1800,'+', 960, 1,3,1000,'+',60,10800,NVT_STATUS_EDID_EST), // 1280x960x60Hz EST_TIMING(1280, 64,160,1728,'+', 960, 1,3,1011,'+',85,14850,NVT_STATUS_EDID_EST), // 1280x960x85Hz EST_TIMING(1280, 48,112,1688,'+',1024, 1,3,1066,'+',60,10800,NVT_STATUS_EDID_EST), // 1280x1024x60Hz EST_TIMING(1280, 64,160,1728,'+',1024, 1,3,1072,'+',85,15750,NVT_STATUS_EDID_EST), // 1280x1024x85Hz EST_TIMING(1360, 64,112,1792,'+', 768, 3,6, 795,'+',60, 8550,NVT_STATUS_EDID_EST), // 1360x768x60Hz EST_TIMING(1440, 48, 32,1600,'+', 900, 3,6, 926,'-',60, 8875,NVT_STATUS_EDID_EST), // 1440x900x60Hz (RB) EST_TIMING(1440, 80,152,1904,'-', 900, 3,6, 934,'+',60,10650,NVT_STATUS_EDID_EST), // 1440x900x60Hz EST_TIMING(1440, 96,152,1936,'-', 900, 3,6, 942,'+',75,13675,NVT_STATUS_EDID_EST), // 1440x900x75Hz EST_TIMING(1440,104,152,1952,'-', 900, 3,6, 948,'+',85,15700,NVT_STATUS_EDID_EST), // 1440x900x85Hz EST_TIMING(1400, 48, 32,1560,'+',1050, 3,4,1080,'-',60,10100,NVT_STATUS_EDID_EST), // 1440x1050x60Hz (RB) EST_TIMING(1400, 88,144,1864,'-',1050, 3,4,1089,'+',60,12175,NVT_STATUS_EDID_EST), // 1440x1050x60Hz EST_TIMING(1400,104,144,1896,'-',1050, 3,4,1099,'+',75,15600,NVT_STATUS_EDID_EST), // 1440x1050x75Hz EST_TIMING(1400,104,152,1912,'-',1050, 3,4,1105,'+',85,17950,NVT_STATUS_EDID_EST), // 1440x1050x85Hz EST_TIMING(1680, 48, 32,1840,'+',1050, 3,6,1080,'-',60,11900,NVT_STATUS_EDID_EST), // 1680x1050x60Hz (RB) EST_TIMING(1680,104,176,2240,'-',1050, 3,6,1089,'+',60,14625,NVT_STATUS_EDID_EST), // 1680x1050x60Hz EST_TIMING(1680,120,176,2272,'-',1050, 3,6,1099,'+',75,18700,NVT_STATUS_EDID_EST), // 1680x1050x75Hz EST_TIMING(1680,128,176,2288,'-',1050, 3,6,1105,'+',85,21475,NVT_STATUS_EDID_EST), // 1680x1050x85Hz EST_TIMING(1600, 64,192,2160,'+',1200, 1,3,1250,'+',60,16200,NVT_STATUS_EDID_EST), // 1600x1200x60Hz EST_TIMING(1600, 64,192,2160,'+',1200, 1,3,1250,'+',65,17550,NVT_STATUS_EDID_EST), // 1600x1200x65Hz EST_TIMING(1600, 64,192,2160,'+',1200, 1,3,1250,'+',70,18900,NVT_STATUS_EDID_EST), // 1600x1200x70Hz EST_TIMING(1600, 64,192,2160,'+',1200, 1,3,1250,'+',75,20250,NVT_STATUS_EDID_EST), // 1600x1200x75Hz EST_TIMING(1600, 64,192,2160,'+',1200, 1,3,1250,'+',85,22950,NVT_STATUS_EDID_EST), // 1600x1200x85Hz EST_TIMING(1792,128,200,2448,'-',1344, 1,3,1394,'+',60,20475,NVT_STATUS_EDID_EST), // 1792x1344x60Hz EST_TIMING(1792, 96,216,2456,'-',1344, 1,3,1417,'+',75,26100,NVT_STATUS_EDID_EST), // 1792x1344x75Hz EST_TIMING(1856, 96,224,2528,'-',1392, 1,3,1439,'+',60,21825,NVT_STATUS_EDID_EST), // 1856x1392x60Hz EST_TIMING(1856,128,224,2560,'-',1392, 1,3,1500,'+',75,28800,NVT_STATUS_EDID_EST), // 1856x1392x75Hz EST_TIMING(1920, 48, 32,2080,'+',1200, 3,6,1235,'-',60,15400,NVT_STATUS_EDID_EST), // 1920x1200x60Hz (RB) EST_TIMING(1920,136,200,2592,'-',1200, 3,6,1245,'+',60,19325,NVT_STATUS_EDID_EST), // 1920x1200x60Hz EST_TIMING(1920,136,208,2608,'-',1200, 3,6,1255,'+',75,24525,NVT_STATUS_EDID_EST), // 1920x1200x75Hz EST_TIMING(1920,144,208,2624,'-',1200, 3,6,1262,'+',85,28125,NVT_STATUS_EDID_EST), // 1920x1200x85Hz EST_TIMING(1920,128,208,2600,'-',1440, 1,3,1500,'+',60,23400,NVT_STATUS_EDID_EST), // 1920x1440x60Hz EST_TIMING(1920,144,224,2640,'-',1440, 1,3,1500,'+',75,29700,NVT_STATUS_EDID_EST), // 1920x1440x75Hz NVT_TIMING_SENTINEL, NVT_TIMING_SENTINEL, NVT_TIMING_SENTINEL, NVT_TIMING_SENTINEL }; static NvU32 MAX_ESTIII_FORMAT = sizeof(EDID_ESTIII)/sizeof(EDID_ESTIII[0]) - 1; CODE_SEGMENT(PAGE_DD_CODE) NVT_STATUS NvTiming_EnumEST(NvU32 index, NVT_TIMING *pT) { if ((pT == NULL) || (index > MAX_EST_FORMAT)) { return NVT_STATUS_ERR; } *pT = EDID_EST[index]; if (pT->HTotal == 0 || pT->VTotal == 0) { return NVT_STATUS_ERR; } pT->etc.rrx1k = axb_div_c((NvU32)pT->pclk1khz, (NvU32)1000*(NvU32)1000, (NvU32)pT->HTotal*(NvU32)pT->VTotal); return NVT_STATUS_SUCCESS; } CODE_SEGMENT(PAGE_DD_CODE) NVT_STATUS NvTiming_EnumESTIII(NvU32 index, NVT_TIMING *pT) { if ((pT == NULL) || (index > MAX_ESTIII_FORMAT)) { return NVT_STATUS_ERR; } *pT = EDID_ESTIII[index]; if (pT->HTotal == 0 || pT->VTotal == 0) { return NVT_STATUS_ERR; } pT->etc.rrx1k = axb_div_c((NvU32)pT->pclk1khz, (NvU32)1000*(NvU32)1000, (NvU32)pT->HTotal*(NvU32)pT->VTotal); return NVT_STATUS_SUCCESS; } CODE_SEGMENT(PAGE_DD_CODE) NvU32 isHdmi3DStereoType(NvU8 StereoStructureType) { return ((NVT_HDMI_VS_BYTE5_HDMI_3DS_FRAMEPACK == StereoStructureType) || (NVT_HDMI_VS_BYTE5_HDMI_3DS_FIELD_ALT == StereoStructureType) || (NVT_HDMI_VS_BYTE5_HDMI_3DS_LINE_ALT == StereoStructureType) || (NVT_HDMI_VS_BYTE5_HDMI_3DS_SIDEBYSIDEFULL == StereoStructureType) || (NVT_HDMI_VS_BYTE5_HDMI_3DS_LDEPTH == StereoStructureType) || (NVT_HDMI_VS_BYTE5_HDMI_3DS_LDEPTHGFX == StereoStructureType) || (NVT_HDMI_VS_BYTE5_HDMI_3DS_TOPBOTTOM == StereoStructureType) || (NVT_HDMI_VS_BYTE5_HDMI_3DS_SIDEBYSIDEHALF == StereoStructureType)); } CODE_SEGMENT(PAGE_DD_CODE) NvU32 NvTiming_GetVESADisplayDescriptorVersion(NvU8 *rawData, NvU32 *pVer) { return getEdidVersion(rawData, pVer); } // get the EDID version CODE_SEGMENT(PAGE_DD_CODE) NvU32 getEdidVersion(NvU8 *pEdid, NvU32 *pVer) { EDIDV1STRUC *p = (EDIDV1STRUC *) pEdid; if (pEdid[0] == 0x00) { // For Version 1.x, first 8 bytes of EDID must be 00h, FFh, FFh, FFh, FFh, FFh, FFh, 00h. // Beware of Endian-ness and signed-ness. if (p->bHeader[1] != 0xFF || p->bHeader[2] != 0xFF || p->bHeader[3] != 0xFF || p->bHeader[4] != 0xFF || p->bHeader[5] != 0xFF || p->bHeader[6] != 0xFF || p->bHeader[7] != 0x00) return NVT_STATUS_ERR; *pVer = (((NvU32) p->bVersionNumber) << 8) + ((NvU32) p->bRevisionNumber); } else if ((pEdid[0] & 0xF0) == 0x20) *pVer = (((NvU32) (pEdid[0] & 0XF0) << 4) + (NvU32) (pEdid[0] & 0X0F)) ; // DisplayID version 2.x else return NVT_STATUS_ERR; // un-recongnized EDID version return NVT_STATUS_SUCCESS; } CODE_SEGMENT(PAGE_DD_CODE) void parseEdidCvt3ByteDescriptor(NvU8 *p, NVT_EDID_INFO *pInfo, NvU32 *vtbCount) { NvU32 k; NvU32 width, height, aspect, rr = 0; NVT_EDID_DD_CVT_3BYTE_BLOCK *pTiming = (NVT_EDID_DD_CVT_3BYTE_BLOCK *)p; NVT_TIMING newTiming; NVT_STATUS status; if (pTiming->addressable_lines == 0) return; height = pTiming->addressable_lines; aspect = pTiming->aspect_ratio; if (aspect == NVT_EDID_CVT3_ASPECT_4X3) width = height * 4 / 3; else if (aspect == NVT_EDID_CVT3_ASPECT_16X9) width = height * 16 / 9; else if (aspect == NVT_EDID_CVT3_ASPECT_16X10) width = height * 16 / 10; else //15:9 width = height * 15 / 9; width &= 0xFFFFFFF8; // round down to nearest 8 // loop through bits4:0 of supported_vert_rate so we can add a timing // for each supported rate for (k=1; k<=0x10; k<<=1) { // skip if this bit indicate no support for the rate; if ( (pTiming->supported_vert_rates & (k)) == 0) continue; // find the correct refresh rate for this bit switch (k) { case NVT_EDID_CVT3_SUPPORTED_RATE_60HZ_REDUCED_BLANKING : case NVT_EDID_CVT3_SUPPORTED_RATE_60HZ : rr = 60; break; case NVT_EDID_CVT3_SUPPORTED_RATE_85HZ : rr = 85; break; case NVT_EDID_CVT3_SUPPORTED_RATE_75HZ : rr = 75; break; case NVT_EDID_CVT3_SUPPORTED_RATE_50HZ : rr = 50; break; } NVMISC_MEMSET(&newTiming, 0, sizeof(newTiming)); if ( (k) != NVT_EDID_CVT3_SUPPORTED_RATE_60HZ_REDUCED_BLANKING) // standard blanking { status = NvTiming_CalcCVT(width, height, rr, NVT_PROGRESSIVE, &newTiming); } else // reduced blanking { status = NvTiming_CalcCVT_RB(width, height, rr, NVT_PROGRESSIVE, &newTiming); } if (status == NVT_STATUS_SUCCESS) { // For VTB timings, add additional information if (vtbCount) { (*vtbCount)++; newTiming.etc.status = NVT_STATUS_EDID_VTB_EXT_CVTn(*vtbCount); newTiming.etc.name[39] = '\0'; } if (!assignNextAvailableTiming(pInfo, &newTiming)) { break; } } } // for (k=1; k<=0x10; k<<=1) } // parse the EDID 1.x based cvt timing info CODE_SEGMENT(PAGE_DD_CODE) void parseEdidCvtTiming(NVT_EDID_INFO *pInfo) { NvU32 i, j; // find display range limit with cvt, or cvt 3-byte LDDs for (i=0; ildd[i].tag == NVT_EDID_DISPLAY_DESCRIPTOR_CVT ) { NVT_EDID_DD_CVT_3BYTE *pCVT = (NVT_EDID_DD_CVT_3BYTE *)&pInfo->ldd[i].u.cvt; // loop through cvt 3-byte blocks for (j=0; jblock + j), pInfo, NULL); } // for(j=0; jestablished_timings_1_2) * 8 - 1), j = 0; i != 0; i >>= 1, j ++) { if ((pInfo->established_timings_1_2 & i) != 0 && EDID_EST[j].pclk1khz != 0) { // count the timing newTiming = EDID_EST[j]; newTiming.etc.status = NVT_STATUS_EDID_ESTn(++count); NVT_SNPRINTF((char *)newTiming.etc.name, 40, "EDID-EST(VESA):%dx%dx%dHz", (int)newTiming.HVisible, (int)newTiming.VVisible, (int)newTiming.etc.rr); newTiming.etc.name[39] = '\0'; if (!assignNextAvailableTiming(pInfo, &newTiming)) { break; } } } // ESTIII block in ldd only supported in EDID1.4 and above if (pInfo->version < NVT_EDID_VER_1_4) return; for (i=0; ildd[i].tag == NVT_EDID_DISPLAY_DESCRIPTOR_ESTIII ) { NVT_EDID_DD_EST_TIMING3* pEST = &pInfo->ldd[i].u.est3; for (j=0; jdata[j] & (1<> 8) & 0x3F) + 60; // bits 5->0 // get the height aspect = ((timing >> 8) & 0xC0); // aspect ratio at bit 7:6 if (aspect == 0x00) height = (pInfo->version < 0x103) ? width : (width * 5 / 8); //16:10 per EDID1.3 and 1:1 with earlier EDID else if (aspect == 0x40) height = width * 3 / 4; //4:3 else if (aspect == 0x80) height = width * 4 / 5; //5:4 else height = width * 9 / 16; //16:9 // try to get the timing from DMT first if (NvTiming_CalcDMT(width, height, rr, 0, pT) == NVT_STATUS_SUCCESS) { pT->etc.status = NVT_STATUS_EDID_STDn(count); NVT_SNPRINTF((char *)pT->etc.name, 40, "EDID-STD(DMT):%dx%dx%dHz", (int)width, (int)height, (int)rr); pT->etc.name[39] = '\0'; } // EDID1.4 and above defaults to CVT, instead of GTF. GTF is deprecated as of 1.4. else if ((pInfo->version >= NVT_EDID_VER_1_4) && (NvTiming_CalcCVT(width, height, rr, NVT_PROGRESSIVE, pT) == NVT_STATUS_SUCCESS)) { pT->etc.status = NVT_STATUS_EDID_STDn(count); NVT_SNPRINTF((char *)pT->etc.name, 40, "EDID-STD(CVT):%dx%dx%dHz", (int)width, (int)height, (int)rr); pT->etc.name[39] = '\0'; } else { // if the mode is not found in DMT, use GTF timing if (NvTiming_CalcGTF(width, height, rr, NVT_PROGRESSIVE, pT) == NVT_STATUS_SUCCESS) { NVT_SNPRINTF((char *)pT->etc.name, 40, "EDID-STD(GTF):%dx%dx%dHz", (int)width, (int)height, (int)rr); pT->etc.name[39] = '\0'; } pT->etc.status = NVT_STATUS_EDID_STDn(count); } } // parse the EDID 1.x based standard timing info CODE_SEGMENT(PAGE_DD_CODE) void parseEdidStandardTiming(NVT_EDID_INFO *pInfo) { NvU32 i, j; NVT_TIMING newTiming; NvU32 count = 0; // now check for standard timings for (i=0; istandard_timings[i] & 0x0FF) != 0x1) && //proper indication of unused field (pInfo->standard_timings[i] != 0x0)) //improper indication (bad edid) { NVMISC_MEMSET(&newTiming, 0, sizeof(newTiming)); parseEdidStandardTimingDescriptor(pInfo->standard_timings[i], pInfo, count, &newTiming); if (!assignNextAvailableTiming(pInfo, &newTiming)) { break; } count++; }//if ((pInfo->standard_timings[i] & 0x0FF) != 0x1) } //for (i=0; iversion < NVT_EDID_VER_1_4) return; // now check for standard timings in long display descriptors for (i=0; ildd[i].tag == NVT_EDID_DISPLAY_DESCRIPTOR_STI ) { NVT_EDID_DD_STD_TIMING* pSTI = &pInfo->ldd[i].u.std_timing; for (j=0; jdescriptor[j] & 0x0FF) != 0x00) { NVMISC_MEMSET(&newTiming, 0, sizeof(newTiming)); parseEdidStandardTimingDescriptor(pSTI->descriptor[j], pInfo, count, &newTiming); if (!assignNextAvailableTiming(pInfo, &newTiming)) { break; } count++; } // if ((pSTI->std_timing[i] & 0x0FF) != 0x1) } //for (j=0; jDetailedTimingDesc[i]).tag = NVT_EDID_DISPLAY_DESCRIPTOR_STI ) } //for (i=0; iwDTPixelClock !=0 || pDTD->bDTHorizontalActive !=0) && (pDTD->wDTPixelClock != 0x0101 || pDTD->bDTHorizontalActive != 1 || pDTD->bDTHorizontalBlanking != 1 || pDTD->bDTHorizActiveBlank != 1)) { // Note that hvisible and vvisible here correspond to the "Addressable Video" portion of the // "Active Video" defined in the EDID spec (see section 3.12: Note Regarding Borders) hvisible = (pDTD->bDTHorizontalActive + ((pDTD->bDTHorizActiveBlank & 0xF0) << 4)) - 2 * pDTD->bDTHorizontalBorder; vvisible = (pDTD->bDTVerticalActive + ((pDTD->bDTVertActiveBlank & 0xF0) << 4)) - 2 * pDTD->bDTVerticalBorder; // Sanity check since we are getting values from the monitor if (hvisible <= 0 || vvisible <= 0 || pDTD->wDTPixelClock == 0) { if (pT) pT->HVisible = 0; return NVT_STATUS_ERR; } // if the output timing buffer is not provide, simply return here to indicate a legal descriptor if (pT == NULL) return NVT_STATUS_SUCCESS; // horizontal timing parameters pT->HVisible = (NvU16)hvisible; pT->HBorder = (NvU16)pDTD->bDTHorizontalBorder; pT->HTotal = (NvU16)hvisible + (NvU16)(pDTD->bDTHorizontalBlanking + ((pDTD->bDTHorizActiveBlank & 0x0F) << 8)) + pT->HBorder * 2; pT->HFrontPorch = (NvU16)(pDTD->bDTHorizontalSync + ((pDTD->bDTHorizVertSyncOverFlow & 0xC0) << 2)); pT->HSyncWidth = (NvU16)(pDTD->bDTHorizontalSyncWidth + ((pDTD->bDTHorizVertSyncOverFlow & 0x30) << 4)); // vertical timing parameters pT->VVisible = (NvU16)vvisible; pT->VBorder = (NvU16)pDTD->bDTVerticalBorder; pT->VTotal = (NvU16)vvisible + (NvU16)(pDTD->bDTVerticalBlanking + ((pDTD->bDTVertActiveBlank & 0x0F) << 8)) + pT->VBorder * 2; pT->VFrontPorch = (NvU16)(((pDTD->bDTVerticalSync & 0xF0) >> 4) + ((pDTD->bDTHorizVertSyncOverFlow & 0x0C) << 2)); pT->VSyncWidth = (NvU16)((pDTD->bDTVerticalSync & 0x0F) + ((pDTD->bDTHorizVertSyncOverFlow & 0x03) << 4)); // pixel clock pT->pclk = (NvU32)pDTD->wDTPixelClock; pT->pclk1khz = (NvU32)((pDTD->wDTPixelClock << 3) + (pDTD->wDTPixelClock << 1)); // sync polarities if ((pDTD->bDTFlags & 0x18) == 0x18) { pT->HSyncPol = ((pDTD->bDTFlags & 0x2) != 0) ? NVT_H_SYNC_POSITIVE : NVT_H_SYNC_NEGATIVE; pT->VSyncPol = ((pDTD->bDTFlags & 0x4) != 0) ? NVT_V_SYNC_POSITIVE : NVT_V_SYNC_NEGATIVE; } else if ((pDTD->bDTFlags & 0x18) == 0x10) { pT->HSyncPol = ((pDTD->bDTFlags & 0x2) != 0) ? NVT_H_SYNC_POSITIVE : NVT_H_SYNC_NEGATIVE; pT->VSyncPol = NVT_V_SYNC_POSITIVE; } else { pT->HSyncPol = NVT_H_SYNC_NEGATIVE; pT->VSyncPol = NVT_V_SYNC_POSITIVE; } // interlaced if ((pDTD->bDTFlags & 0x80) == 0x80) pT->interlaced = 1; else pT->interlaced = 0; // Eizo split EDID case, using 0th bit to indicate split display capability if (((pDTD->bDTFlags & 1) == 1) && !(((pDTD->bDTFlags & 0x20) == 0x20) || ((pDTD->bDTFlags & 0x40) == 0x40))) { pT->etc.flag |= NVT_FLAG_EDID_DTD_EIZO_SPLIT; } if (pT->interlaced) { // Adjust for one extra blank line in every other frame. dwTotalPixels = (((NvU32)pT->HTotal * pT->VTotal) + ((NvU32)pT->HTotal * (pT->VTotal + 1))) / 2; } else { dwTotalPixels = (NvU32)pT->HTotal * pT->VTotal; } pT->etc.rr = (NvU16)(((NvU32)pDTD->wDTPixelClock*10000+dwTotalPixels/2)/dwTotalPixels); // Using utility call to multiply and divide to take care of overflow and truncation of large values // How did we arrive at 10000000? It comes from the fact that Pixel clock mentioned in EDID is in mulitples of 10KHz = 10000 // and the refresh rate is mentioned in 0.001Hz, that is 60Hz will be represented as 60000, which brings in the factor of 1000. // And hence 10000 * 1000 = 10000000 pT->etc.rrx1k = axb_div_c(pDTD->wDTPixelClock, 10000000, dwTotalPixels); pT->etc.status = NVT_STATUS_EDID_DTD; NVT_SNPRINTF((char *)pT->etc.name, sizeof(pT->etc.name), "EDID-Detailed:%dx%dx%d.%03dHz%s", (int)pT->HVisible, (int)(pT->interlaced ? 2 : 1)*pT->VVisible , (int)pT->etc.rrx1k/1000, (int)pT->etc.rrx1k%1000, (pT->interlaced ? "/i" : "")); pT->etc.name[sizeof(pT->etc.name) - 1] = '\0'; // aspect ratio pT->etc.aspect = (pDTD->bDTHorizVertImage & 0xF0) << 20 | pDTD->bDTHorizontalImage << 16 | (pDTD->bDTHorizVertImage & 0x0F) << 8 | pDTD->bDTVerticalImage; pT->etc.rep = 0x1; // Bit mask for no pixel repetition. return NVT_STATUS_SUCCESS; } return NVT_STATUS_ERR; } // parse the EDID 1.x based standard timing info CODE_SEGMENT(PAGE_DD_CODE) void parseEdidDetailedTiming(NvU8 *pEdid, NVT_EDID_INFO *pInfo) { EDIDV1STRUC *p = (EDIDV1STRUC *) pEdid; NVT_TIMING newTiming; NvU32 i; NvBool found = NV_FALSE; for (i = 0; i < 4; i++) { NVMISC_MEMSET(&newTiming, 0, sizeof(newTiming)); if (parseEdidDetailedTimingDescriptor((NvU8 *)&p->DetailedTimingDesc[i], &newTiming) == NVT_STATUS_SUCCESS) { newTiming.etc.status = NVT_STATUS_EDID_DTDn(i+1); if (!assignNextAvailableTiming(pInfo, &newTiming)) { break; } found = NV_TRUE; } } if (found) { // if edid_ver 1.3, PTM flag should be set //nvt_assert(pInfo->version > 0x103 || (pInfo->u.feature & // NVT_EDID_OTHER_FEATURES_PTM_INCLUDE_NATIVE)); if (pInfo->u.feature & NVT_EDID_OTHER_FEATURES_PTM_INCLUDE_NATIVE) { pInfo->timing[0].etc.flag |= NVT_FLAG_DTD1_PREFERRED_TIMING; } } } // parse the EDID 1.x 18-byte long display descriptor CODE_SEGMENT(PAGE_DD_CODE) static void parseEdidLongDisplayDescriptor(EDID_LONG_DISPLAY_DESCRIPTOR *descriptor, NVT_EDID_18BYTE_DESCRIPTOR *p, NvU32 version) { NvU32 i; // bypass the input pointer check in this private function // return if it's a detailed timing descriptor if (descriptor->prefix[0] != 0 || descriptor->prefix[1] != 0) return; // other sanity check for the input data if (descriptor->rsvd != 0) return; p->tag = descriptor->tag; // now translate the descriptor switch (descriptor->tag) { case NVT_EDID_DISPLAY_DESCRIPTOR_DPSN: // display product serial number case NVT_EDID_DISPLAY_DESCRITPOR_DPN: // display product name case NVT_EDID_DISPLAY_DESCRIPTOR_ADS: // alphanumeric data string (ASCII) // copy the 13 characters payload from the 18-byte descriptor for (i = 0; i < NVT_PVT_EDID_LDD_PAYLOAD_SIZE; i++) { if (descriptor->data[i] == 0x0A) p->u.serial_number.str[i] = '\0'; else p->u.serial_number.str[i] = descriptor->data[i]; } break; case NVT_EDID_DISPLAY_DESCRIPTOR_DRL: // display range limit { EDID_MONITOR_RANGE_LIMIT *pRangeLimit = (EDID_MONITOR_RANGE_LIMIT *)&descriptor->data[0]; p->u.range_limit.min_v_rate = pRangeLimit->minVRate; p->u.range_limit.max_v_rate = pRangeLimit->maxVRate; p->u.range_limit.min_h_rate = pRangeLimit->minHRate; p->u.range_limit.max_h_rate = pRangeLimit->maxHRate; p->u.range_limit.max_pclk_MHz = pRangeLimit->maxPClock10M * 10; p->u.range_limit.timing_support = pRangeLimit->timing_support; // add 255Hz offsets if needed, use descriptor->rsvd2 // to offset the min values their max MUST be offset as well if (version >= NVT_EDID_VER_1_4) { if (descriptor->rsvd2 & NVT_PVT_EDID_RANGE_OFFSET_VER_MAX) { p->u.range_limit.max_v_rate += NVT_PVT_EDID_RANGE_OFFSET_AMOUNT; if (descriptor->rsvd2 & NVT_PVT_EDID_RANGE_OFFSET_VER_MIN) { p->u.range_limit.min_v_rate += NVT_PVT_EDID_RANGE_OFFSET_AMOUNT; } } if (descriptor->rsvd2 & NVT_PVT_EDID_RANGE_OFFSET_HOR_MAX) { p->u.range_limit.max_h_rate += NVT_PVT_EDID_RANGE_OFFSET_AMOUNT; if (descriptor->rsvd2 & NVT_PVT_EDID_RANGE_OFFSET_HOR_MIN) { p->u.range_limit.min_h_rate += NVT_PVT_EDID_RANGE_OFFSET_AMOUNT; } } } if (p->u.range_limit.timing_support == NVT_EDID_RANGE_SUPPORT_GTF2) { // descriptor->data[7] // Start frequency for secondary curve, hor freq./2[kHz] p->u.range_limit.u.gtf2.C = pRangeLimit->u.gtf2.C / 2; // 0 <= C <= 127 p->u.range_limit.u.gtf2.K = pRangeLimit->u.gtf2.K; // 0 <= K <= 255 p->u.range_limit.u.gtf2.J = pRangeLimit->u.gtf2.J / 2; // 0 <= J <= 127 p->u.range_limit.u.gtf2.M = (pRangeLimit->u.gtf2.M_MSB << 8) + pRangeLimit->u.gtf2.M_LSB; // 0 <= M <= 65535 } else if (p->u.range_limit.timing_support == NVT_EDID_RANGE_SUPPORT_CVT) { // the pixel clock adjustment is in cvt.pixel_clock @ bits7:2 // that number is in 0.25MHz, ie actual max clock is max_pclk_MHz - (0.25 x cvt_pixel_clock) // subtract the whole number part from max_pclk_MHz, save the remainder p->u.range_limit.max_pclk_MHz -= (pRangeLimit->u.cvt.pixel_clock & NVT_PVT_EDID_CVT_PIXEL_CLOCK_MASK) >> NVT_PVT_EDID_CVT_PIXEL_CLOCK_SHIFT >> 2; // ie divide by 4 to get whole number p->u.range_limit.u.cvt.pixel_clock_adjustment = ((pRangeLimit->u.cvt.pixel_clock & NVT_PVT_EDID_CVT_PIXEL_CLOCK_MASK) >> NVT_PVT_EDID_CVT_PIXEL_CLOCK_SHIFT) & 0x03; // ie modulus 4 p->u.range_limit.u.cvt.max_active_pixels_per_line = (pRangeLimit->u.cvt.pixel_clock & NVT_PVT_EDID_CVT_ACTIVE_MSB_MASK) << NVT_PVT_EDID_CVT_ACTIVE_MSB_SHIFT; p->u.range_limit.u.cvt.max_active_pixels_per_line |= pRangeLimit->u.cvt.max_active; p->u.range_limit.u.cvt.max_active_pixels_per_line <<= 3; // ie multiply 8 p->u.range_limit.u.cvt.aspect_supported = (pRangeLimit->u.cvt.aspect_supported & NVT_PVT_EDID_CVT_ASPECT_SUPPORTED_MASK) >> NVT_PVT_EDID_CVT_ASPECT_SUPPORTED_SHIFT; p->u.range_limit.u.cvt.aspect_preferred = ( pRangeLimit->u.cvt.aspect_preferred_blanking & NVT_PVT_EDID_CVT_ASPECT_PREFERRED_MASK) >> NVT_PVT_EDID_CVT_ASPECT_PREFERRED_SHIFT; p->u.range_limit.u.cvt.blanking_support = ( pRangeLimit->u.cvt.aspect_preferred_blanking & NVT_PVT_EDID_CVT_BLANKING_MASK) >> NVT_PVT_EDID_CVT_BLANKING_SHIFT; p->u.range_limit.u.cvt.scaling_support = (pRangeLimit->u.cvt.scaling_support & NVT_PVT_EDID_CVT_SCALING_MASK) >> NVT_PVT_EDID_CVT_SCALING_SHIFT; p->u.range_limit.u.cvt.preferred_refresh_rate = pRangeLimit->u.cvt.preferred_refresh_rate; } } break; case NVT_EDID_DISPLAY_DESCRIPTOR_CPD: // color point data { EDID_COLOR_POINT_DATA *pColorPoint = (EDID_COLOR_POINT_DATA *)&descriptor->data[0]; p->u.color_point.wp1_index = pColorPoint->wp1_index; p->u.color_point.wp1_x = pColorPoint->wp1_x << 2; p->u.color_point.wp1_x |= (pColorPoint->wp1_x_y & NVT_PVT_EDID_CPD_WP_X_MASK) >> NVT_PVT_EDID_CPD_WP_X_SHIFT; p->u.color_point.wp1_y = pColorPoint->wp1_y << 2; p->u.color_point.wp1_y |= (pColorPoint->wp1_x_y & NVT_PVT_EDID_CPD_WP_Y_MASK) >> NVT_PVT_EDID_CPD_WP_Y_SHIFT; p->u.color_point.wp1_gamma = pColorPoint->wp1_gamma + 100; p->u.color_point.wp2_index = pColorPoint->wp2_index; p->u.color_point.wp2_x = pColorPoint->wp2_x << 2; p->u.color_point.wp2_x |= (pColorPoint->wp2_x_y & NVT_PVT_EDID_CPD_WP_X_MASK) >> NVT_PVT_EDID_CPD_WP_X_SHIFT; p->u.color_point.wp2_y = pColorPoint->wp2_y << 2; p->u.color_point.wp2_y |= (pColorPoint->wp2_x_y & NVT_PVT_EDID_CPD_WP_Y_MASK) >> NVT_PVT_EDID_CPD_WP_Y_SHIFT; p->u.color_point.wp2_gamma = pColorPoint->wp2_gamma + 100; } break; case NVT_EDID_DISPLAY_DESCRIPTOR_STI: // standard timing identification { EDID_STANDARD_TIMING_ID *pStdTiming = (EDID_STANDARD_TIMING_ID *)&descriptor->data[0]; for(i=0; iu.std_timing.descriptor[i] = pStdTiming->std_timing[i]; } } break; case NVT_EDID_DISPLAY_DESCRIPTOR_DCM: // display color management { EDID_COLOR_MANAGEMENT_DATA *pColorMan = (EDID_COLOR_MANAGEMENT_DATA *)&descriptor->data[0]; p->u.color_man.red_a3 = pColorMan->red_a3_lsb | (pColorMan->red_a3_msb << 8); p->u.color_man.red_a2 = pColorMan->red_a2_lsb | (pColorMan->red_a2_msb << 8); p->u.color_man.green_a3 = pColorMan->green_a3_lsb | (pColorMan->green_a3_msb << 8); p->u.color_man.green_a2 = pColorMan->green_a2_lsb | (pColorMan->green_a2_msb << 8); p->u.color_man.blue_a3 = pColorMan->blue_a3_lsb | (pColorMan->blue_a3_msb << 8); p->u.color_man.blue_a2 = pColorMan->blue_a2_lsb | (pColorMan->blue_a2_msb << 8); } break; case NVT_EDID_DISPLAY_DESCRIPTOR_CVT: // CVT 3-byte timing code { EDID_CVT_3BYTE *pCVT_3byte = (EDID_CVT_3BYTE *)&descriptor->data[0]; for (i=0; iblock[i].addressable_lines != 0) { p->u.cvt.block[i].addressable_lines = pCVT_3byte->block[i].addressable_lines; p->u.cvt.block[i].addressable_lines |= (pCVT_3byte->block[i].lines_ratio & NVT_PVT_EDID_CVT3_LINES_MSB_MASK) << NVT_PVT_EDID_CVT3_LINES_MSB_SHIFT; p->u.cvt.block[i].addressable_lines +=1; p->u.cvt.block[i].addressable_lines <<= 1; p->u.cvt.block[i].aspect_ratio = (pCVT_3byte->block[i].lines_ratio & NVT_PVT_EDID_CVT3_ASPECT_MASK) >> NVT_PVT_EDID_CVT3_ASPECT_SHIFT; p->u.cvt.block[i].preferred_vert_rates = (pCVT_3byte->block[i].refresh_rates & NVT_PVT_EDID_CVT3_PREFERRED_RATE_MASK) >> NVT_PVT_EDID_CVT3_PREFERRED_RATE_SHIFT; p->u.cvt.block[i].supported_vert_rates = (pCVT_3byte->block[i].refresh_rates & NVT_PVT_EDID_CVT3_SUPPORTED_RATE_MASK) >> NVT_PVT_EDID_CVT3_SUPPORTED_RATE_SHIFT; } } } break; case NVT_EDID_DISPLAY_DESCRIPTOR_ESTIII: // establishied timing III { EDID_EST_TIMINGS_III *pEstTiming = (EDID_EST_TIMINGS_III *)&descriptor->data[0]; for(i=0; iu.est3.data[i] = pEstTiming->timing_byte[i]; } } break; case NVT_EDID_DISPLAY_DESCRIPTOR_DUMMY: // dummy descriptor default: // unresolved descriptor yet for (i = 0; i < NVT_PVT_EDID_LDD_PAYLOAD_SIZE; i++) { p->u.dummy.data[i] = descriptor->data[i]; } break; } } // get generic EDID info CODE_SEGMENT(PAGE_DD_CODE) NVT_STATUS NV_STDCALL NvTiming_ParseEDIDInfo(NvU8 *pEdid, NvU32 length, NVT_EDID_INFO *pInfo) { NvU32 i, j, k, data; EDIDV1STRUC *p; NvU8 *pExt; NVT_EDID_CEA861_INFO *p861Info; // parameter check if (pEdid == NULL || length < 128 || pInfo == NULL) { return NVT_STATUS_ERR; } NVMISC_MEMSET(pInfo, 0, sizeof(NVT_EDID_INFO)); // get the EDID version if (getEdidVersion(pEdid, &pInfo->version) == NVT_STATUS_ERR) { return NVT_STATUS_ERR; } p = (EDIDV1STRUC *) pEdid; // get the IDs pInfo->manuf_id = p->wIDManufName; pInfo->product_id = p->wIDProductCode; // translate the ID into manufacturer's name pInfo->manuf_name[0] = 'A' + (NvU8)((pInfo->manuf_id & 0x007c) >> 2) - 1; pInfo->manuf_name[1] = 'A' + (NvU8)((pInfo->manuf_id & 0x0003) << 3 | (pInfo->manuf_id & 0xe000) >> 13) - 1; pInfo->manuf_name[2] = 'A' + (NvU8)((pInfo->manuf_id & 0x1f00) >> 8) - 1; pInfo->manuf_name[3] = '\0'; // get serial number pInfo->serial_number = p->dwIDSerialNumber; // get the week and year pInfo->week = p->bWeekManuf; pInfo->year = p->bYearManuf + 1990; // get the interface info pInfo->input.isDigital = (p->bVideoInputDef & NVT_PVT_EDID_INPUT_ISDIGITAL_MASK) >> NVT_PVT_EDID_INPUT_ISDIGITAL_SHIFT; if (pInfo->input.isDigital && pInfo->version > 0x103) // must be at least EDID1.4 to support the following fields { switch ( (p->bVideoInputDef & NVT_PVT_EDID_INPUT_BPC_MASK) >> NVT_PVT_EDID_INPUT_BPC_SHIFT) { case NVT_PVT_EDID_INPUT_BPC_6 : pInfo->input.u.digital.bpc = 6; break; case NVT_PVT_EDID_INPUT_BPC_8 : pInfo->input.u.digital.bpc = 8; break; case NVT_PVT_EDID_INPUT_BPC_10 : pInfo->input.u.digital.bpc = 10; break; case NVT_PVT_EDID_INPUT_BPC_12 : pInfo->input.u.digital.bpc = 12; break; case NVT_PVT_EDID_INPUT_BPC_14 : pInfo->input.u.digital.bpc = 14; break; case NVT_PVT_EDID_INPUT_BPC_16 : pInfo->input.u.digital.bpc = 16; break; default : pInfo->input.u.digital.bpc = 0; break; } pInfo->input.u.digital.video_interface = (p->bVideoInputDef & NVT_PVT_EDID_INPUT_INTERFACE_MASK) >> NVT_PVT_EDID_INPUT_INTERFACE_SHIFT; } else if (!pInfo->input.isDigital) { pInfo->input.u.analog_data = (p->bVideoInputDef & NVT_PVT_EDID_INPUT_ANALOG_ETC_MASK) >> NVT_PVT_EDID_INPUT_ANALOG_ETC_SHIFT; } // get the max image size and aspect ratio if (p->bMaxHorizImageSize != 0 && p->bMaxVertImageSize != 0) { pInfo->screen_size_x = p->bMaxHorizImageSize; pInfo->screen_size_y = p->bMaxVertImageSize; pInfo->screen_aspect_x = 0; pInfo->screen_aspect_y = 0; } else if (p->bMaxHorizImageSize != 0 && p->bMaxVertImageSize == 0) { pInfo->screen_size_x = 0; pInfo->screen_size_y = 0; pInfo->screen_aspect_x = 99 + p->bMaxHorizImageSize; pInfo->screen_aspect_y = 100; } else if (p->bMaxHorizImageSize == 0 && p->bMaxVertImageSize != 0) { pInfo->screen_size_x = 0; pInfo->screen_size_y = 0; pInfo->screen_aspect_x = 100; pInfo->screen_aspect_y = 99 + p->bMaxVertImageSize; } // get the gamma pInfo->gamma = p->bDisplayXferChar + 100; // get the features pInfo->u.feature = p->bFeatureSupport; // get chromaticity coordinates pInfo->cc_red_x = p->Chromaticity[2] << 2; pInfo->cc_red_x |= (p->Chromaticity[0] & NVT_PVT_EDID_CC_RED_X1_X0_MASK) >> NVT_PVT_EDID_CC_RED_X1_X0_SHIFT; pInfo->cc_red_y = p->Chromaticity[3] << 2; pInfo->cc_red_y |= (p->Chromaticity[0] & NVT_PVT_EDID_CC_RED_Y1_Y0_MASK) >> NVT_PVT_EDID_CC_RED_Y1_Y0_SHIFT; pInfo->cc_green_x = p->Chromaticity[4] << 2; pInfo->cc_green_x |= (p->Chromaticity[0] & NVT_PVT_EDID_CC_GREEN_X1_X0_MASK) >> NVT_PVT_EDID_CC_GREEN_X1_X0_SHIFT; pInfo->cc_green_y = p->Chromaticity[5] << 2; pInfo->cc_green_y |= (p->Chromaticity[0] & NVT_PVT_EDID_CC_GREEN_Y1_Y0_MASK) >> NVT_PVT_EDID_CC_GREEN_Y1_Y0_SHIFT; pInfo->cc_blue_x = p->Chromaticity[6] << 2; pInfo->cc_blue_x |= (p->Chromaticity[1] & NVT_PVT_EDID_CC_BLUE_X1_X0_MASK) >> NVT_PVT_EDID_CC_BLUE_X1_X0_SHIFT; pInfo->cc_blue_y = p->Chromaticity[7] << 2; pInfo->cc_blue_y |= (p->Chromaticity[1] & NVT_PVT_EDID_CC_BLUE_Y1_Y0_MASK) >> NVT_PVT_EDID_CC_BLUE_Y1_Y0_SHIFT; pInfo->cc_white_x = p->Chromaticity[8] << 2; pInfo->cc_white_x |= (p->Chromaticity[1] & NVT_PVT_EDID_CC_WHITE_X1_X0_MASK) >> NVT_PVT_EDID_CC_WHITE_X1_X0_SHIFT; pInfo->cc_white_y = p->Chromaticity[9] << 2; pInfo->cc_white_y |= (p->Chromaticity[1] & NVT_PVT_EDID_CC_WHITE_Y1_Y0_MASK) >> NVT_PVT_EDID_CC_WHITE_Y1_Y0_SHIFT; // copy established timings pInfo->established_timings_1_2 = (NvU16)p->bEstablishedTimings1 << 8; pInfo->established_timings_1_2 |= (NvU16)p->bEstablishedTimings2; // copy manuf reserved timings pInfo->manufReservedTimings = p->bManufReservedTimings; // copy standard timings for (i = 0; i < NVT_EDID_MAX_STANDARD_TIMINGS; i++) { pInfo->standard_timings[i] = p->wStandardTimingID[i]; } // get the number of extensions pInfo->total_extensions = p->bExtensionFlag; // check_sum for (i = 0, data = 0; i < length; i++) { data += pEdid[i]; } pInfo->checksum_ok = !(data & 0xFF); pInfo->checksum = p->bChecksum; // now find out the total number of all of the timings in the EDID pInfo->total_timings = 0; // now find out the detailed timings parseEdidDetailedTiming(pEdid, pInfo); // now parse all 18-byte long display descriptors (not detailed timing) for (i = 0; i < NVT_EDID_MAX_LONG_DISPLAY_DESCRIPTOR; i++) { parseEdidLongDisplayDescriptor((EDID_LONG_DISPLAY_DESCRIPTOR *)&p->DetailedTimingDesc[i], &pInfo->ldd[i], pInfo->version); } // now check the number of timings in the extension for (k = 0, j = 1; j <= pInfo->total_extensions && (j + 1) * sizeof(EDIDV1STRUC) <= length; j++) { pExt = pEdid + sizeof(EDIDV1STRUC) * j; // check for 861 extension first switch (*pExt) { case NVT_EDID_EXTENSION_CTA: p861Info = (k == 0) ? &pInfo->ext861 : &pInfo->ext861_2; get861ExtInfo(pExt, sizeof(EDIDV1STRUC), p861Info); // HF EEODB is present in edid v1.3 and v1.4 does not need this.Also, it is always present in the 1st CTA extension block. if (j == 1 && pInfo->version == NVT_EDID_VER_1_3) { parseCta861HfEeodb(p861Info, &pInfo->total_extensions); } // update pInfo with basic hdmi info // assumes each edid will only have one such block across multiple cta861 blocks (otherwise may create declaration conflict) // In case of multiple such blocks, the last one takes precedence, except for SCDB // parseCta861VsdbBlocks() uses hfScdb info so need to be parsed first parseCta861HfScdb(p861Info, pInfo, FROM_CTA861_EXTENSION); parseCta861VsdbBlocks(p861Info, pInfo, FROM_CTA861_EXTENSION); parseCta861VsvdbBlocks(p861Info, pInfo, FROM_CTA861_EXTENSION); // parse HDR related information from the HDR static metadata data block if (p861Info->valid.hdr_static_metadata != 0) { parseCta861HdrStaticMetadataDataBlock(p861Info, pInfo, FROM_CTA861_EXTENSION); } // Timings are listed (or shall) be listed in priority order // So read SVD, yuv420 SVDs first before reading detailed timings // add the 861B short video timing descriptor if (p861Info->revision >= NVT_CEA861_REV_B) { // base video parse861bShortTiming(p861Info, pInfo, FROM_CTA861_EXTENSION); // yuv420-only video parse861bShortYuv420Timing(p861Info, pInfo, FROM_CTA861_EXTENSION); } // add the detailed timings in 18-byte long display descriptor parse861ExtDetailedTiming(pExt, p861Info->basic_caps, pInfo); if (p861Info->revision >= NVT_CTA861_REV_H) { if (p861Info->total_vfdb != 0) parseCta861VideoFormatDataBlock(p861Info, pInfo); if (p861Info->total_did_type7db != 0) parseCta861DIDType7VideoTimingDataBlock(p861Info, pInfo); if (p861Info->total_did_type8db != 0) parseCta861DIDType8VideoTimingDataBlock(p861Info, pInfo); if (p861Info->total_did_type10db != 0) parseCta861DIDType10VideoTimingDataBlock(p861Info, pInfo); } // CEA861-F at 7.5.12 section about VFPDB block. if (p861Info->revision >= NVT_CEA861_REV_F && (p861Info->total_svr != 0 || p861Info->valid.NVRDB == 1)) { parseCta861NativeOrPreferredTiming(p861Info, pInfo, FROM_CTA861_EXTENSION); } k++; break; case NVT_EDID_EXTENSION_VTB: parseVTBExtension(pExt, pInfo); break; case NVT_EDID_EXTENSION_DISPLAYID: if ((pExt[1] & 0xF0) == 0x20) // displayID2.x as EDID extension { if(getDisplayId20EDIDExtInfo(pExt, sizeof(EDIDV1STRUC), pInfo) == NVT_STATUS_SUCCESS) { if (pInfo->ext861.total_y420vdb != 0 || pInfo->ext861.total_y420cmdb != 0) { pInfo->ext_displayid20.interface_features.yuv420_min_pclk = 0; } if (pInfo->ext_displayid20.valid_data_blocks.interface_feature_present) { pInfo->ext861.basic_caps |= pInfo->ext_displayid20.basic_caps; } } } else // displayID13 as EDID extension { //do not fail function based on return value of getDisplayIdEDIDExtInfo refer bug 3247180 where some rogue monitors don't provide correct DID13 raw data. if (getDisplayIdEDIDExtInfo(pExt, sizeof(EDIDV1STRUC), pInfo) == NVT_STATUS_SUCCESS) { // Check if YCbCr is supported in base block // since it is mandatory if YCbCr is supported on any other display interface as per 5.1.1.1 Video Colorimetry if(pInfo->u.feature_ver_1_4_digital.support_ycrcb_444) { if (!pInfo->ext_displayid.supported_displayId2_0) { pInfo->ext_displayid.u4.display_interface.ycbcr444_depth.support_8b = 1; } else { pInfo->ext_displayid.u4.display_interface_features.ycbcr444_depth.support_8b = 1; } } if(pInfo->u.feature_ver_1_4_digital.support_ycrcb_422) { if (!pInfo->ext_displayid.supported_displayId2_0) { pInfo->ext_displayid.u4.display_interface.ycbcr422_depth.support_8b = 1; } else { pInfo->ext_displayid.u4.display_interface_features.ycbcr422_depth.support_8b = 1; } } } } break; default: break; } } // Copy all the timings(could include type 7/8/9/10) from displayid20->timings[] to pEdidInfo->timings[] for (i = 0; i < pInfo->ext_displayid20.total_timings; i++) { if (!assignNextAvailableTiming(pInfo, &(pInfo->ext_displayid20.timing[i]))) { return NVT_STATUS_ERR; } } // check for cvt timings - in display range limits or cvt 3-byte LDD, only for EDID1.4 and above if (pInfo->version > NVT_EDID_VER_1_3) { parseEdidCvtTiming(pInfo); } // now check for standard timings - base EDID and then the LDDs parseEdidStandardTiming(pInfo); // find out the total established timings - base EDID and then the LDDs parseEdidEstablishedTiming(pInfo); // remove the T8VTDB timing if it co-existed in standard or established timings if (pInfo->ext861.revision >= NVT_CTA861_REV_H && pInfo->ext861.total_did_type8db != 0 && pInfo->total_timings > 1) { for (i = 0; i < pInfo->total_timings; i++) { if (NVT_GET_TIMING_STATUS_TYPE(pInfo->timing[i].etc.status) == NVT_TYPE_CTA861_DID_T8) { if (isMatchedStandardTiming(pInfo, &pInfo->timing[i]) || isMatchedEstablishedTiming(pInfo, &pInfo->timing[i])) { for (j = i; j < pInfo->total_timings - 1; j++) { // remove the entry by moving the next entry up. pInfo->timing[j] = pInfo->timing[j+1]; } NVMISC_MEMSET(&pInfo->timing[pInfo->total_timings-1], 0, sizeof(NVT_TIMING)); pInfo->total_timings--; i--; } } } } getEdidHDM1_4bVsdbTiming(pInfo); // Assert if no timings were found (due to a bad EDID) or if we mistakenly // assigned more timings than we allocated space for (due to bad logic above) nvt_assert(pInfo->total_timings && (pInfo->total_timings <= COUNT(pInfo->timing))); // go through all timings and update supported color formats // consider the supported bpc per color format from parsed EDID / CTA861 / DisplayId updateColorFormatAndBpcTiming(pInfo); return NVT_STATUS_SUCCESS; } CODE_SEGMENT(PAGE_DD_CODE) void updateColorFormatAndBpcTiming(NVT_EDID_INFO *pInfo) { NvU32 i, j, data; for (i = 0; i < pInfo->total_timings; i++) { data = NVT_GET_TIMING_STATUS_TYPE(pInfo->timing[i].etc.status); switch (data) { case NVT_TYPE_HDMI_STEREO: case NVT_TYPE_HDMI_EXT: // VTB timing use the base EDID (block 0) to determine the color format support case NVT_TYPE_EDID_VTB_EXT: case NVT_TYPE_EDID_VTB_EXT_STD: case NVT_TYPE_EDID_VTB_EXT_DTD: case NVT_TYPE_EDID_VTB_EXT_CVT: // pInfo->u.feature_ver_1_3.color_type provides mono, rgb, rgy, undefined // assume RGB 8-bpc support only (VTB is pretty old edid standard) pInfo->timing[i].etc.rgb444.bpc.bpc8 = 1; break; // These are from the CTA block, and relies on // Since there could be multiple CEA blocks, these are adjusted when the blocks are parsed case NVT_TYPE_EDID_861ST: case NVT_TYPE_EDID_EXT_DTD: if (pInfo->ext_displayid20.as_edid_extension && pInfo->ext_displayid20.valid_data_blocks.cta_data_present) { updateColorFormatForDisplayId20ExtnTimings(pInfo, i); } updateBpcForTiming(pInfo, i); break; default: // * the displayID_v1.3/v2.0 EDID extension need to follow the EDID bpc definition. // * all other default to base edid updateBpcForTiming(pInfo, i); } // The timings[i] entries need to update the bpc values where are based on the different color format again // if displayId extension existed it's interface feature data block if (pInfo->ext_displayid.version == 0x12 || pInfo->ext_displayid.version == 0x13) { updateColorFormatForDisplayIdExtnTimings(pInfo, i); } else if (pInfo->ext_displayid20.valid_data_blocks.interface_feature_present) { // DisplayId2.0 spec has its own way of determining color format support which includes bpc + color format updateColorFormatForDisplayId20ExtnTimings(pInfo, i); } } // Go through all the timings and set CTA format accordingly. If a timing is a CTA 861b timing, store the // index of this CTA 861b standard in NVT_TIMING.etc.status field. // However parser needs to exclude the DTD timing in EDID base block where is shared same detailed timing in VIC/DTD_ext in CTA861 for (i = 0; i < pInfo->total_timings; i++) { data = NvTiming_GetCEA861TimingIndex(&pInfo->timing[i]); // DisplayID block did not belong to CTA timing and it owned the deep color block itself if (data && !((NVT_GET_TIMING_STATUS_TYPE(pInfo->timing[i].etc.status) == NVT_TYPE_DISPLAYID_1) || (NVT_GET_TIMING_STATUS_TYPE(pInfo->timing[i].etc.status) == NVT_TYPE_DISPLAYID_2) || (NVT_GET_TIMING_STATUS_TYPE(pInfo->timing[i].etc.status) == NVT_TYPE_DISPLAYID_7) || (NVT_GET_TIMING_STATUS_TYPE(pInfo->timing[i].etc.status) == NVT_TYPE_DISPLAYID_8) || (NVT_GET_TIMING_STATUS_TYPE(pInfo->timing[i].etc.status) == NVT_TYPE_DISPLAYID_9) || (NVT_GET_TIMING_STATUS_TYPE(pInfo->timing[i].etc.status) == NVT_TYPE_DISPLAYID_10))) { // CEA timings may be enumerated outside of SVD blocks -- the formats of these timings don't have CEA FORMAT (vic) set // before marking them CEA, make sure their color formats are updated too if (NVT_GET_CEA_FORMAT(pInfo->timing[i].etc.status) == 0 && (!NVT_IS_DTD(pInfo->timing[i].etc.status) || isMatchedCTA861Timing(pInfo, &pInfo->timing[i]))) { for (j = 0; j < pInfo->total_timings; j++) { // It is assumed CTA timings that are repeated by the CTA block or different CTA blocks will // announce the same color format for the same CTA timings if (NVT_GET_CEA_FORMAT(pInfo->timing[j].etc.status) == data) { // There could be anomalies between EDID 1.4 base block color format vs CEA861 basic caps // In this case we assume the union is supported pInfo->timing[i].etc.rgb444.bpcs |= pInfo->timing[j].etc.rgb444.bpcs; pInfo->timing[i].etc.yuv444.bpcs |= pInfo->timing[j].etc.yuv444.bpcs; pInfo->timing[i].etc.yuv422.bpcs |= pInfo->timing[j].etc.yuv422.bpcs; pInfo->timing[i].etc.yuv420.bpcs |= pInfo->timing[j].etc.yuv420.bpcs; break; } } // now update the VIC of this timing NVT_SET_CEA_FORMAT(pInfo->timing[i].etc.status, data); } // see the aspect ratio info if needed if (pInfo->timing[i].etc.aspect == 0) { pInfo->timing[i].etc.aspect = getCEA861TimingAspectRatio(data); } } } } CODE_SEGMENT(PAGE_DD_CODE) NvBool isMatchedStandardTiming(NVT_EDID_INFO *pInfo, NVT_TIMING *pT) { NvU32 j; for (j = 0; j < pInfo->total_timings; j++) { if (NVT_GET_TIMING_STATUS_TYPE(pInfo->timing[j].etc.status) == NVT_TYPE_EDID_STD && NvTiming_IsTimingRelaxedEqual(&pInfo->timing[j], pT)) { return NV_TRUE; } } return NV_FALSE; } CODE_SEGMENT(PAGE_DD_CODE) NvBool isMatchedEstablishedTiming(NVT_EDID_INFO *pInfo, NVT_TIMING *pT) { NvU32 j; for (j = 0; j < pInfo->total_timings; j++) { if (NVT_GET_TIMING_STATUS_TYPE(pInfo->timing[j].etc.status) == NVT_TYPE_EDID_EST && NvTiming_IsTimingRelaxedEqual(&pInfo->timing[j], pT)) { return NV_TRUE; } } return NV_FALSE; } CODE_SEGMENT(PAGE_DD_CODE) NvBool isMatchedCTA861Timing(NVT_EDID_INFO *pInfo, NVT_TIMING *pT) { NvU32 j; for (j = 0; j < pInfo->total_timings; j++) { if (NVT_GET_CEA_FORMAT(pInfo->timing[j].etc.status) && NvTiming_IsTimingExactEqual(&pInfo->timing[j], pT)) { return NV_TRUE; } } return NV_FALSE; } CODE_SEGMENT(PAGE_DD_CODE) void updateBpcForTiming(NVT_EDID_INFO *pInfo, NvU32 index) { NVT_EDID_CEA861_INFO *p861Info; // assume/prefer data from 1st CEA block if multiple exist p861Info = &pInfo->ext861; pInfo->timing[index].etc.rgb444.bpc.bpc8 = 1; if (pInfo->version >= NVT_EDID_VER_1_4 && pInfo->input.isDigital) { if (pInfo->u.feature_ver_1_4_digital.support_ycrcb_444) { pInfo->timing[index].etc.yuv444.bpc.bpc8 = 1; } if (pInfo->u.feature_ver_1_4_digital.support_ycrcb_422) { pInfo->timing[index].etc.yuv422.bpc.bpc8 = 1; } if (pInfo->input.u.digital.video_interface == NVT_EDID_DIGITAL_VIDEO_INTERFACE_STANDARD_DISPLAYPORT_SUPPORTED || pInfo->input.u.digital.video_interface == NVT_EDID_DIGITAL_VIDEO_INTERFACE_STANDARD_UNDEFINED) { pInfo->timing[index].etc.rgb444.bpc.bpc6 = 1; // trust bpc claim in edid base block for DP only if (pInfo->input.u.digital.bpc >= NVT_EDID_VIDEOSIGNAL_BPC_10) { pInfo->timing[index].etc.rgb444.bpc.bpc10 = 1; pInfo->timing[index].etc.yuv444.bpc.bpc10 = pInfo->u.feature_ver_1_4_digital.support_ycrcb_444 || (p861Info->basic_caps & NVT_CEA861_CAP_YCbCr_444); pInfo->timing[index].etc.yuv422.bpc.bpc10 = pInfo->u.feature_ver_1_4_digital.support_ycrcb_422 || (p861Info->basic_caps & NVT_CEA861_CAP_YCbCr_422); } if (pInfo->input.u.digital.bpc >= NVT_EDID_VIDEOSIGNAL_BPC_12) { pInfo->timing[index].etc.rgb444.bpc.bpc12 = 1; pInfo->timing[index].etc.yuv444.bpc.bpc12 = pInfo->u.feature_ver_1_4_digital.support_ycrcb_444 || (p861Info->basic_caps & NVT_CEA861_CAP_YCbCr_444); pInfo->timing[index].etc.yuv422.bpc.bpc12 = pInfo->u.feature_ver_1_4_digital.support_ycrcb_422 || (p861Info->basic_caps & NVT_CEA861_CAP_YCbCr_422); } if (pInfo->input.u.digital.bpc >= NVT_EDID_VIDEOSIGNAL_BPC_16) { pInfo->timing[index].etc.rgb444.bpc.bpc16 = 1; pInfo->timing[index].etc.yuv444.bpc.bpc16 = pInfo->u.feature_ver_1_4_digital.support_ycrcb_444 || (p861Info->basic_caps & NVT_CEA861_CAP_YCbCr_444); pInfo->timing[index].etc.yuv422.bpc.bpc16 = pInfo->u.feature_ver_1_4_digital.support_ycrcb_422 || (p861Info->basic_caps & NVT_CEA861_CAP_YCbCr_422); } } else if ((pInfo->input.u.digital.video_interface == NVT_EDID_DIGITAL_VIDEO_INTERFACE_STANDARD_HDMI_A_SUPPORTED || pInfo->input.u.digital.video_interface == NVT_EDID_DIGITAL_VIDEO_INTERFACE_STANDARD_HDMI_B_SUPPORTED || pInfo->input.u.digital.video_interface == NVT_EDID_DIGITAL_VIDEO_INTERFACE_STANDARD_UNDEFINED) && p861Info->revision >= NVT_CEA861_REV_A) { updateHDMILLCDeepColorForTiming(pInfo, index); } } else if (p861Info->revision >= NVT_CEA861_REV_A) { updateHDMILLCDeepColorForTiming(pInfo, index); } } CODE_SEGMENT(PAGE_DD_CODE) NVT_STATUS NvTiming_Get18ByteLongDescriptorIndex(NVT_EDID_INFO *pEdidInfo, NvU8 tag, NvU32 *pDtdIndex) { NvU32 dtdIndex; if (!pEdidInfo || !pDtdIndex) { return NVT_STATUS_ERR; } for (dtdIndex = *pDtdIndex; dtdIndex < NVT_EDID_MAX_LONG_DISPLAY_DESCRIPTOR; dtdIndex++) { if (pEdidInfo->ldd[dtdIndex].tag == tag) { *pDtdIndex = dtdIndex; return NVT_STATUS_SUCCESS; } } return NVT_STATUS_ERR; } // get the edid timing CODE_SEGMENT(PAGE_DD_CODE) NVT_STATUS NvTiming_GetEdidTimingExWithPclk(NvU32 width, NvU32 height, NvU32 rr, NvU32 flag, NVT_EDID_INFO *pEdidInfo, NVT_TIMING *pT, NvU32 rrx1k, NvU32 pclk) { NvU8 kth = 0; NvU32 i, j; NvU32 native_cta, preferred_cta, preferred_displayid_dtd, preferred_dtd1, dtd1, map0, map1, map2, map3, map4, ceaIndex, max, cvt; NVT_TIMING *pEdidTiming; NVT_EDID_DD_RANGE_CVT *pCVT = NULL; NVT_TIMING cvtTiming; // input check if (pEdidInfo == NULL || pEdidInfo->total_timings == 0 || pT == 0) return NVT_STATUS_ERR; if (width == 0 || height == 0 || rr == 0 ) // rrx1k and pclk are optional, can be 0. return NVT_STATUS_ERR; pEdidTiming = pEdidInfo->timing; // the timing mapping index : // // native_cta - the "native resoluiotn of the sink" in the CTA861.6 A Source shall override any other native video resolution indicators // if the Source supports NVRDB and the NVRDB was found in the E-EDID // preferred_cta - the "prefer SVD" in CTA-861-F (i.e. A Sink that prefers a Video Format that is not listed as an SVD in Video Data Block, but instead listed in YCBCR 4:2:0 VDB) // preferred_displayid_dtd - the "prefer detailed timing of DispalyID" extension // preferred_dtd1 - the first deatiled timing and PTM flag is enable // dtd1 - the first detailed timing // map0 - the "perfect" match (the timing's H/V-visible and pixel clock(refresh rate) are the same as the asking "width", "height" and "rr". // map1 - the "closest" match with the honor of the interlaced flag // map2 - the "closest" match without the honor of the interlaced flag // map3 - the "closest" match to the panel's native timing (i.e. the first DTD timing or the short 861B/C/D timings with "native" flag). // map4 - the "closest" match with the same refresh rate // max - the timing with the max visible area native_cta = preferred_cta = preferred_displayid_dtd = preferred_dtd1 = dtd1 = map0 = map1 = map2 = map3 = map4 = ceaIndex = pEdidInfo->total_timings; max = cvt = 0; if (pEdidInfo->ext861.total_svr > 1) { kth = getHighestPrioritySVRIdx(&pEdidInfo->ext861); } for (i = 0; i < pEdidInfo->total_timings; i++) { // if the client prefers _NATIVE timing, then don't select custom timing if ((flag & (NVT_FLAG_NATIVE_TIMING | NVT_FLAG_EDID_TIMING)) != 0 && NVT_IS_CUST_ENTRY(pEdidTiming[i].etc.status) != 0) { continue; } // find the perfect match is possible if ((flag & NVT_FLAG_MAX_EDID_TIMING) == 0 && width == pEdidTiming[i].HVisible && height == frame_height(pEdidTiming[i]) && rr == pEdidTiming[i].etc.rr && ((rrx1k == 0) || (rrx1k == pEdidTiming[i].etc.rrx1k)) && !!(flag & NVT_PVT_INTERLACED_MASK) == !!pEdidTiming[i].interlaced) { if (map0 >= pEdidInfo->total_timings || pEdidTiming[i].pclk == pclk) { // make sure we take the priority as "detailed>standard>established". (The array timing[] always have the detailed timings in the front and then the standard and established.) map0 = i; } if ( (NVT_PREFERRED_TIMING_IS_CTA(pEdidTiming[i].etc.flag)) || ((0 == (flag & NVT_FLAG_EDID_861_ST)) && NVT_PREFERRED_TIMING_IS_DTD1(pEdidTiming[i].etc.flag, pEdidTiming[i].etc.status)) || (NVT_PREFERRED_TIMING_IS_DISPLAYID(pEdidTiming[i].etc.flag)) || (NVT_IS_NATIVE_TIMING(pEdidTiming[i].etc.status))) { *pT = pEdidTiming[i]; return NVT_STATUS_SUCCESS; } if (NVT_GET_TIMING_STATUS_TYPE(pEdidTiming[i].etc.status) == NVT_TYPE_EDID_861ST) { if (ceaIndex == pEdidInfo->total_timings) { // Save the first entry found. ceaIndex = i; } else { if (((flag & NVT_FLAG_CEA_4X3_TIMING) && (pEdidTiming[i].etc.aspect == 0x40003)) || ((flag & NVT_FLAG_CEA_16X9_TIMING) && (pEdidTiming[i].etc.aspect == 0x160009))) { // Use preferred aspect ratio if specified. ceaIndex = i; } } } } // if ((flag & NVT_FLAG_MAX_EDID_TIMING) == 0 && // bypass the custom timing to be select for the mismatch case if (NVT_GET_TIMING_STATUS_TYPE(pEdidTiming[i].etc.status) == NVT_TYPE_CUST || NVT_IS_CUST_ENTRY(pEdidTiming[i].etc.status) != 0) { if (width != pEdidTiming[i].HVisible || height != frame_height(pEdidTiming[i]) || rr != pEdidTiming[i].etc.rr) { continue; } } // find out the preferred timing just in case of cea_vfpdb is existed if (native_cta == pEdidInfo->total_timings && NVT_NATIVE_TIMING_IS_CTA(pEdidTiming[i].etc.flag)) { native_cta = i; } if (preferred_cta == pEdidInfo->total_timings && NVT_PREFERRED_TIMING_IS_CTA(pEdidTiming[i].etc.flag)) { if (pEdidInfo->ext861.total_svr > 1) { if (kth != 0) { // svr == vic if (NVT_IS_CTA861(pEdidTiming[i].etc.status) && (NVT_GET_CEA_FORMAT(pEdidTiming[i].etc.status) == kth)) { preferred_cta = i; } else if (NVT_GET_TIMING_STATUS_SEQ(pEdidTiming[i].etc.status) == kth) { preferred_cta = i; } } } else { preferred_cta = i; } } // find out the preferred timing just in case // Caller we will force rr value as 1 to select the DisplayID prefer timing in pEdidTiming if it existed // however, we can't assign the correct refresh rate we want if we had two and above rr values which shared the same timing. if (rr != 1) { if (pEdidTiming[i].etc.rr == rr && NVT_PREFERRED_TIMING_IS_DISPLAYID(pEdidTiming[i].etc.flag)) { preferred_displayid_dtd = i; } } else if (preferred_displayid_dtd == pEdidInfo->total_timings && NVT_PREFERRED_TIMING_IS_DISPLAYID(pEdidTiming[i].etc.flag)) { preferred_displayid_dtd = i; } if (NVT_PREFERRED_TIMING_IS_DTD1(pEdidTiming[i].etc.flag, pEdidTiming[i].etc.status)) { preferred_dtd1 = i; } if (NVT_IS_DTD1(pEdidTiming[i].etc.status)) { dtd1 = i; } // find out the max mode just in case if (pEdidTiming[i].HVisible * pEdidTiming[i].VVisible > pEdidTiming[max].HVisible * pEdidTiming[max].VVisible) max = i; // if the requested timing is not in the EDID, try to find out the EDID entry with the same progressive/interlaced setting if (map1 >= pEdidInfo->total_timings) { if (!!(flag & NVT_PVT_INTERLACED_MASK) == !!pEdidTiming[i].interlaced && width <= pEdidTiming[i].HVisible && height <= frame_height(pEdidTiming[i])) { map1 = i; } } else { if (!!(flag & NVT_PVT_INTERLACED_MASK) == !!pEdidTiming[i].interlaced && width <= pEdidTiming[i].HVisible && height <= frame_height(pEdidTiming[i]) && abs_delta(pEdidTiming[i].HVisible, width) <= abs_delta(pEdidTiming[map1].HVisible, width) && abs_delta(frame_height(pEdidTiming[i]), height) <= abs_delta(frame_height(pEdidTiming[map1]), height)) { // if there're 2 timings with the same visible size, choose the one with closer refresh rate if (pEdidTiming[i].HVisible == pEdidTiming[map1].HVisible && frame_height(pEdidTiming[i]) == frame_height(pEdidTiming[map1])) { if (abs_delta(pEdidTiming[i].etc.rr, rr) < abs_delta(pEdidTiming[map1].etc.rr, rr)) { map1 = i; } } else { map1 = i; } } } // if the requested timing is not in the EDID, try to find out the EDID entry without the progressive/interlaced setting if (map2 >= pEdidInfo->total_timings) { if (width <= pEdidTiming[i].HVisible && height <= frame_height(pEdidTiming[i])) { map2 = i; } } else { if (width <= pEdidTiming[i].HVisible && height <= frame_height(pEdidTiming[i]) && abs_delta(pEdidTiming[i].HVisible, width) <= abs_delta(pEdidTiming[map2].HVisible, width) && abs_delta(frame_height(pEdidTiming[i]), height) <= abs_delta(frame_height(pEdidTiming[map2]), height)) { // if there're 2 timings with the same visible size, choose the one with closer refresh rate if (pEdidTiming[i].HVisible == pEdidTiming[map2].HVisible && frame_height(pEdidTiming[i]) == frame_height(pEdidTiming[map2])) { if (abs_delta(pEdidTiming[i].etc.rr, rr) < abs_delta(pEdidTiming[map2].etc.rr, rr)) { map2 = i; } } else { map2 = i; } } } // find out the native timing if (NVT_IS_NATIVE_TIMING(pEdidTiming[i].etc.status) || NVT_IS_DTD1(pEdidTiming[i].etc.status)) { if (map3 >= pEdidInfo->total_timings) { if (width <= pEdidTiming[i].HVisible && height <= frame_height(pEdidTiming[i])) { map3 = i; } } else if(abs_delta(pEdidTiming[i].HVisible, width) <= abs_delta(pEdidTiming[map3].HVisible, width) && abs_delta(frame_height(pEdidTiming[i]), height) <= abs_delta(frame_height(pEdidTiming[map3]), height) && width <= pEdidTiming[i].HVisible && height <= frame_height(pEdidTiming[i])) { map3 = i; } } // find the edid timing with refresh rate matching if (map4 >= pEdidInfo->total_timings) { if (width <= pEdidTiming[i].HVisible && height <= pEdidTiming[i].VVisible && rr == pEdidTiming[i].etc.rr) { map4 = i; } } else { if (width <= pEdidTiming[i].HVisible && height <= pEdidTiming[i].HVisible && rr == pEdidTiming[i].etc.rr && abs_delta(pEdidTiming[i].HVisible, width) <= abs_delta(pEdidTiming[map4].HVisible, width) && abs_delta(pEdidTiming[i].VVisible, height) <= abs_delta(pEdidTiming[map4].VVisible, height)) { map4 = i; } } }//for (i = 0; i < pEdidInfo->total_timings; i++) if ( (preferred_displayid_dtd == preferred_dtd1) && (preferred_dtd1 == dtd1) && (dtd1 == map0) && (map0 == map1) && (map1 == map2) && (map2 == map3) && (map3 == map4) && (map4 == pEdidInfo->total_timings) && pEdidInfo->version >= NVT_EDID_VER_1_4 && pEdidInfo->u.feature_ver_1_4_digital.continuous_frequency && !(flag & NVT_PVT_INTERLACED_MASK)) { // try to find CVT timing that fits NvU32 maxHeight, minHeight, tempHeight; minHeight = ~0; maxHeight = tempHeight= 0; // looping through long display descriptors for (i=0; ildd[i].tag != NVT_EDID_DISPLAY_DESCRIPTOR_DRL || pEdidInfo->ldd[i].u.range_limit.timing_support != NVT_EDID_RANGE_SUPPORT_CVT) { continue; } pCVT = &pEdidInfo->ldd[i].u.range_limit.u.cvt; if (width <= pCVT->max_active_pixels_per_line || (pCVT->scaling_support & NVT_EDID_CVT_SCALING_HOR_SHRINK)) { for (j=0; jaspect_supported & (1< tempHeight) { minHeight = tempHeight; } if (maxHeight < tempHeight) { maxHeight = tempHeight; } }//for (j=0; j<5; j++) }//if (width <= pCVT->max_active_pixels_per_line || (pCVT->scaling_support & NVT_EDID_CVT_SCALING_HOR_STRETCH)) if ( ((minHeight < height) && (pCVT->scaling_support & NVT_EDID_CVT_SCALING_VER_SHRINK)) || ((maxHeight > height) && (pCVT->scaling_support & NVT_EDID_CVT_SCALING_VER_STRETCH)) ) { cvt = 1; } if (cvt) { break; } }//for (i=0; iblanking_support & NVT_EDID_CVT_BLANKING_REDUCED && NvTiming_CalcCVT_RB(width, height, rr, NVT_PROGRESSIVE, &cvtTiming) == NVT_STATUS_SUCCESS) { if ( cvtTiming.pclk > (NvU32)((pEdidInfo->ldd[i].u.range_limit.max_pclk_MHz * 100) - (pCVT->pixel_clock_adjustment * 25)) ) { cvt = 0; } } else if (pCVT->blanking_support & NVT_EDID_CVT_BLANKING_STANDARD && NvTiming_CalcCVT(width, height, rr, NVT_PROGRESSIVE, &cvtTiming) == NVT_STATUS_SUCCESS) { if ( cvtTiming.pclk > (NvU32)((pEdidInfo->ldd[i].u.range_limit.max_pclk_MHz * 100) - (pCVT->pixel_clock_adjustment * 25)) ) { cvt = 0; } } else { cvt = 0; } } }//(dtd1 == map0 == map1 == map2 == map3 == pEdidInfo->total_timings) && pEdidInfo->version >= NVT_EDID_VER_1_4 && // pEdidInfo->feature_ver_1_4_digital.continuous_frequency && !(flag & NVT_PVT_INTERLACED_MASK)) // now return the mismatched EDID timing if (flag & NVT_FLAG_NV_PREFERRED_TIMING) { *pT = (preferred_displayid_dtd != pEdidInfo->total_timings) ? pEdidTiming[preferred_displayid_dtd] : (native_cta != pEdidInfo->total_timings) ? pEdidTiming[native_cta] : (preferred_cta != pEdidInfo->total_timings) ? pEdidTiming[preferred_cta] : (preferred_dtd1 != pEdidInfo->total_timings) ? pEdidTiming[preferred_dtd1] : pEdidTiming[dtd1]; // what if DTD1 itself is filtered out, in such case dtd1 index points to an invalid timing[]? // (dtd1 != pEdidInfo->total_timings) ? pEdidTiming[dtd1] : pEdidTiming[0]; } else if (flag & NVT_FLAG_DTD1_TIMING) { *pT = pEdidTiming[dtd1]; } else if ((flag & NVT_FLAG_MAX_EDID_TIMING) && (0 == (flag & NVT_FLAG_EDID_861_ST))) { *pT = pEdidTiming[max]; } else if ((flag & (NVT_FLAG_CEA_4X3_TIMING | NVT_FLAG_CEA_16X9_TIMING | NVT_FLAG_EDID_861_ST)) && ceaIndex < (pEdidInfo->total_timings)) { *pT = pEdidTiming[ceaIndex]; } else if ((flag & NVT_FLAG_NATIVE_TIMING) != 0 && map3 < pEdidInfo->total_timings) { // Allow closest refresh rate match when EDID has detailed timing for different RR on native resolution. if (map0 < pEdidInfo->total_timings && pEdidTiming[map0].HVisible == pEdidTiming[map3].HVisible && pEdidTiming[map0].VVisible == pEdidTiming[map3].VVisible) { *pT = pEdidTiming[map0]; } else { *pT = pEdidTiming[map3]; } } else if (map0 < pEdidInfo->total_timings) { // use the exact mapped timing if possible *pT = pEdidTiming[map0]; } else if ((flag & NVT_FLAG_EDID_TIMING_RR_MATCH) && map4 < pEdidInfo->total_timings) { *pT = pEdidTiming[map4]; } else if (map1 < pEdidInfo->total_timings) { // use the mapped timing if possible *pT = pEdidTiming[map1]; } else if (map2 < pEdidInfo->total_timings) { // use the 2nd mapped timing if possible *pT = pEdidTiming[map2]; } else if (dtd1 < pEdidInfo->total_timings && width <= pEdidTiming[dtd1].HVisible && height <= pEdidTiming[dtd1].VVisible) { // use the 1st detailed timing if possible *pT = pEdidTiming[dtd1]; } else if (cvt) { // use the cvt timing *pT = cvtTiming; } else { // use the max timing for all other cases *pT = pEdidTiming[max]; } // set the mismatch status if (pT->HVisible != width || frame_height(*pT) != height) { NVT_SET_TIMING_STATUS_MISMATCH(pT->etc.status, NVT_STATUS_TIMING_MISMATCH_SIZE); } if (!NvTiming_IsRoundedRREqual(pT->etc.rr, pT->etc.rrx1k, (NvU16)rr)) { NVT_SET_TIMING_STATUS_MISMATCH(pT->etc.status, NVT_STATUS_TIMING_MISMATCH_RR); } if (!!pT->interlaced != !!(flag & NVT_PVT_INTERLACED_MASK)) { NVT_SET_TIMING_STATUS_MISMATCH(pT->etc.status, NVT_STATUS_TIMING_MISMATCH_FORMAT); } return NVT_STATUS_SUCCESS; } CODE_SEGMENT(PAGE_DD_CODE) NVT_STATUS NvTiming_GetEdidTimingEx(NvU32 width, NvU32 height, NvU32 rr, NvU32 flag, NVT_EDID_INFO *pEdidInfo, NVT_TIMING *pT, NvU32 rrx1k) { return NvTiming_GetEdidTimingExWithPclk(width, height, rr, flag, pEdidInfo, pT, rrx1k, 0); } // get the edid timing CODE_SEGMENT(PAGE_DD_CODE) NVT_STATUS NvTiming_GetEdidTiming(NvU32 width, NvU32 height, NvU32 rr, NvU32 flag, NVT_EDID_INFO *pEdidInfo, NVT_TIMING *pT) { return NvTiming_GetEdidTimingEx(width, height, rr, flag, pEdidInfo, pT, 0); } CODE_SEGMENT(PAGE_DD_CODE) NVT_STATUS NvTiming_GetHDMIStereoExtTimingFromEDID(NvU32 width, NvU32 height, NvU32 rr, NvU8 StereoStructureType, NvU8 SideBySideHalfDetail, NvU32 flag, NVT_EDID_INFO *pEdidInfo, NVT_EXT_TIMING *pT) { NVT_STATUS status = NVT_STATUS_ERR; NvU8 Vic; NvU32 i; NVT_TIMING Timing; NVMISC_MEMSET(pT, 0, sizeof(NVT_EXT_TIMING)); // adjust the flags -- // need EDID timing with RR match, // not max timing, flag = flag | NVT_FLAG_EDID_TIMING | NVT_FLAG_EDID_TIMING_RR_MATCH | NVT_FLAG_EDID_861_ST; flag = flag & ~(NVT_FLAG_MAX_EDID_TIMING); status = NvTiming_GetEdidTiming(width, height, rr, flag, pEdidInfo, &Timing); if (NVT_STATUS_SUCCESS == status) { status = NVT_STATUS_ERR; // is this an exact match? if (0 == NVT_GET_TIMING_STATUS_MATCH(Timing.etc.status)) { if (NVT_TYPE_EDID_861ST == NVT_GET_TIMING_STATUS_TYPE(Timing.etc.status)) { // lookup the vic for this timing in the support map. Vic = (NvU8) NVT_GET_CEA_FORMAT(Timing.etc.status); for (i = 0; i < pEdidInfo->Hdmi3Dsupport.total; ++i) { if (Vic == pEdidInfo->Hdmi3Dsupport.map[i].Vic) { break; } } if (i < pEdidInfo->Hdmi3Dsupport.total) { // does this vic support the requested structure type? if (0 != (NVT_HDMI_3D_SUPPORTED_STRUCT_MASK(StereoStructureType) & pEdidInfo->Hdmi3Dsupport.map[i].StereoStructureMask)) { // if this is side-by-side(half) the detail needs to match also. if ((NVT_HDMI_VS_BYTE5_HDMI_3DS_SIDEBYSIDEHALF != StereoStructureType) || (SideBySideHalfDetail == pEdidInfo->Hdmi3Dsupport.map[i].SideBySideHalfDetail)) { // convert the 2D timing to 3D. NvTiming_GetHDMIStereoTimingFrom2DTiming(&Timing, StereoStructureType, SideBySideHalfDetail, pT); status = NVT_STATUS_SUCCESS; } } } } } } return status; } // EDID based AspectRatio Timing CODE_SEGMENT(PAGE_DD_CODE) NVT_STATUS NvTiming_GetEDIDBasedASPRTiming( NvU16 width, NvU16 height, NvU16 rr, NVT_EDID_INFO *pEI, NVT_TIMING *pT) { NvU32 i, dwStatus; NvU32 dwNativeIndex; NvU32 flag; NvU32 ret; // sanity check if( pEI == NULL || pEI->total_timings == 0 || pT == NULL ) { return NVT_STATUS_ERR; } if( width == 0 || height == 0 ) { return NVT_STATUS_ERR; } // get an EDID timing. Return err if it fails as we don't have any timing to tweak. flag = 0; ret = NvTiming_GetEdidTiming(width, height, rr, flag, pEI, pT); if( NVT_STATUS_SUCCESS != ret ) { return NVT_STATUS_ERR; } // in case we have an exact match from EDID (in terms of Size), we return Success. else if ((NVT_GET_TIMING_STATUS_MATCH(pT->etc.status) & NVT_STATUS_TIMING_MISMATCH_SIZE) == 0) { return NVT_STATUS_SUCCESS; } // find the Native timing for (i = 0, dwNativeIndex = pEI->total_timings + 1; i < pEI->total_timings; i++) { dwStatus = pEI->timing[i].etc.status; if ((NVT_IS_NATIVE_TIMING(dwStatus)) || NVT_IS_DTD1(dwStatus)) { dwNativeIndex = i; break; } } // we don't want to apply LogicScaling(Letterboxing) to Wide Mode on Wide Panel (or non-Wide Mode on non-Wide Panel) if( nvt_is_wideaspect(width, height) == nvt_is_wideaspect(pEI->timing[dwNativeIndex].HVisible, pEI->timing[dwNativeIndex].VVisible) ) { return NVT_STATUS_ERR; } // Letterbox mode enabled by regkey LogicScalingMode // When we try to set modes not supported in EDID (eg. DFP over DSub) the display may not fit the screen. // If Logic Scaling is enabled (ie why we are here), we need to tweak the timing (for CRT) provided: // 1) the aspect ratio of native mode and requested mode differ // eg. Native AR = 5:4, 1280x1024 // Requested AR = 16:10, 1280x800 // 2) Both Width and Height do not mismatch together; If they do we shall go in for DMT/GTF timing // by failing this call. if( pT->interlaced == 0 && dwNativeIndex < pEI->total_timings && (pEI->timing[dwNativeIndex].HVisible*height != pEI->timing[dwNativeIndex].VVisible*width) && (width == pT->HVisible || height == pT->VVisible)) { pT->HFrontPorch += (pT->HVisible - width) / 2; pT->VFrontPorch += (pT->VVisible - height) / 2; pT->HVisible = width; pT->VVisible = height; if(rr != pT->etc.rr) { pT->etc.rrx1k = rr * 1000; pT->pclk = RRx1kToPclk (pT); pT->pclk1khz = RRx1kToPclk1khz(pT); } pT->etc.status = NVT_STATUS_ASPR; return NVT_STATUS_SUCCESS; } return NVT_STATUS_ERR; } /** * * @brief check EDID raw data is valid or not, and it will return the err flags if it existed * @param pEdid : this is a pointer to EDID data * @param length : read length of EDID * @param bIsTrongValidation : true - added more check * false- only header and checksum and size check * */ CODE_SEGMENT(PAGE_DD_CODE) NvU32 NvTiming_EDIDValidationMask(NvU8 *pEdid, NvU32 length, NvBool bIsStrongValidation) { NvU32 i, j, version, checkSum; EDIDV1STRUC *p = (EDIDV1STRUC *)pEdid; EDID_LONG_DISPLAY_DESCRIPTOR *pLdd; NvU8 *pExt; DETAILEDTIMINGDESCRIPTOR *pDTD; NvU32 ret = 0; // check the EDID base size to avoid accessing beyond the EDID buffer, do not proceed with // further validation. if (length < sizeof(EDIDV1STRUC)) { ret |= NVT_EDID_VALIDATION_ERR_MASK(NVT_EDID_VALIDATION_ERR_SIZE); return ret; } // check the EDID version and signature if (getEdidVersion(pEdid, &version) != NVT_STATUS_SUCCESS) { ret |= NVT_EDID_VALIDATION_ERR_MASK(NVT_EDID_VALIDATION_ERR_VERSION); return ret; } // check block 0 checksum value if (!isChecksumValid(pEdid)) { ret |= NVT_EDID_VALIDATION_ERR_MASK(NVT_EDID_VALIDATION_ERR_CHECKSUM); return ret; } // Strong validation to follow if (bIsStrongValidation == NV_TRUE) { // range limit check for (i = 0; i < NVT_EDID_MAX_LONG_DISPLAY_DESCRIPTOR; i++) { pLdd = (EDID_LONG_DISPLAY_DESCRIPTOR *)&p->DetailedTimingDesc[i]; if (pLdd->tag == NVT_EDID_DISPLAY_DESCRIPTOR_DRL && (version == 0x103 || (version == 0x104 && (p->bFeatureSupport & 1)))) { EDID_MONITOR_RANGE_LIMIT *pRangeLimit = (EDID_MONITOR_RANGE_LIMIT *)pLdd->data; NvU8 max_v_rate_offset, min_v_rate_offset, max_h_rate_offset, min_h_rate_offset; // add 255Hz offsets as needed before doing the check, use descriptor->rsvd2 nvt_assert(!(pLdd->rsvd2 & 0xF0)); max_v_rate_offset = pLdd->rsvd2 & NVT_PVT_EDID_RANGE_OFFSET_VER_MAX ? NVT_PVT_EDID_RANGE_OFFSET_AMOUNT : 0; min_v_rate_offset = pLdd->rsvd2 & NVT_PVT_EDID_RANGE_OFFSET_VER_MIN ? NVT_PVT_EDID_RANGE_OFFSET_AMOUNT : 0; max_h_rate_offset = pLdd->rsvd2 & NVT_PVT_EDID_RANGE_OFFSET_HOR_MAX ? NVT_PVT_EDID_RANGE_OFFSET_AMOUNT : 0; min_h_rate_offset = pLdd->rsvd2 & NVT_PVT_EDID_RANGE_OFFSET_HOR_MIN ? NVT_PVT_EDID_RANGE_OFFSET_AMOUNT : 0; if ((pRangeLimit->minVRate + min_v_rate_offset) > (pRangeLimit->maxVRate + max_v_rate_offset) || (pRangeLimit->minHRate + min_h_rate_offset) > (pRangeLimit->maxHRate + max_h_rate_offset) || pRangeLimit->maxVRate == 0 || pRangeLimit->maxHRate == 0) { ret |= NVT_EDID_VALIDATION_ERR_MASK(NVT_EDID_VALIDATION_ERR_RANGE_LIMIT); } break; } } // extension and size check if ((NvU32)(p->bExtensionFlag + 1) * sizeof(EDIDV1STRUC) > length) { // Do not proceed with further validation if the size is invalid. ret |= NVT_EDID_VALIDATION_ERR_MASK(NVT_EDID_VALIDATION_ERR_SIZE); return ret; } // validate Detailed Timing Descriptors, 4 blocks for (i = 0; i < 4; i++) { if (*((NvU16 *)&p->DetailedTimingDesc[i]) != 0) { // This block is not a Display Descriptor. // It must be a valid timing definition // validate the block by passing NULL as the NVTIMING parameter to parseEdidDetailedTimingDescriptor if (parseEdidDetailedTimingDescriptor((NvU8 *)&p->DetailedTimingDesc[i], NULL) != NVT_STATUS_SUCCESS) { ret |= NVT_EDID_VALIDATION_ERR_MASK(NVT_EDID_VALIDATION_ERR_DTD); } } else { // This block is a display descriptor, validate if (((EDID_LONG_DISPLAY_DESCRIPTOR *)&p->DetailedTimingDesc[i])->rsvd != 0) { ret |= NVT_EDID_VALIDATION_ERR_MASK(NVT_EDID_VALIDATION_ERR_DTD); } } } // validate extension blocks for (j = 1; j <= p->bExtensionFlag; j++) { pExt = pEdid + sizeof(EDIDV1STRUC) * j; // check for 861 extension switch (*pExt) { case NVT_EDID_EXTENSION_CTA: // first sanity check on the extension block if (get861ExtInfo(pExt, sizeof(EIA861EXTENSION), NULL) != NVT_STATUS_SUCCESS) { ret |= NVT_EDID_VALIDATION_ERR_MASK(NVT_EDID_VALIDATION_ERR_EXT); } // check sum on CEA extension block for (i = 0, checkSum = 0; i < sizeof(EIA861EXTENSION); i ++) { checkSum += pExt[i]; } if ((checkSum & 0xFF) != 0) { ret |= NVT_EDID_VALIDATION_ERR_MASK(NVT_EDID_VALIDATION_ERR_CHECKSUM); } // 0 indicates no DTD in this block if (((EIA861EXTENSION*)pExt)->offset == 0) { continue; } // validate DTD blocks pDTD = (DETAILEDTIMINGDESCRIPTOR *)&pExt[((EIA861EXTENSION *)pExt)->offset]; while ((pDTD->wDTPixelClock != 0) && (((NvU8 *)pDTD - pExt + sizeof(DETAILEDTIMINGDESCRIPTOR)) < ((NvU8)sizeof(EIA861EXTENSION)))) { if (parseEdidDetailedTimingDescriptor((NvU8 *)pDTD, NULL) != NVT_STATUS_SUCCESS) { ret |= NVT_EDID_VALIDATION_ERR_MASK(NVT_EDID_VALIDATION_ERR_EXT_DTD); } pDTD++; } break; case NVT_EDID_EXTENSION_VTB: // perform a checksum on the VTB block for (i = 0, checkSum = 0; i < sizeof(VTBEXTENSION); i++) { checkSum += pExt[i]; } if ((checkSum & 0xFF) != 0) { ret |= NVT_EDID_VALIDATION_ERR_MASK(NVT_EDID_VALIDATION_ERR_CHECKSUM); } break; case NVT_EDID_EXTENSION_DISPLAYID: // perform a checksum on the VTB block for (i = 0, checkSum = 0; i < sizeof(EIA861EXTENSION); i++) { checkSum += pExt[i]; } if ((checkSum & 0xFF) != 0) { ret |= NVT_EDID_VALIDATION_ERR_MASK(NVT_EDID_VALIDATION_ERR_CHECKSUM); } break; default: break; } } } return ret; } /** * * @brief sanity check EDID binary frequently used data block is valid or not, * and it will return error checkpoint flag if it existed * @param pEdid : this is a pointer to EDID raw data * @param length : read length of EDID * */ CODE_SEGMENT(PAGE_DD_CODE) NvU32 NvTiming_EDIDStrongValidationMask(NvU8 *pEdid, NvU32 length) { NvU32 i, j, version, extnCount; EDIDV1STRUC *p = (EDIDV1STRUC *)pEdid; EDID_LONG_DISPLAY_DESCRIPTOR *pLdd; NvU8 *pExt; DETAILEDTIMINGDESCRIPTOR *pDTD; // For CTA861 NvU8 ctaDTD_Offset; NvU8 *pData_collection; NvU32 ctaBlockTag, ctaPayload, vic; // For DisplayID DIDEXTENSION *pDisplayid; NvU8 did_section_length = 0x79; NvU8 did2ExtCount = 0; DISPLAYID_2_0_DATA_BLOCK_HEADER *pDID2Header; DISPLAYID_DATA_BLOCK_HEADER *pHeader; NvU8 block_length = 0; NvBool bAllZero = NV_TRUE; NvU32 ret = 0; // check the EDID base size to avoid accessing beyond the EDID buffer if (length < sizeof(EDIDV1STRUC) || (length > sizeof(EDIDV1STRUC) && (length % sizeof(EDIDV1STRUC) != 0))) ret |= NVT_EDID_VALIDATION_ERR_MASK(NVT_EDID_VALIDATION_ERR_SIZE); // check the EDID version and signature if (getEdidVersion(pEdid, &version) != NVT_STATUS_SUCCESS) ret |= NVT_EDID_VALIDATION_ERR_MASK(NVT_EDID_VALIDATION_ERR_HEADER); // check block 0 checksum value if (!isChecksumValid(pEdid)) ret |= NVT_EDID_VALIDATION_ERR_MASK(NVT_EDID_VALIDATION_ERR_CHECKSUM); if (p->bVersionNumber != 0x01 || p->bRevisionNumber > 0x04) { ret |= NVT_EDID_VALIDATION_ERR_MASK(NVT_EDID_VALIDATION_ERR_VERSION); } // 18bytes in DTD or Display Descriptor check for (i = 0; i < NVT_EDID_MAX_LONG_DISPLAY_DESCRIPTOR; i++) { if (*((NvU16 *)&p->DetailedTimingDesc[i]) != 0) { // This block is not a Display Descriptor. // It must be a valid timing definition // validate the block by passing NULL as the NVTIMING parameter to parseEdidDetailedTimingDescriptor if (parseEdidDetailedTimingDescriptor((NvU8 *)&p->DetailedTimingDesc[i], NULL) != NVT_STATUS_SUCCESS) { ret |= NVT_EDID_VALIDATION_ERR_MASK(NVT_EDID_VALIDATION_ERR_DTD); } else { // check the max image size in monitor and its DTD defines value if (p->bMaxHorizImageSize != 0 && p->bMaxVertImageSize != 0) { DETAILEDTIMINGDESCRIPTOR *pDTD = (DETAILEDTIMINGDESCRIPTOR *)&p->DetailedTimingDesc[i]; NvU16 hDTDImageSize = (pDTD->bDTHorizVertImage & 0xF0) << 4 | pDTD->bDTHorizontalImage; NvU16 vDTDImageSize = (pDTD->bDTHorizVertImage & 0x0F) << 8 | pDTD->bDTVerticalImage; if ((hDTDImageSize/10) > p->bMaxHorizImageSize || (vDTDImageSize/10) > p->bMaxVertImageSize) { ret |= NVT_EDID_VALIDATION_ERR_MASK(NVT_EDID_VALIDATION_ERR_DTD); } } } } else { pLdd = (EDID_LONG_DISPLAY_DESCRIPTOR *)&p->DetailedTimingDesc[i]; // This block is a display descriptor, validate if (((EDID_LONG_DISPLAY_DESCRIPTOR *)&p->DetailedTimingDesc[i])->rsvd != 0 || // (00 00 00)h indicates Display Descriptor (pLdd->tag >= 0x11 && pLdd->tag <= 0xF6)) // Reserved : Do Not Use ret |= NVT_EDID_VALIDATION_ERR_MASK(NVT_EDID_VALIDATION_ERR_DESCRIPTOR); if (pLdd->tag == NVT_EDID_DISPLAY_DESCRIPTOR_DRL && (version == 0x103 || (version == 0x104 && (p->bFeatureSupport & 1)))) { EDID_MONITOR_RANGE_LIMIT *pRangeLimit = (EDID_MONITOR_RANGE_LIMIT *)pLdd->data; NvU8 max_v_rate_offset, min_v_rate_offset, max_h_rate_offset, min_h_rate_offset; // add 255Hz offsets as needed before doing the check, use descriptor->rsvd2 nvt_assert(!(pLdd->rsvd2 & 0xF0)); max_v_rate_offset = pLdd->rsvd2 & NVT_PVT_EDID_RANGE_OFFSET_VER_MAX ? NVT_PVT_EDID_RANGE_OFFSET_AMOUNT : 0; min_v_rate_offset = pLdd->rsvd2 & NVT_PVT_EDID_RANGE_OFFSET_VER_MIN ? NVT_PVT_EDID_RANGE_OFFSET_AMOUNT : 0; max_h_rate_offset = pLdd->rsvd2 & NVT_PVT_EDID_RANGE_OFFSET_HOR_MAX ? NVT_PVT_EDID_RANGE_OFFSET_AMOUNT : 0; min_h_rate_offset = pLdd->rsvd2 & NVT_PVT_EDID_RANGE_OFFSET_HOR_MIN ? NVT_PVT_EDID_RANGE_OFFSET_AMOUNT : 0; if ((pRangeLimit->minVRate + min_v_rate_offset) > (pRangeLimit->maxVRate + max_v_rate_offset) || (pRangeLimit->minHRate + min_h_rate_offset) > (pRangeLimit->maxHRate + max_h_rate_offset) || pRangeLimit->maxVRate == 0 || pRangeLimit->maxHRate == 0) { ret |= NVT_EDID_VALIDATION_ERR_MASK(NVT_EDID_VALIDATION_ERR_RANGE_LIMIT); } } } } // extension and size check if ((NvU32)(p->bExtensionFlag + 1) * sizeof(EDIDV1STRUC) > length) { ret |= NVT_EDID_VALIDATION_ERR_MASK(NVT_EDID_VALIDATION_ERR_EXTENSION_COUNT); } // we shall not trust any extension blocks with wrong input EDID size if (NVT_IS_EDID_VALIDATION_FLAGS(ret, NVT_EDID_VALIDATION_ERR_SIZE) || NVT_IS_EDID_VALIDATION_FLAGS(ret, NVT_EDID_VALIDATION_ERR_EXTENSION_COUNT)) return ret; // validate extension blocks for (j = 1; j <= p->bExtensionFlag; j++) { pExt = pEdid + sizeof(EDIDV1STRUC) * j; // check for 861 extension switch (*pExt) { case NVT_EDID_EXTENSION_CTA: ctaDTD_Offset = ((EIA861EXTENSION *)pExt)->offset; // first sanity check on the extension block if (get861ExtInfo(pExt, sizeof(EIA861EXTENSION), NULL) != NVT_STATUS_SUCCESS || ((EIA861EXTENSION *)pExt)->revision < NVT_CEA861_REV_B) { ret |= NVT_EDID_VALIDATION_ERR_MASK(NVT_EDID_VALIDATION_ERR_EXT_CTA_BASIC); } // 0 indicated there is no DTD and data collection in this block if (ctaDTD_Offset == 0) { if(!isChecksumValid(pExt)) ret |= NVT_EDID_VALIDATION_ERR_MASK(NVT_EDID_VALIDATION_ERR_EXT_CTA_CHECKSUM); continue; } // validate SVD block ctaBlockTag = NVT_CEA861_GET_SHORT_DESCRIPTOR_TAG(((EIA861EXTENSION *)pExt)->data[0]); pData_collection = ((EIA861EXTENSION *)pExt)->data; while ((ctaDTD_Offset - 4) > 0 && pData_collection != &pExt[ctaDTD_Offset] && ctaBlockTag > NVT_CEA861_TAG_RSVD && ctaBlockTag <= NVT_CEA861_TAG_EXTENDED_FLAG) { ctaBlockTag = NVT_CEA861_GET_SHORT_DESCRIPTOR_TAG(*pData_collection); ctaPayload = NVT_CEA861_GET_SHORT_DESCRIPTOR_SIZE(*pData_collection); if (parseCta861DataBlockInfo(pData_collection, (NvU32)ctaDTD_Offset - 4, NULL) == NVT_STATUS_SUCCESS) { pData_collection++; // go to the next byte. skip Tag+Length byte if (ctaBlockTag == NVT_CEA861_TAG_VIDEO) { for (i=0; i < ctaPayload; i++) { vic = NVT_GET_CTA_8BIT_VIC(*pData_collection); if (vic == 0 || vic > 255 || (vic >= 128 && vic <=192)) ret |= NVT_EDID_VALIDATION_ERR_MASK(NVT_EDID_VALIDATION_ERR_EXT_CTA_SVD); pData_collection++; } } else if (ctaBlockTag == NVT_CEA861_TAG_EXTENDED_FLAG) { if (*pData_collection == NVT_CTA861_EXT_TAG_HF_EEODB) { if ((p->bVersionNumber != 0x01) || (p->bRevisionNumber != 0x03)) { ret |= NVT_EDID_VALIDATION_ERR_MASK(NVT_EDID_VALIDATION_ERR_EXT_CTA_INVALID_DATA_BLOCK); pData_collection += ctaPayload; } else { ret &= ~NVT_EDID_VALIDATION_ERR_MASK(NVT_EDID_VALIDATION_ERR_EXTENSION_COUNT); extnCount = *(++pData_collection); // check the EDID extension count value again because EDID extension block count // value in EEODB override it and source shall ignore extension flag > 1 value if ((extnCount + 1) != (length / (sizeof(EDIDV1STRUC)))) ret |= NVT_EDID_VALIDATION_ERR_MASK(NVT_EDID_VALIDATION_ERR_EXTENSION_COUNT); pData_collection++; } } else pData_collection += ctaPayload; } else if (ctaBlockTag == NVT_CEA861_TAG_RSVD) { ret |= NVT_EDID_VALIDATION_ERR_MASK(NVT_EDID_VALIDATION_ERR_EXT_CTA_TAG); pData_collection += ctaPayload; } else pData_collection += ctaPayload; } else { pData_collection++; // go to the next byte. skip Tag+Length byte ret |= NVT_EDID_VALIDATION_ERR_MASK(NVT_EDID_VALIDATION_ERR_EXT_CTA_INVALID_DATA_BLOCK); pData_collection += ctaPayload; } } // validate DTD blocks pDTD = (DETAILEDTIMINGDESCRIPTOR *)&pExt[((EIA861EXTENSION *)pExt)->offset]; while ((pDTD->wDTPixelClock != 0) && (((NvU8 *)pDTD - pExt + sizeof(DETAILEDTIMINGDESCRIPTOR)) < ((NvU8)sizeof(EIA861EXTENSION)))) { if (parseEdidDetailedTimingDescriptor((NvU8 *)pDTD, NULL) != NVT_STATUS_SUCCESS) ret |= NVT_EDID_VALIDATION_ERR_MASK(NVT_EDID_VALIDATION_ERR_EXT_DTD); else { // check the max image size and if (p->bMaxHorizImageSize != 0 && p->bMaxVertImageSize != 0) { NvU16 hDTDImageSize = (pDTD->bDTHorizVertImage & 0xF0) << 4 | pDTD->bDTHorizontalImage; NvU16 vDTDImageSize = (pDTD->bDTHorizVertImage & 0x0F) << 8 | pDTD->bDTVerticalImage; if ((hDTDImageSize/10) > (p->bMaxHorizImageSize) || (vDTDImageSize/10) > p->bMaxVertImageSize) ret |= NVT_EDID_VALIDATION_ERR_MASK(NVT_EDID_VALIDATION_ERR_EXT_CTA_DTD); } } pDTD++; } if(!isChecksumValid(pExt)) ret |= NVT_EDID_VALIDATION_ERR_MASK(NVT_EDID_VALIDATION_ERR_EXT_CTA_CHECKSUM); break; case NVT_EDID_EXTENSION_DISPLAYID: pDisplayid = ((DIDEXTENSION *)pExt); if (pDisplayid->ext_count != 0) ret |= NVT_EDID_VALIDATION_ERR_MASK(NVT_EDID_VALIDATION_ERR_EXT_DID_EXTCOUNT); if (pDisplayid->length != 0x79) ret |= NVT_EDID_VALIDATION_ERR_MASK(NVT_EDID_VALIDATION_ERR_EXT_DID_SEC_SIZE); if (!isChecksumValid(pExt)) ret |= NVT_EDID_VALIDATION_ERR_MASK(NVT_EDID_VALIDATION_ERR_EXT_DID_CHECKSUM); // check the DID2 data blocks if ((pDisplayid->struct_version & 0xF0) >> 4 == 2) { if ((pDisplayid->struct_version & 0xFF) == 0x21) ret |= NVT_EDID_VALIDATION_ERR_MASK(NVT_EDID_VALIDATION_ERR_EXT_DID_VERSION); did2ExtCount++; if (pDisplayid->use_case == 0 && did2ExtCount == 1) ret |= NVT_EDID_VALIDATION_ERR_MASK(NVT_EDID_VALIDATION_ERR_EXT_DID2_USE_CASE); // check the DisplayId2 valid timing pDID2Header = (DISPLAYID_2_0_DATA_BLOCK_HEADER*)pDisplayid->data; pData_collection = pDisplayid->data; // Sanity check every data blocks while (((pDID2Header->type >= DISPLAYID_2_0_BLOCK_TYPE_PRODUCT_IDENTITY && pDID2Header->type <= DISPLAYID_2_0_BLOCK_TYPE_BRIGHTNESS_LUMINANCE_RANGE) || pDID2Header->type == DISPLAYID_2_0_BLOCK_TYPE_VENDOR_SPEC || pDID2Header->type == DISPLAYID_2_0_BLOCK_TYPE_CTA_DATA) && pDID2Header->data_bytes != 0 && (pData_collection - pExt < (int)sizeof(DIDEXTENSION))) { if (parseDisplayId20EDIDExtDataBlocks(pData_collection, did_section_length, &block_length, NULL) == NVT_STATUS_ERR) { if (pDID2Header->type == DISPLAYID_2_0_BLOCK_TYPE_TIMING_7) ret |= NVT_EDID_VALIDATION_ERR_MASK(NVT_EDID_VALIDATION_ERR_EXT_DID2_TYPE7); if (pDID2Header->type == DISPLAYID_2_0_BLOCK_TYPE_RANGE_LIMITS) ret |= NVT_EDID_VALIDATION_ERR_MASK(NVT_EDID_VALIDATION_ERR_EXT_RANGE_LIMIT); if (pDID2Header->type == DISPLAYID_2_0_BLOCK_TYPE_ADAPTIVE_SYNC) ret |= NVT_EDID_VALIDATION_ERR_MASK(NVT_EDID_VALIDATION_ERR_EXT_DID2_ADAPTIVE_SYNC); // add more data blocks tag here to evaluate } pData_collection += block_length; pDID2Header = (DISPLAYID_2_0_DATA_BLOCK_HEADER*)pData_collection; } // compare the remain 0 value are correct or not before meet checksum byte for (i = 0; i < (NvU32)(&pDisplayid->data[NVT_DID_MAX_EXT_PAYLOAD-1] - pData_collection); i++) { if (pData_collection[i] != 0) { bAllZero = NV_FALSE; break; } } // if the first tag failed, ignore all the tags afterward then if (!bAllZero && (pDID2Header->type < DISPLAYID_2_0_BLOCK_TYPE_PRODUCT_IDENTITY || (pDID2Header->type > DISPLAYID_2_0_BLOCK_TYPE_BRIGHTNESS_LUMINANCE_RANGE && pDID2Header->type != DISPLAYID_2_0_BLOCK_TYPE_VENDOR_SPEC && pDID2Header->type != DISPLAYID_2_0_BLOCK_TYPE_CTA_DATA)) && (pData_collection - pExt < (int)sizeof(DIDEXTENSION))) { ret |= NVT_EDID_VALIDATION_ERR_MASK(NVT_EDID_VALIDATION_ERR_EXT_DID2_TAG); continue; } } else if ((pDisplayid->struct_version & 0xFF) == 0x12 || (pDisplayid->struct_version & 0xFF) == 0x13) { if ((pDisplayid->struct_version & 0xFF) == 0x13) ret |= NVT_EDID_VALIDATION_ERR_MASK(NVT_EDID_VALIDATION_ERR_EXT_DID_VERSION); pHeader = (DISPLAYID_DATA_BLOCK_HEADER*)pDisplayid->data; pData_collection = pDisplayid->data; // Sanity check every data blocks while ((pHeader->type <= NVT_DISPLAYID_BLOCK_TYPE_TILEDDISPLAY || pHeader->type == NVT_DISPLAYID_BLOCK_TYPE_CTA_DATA || pHeader->type == NVT_DISPLAYID_BLOCK_TYPE_VENDOR_SPEC) && pHeader->data_bytes != 0 && (pData_collection - pExt < (int)sizeof(DIDEXTENSION))) { if (parseDisplayIdBlock(pData_collection, did_section_length, &block_length, NULL) == NVT_STATUS_ERR) { if (pHeader->type == NVT_DISPLAYID_BLOCK_TYPE_TIMING_1) ret |= NVT_EDID_VALIDATION_ERR_MASK(NVT_EDID_VALIDATION_ERR_EXT_DID13_TYPE1); if (pHeader->type == NVT_DISPLAYID_BLOCK_TYPE_RANGE_LIMITS) ret |= NVT_EDID_VALIDATION_ERR_MASK(NVT_EDID_VALIDATION_ERR_EXT_RANGE_LIMIT); // add more data blocks tag here to evaluate } pData_collection += block_length; pHeader = (DISPLAYID_DATA_BLOCK_HEADER*)pData_collection; } // compare the remain 0 value are correct or not before meet checksum byte for (i = 0; i < (NvU32)(&pDisplayid->data[NVT_DID_MAX_EXT_PAYLOAD-1] - pData_collection); i++) { if (pData_collection[i] != 0) { bAllZero = NV_FALSE; break; } } // if the first tag failed, ignore all the tags afterward then if (!bAllZero && pHeader->type > NVT_DISPLAYID_BLOCK_TYPE_TILEDDISPLAY && pHeader->type != NVT_DISPLAYID_BLOCK_TYPE_CTA_DATA && pHeader->type != NVT_DISPLAYID_BLOCK_TYPE_VENDOR_SPEC && (pData_collection - pExt < (int)sizeof(DIDEXTENSION))) { ret |= NVT_EDID_VALIDATION_ERR_MASK(NVT_EDID_VALIDATION_ERR_EXT_DID13_TAG); continue; } } else ret |= NVT_EDID_VALIDATION_ERR_MASK(NVT_EDID_VALIDATION_ERR_EXT_DID_VERSION); break; default: // the useful extension only CTA (0x02) and DisplayID (0x70) if ( *pExt != NVT_EDID_EXTENSION_VTB && *pExt != NVT_EDID_EXTENSION_DI && *pExt != NVT_EDID_EXTENSION_LS && *pExt != NVT_EDID_EXTENSION_DPVL && *pExt != NVT_EDID_EXTENSION_BM && *pExt != NVT_EDID_EXTENSION_OEM ) { ret |= NVT_EDID_VALIDATION_ERR_MASK(NVT_EDID_VALIDATION_ERR_EXTENSION_TAG); } break; } } return ret; } CODE_SEGMENT(PAGE_DD_CODE) NVT_STATUS NvTiming_EDIDValidation (NvU8 *pEdid, NvU32 length, NvBool bIsStrongValidation) { if (NvTiming_EDIDValidationMask(pEdid, length, bIsStrongValidation) != 0) { return NVT_STATUS_ERR; } else { return NVT_STATUS_SUCCESS; } } // Function Description: Get the first Detailed Timing Descriptor // // Parameters: // pEdidInfo: IN - pointer to parsed EDID // pT: OUT - pointer to where the DTD1 timing will be stored // // Return: // NVT_STATUS_SUCCESS: DTD1 was found in parsed EDID, pT is a valid result // NVT_STATUS_INVALID_PARAMETER: one or more parameter was invalid // NVT_STATUS_ERR: DTD1 was not found in parsed EDID, pT is invalid CODE_SEGMENT(PAGE_DD_CODE) NVT_STATUS NvTiming_GetDTD1Timing (NVT_EDID_INFO * pEdidInfo, NVT_TIMING * pT) { NvU32 j; // check param if (pEdidInfo == NULL || pT == NULL) { return NVT_STATUS_INVALID_PARAMETER; } // find the PTM mode for (j = 0; j < pEdidInfo->total_timings; j++) { if (NVT_PREFERRED_TIMING_IS_DTD1(pEdidInfo->timing[j].etc.flag, pEdidInfo->timing[j].etc.status)) { *pT = pEdidInfo->timing[j]; return NVT_STATUS_SUCCESS; } } // find DisplayID preferred for (j = 1; j < pEdidInfo->total_timings; j++) { if (NVT_PREFERRED_TIMING_IS_DISPLAYID(pEdidInfo->timing[j].etc.flag)) { *pT = pEdidInfo->timing[j]; return NVT_STATUS_SUCCESS; } } // DTD1 should exist, but if it doesn't, return not found for (j = 0; j < pEdidInfo->total_timings; j++) { NvU32 data = pEdidInfo->timing[j].etc.status; if (NVT_IS_DTD1(data)) { *pT = pEdidInfo->timing[j]; return NVT_STATUS_SUCCESS; } } // DTD1 should exist, but if it doesn't, return not found return NVT_STATUS_ERR; } // Description: Parses a VTB extension block into its associated timings // // Parameters: // pEdidExt: IN - pointer to the beginning of the extension block // pInfo: IN - The original block information, including the // array of timings. // // NOTE: this function *really* should be in its own separate file, but a certain DVS test // uses cross build makefiles which do not allow the specification of a new file. CODE_SEGMENT(PAGE_DD_CODE) void parseVTBExtension(NvU8 *pEdidExt, NVT_EDID_INFO *pInfo) { NvU32 i; VTBEXTENSION *pExt = (VTBEXTENSION *)pEdidExt; NvU32 count; NvU32 bytes; NVT_TIMING newTiming; // Null = bad idea if (pEdidExt == NULL) { return; } // Sanity check for VTB extension block if (pExt->tag != NVT_EDID_EXTENSION_VTB || pExt->revision == NVT_VTB_REV_NONE) { return; } // Sanity check - ensure that the # of descriptor does not exceed // byte size count = (NvU32)sizeof(EDID_LONG_DISPLAY_DESCRIPTOR) * pExt->num_detailed + (NvU32)sizeof(EDID_CVT_3BYTE_BLOCK) * pExt->num_cvt + (NvU32)sizeof(NvU16) * pExt->num_standard; if (count > NVT_VTB_MAX_PAYLOAD) { return; } count = 0; bytes = 0; // Process Detailed Timings for (i = 0; i < pExt->num_detailed; i++) { NVMISC_MEMSET(&newTiming, 0, sizeof(newTiming)); if (parseEdidDetailedTimingDescriptor((NvU8 *)(pExt->data + bytes), &newTiming) == NVT_STATUS_SUCCESS) { newTiming.etc.name[39] = '\0'; newTiming.etc.status = NVT_STATUS_EDID_VTB_EXT_DTDn(++count); if (!assignNextAvailableTiming(pInfo, &newTiming)) { break; } bytes += (NvU32)(sizeof(EDID_LONG_DISPLAY_DESCRIPTOR)); } } // Process CVT Timings for (i = 0; i < pExt->num_cvt; i++) { parseEdidCvt3ByteDescriptor((NvU8 *)(pExt->data + bytes), pInfo, &count); bytes += (NvU32)sizeof(EDID_CVT_3BYTE_BLOCK); } // Process Standard Timings for (i = 0; i < pExt->num_standard; i++) { NVMISC_MEMSET(&newTiming, 0, sizeof(newTiming)); parseEdidStandardTimingDescriptor(*(NvU16 *)(pExt->data + bytes), pInfo, count, &newTiming); newTiming.etc.name[39] = '\0'; newTiming.etc.status = NVT_STATUS_EDID_VTB_EXT_STDn(++count); if (!assignNextAvailableTiming(pInfo, &newTiming)) { break; } bytes += (NvU32)sizeof(NvU16); } } CODE_SEGMENT(PAGE_DD_CODE) static int IsPrintable(NvU8 c) { return ((c >= ' ') && (c <= '~')); } CODE_SEGMENT(PAGE_DD_CODE) static int IsWhiteSpace(NvU8 c) { // consider anything unprintable or single space (ASCII 32) // to be whitespace return (!IsPrintable(c) || (c == ' ')); } CODE_SEGMENT(PAGE_DD_CODE) static void RemoveTrailingWhiteSpace(NvU8 *str, int len) { int i; for (i = len; (i >= 0) && IsWhiteSpace(str[i]); i--) { str[i] = '\0'; } } CODE_SEGMENT(PAGE_DD_CODE) static void RemoveNonPrintableCharacters(NvU8 *str) { int i; // Check that all characters are printable. // If not, replace them with '?' for (i = 0; str[i] != '\0'; i++) { if (!IsPrintable(str[i])) { str[i] = '?'; } } } /** * @brief Assigns this timing to the next available slot in pInfo->timing[] if * possible. * @param pInfo EDID struct containing the parsed timings * @param pTiming New timing to be copied into pInfo->timing[] */ CODE_SEGMENT(PAGE_DD_CODE) NvBool assignNextAvailableTiming(NVT_EDID_INFO *pInfo, const NVT_TIMING *pTiming) { if (pInfo == NULL) return NV_TRUE; // Don't write past the end of // pInfo->timing[NVT_EDID_MAX_TOTAL_TIMING] if (pInfo->total_timings >= COUNT(pInfo->timing)) { return NV_FALSE; } pInfo->timing[pInfo->total_timings++] = *pTiming; return NV_TRUE; } /** * @brief Return the nth highest priority index based on the different SVR * @param svr Short Video Reference */ CODE_SEGMENT(PAGE_DD_CODE) NvU8 getHighestPrioritySVRIdx(const NVT_EDID_CEA861_INFO *pExt861) { // In general, sink shall define the first one timing sequence NvU8 kth = 1; NvU8 i = 0; for (i = 0; i < pExt861->total_svr; i++) { NvU8 svr = pExt861->svr_vfpdb[i]; // Reserved if (svr == 0 || svr == 128 || (svr >= 176 && svr <= 192) || svr == 255) continue; if (svr >= 129 && svr <= 144) return svr - 128; // Interpret as the Kth 18-byte DTD in both base0 and CTA block (for N = 1 to 16) else if (svr >= 145 && svr <= 160) return svr - 144; // Interpret as the Nth 20-byte DTD or 6- or 7-byte CVT-based descriptor. (for N = 1 to 16) else if (svr >= 161 && svr <= 175) return svr - 160; // Interpret as the video format indicated by the first VFD of the first VFDB with Frame Rates of Rate Index N (for N = 1 to 15) else if (svr == 254) return kth; // Interpret as the timing format indicated by the first code of the first T8VTDB (for N = 1) else // assign corresponding CTA format's timing from pre-defined CE timing table, EIA861B { // ( SVR >= 1 and SVR <= 127) and (SVR >= 193 and SVR <= 253) needs to handle it by client return svr; } } return 0; } CODE_SEGMENT(PAGE_DD_CODE) NVT_STATUS NvTiming_GetProductName(const NVT_EDID_INFO *pEdidInfo, NvU8 *pProductName, const NvU32 productNameLength) { NvU32 i = 0, m = 0, n = 0; if( pEdidInfo == NULL || pProductName == NULL ) { return NVT_STATUS_INVALID_PARAMETER; } for ( i = 0; i < NVT_EDID_MAX_LONG_DISPLAY_DESCRIPTOR; i++) { if (pEdidInfo->ldd[i].tag == NVT_EDID_DISPLAY_DESCRITPOR_DPN) { for(n = 0; n < NVT_EDID_LDD_PAYLOAD_SIZE && pEdidInfo->ldd[i].u.product_name.str[n] != 0x0; n++) { pProductName[m++] = pEdidInfo->ldd[i].u.product_name.str[n]; if ((m + 1) >= productNameLength) { goto done; } } } } done: pProductName[m] = '\0'; //Ensure a null termination at the end. RemoveTrailingWhiteSpace(pProductName, m); RemoveNonPrintableCharacters(pProductName); return NVT_STATUS_SUCCESS; } CODE_SEGMENT(PAGE_DD_CODE) NvU32 NvTiming_CalculateEDIDCRC32(NvU8* pEDIDBuffer, NvU32 edidsize) { return calculateCRC32(pEDIDBuffer, edidsize); } //Calculates EDID/DisplayID2 CRC after purging 'Week of Manufacture', 'Year of Manufacture', //'Product ID String' & 'Serial Number' from EDID CODE_SEGMENT(PAGE_DD_CODE) NvU32 NvTiming_CalculateCommonEDIDCRC32(NvU8* pEDIDBuffer, NvU32 edidVersion) { NvU32 commonEDIDBufferSize = 0; NvU8 CommonEDIDBuffer[256]; NvU32 edidBufferIndex = 0; if(pEDIDBuffer==NULL) { return 0; } // Transfer over the original EDID buffer NVMISC_MEMCPY(CommonEDIDBuffer, pEDIDBuffer, 256); if ((pEDIDBuffer[0] & 0xF0) == 0x20) { /* typedef struct DisplayId2Struct { NvU8 bVersion; // 0x00 NvU8 bSectionBytes; // 0x01 - section length, exclusive the five mandatory bytes. NvU8 bwPrimaryUseCase; // 0x02 NvU8 bExtensionCount; // 0x03 // 0x20 DisplayId2 Standalone always exists Product Identification data block NvU8 bProductIdtag; // 0x04 NvU8 bPIDRevision; // 0x05 NvU8 bPayloadByte; // 0x06 NvU8 bManuId[3]; // 0x07-0x09 NvU16 wProductId; // 0x0A-0x0B NvU32 dwSerialNum; // 0x0C-0x0F NvU16 wWeekandYear; // 0x10-0x11 NvU8 SizeOfProductNameString; // 0x12 } DISPLAY_ID2_FIXED_FORMAT; */ // Wipe out the Serial Number, Week of Manufacture, and Year of Manufacture or Model Year NVMISC_MEMSET(CommonEDIDBuffer + 0x0C, 0, 6); // Wipe out the checksums CommonEDIDBuffer[CommonEDIDBuffer[1]+5/*mandatory bytes*/-1] = 0; CommonEDIDBuffer[0xFF] = 0; // zero out any Produc Name in Prodcut Identification data block if (CommonEDIDBuffer[0x12] != 0) { NVMISC_MEMSET(CommonEDIDBuffer + 0x13, 0, CommonEDIDBuffer[0x12]); CommonEDIDBuffer[0x12] = 0; } // displayId2 standalone uses 256 length sections commonEDIDBufferSize = 256; } else { // Wipe out the Serial Number, Week of Manufacture, and Year of Manufacture or Model Year NVMISC_MEMSET(CommonEDIDBuffer + 0x0C, 0, 6); // Wipe out the checksums CommonEDIDBuffer[0x7F] = 0; CommonEDIDBuffer[0xFF] = 0; // We also need to zero out any "EDID Other Monitor Descriptors" (https://en.wikipedia.org/wiki/Extended_display_identification_data) for (edidBufferIndex = 54; edidBufferIndex <= 108; edidBufferIndex += 18) { if (CommonEDIDBuffer[edidBufferIndex] == 0 && CommonEDIDBuffer[edidBufferIndex+1] == 0) { // Wipe this block out. It contains OEM-specific details that contain things like serial numbers NVMISC_MEMSET(CommonEDIDBuffer + edidBufferIndex, 0, 18); } } // Check what size we should do the compare against commonEDIDBufferSize = 128; } return NvTiming_CalculateEDIDCRC32(CommonEDIDBuffer, commonEDIDBufferSize); } // NvTiming_CalculateCommonEDIDCRC32 // Calculate the minimum and maximum v_rate and h_rate, as well as // maximum pclk; initialize with the range of values in the EDID mode // list, but override with what is in the range limit descriptor section. // // based on drivers/modeset.nxt/CODE/edid.c:EdidGetMonitorLimits() and // EdidBuildRangeLimits() CODE_SEGMENT(PAGE_DD_CODE) NVT_STATUS NvTiming_CalculateEDIDLimits(NVT_EDID_INFO *pEdidInfo, NVT_EDID_RANGE_LIMIT *pLimit) { NvU32 i, pclk10khz; NVMISC_MEMSET(pLimit, 0, sizeof(NVT_EDID_RANGE_LIMIT)); // the below currently only supports 1.x EDIDs if ((pEdidInfo->version & 0xFF00) != 0x100) { return NVT_STATUS_ERR; } pLimit->min_v_rate_hzx1k = ~0; pLimit->max_v_rate_hzx1k = 0; pLimit->min_h_rate_hz = ~0; pLimit->max_h_rate_hz = 0; pLimit->max_pclk_10khz = 0; // find the ranges in the EDID mode list for (i = 0; i < pEdidInfo->total_timings; i++) { NVT_TIMING *pTiming = &pEdidInfo->timing[i]; NvU32 h_rate_hz; if (pLimit->min_v_rate_hzx1k > pTiming->etc.rrx1k) { pLimit->min_v_rate_hzx1k = pTiming->etc.rrx1k; } if (pLimit->max_v_rate_hzx1k < pTiming->etc.rrx1k) { pLimit->max_v_rate_hzx1k = pTiming->etc.rrx1k; } h_rate_hz = axb_div_c(pTiming->pclk1khz, 1000, (NvU32)pTiming->HTotal); if (pLimit->min_h_rate_hz > h_rate_hz) { pLimit->min_h_rate_hz = h_rate_hz; } if (pLimit->max_h_rate_hz < h_rate_hz) { pLimit->max_h_rate_hz = h_rate_hz; } pclk10khz = (pTiming->pclk1khz + 5 ) / 10; if (pLimit->max_pclk_10khz < pclk10khz) { pLimit->max_pclk_10khz = pclk10khz; } } // use the range limit display descriptor, if available: these // override anything we found in the EDID mode list for (i = 0; i < NVT_EDID_MAX_LONG_DISPLAY_DESCRIPTOR; i++) { if (pEdidInfo->ldd[i].tag == NVT_EDID_DISPLAY_DESCRIPTOR_DRL) { NVT_EDID_DD_RANGE_LIMIT *pRangeLimit = &pEdidInfo->ldd[i].u.range_limit; NvU32 max_pclk_10khz; // {min,max}_v_rate is in hz if (pRangeLimit->min_v_rate != 0) { pLimit->min_v_rate_hzx1k = pRangeLimit->min_v_rate * 1000; } if (pRangeLimit->max_v_rate != 0) { pLimit->max_v_rate_hzx1k = pRangeLimit->max_v_rate * 1000; } // {min,max}_h_rate is in khz if (pRangeLimit->min_h_rate != 0) { pLimit->min_h_rate_hz = pRangeLimit->min_h_rate * 1000; } if (pRangeLimit->max_h_rate != 0) { pLimit->max_h_rate_hz = pRangeLimit->max_h_rate * 1000; } // EdidGetMonitorLimits() honored the pclk from the // modelist over what it found in the range limit // descriptor, so do the same here max_pclk_10khz = pRangeLimit->max_pclk_MHz * 100; if (pLimit->max_pclk_10khz < max_pclk_10khz) { pLimit->max_pclk_10khz = max_pclk_10khz; } break; } } return NVT_STATUS_SUCCESS; } // Build a user-friendly name: // // * get the vendor name: // * use the 3 character PNP ID from the EDID's manufacturer ID field // * expand, if possible, the PNP ID using the PNPVendorIds[] table // * get the product name from the descriptor block(s) // * prepend the vendor name and the product name, unless the product // name already contains the vendor name // * if any characters in the string are outside the printable ASCII // range, replace them with '?' #define tolower(c) (((c) >= 'A' && (c) <= 'Z') ? (c) + ('a'-'A') : (c)) CODE_SEGMENT(PAGE_DD_CODE) void NvTiming_GetMonitorName(NVT_EDID_INFO *pEdidInfo, NvU8 monitor_name[NVT_EDID_MONITOR_NAME_STRING_LENGTH]) { NvU8 product_name[NVT_EDID_MONITOR_NAME_STRING_LENGTH]; const NvU8 *vendor_name; NVT_STATUS status; NvU32 i, j; NvBool prepend_vendor; NVMISC_MEMSET(monitor_name, 0, NVT_EDID_MONITOR_NAME_STRING_LENGTH); // get vendor_name: it is either the manufacturer ID or the PNP vendor name vendor_name = pEdidInfo->manuf_name; for (i = 0; i < (sizeof(PNPVendorIds)/sizeof(PNPVendorIds[0])); i++) { if ((vendor_name[0] == PNPVendorIds[i].vendorId[0]) && (vendor_name[1] == PNPVendorIds[i].vendorId[1]) && (vendor_name[2] == PNPVendorIds[i].vendorId[2])) { vendor_name = (const NvU8 *) PNPVendorIds[i].vendorName; break; } } // get the product name from the descriptor blocks status = NvTiming_GetProductName(pEdidInfo, product_name, sizeof(product_name)); if (status != NVT_STATUS_SUCCESS) { product_name[0] = '\0'; } // determine if the product name already includes the vendor name; // if so, do not prepend the vendor name to the monitor name prepend_vendor = NV_TRUE; for (i = 0; i < NVT_EDID_MONITOR_NAME_STRING_LENGTH; i++) { if (vendor_name[i] == '\0') { prepend_vendor = NV_FALSE; break; } if (tolower(product_name[i]) != tolower(vendor_name[i])) { break; } } j = 0; // prepend the vendor name to the monitor name if (prepend_vendor) { for (i = 0; (i < NVT_EDID_MONITOR_NAME_STRING_LENGTH) && (vendor_name[i] != '\0'); i++) { monitor_name[j++] = vendor_name[i]; } } // if we added the vendor name above, add a space between the // vendor name and the product name if ((j > 0) && (j < (NVT_EDID_MONITOR_NAME_STRING_LENGTH - 1))) { monitor_name[j++] = ' '; } // append the product name to the monitor string for (i = 0; (i < NVT_EDID_MONITOR_NAME_STRING_LENGTH) && (product_name[i] != '\0'); i++) { if (j >= (NVT_EDID_MONITOR_NAME_STRING_LENGTH - 1)) { break; } monitor_name[j++] = product_name[i]; } monitor_name[j] = '\0'; RemoveTrailingWhiteSpace(monitor_name, j); RemoveNonPrintableCharacters(monitor_name); } CODE_SEGMENT(PAGE_DD_CODE) void updateHDMILLCDeepColorForTiming(NVT_EDID_INFO *pInfo, NvU32 index) { NVT_EDID_CEA861_INFO *p861Info = &pInfo->ext861; // NOTE: EDID and CEA861 does not have clear statement regarding this. // To be backward compatible with current Nvidia implementation, if not edid >= 1.4 and CEA block exists, follow color format declaration from CEA block. // update supported color space within each bpc // rgb 8bpc always supported UPDATE_BPC_FOR_COLORFORMAT(pInfo->timing[index].etc.rgb444, 0, 1, pInfo->hdmiLlcInfo.dc_30_bit, pInfo->hdmiLlcInfo.dc_36_bit, 0, pInfo->hdmiLlcInfo.dc_48_bit); if (p861Info->basic_caps & NVT_CEA861_CAP_YCbCr_444) { // pHdmiLlc->dc_y444 assumed basic cap is set; when base cap is set, 8bpc yuv444 always supported UPDATE_BPC_FOR_COLORFORMAT(pInfo->timing[index].etc.yuv444, 0, 1, pInfo->hdmiLlcInfo.dc_y444 && pInfo->hdmiLlcInfo.dc_30_bit, pInfo->hdmiLlcInfo.dc_y444 && pInfo->hdmiLlcInfo.dc_36_bit, 0, pInfo->hdmiLlcInfo.dc_y444 && pInfo->hdmiLlcInfo.dc_48_bit); } if (p861Info->basic_caps & NVT_CEA861_CAP_YCbCr_422) { // pHdmiLlc->dc_y444 assumed basic cap is set; when base cap is set, 8bpc yuv422 always supported // newer CEA861/HDMI specs suggest the base cap should support both or neither (Nvidia puts no limitations here) // HDMI1.4b spec Section 6.2.4 Color Depth Requirements states that YCbCr 4:2:2 format is 36-bit mode, which means 8, 10 and 12bpc output is supported as soon as there is enough bandwidth UPDATE_BPC_FOR_COLORFORMAT(pInfo->timing[index].etc.yuv422, 0, 1, 1, 1, 0, 0); } } POP_SEGMENTS