/*++

Copyright (c) 1990-2000  Microsoft Corporation

Module Name:

    pnp.c

Abstract:

    This is the pnp portion of the video port driver.

Environment:

    kernel mode only

Revision History:

--*/

#include "videoprt.h"

#pragma alloc_text(PAGE,pVideoPortQueryACPIInterface)
#pragma alloc_text(PAGE,pVideoPortACPIEventHandler)
#pragma alloc_text(PAGE,pVideoPortACPIIoctl)
#pragma alloc_text(PAGE,VpRegisterLCDCallbacks)
#pragma alloc_text(PAGE,VpUnregisterLCDCallbacks)
#pragma alloc_text(PAGE,VpRegisterPowerStateCallback)
#pragma alloc_text(PAGE,VpDelayedPowerStateCallback)
#pragma alloc_text(PAGE,VpSetLCDPowerUsage)

BOOLEAN 
pCheckDeviceRelations(
    PFDO_EXTENSION FdoExtension, 
    BOOLEAN bNewMonitor
    );

NTSTATUS
pVideoPortQueryACPIInterface(
    PDEVICE_SPECIFIC_EXTENSION DoSpecificExtension
    )

/*++

Routine Description:

    Send a QueryInterface Irp to our parent (the PCI bus driver) to
    retrieve the AGP_BUS_INTERFACE.

Returns:

    NT_STATUS code

--*/

{
    KEVENT                  Event;
    PIRP                    QueryIrp = NULL;
    IO_STATUS_BLOCK         IoStatusBlock;
    PIO_STACK_LOCATION      NextStack;
    NTSTATUS                Status;
    ACPI_INTERFACE_STANDARD AcpiInterface;
    PFDO_EXTENSION          FdoExtension = DoSpecificExtension->pFdoExtension;

    //
    // For those special cases, don't use ACPI HotKey switching
    //
    if (VpSetupTypeAtBoot != SETUPTYPE_NONE) {
        return STATUS_INVALID_PARAMETER;
    }

    if (FdoExtension->Flags & LEGACY_DETECT) {
        return STATUS_INVALID_PARAMETER;
    }
    
    if ((FdoExtension->Flags & FINDADAPTER_SUCCEEDED) == 0) {
        return STATUS_INVALID_PARAMETER;
    }

    KeInitializeEvent(&Event, SynchronizationEvent, FALSE);

    QueryIrp = IoBuildSynchronousFsdRequest(IRP_MJ_PNP,
                                            FdoExtension->AttachedDeviceObject,
                                            NULL,
                                            0,
                                            NULL,
                                            &Event,
                                            &IoStatusBlock);

    if (QueryIrp == NULL) {

        return STATUS_INVALID_PARAMETER;
    }

    //
    // Set the default error code.
    //

    QueryIrp->IoStatus.Status = STATUS_NOT_SUPPORTED;

    //
    // Set up for a QueryInterface Irp.
    //

    NextStack = IoGetNextIrpStackLocation(QueryIrp);

    NextStack->MajorFunction = IRP_MJ_PNP;
    NextStack->MinorFunction = IRP_MN_QUERY_INTERFACE;

    NextStack->Parameters.QueryInterface.InterfaceType = &GUID_ACPI_INTERFACE_STANDARD;
    NextStack->Parameters.QueryInterface.Size = sizeof(ACPI_INTERFACE_STANDARD);
    NextStack->Parameters.QueryInterface.Version = 1;
    NextStack->Parameters.QueryInterface.Interface = (PINTERFACE) &AcpiInterface;
    NextStack->Parameters.QueryInterface.InterfaceSpecificData = NULL;

    AcpiInterface.Size = sizeof(ACPI_INTERFACE_STANDARD);
    AcpiInterface.Version = 1;


    //
    // Call the filter driver.
    //

    Status = IoCallDriver(FdoExtension->AttachedDeviceObject, QueryIrp);

    if (Status == STATUS_PENDING) {

        KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL);

        Status = IoStatusBlock.Status;

    }

    if (NT_SUCCESS(Status))
    {
        pVideoDebugPrint((0, "VideoPort: This is an ACPI Machine !\n"));

        //
        // Let's register for this event and provide our default handler.
        //

        AcpiInterface.RegisterForDeviceNotifications(AcpiInterface.Context, //FdoExtension->AttachedDeviceObject,
                                                     pVideoPortACPIEventCallback,
                                                     DoSpecificExtension);

        //
        // Register for LCD notifications 
        //

        VpRegisterLCDCallbacks();
    }

    //
    // Turn on HotKey switching notify mode 
    //
    if (NT_SUCCESS(Status))
    {
        ULONG active = 0;
        UCHAR outputBuffer[sizeof(ACPI_EVAL_OUTPUT_BUFFER) + 10];
        Status = pVideoPortACPIIoctl(FdoExtension->AttachedDeviceObject,
                                     (ULONG) ('SOD_'),
                                     &active,
                                     NULL,
                                     0,
                                     (PACPI_EVAL_OUTPUT_BUFFER)outputBuffer);
    }


    //
    // Register Dock/Undock notification
    //
    if (NT_SUCCESS(Status))
    {
        Status = IoRegisterPlugPlayNotification(EventCategoryHardwareProfileChange, 
                                                0,
                                                NULL,
                                                FdoExtension->FunctionalDeviceObject->DriverObject,
                                                pVideoPortDockEventCallback,
                                                DoSpecificExtension,
                                                &DockCallbackHandle);
    }

    return Status;
}

