//***************************************************************************
//
// Module Name:
//
//   power.c
//
// Abstract:
//   This module contains the code that implements the Plug & Play and 
//   power management features
//
// Environment:
//
//   Kernel mode
//
//
// Copyright (c) 1994-1998 3Dlabs Inc. Ltd. All rights reserved.            
// Copyright (c) 1995-1999 Microsoft Corporation.  All Rights Reserved.
//
//***************************************************************************

#include "permedia.h"

#define VESA_POWER_FUNCTION   0x4f10
#define VESA_POWER_ON         0x0000
#define VESA_POWER_STANDBY    0x0100
#define VESA_POWER_SUSPEND    0x0200
#define VESA_POWER_OFF        0x0400
#define VESA_GET_POWER_FUNC   0x0000
#define VESA_SET_POWER_FUNC   0x0001
#define VESA_STATUS_SUCCESS   0x004f

//
// all our IDs begin with 0x1357bd so they are readily identifiable as our own
//

#define P2_DDC_MONITOR        (0x1357bd00)
#define P2_NONDDC_MONITOR     (0x1357bd01)

BOOLEAN PowerOnReset( PHW_DEVICE_EXTENSION hwDeviceExtension );
VOID    SaveDeviceState( PHW_DEVICE_EXTENSION hwDeviceExtension );
VOID    RestoreDeviceState( PHW_DEVICE_EXTENSION hwDeviceExtension );

VOID    I2CWriteClock(PVOID HwDeviceExtension, UCHAR data);
VOID    I2CWriteData(PVOID HwDeviceExtension, UCHAR data);
BOOLEAN I2CReadClock(PVOID HwDeviceExtension);
BOOLEAN I2CReadData(PVOID HwDeviceExtension);
VOID    I2CWaitVSync(PVOID HwDeviceExtension);

#if defined(ALLOC_PRAGMA)
#pragma alloc_text(PAGE, PowerOnReset)
#pragma alloc_text(PAGE, SaveDeviceState)
#pragma alloc_text(PAGE, RestoreDeviceState)
#pragma alloc_text(PAGE, Permedia2GetPowerState)
#pragma alloc_text(PAGE, Permedia2SetPowerState)
#pragma alloc_text(PAGE, Permedia2GetChildDescriptor)
#pragma alloc_text(PAGE, I2CWriteClock) 
#pragma alloc_text(PAGE, I2CWriteData) 
#pragma alloc_text(PAGE, I2CReadClock) 
#pragma alloc_text(PAGE, I2CReadData)  
#pragma alloc_text(PAGE, I2CWaitVSync)
#endif


I2C_FNC_TABLE I2CFunctionTable = 
{
    sizeof(I2C_FNC_TABLE), 
    I2CWriteClock, 
    I2CWriteData, 
    I2CReadClock, 
    I2CReadData,  
    I2CWaitVSync, 
    NULL
};


VP_STATUS Permedia2GetPowerState (
    PVOID HwDeviceExtension, 
    ULONG HwId, 
    PVIDEO_POWER_MANAGEMENT VideoPowerControl 
    )

/*++

Routine Description:

    Returns power state information.

Arguments:

    HwDeviceExtension    - Pointer to our hardware device extension structure.

    HwId                 - Private unique 32 bit ID identifing the device.

    VideoPowerControl    - Points to a VIDEO_POWER_MANAGEMENT structure that 
                           specifies the power state for which support is 
                           being queried. 

Return Value:

    VP_STATUS value (NO_ERROR or error value)

--*/

