/*
 * PROJECT:         ReactOS PCI Bus Driver
 * LICENSE:         BSD - See COPYING.ARM in the top level directory
 * FILE:            drivers/bus/pci/power.c
 * PURPOSE:         Bus/Device Power Management
 * PROGRAMMERS:     ReactOS Portable Systems Group
 */

/* INCLUDES *******************************************************************/

#include <pci.h>

#define NDEBUG
#include <debug.h>

/* GLOBALS ********************************************************************/

ULONG PciPowerDelayTable[PowerDeviceD3 * PowerDeviceD3] =
{
    0,      // D0 -> D0
    0,      // D1 -> D0
    200,    // D2 -> D0
    10000,  // D3 -> D0

    0,      // D0 -> D1
    0,      // D1 -> D1
    200,    // D2 -> D1
    10000,  // D3 -> D1

    200,    // D0 -> D2
    200,    // D1 -> D2
    0,      // D2 -> D2
    10000,  // D3 -> D2

    10000,  // D0 -> D3
    10000,  // D1 -> D3
    10000,  // D2 -> D3
    0       // D3 -> D3
};

/* FUNCTIONS ******************************************************************/

NTSTATUS
NTAPI
PciStallForPowerChange(IN PPCI_PDO_EXTENSION PdoExtension,
                       IN DEVICE_POWER_STATE PowerState,
                       IN ULONG_PTR CapOffset)
{
    ULONG PciState, TimeoutEntry, PmcsrOffset, TryCount;
    PPCI_VERIFIER_DATA VerifierData;
    LARGE_INTEGER Interval;
    PCI_PMCSR Pmcsr;
    KIRQL Irql;

    /* Make sure the power state is valid, and the device can support it */
    ASSERT((PdoExtension->PowerState.CurrentDeviceState >= PowerDeviceD0) &&
           (PdoExtension->PowerState.CurrentDeviceState <= PowerDeviceD3));
    ASSERT((PowerState >= PowerDeviceD0) && (PowerState <= PowerDeviceD3));
    ASSERT(!(PdoExtension->HackFlags & PCI_HACK_NO_PM_CAPS));

    /* Save the current IRQL */
    Irql = KeGetCurrentIrql();

    /* Pick the expected timeout for this transition */
    TimeoutEntry = PciPowerDelayTable[PowerState * PdoExtension->PowerState.CurrentDeviceState];

    /* PCI power states are one less than NT power states */
    PciState = PowerState - 1;

    /* The state status is stored in the PMCSR offset */
    PmcsrOffset = CapOffset + FIELD_OFFSET(PCI_PM_CAPABILITY, PMCSR);

    /* Try changing the power state up to 100 times */
    TryCount = 100;
    while (--TryCount)
    {
        /* Check if this state transition will take time */
        if (TimeoutEntry > 0)
        {
            /* Check if this is happening at high IRQL */
            if (Irql >= DISPATCH_LEVEL)
            {
                /* Can't wait at high IRQL, stall the processor */
                KeStallExecutionProcessor(TimeoutEntry);
            }
            else
            {
                /* Do a wait for the timeout specified instead */
                Interval.QuadPart = -10 * TimeoutEntry;
                Interval.QuadPart -= KeQueryTimeIncrement() - 1;
                KeDelayExecutionThread(0, 0, &Interval);
            }
        }

        /* Read the PMCSR and see if the state has changed */
        PciReadDeviceConfig(PdoExtension, &Pmcsr, PmcsrOffset, sizeof(PCI_PMCSR));
        if (Pmcsr.PowerState == PciState) return STATUS_SUCCESS;

        /* Try again, forcing a timeout of 1ms */
        TimeoutEntry = 1000;
    }

    /* Call verifier with this error */
    VerifierData = PciVerifierRetrieveFailureData(2);
    ASSERT(VerifierData);
    VfFailDeviceNode(PdoExtension->PhysicalDeviceObject,
                     PCI_VERIFIER_DETECTED_VIOLATION,
                     2, // The PMCSR register was not updated within the spec-mandated time.
                     VerifierData->FailureClass,
                     &VerifierData->AssertionControl,
                     VerifierData->DebuggerMessageText,
                     "%DevObj%Ulong",
                     PdoExtension->PhysicalDeviceObject,
                     PciState);

    return STATUS_DEVICE_PROTOCOL_ERROR;
}

