//***************************************************************************
//  Module Name:    s3ddc.c
//
//  Description:    This module checks for a DDC monitor, and returns the 
//                  128 bytes of EDID table if found.  
//
//  Notes:          The routine, DdcSetupRefresh, keeps track of resolution
//                  changes in the registry.  On a resolution change,
//                  DdcSetupRefresh will select the optimal refresh rate.  If
//                  there is NOT any change in the resolution, the user can 
//                  select any refresh rate, as long as the monitor and
//                  driver can support it.
//
//  Copyright (c) 1996  S3, Inc.
//
//***************************************************************************
//@@BEGIN_S3MSINTERNAL
//
//  Revision History:
//
//  $Log:   Q:/SOFTDEV/VCS/NT/MINIPORT/s3ddc.c_v  $
//
//   Rev 1.13   04 Feb 1997 23:40:52   kkarnos
//Added BEGIN/END S3MSINTERNAL blocks.
//
//   Rev 1.12   30 Jan 1997 14:56:24   bryhti
//Fixed the refresh frequency calculation in the Detailed Timing section
//of DdcMaxRefresh - was causing problems in NT 3.51.
//
//   Rev 1.11   30 Jan 1997 09:47:36   bryhti
//Fixed the "for" loop count for Standard Timings in DdcMaxRefresh.
//
//   Rev 1.10   16 Jan 1997 09:21:28   bryhti
//Added CheckDDCType routine to return monitor DDC type.
//
//   Rev 1.9   11 Dec 1996 10:24:38   kkarnos
//
//Fix Set_VSYNC.
//
//   Rev 1.8   10 Dec 1996 16:45:42   kkarnos
//Just added a comment to explain the source of some odd 764 code (EKL input)
//
//   Rev 1.7   10 Dec 1996 16:37:08   kkarnos
//Use register and register bit defines.  Correct assignment of SET VSYNC bit
//
//   Rev 1.6   02 Dec 1996 07:46:16   bryhti
//
//Moved GetDdcInformation () prototype to S3.H.  Added code to
//DdcMaxRefresh () to also check the Detailed Timing Descriptions.
//
//   Rev 1.5   13 Nov 1996 10:14:08   bryhti
//Major cleanup/rewrite to get DDC1 and DDC2 support on M65.  Also got DDC1
//support working on 765.
//
//   Rev 1.4   02 Oct 1996 13:56:42   elau
//765 and new chips support DDC; the newer chip must have a serial port at FF20
//
//   Rev 1.3   22 Aug 1996 11:44:40   elau
//Change int to ULONG to remove warning
//
//   Rev 1.2   18 Aug 1996 16:30:42   elau
//Use HW default setting for DDC if supports
//
//   Rev 1.1   24 Jul 1996 15:37:42   elau
//DDC support for 764
//
//   Rev 1.0   12 Jul 1996 11:52:36   elau
//Initial revision.
//
//@@END_S3MSINTERNAL
//***************************************************************************

#include "s3.h"
#include "cmdcnst.h"

#include "s3ddc.h"

#define     MMFF20  (PVOID) ((ULONG)(HwDeviceExtension->MmIoBase) + SERIAL_PORT_MM)

#define     NO_FLAGS        0
#define     VERIFY_CHECKSUM 1

//
//  Function Prototypes
//
VOID    I2C_Out (PHW_DEVICE_EXTENSION HwDeviceExtension, UCHAR ucData);
VOID    I2C_Setup (PHW_DEVICE_EXTENSION HwDeviceExtension);
VOID    I2C_StartService (PHW_DEVICE_EXTENSION HwDeviceExtension);
VOID    I2C_StopService (PHW_DEVICE_EXTENSION HwDeviceExtension);
VOID    I2C_BitWrite (PHW_DEVICE_EXTENSION HwDeviceExtension, UCHAR ucData);
VOID    I2C_AckWrite (PHW_DEVICE_EXTENSION HwDeviceExtension);
VOID    I2C_NackWrite (PHW_DEVICE_EXTENSION HwDeviceExtension);
UCHAR   I2C_ByteWrite (PHW_DEVICE_EXTENSION HwDeviceExtension, UCHAR ucData);
UCHAR   I2C_BitRead (PHW_DEVICE_EXTENSION HwDeviceExtension);
UCHAR   I2C_ByteRead (PHW_DEVICE_EXTENSION HwDeviceExtension);
UCHAR   I2C_Data_Request (PHW_DEVICE_EXTENSION, UCHAR, long, long, UCHAR *);

VOID    Wait_For_Active (PHW_DEVICE_EXTENSION HwDeviceExtension);
VOID    Set_Vsync (PHW_DEVICE_EXTENSION HwDeviceExtension, UCHAR ucFlag);
VOID    Provide_Fake_VSYNC (PHW_DEVICE_EXTENSION HwDeviceExtension);
UCHAR   Read_EDID_Byte (PHW_DEVICE_EXTENSION HwDeviceExtension);
VOID    Disable_DAC_Video (PHW_DEVICE_EXTENSION HwDeviceExtension);
VOID    Enable_DAC_Video (PHW_DEVICE_EXTENSION HwDeviceExtension);
UCHAR   Read_EDID_Bit (PHW_DEVICE_EXTENSION HwDeviceExtension);
UCHAR   Read_EDID_Byte (PHW_DEVICE_EXTENSION HwDeviceExtension);

UCHAR   Sync_EDID_Header (PHW_DEVICE_EXTENSION HwDeviceExtension);
UCHAR   EDID_Buffer_Xfer (PHW_DEVICE_EXTENSION HwDeviceExtension, UCHAR* pBuffer);

UCHAR   Check_DDC1_Monitor (PHW_DEVICE_EXTENSION HwDeviceExtension);
UCHAR   Configure_Chip_DDC_Caps (PHW_DEVICE_EXTENSION HwDeviceExtension);
UCHAR   GetDdcInformation (PHW_DEVICE_EXTENSION HwDeviceExtension, UCHAR* pBuffer);


