/*++

Copyright (c) Microsoft Corporation

Module Name:

    modeset.c

Abstract:

    Programs the CRTC to stream the frame buffer to the tv encoder.

--*/

#include "avp.h"
#include "xpcicfg.h"

#if DBG
ULONG AvpDump;
#endif

DECLSPEC_STICKY ULONG AvpCurrentMode;
ULONG AvpMacrovisionMode;
ULONG AvpCGMS;
ULONG AvpCapabilities;

//
// Address for the frame saved across reboots and mode changes
//
DECLSPEC_STICKY PVOID AvpSavedDataAddress;

// 
// Supported frame buffer modes.
//

#define D3DFMT_LIN_A1R5G5B5   0x00000010
#define D3DFMT_LIN_X1R5G5B5   0x0000001C
#define D3DFMT_LIN_R5G6B5     0x00000011
#define D3DFMT_LIN_A8R8G8B8   0x00000012
#define D3DFMT_LIN_X8R8G8B8   0x0000001E

ULONG
AvpCalcWSSCRC(
    IN  ULONG Value
    )
/*++

Routine Description:

    Sets various encoder options.

Arguments:

    Value - the 20 bit number to calculate CRC for

Return Value:

    The value plus the proper CRC

--*/
{
    ULONG CRC;    
    ULONG i, lsb, msb;

    CRC = 0x3F;    

    // This can ultimately be done more efficiently, but why?

    for (i = 0; i < 14; i++)
    {
        lsb = (CRC & 0x01) ^ ((Value >> i) & 0x01);
        msb = ((CRC >> 5) & 0x01) ^ lsb;

        CRC  = (CRC >> 1) & 0x0F;
        CRC |= msb << 4;
        CRC |= lsb << 5;
    }

    return Value | (CRC << 14);
}

VOID
AvpSetWSSBits(
    IN  PVOID RegisterBase
    )
/*++

Routine Description:

    Uses the current mode and macrovision state to correctly
    set the WSS and CGMS bits.

Arguments:

    RegisterBase - base address of the gpu hw registers

Return Value:

    None

--*/
{
    ULONG OutputMode;
    ULONG WSS;
    
    OutputMode = AvpCurrentMode & AV_MODE_OUT_MASK;

    WSS = 0;

    if (OutputMode == AV_MODE_OUT_480SDTV)
    {
        if (AvpCurrentMode & AV_MODE_FLAGS_WSS)
        {
            WSS |= 0x00000001;
        }

        WSS |= AvpCGMS << 6;
        WSS |= (AvpMacrovisionMode & 0x01) << 9;
        WSS |= (AvpMacrovisionMode & 0x02) << 7;

        WSS = AvpCalcWSSCRC(WSS);
    }

    // 525 should be 576
    else if (OutputMode == AV_MODE_OUT_525SDTV)
    {
        if (AvpCurrentMode & AV_MODE_FLAGS_WSS)
        {
            WSS |= 0x00000007;
        }
        else
        {
            WSS |= 0x00000008;
        }

        WSS |= AvpCGMS << 12;
    }

    // Do nothing.
    else
    {
        return;
    }

#ifdef FOCUS

    // Set the registers.
    SMB_WR(RegisterBase, 0x85, (UCHAR)((WSS & 0x0F)));
    SMB_WR(RegisterBase, 0x84, (UCHAR)((WSS >> 4) & 0xFF));
    SMB_WR(RegisterBase, 0x83, (UCHAR)((WSS >> 12) & 0xFF));

    SMB_WR(RegisterBase, 0x88, (UCHAR)((WSS & 0x0F)));
    SMB_WR(RegisterBase, 0x87, (UCHAR)((WSS >> 4) & 0xFF));
    SMB_WR(RegisterBase, 0x86, (UCHAR)((WSS >> 12) & 0xFF));

#else !FOCUS

    // Set the registers.
    SMB_WR(RegisterBase, 0x60, (UCHAR)(0xC0 | (WSS & 0x0F)));
    SMB_WR(RegisterBase, 0x62, (UCHAR)((WSS >> 4) & 0xFF));
    SMB_WR(RegisterBase, 0x64, (UCHAR)((WSS >> 12) & 0xFF));

#endif !FOCUS
}

VOID
AvpEnableSCART(
    IN  PVOID RegisterBase,
    IN  ULONG iTV
    )