NTSTATUS
NTAPI
PciSetPowerManagedDevicePowerState(IN PPCI_PDO_EXTENSION DeviceExtension,
                                   IN DEVICE_POWER_STATE DeviceState,
                                   IN BOOLEAN IrpSet)
{
    NTSTATUS Status;
    PCI_PM_CAPABILITY PmCaps;
    ULONG CapsOffset;

    /* Assume success */
    Status = STATUS_SUCCESS;

    /* Check if this device can support low power states */
    if (!(PciCanDisableDecodes(DeviceExtension, NULL, 0, TRUE)) &&
         (DeviceState != PowerDeviceD0))
    {
        /* Simply return success, ignoring this request */
        DPRINT1("Cannot disable decodes on this device, ignoring PM request...\n");
        return Status;
    }

    /* Does the device support power management at all? */
    if (!(DeviceExtension->HackFlags & PCI_HACK_NO_PM_CAPS))
    {
        /* Get the PM capabailities register */
        CapsOffset = PciReadDeviceCapability(DeviceExtension,
                                             DeviceExtension->CapabilitiesPtr,
                                             PCI_CAPABILITY_ID_POWER_MANAGEMENT,
                                             &PmCaps.Header,
                                             sizeof(PCI_PM_CAPABILITY));
        ASSERT(CapsOffset);
        ASSERT(DeviceState != PowerDeviceUnspecified);

        /* Check if the device is being powered up */
        if (DeviceState == PowerDeviceD0)
        {
            /* Set full power state */
            PmCaps.PMCSR.ControlStatus.PowerState = 0;

            /* Check if the device supports Cold-D3 poweroff */
            if (PmCaps.PMC.Capabilities.Support.PMED3Cold)
            {
                /* If there was a pending PME, clear it */
                PmCaps.PMCSR.ControlStatus.PMEStatus = 1;
            }
        }
        else
        {
            /* Otherwise, just set the new power state, converting from NT */
            PmCaps.PMCSR.ControlStatus.PowerState = DeviceState - 1;
        }

        /* Write the new power state in the PMCSR */
        PciWriteDeviceConfig(DeviceExtension,
                             &PmCaps.PMCSR,
                             CapsOffset + FIELD_OFFSET(PCI_PM_CAPABILITY, PMCSR),
                             sizeof(PCI_PMCSR));

        /* Now wait for the change to "stick" based on the spec-mandated time */
        Status = PciStallForPowerChange(DeviceExtension, DeviceState, CapsOffset);
        if (!NT_SUCCESS(Status)) return Status;
    }
    else
    {
        /* Nothing to do! */
        DPRINT1("No PM on this device, ignoring request\n");
    }

    /* Check if new resources have to be assigned */
    if (IrpSet)
    {
        /* Check if the new device state is lower (higher power) than now */
        if (DeviceState < DeviceExtension->PowerState.CurrentDeviceState)
        {
            /* We would normally re-assign resources after powerup */
            UNIMPLEMENTED_DBGBREAK();
            Status = STATUS_NOT_IMPLEMENTED;
        }
    }

    /* Return the power state change status */
    return Status;
}

NTSTATUS
NTAPI
PciFdoWaitWake(IN PIRP Irp,
               IN PIO_STACK_LOCATION IoStackLocation,
               IN PPCI_FDO_EXTENSION DeviceExtension)
{
    UNREFERENCED_PARAMETER(Irp);
    UNREFERENCED_PARAMETER(IoStackLocation);
    UNREFERENCED_PARAMETER(DeviceExtension);

    UNIMPLEMENTED;
    while (TRUE);
    return STATUS_NOT_SUPPORTED;
}

NTSTATUS
NTAPI
PciFdoSetPowerState(IN PIRP Irp,
                    IN PIO_STACK_LOCATION IoStackLocation,
                    IN PPCI_FDO_EXTENSION DeviceExtension)
{
    UNREFERENCED_PARAMETER(Irp);
    UNREFERENCED_PARAMETER(IoStackLocation);
    UNREFERENCED_PARAMETER(DeviceExtension);

    UNIMPLEMENTED;
    while (TRUE);
    return STATUS_NOT_SUPPORTED;
}

NTSTATUS
NTAPI
PciFdoIrpQueryPower(IN PIRP Irp,
                    IN PIO_STACK_LOCATION IoStackLocation,
                    IN PPCI_FDO_EXTENSION DeviceExtension)
{
    UNREFERENCED_PARAMETER(Irp);
    UNREFERENCED_PARAMETER(IoStackLocation);
    UNREFERENCED_PARAMETER(DeviceExtension);

    UNIMPLEMENTED;
    while (TRUE);
    return STATUS_NOT_SUPPORTED;
}

/* EOF */