/****************************************************************
;       I2C_Out
;
;       Controls the individual toggling of bits in MMFF20 to produce
;       clock and data pulses, and in the end provides a delay.
;
; MMIO FF20h is defined as follows:
;
;      ...  3   2   1   0    SCW = CLK  Write
; --------|---|---|---|---|  SDW = DATA Write
;      ...|SDR|SCR|SDW|SCW|  SCR = CLK  Read
; -------------------------  SDR = DATA Read
;
;       Input:  
;               Using MMIO Base in PHW_DEVICE_EXTENSION 
;               UCHAR ucData
;                   Bit 7:2 = 0
;                   Bit 1   = SDA
;                   Bit 0   = SCL
;       Output:
;
;****************************************************************/

VOID I2C_Out (PHW_DEVICE_EXTENSION HwDeviceExtension, UCHAR ucData)
{

    UCHAR ucPortData;
    unsigned int uCount;

    //
    //  read the current value, clear the clock and data bits, and add
    //  the new clock and data values
    //
        
    ucPortData = (VideoPortReadRegisterUchar (MMFF20) & 0xFC) | ucData;

    VideoPortWriteRegisterUchar (MMFF20, ucPortData);

    //
    //  if we set the clock high, wait for target to set clock high
    //

    if (ucData & 0x01)
    {
        uCount = 2000;
        do
        {
            --uCount;
            ucPortData = VideoPortReadRegisterUchar (MMFF20) & 0x04;

        } while ( !ucPortData && uCount );
    }

    VideoPortStallExecution(5);
}		



/****************************************************************
;   I2C_Setup
;
;   Allow one very long low clock pulse so that monitor has time 
;   to switch to DDC2 mode.
;
;   Input:  
;       Using MMIO Base in PHW_DEVICE_EXTENSION 
;
;   Output:
;
;****************************************************************/

VOID I2C_Setup (PHW_DEVICE_EXTENSION HwDeviceExtension)
{
    //
    //  CLK=low,  DATA=high
    //

    I2C_Out (HwDeviceExtension, 0x02); 

    Wait_For_Active (HwDeviceExtension);
    Wait_For_Active (HwDeviceExtension);

    //
    //  CLK=high, DATA=high
    //

    I2C_Out (HwDeviceExtension, 0x03); 

    Wait_For_Active (HwDeviceExtension);
    Wait_For_Active (HwDeviceExtension);

}

/****************************************************************
;   I2C_StartService
;
;   Provide start sequence for talking to I2C bus.
;
;   Input:  
;       Using MMIO Base in PHW_DEVICE_EXTENSION 
;
;   Output:
;
;****************************************************************/

VOID I2C_StartService (PHW_DEVICE_EXTENSION HwDeviceExtension)
{
    //
    //  CLK=low, DATA=high
    //

    I2C_Out (HwDeviceExtension, 0x02); 

    //
    //  CLK=high, DATA=high
    //

    I2C_Out (HwDeviceExtension, 0x03); 


    //
    //  CLK=high, DATA=low
    //

    I2C_Out (HwDeviceExtension, 0x01); 

    //
    //  CLK=low, DATA=low
    //

    I2C_Out (HwDeviceExtension, 0x00); 

}

/****************************************************************
;   I2C_StopService
;
;   Provide stop sequence to the I2C bus.
;
;   Input:  
;       Using MMIO Base in PHW_DEVICE_EXTENSION 
;
;   Output:
;
;***************************************************************/

VOID I2C_StopService (PHW_DEVICE_EXTENSION HwDeviceExtension)
{
    //
    //  CLK=low, DATA=low
    //

    I2C_Out (HwDeviceExtension, 0x00); 

    //
    //  CLK=high, DATA=low
    //

    I2C_Out (HwDeviceExtension, 0x01); 

    //
    //  CLK=high, DATA=high
    //

    I2C_Out (HwDeviceExtension, 0x03); 

    //
    //  CLK=low, DATA=high
    //

    I2C_Out (HwDeviceExtension, 0x02); 
}



/****************************************************************
;   I2C_BitWrite
;
;   Writes one SDA bit to the I2C bus.
;
;   Input:  
;       Using MMIO Base in PHW_DEVICE_EXTENSION 
;       Bit 1 of ucData = Bit to be written.
;
;   Output:
;
;***************************************************************/

VOID I2C_BitWrite (PHW_DEVICE_EXTENSION HwDeviceExtension, UCHAR ucData)
{

    //
    //  save valid data bit
    //

    ucData &= 0x02;

    //
    // CLK=low,  DATA=xxxx
    //

    I2C_Out (HwDeviceExtension, ucData);

    //
    // CLK=high, DATA=xxxx
    //

    I2C_Out (HwDeviceExtension, (UCHAR) (ucData | 0x01));

    //
    // CLK=low,  DATA=xxxx
    //

    I2C_Out(HwDeviceExtension, ucData);

}



/****************************************************************
;   I2C_ByteWrite
;
;   Output a byte of information to the Display.
;
;   Input:  
;       Using MMIO Base in PHW_DEVICE_EXTENSION 
;       ucData = Byte to be written.
;
;   Output:
;       TRUE - write successfully
;       FALSE - write failure
;
;***************************************************************/

UCHAR I2C_ByteWrite (PHW_DEVICE_EXTENSION HwDeviceExtension, UCHAR ucData)
{
    UCHAR uOutData;
    int i;

    uOutData = ucData;

    //
    //  send MSB first
    //

    for (i=6; i >= 0; i--)
    {
        //
        //  move data bit to bit 1
        //

        uOutData = (ucData >> i);
        I2C_BitWrite (HwDeviceExtension, uOutData);
    }

    //
    //  now send LSB
    //

    uOutData = (ucData << 1);
    I2C_BitWrite (HwDeviceExtension, uOutData);

    //
    //  float the data line high for ACK
    //

    I2C_BitWrite (HwDeviceExtension, 2);
    
    return (TRUE);
}