NTSTATUS
pVideoPortDockEventCallback (
    PVOID NotificationStructure,
    PDEVICE_SPECIFIC_EXTENSION DoSpecificExtension
    )
{
    UNREFERENCED_PARAMETER(NotificationStructure);

    pVideoPortACPIEventCallback(DoSpecificExtension, 0x77);

    return STATUS_SUCCESS;
}

VOID
pVideoPortACPIEventCallback(
    PDEVICE_SPECIFIC_EXTENSION DoSpecificExtension,
    ULONG eventID
    )
/*++

Routine Description:

    Event notification callback for panel switching

    NOTE  This routine is not pageable as it is called from DPC level by
    the ACPI BIOS.

--*/
{
    PVIDEO_ACPI_EVENT_CONTEXT pContext;

    //
    // There are some cases the BIOS send the notofication even before the device is opened
    //
    if (!DoSpecificExtension->DeviceOpened)
        return;

    if (InterlockedIncrement(&(DoSpecificExtension->AcpiVideoEventsOutstanding)) < 2) {

        // Queue work item
        pContext = ExAllocatePoolWithTag(NonPagedPool,
                                         sizeof(VIDEO_ACPI_EVENT_CONTEXT),
                                         VP_TAG);

        if (pContext && (eventID == 0x80 || eventID == 0x81 || eventID == 0x90 || eventID == 0x77))
        {
            pContext->DoSpecificExtension = DoSpecificExtension;
            pContext->EventID             = eventID;

            ExInitializeWorkItem(&(pContext->workItem),
                                 pVideoPortACPIEventHandler,
                                 pContext);

            ExQueueWorkItem(&(pContext->workItem), DelayedWorkQueue);
        }
    }
    else
    {
        // We're getting a Notify storm, and we already have a work item on the job.
        InterlockedDecrement(&(DoSpecificExtension->AcpiVideoEventsOutstanding));
    }

    return;
}


VOID
pVideoPortACPIEventHandler(
    PVIDEO_ACPI_EVENT_CONTEXT EventContext
    )