/*++

Routine Description:

    Do all of the work to enable SCART, assuming that 
    it has already been set up in the mode-set code.

Arguments:

    RegisterBase - base address of the gpu hw registers

    iTV - tv encoder table index.

Return Value:

    None

--*/
{
#ifdef FOCUS

    const UCHAR *pByte, *pByteMax;
    ULONG i;
    UCHAR Data;    
    
    pByte    = AvpFocusRGBEnable[2];
    pByteMax = pByte + sizeof(AvpFocusRGBEnable[0]);

    for (i = 0; pByte < pByteMax; pByte++, i++)
    {
        Data = SMB_RD(RegisterBase, AvpFocusRGBEnable[0][i]);

        Data &= ~AvpFocusRGBEnable[1][i];

        SMB_WR(RegisterBase, AvpFocusRGBEnable[0][i], Data | *pByte);
    }

#else

    const UCHAR *pByte, *pByteMax;
    ULONG i;

    pByte    = AvpSCARTEnable[iTV];
    pByteMax = pByte + sizeof(AvpSCARTEnable[0]);

    for (i = 0; pByte < pByteMax; pByte++, i++)
    {
        SMB_WR(RegisterBase, AvpSCARTEnable[0][i], *pByte);
    }

#endif
}

//****************************************************************************
//
// Mode set code.
//
//****************************************************************************

ULONG
AvSetDisplayMode(
    IN  PVOID RegisterBase,
    IN  ULONG Step,
    IN  ULONG Mode,                   // mode from the AV_MODE list (avmode.h)
    IN  ULONG Format,                 // D3DFORMAT
    IN  ULONG Pitch,
    IN  ULONG FrameBuffer
    )