/****************************************************************
;   I2C_AckWrite
;
;   Send Acknowledgement when reading info.
;
;   Input:  
;       Using MMIO Base in PHW_DEVICE_EXTENSION 
;
;   Output:
;
;***************************************************************/

VOID I2C_AckWrite (PHW_DEVICE_EXTENSION HwDeviceExtension)
{
    I2C_BitWrite (HwDeviceExtension, 0);
}


/****************************************************************
;   I2C_NackWrite
;
;   Send Not ACKnowledgement when reading information.
;   A NACK is DATA high during one clock pulse.
;
;   Input:  
;       Using MMIO Base in PHW_DEVICE_EXTENSION 
;
;   Output: 
;
;***************************************************************/

VOID I2C_NackWrite (PHW_DEVICE_EXTENSION HwDeviceExtension)
{

    I2C_BitWrite (HwDeviceExtension, 02);
}


/****************************************************************
;   I2C_BitRead
;
;   Reads in 1 bit from SDA via the GIP.
;
;   Input:
;       Using MMIO Base in PHW_DEVICE_EXTENSION 
;
;   Output:
;       Bit 0 of return value contains bit read
;
;***************************************************************/

UCHAR I2C_BitRead (PHW_DEVICE_EXTENSION HwDeviceExtension)
{
    UCHAR ucRetval;

    //
    //  CLK=low,  DATA=high
    //

    I2C_Out (HwDeviceExtension, 0x02); 

    //
    //  CLK=high,  DATA=high
    //

    I2C_Out (HwDeviceExtension, 0x03); 

    //
    //  now read in the data bit
    //

    ucRetval = (VideoPortReadRegisterUchar (MMFF20) & 0x08) >> 3;

    //
    //  CLK=low,  DATA=high
    //

    I2C_Out (HwDeviceExtension, 0x02); 

    return (ucRetval);
}


/****************************************************************
;   I2C_ByteRead
;
;   Read a byte of information from the Display
;
;   Input:  
;       Using MMIO Base in PHW_DEVICE_EXTENSION 
;
;   Output:
;       return value is the byte read
;
;***************************************************************/

UCHAR I2C_ByteRead (PHW_DEVICE_EXTENSION HwDeviceExtension)
{
    UCHAR ucRetval;
    int i;

    ucRetval = 0;
    for (i=0; i < 8; i++)
    {
        ucRetval <<= 1;
        ucRetval |= I2C_BitRead (HwDeviceExtension);
    }

    return (ucRetval);
        
}


/****************************************************************
;   I2C_DATA_Request
;
;   Setup Display to query EDID or VDIF information depending
;   upon the offset given.
;
;   Input:  
;       Using MMIO Base in PHW_DEVICE_EXTENSION 
;       ucWriteAddr Write Address of info
;       lLength     Length to read, 
;       lFlags      VERIFY_CHECKSUM
;       pBuffer     pointer to buffer to receive data
;        
;   Output:
;       TRUE    successful read
;       FALSE   read failure or bad checksum
;
;****************************************************************/

UCHAR I2C_Data_Request (    PHW_DEVICE_EXTENSION    HwDeviceExtension,
                            UCHAR                   ucWriteAddr, 
                            long                    lLength, 
                            long                    lFlags,
                            UCHAR                   *pBuffer )
{
    UCHAR ucData;
    UCHAR ucCheckSum = 0;
    long lCount;
    
    I2C_StartService (HwDeviceExtension);
    I2C_ByteWrite (HwDeviceExtension, 0xA0); //Send Device Address + write

    I2C_ByteWrite (HwDeviceExtension, ucWriteAddr); //Send Write Address

    I2C_StartService (HwDeviceExtension);
    I2C_ByteWrite (HwDeviceExtension, 0xA1); //Send Device Address + read

    for (lCount = 0; lCount < lLength - 1; lCount++)
    {
        ucData= I2C_ByteRead (HwDeviceExtension);
        I2C_AckWrite (HwDeviceExtension);
        *pBuffer++ = ucData;
        ucCheckSum += ucData;
    }

    ucData= I2C_ByteRead (HwDeviceExtension);
    I2C_NackWrite (HwDeviceExtension);
    *pBuffer = ucData;
    ucCheckSum += ucData;
    I2C_StopService (HwDeviceExtension);

    
    if (lFlags & VERIFY_CHECKSUM)
    {
        if (ucCheckSum)
        {
            return (FALSE);     // bad checksum
        }
    }

    return TRUE;


}


/****************************************************************
;   GetDdcInformation
;
;   Get 128 bytes EDID information if the monitor supports it.
;   The caller is responsible for allocating the memory.
;        
;   Input:  
;       Using MMIO Base in PHW_DEVICE_EXTENSION 
;       Buffer to receive information
;
;   Output:
;       TRUE    successful
;       FALSE   cannot get DdcInformation 
;        
;***************************************************************/

