/*++ Copyright (c) 2006 - 2010 Intel Corporation. All rights reserved This program and the accompanying materials are licensed and made available under the terms and conditions of the BSD License which accompanies this distribution. The full text of the license may be found at http://opensource.org/licenses/bsd-license.php. THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. Module Name: EDID.c Abstract: This file produces the panel EDID information to graphics controller. It is called by GraphicsOutput.c. It reads the EDID table and parse the timting information to graphics controller --*/ #include "Gop.h" #include "EDID.h" // // Standard timing defined by VESA EDID // EDID_TIMING mVbeEstablishedEdidTiming[] = { // // Established Timing I // {800, 600, 60}, {800, 600, 56}, {640, 480, 75}, {640, 480, 72}, {640, 480, 67}, {640, 480, 60}, {720, 400, 88}, {720, 400, 70}, // // Established Timing II // {1280, 1024, 75}, {1024, 768, 75}, {1024, 768, 70}, {1024, 768, 60}, {1024, 768, 87}, {832, 624, 75}, {800, 600, 75}, {800, 600, 72}, // // Established Timing III // {1152, 870, 75} }; // // Commonly used utilities to read/write MMIO register // UINT8 GopMMIOReadByte ( IN EFI_PCI_IO_PROTOCOL *PciIo, IN UINTN Offset ) { UINT8 Data; PciIo->Mem.Read (PciIo, EfiPciIoWidthUint8, MMADR_BAR_INDEX, Offset, 1, &Data); return Data; } VOID GopMMIOWriteByte ( IN EFI_PCI_IO_PROTOCOL *PciIo, IN UINTN Offset, IN UINT8 Data ) { PciIo->Mem.Write (PciIo, EfiPciIoWidthUint8, MMADR_BAR_INDEX, Offset, 1, &Data); return ; } UINT16 GopMMIOReadWord ( IN EFI_PCI_IO_PROTOCOL *PciIo, IN UINTN Offset ) { UINT16 Data; PciIo->Mem.Read (PciIo, EfiPciIoWidthUint16, MMADR_BAR_INDEX, Offset, 1, &Data); return Data; } VOID GopMMIOWriteWord ( IN EFI_PCI_IO_PROTOCOL *PciIo, IN UINTN Offset, IN UINT16 Data ) { PciIo->Mem.Write (PciIo, EfiPciIoWidthUint16, MMADR_BAR_INDEX, Offset, 1, &Data); return ; } UINT32 GopMMIOReadDWord ( IN EFI_PCI_IO_PROTOCOL *PciIo, IN UINTN Offset ) { UINT32 Data; PciIo->Mem.Read (PciIo, EfiPciIoWidthUint32, MMADR_BAR_INDEX, Offset, 1, &Data); return Data; } VOID GopMMIOWriteDWord ( IN EFI_PCI_IO_PROTOCOL *PciIo, IN UINTN Offset, IN UINT32 Data ) { PciIo->Mem.Write (PciIo, EfiPciIoWidthUint32, MMADR_BAR_INDEX, Offset, 1, &Data); return ; } // // Local Function Prototypes // EFI_STATUS AcquireGMBus ( IN EFI_PCI_IO_PROTOCOL *PciIo ) { UINTN Index; EFI_GMBUS_STATUS_REG BusStatus; for (Index = 0; Index < RETRY_TIMES; Index++) { BusStatus.uint32 = GopMMIOReadDWord (PciIo, GMBUS2); if (BusStatus.Status.InUse == 0) { return EFI_SUCCESS; } } return EFI_NOT_READY; } EFI_STATUS ReleaseGMBus ( IN EFI_PCI_IO_PROTOCOL *PciIo ) { UINT16 Value; Value = GopMMIOReadWord (PciIo, GMBUS2); Value = (UINT16) (Value | GMINUSE); GopMMIOWriteWord (PciIo, GMBUS2, Value); return EFI_SUCCESS; } EFI_STATUS GMBusReadData ( IN EFI_PCI_IO_PROTOCOL *PciIo, IN UINT8 *Buffer, IN OUT UINT32 *NumOfBytes ) { EFI_GMBUS_COMMAND_REG BusCommand; EFI_GMBUS_STATUS_REG BusStatus; UINTN Index; UINT32 *Data; BusCommand.Command.SlaveDirect = 1; BusCommand.Command.SlaveAddr = VCLADDRESS >> 1; BusCommand.Command.SlaveIndex = 0; BusCommand.Command.TotalByte = *NumOfBytes; BusCommand.Command.BusCycle = 0x5; BusCommand.Command.RSVD = 0; BusCommand.Command.EnableTimeout = 1; BusCommand.Command.SWRdy = 1; BusCommand.Command.SWClrInt = 0; GopMMIOWriteDWord (PciIo, GMBUS1, BusCommand.uint32); for (Index = 0; Index < *NumOfBytes; Index += 4) { // // HW_RDY bit asserted? // do { BusStatus.uint32 = GopMMIOReadDWord (PciIo, GMBUS2); } while (BusStatus.Status.HardwareReady != 1); if ((BusStatus.Status.SlaveStallTimeoutErr == 1) || (BusStatus.Status.SlaveAckTimeoutErr == 1)) { // // bus error, set/reset software clear interrupt // BusCommand.uint32 = 0; BusCommand.Command.SWClrInt = 1; GopMMIOWriteDWord (PciIo, GMBUS1, BusCommand.uint32); BusCommand.Command.SWClrInt = 0; GopMMIOWriteDWord (PciIo, GMBUS1, BusCommand.uint32); // // Now poll GMBUS2 again for HW_RDY and GA // do { BusStatus.uint32 = GopMMIOReadDWord (PciIo, GMBUS2); } while ((BusStatus.Status.HardwareReady != 1) && (BusStatus.Status.GActive != 0)); return EFI_UNSUPPORTED; } // // Data is now in GMBUS data register now // Data = (UINT32 *) Buffer; *Data = GopMMIOReadDWord (PciIo, GMBUS3); Buffer += 4; } // // read completed? bus idle? // do { BusStatus.uint32 = GopMMIOReadDWord (PciIo, GMBUS2); } while (BusStatus.Status.GActive == 1); return EFI_SUCCESS; } EFI_STATUS ReadEDID ( IN EFI_PCI_IO_PROTOCOL *PciIo, OUT VOID **EDIDBlock, OUT UINT32 *EDIDSize ) /*++ Routine Description: Returns information about the geometry and configuration of the graphics controller's current frame buffer configuration Arguments: PciIo - PCI IO protocol EDIDBlock - Callee allocated buffer to return EDID table EDIDSize - Size of the returning buffer Returns: EFI_SUCCESS - Valid EDID table was returned EFI_OUT_OF_RESOURCES - Not enough resources EFI_UNSUPPORTED - Panel did not support DDC2/EDID EFI_NOT_READY - GMBus not ready --*/ { EFI_STATUS Status; UINT8 EdidData[512]; UINT8 *ValidEdid; UINT32 TotalBytes; EFI_GMBUS_CLOCK_PORT_REG BusClockPort; UINTN Index; UINT64 Signature; // // First acquire the bus // Status = AcquireGMBus (PciIo); if (EFI_ERROR (Status)) { return Status; } // // Select I2C port/clock: Analog Monitor // BusClockPort.uint32 = 0; BusClockPort.Clock_Port.BusRateSelect = 0; BusClockPort.Clock_Port.PinPairSelect = 2; GopMMIOWriteDWord (PciIo, GMBUS0, BusClockPort.uint32); // // Read 256 byte data // TotalBytes = 2 * sizeof (EDID_BLOCK); Status = GMBusReadData (PciIo, &EdidData[0], &TotalBytes); // // Release the bus // ReleaseGMBus (PciIo); if (!EFI_ERROR (Status)) { // // Make a copy of the 256 byte EDID data to ensure integrity of at least one EDID block // CopyMem (&EdidData[256], &EdidData[0], 256); // // Search for the EDID signature // ValidEdid = &EdidData[0]; Signature = EDID_HEADER_SIGNATURE; for (Index = 0; Index < 256; Index++, ValidEdid++) { if (CompareMem (ValidEdid, &Signature, 8) == 0) { break; } } if (Index == 256) { // // No EDID signature found // return EFI_UNSUPPORTED; } *EDIDBlock = AllocateCopyPool ( sizeof (EDID_BLOCK), ValidEdid ); if (!(*EDIDBlock)) { return EFI_OUT_OF_RESOURCES; } // // Currently only support EDID 1.x // *EDIDSize = 128; } return Status; } BOOLEAN CheckTableIntegrity ( UINT8 *Data, UINTN NumOfBytes ) /*++ Routine Description: Returns information about the geometry and configuration of the graphics controller's current frame buffer configuration Arguments: Data - Data buffer NumOfBytes - Size in bytes of the data buffer Returns: TRUE - Valid EDID table FALSE - Data corrupted --*/ { UINT8 Checksum; UINTN Index; Checksum = 0; for (Index = 0; Index < NumOfBytes; Index++) { Checksum = (UINT8) (Checksum + Data[Index]); } if (Checksum) { return FALSE; } else { return TRUE; } } UINT32 CalculateEdidKey ( EDID_TIMING *EdidTiming ) /*++ Routine Description: Generate a search key for a specified timing data. Arguments: EdidTiming - Pointer to EDID timing Returns: The 32 bit unique key for search. --*/ { UINT32 Key; // // Be sure no conflicts for all standard timing defined by VESA. // Key = (EdidTiming->HorizontalResolution * 2) + EdidTiming->VerticalResolution + EdidTiming->RefreshRate; return Key; } EFI_STATUS ParseEDIDTable ( IN EDID_BLOCK *EDIDTable, IN UINTN TableSize, OUT VALID_EDID_TIMING *ValidEdidTiming ) /*++ Routine Description: Returns information about the geometry and configuration of the graphics controller's current frame buffer configuration Arguments: EDIDTable - EDID block to be parsed TableSize - Size of the EDID block TimingInfo - The timing information read out from EDID Returns: EFI_SUCCESS - Valid timing infor returned EFI_VOLUME_CORRUPTED - EDID table corrupted EFI_UNSUPPORTED - No valid information found --*/ { UINT32 Index; UINT32 ValidNumber; UINT32 TimingBits; UINT8 *BufferIndex; UINT16 HorizontalResolution; UINT16 VerticalResolution; UINT8 AspectRatio; UINT8 RefreshRate; EDID_TIMING TempTiming; // // First check the integrity of the first block // if (!CheckTableIntegrity ((UINT8 *) EDIDTable, sizeof (EDID_BLOCK))) { return EFI_VOLUME_CORRUPTED; } if (EDIDTable->EDIDHeader != EDID_HEADER_SIGNATURE) { return EFI_UNSUPPORTED; } // // We only support EDID 1.3 and above // if ((EDIDTable->EDIDVersion < SUPPORTED_EDID_VERSION) || (EDIDTable->EDIDRevision < SUPPORTED_EDID_REVISION)) { return EFI_UNSUPPORTED; } // // In EDID1.3, preferred timing mode is required. The preferred // timing mode is indicated in the first detailed timing block // if (!(EDIDTable->FeatureSupport & PREFERRED_TIMING_MODE)) { return EFI_UNSUPPORTED; } ValidNumber = 0; ZeroMem (ValidEdidTiming, sizeof (VALID_EDID_TIMING)); if ((EDIDTable->ET1 != 0) || (EDIDTable->ET2 != 0) || (EDIDTable->ET3 != 0)) { // // Established timing data // TimingBits = EDIDTable->ET1 | (EDIDTable->ET2 << 8) | ((EDIDTable->ET3 & 0x80) << 9) ; for (Index = 0; Index < VBE_EDID_ESTABLISHED_TIMING_MAX_NUMBER; Index ++) { if (TimingBits & 0x1) { ValidEdidTiming->Key[ValidNumber] = CalculateEdidKey (&mVbeEstablishedEdidTiming[Index]); ValidNumber ++; } TimingBits = TimingBits >> 1; } } else { // // If no Established timing data, read the standard timing data // BufferIndex = (UINT8 *) &EDIDTable->STD1; for (Index = 0; Index < 8; Index ++) { if ((BufferIndex[0] != 0x1) && (BufferIndex[1] != 0x1)){ // // A valid Standard Timing // HorizontalResolution = BufferIndex[0] * 8 + 248; AspectRatio = BufferIndex[1] >> 6; switch (AspectRatio) { case 0: VerticalResolution = HorizontalResolution / 16 * 10; break; case 1: VerticalResolution = HorizontalResolution / 4 * 3; break; case 2: VerticalResolution = HorizontalResolution / 5 * 4; break; case 3: VerticalResolution = HorizontalResolution / 16 * 9; break; default: VerticalResolution = HorizontalResolution / 4 * 3; break; } RefreshRate = (BufferIndex[1] & 0x1f) + 60; TempTiming.HorizontalResolution = HorizontalResolution; TempTiming.VerticalResolution = VerticalResolution; TempTiming.RefreshRate = RefreshRate; ValidEdidTiming->Key[ValidNumber] = CalculateEdidKey (&TempTiming); ValidNumber ++; } BufferIndex += 2; } } ValidEdidTiming->ValidNumber = ValidNumber; return TRUE; } BOOLEAN SearchEdidTiming ( VALID_EDID_TIMING *ValidEdidTiming, EDID_TIMING *EdidTiming ) /*++ Routine Description: Search a specified Timing in all the valid EDID timings. Arguments: ValidEdidTiming - All valid EDID timing information. EdidTiming - The Timing to search for. Returns: TRUE - Found. FALSE - Not found. --*/ { UINT32 Index; UINT32 Key; Key = CalculateEdidKey (EdidTiming); for (Index = 0; Index < ValidEdidTiming->ValidNumber; Index ++) { if (Key == ValidEdidTiming->Key[Index]) { return TRUE; } } return FALSE; }