/*++

Routine Description:

    Initializes the CRTC to stream the framebuffer to the 
    tv encoder.

Arguments:

    RegisterBase - base address of the gpu hw registers

    Step - what step we're currently doing in the mode-set process

    Mode - the AV_PIPELINE mode to initialize

    Format - format of the frame buffer

    Pitch - the pitch of the frame buffer

    FrameBuffer - physical address of the frame buffer

Return Value:

    None

--*/
{
    ULONG GeneralControl;
    ULONG Value;
    ULONG i;
    ULONG LgsValue;

    UCHAR Data;
    UCHAR Register;

    const UCHAR *pByte;
    const UCHAR *pByteMax;

    const ULONG *pLong;
    const ULONG *pLongMax;

    UCHAR CR28Depth;
    UCHAR CR13Value;

    ULONG OutputMode;
    ULONG iRegister;
    ULONG iCRTC;
    ULONG iTV;

    UCHAR DACs;

    USHORT FocusValue;
    NTSTATUS Status;

    // 
    // Decode the mode.
    //

    // If the mode is zero then we just set this to NTSC and turn
    // the DACs off.
    //
    if (Mode == AV_MODE_OFF)
    {
        Mode = AV_MODE_640x480_TO_NTSC_M_YC
                | AV_MODE_FLAGS_DACA_DISABLE
                | AV_MODE_FLAGS_DACB_DISABLE
                | AV_MODE_FLAGS_DACC_DISABLE
                | AV_MODE_FLAGS_DACD_DISABLE;
    }

    OutputMode = Mode & AV_MODE_OUT_MASK;

    iRegister = (Mode & 0x00FF0000) >> 16;
    iCRTC     = (Mode & 0x0000FF00) >> 8;
    iTV       = (Mode & 0x0000007F);

    DACs      = (UCHAR)((Mode & 0x0F000000) >> 24);

    //
    // Get the format-specific parameters.
    //

    switch(Format)
    {
    case D3DFMT_LIN_A1R5G5B5:
    case D3DFMT_LIN_X1R5G5B5:
        GeneralControl = 0x00100030;
        CR28Depth = 2;
        break;

    case D3DFMT_LIN_R5G6B5:
        GeneralControl = 0x00101030;
        CR28Depth = 2;
        break;

    case D3DFMT_LIN_A8R8G8B8:
    case D3DFMT_LIN_X8R8G8B8:
        GeneralControl = 0x00100030;
        CR28Depth = 3;
        break;
    }

    // Pitch is always measured in bytes.
    Pitch /= 8;

    //
    // Handle format-only changes, very little has to be munged
    // for this case.
    //

    if (AvpCurrentMode == Mode)
    {
        ASSERT(Step == 0);

        // Reset the general control register.
        REG_WR32(RegisterBase, NV_PRAMDAC_GENERAL_CONTROL, GeneralControl);

        // Unlock the CRTC
        CRTC_WR(RegisterBase, NV_CIO_SR_LOCK_INDEX, NV_CIO_SR_UNLOCK_RW_VALUE);

        // Set the pitch.
        CRTC_WR(RegisterBase, 0x13, (UCHAR)(Pitch & 0xFF));
        CRTC_WR(RegisterBase, 0x19, (UCHAR)((Pitch & 0x700) >> 3));

        // Slave the CRTC to the encoder with the proper color depth.
        CRTC_WR(RegisterBase, 0x28, 0x80 | CR28Depth);

        // Set the frame buffer pointer.
        REG_WR32(RegisterBase, NV_PCRTC_START, FrameBuffer);

        // Make sure the flicker filter and luma filter gets reset to their
        // default values.
        //
        AvSendTVEncoderOption(RegisterBase,
                              AV_OPTION_FLICKER_FILTER,
                              5,
                              NULL);

        AvSendTVEncoderOption(RegisterBase,
                              AV_OPTION_ENABLE_LUMA_FILTER,
                              FALSE,
                              NULL);

        AvpCurrentMode = Mode;
        return 0;
    }

    // 
    // Do a full mode change
    //

    else
    {
        ASSERT(Step == 0);

        // Use the hardware to disable the DACs.
        _outp(XPCICFG_LPCBRIDGE_IO_REGISTER_BASE_0 + 0xD3, 0x5);

        //
        // Wait for any previous lightgun changes have been processed by the
        // hardware before making any other changes.
        //

        do
        {
            LgsValue = REG_RD32(RegisterBase, NV_PRAMDAC_LGS_TRANSFER);
        }
        while (!(LgsValue & 0x01) != !(LgsValue & 0x10));

        //
        // Unlock the CRTC and the tv encoder
        //

        CRTC_WR(RegisterBase, 0x1F, 0x57);
        CRTC_WR(RegisterBase, 0x21, 0xFF);

        // Unslave the CRTC from the tv encoder clock for now.
        CRTC_WR(RegisterBase, 0x28, 0x00);

        // Disable pclk stalling.
        REG_WR32(RegisterBase, NV_PRAMDAC_FP_DEBUG_0, 0x21121111);

#ifndef FOCUS

        // Do a reset and turn off dacs.
        SMB_WR(RegisterBase, 0xBA, 0x80);
        SMB_WR(RegisterBase, 0xBA, 0x3F);

        // Enable tv encoder register munging.
        SMB_WR(RegisterBase, 0x6C, 0x46);

#endif

        // Wait 1 microsecond
        KeStallExecutionProcessor(1);

#ifndef FOCUS

        // Reset the timing.
        SMB_WR(RegisterBase, 0x6C, 0xC6);

#endif

        // 
        // Program the TV encoder
        //

        // !!! PERF we can write runs of registers more efficiently.    
        if (OutputMode == AV_MODE_OUT_HDTV)
        {

            pByte    = AvpHDTVRegisters[iTV];
            pByteMax = pByte + sizeof(AvpHDTVRegisters[0]);

            for (i = 0; pByte < pByteMax; pByte++, i++)
            {
                SMB_WR(RegisterBase, AvpHDTVRegisters[0][i], *pByte);
            }
        }
        else
        {

#ifndef FOCUS

            pByte    = AvpCommonTVRegisters[1];
            pByteMax = pByte + sizeof(AvpCommonTVRegisters[0]);

            for (i = 0; pByte < pByteMax; pByte++, i++)
            {
                SMB_WR(RegisterBase, AvpCommonTVRegisters[0][i], *pByte);
            }

#endif !FOCUS

            pByte    = AvpTVRegisters[iTV];
            pByteMax = pByte + sizeof(AvpTVRegisters[0]);

            for (i = 0; pByte < pByteMax; pByte++, i++)
            {
                SMB_WR(RegisterBase, AvpTVRegisters[0][i], *pByte);
            }

            // Hack for NTSC-M and NTSC-J YPrPb outputs.
            if (XboxGameRegion == XC_GAME_REGION_JAPAN && (iTV == 0xD || iTV == 0xE || iTV == 0x12))
            {
#ifdef FOCUS

                SMB_WRW(RegisterBase, 0x4E, 0x013C);

#else !FOCUS

                SMB_WR(RegisterBase, 0xA2, 0x08);
                SMB_WR(RegisterBase, 0xA4, 0xF0);
                SMB_WR(RegisterBase, 0xAC, 0x9A);

#endif !FOCUS

            }
        }

        // Enable SCART
        if (Mode & AV_MODE_FLAGS_SCART)
        {
            // Twiddle the other registers.
            AvpEnableSCART(RegisterBase, iTV);
        }

        // Always set the SCART aspect ratio signal because it may be
        // handy to have for future AV packs.
        //
        _outp(XPCICFG_LPCBRIDGE_IO_REGISTER_BASE_0 + 0xD6, 
              (Mode & AV_MODE_FLAGS_WSS) ? 0x5 : 0x4);

        // We are now allowed to always have RGB output on...so just
        // set this pin and leave it.
        //
        _outp(XPCICFG_LPCBRIDGE_IO_REGISTER_BASE_0 + 0xD8, 0x4);

        //
        // Set up the RAMDAC to stream from the frame buffer.
        //

        // Set up the clocks to always use the tv-encoder as the 
        // source of the pclk and to divice pclk by 2 for the 
        // vclk.
        //
        REG_OR32(RegisterBase, NV_PRAMDAC_PLL_COEFF_SELECT, 0x10020000);

        // Unlock the VGA registers.
        REG_WR08(RegisterBase, NV_PRMVIO_VSE2, 0x01);

        // Set random output bits.
        REG_WR08(RegisterBase, NV_PRMVIO_MISC__WRITE, 0xE3);

        // Set up the general control register.  This varies depending on
        // the frame buffer format.
        //
        // The interesting bits:
        //
        //     5: 4 - 3 = PIXMIX bit is ignored
        //    12:12 - Selects between 5-5-5 (off) and 5-6-5 (on)
        //    16:16 - BLK_PEDSTL ON (??)
        //
        REG_WR32(RegisterBase, NV_PRAMDAC_GENERAL_CONTROL, GeneralControl);

        pLong    = AvpRegisters[iRegister];    
        pLongMax = pLong + sizeof(AvpRegisters[0]) / sizeof(ULONG);
    
        // Set the rest.
        for (i = 0; pLong < pLongMax; pLong++, i++)
        {
            REG_WR32(RegisterBase, AvpRegisters[0][i], *pLong);
        }

        // SCART shovels its output in RGB mode.
        if (Mode & AV_MODE_FLAGS_SCART)
        {
            REG_WR32(RegisterBase, 0x680630, 0);  
            REG_WR32(RegisterBase, 0x6808C4, 0);  
            REG_WR32(RegisterBase, 0x68084C, 0);  
        }

        //
        // Set the initial VGA registers.
        //

        // Write the SRX registers.
        pByte    = AvpSRXRegisters;
        pByteMax = pByte + sizeof(AvpSRXRegisters);

        for (i = 0; pByte < pByteMax; pByte++, i++)
        {
            SRX_WR(RegisterBase, (UCHAR)i, *pByte);
        }

        // Write the GRX registers
        pByte    = AvpGRXRegisters;
        pByteMax = pByte + sizeof(AvpGRXRegisters);

        for (i = 0; pByte < pByteMax; pByte++, i++)
        {
            GRX_WR(RegisterBase, (UCHAR)i, *pByte);
        }

        // Write the ARX registers.  This will cause the screen to blank
        // until the registers have all been written.
        //
        REG_RD08(RegisterBase, NV_PRMCIO_INP0__COLOR);

        pByte    = AvpARXRegisters;
        pByteMax = pByte + sizeof(AvpARXRegisters);

        for (i = 0; pByte < pByteMax; pByte++, i++)
        {
            ARX_WR(RegisterBase, (UCHAR)i, *pByte);
        }

        // Unblank the screen.
        REG_WR08(RegisterBase, NV_PRMCIO_ARX, 0x20); 

        //
        // Program the CRTC
        //

        // Unlock the rest of the CRTC.
        CRTC_WR(RegisterBase, 0x11, 0x00);

        pByte    = AvpCRTCRegisters[iCRTC];
        pByteMax = pByte + sizeof(AvpCRTCRegisters[0]);

        for (i = 0; pByte < pByteMax; pByte++, i++)
        {
            Register = AvpCRTCRegisters[0][i];
            Data = *pByte;

            if (Register == 0x13)
            {
                Data = (UCHAR)(Pitch & 0xFF);
            }
            else if (Register == 0x19)
            {
                Data |= (UCHAR)((Pitch & 0x700) >> 3);
            }
            else if (Register == 0x25)
            {
                Data |= (UCHAR)((Pitch & 0x800) >> 6);
            }

            CRTC_WR(RegisterBase, AvpCRTCRegisters[0][i], Data);
        }

        //
        // Commit the light gun changes.
        //

        REG_WR32(RegisterBase, NV_PRAMDAC_LGS_TRANSFER, !(LgsValue & 0x01) & 0x01);

        //
        // Sync up the CRTC with the tv encoder.
        //

        // Slave the CRTC to the encoder.
        CRTC_WR(RegisterBase, 0x28, 0x80 | CR28Depth);

        // Wait a few lines until everything gets in sync.
        for (i = 0; i < 3; i++)
        {
            while ((REG_RD32(RegisterBase, NV_PRMCIO_INP0__COLOR) & 0x08) == 0x08)
                ;

            while ((REG_RD32(RegisterBase, NV_PRMCIO_INP0__COLOR) & 0x08) == 0x00)
                ;
        }

        // Set FP_DEBUG_0 which will also enable pclk stalling.
        REG_WR32(RegisterBase, NV_PRAMDAC_FP_DEBUG_0, AvpFpDebug0[iRegister]);

        //
        // Afterglow
        //

        // Wait 1 microsecond
        KeStallExecutionProcessor(1);

#ifndef FOCUS

        // Reset the timing.
        Data = SMB_RD(RegisterBase, 0x6C);   
        SMB_WR(RegisterBase, 0x6C, Data | 0x80);

        // Enable the DACs.
        SMB_WR(RegisterBase, 0xBA, 0x20 | DACs);

#endif

        // Set the frame buffer pointer.
        REG_WR32(RegisterBase, NV_PCRTC_START, FrameBuffer);

        AvpCurrentMode = Mode;

        AvpSetWSSBits(RegisterBase);
                  
        return 0;
    }
}