UCHAR GetDdcInformation (PHW_DEVICE_EXTENSION HwDeviceExtension, UCHAR* pBuffer)
{
    UCHAR ucOldCr40;
    UCHAR ucOldCr53;
    UCHAR ucOldCr55;
    UCHAR ucOldCr5C;
    UCHAR ucOldSr0D;
    UCHAR ucOldSr08;
    UCHAR ucOldMMFF20;
    UCHAR ucData;
    UCHAR ucRetval;

    //
    //  unlock the Sequencer registers
    //

    VideoPortWritePortUchar (SEQ_ADDRESS_REG, UNLOCK_SEQREG); 
    ucOldSr08 = ucData = VideoPortReadPortUchar (SEQ_DATA_REG);
    ucData = UNLOCK_SEQ; 
    VideoPortWritePortUchar (SEQ_DATA_REG, ucData);


    VideoPortWritePortUchar (SEQ_ADDRESS_REG, SRD_SEQREG); 
    ucOldSr0D = ucData = VideoPortReadPortUchar (SEQ_DATA_REG);
    ucData &= DISAB_FEATURE_BITS;    // Disable feature connector

    VideoPortWritePortUchar (SEQ_DATA_REG, ucData);

    //
    //  Enable access to the enhanced registers
    //

    VideoPortWritePortUchar (CRT_ADDRESS_REG, SYS_CONFIG_S3EXTREG);
    ucOldCr40 = ucData = VideoPortReadPortUchar (CRT_DATA_REG);
    ucData |= ENABLE_ENH_REG_ACCESS;
    VideoPortWritePortUchar (CRT_DATA_REG, ucData);

    //
    // Enable MMIO
    //

    VideoPortWritePortUchar (CRT_ADDRESS_REG, EXT_MEM_CTRL1_S3EXTREG);
    ucOldCr53 = ucData = VideoPortReadPortUchar (CRT_DATA_REG);
    ucData |= (ENABLE_OLDMMIO | ENABLE_NEWMMIO);    
    VideoPortWritePortUchar (CRT_DATA_REG, ucData);

    //
    // GOP_1:0=00b, select MUX channel 0
    //
    
    VideoPortWritePortUchar (CRT_ADDRESS_REG, GENERAL_OUT_S3EXTREG);
    ucOldCr5C = ucData = VideoPortReadPortUchar (CRT_DATA_REG);
    ucData |= 0x03;    
    VideoPortWritePortUchar (CRT_DATA_REG, ucData);

    //
    //  enable general input port
    //

    VideoPortWritePortUchar (CRT_ADDRESS_REG, EXT_DAC_S3EXTREG);
    ucOldCr55 = VideoPortReadPortUchar (CRT_DATA_REG);

    //
    //  the 764 doesn't support MMFF20
    //
    //  enable the General Input Port
    //

    if (HwDeviceExtension->SubTypeID == SUBTYPE_764)
    {
        VideoPortWritePortUchar (CRT_DATA_REG,
                                (UCHAR) (ucOldCr55 | ENABLE_GEN_INPORT_READ));
    }
    else
    {
        //
        //  enable the serial port
        //

        ucOldMMFF20 = VideoPortReadRegisterUchar (MMFF20);
        VideoPortWriteRegisterUchar (MMFF20, 0x13);
    }

    //
    //  determine DDC capabilities and branch accordingly
    //
        
    switch ( Configure_Chip_DDC_Caps (HwDeviceExtension) )
    {
    case DDC2:
        I2C_Setup (HwDeviceExtension);
    
        ucRetval = I2C_Data_Request ( 
                                HwDeviceExtension, 
                                0,                  // address offset
                                128,                // read 128 bytes
                                VERIFY_CHECKSUM,    // verify checksum
                                pBuffer);           // buffer to put data
        break;

    case DDC1:
        Disable_DAC_Video (HwDeviceExtension);

        //
        //  first try to sync with the EDID header
        //

        if (ucRetval = Sync_EDID_Header (HwDeviceExtension))
        {
            //
            //  now read in the remainder of the information
            //

            ucRetval = EDID_Buffer_Xfer (HwDeviceExtension, pBuffer);
        }
        Enable_DAC_Video (HwDeviceExtension);
        break;

    default:
        ucRetval = FALSE;       // failure
        break;

    }

    //
    // restore the original register values
    //

    if (HwDeviceExtension->SubTypeID != SUBTYPE_764)
    {
        VideoPortWriteRegisterUchar (MMFF20, ucOldMMFF20);
    }

    VideoPortWritePortUchar (CRT_ADDRESS_REG, EXT_DAC_S3EXTREG);
    VideoPortWritePortUchar (CRT_DATA_REG, ucOldCr55);


    VideoPortWritePortUchar (CRT_ADDRESS_REG, GENERAL_OUT_S3EXTREG);
    VideoPortWritePortUchar (CRT_DATA_REG, ucOldCr5C);

    VideoPortWritePortUchar (CRT_ADDRESS_REG, EXT_MEM_CTRL1_S3EXTREG);
    VideoPortWritePortUchar (CRT_DATA_REG, ucOldCr53);

    VideoPortWritePortUchar (CRT_ADDRESS_REG, SYS_CONFIG_S3EXTREG);
    VideoPortWritePortUchar (CRT_DATA_REG, ucOldCr40);

    VideoPortWritePortUchar (SEQ_ADDRESS_REG, SRD_SEQREG);
    VideoPortWritePortUchar (SEQ_DATA_REG, ucOldSr0D);

    VideoPortWritePortUchar (SEQ_ADDRESS_REG, UNLOCK_SEQREG);
    VideoPortWritePortUchar (SEQ_DATA_REG, ucOldSr08);

    return (ucRetval);

}



/****************************************************************
;   Wait_For_Active
;
;   Use two loop method to find VSYNC then return just after the 
;   falling edge.
;
;   Input:  
;       Using MMIO Base in PHW_DEVICE_EXTENSION 
;
;   Output:
;
;***************************************************************/

VOID Wait_For_Active (PHW_DEVICE_EXTENSION HwDeviceExtension)
{
    PUCHAR InStatPort = SYSTEM_CONTROL_REG;

    while ((VideoPortReadPortUchar (InStatPort) & VSYNC_ACTIVE) != 0) ;
    while ((VideoPortReadPortUchar (InStatPort) & VSYNC_ACTIVE) == 0) ;
}

/****************************************************************
;   Set_VSYNC
;
;   Read the current polarity of the sync, then toggle it on
;   if ucFlag=1, or off if ucFlag=0.
;
;   Input:  
;       using Seq. registers PHW_DEVICE_EXTENSION 
;       ucFlag - see above comment   
;           
;   Output:
;                   
;****************************************************************/

