Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

636 lines
25 KiB

/************************************************************************/
/* */
/* CVTDDC.C */
/* */
/* November 10 1995 (c) 1995 ATI Technologies Incorporated. */
/************************************************************************/
/********************** PolyTron RCS Utilities
$Revision: 1.5 $
$Date: 10 Apr 1996 16:58:22 $
$Author: RWolff $
$Log: S:/source/wnt/ms11/miniport/archive/cvtddc.c_v $
//
// Rev 1.5 10 Apr 1996 16:58:22 RWolff
//Temorarily treats all cards as non-DDC to avoid system hang due to
//conflict over system timer registers. Final solution is to make DDC
//query in the miniport, using the StallExecution function, rather than
//calling the BIOS function which can hang the machine.
//
// Rev 1.4 01 Mar 1996 12:13:28 RWolff
//Now saves and restores the portion of video memory used as
//a buffer to hold data returned by the DDC query call.
//
// Rev 1.3 02 Feb 1996 17:15:44 RWolff
//Now gets DDC/VDIF merge source information from hardware device
//extension rather than storing it in static variables, moved code to
//obtain a buffer in VGA memory to a separate routine.
//
// Rev 1.2 29 Jan 1996 16:54:40 RWolff
//Now uses VideoPortInt10() rather than no-BIOS code on PPC.
//
// Rev 1.1 11 Jan 1996 19:37:44 RWolff
//Now restricts "canned" mode tables by both maximum index and maximum
//pixel clock frequency, and EDID mode tables by maximum pixel clock
//frequency only, rather than both by maximum refresh rate.
//
// Rev 1.0 21 Nov 1995 11:04:38 RWolff
//Initial revision.
End of PolyTron RCS section *****************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <math.h>
#include "dderror.h"
#include "miniport.h"
#include "ntddvdeo.h"
#include "video.h" /* for VP_STATUS definition */
#include "stdtyp.h"
#include "amachcx.h"
#include "amach1.h"
#include "atimp.h"
#include "cvtvga.h"
#include "services.h"
#include "vdptocrt.h"
#include "cvtvdif.h"
#define INCLUDE_CVTDDC
#include "cvtddc.h"
/*
* Allow miniport to be swapped out when not needed.
*/
#if defined (ALLOC_PRAGMA)
#pragma alloc_text(PAGE_CX, IsDDCSupported)
#pragma alloc_text(PAGE_DDC, MergeEDIDTables)
#endif
/*****************************************************************************
*
* ULONG IsDDCSupported(void);
*
* DESCRIPTION:
* Reports the degree of DDC support for the available monitor/graphics
* card combination.
*
* RETURN VALUE:
* MERGE_EDID_DDC if DDC can return EDID data structures
* MERGE_VDIF_FILE if no monitor data available from DDC
*
* GLOBALS CHANGED:
* None
*
* CALLED BY:
* SetFixedModes()
*
* AUTHOR:
* Robert Wolff
*
* CHANGE HISTORY:
*
* TEST HISTORY:
*
***************************************************************************/
ULONG IsDDCSupported(void)
{
VIDEO_X86_BIOS_ARGUMENTS Registers; /* Used in VideoPortInt10() calls */
VP_STATUS RetVal; /* Status returned by VideoPortInt10() */
ULONG MergeSource; /* Source of mode tables to merge with "canned" tables */
VideoPortZeroMemory(&Registers, sizeof(VIDEO_X86_BIOS_ARGUMENTS));
Registers.Eax = BIOS_DDC_SUPPORT;
Registers.Ebx = 0;
if ((RetVal = VideoPortInt10(phwDeviceExtension, &Registers)) != NO_ERROR)
/*
* If we can't find out DDC status from the BIOS,
* assume DDC is not supported.
*/
{
VideoDebugPrint((DEBUG_ERROR, "Error querying DDC status, assume it's not supported\n"));
MergeSource = MERGE_VDIF_FILE;
}
else
{
#if 0
/*
* Workaround: Our BIOS call to obtain the DDC information uses
* the system timer (0x40/0x43) registers, which (according to
* Microsoft) the video BIOS is not supposed to touch. This
* causes some machines to hang during the DDC query. Until
* we can bring the DDC query into the miniport (using approved
* time delay routines), report that this card doesn't support
* DDC.
*/
if ((Registers.Eax & 0x00000002) && (Registers.Ebx & 0x00000002))
{
/*
* DDC2 supported by both BIOS and monitor. Check separately
* for DDC1 and DDC2 in case we decide to handle them
* differently in future.
*/
VideoDebugPrint((DEBUG_NORMAL, "DDC2 supported\n"));
MergeSource = MERGE_EDID_DDC;
}
else if ((Registers.Eax & 0x00000001) && (Registers.Ebx & 0x00000001))
{
/*
* DDC1 supported by both BIOS and monitor.
*/
VideoDebugPrint((DEBUG_NORMAL, "DDC1 supported\n"));
MergeSource = MERGE_EDID_DDC;
}
else
{
/*
* Either the BIOS or the monitor does not support DDC.
*/
VideoDebugPrint((DEBUG_NORMAL, "DDC not supported\n"));
MergeSource = MERGE_VDIF_FILE;
}
#else
MergeSource = MERGE_VDIF_FILE;
#endif
}
return MergeSource;
} /* IsDDCSupported() */
/*****************************************************************************
*
* VP_STATUS MergeEDIDTables(void);
*
* DESCRIPTION:
* Merges canned mode tables from BookValues[] with tables found in an
* EDID structure retrieved via DDC. Global pointer variable pCallbackArgs
* is used to point to a structure that passes data in both directions
* between this function and SetFixedModes(). For details on input and
* output data see definition of stVDIFCallbackData structure.
*
* RETURN VALUE:
* NO_ERROR if tables retrieved correctly
* ERROR_INVALID_PARAMETER if unable to retrieve data via DDC
*
* GLOBALS CHANGED:
* None
*
* CALLED BY:
* SetFixedModes()
*
* AUTHOR:
* Robert Wolff
*
* CHANGE HISTORY:
*
* TEST HISTORY:
*
***************************************************************************/
VP_STATUS MergeEDIDTables(void)
{
VIDEO_X86_BIOS_ARGUMENTS Registers; /* Used in VideoPortInt10() calls */
VP_STATUS RetVal; /* Status returned by VideoPortInt10() */
ULONG BufferSeg; /* Segment to use for buffer */
ULONG BufferSize = 128; /* EDID structure is 128 bytes long */
PUCHAR MappedBuffer; /* Pointer to buffer used for BIOS query */
static UCHAR FixedBuffer[128]; /* Buffer used to avoid repeated BIOS queries */
struct EdidDetailTiming *EdidPtr; /* Used in extracting information from buffer */
ULONG DetailOffset; /* Offset of detailed timing into EDID structure */
ULONG Scratch; /* Temporary variable */
struct stVDIFCallbackData *pArgs; /* Pointer to arguments structure */
struct st_mode_table BuildTbl; /* Mode table being built */
struct st_mode_table LiveTables[4]; /* Tables already extracted */
USHORT NumTablesFound = 0; /* Number of valid entries in LiveTables[] */
USHORT NumLowerTables; /* Number of tables with a lower refresh rate than BuildTbl */
USHORT HorTotal; /* Horizontal total */
USHORT VerTotal; /* Vertical total */
USHORT SyncStrt; /* Sync start */
USHORT HighBound; /* Highest frame rate to look for */
UCHAR SavedScreen[128]; /* Data saved from screen buffer used for DDC query */
pArgs = pCallbackArgs;
/*
* If we haven't already retrieved the EDID information into local
* storage, do it now.
*/
if (phwDeviceExtension->EdidChecksum == 0)
{
MappedBuffer = GetVgaBuffer(BufferSize, 0x500, &BufferSeg, SavedScreen);
/*
* We now have a buffer big enough to hold the EDID structure,
* so make the BIOS call to fill it in.
*/
VideoPortZeroMemory(&Registers, sizeof(VIDEO_X86_BIOS_ARGUMENTS));
Registers.Eax = BIOS_DDC_SUPPORT;
Registers.Ebx = 1;
Registers.Ecx = BufferSize;
Registers.Edx = BufferSeg;
Registers.Edi = 0;
if ((RetVal = VideoPortInt10(phwDeviceExtension, &Registers)) != NO_ERROR)
{
VideoDebugPrint((DEBUG_ERROR, "MergeEDIDTables() - failed BIOS_DDC_SUPPORT call\n"));
VideoPortFreeDeviceBase(phwDeviceExtension, MappedBuffer);
return RetVal;
}
/*
* Copy the EDID structure into local storage, then restore
* the contents of the buffer we used for the DDC query.
*/
for (Scratch = 0; Scratch < 128; Scratch++)
{
FixedBuffer[Scratch] = VideoPortReadRegisterUchar(&(MappedBuffer[Scratch]));
phwDeviceExtension->EdidChecksum += FixedBuffer[Scratch];
VideoPortWriteRegisterUchar(&(MappedBuffer[Scratch]), SavedScreen[Scratch]);
}
/*
* Check if we have a valid EDID header. If we don't, then
* we can't extract EDID information. Occasionally, a
* monitor hooked up to a switchbox will return corrupt
* EDID data.
*/
if ((FixedBuffer[0] != 0) ||
(FixedBuffer[1] != 0xFF) ||
(FixedBuffer[2] != 0xFF) ||
(FixedBuffer[3] != 0xFF) ||
(FixedBuffer[4] != 0xFF) ||
(FixedBuffer[5] != 0xFF) ||
(FixedBuffer[6] != 0xFF) ||
(FixedBuffer[7] != 0))
{
VideoDebugPrint((DEBUG_ERROR, "Invalid EDID header\n"));
return ERROR_INVALID_PARAMETER;
}
/*
* We now have the EDID structure in local storage, so we can free
* the buffer we collected it into. If the lower 8 bits of the
* checksum are nonzero, the structure is invalid.
*/
VideoPortFreeDeviceBase(phwDeviceExtension, MappedBuffer);
if ((phwDeviceExtension->EdidChecksum & 0x000000FF) != 0)
{
VideoDebugPrint((DEBUG_ERROR, "MergeEDIDTables() - invalid checksum 0x%X\n", phwDeviceExtension->EdidChecksum));
return ERROR_INVALID_PARAMETER;
}
} /* endif (phwDeviceExtension->EdidChecksum == 0) */
/*
* There are 4 detailed timing blocks in the EDID structure. Read
* each of them in turn.
*/
for (DetailOffset = 54; DetailOffset <= 108; DetailOffset += 18)
{
((PUCHAR)EdidPtr) = FixedBuffer + DetailOffset;
/*
* Initially check only the horizontal and vertical
* resolution. If they don't match the resolution we
* are working on, skip to the next detailed timing block.
*/
BuildTbl.m_x_size = ((EdidPtr->HorHighNybbles & 0xF0) << 4) | EdidPtr->HorActiveLowByte;
BuildTbl.m_y_size = ((EdidPtr->VerHighNybbles & 0xF0) << 4) | EdidPtr->VerActiveLowByte;
if ((BuildTbl.m_x_size != pArgs->HorRes) || (BuildTbl.m_y_size != pArgs->VerRes))
{
VideoDebugPrint((DEBUG_DETAIL, "EDID mode %dx%d doesn't match desired mode %dx%d, skipping\n",
BuildTbl.m_x_size, BuildTbl.m_y_size, pArgs->HorRes, pArgs->VerRes));
continue;
}
/*
* The table we are looking at matches the resolution we are
* working on. Fill in the remaining parameters.
*/
BuildTbl.m_h_disp = (UCHAR)(BuildTbl.m_x_size / 8 - 1);
BuildTbl.m_v_disp = (short) normal_to_skip2((long)(BuildTbl.m_y_size - 1));
BuildTbl.ClockFreq = (ULONG)(EdidPtr->PixClock) * 10000L;
/*
* If the pixel clock frequency for this mode is greater than
* the maximum pixel clock frequency the graphics card supports
* for the current resolution and pixel depth (this routine deals
* with only one resolution/pixel depth combination at a time,
* so our limiting pixel clock rate will always be for the current
* resolution/pixel depth combination), we can't use this mode.
*/
if (BuildTbl.ClockFreq > pArgs->MaxDotClock)
{
VideoDebugPrint((DEBUG_NORMAL, "Skipping table because pixel clock rate is too high\n"));
continue;
}
HorTotal = ((EdidPtr->HorHighNybbles & 0x0F) << 8) | EdidPtr->HorBlankLowByte;
HorTotal += BuildTbl.m_x_size;
BuildTbl.m_h_total = (UCHAR)(HorTotal / 8 - 1);
VerTotal = ((EdidPtr->VerHighNybbles & 0x0F) << 8) | EdidPtr->VerBlankLowByte;
VerTotal += BuildTbl.m_y_size;
BuildTbl.m_v_total = (short) normal_to_skip2((long)(VerTotal - 1));
SyncStrt = ((EdidPtr->SyncHighBits & 0xC0) << 2) | EdidPtr->HSyncOffsetLB;
SyncStrt += BuildTbl.m_x_size;
BuildTbl.m_h_sync_strt = (UCHAR)(SyncStrt / 8 - 1);
SyncStrt = ((EdidPtr->SyncHighBits & 0x0C) << 2) | ((EdidPtr->VSyncOffWidLN & 0xF0) >> 4);
SyncStrt += BuildTbl.m_y_size;
BuildTbl.m_v_sync_strt = (short) normal_to_skip2((long)(SyncStrt - 1));
/*
* We only support digital separate sync monitors.
*/
if ((EdidPtr->Flags & EDID_FLAGS_SYNC_TYPE_MASK) != EDID_FLAGS_SYNC_DIGITAL_SEP)
{
VideoDebugPrint((DEBUG_NORMAL, "Skipping table due to wrong sync type\n"));
continue;
}
Scratch = ((EdidPtr->SyncHighBits & 0x30) << 4) | EdidPtr->HSyncWidthLB;
if (!(EdidPtr->Flags & EDID_FLAGS_H_SYNC_POS))
Scratch |= 0x20;
BuildTbl.m_h_sync_wid = (UCHAR)Scratch;
Scratch = ((EdidPtr->SyncHighBits & 0x03) << 4) | (EdidPtr->VSyncOffWidLN & 0x0F);
if (!(EdidPtr->Flags & EDID_FLAGS_V_SYNC_POS))
Scratch |= 0x20;
BuildTbl.m_v_sync_wid = (UCHAR)Scratch;
BuildTbl.m_status_flags = 0;
BuildTbl.m_vfifo_16 = 8;
BuildTbl.m_vfifo_24 = 8;
BuildTbl.m_clock_select = 0x800;
BuildTbl.m_h_overscan = 0;
BuildTbl.m_v_overscan = 0;
BuildTbl.m_overscan_8b = 0;
BuildTbl.m_overscan_gr = 0;
if (EdidPtr->Flags & EDID_FLAGS_INTERLACE)
BuildTbl.m_disp_cntl = 0x33;
else
BuildTbl.m_disp_cntl = 0x23;
/*
* The EDID detailed timing tables don't include the refresh
* rate. In our VDIF to monitor timings routines, we obtain
* the horizontal and vertical totals from the equations
*
* Htot = PixClk/HorFreq
* Vtot = HorFreq/FrameRate
*
* These equations can be rearranged to
*
* HorFreq = PixClk/Htot
* FrameRate = HorFreq/Vtot = (PixClk/Htot)/Vtot = PixClk/(Htot*Vtot)
*
* The multiplication, addition, and division below is to
* round up to the nearest whole number, since we don't
* have access to floating point.
*/
Scratch = (BuildTbl.ClockFreq * 10)/(HorTotal*VerTotal);
Scratch += 5;
Scratch /= 10;
BuildTbl.Refresh = (short)Scratch;
VideoDebugPrint((DEBUG_DETAIL, "Refresh rate = %dHz\n", BuildTbl.Refresh));
/*
* Set the pixel depth and pitch, and adjust the clock frequency
* if the DAC needs multiple clocks per pixel.
*/
SetOtherModeParameters(pArgs->PixelDepth, pArgs->Pitch,
pArgs->Multiplier, &BuildTbl);
/*
* We now have a mode table for the resolution we are
* looking at. If this is the first table we have found
* at this resolution, we can simply fill in the first
* entry in LiveTables[]. If not, we must put the table
* into the list in order by refresh rate.
*/
if (NumTablesFound == 0)
{
VideoDebugPrint((DEBUG_DETAIL, "First DDC table for this resolution\n"));
VideoPortMoveMemory(&(LiveTables[0]), &BuildTbl, sizeof(struct st_mode_table));
NumTablesFound = 1;
}
else
{
/*
* Run through the list of tables we have already found.
* Skip over the tables which have refresh rates lower than
* the new table, and shift tables with higher refresh
* rates up one position to make room for the new table.
* There is no need to check for available spaces in the
* LiveTables[] array, since this array has 4 entries and
* the EDID structure can hold a maximum of 4 detailed
* timings.
*/
for (NumLowerTables = 0; NumLowerTables < NumTablesFound; NumLowerTables++)
{
if (LiveTables[NumLowerTables].Refresh < BuildTbl.Refresh)
{
VideoDebugPrint((DEBUG_DETAIL, "Skipping table %d, since %dHz is less than %dHz\n",
NumLowerTables, LiveTables[NumLowerTables].Refresh, BuildTbl.Refresh));
continue;
}
/*
* NumLowerTables now holds the number of tables in LiveTables[] which
* have refresh rates lower than that in BuildTbl. We must now move
* the tables in LiveTables[] with refresh rates higher than that in
* BuildTbl up one space to make room for BuildTbl to be inserted.
* After moving the tables, break out of the outer loop.
*/
for (Scratch = NumTablesFound; Scratch >= NumLowerTables; Scratch--)
{
VideoDebugPrint((DEBUG_DETAIL, "Moving table %d, since %dHz is more than %dHz\n",
Scratch, LiveTables[Scratch].Refresh, BuildTbl.Refresh));
VideoPortMoveMemory(&(LiveTables[Scratch+1]), &(LiveTables[Scratch]), sizeof(struct st_mode_table));
}
break;
}
/*
* When we get here, one of two conditions is satisfied:
*
* 1. All the existing tables in LiveTables[] have a refresh
* rate less than that in BuildTbl, so the outer loop will
* have exited with NumLowerTables equal to NumTablesFound.
*
* 2. There are some tables in LiveTables[] which have a refresh
* rate greater than that in BuildTbl. The inner loop will
* have exited after moving these tables up one space, then
* we will have broken out of the outer loop. NumLowerTables
* is equal to the number of existing tables which have a
* refresh rate less than that in BuildTbl.
*
* In both cases, LiveTables[NumLowerTables] is a free slot
* at the location where BuildTbl should be copied.
*/
VideoDebugPrint((DEBUG_DETAIL, "Copying new table to entry %d\n", NumLowerTables));
VideoPortMoveMemory(&(LiveTables[NumLowerTables]), &BuildTbl, sizeof(struct st_mode_table));
NumTablesFound++;
} /* end if (NumTablesFound != 0) */
} /* end for (look at next detailed timing block) */
/*
* We now have all the mode tables from the EDID structure which
* match the desired resolution stored in LiveTables[] in order
* of increasing refresh rate, with the number of such tables
* in NumTablesFound. Now we must merge the results with the
* "canned" mode tables.
*/
HighBound = BookValues[pArgs->EndIndex].Refresh;
/*
* Use NumLowerTables to go through the list of tables from
* the EDID structure.
*
* Since there will never be a legitimate mode table with a
* pixel clock frequency of zero hertz, we can use this value
* as a flag to show that we don't want to use the tables from
* the EDID structure. Initially, we only want to lock out the
* use of these tables if none exist, but we will later lock
* them out if we have already used all of them.
*/
NumLowerTables = 0;
if (NumTablesFound == 0)
LiveTables[0].ClockFreq = 0;
while (pArgs->FreeTables > 0)
{
/*
* If the EDID table exists, and either it has a refresh rate
* less than or equal to that of the next "canned" table or
* we have run out of acceptable "canned" tables, use the EDID
* table. We know that any EDID table will have an acceptable
* pixel clock frequency because we have already discarded any
* that are out of range.
*/
if ((LiveTables[NumLowerTables].ClockFreq != 0) &&
((LiveTables[NumLowerTables].Refresh <= BookValues[pArgs->Index].Refresh) ||
(pArgs->Index > pArgs->EndIndex) ||
(BookValues[pArgs->Index].ClockFreq > pArgs->MaxDotClock)))
{
VideoDebugPrint((DEBUG_DETAIL, "Copying %dHz table from EDID\n", LiveTables[NumLowerTables].Refresh));
VideoPortMoveMemory((*pArgs->ppFreeTables), &(LiveTables[NumLowerTables]), sizeof(struct st_mode_table));
NumLowerTables++;
}
/*
* The above check will have failed if the EDID table did not exist,
* or if it did but an acceptable "canned" table with a lower
* refresh rate also exists. Check to see if we have an acceptable
* "canned" table, and use it if we do.
*/
else if ((pArgs->Index <= pArgs->EndIndex) &&
(BookValues[pArgs->Index].ClockFreq <= pArgs->MaxDotClock))
{
VideoDebugPrint((DEBUG_DETAIL, "Copying %dHz \"canned\" table\n", BookValues[pArgs->Index].Refresh));
BookVgaTable(pArgs->Index, *pArgs->ppFreeTables);
SetOtherModeParameters(pArgs->PixelDepth, pArgs->Pitch,
pArgs->Multiplier, *pArgs->ppFreeTables);
pArgs->Index++;
}
/*
* The only way we will fail both of the above checks is if there
* are no acceptable mode tables remaining, either from the EDID
* structure or from our list of "canned" tables. If this is the
* case, we don't need to look for more mode tables to add to
* our list.
*/
else
{
break;
}
/*
* Update the lower bound, since we don't want to consider
* tables with refresh rates lower than or equal to the one
* in the table we just added to the list. After we have
* done this, skip ahead in both the "canned" and EDID tables
* to get past those which are below the new lower bound.
*
* Don't skip a mode table from the EDID structure with a pixel
* clock frequency of zero, since this is a flag to show that we
* have already used all of the suitable mode tables from the
* EDID structure, rather than a legitimate mode table.
*/
pArgs->LowBound = (*pArgs->ppFreeTables)->Refresh + 1;
while ((pArgs->Index <= pArgs->EndIndex) &&
(BookValues[pArgs->Index].Refresh < pArgs->LowBound))
{
VideoDebugPrint((DEBUG_DETAIL, "Skipping %dHz \"canned\" table\n", BookValues[pArgs->Index].Refresh));
pArgs->Index++;
}
while ((NumLowerTables < NumTablesFound) &&
(LiveTables[NumLowerTables].ClockFreq != 0) &&
(LiveTables[NumLowerTables].Refresh < pArgs->LowBound))
{
VideoDebugPrint((DEBUG_DETAIL, "Skipping %dHz table from EDID\n", LiveTables[NumLowerTables].Refresh));
NumLowerTables++;
}
/*
* If we have run out of EDID tables, mark the EDID tables
* with our flag to show that they should be ignored (no
* legitimate mode will have a pixel clock rate of zero
* hertz).
*
* We must do this in the first entry of the structure then
* reset the "next EDID table to use" index to point to the
* first entry, rather than modifying whatever happens to be
* the next entry, to avoid trampling data outside our array
* in the (unlikely) event that all of the possible detailed
* timings in the EDID structure were valid mode tables with
* in-range pixel clock frequencies for the resolution we are
* looking at.
*
* There is no need to set a flag if we run out of "canned"
* tables because we identify this condition by the index
* being higher than the highest index we want to look for,
* which is an input parameter.
*/
if (NumLowerTables == NumTablesFound)
{
VideoDebugPrint((DEBUG_DETAIL, "Ran out of EDID tables\n"));
NumLowerTables = 0;
LiveTables[0].ClockFreq = 0;
}
/*
* Adjust the free tables pointer and count to reflect the
* table we have just added.
*/
(*pArgs->ppFreeTables)++;
pArgs->NumModes++;
pArgs->FreeTables--;
} /* end while (more tables and not yet reached high bound) */
return NO_ERROR;
} /* MergeEDIDTables() */