VOID 
AvpSetMacrovisionMode(
    IN PVOID RegisterBase,
    IN ULONG MacrovisionMode
    )
/*++

Routine Description:

    Sets up macrovision.

Arguments:

    RegisterBase - base address of the gpu hw registers

    MacrovisionMode -

Return Value:

    None

--*/
{
    const UCHAR *Registers = NULL;
    UCHAR Tv;

    ULONG i,c ;
    ULONG OutputMode;
    UCHAR Data;
         
#ifdef FOCUS

    UCHAR CCEnable;

#endif

    Tv = (UCHAR)(AvpCurrentMode & 0xFF);
    OutputMode = AvpCurrentMode & AV_MODE_OUT_MASK;

    // 525 should be 576
    if (OutputMode != AV_MODE_OUT_480SDTV && OutputMode != AV_MODE_OUT_525SDTV)
    {
        // No macrovision.
        return;
    }

    if (OutputMode == AV_MODE_OUT_480SDTV)
    {
        switch(MacrovisionMode)
        {
        case AV_MV_OFF:
            Registers = AvpMacrovision[AVP_MV_NTSC_MODE_0];
            break;

        case AV_MV_AGC_ONLY:
            Registers = AvpMacrovision[AVP_MV_NTSC_MODE_1];
            break;

        case AV_MV_TWO_STRIPES_PLUS_AGC:
            Registers = AvpMacrovision[AVP_MV_NTSC_MODE_2];
            break;

        case AV_MV_FOUR_STRIPES_PLUS_AGC:
        default:
            Registers = AvpMacrovision[AVP_MV_NTSC_MODE_3];
            break;
        }

        // Sploich the registers.
        c = sizeof(AvpMacrovision[0]);

#ifdef FOCUS

        CCEnable = (SMB_RDW(RegisterBase, 0xB2) >> 8 ) & 0xC0;

#endif

        for (i = 0; i < c; i++)
        {

#ifdef FOCUS

            if (AvpMacrovision[0][i] == 0xB3)
            {
                SMB_WR(RegisterBase, AvpMacrovision[0][i], Registers[i] | CCEnable);
            }
            else

#endif

            {
                SMB_WR(RegisterBase, AvpMacrovision[0][i], Registers[i]);
            }
        }
    }

    else
    {
        switch(MacrovisionMode)
        {
        case AV_MV_OFF:
            Registers = AvpMacrovisionPAL[AVP_MV_PAL_MODE_0];
            break;

        case AV_MV_AGC_ONLY:
        default:
            Registers = AvpMacrovisionPAL[AVP_MV_PAL_MODE_1];
            break;
        }

        // Sploich the registers.
        c = sizeof(AvpMacrovisionPAL[0]);

        for (i = 0; i < c; i++)
        {
            SMB_WR(RegisterBase, AvpMacrovisionPAL[0][i], Registers[i]);
        }
    }
}