{
    VIDEO_X86_BIOS_ARGUMENTS biosArguments;
    VP_STATUS status;
    PHW_DEVICE_EXTENSION hwDeviceExtension = HwDeviceExtension;

    DEBUG_PRINT((2, "Permedia2GetPowerState: hwId(%xh) state = %d\n", 
                     (int)HwId, (int)VideoPowerControl->PowerState));

    switch((int)HwId)
    {
        case P2_DDC_MONITOR:
        case P2_NONDDC_MONITOR:

            switch (VideoPowerControl->PowerState)
            {

                case VideoPowerOn:
                case VideoPowerStandBy:
                case VideoPowerSuspend:
                case VideoPowerOff:
                case VideoPowerHibernate:
                case VideoPowerShutdown:

                    status = NO_ERROR;
                    break;

                default:

                    DEBUG_PRINT((2, "Permedia2GetPowerState: Unknown monitor PowerState(%xh)\n", 
                                    (int)VideoPowerControl->PowerState));

                    ASSERT(FALSE);
                    status = ERROR_INVALID_PARAMETER;
            }
            break;

        case DISPLAY_ADAPTER_HW_ID:

            //
            // only support ON at the moment
            //

            switch (VideoPowerControl->PowerState)
            {
                case VideoPowerOn:
                case VideoPowerStandBy:
                case VideoPowerSuspend:
                case VideoPowerHibernate:
                case VideoPowerShutdown:
                    status = NO_ERROR;
                    break;

                case VideoPowerOff:

                    if( hwDeviceExtension->HardwiredSubSystemId )
                    {
                        status = NO_ERROR;
                    } 
                    else
                    {
                        //
                        // If SubSystemId is not hardwired in a read-only way, 
                        // it is possible we'll see a different value when 
                        // system comes back form S3 mode. This will cause 
                        // problem since os will assume this is a different 
                        // device 
                        //

                        DEBUG_PRINT((2, "Permedia2GetPowerState: VideoPowerOff is not suported by this card!\n"));
 
                        status = ERROR_INVALID_FUNCTION;
                    }

                    break;
 

                default:

                    DEBUG_PRINT((2, "Permedia2GetPowerState: Unknown adapter PowerState(%xh)\n", 
                                 (int)VideoPowerControl->PowerState));

                    ASSERT(FALSE);

                    status = ERROR_INVALID_PARAMETER;

            }
            break;

        default:

            DEBUG_PRINT((1, "Permedia2GetPowerState: Unknown hwId(%xh)", 
                            (int)HwId));
            ASSERT(FALSE);

            status = ERROR_INVALID_PARAMETER;
    }

    DEBUG_PRINT((2, "Permedia2GetPowerState: returning %xh\n", status));

    return(status);
}

VP_STATUS Permedia2SetPowerState ( 
    PVOID HwDeviceExtension, 
    ULONG HwId, 
    PVIDEO_POWER_MANAGEMENT VideoPowerControl
    )

/*++

Routine Description:

    Set the power state for a given device.

Arguments:

    HwDeviceExtension - Pointer to our hardware device extension structure.

    HwId              - Private unique 32 bit ID identifing the device.

    VideoPowerControl - Points to a VIDEO_POWER_MANAGEMENT structure that 
                        specifies the power state to be set. 

Return Value:

    VP_STATUS value (NO_ERROR, if all's well)

--*/