/*++

Routine Description:

    Event handler for panel switching

--*/
{
    UCHAR                outputBuffer[0x200 + sizeof(ACPI_EVAL_OUTPUT_BUFFER)];
    PCHILD_PDO_EXTENSION pChildDeviceExtension;
    PDEVICE_OBJECT       AttachedDeviceObject;
    PDEVICE_OBJECT       pChildPdos[10];
    ULONG                active, szChildIDs, i, AllowSwitch = 0, Switched = 0;
    PVIDEO_CHILD_STATE_CONFIGURATION pChildIDs;
    NTSTATUS             Status;
    BOOLEAN              bNewMonitor;
    VIDEO_WIN32K_CALLBACKS_PARAMS calloutParams;
    PFDO_EXTENSION FdoExtension;

    FdoExtension = EventContext->DoSpecificExtension->pFdoExtension;

    ASSERT (FdoExtension != NULL);
    
    pVideoDebugPrint((1, "pVideoPortACPIEventHandler: Event %08lx trigerred!\n",
                      EventContext->EventID));

    AttachedDeviceObject = FdoExtension->AttachedDeviceObject;

    if (FdoExtension->DevicePowerState != PowerDeviceD0)
    {
        EventContext->DoSpecificExtension->CachedEventID = EventContext->EventID;
        goto ExitACPIEventHandler;
    }
    else
    {
        EventContext->DoSpecificExtension->CachedEventID = 0;
    }

    //
    // Dock/Undock event handling
    //
    if (EventContext->EventID == 0x77)
    {
        calloutParams.CalloutType = VideoDisplaySwitchCallout;
        calloutParams.PhysDisp = EventContext->DoSpecificExtension->PhysDisp;
        calloutParams.Param = (ULONG_PTR)NULL;
        VpWin32kCallout(&calloutParams);

        goto ExitACPIEventHandler;
    }

    //
    // Disable BIOS notification
    //
    active = 2;
    pVideoPortACPIIoctl(AttachedDeviceObject,
                        (ULONG) ('SOD_'),
                        &active,
                        NULL,
                        0,
                        (PACPI_EVAL_OUTPUT_BUFFER)outputBuffer);

    if (EventContext->EventID == 0x90)
    {
        calloutParams.CalloutType = VideoWakeupCallout;

        VpWin32kCallout(&calloutParams);
    }
    else
    {
        szChildIDs = sizeof(VIDEO_CHILD_STATE_CONFIGURATION) + FdoExtension->ChildPdoNumber*sizeof(VIDEO_CHILD_STATE);
        pChildIDs = (PVIDEO_CHILD_STATE_CONFIGURATION)ExAllocatePoolWithTag(PagedPool,
                                                                  szChildIDs,
                                                                  VP_TAG);
        if (pChildIDs != NULL)
        {
            //
            // During switching, no PnP action is allowed
            //
            ACQUIRE_DEVICE_LOCK (FdoExtension);

            pChildIDs->Count = 0;

            for (pChildDeviceExtension = FdoExtension->ChildPdoList;
                 pChildDeviceExtension != NULL;
                 pChildDeviceExtension = pChildDeviceExtension->NextChild
                )
            {
                if ((!pChildDeviceExtension->bIsEnumerated) ||
                    pChildDeviceExtension->VideoChildDescriptor->Type != Monitor)
                {
                    continue;
                }

                pChildIDs->ChildStateArray[pChildIDs->Count].Id = pChildDeviceExtension->VideoChildDescriptor->UId;
                pChildIDs->ChildStateArray[pChildIDs->Count].State = 0;
                pChildPdos[pChildIDs->Count] = pChildDeviceExtension->ChildDeviceObject;

                Status = pVideoPortACPIIoctl(
                             IoGetAttachedDevice(pChildDeviceExtension->ChildDeviceObject),
                             (ULONG) ('SGD_'),
                             NULL,
                             NULL,
                             sizeof(ACPI_EVAL_OUTPUT_BUFFER)+0x10,
                             (PACPI_EVAL_OUTPUT_BUFFER)outputBuffer);

                if (NT_SUCCESS(Status))
                {
                    ASSERT(((PACPI_EVAL_OUTPUT_BUFFER)outputBuffer)->Argument[0].Type == ACPI_METHOD_ARGUMENT_INTEGER);
                    ASSERT(((PACPI_EVAL_OUTPUT_BUFFER)outputBuffer)->Argument[0].DataLength == sizeof(ULONG));

                    if (((PACPI_EVAL_OUTPUT_BUFFER)outputBuffer)->Argument[0].Argument)
                    {
                        pChildIDs->ChildStateArray[pChildIDs->Count].State = 1;
                    }
                }

                pChildIDs->Count++;
            }

            szChildIDs = sizeof(VIDEO_CHILD_STATE_CONFIGURATION) + pChildIDs->Count*sizeof(VIDEO_CHILD_STATE);

            //
            // Notify Miniport that display switching is about to happen.
            // Treat the switch is allowed by default.
            //

            AllowSwitch = 1;

            pVideoMiniDeviceIoControl(FdoExtension->FunctionalDeviceObject,
                                      IOCTL_VIDEO_VALIDATE_CHILD_STATE_CONFIGURATION,
                                      (PVOID)pChildIDs,
                                      szChildIDs,
                                      &AllowSwitch,
                                      sizeof(ULONG));

            //
            // If Miniport says it's OK to proceed
            //
            if (AllowSwitch != 0)
            {
                //
                // Check the Miniport do the switching for us
                //
                Status = pVideoMiniDeviceIoControl(FdoExtension->FunctionalDeviceObject,
                                                   IOCTL_VIDEO_SET_CHILD_STATE_CONFIGURATION,
                                                   (PVOID)pChildIDs,
                                                   szChildIDs,
                                                   NULL,
                                                   0);
                if (NT_SUCCESS(Status))
                {
                    pVideoDebugPrint((1, "VideoPort: Moniport does the switch!\n"));
                    Switched = 1;
                }
            }

            //
            // The last _DSS needs to commit the switching
            //
            if (pChildIDs->Count > 0)
            {
                pChildIDs->ChildStateArray[pChildIDs->Count-1].State |= 0x80000000;
            }
        
            for (i = 0; i < pChildIDs->Count; i++)
            {
                //
                // If Miniport doesn't like to proceed or it does the switching already, just notify BIOS to go to next _DGS state
                //
                // Found some bad BIOS(Toshiba).  They do switch anyway regardless of 0x40000000 bit.  This has extremely bad effect
                // on DualView
                //
                if (!AllowSwitch)
                    continue;
                if (Switched)
                {
                    pChildIDs->ChildStateArray[i].State |= 0x40000000;
                }
                pVideoPortACPIIoctl(IoGetAttachedDevice(pChildPdos[i]),
                                    (ULONG) ('SSD_'),
                                    &pChildIDs->ChildStateArray[i].State,
                                    NULL,
                                    0,
                                    (PACPI_EVAL_OUTPUT_BUFFER)outputBuffer);
            }

            RELEASE_DEVICE_LOCK (FdoExtension);

            ExFreePool(pChildIDs);
        }

        //
        // On switching displays, call GDI / USER to tell the device to rebuild mode list
        // and change current mode if neccesary
        //

        pVideoDebugPrint((0, "VideoPrt.sys: Display switching occured - calling GDI to rebuild mode table.\n"));

        calloutParams.CalloutType = VideoDisplaySwitchCallout;
        calloutParams.PhysDisp = (AllowSwitch) ? EventContext->DoSpecificExtension->PhysDisp : NULL;

        //
        // On Monitor changing, we receive Notify(81)
        // On waking up from hibernation, we receive Notify(90)
        // We also make IoInvalidateDeviceRelation happen inside Callout routine
        //

        bNewMonitor = (EventContext->EventID == 0x81);

        if (pCheckDeviceRelations(FdoExtension, bNewMonitor) )
        {
            calloutParams.Param = (ULONG_PTR)FdoExtension->PhysicalDeviceObject;
        }
        else
        {
            calloutParams.Param = (ULONG_PTR)NULL;
        }

        VpWin32kCallout(&calloutParams);
    }

ExitACPIEventHandler:

    //
    // Reenable BIOS notification 
    //

    active = 0;
    pVideoPortACPIIoctl(AttachedDeviceObject,
                        (ULONG) ('SOD_'),
                        &active,
                        NULL,
                        0,
                        (PACPI_EVAL_OUTPUT_BUFFER)outputBuffer);

    InterlockedDecrement(&(EventContext->DoSpecificExtension->AcpiVideoEventsOutstanding));

    //
    // This also ends up freeing the work item as it's embedded in the context.
    //

    ExFreePool(EventContext);

    return;
}