VOID 
AvpCommitMacrovisionMode(
    IN PVOID RegisterBase,
    IN ULONG MacrovisionMode
    )
/*++

Routine Description:

    Commits the macrovision changes.

Arguments:

    RegisterBase - base address of the gpu hw registers

    MacrovisionMode -

Return Value:

    None

--*/
{
    const UCHAR *Registers = NULL;
    UCHAR Tv;

    ULONG i,c ;
    ULONG OutputMode;
    USHORT Data;
        
    Tv = (UCHAR)(AvpCurrentMode & 0xFF);
    OutputMode = AvpCurrentMode & AV_MODE_OUT_MASK;

#ifdef FOCUS

    if (OutputMode == AV_MODE_OUT_480SDTV 
        || OutputMode == AV_MODE_OUT_525SDTV
        || (AvpCurrentMode & 0xFF0000FF) == 0x88000001)
    {
        Data = SMB_RDW(RegisterBase, 0x0E);

        if (MacrovisionMode == AV_MV_OFF)
        {
            SMB_WRW(RegisterBase, 0x0E, Data & ~0x8000);
        }
        else
        {
            SMB_WRW(RegisterBase, 0x0E, Data | 0x8000);
        }
    }

#else !FOCUS

    if (OutputMode == AV_MODE_OUT_480SDTV)
    {

        switch(MacrovisionMode)
        {
        case AV_MV_OFF:
            Registers = AvpMacrovisionEnable[AVP_MV_NTSC_MODE_0];
            break;

        case AV_MV_AGC_ONLY:
            Registers = AvpMacrovisionEnable[AVP_MV_NTSC_MODE_1];
            break;

        case AV_MV_TWO_STRIPES_PLUS_AGC:
            Registers = AvpMacrovisionEnable[AVP_MV_NTSC_MODE_2];
            break;

        case AV_MV_FOUR_STRIPES_PLUS_AGC:
        default:
            Registers = AvpMacrovisionEnable[AVP_MV_NTSC_MODE_3];
            break;
        }

        // Sploich the registers.
        c = sizeof(AvpMacrovisionEnable[0]);

        for (i = 0; i < c; i++)
        {
            SMB_WR(RegisterBase, AvpMacrovisionEnable[0][i], Registers[i]);
        }
    }

#endif !FOCUS

}