{
    PHW_DEVICE_EXTENSION hwDeviceExtension = HwDeviceExtension;
    ULONG Polarity;
    VIDEO_X86_BIOS_ARGUMENTS biosArguments;
    VP_STATUS status;
    P2_DECL;

    DEBUG_PRINT((2, "Permedia2SetPowerState: hwId(%xh) state = %d\n", 
                     (int)HwId, (int)VideoPowerControl->PowerState));

    switch((int)HwId)
    {

        case P2_DDC_MONITOR:
        case P2_NONDDC_MONITOR:

            Polarity = VideoPortReadRegisterUlong(VIDEO_CONTROL);

            DEBUG_PRINT((2, "Permedia2SetPowerState: VideoControl before power state change = %08x\n", 
                             Polarity));

            Polarity &= ~((1 << 5) | (1 << 3) | 1);

            switch (VideoPowerControl->PowerState)
            {

                case VideoPowerHibernate:
                case VideoPowerShutdown:

                    DEBUG_PRINT((2, "Permedia2SetPowerState: monitor HIBERNATE\n"));

                    //
                    // Do nothing for hibernate as the monitor must stay on.
                    //

                    status = NO_ERROR;
                    break;

                case VideoPowerOn:

                    DEBUG_PRINT((2, "Permedia2SetPowerState: monitor ON\n"));

                    RestoreDeviceState(hwDeviceExtension);
                    status = NO_ERROR;
                    break;

                case VideoPowerStandBy:

                    DEBUG_PRINT((2, "Permedia2SetPowerState: monitor STANDBY\n"));

                    //
                    // hsync low, vsync active high, video disabled
                    //

                    SaveDeviceState(hwDeviceExtension);
                    VideoPortWriteRegisterUlong(VIDEO_CONTROL, 
                                                Polarity | (1 << 5) | (2 << 3) | 0);

                    status = NO_ERROR;
                    break;

                case VideoPowerSuspend:

                    DEBUG_PRINT((2, "Permedia2SetPowerState: monitor SUSPEND\n"));

                    //
                    // vsync low, hsync active high, video disabled
                    //

                    VideoPortWriteRegisterUlong(VIDEO_CONTROL, 
                                                Polarity | (2 << 5) | (1 << 3) | 0);

                    status = NO_ERROR;
                    break;

                case VideoPowerOff:

                    DEBUG_PRINT((2, "Permedia2SetPowerState: monitor OFF\n"));

                    //
                    // vsync low, hsync low, video disabled
                    //

                    VideoPortWriteRegisterUlong(VIDEO_CONTROL, 
                                                Polarity | (2 << 5) | (2 << 3) | 0);

                    status = NO_ERROR;
                    break;

                default:

                    DEBUG_PRINT((2, "Permedia2GetPowerState: Unknown monitor PowerState(%xh)\n", 
                                     (int)VideoPowerControl->PowerState));

                    ASSERT(FALSE);
                    status = ERROR_INVALID_PARAMETER;
            }

            //
            // Track the current monitor power state
            //

            hwDeviceExtension->bMonitorPoweredOn =
                (VideoPowerControl->PowerState == VideoPowerOn) ||
                (VideoPowerControl->PowerState == VideoPowerHibernate);

            Polarity = VideoPortReadRegisterUlong(VIDEO_CONTROL);

            DEBUG_PRINT((2, "Permedia2SetPowerState: VideoControl after power state change = %08x\n", 
                             Polarity));
            break;

        case DISPLAY_ADAPTER_HW_ID:

            switch (VideoPowerControl->PowerState)
            {
                case VideoPowerHibernate:
                case VideoPowerShutdown:

                    DEBUG_PRINT((2, "Permedia2SetPowerState: adapter HIBERNATE\n"));

                    status = NO_ERROR;
                    break;

                case VideoPowerOn:

                    DEBUG_PRINT((2, "Permedia2SetPowerState: adapter ON\n"));

                    if ((hwDeviceExtension->PreviousPowerState == VideoPowerOff) ||
                        (hwDeviceExtension->PreviousPowerState == VideoPowerHibernate))
                    {
                        PowerOnReset(hwDeviceExtension);
                    }

                    status = NO_ERROR;
                    break;

                case VideoPowerStandBy:

                    DEBUG_PRINT((2, "Permedia2SetPowerState: adapter STANDBY\n"));

                    status = NO_ERROR;
                    break;

                case VideoPowerSuspend:

                    DEBUG_PRINT((2, "Permedia2SetPowerState: adapter SUSPEND\n"));

                    status = NO_ERROR;
                    break;
    
                case VideoPowerOff:

                    DEBUG_PRINT((2, "Permedia2SetPowerState: adapter OFF\n"));

                    status = NO_ERROR;
                    break;

                default:

                    DEBUG_PRINT((2, "Permedia2GetPowerState: Unknown adapter PowerState(%xh)\n", 
                                     (int)VideoPowerControl->PowerState));

                    ASSERT(FALSE);
                    status = ERROR_INVALID_PARAMETER;
            }

            hwDeviceExtension->PreviousPowerState = 
                    VideoPowerControl->PowerState;

            break;
    
        default:

            DEBUG_PRINT((1, "Permedia2SetPowerState: Unknown hwId(%xh)\n", 
                             (int)HwId));

            ASSERT(FALSE);
            status = ERROR_INVALID_PARAMETER;
    }

    DEBUG_PRINT((2, "Permedia2SetPowerState: returning %xh\n", status));

    return(status);

}