VOID Set_Vsync (PHW_DEVICE_EXTENSION HwDeviceExtension, UCHAR ucFlag)
{

    UCHAR ucData;

    //
    //  read Sequencer Register D and clear VSYNC bits
    //

    VideoPortWritePortUchar (SEQ_ADDRESS_REG, SRD_SEQREG);
    ucData = VideoPortReadPortUchar (SEQ_DATA_REG) & CLEAR_VSYNC;

    //
    //  set VSYNC per the input flag
    //

    if (ucFlag)
        ucData = ((ucData & CLEAR_VSYNC) | SET_VSYNC1);  
    else
        ucData = ((ucData & CLEAR_VSYNC) | SET_VSYNC0);  

    VideoPortWritePortUchar (SEQ_DATA_REG, ucData);
}

/****************************************************************
;   Provide_Fake_VSYNC
;
;   Use loop delays to create a fake VSYNC signal. (~14.9KHz)
;
;   Input:  
;       using Seq. registers PHW_DEVICE_EXTENSION 
;           
;   Output:
;
;***************************************************************/

VOID Provide_Fake_VSYNC (PHW_DEVICE_EXTENSION HwDeviceExtension)
{
    int i;

    Set_Vsync (HwDeviceExtension, 0x01);     // Turn on VSYNC
    VideoPortStallExecution(5);

    Set_Vsync (HwDeviceExtension, 0x00);     // Turn off VSYNC
    VideoPortStallExecution(5);

}


/****************************************************************
;   Disable_DAC_Video
;
;   Disable the DAC video driving BLANK active high. This is
;   done by setting bit D5 of sequencer register 01.
;****************************************************************/

VOID Disable_DAC_Video (PHW_DEVICE_EXTENSION HwDeviceExtension)
{
    UCHAR ucIndex;
    UCHAR ucData;


    ucIndex = VideoPortReadPortUchar (SEQ_ADDRESS_REG);


    VideoPortWritePortUchar (SEQ_ADDRESS_REG, CLK_MODE_SEQREG);

    //
    //  set screen off bit
    //

    ucData = VideoPortReadPortUchar (SEQ_DATA_REG) | SCREEN_OFF_BIT;

    VideoPortWritePortUchar (SEQ_DATA_REG, ucData);

    //
    // restore old index value
    //

    VideoPortWritePortUchar (SEQ_ADDRESS_REG, ucIndex);

}

/****************************************************************
;   Disable_DAC_Video
;
;   Enable the DAC video by clearing bit D5 in sequencer register 01
;***************************************************************/

VOID Enable_DAC_Video (PHW_DEVICE_EXTENSION HwDeviceExtension)
{
    UCHAR ucIndex;
    UCHAR ucData;


    ucIndex = VideoPortReadPortUchar (SEQ_ADDRESS_REG);


    VideoPortWritePortUchar (SEQ_ADDRESS_REG, CLK_MODE_SEQREG);

    //
    //  clear screen off bit
    //

    ucData = VideoPortReadPortUchar (SEQ_DATA_REG) & (~SCREEN_OFF_BIT);

    VideoPortWritePortUchar (SEQ_DATA_REG, ucData);

    //
    // restore old Index value
    //

    VideoPortWritePortUchar (SEQ_ADDRESS_REG, ucIndex);

}


/****************************************************************
;   Read_EDID_Bit:
;
;   Read the next DDC1 EDID data bit
;       
;   Inputs:     
;       PHW_DEVICE_EXTENSION HwDeviceExtension
;
;   Return:     
;       UCHAR   ucData - data in bit 0
;
;***************************************************************/

UCHAR Read_EDID_Bit (PHW_DEVICE_EXTENSION HwDeviceExtension)

{
    switch (HwDeviceExtension->SubTypeID)
    {
    case SUBTYPE_764:
        return (VideoPortReadPortUchar (DAC_ADDRESS_WRITE_PORT) & 1);
        break;

    default:
        return ((VideoPortReadRegisterUchar (MMFF20) & 8) >> 3);
        break;
    }

}

/****************************************************************
;   Read_EDID_Byte
;
;   Reads eight bits from the EDID string
;
;   Input:
;       Using MMIO Base in PHW_DEVICE_EXTENSION 
;       
;   Output:
;       return byte value 
;
;****************************************************************/

UCHAR Read_EDID_Byte (PHW_DEVICE_EXTENSION HwDeviceExtension)
{
    long    i;
    UCHAR   ucRetData;

    ucRetData = 0;
    for (i=0; i < 8; i++)
    {
        ucRetData <<= 1;
        Provide_Fake_VSYNC (HwDeviceExtension);
        ucRetData |= Read_EDID_Bit (HwDeviceExtension);
    }

    return (ucRetData);
}


/****************************************************************
;   Sync_EDID_Header
;
;   Find and sync to the header - 00 FF FF FF FF FF FF 00
;   
;   Inputs:
;           Using MMIO Base in PHW_DEVICE_EXTENSION 
;
;   Outputs:
;           TRUE  = Header Found
;           FALSE = Header NOT Found
;
;***************************************************************/