ULONG
AvSMCVideoModeToAVPack(
    ULONG VideoMode
    )
/*++

Routine Description:

    Converts an SMC video mode to an AV pack constant.

Arguments:

    VideoMode - Video mode as returned by SMC_COMMAND_VIDEO_MODE.

Return Value:

    Returns the AV pack constant.

--*/
{
    ULONG Result;

    switch(VideoMode)
    {
    case 0x0:

        // DVT-1 boards will always select this mode.  That should
        // be ok because only the composite output will be enabled
        // on an NTSC-M system.
        //
        Result = AV_PACK_SCART;
        break;

    case 0x1:
        Result = AV_PACK_HDTV;
        break;

    case 0x2:
        Result = AV_PACK_VGA;
        break;

    case 0x3:
        Result = AV_PACK_RFU;
        break;

    case 0x4:
        Result = AV_PACK_SVIDEO;
        break;

    case 0x6:
        Result = AV_PACK_STANDARD;
        break;

    default:
    case 0x7:
        Result = AV_PACK_NONE;
        break;
    }

    return Result;
}

ULONG
AvpQueryAvCapabilities(
    )
/*++

Routine Description:

    Reads the AV settings from the AV pack and EEPROM.

Arguments:

    None

Return Value:

    The AV caps flags.

--*/
{
    ULONG VideoMode;
    ULONG Type;
    ULONG ResultLength;

    NTSTATUS status;

    ULONG Result;

    Result = AvSMCVideoModeToAVPack(HalBootSMCVideoMode);

    // Get the region from the EEPROM.
    status = ExQueryNonVolatileSetting(XC_FACTORY_AV_REGION,
                                       &Type,
                                       &VideoMode,
                                       sizeof(VideoMode),
                                       &ResultLength);

    // Didn't work?  Default to NTSC.
    if (status != STATUS_SUCCESS || ResultLength != sizeof(VideoMode))
    {
        VideoMode = AV_STANDARD_NTSC_M | AV_FLAGS_60Hz;
    }

    Result |= VideoMode & (AV_STANDARD_MASK | AV_REFRESH_MASK);

    // Get the user settings.
    status = ExQueryNonVolatileSetting(XC_VIDEO_FLAGS,
                                       &Type,
                                       &VideoMode,
                                       sizeof(VideoMode),
                                       &ResultLength);

    // Didn't work?  Default to no options.
    if (status != STATUS_SUCCESS || ResultLength != sizeof(VideoMode))
    {
        VideoMode = 0;
    }

    Result |= VideoMode & ~(AV_STANDARD_MASK | AV_PACK_MASK);

    return Result;
}

void
AvSendTVEncoderOption(
    IN  PVOID RegisterBase,
    IN  ULONG Option,
    IN  ULONG Param,
    OUT PULONG Result
    )