BOOLEAN PowerOnReset(
    PHW_DEVICE_EXTENSION hwDeviceExtension
    )

/*++

Routine Description:

   Called when the adapter is powered on 

--*/

{
    int      i;
    ULONG    ulValue;
    BOOLEAN  bOK;
    P2_DECL;

    if(!hwDeviceExtension->bVGAEnabled ||
       !hwDeviceExtension->bDMAEnabled)
    {
        PCI_COMMON_CONFIG  PciData;

        //
        // in a multi-adapter system we'll need to turn on the DMA and 
        // memory space for the secondary adapters
        //

        DEBUG_PRINT((1, "PowerOnReset() enabling memory space access for the secondary card\n"));

        VideoPortGetBusData( hwDeviceExtension, 
                             PCIConfiguration, 
                             0, 
                             &PciData, 
                             0, 
                             PCI_COMMON_HDR_LENGTH);

        PciData.Command |= PCI_ENABLE_MEMORY_SPACE;
        PciData.Command |= PCI_ENABLE_BUS_MASTER; 

        VideoPortSetBusData( hwDeviceExtension, 
                             PCIConfiguration, 
                             0, 
                             &PciData, 
                             0, 
                             PCI_COMMON_HDR_LENGTH );

#if DBG
        DumpPCIConfigSpace(hwDeviceExtension, hwDeviceExtension->pciBus, 
                            (ULONG)hwDeviceExtension->pciSlot.u.AsULONG);
#endif

    }

    //
    // While waking up from hibernation, we usually don't need
    // to reset perm2 and call ProcessInitializationTable()
    // for the primary card since video bios will get posted. 
    // We do so here because we saw cases that the perm2 bios 
    // failed to worked correctly on some machines. 
    //

    //
    // reset the device
    //

    VideoPortWriteRegisterUlong(RESET_STATUS, 0);

    for(i = 0; i < 100000; ++i)
    {
        ulValue = VideoPortReadRegisterUlong(RESET_STATUS);

        if (ulValue == 0)
            break;
    }

    if(ulValue)
    {
        DEBUG_PRINT((1, "PowerOnReset() Read RESET_STATUS(%xh) - failed to reset\n", 
                         ulValue));

        ASSERT(FALSE);
        bOK = FALSE;
    }
    else
    {
        //
        // reload registers given in ROM
        //

        if(hwDeviceExtension->culTableEntries)
        {
            ProcessInitializationTable(hwDeviceExtension);
        }

        //
        // set-up other registers not set in InitializeVideo
        //

        VideoPortWriteRegisterUlong(BYPASS_WRITE_MASK, 0xFFFFFFFF);
        VideoPortWriteRegisterUlong(APERTURE_ONE, 0x0);
        VideoPortWriteRegisterUlong(APERTURE_TWO, 0x0);    

        bOK = TRUE;

    }

    return(bOK);

}


VOID SaveDeviceState(PHW_DEVICE_EXTENSION hwDeviceExtension)

/*++

Routine Description:

    Save any registers that will be destroyed when we power down the monitor

--*/

{
    P2_DECL;

    DEBUG_PRINT((2, "SaveDeviceState() called\n"));
    
    //
    // hwDeviceExtension->VideoControl should be set in InitializeVideo,
    // just in case we get here before InitializeVideo
    //

    if( !(hwDeviceExtension->VideoControl) )
    {
        hwDeviceExtension->VideoControl = 
               VideoPortReadRegisterUlong(VIDEO_CONTROL);
    }

    hwDeviceExtension->IntEnable = VideoPortReadRegisterUlong(INT_ENABLE);

}


VOID RestoreDeviceState(PHW_DEVICE_EXTENSION hwDeviceExtension)

/*++

Routine Description:

    Restore registers saved before monitor power down

--*/

{
    P2_DECL;

    DEBUG_PRINT((2, "RestoreDeviceState() called\n"));
    VideoPortWriteRegisterUlong(VIDEO_CONTROL, hwDeviceExtension->VideoControl);
    VideoPortWriteRegisterUlong(INT_ENABLE, hwDeviceExtension->IntEnable);

}