UCHAR Sync_EDID_Header (PHW_DEVICE_EXTENSION HwDeviceExtension)
{
            
    long lBitCount;
    long lEndCount;
    UCHAR uInSync;
    UCHAR ucEdidData;

    //
    //  there are 8 * 128 bits total, but we could start reading near
    //  the end of the header and realize the error after starting into
    //  the beginning of the header and have to read the entire header
    //  again, so we will try reading up to 144 bytes for safety
    //
    //  the header is 00 FF FF FF FF FF FF 00
    //

    lBitCount = 0;              // init bit counter
    do
    {
        uInSync = TRUE;         // assume found header

        //
        //  looking for 00
        //  checking first bit
        //

        for (lEndCount = lBitCount + 8; lBitCount < lEndCount; lBitCount++)
        {
            Provide_Fake_VSYNC (HwDeviceExtension);
            ucEdidData = Read_EDID_Bit (HwDeviceExtension);
            
            if (ucEdidData == 1)
            {
                uInSync = FALSE;
                break;
            }
        }

        if (!uInSync)
            continue;           // start all over

        //
        // send ACK
        //

        Provide_Fake_VSYNC (HwDeviceExtension);

        //
        //  looking for FF FF FF FF FF FF
        //  8 data bits 
        //  1 bit of acknowledgement
        //

        for (lEndCount = lBitCount + 6 * 8; lBitCount < lEndCount; lBitCount++)
        {
            Provide_Fake_VSYNC (HwDeviceExtension);
            ucEdidData = Read_EDID_Bit (HwDeviceExtension);

            if (ucEdidData == 0)
            {
                uInSync = FALSE;
                break;
            }

            //
            //  send an ACK if we have read 8 bits
            //

            if (!((lEndCount - lBitCount + 1) % 8))
            {
                Provide_Fake_VSYNC (HwDeviceExtension);
            }

        }
        if (!uInSync)
            continue;           // start all over

        //
        //  now looking for last 00 of header
        //

        for (lEndCount = lBitCount + 8; lBitCount < lEndCount; lBitCount++)
        {
            Provide_Fake_VSYNC (HwDeviceExtension);
            ucEdidData = Read_EDID_Bit (HwDeviceExtension);

            if (ucEdidData == 1)
            {
                uInSync = FALSE;
                break;
            }
        }

        if(!uInSync)
            continue;           // start all over

        //
        // Acknowledgment
        //

        Provide_Fake_VSYNC (HwDeviceExtension);


    } while ( (!uInSync) && (lBitCount < (8 * 144)) );

    return (uInSync);
}


/****************************************************************
;   EDID_Buffer_Xfer
;
;   Transfer all EDID data to pBuffer. Caller must allocate enough 
;   memory to receive 128 bytes.
;
;   Input:  
;       Using MMIO Base in PHW_DEVICE_EXTENSION 
;       Pointer to receive buffer
;
;   Output:
;       TRUE    data in buffer & checksum is correct
;       FALSE   error or bad checksum
;
;****************************************************************/

UCHAR EDID_Buffer_Xfer (PHW_DEVICE_EXTENSION HwDeviceExtension, UCHAR* pBuffer)
{
    UCHAR ucChecksum = 0x0FA;
    UCHAR ucEdidData;
    unsigned int uCount;

    //
    //  put the 8 header bytes in the buffer
    //

    *pBuffer = 0;
    for (uCount = 1; uCount < 7; uCount++)
        *(pBuffer+uCount) = 0xFF;

    *(pBuffer+uCount) = 0x00;

    for (uCount = 8; uCount < 128; uCount++)
    {
        ucEdidData = Read_EDID_Byte (HwDeviceExtension);

        //
        //  send Acknowledgment
        //  add data to buffer
        //  add data to checksum
        //

        Provide_Fake_VSYNC (HwDeviceExtension);
        *(pBuffer+uCount) = ucEdidData;
        ucChecksum += ucEdidData;
    }

    if (!ucChecksum)
    {
        return (TRUE);           // checksum is OK
    }

    return (FALSE);              // checksum is NOT
}


/****************************************************************
;   Check_DDC1_Monitor
;   
;   Check for a DDC1 monitor using current vsync.
;
;   Input:  
;           Using MMIO Base in PHW_DEVICE_EXTENSION 
;
;   Output:
;           TRUE    possible DDC1 monitor 
;           FALSE   no EDID data detected on input port
;
;****************************************************************/

UCHAR Check_DDC1_Monitor (PHW_DEVICE_EXTENSION HwDeviceExtension)
{
    
    UCHAR ucSaveOldData;
    UCHAR ucData;
    UCHAR ucGD0;
    unsigned int uCount;
    UCHAR ucDDC1;

    //
    //  assume not DDC1
    //

    ucDDC1 = FALSE;
    
    switch (HwDeviceExtension->SubTypeID)
    {
    //
    //  use reads from 3C8 on the 764 (undocumented, but this use
    //  of the DAC register comes from the 764 BIOS source code).
    //

    case SUBTYPE_764:
        ucSaveOldData = VideoPortReadPortUchar (MISC_OUTPUT_REG_READ);

        //
        // Bit 7 = 0 Positive VSYNC
        //
        VideoPortWritePortUchar (MISC_OUTPUT_REG_WRITE, 
                                (UCHAR) (ucSaveOldData & SEL_POS_VSYNC));
        Wait_For_Active (HwDeviceExtension);


        ucData = VideoPortReadPortUchar (DAC_ADDRESS_WRITE_PORT);

        //
        // Another read for VL systems. (Data left on the GD/SD lines)
        //

        ucGD0 = VideoPortReadPortUchar (DAC_ADDRESS_WRITE_PORT) & 0x01;

        //
        //  read up to 350 bits looking for the data to toggle, indicating
        //  DDC1 data is being sent
        //

        for (uCount = 0; uCount < 350; uCount++)
        {
            Wait_For_Active (HwDeviceExtension);
            ucData = VideoPortReadPortUchar (DAC_ADDRESS_WRITE_PORT) & 0x01;
            if (ucData != ucGD0)
            {
                //
                //  data line toggled, assume DDC1 data is being sent
                //

                ucDDC1 = TRUE;
                break;
            }
        }

        //
        // restore old value
        //

        VideoPortWritePortUchar (MISC_OUTPUT_REG_WRITE, ucSaveOldData);
        break;

    //
    //  else use MMFF20 on the other chips
    //

    default:
        Disable_DAC_Video (HwDeviceExtension);
        Provide_Fake_VSYNC (HwDeviceExtension);
        ucGD0 = VideoPortReadRegisterUchar (MMFF20) & 8;

        for (uCount = 0; uCount < 350; uCount++)
        {
            Provide_Fake_VSYNC (HwDeviceExtension);
            ucData = VideoPortReadRegisterUchar (MMFF20) & 8;
        
            if (ucData != ucGD0)
            {
                //
                //  data line toggled, assume DDC1 data is being sent
                //

                ucDDC1 = TRUE;
                break;
            }
        }
        Enable_DAC_Video (HwDeviceExtension);
        break;

    }

    return (ucDDC1);

}
    
