/* * SPDX-FileCopyrightText: Copyright (c) 2010-2025 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. */ /******************************* DisplayPort *******************************\ * * * Module: dp_edid.h * * reading EDID from SST/MST Device * * * \***************************************************************************/ #ifndef INCLUDED_DP_EDID_H #define INCLUDED_DP_EDID_H #include "dp_buffer.h" #include "dp_auxbus.h" #include "dp_address.h" #include "dp_messages.h" #include "dp_messagecodings.h" #include "dp_timer.h" namespace DisplayPort { class Edid; // // Shared utility object for MST/SST edid reading. // This object handles the retry, CRC validating, // identification of EDID length, DDC ping, etc. // // It's designed as an asynchronous state machine // because of the way MST EDID reads are built. // class EdidAssembler { public: EdidAssembler(Edid * const edid, bool bPatchCrc = false); // // returns false - when existing data in Edid is invalid // returns seg - segment from which to read next block // returns offset - offset within block from which to start reading next block // bool readNextRequest(NvU8 & seg, NvU8 & offset); // returns false when Edid read is completed void postReply(const Buffer & buffer, unsigned sizeCompleted, bool success); void postReply(unsigned char * data, unsigned sizeCompleted, bool success); // returns true when it read all the required blocks bool readIsComplete(); void reset(); private: Edid * edid; Stream stream; NvU8 oldBlockChecksum; unsigned blocksRead; unsigned totalBlockCnt; unsigned retriesCount; bool bPatchCrc; }; // // EDID // class Edid { public: Edid(); ~Edid(); Buffer * getBuffer() const { return &buffer; } NvU8 getFirstPageChecksum(); // Get checksum byte NvU8 getLastPageChecksum(); // Get checksum byte for last block bool verifyCRC(); unsigned getEdidVersion(); unsigned getBlockCount(); const char * getName() const; unsigned getEdidSize() const; bool isChecksumValid() const; bool isJunkEdid() const; bool isFallbackEdid() const; void swap(Edid & right); void applyEdidWorkArounds(NvU32 warFlag, const DpMonitorDenylistData *pDenylistData); void patchCrc(); void setForcedEdidChecksum(bool set) { this->forcedCheckSum = set; } void setFallbackFlag(bool set) { this->fallbackEdid = set; } void setPatchedChecksum(bool set) { this->patchedChecksum = set; } bool isPatchedChecksum() const { return this->patchedChecksum; } bool isValidHeader() const; unsigned getManufId() const { if (buffer.getLength() < 0xa) return 0; return ((buffer.data[0x9] << 8) | (buffer.data[0x8])); } unsigned getProductId() const { if (buffer.getLength() < 0xc) return 0; return ((buffer.data[0xb] << 8) | (buffer.data[0xa])); } unsigned getYearWeek() const { if (buffer.getLength() < 0x12) return 0; return ((buffer.data[0x11] << 8) | (buffer.data[0x10])); } typedef struct { bool extensionCountDisabled; bool dataForced; bool disableDpcdPowerOff; bool forceMaxLinkConfig; bool powerOnBeforeLt; bool skipRedundantLt; bool skipCableBWCheck; bool overrideOptimalLinkCfg; bool overrideMaxLaneCount; bool ignoreRedundantHotplug; bool delayAfterD3; bool keepLinkAlive; bool useLegacyAddress; bool bIgnoreDscCap; // Ignore DSC even if sink reports DSC capability bool bDisableDownspread; bool bForceHeadShutdown; bool bDisableDscMaxBppLimit; bool bSkipCableIdCheck; bool bAllocateManualTimeslots; }_WARFlags; _WARFlags WARFlags; typedef struct { unsigned maxLaneCount; // Max lane count value to override unsigned maxLaneAtHighRate; // Max lane count supported at HBR unsigned maxLaneAtLowRate; // Max lane count supported at RBR unsigned optimalLinkRate; // Optimal link rate value to override unsigned optimalLaneCount; // Optimal lane count value to override }_WARData; _WARData WARData; void resetData() { buffer.reset(); checkSumValid = false; forcedCheckSum = false; fallbackEdid = false; // clear the WARFlags _WARFlags temp = {0}; WARFlags = temp; } bool operator== (const Edid & other) { return (buffer == other.buffer); } bool operator!= (const Edid & other) { return !(buffer == other.buffer); } private: void validateCheckSum(); mutable Buffer buffer; bool checkSumValid; bool forcedCheckSum; bool fallbackEdid; bool patchedChecksum; }; // // SST EDID Read API // bool EdidReadSST(Edid & edid, AuxBus * aux, Timer * timer, bool pendingTestRequestEdidRead = false, bool bBypassAssembler = false, MainLink *main = NULL); enum EDID_DDC { EDID_DDC_NONE = 0x00, EDID_DDC_ADR0 = 0xA0, EDID_DDC_ADR1 = 0xA2, EDID_DDC_ADR2 = 0xA6, EDID_SEG_SELECTOR_OFFSET = 0x60, }; EDID_DDC sstDDCPing(AuxBus & dpAux); // // MST EDID Read API // class EdidReadMultistream : public Object, protected MessageManager::Message::MessageEventSink, Timer::TimerCallback { public: class EdidReadMultistreamEventSink // Connector will inherit from this { public: virtual void mstEdidCompleted(EdidReadMultistream * from) = 0; virtual void mstEdidReadFailed(EdidReadMultistream * from) = 0; }; EdidReadMultistream(Timer * timer, MessageManager * manager, EdidReadMultistream::EdidReadMultistreamEventSink * sink, Address topologyAddress) : topologyAddress(topologyAddress), manager(manager), edidReaderManager(&edid), ddcIndex(0), retries(0), timer(timer), sink(sink) { startReadingEdid(); } Edid edid; Address topologyAddress; ~EdidReadMultistream(); private: void startReadingEdid(); MessageManager * manager; RemoteI2cReadMessage remoteI2cRead; EdidAssembler edidReaderManager; // come up another word besides edidReaderManager eg Manager NvU8 DDCAddress; NvU8 ddcIndex; unsigned retries; Timer * timer; void readNextBlock(NvU8 seg, NvU8 offset); void failedToReadEdid(); void expired(const void * tag); EdidReadMultistreamEventSink * sink; virtual void messageFailed(MessageManager::Message * from, NakData * nakData); virtual void messageCompleted(MessageManager::Message * from); void edidAttemptDone(bool succeeded); }; // // Useful defines // enum { EDID_BLOCK_SIZE = 0x80, EDID_SEGMENT_SIZE = 2*EDID_BLOCK_SIZE, EDID_POLICY_BLOCK_READ_MAX_RETRY_COUNT = 3, // DID EDID CTS v1.3 d12 currently outlines that Source shall support up to 16 blocks of EDID data. EDID_MAX_BLOCK_COUNT = 16, }; static const NvU8 ddcAddrList[] = {EDID_DDC_ADR0, EDID_DDC_ADR1, EDID_DDC_ADR2}; const NvU8 ddcAddrListSize = sizeof(ddcAddrList)/sizeof(NvU8); // HDMI 1.4 Section 8.5: HDMI Sink can have up to 100ms to get EDID ready. const NvU8 EDID_READ_RETRY_TIMEOUT_MS = 100; const NvU8 EDID_MAX_AUX_RETRIES = 10; const NvU8 EDID_AUX_WAIT_TIME = 1; NvU8 getEDIDBlockChecksum(const Buffer &); void makeEdidFallback(Edid & edid, NvU32 fallbackFormatSupported = 0); void makeEdidFallbackVGA(Edid & edid); } #endif //INCLUDED_DP_EDID_H