/*++

Routine Description:

    Sets various encoder options.

Arguments:

    RegisterBase - base address of the gpu hw registers

Return Value:

    None

--*/
{
    ULONG OutputMode;
    UCHAR Data, OldValue;
    ULONG iTV, i;
    USHORT WValue;

    const UCHAR *pByte;
    const UCHAR *pByteMax;

    OutputMode = AvpCurrentMode & AV_MODE_OUT_MASK;
    iTV       = (AvpCurrentMode & 0x0000007F);

    if (!RegisterBase)
    {
        RegisterBase = (void *)XPCICFG_GPU_MEMORY_REGISTER_BASE_0;
    }

    switch (Option) {

        case AV_OPTION_BLANK_SCREEN:

            REG_WR08(RegisterBase, NV_PRMVIO_SRX, 0x01);

            if (Param)
            {
                REG_WR08(RegisterBase, NV_PRMVIO_SR_RESET, 0x21);
            }
            else
            {
                REG_WR08(RegisterBase, NV_PRMVIO_SR_RESET, 0x01);

                // Renable the DACs.
                _outp(XPCICFG_LPCBRIDGE_IO_REGISTER_BASE_0 + 0xD3, 0x4);
            }
            break;

        case AV_OPTION_MACROVISION_MODE:

            AvpSetMacrovisionMode(RegisterBase, Param);
            AvpMacrovisionMode = Param;
            break;

        case AV_OPTION_MACROVISION_COMMIT:
                
            AvpCommitMacrovisionMode(RegisterBase, Param);

            AvpSetWSSBits(RegisterBase);

            break;
        
        case AV_OPTION_ENABLE_CC:

            if (OutputMode == AV_MODE_OUT_480SDTV)
            {

#ifdef FOCUS

                WValue = SMB_RDW(RegisterBase, 0xB2);
                SMB_WRW(RegisterBase, 0xB2, WValue | 0xC000);

#else !FOCUS
                                                                                                                                                                                                
                Data = SMB_RD(RegisterBase, 0xC4);
                SMB_WR(RegisterBase, 0xC4, Data | 0x38);

                pByte    = AvpCCRegisters[iTV];
                pByteMax = pByte + sizeof(AvpCCRegisters[0]);

                for (i = 0; pByte < pByteMax; pByte++, i++)
                {
                    SMB_WR(RegisterBase, AvpCCRegisters[0][i], *pByte);
                }

#endif !FOCUS

            }
            break;

        case AV_OPTION_DISABLE_CC:

            if (OutputMode == AV_MODE_OUT_480SDTV)
            {

#ifdef FOCUS
                
                WValue = SMB_RDW(RegisterBase, 0xB2);
                SMB_WRW(RegisterBase, 0xB2, WValue & 0x3FFF);

#else !FOCUS
                UCHAR Data = SMB_RD(RegisterBase, 0xC4);

                // Clear ECC enable bits.
                SMB_WR(RegisterBase, 0xC4, Data & ~0x30);

#endif !FOCUS

            }
            break;

        case AV_OPTION_SEND_CC_DATA:

            if (OutputMode == AV_MODE_OUT_480SDTV)
            {

#ifdef FOCUS

                USHORT Value;

                Value  = (USHORT)(*Result & 0xFF);
                Value |= (USHORT)((*Result >> 16) & 0xFF) << 8;

                if (Param & 1)
                {
                    SMB_WRW(RegisterBase, 0xAE, Value);
                }
                else
                {
                    SMB_WRW(RegisterBase, 0xB0, Value);
                }

#else !FOCUS

                if (Param & 1)
                {
                    SMB_WR(RegisterBase, 0xC0, (UCHAR)(*Result >> 16));
                    SMB_WR(RegisterBase, 0xC2, (UCHAR)*Result);
                }
                else
                {
                    SMB_WR(RegisterBase, 0xBC, (UCHAR)(*Result >> 16));
                    SMB_WR(RegisterBase, 0xBE, (UCHAR)*Result);
                }

#endif !FOCUS

            }
            break;

        case AV_QUERY_CC_STATUS:

            if (OutputMode == AV_MODE_OUT_480SDTV)
            {
#ifdef FOCUS

                *Result = 0;

                if (SMB_RDW(RegisterBase, 0xAE))
                {
                    *Result |= 0x1;
                }

                if (SMB_RDW(RegisterBase, 0xB0))
                {
                    *Result |= 0x2;
                }

#else !FOCUS

                *Result = (SMB_RD(RegisterBase, 0x02) >> 2) & 3;

#endif !FOCUS

            }
            else
            {
                *Result = 0;
            }

            break;

        case AV_QUERY_AV_CAPABILITIES:

            AvpCapabilities = AvpQueryAvCapabilities();
            *Result = AvpCapabilities;
            break;

        case AV_OPTION_FLICKER_FILTER:

#ifdef FOCUS

            SMB_WR(RegisterBase, 0x26, (UCHAR)(Param * 16 / 5));
            SMB_WR(RegisterBase, 0x27, 0);

#else !FOCUS

            if (Param == 0)
            {
                // Turn the flicker filter off.
                Data = SMB_RD(RegisterBase, 0xC8);

                SMB_WR(RegisterBase, 0xC8, Data | 0x40);
            }
            else
            {
                switch(Param)
                {
                case 1:
                    Data = 0x01;
                    break;

                case 2:
                    Data = 0x02;
                    break;

                case 3:
                    Data = 0x03;
                    break;

                case 4:
                default:
                    Data = 0x00;
                    break;
                }

                // Turn the flicker filter on.
                OldValue = SMB_RD(RegisterBase, 0xC8);

                // Clear out the old values and clear the disable bit.
                OldValue &= 0x80;

                // Set the new values.
                OldValue |= Data | (Data << 3);

                SMB_WR(RegisterBase, 0xC8, OldValue);

                // Munge the adapate FF.
                SMB_WR(RegisterBase, 0x34, Param == 5 ? 0x80 : 0x00);
            }

#endif !FOCUS

            break;

        case AV_OPTION_ZERO_MODE:

            AvpCurrentMode = 0;
            break;

        case AV_OPTION_QUERY_MODE:

            *Result = AvpCurrentMode;
            break;

        case AV_OPTION_ENABLE_LUMA_FILTER:

#ifndef FOCUS

            Data = SMB_RD(RegisterBase, 0x96) & 0x0F;

            if (Param)
            {
                Data |= 0x10;
            }

            SMB_WR(RegisterBase, 0x96, Data);

#endif

            break;

        case AV_OPTION_GUESS_FIELD:

#ifndef FOCUS

            *Result = SMB_RD(RegisterBase, 0x06) & 0x01;
                                
#endif

            break;

        case AV_QUERY_ENCODER_TYPE:

#ifdef FOCUS
            *Result = AV_ENCODER_FOCUS;
#else
            *Result = AV_ENCODER_CONEXANT_871;
#endif

            break;

        case AV_QUERY_MODE_TABLE_VERSION:

            *Result = AV_MODE_TABLE_VERSION;
            break;

        case AV_OPTION_CGMS:
            
            AvpCGMS = Param;

            AvpSetWSSBits(RegisterBase);

            break;

        case AV_OPTION_WIDESCREEN:

            AvpCurrentMode = (AvpCurrentMode & ~AV_MODE_FLAGS_WSS);

            if (Param)
            {
                AvpCurrentMode |= AV_MODE_FLAGS_WSS;
            }

            AvpSetWSSBits(RegisterBase);

            break;

        default:
            ;
    }
}