/****************************************************************
;   Configure_Chip_DDC_Caps
;
;   Determine DDC capabilities of display.
;
;   Input:
;           Using MMIO Base in PHW_DEVICE_EXTENSION 
;
;   Output:
;           NO_DDC
;           DDC1: Support DDC1
;           DDC2: Support DDC2
;
;****************************************************************/

UCHAR Configure_Chip_DDC_Caps (PHW_DEVICE_EXTENSION HwDeviceExtension)
{
    UCHAR ucBuffer[2];

    //
    //  we will only use DDC1 on 764
    //

    if (HwDeviceExtension->SubTypeID != SUBTYPE_764)
    {
        //
        //  first check if DDC2 capable
        //

        I2C_Setup (HwDeviceExtension);
        I2C_Data_Request (  HwDeviceExtension, 
                            0,                  // address offset
                            2,                  // look at first 2 bytes
                            NO_FLAGS,           // don't verify checksum
                            ucBuffer );         // buffer to place data

        //
        //  check if the first 2 bytes of the EDID header look correct
        //

        if ( (ucBuffer [0] == 0)    &&
             (ucBuffer [1] == 0xFF) )
        {
            return (DDC2);      // assume DDC2 capable
        }
    }

    //
    //  try DDC1
    //

    if (Check_DDC1_Monitor (HwDeviceExtension))
    {
        return (DDC1);
    }

    return (NO_DDC);
}


//---------------------------------------------------------------------------


ULONG DdcMaxRefresh(ULONG uXresolution, UCHAR * pEdid)
{
    ULONG uMaxFreq = 0;
    ULONG uEdidRes;
    ULONG uEdidFreq;
    ULONG HorRes, VertRes;
    ULONG i, Index;

    //
    // Detailed timing 
    //

    for (i = 0; i < 4; ++i)     // 4 Detailed Descriptions
    {
        Index = 54 + i * 18;
        if ( (pEdid [Index] == 0)       &&
             (pEdid [Index + 1] == 0)   &&
             (pEdid [Index + 2] == 0) )
        {
            continue;   // Monitor descriptor block, skip it
        }

        HorRes = ((ULONG) (pEdid [Index + 4] & 0xF0)) << 4;
        HorRes += (ULONG) pEdid [Index + 2];

        if (HorRes == uXresolution)
        {
            //
            //  add Horizontal blanking
            //

            HorRes += (ULONG) pEdid [Index + 3];
            HorRes += ((ULONG) (pEdid [Index + 4] & 0x0F)) << 8;

            //
            //  now get Vertical Total (Active & Blanking)
            //
                        
            VertRes =  ((ULONG) (pEdid [Index + 7] & 0xF0)) << 4;
            VertRes += ((ULONG) (pEdid [Index + 7] & 0x0F)) << 8;
            VertRes += (ULONG) pEdid [Index + 5];
            VertRes += (ULONG) pEdid [Index + 6];

            uEdidFreq = (((ULONG) pEdid [Index + 1]) << 8) +
                         ((ULONG) pEdid [Index]);

            uEdidFreq = uEdidFreq * 10000 / HorRes / VertRes;

            if (uEdidFreq > uMaxFreq)
            {
                uMaxFreq = uEdidFreq;
            }
        }
    }
    //
    // Standard timing id.
    //

    for (i = 38; i < 54; i += 2)
    {
        uEdidRes = (((ULONG) pEdid[i]) + 31) * 8;
        if (uXresolution == uEdidRes)
        {
            uEdidFreq = (((ULONG) pEdid[i+1]) & 0x3F) + 60;
            if (uEdidFreq > uMaxFreq)
            {
                uMaxFreq = uEdidFreq;
            }
        }
    }

    //    
    // Established timing
    //
        
    switch (uXresolution)
    {
        case 640:
            uEdidFreq = (ULONG)pEdid[0x23];
            if (uEdidFreq & 0x020)
            {
                if (uMaxFreq < 60)
                {
                    uMaxFreq = 60;
                }
            }
            if (uEdidFreq & 0x08)
            {
                if (uMaxFreq < 72)
                {
                    uMaxFreq = 72;
                }
            }
            if (uEdidFreq & 0x04)
            {
                if (uMaxFreq < 75)
                {
                    uMaxFreq = 75;
                }
            }
            break;

        case 800:
            uEdidFreq = (ULONG)pEdid[0x23];
            if (uEdidFreq & 0x02)
            {
                if (uMaxFreq < 56)
                {
                    uMaxFreq = 56;
                }
            }
            if (uEdidFreq & 0x01)
            {
                if (uMaxFreq < 60)
                {
                    uMaxFreq = 60;
                }
            }

            uEdidFreq = (ULONG)pEdid[0x24];
            if (uEdidFreq & 0x80)
            {
                if (uMaxFreq < 72)
                {
                    uMaxFreq = 72;
                }
            }
            if (uEdidFreq & 0x40)
            {
                if (uMaxFreq < 75)
                {
                    uMaxFreq = 75;
                }
            }
            break;

        case 1024:
            uEdidFreq = (ULONG)pEdid[0x24];
            if (uEdidFreq & 0x08)
            {
                if (uMaxFreq < 60)
                {
                    uMaxFreq = 60;
                }
            }
            if (uEdidFreq & 0x04)
            {
                if (uMaxFreq < 70)
                {
                    uMaxFreq = 70;
                }
            }
            if (uEdidFreq & 0x02)
            {
                if (uMaxFreq < 75)
                {
                    uMaxFreq = 75;
                }
            }
            break;

        case 1280:
            uEdidFreq = (ULONG)pEdid[0x24];
            if (uEdidFreq & 0x01)
            {
                if (uMaxFreq < 75)
                {
                    uMaxFreq = 75;
                }
            }
            break;
    }

    return(uMaxFreq);

}