NTSTATUS
pVideoPortACPIIoctl(
    IN  PDEVICE_OBJECT           DeviceObject,
    IN  ULONG                    MethodName,
    IN  PULONG                   InputParam1,
    IN  PULONG                   InputParam2,
    IN  ULONG                    OutputBufferSize,
    IN  PACPI_EVAL_OUTPUT_BUFFER pOutputBuffer
    )
/*++

Routine Description:

    Called to send a request to the DeviceObject

Arguments:

    DeviceObject    - The request is sent to this device object
    MethodName      - Name of the method to be run in ACPI space
    pArgumets       - Pointer that will receive the address of the ACPI data

Return Value:

    NT Status of the operation

--*/
{
    UCHAR                           buffer[sizeof(ACPI_EVAL_INPUT_BUFFER_COMPLEX) +
                                           sizeof(ACPI_METHOD_ARGUMENT)];
    PACPI_EVAL_INPUT_BUFFER_COMPLEX pInputBuffer;
    ULONG                           size;
    IO_STATUS_BLOCK                 ioBlock;
    KEVENT                          event;
    NTSTATUS                        status;
    PIRP                            irp;

    pVideoDebugPrint((2, "Call ACPI method %c%c%c%c!\n",
                      *((PUCHAR)&MethodName),   *((PUCHAR)&MethodName+1),
                      *((PUCHAR)&MethodName+2), *((PUCHAR)&MethodName+3) ));

    pInputBuffer = (PACPI_EVAL_INPUT_BUFFER_COMPLEX) buffer;

    pInputBuffer->MethodNameAsUlong = MethodName;

    if (InputParam1 == NULL)
    {
        size = sizeof(ACPI_EVAL_INPUT_BUFFER);

        pInputBuffer->Signature = ACPI_EVAL_INPUT_BUFFER_SIGNATURE;
    }
    else
    {
        size = sizeof(ACPI_EVAL_INPUT_BUFFER_COMPLEX);

        pInputBuffer->Signature       = ACPI_EVAL_INPUT_BUFFER_COMPLEX_SIGNATURE;
        pInputBuffer->Size            = sizeof(ACPI_METHOD_ARGUMENT);
        pInputBuffer->ArgumentCount   = 1;

        pInputBuffer->Argument[0].Type       = ACPI_METHOD_ARGUMENT_INTEGER;
        pInputBuffer->Argument[0].DataLength = sizeof(ULONG);
        pInputBuffer->Argument[0].Argument   = *InputParam1;
    }

    if (InputParam2)
    {
        size = sizeof(ACPI_EVAL_INPUT_BUFFER_COMPLEX) +
               sizeof(ACPI_METHOD_ARGUMENT);

        pInputBuffer->Size            = 2 * sizeof(ACPI_METHOD_ARGUMENT);
        pInputBuffer->ArgumentCount   = 2;

        pInputBuffer->Argument[1].Type       = ACPI_METHOD_ARGUMENT_INTEGER;
        pInputBuffer->Argument[1].DataLength = sizeof(ULONG);
        pInputBuffer->Argument[1].Argument   = *InputParam2;
    }

    //
    // Initialize an event to wait on
    //

    KeInitializeEvent(&event, SynchronizationEvent, FALSE);

    //
    // Build the request
    //

    irp = IoBuildDeviceIoControlRequest(IOCTL_ACPI_EVAL_METHOD,
                                        DeviceObject,
                                        pInputBuffer,
                                        size,
                                        pOutputBuffer,
                                        OutputBufferSize,
                                        FALSE,
                                        &event,
                                        &ioBlock);

    if (!irp)
    {
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    //
    // Pass request to DeviceObject, always wait for completion routine
    //

    status = IoCallDriver(DeviceObject, irp);

    if (status == STATUS_PENDING)
    {
        //
        // Wait for the irp to be completed, then grab the real status code
        //

        KeWaitForSingleObject(&event,
                              Executive,
                              KernelMode,
                              FALSE,
                              NULL);

        status = ioBlock.Status;
    }

    //
    // Sanity check the data
    //

    if (NT_SUCCESS(status) && OutputBufferSize != 0)
    {
        if (((pOutputBuffer)->Signature != ACPI_EVAL_OUTPUT_BUFFER_SIGNATURE) ||
            ((pOutputBuffer)->Count == 0))
        {
            status = STATUS_ACPI_INVALID_DATA;
        }
    }

    return status;
}

NTSTATUS
pVideoMiniDeviceIoControl(
    IN PDEVICE_OBJECT DeviceObject,
    IN ULONG dwIoControlCode,
    IN PVOID lpInBuffer,
    IN ULONG nInBufferSize,
    OUT PVOID lpOutBuffer,
    IN ULONG nOutBufferSize
    )
{
    PFDO_EXTENSION combinedExtension;
    PFDO_EXTENSION fdoExtension;

    VIDEO_REQUEST_PACKET vrp;
    STATUS_BLOCK statusBlock;

    combinedExtension = DeviceObject->DeviceExtension;
    fdoExtension = combinedExtension->pFdoExtension;

    statusBlock.Status = ERROR_INVALID_FUNCTION;

    vrp.IoControlCode      = dwIoControlCode;
    vrp.StatusBlock        = &statusBlock;
    vrp.InputBuffer        = lpInBuffer;
    vrp.InputBufferLength  = nInBufferSize;
    vrp.OutputBuffer       = lpOutBuffer;
    vrp.OutputBufferLength = nOutBufferSize;

    //
    // Send the request to the miniport directly.
    //

    fdoExtension->HwStartIO(combinedExtension->HwDeviceExtension, &vrp);

    pVideoPortMapToNtStatus(&statusBlock);

    return (statusBlock.Status);
}


NTSTATUS
VpQueryBacklightLevels(
    IN  PDEVICE_OBJECT DeviceObject,
    OUT PUCHAR ucBacklightLevels,
    OUT PULONG pulNumberOfLevelsSupported
    )

/*++

Routine Description:

    This function will query the list of levels supported by _BCL.
    
Arguments:

    DeviceObject: The ACPI device object attached to our LCD device.
    
    ucBacklightLevels: The list of backlight levels supported by the ACPI BIOS.

    pulNumberOfLevelsSupported: This is the number of actual levels the ACPI BIOS supports,

Returns:

    NO_ERROR if it succeeds
    Various error codes if it fails
    
--*/

{
    PACPI_EVAL_OUTPUT_BUFFER Buffer = NULL;
    PACPI_METHOD_ARGUMENT Argument = NULL;
    ULONG Granularity = 80;
    ULONG BufferMaxSize = 4096;
    NTSTATUS Status = STATUS_UNSUCCESSFUL;
    ULONG Level = 100;
    ULONG count = 0;
    PBACKLIGHT_STATUS pVpBacklightStatus = &VpBacklightStatus;
    PUCHAR ucLevels = ucBacklightLevels;
    

    PAGED_CODE();
    ASSERT (DeviceObject != NULL);

    //
    // Get the list of brightness levels supported
    //

    do {

        Buffer = (PACPI_EVAL_OUTPUT_BUFFER)ExAllocatePoolWithTag(
            PagedPool,
            Granularity,
            VP_TAG);
    
        if (Buffer == NULL) {
            
            pVideoDebugPrint((Warn, 
                "VIDEOPRT: VpQueryBacklightLevels: Memory allocation failed."));
            Status = STATUS_INSUFFICIENT_RESOURCES;
            break;
        }

        RtlZeroMemory(Buffer, Granularity);   

        Status = pVideoPortACPIIoctl(
            DeviceObject,
            (ULONG) ('LCB_'),
            NULL,
            NULL,
            Granularity,
            Buffer);
    
        if (Status == STATUS_BUFFER_OVERFLOW) {

            ExFreePool(Buffer);
            Buffer = NULL;
            Granularity <<= 1;
            
            if (Granularity > BufferMaxSize) {

                pVideoDebugPrint((Warn, 
                    "VIDEOPRT: _BCL failed. Expected buffer is too big."));
                Status = STATUS_ACPI_INVALID_DATA;
                break;
            }
        
        } else if (!NT_SUCCESS(Status)) {
            
            pVideoDebugPrint((Warn, 
                "VIDEOPRT: _BCL failed. Status = 0x%x\n", Status));
        
        } else {

            pVideoDebugPrint((Trace, "VIDEOPRT: _BCL succeeded.\n"));
        }
    
    } while (Status == STATUS_BUFFER_OVERFLOW);

    if ((Buffer == NULL) || (!NT_SUCCESS(Status))) 
        goto Fallout;

    //
    // We should have 2+ levels.  If we have only have 2 levels, the
    //  BIOS only reports the recommended AC/DC values.  This function
    //  is therefore only useful if we have 3+ levels reported by the
    //  ACPI BIOS.
    //

    if (Buffer->Count < 3) {
        pVideoDebugPrint((Warn, 
            "VIDEOPRT: _BCL returned an fewer than three arguments."));
        Status = STATUS_ACPI_INVALID_DATA;
        goto Fallout;
    }

    //
    // Save off BIOS "default" AC value for initial settings.
    //

    Argument = Buffer->Argument;
    
    if (Argument->Type != ACPI_METHOD_ARGUMENT_INTEGER) {
        pVideoDebugPrint((Warn, 
            "VIDEOPRT: _BCL returned an invalid argument."));
        Status = STATUS_ACPI_INVALID_DATA;
        goto Fallout;
    }

    Level = Argument->Argument;
    pVpBacklightStatus->bBIOSDefaultACKnown = TRUE;
    pVpBacklightStatus->ucBIOSDefaultAC = (unsigned char) Level;

    //
    // Save off BIOS "default" DC value for initial settings.
    //
    
    Argument = ACPI_METHOD_NEXT_ARGUMENT(Argument);
    
    if (Argument->Type != ACPI_METHOD_ARGUMENT_INTEGER) {
        pVideoDebugPrint((Warn, 
            "VIDEOPRT: _BCL returned an invalid argument."));
        Status = STATUS_ACPI_INVALID_DATA;
        goto Fallout;
    }

    //
    // Full power level should be greater than the battery level
    //

    ASSERT (Level >= Argument->Argument); 
    
    Level = Argument->Argument;
    pVpBacklightStatus->bBIOSDefaultDCKnown = TRUE;
    pVpBacklightStatus->ucBIOSDefaultDC = (unsigned char) Level;

    //
    // Run through the list of supported modes that follow the AC/DC
    //  values and return them to the caller.
    //

    *pulNumberOfLevelsSupported = 0;
    for (count = 0; count < (Buffer->Count - 2); count++) {

        //
        // Proceed to the next argument
        //

        Argument = ACPI_METHOD_NEXT_ARGUMENT(Argument);

        if (Argument->Type != ACPI_METHOD_ARGUMENT_INTEGER) {
            pVideoDebugPrint((Warn, 
                "VIDEOPRT: _BCL returned an invalid argument."));
            Status = STATUS_ACPI_INVALID_DATA;
            goto Fallout;
        }

        //
        // Save off the argument in our array of levels.
        //

        Level = Argument->Argument;
        *ucLevels++ = (unsigned char) Level;
        *pulNumberOfLevelsSupported += 1;
        
    }

    Status = NO_ERROR;

Fallout:

    if (Buffer != NULL) {
        ExFreePool(Buffer);
    }

    return Status;

}

NTSTATUS
VpSetLCDPowerUsage(
    IN PDEVICE_OBJECT DeviceObject,
    IN BOOLEAN FullPower
    )

/*++

Routine Description:

    It changes the brightness level, based on the value of FullPower.
    
Arguments:

    DeviceObject: the ACPI device object attached to our LCD device.
    
    FullPower: if TRUE, it sets the brightness level to FullPower level  
               if FALSE, it sets the brightness level to Battery level  
Returns:

    NO_ERROR if it succeeds
    Various error codes if it fails
    
--*/

{
    PACPI_EVAL_OUTPUT_BUFFER Buffer = NULL;
    PACPI_METHOD_ARGUMENT Argument = NULL;
    ULONG Granularity = 80;
    ULONG BufferMaxSize = 4096;
    NTSTATUS Status = STATUS_UNSUCCESSFUL;
    ULONG Level = 100;
    PBACKLIGHT_STATUS pVpBacklightStatus = &VpBacklightStatus;

    PAGED_CODE();
    ASSERT (DeviceObject != NULL);

    //
    // Track whether we are running on AC or DC
    //

    VpRunningOnAC = FullPower;

    //
    // If we are using the new API for backlight control, we don't need to query the 
    //  BIOS with _BCL.  We will set the AC or DC level as appropriate with _BCM.
    //

    if (pVpBacklightStatus->bNewAPISupported) {

        if (VpRunningOnAC) {

            Level = (ULONG) pVpBacklightStatus->ucACBrightness;
        }
        else {

            Level = (ULONG) pVpBacklightStatus->ucDCBrightness;
        }

        Status = pVideoPortACPIIoctl(
            DeviceObject,
            (ULONG) ('MCB_'),
            &Level,
            NULL,
            0,
            (PACPI_EVAL_OUTPUT_BUFFER)NULL);

        return Status;

    }

    //
    // Get the list of brightness levels supported
    //

    do {

        Buffer = (PACPI_EVAL_OUTPUT_BUFFER)ExAllocatePoolWithTag(
            PagedPool,
            Granularity,
            VP_TAG);
    
        if (Buffer == NULL) {
            
            pVideoDebugPrint((Warn, 
                "VIDEOPRT: VpSetLCDPowerUsage: Memory allocation failed."));
            Status = STATUS_INSUFFICIENT_RESOURCES;
            break;
        }

        RtlZeroMemory(Buffer, Granularity);   

        Status = pVideoPortACPIIoctl(
            DeviceObject,
            (ULONG) ('LCB_'),
            NULL,
            NULL,
            Granularity,
            Buffer);
    
        if (Status == STATUS_BUFFER_OVERFLOW) {

            ExFreePool(Buffer);
            Buffer = NULL;
            Granularity <<= 1;
            
            if (Granularity > BufferMaxSize) {

                pVideoDebugPrint((Warn, 
                    "VIDEOPRT: _BCL failed. Expected buffer is too big."));
                Status = STATUS_ACPI_INVALID_DATA;
                break;
            }
        
        } else if (!NT_SUCCESS(Status)) {
            
            pVideoDebugPrint((Warn, 
                "VIDEOPRT: _BCL failed. Status = 0x%x\n", Status));
        
        } else {

            pVideoDebugPrint((Trace, "VIDEOPRT: _BCL succeeded.\n"));
        }
    
    } while (Status == STATUS_BUFFER_OVERFLOW);

    if ((Buffer == NULL) || (!NT_SUCCESS(Status))) 
        goto Fallout;

    //
    // Now try to set the state.
    //

    if (Buffer->Count < 2) {
        pVideoDebugPrint((Warn, 
            "VIDEOPRT: _BCL returned an invalid number of arguments."));
        Status = STATUS_ACPI_INVALID_DATA;
        goto Fallout;
    }
        
    Argument = Buffer->Argument;
    
    if (Argument->Type != ACPI_METHOD_ARGUMENT_INTEGER) {
        pVideoDebugPrint((Warn, 
            "VIDEOPRT: _BCL returned an invalid argument."));
        Status = STATUS_ACPI_INVALID_DATA;
        goto Fallout;
    }

    Level = Argument->Argument;

    if (!FullPower) {
    
        Argument = ACPI_METHOD_NEXT_ARGUMENT(Argument);
        
        if (Argument->Type != ACPI_METHOD_ARGUMENT_INTEGER) {
            pVideoDebugPrint((Warn, 
                "VIDEOPRT: _BCL returned an invalid argument."));
            Status = STATUS_ACPI_INVALID_DATA;
            goto Fallout;
        }

        //
        // Full power level should be greater than the battery level
        //

        ASSERT (Level >= Argument->Argument); 
        
        Level = Argument->Argument;
    }

    Status = pVideoPortACPIIoctl(
        DeviceObject,
        (ULONG) ('MCB_'),
        &Level,
        NULL,
        0,
        (PACPI_EVAL_OUTPUT_BUFFER)NULL);

    if (!NT_SUCCESS(Status)) {

        pVideoDebugPrint((Warn, 
            "VIDEOPRT: _BCM failed. Status = 0x%x\n", Status));
    
    } else {

        pVideoDebugPrint((Trace, "VIDEOPRT: _BCM succeeded.\n"));
    }
    
Fallout:

    if (Buffer != NULL) {
        ExFreePool(Buffer);
    }

    return Status;
}

VOID
VpPowerStateCallback(
    IN PVOID CallbackContext,
    IN PVOID Argument1,
    IN PVOID Argument2
    )
    
/*++

Routine Description:

    This is the callback routine that is called when the power state changes.
    
Arguments:

    CallbackContext: NULL
    
    Argument1: event code
    
    Argument2: if Argument1 is PO_CB_AC_STATUS, Argument2 contains TRUE if 
               the current power source is AC and FALSE otherwise. 

Note:

    This function can be called at DISPATCH_LEVEL
    
--*/

{
    PPOWER_STATE_WORK_ITEM PowerStateWorkItem = NULL;

    ASSERT (CallbackContext == NULL);

    switch ((ULONG_PTR)Argument1) {

    case PO_CB_LID_SWITCH_STATE:
    case PO_CB_AC_STATUS:

        PowerStateWorkItem = 
            (PPOWER_STATE_WORK_ITEM)ExAllocatePoolWithTag(
                NonPagedPool, 
                sizeof(POWER_STATE_WORK_ITEM), 
                VP_TAG);

        if (PowerStateWorkItem != NULL) {

            PowerStateWorkItem->Argument1 = Argument1;
            PowerStateWorkItem->Argument2 = Argument2;

            ExInitializeWorkItem(
                &PowerStateWorkItem->WorkItem,
                VpDelayedPowerStateCallback,
                PowerStateWorkItem);

            ExQueueWorkItem(
                &PowerStateWorkItem->WorkItem,
                DelayedWorkQueue);

        } else {

            pVideoDebugPrint((Warn, 
                "VIDEOPRT: VpPowerStateCallback: Memory allocation failed."));
        }

        break;

    default:
        
        //
        // Ignore all other cases
        //

        break;
    }
}


VOID
VpDelayedPowerStateCallback(
    IN PVOID Context
    )

/*++

Routine Description:

    VpPowerStateCallback queues this work item in order to handle 
    PowerState changes at PASSIVE_LEVEL.
    
Arguments:

    Context: pointer to POWER_STATE_WORK_ITEM 

--*/

{
    PPOWER_STATE_WORK_ITEM PowerStateWorkItem = 
        (PPOWER_STATE_WORK_ITEM)Context;
    PDEVICE_OBJECT AttachedDevice = NULL;
    NTSTATUS status;
    POWER_STATE powerState;
    PCHILD_PDO_EXTENSION pdoExtension;

    PAGED_CODE();
    ASSERT (PowerStateWorkItem != NULL);

    KeWaitForSingleObject(&LCDPanelMutex,
        Executive,
        KernelMode,
        FALSE,
        (PTIME)NULL);

    if (LCDPanelDevice == NULL) {
        goto Fallout;
    }

    switch ((ULONG_PTR)PowerStateWorkItem->Argument1) {

    case PO_CB_AC_STATUS:

        AttachedDevice = IoGetAttachedDeviceReference(LCDPanelDevice);
        
        VpSetLCDPowerUsage(
            AttachedDevice, 
            (PowerStateWorkItem->Argument2 != 0));
        
        ObDereferenceObject(AttachedDevice);
        
        break;

    case PO_CB_LID_SWITCH_STATE:

        pdoExtension = LCDPanelDevice->DeviceExtension;

        if ((ULONG_PTR)PowerStateWorkItem->Argument2 == 0) {
        
            //
            // The lid is closed. Put the LCD Panel into D3 and override
            // any future power requests to the panel.
            //

            pdoExtension->PowerOverride = TRUE;
            powerState.DeviceState = PowerDeviceD3;
            pVideoDebugPrint((Trace, "VIDEOPRT: LCD Panel Closed.\n"));

        } else if ((ULONG_PTR)PowerStateWorkItem->Argument2 == 1) {

            pdoExtension->PowerOverride = FALSE;
            powerState.DeviceState = PowerDeviceD0;
            pVideoDebugPrint((Trace, "VIDEOPRT: LCD Panel Open.\n"));

        } else {

            pVideoDebugPrint((Error, "VIDEOPRT: Unknown LCD lid close event recieved.\n"));
            ASSERT(FALSE);
            goto Fallout;
        }

        if (pdoExtension->pFdoExtension->DevicePowerState == PowerDeviceD0)
        {

            if (VpLidCloseSetPower == TRUE)
            {

                status = PoRequestPowerIrp (LCDPanelDevice,
                IRP_MN_SET_POWER,
                powerState,
                pVideoPortPowerIrpComplete,
                (PVOID)NULL,
                NULL);

                if (status != STATUS_PENDING) {
                    pVideoDebugPrint((Error, "VIDEOPRT: Could not send power IRP to toggle panel\n"));
                }
            }
        }
    
        break;

    default:
        
        pVideoDebugPrint((Warn, 
            "VIDEOPRT: Unexpected PowerState event recieved.\n"));
        
        ASSERT(FALSE);
        break;
    }

Fallout:

    KeReleaseMutex(&LCDPanelMutex, FALSE);

    ExFreePool(Context);
}


VOID
VpRegisterPowerStateCallback(
    VOID
    )

/*++

Routine Description:

    This routine registers the PowerState callback routine.
    
--*/

{
    OBJECT_ATTRIBUTES ObjectAttributes;
    UNICODE_STRING CallbackName;
    PCALLBACK_OBJECT CallbackObject;
    NTSTATUS Status;

    PAGED_CODE();

    RtlInitUnicodeString(&CallbackName, L"\\Callback\\PowerState");

    InitializeObjectAttributes(
        &ObjectAttributes,
        &CallbackName,
        OBJ_CASE_INSENSITIVE | OBJ_PERMANENT,  
        NULL,
        NULL
        );

    Status = ExCreateCallback(
        &CallbackObject,
        &ObjectAttributes,
        FALSE,
        TRUE
        );

    if (NT_SUCCESS(Status)) {
        
        PowerStateCallbackHandle = ExRegisterCallback(
            CallbackObject,
            VpPowerStateCallback,
            NULL
            );

        if (PowerStateCallbackHandle == NULL) {

            pVideoDebugPrint((Warn, 
                "VIDEOPRT: Could not register VpPowerStateCallback.\n"));
        
        } else {

            pVideoDebugPrint((Trace, 
                "VIDEOPRT: VpPowerStateCallback registered. \n"));
        }
    
    } else {

        pVideoDebugPrint((Warn, 
            "VIDEOPRT: Could not get the PowerState callback object.\n"));
    }
}


VOID
VpRegisterLCDCallbacks(
    VOID
    )

/*++

Routine Description:

    This routine registers a callback with the system so that we can
    be notified of power state changes.
        
--*/

{
    PAGED_CODE();

    //
    // Register power state callback. This works for the lid as well.
    //

    if (PowerStateCallbackHandle == NULL) {
        VpRegisterPowerStateCallback();
    }
    
}


VOID
VpUnregisterLCDCallbacks(
    VOID
    )

/*++

Routine Description:

    This routine unregisters the callbacks previously registered by 
    VpRegisterLCDCallbacks

Arguments:

    None. 
    
Note:    
    
    The global PowerStateCallbackHandle acts as an implicit parameter.

--*/

{
    PAGED_CODE();

    //
    // Unregister power state callback
    //

    if (PowerStateCallbackHandle != NULL) {
        
        ExUnregisterCallback(PowerStateCallbackHandle);
        PowerStateCallbackHandle = NULL;
    }
}