ULONG
Permedia2GetChildDescriptor( 
    PVOID HwDeviceExtension,
    PVIDEO_CHILD_ENUM_INFO ChildEnumInfo,
    PVIDEO_CHILD_TYPE pChildType,  
    PVOID pChildDescriptor, 
    PULONG pUId, 
    PULONG pUnused )


/*++

Routine Description:

    Enumerate all child devices controlled by the Permedia 2 chip.

    This includes DDC monitors attached to the board, as well as other devices
    which may be connected to a proprietary bus.

Arguments:

    HwDeviceExtension -
        Pointer to our hardware device extension structure.

    ChildEnumInfo - 
        Information about the device that should be enumerated.

    pChildType -
        Type of child we are enumerating - monitor, I2C ...

    pChildDescriptor -
        Identification structure of the device (EDID, string)

    pUId -
        Private unique 32 bit ID to passed back to the miniport

    pUnused -
        Do not use

Return Value:

    ERROR_NO_MORE_DEVICES -
        if no more child devices exist.

    ERROR_INVALID_NAME -
        The miniport could not enumerate the child device identified in 
        ChildEnumInfo but does have more devices to be enumerated. 

    ERROR_MORE_DATA - 
        There are more devices to be enumerated. 

Note:

    In the event of a failure return, none of the fields are valid except for
    the return value and the pMoreChildren field.

--*/


{

    PHW_DEVICE_EXTENSION hwDeviceExtension = HwDeviceExtension;
    BOOLEAN              bGotEdid = FALSE;


    DEBUG_PRINT((2, "Permedia2GetChildDescriptor called\n"));

    switch (ChildEnumInfo->ChildIndex) 
    {
        case 0:

            //
            // Case 0 is used to enumerate devices found by the ACPI firmware.
            // We do not currently support ACPI devices
            //

            return ERROR_NO_MORE_DEVICES;

        case 1:

            //
            // Treat index 1 as the monitor
            //

            *pChildType = Monitor;
    
            //
            // if it's a DDC monitor we return its EDID in pjBuffer
            // (always 128 bytes)
            //

            if(VideoPortDDCMonitorHelper(HwDeviceExtension,
                                         &I2CFunctionTable,
                                         pChildDescriptor,
                                         ChildEnumInfo->ChildDescriptorSize))
            {
                //
                // found a DDC monitor
                //

                DEBUG_PRINT((2, "Permedia2GetChildDescriptor: found a DDC monitor\n"));

                *pUId = P2_DDC_MONITOR;
            }
            else
            {
                //
                // failed: assume non-DDC monitor
                //

                DEBUG_PRINT((2, "Permedia2GetChildDescriptor: found a non-DDC monitor\n"));

                *pUId = P2_NONDDC_MONITOR;
            }

            return ERROR_MORE_DATA;

        default:

            return ERROR_NO_MORE_DEVICES;
    }
}


VOID I2CWriteClock(PVOID HwDeviceExtension, UCHAR data)
{
    const ULONG nbitClock = 3;
    const ULONG Clock = 1 << nbitClock;

    PHW_DEVICE_EXTENSION hwDeviceExtension = HwDeviceExtension;
    ULONG ul;
    P2_DECL;

    ul = VideoPortReadRegisterUlong(DDC_DATA);
    ul &= ~Clock;
    ul |= (data & 1) << nbitClock;
    VideoPortWriteRegisterUlong(DDC_DATA, ul);
}

VOID I2CWriteData(PVOID HwDeviceExtension, UCHAR data)
{
    const ULONG nbitData = 2;
    const ULONG Data = 1 << nbitData;

    PHW_DEVICE_EXTENSION hwDeviceExtension = HwDeviceExtension;
    ULONG ul;
    P2_DECL;

    ul = VideoPortReadRegisterUlong(DDC_DATA);
    ul &= ~Data;
    ul |= ((data & 1) << nbitData);
    VideoPortWriteRegisterUlong(DDC_DATA, ul);
}