//---------------------------------------------------------------------------

ULONG DdcRefresh (PHW_DEVICE_EXTENSION hwDeviceExtension, ULONG uXResolution)
{

    ULONG  lRefresh = 0;
    char szBuffer[200];

                        
    if (GetDdcInformation (hwDeviceExtension, szBuffer))
    {
        lRefresh = DdcMaxRefresh (uXResolution, szBuffer);
    }

    return lRefresh;
}


/****************************************************************
;   CheckDDCType
;
;   Check the monitor for DDC type.
;        
;   Input:  
;       Using MMIO Base in PHW_DEVICE_EXTENSION 
;
;   Output:
;       NO_DDC  non-DDC monitor
;       DDC1    DDC1 monitor
;       DDC2    DDC2 monitor
;       
;***************************************************************/

UCHAR CheckDDCType (PHW_DEVICE_EXTENSION HwDeviceExtension)
{
    UCHAR ucOldCr40;
    UCHAR ucOldCr53;
    UCHAR ucOldCr55;
    UCHAR ucOldCr5C;
    UCHAR ucOldSr0D;
    UCHAR ucOldSr08;
    UCHAR ucOldMMFF20;
    UCHAR ucData;
    UCHAR ucRetval;

    //
    //  unlock the Sequencer registers
    //

    VideoPortWritePortUchar (SEQ_ADDRESS_REG, UNLOCK_SEQREG); 
    ucOldSr08 = ucData = VideoPortReadPortUchar (SEQ_DATA_REG);
    ucData = UNLOCK_SEQ; 
    VideoPortWritePortUchar (SEQ_DATA_REG, ucData);


    VideoPortWritePortUchar (SEQ_ADDRESS_REG, SRD_SEQREG); 
    ucOldSr0D = ucData = VideoPortReadPortUchar (SEQ_DATA_REG);
    ucData &= DISAB_FEATURE_BITS;    // Disable feature connector

    VideoPortWritePortUchar (SEQ_DATA_REG, ucData);

    //
    //  Enable access to the enhanced registers
    //

    VideoPortWritePortUchar (CRT_ADDRESS_REG, SYS_CONFIG_S3EXTREG);
    ucOldCr40 = ucData = VideoPortReadPortUchar (CRT_DATA_REG);
    ucData |= ENABLE_ENH_REG_ACCESS;
    VideoPortWritePortUchar (CRT_DATA_REG, ucData);

    //
    // Enable MMIO
    //

    VideoPortWritePortUchar (CRT_ADDRESS_REG, EXT_MEM_CTRL1_S3EXTREG);
    ucOldCr53 = ucData = VideoPortReadPortUchar (CRT_DATA_REG);
    ucData |= (ENABLE_OLDMMIO | ENABLE_NEWMMIO);    
    VideoPortWritePortUchar (CRT_DATA_REG, ucData);

    //
    // GOP_1:0=00b, select MUX channel 0
    //
    
    VideoPortWritePortUchar (CRT_ADDRESS_REG, GENERAL_OUT_S3EXTREG);
    ucOldCr5C = ucData = VideoPortReadPortUchar (CRT_DATA_REG);
    ucData |= 0x03;    
    VideoPortWritePortUchar (CRT_DATA_REG, ucData);

    //
    //  enable general input port
    //

    VideoPortWritePortUchar (CRT_ADDRESS_REG, EXT_DAC_S3EXTREG);
    ucOldCr55 = VideoPortReadPortUchar (CRT_DATA_REG);

    //
    //  the 764 doesn't support MMFF20
    //
    //  enable the General Input Port
    //

    if (HwDeviceExtension->SubTypeID == SUBTYPE_764)
    {
        VideoPortWritePortUchar (CRT_DATA_REG,
                                (UCHAR) (ucOldCr55 | ENABLE_GEN_INPORT_READ));
    }
    else
    {
        //
        //  enable the serial port
        //

        ucOldMMFF20 = VideoPortReadRegisterUchar (MMFF20);
        VideoPortWriteRegisterUchar (MMFF20, 0x13);
    }

    //
    //  determine DDC capabilities and branch accordingly
    //
        
    ucRetval = Configure_Chip_DDC_Caps (HwDeviceExtension);

    //
    // restore the original register values
    //

    if (HwDeviceExtension->SubTypeID != SUBTYPE_764)
    {
        VideoPortWriteRegisterUchar (MMFF20, ucOldMMFF20);
    }

    VideoPortWritePortUchar (CRT_ADDRESS_REG, EXT_DAC_S3EXTREG);
    VideoPortWritePortUchar (CRT_DATA_REG, ucOldCr55);


    VideoPortWritePortUchar (CRT_ADDRESS_REG, GENERAL_OUT_S3EXTREG);
    VideoPortWritePortUchar (CRT_DATA_REG, ucOldCr5C);

    VideoPortWritePortUchar (CRT_ADDRESS_REG, EXT_MEM_CTRL1_S3EXTREG);
    VideoPortWritePortUchar (CRT_DATA_REG, ucOldCr53);

    VideoPortWritePortUchar (CRT_ADDRESS_REG, SYS_CONFIG_S3EXTREG);
    VideoPortWritePortUchar (CRT_DATA_REG, ucOldCr40);

    VideoPortWritePortUchar (SEQ_ADDRESS_REG, SRD_SEQREG);
    VideoPortWritePortUchar (SEQ_DATA_REG, ucOldSr0D);

    VideoPortWritePortUchar (SEQ_ADDRESS_REG, UNLOCK_SEQREG);
    VideoPortWritePortUchar (SEQ_DATA_REG, ucOldSr08);

    return (ucRetval);

}