PVOID
AvGetSavedDataAddress(
    VOID
    )
/*++

Routine Description:

    Returns sticky data address saved acress quick reboots.

Arguments:

    None.

Return Value:

    Saved global address.

--*/
{
    return AvpSavedDataAddress;
}

VOID
AvSetSavedDataAddress(
    PVOID Address
    )
/*++

Routine Description:

    Sets sticky data address saved acress quick reboots.

Arguments:

    The address to save.

Return Value:

    None.

--*/
{
    AvpSavedDataAddress = Address;
}

VOID
AvRelocateSavedDataAddress(
    IN PVOID NewAddress,
    IN SIZE_T NumberOfBytes
    )
/*++

Routine Description:

    Sets sticky data address saved acress quick reboots.

Arguments:

    The address to save.

Return Value:

    None.

--*/
{
    D3DSurface *OldSurface;
    D3DSurface *NewSurface;

    ASSERT(AvpSavedDataAddress != NULL);

    OldSurface = (D3DSurface*)AvpSavedDataAddress;
    NewSurface = (D3DSurface*)NewAddress;

    //
    // Copy the bits from the the old surface to the new surface, including all
    // of the headers.
    //

    RtlCopyMemory(NewSurface, OldSurface, NumberOfBytes);

    //
    // Update the physical address of the surface data stored in the surface
    // header.
    //

    NewSurface->Data = MmGetPhysicalAddress(NewSurface) +
        (OldSurface->Data - MmGetPhysicalAddress(OldSurface));

    //
    // Wait for the write combine buffers to flush.
    //

    __asm sfence;

    //
    // Program the NV2A to scan from the new surface.
    //

    REG_WR32((PVOID)XPCICFG_GPU_MEMORY_REGISTER_BASE_0, NV_PCRTC_START,
        NewSurface->Data);

    //
    // Reset the VBLANK pending flag.
    //

    REG_WR32((PVOID)XPCICFG_GPU_MEMORY_REGISTER_BASE_0, NV_PCRTC_INTR_0,
        NV_PCRTC_INTR_0_VBLANK_RESET);

    //
    // Persist the new surface and update the saved data address to point at
    // this surface.
    //

    MmPersistContiguousMemory(NewSurface, NumberOfBytes, TRUE);
    AvpSavedDataAddress = NewSurface;

    //
    // Before freeing the old surface, loop until a VBLANK has occurred.  The
    // NV2A begins using the new surface at the next VBLANK.
    //

    while ((REG_RD32((PVOID)XPCICFG_GPU_MEMORY_REGISTER_BASE_0, NV_PCRTC_INTR_0) &
        NV_PCRTC_INTR_0_VBLANK_PENDING) == 0);

    //
    // Free the memory used for the old surface.
    //

    MmFreeContiguousMemory(OldSurface);
}