BOOLEAN I2CReadClock(PVOID HwDeviceExtension)
{
    const ULONG nbitClock = 1;
    const ULONG Clock = 1 << nbitClock;
    PHW_DEVICE_EXTENSION hwDeviceExtension = HwDeviceExtension;
    ULONG ul;
    P2_DECL;

    ul = VideoPortReadRegisterUlong(DDC_DATA);
    ul &= Clock;
    ul >>= nbitClock;

    return((BOOLEAN)ul);
}

BOOLEAN I2CReadData(PVOID HwDeviceExtension)
{
    const ULONG nbitData = 0;
    const ULONG Data = 1 << nbitData;
    PHW_DEVICE_EXTENSION hwDeviceExtension = HwDeviceExtension;
    ULONG ul;
    P2_DECL;

    ul = VideoPortReadRegisterUlong(DDC_DATA);
    ul &= Data;
    ul >>= nbitData;
    return((BOOLEAN)ul);
}

VOID I2CWaitVSync(PVOID HwDeviceExtension)
{
    PHW_DEVICE_EXTENSION hwDeviceExtension = HwDeviceExtension;
    UCHAR jIndexSaved, jStatus;
    P2_DECL;
    
    if(hwDeviceExtension->bVGAEnabled)
    {

        //
        // VGA run on this board, is it currently in VGA or VTG mode?
        //

        jIndexSaved = VideoPortReadRegisterUchar(PERMEDIA_MMVGA_INDEX_REG);

        VideoPortWriteRegisterUchar(PERMEDIA_MMVGA_INDEX_REG, 
                                    PERMEDIA_VGA_CTRL_INDEX);

        jStatus = VideoPortReadRegisterUchar(PERMEDIA_MMVGA_DATA_REG);

        VideoPortWriteRegisterUchar(PERMEDIA_MMVGA_INDEX_REG, jIndexSaved);

    }
    else
    {
        //
        // VGA not run
        //

        jStatus = 0;

    }

    
    if(jStatus & PERMEDIA_VGA_ENABLE)
    {
        //
        // in VGA, so check VSync via the VGA registers
        // 1. if we're in VSync, wait for it to end
        //

        while( (VideoPortReadRegisterUchar(PERMEDIA_MMVGA_STAT_REG) & 
                PERMEDIA_VGA_STAT_VSYNC) == 1); 

        //
        // 2. wait for the start of VSync
        //

        while( (VideoPortReadRegisterUchar(PERMEDIA_MMVGA_STAT_REG) & 
                PERMEDIA_VGA_STAT_VSYNC) == 0); 
    }
    else
    {
        if(!hwDeviceExtension->bVTGRunning)
        {

            //
            // time to set-up the VTG - we'll need a valid mode to do this, 
            // so we;ll choose 640x480x8 we get here (at boot-up only) if 
            // the secondary card has VGA disabled: GetChildDescriptor is 
            // called before InitializeVideo so that the VTG hasn't been 
            // programmed yet
            //

            DEBUG_PRINT((2, "I2CWaitVSync() - VGA nor VTG running: attempting to setup VTG\n"));

            if(hwDeviceExtension->pFrequencyDefault == NULL)
            {
                DEBUG_PRINT((1, "I2CWaitVSync() - no valid modes to use: can't set-up VTG\n"));
                return;
            }

            Permedia2GetClockSpeeds(HwDeviceExtension);
            ZeroMemAndDac(hwDeviceExtension, 0);

            if (!InitializeVideo( HwDeviceExtension, 
                                  hwDeviceExtension->pFrequencyDefault) )
            {
                DEBUG_PRINT((1, "I2CWaitVSync() - InitializeVideo failed\n"));
                return;
            }        
        }

        //
        // VTG has been set-up: check via the control registers
        //

        VideoPortWriteRegisterUlong ( INT_FLAGS, 
                                      INTR_VBLANK_SET );

        while (( (VideoPortReadRegisterUlong (INT_FLAGS) ) & 
                 INTR_VBLANK_SET ) == 0 ); 
    }
}

