/*
* COPYRIGHT:       See COPYING in the top level directory
* PROJECT:         ReactOS Kernel Streaming
* FILE:            drivers/wdm/audio/hdaudbus/fdo.cpp
* PURPOSE:         HDA Driver Entry
* PROGRAMMER:      Johannes Anderwald
*/

#include "hdaudbus.h"

BOOLEAN
NTAPI
HDA_InterruptService(
    IN PKINTERRUPT  Interrupt,
    IN PVOID  ServiceContext)
{
    PDEVICE_OBJECT DeviceObject;
    PHDA_FDO_DEVICE_EXTENSION DeviceExtension;
    ULONG InterruptStatus;
    UCHAR RirbStatus, CorbStatus;

    /* get device extension */
    DeviceObject = static_cast<PDEVICE_OBJECT>(ServiceContext);
    DeviceExtension = static_cast<PHDA_FDO_DEVICE_EXTENSION>(DeviceObject->DeviceExtension);
    ASSERT(DeviceExtension->IsFDO == TRUE);

    // Check if this interrupt is ours
    InterruptStatus = READ_REGISTER_ULONG((PULONG)(DeviceExtension->RegBase + HDAC_INTR_STATUS));

    DPRINT("HDA_InterruptService %lx\n", InterruptStatus);
    if ((InterruptStatus & INTR_STATUS_GLOBAL) == 0)
        return FALSE;

    // Controller or stream related?
    if (InterruptStatus & INTR_STATUS_CONTROLLER) {
        RirbStatus = READ_REGISTER_UCHAR(DeviceExtension->RegBase + HDAC_RIRB_STATUS);
        CorbStatus = READ_REGISTER_UCHAR(DeviceExtension->RegBase + HDAC_CORB_STATUS);

        // Check for incoming responses
        if (RirbStatus) {
            WRITE_REGISTER_UCHAR(DeviceExtension->RegBase + HDAC_RIRB_STATUS, RirbStatus);

            if (DeviceExtension->RirbLength == 0)
            {
                /* HACK: spurious interrupt */
                return FALSE;
            }

            if ((RirbStatus & RIRB_STATUS_RESPONSE) != 0) {
                IoRequestDpc(DeviceObject, NULL, NULL);
            }

            if ((RirbStatus & RIRB_STATUS_OVERRUN) != 0)
                DPRINT1("hda: RIRB Overflow\n");
        }

        // Check for sending errors
        if (CorbStatus) {
            WRITE_REGISTER_UCHAR(DeviceExtension->RegBase + HDAC_CORB_STATUS, CorbStatus);

            if ((CorbStatus & CORB_STATUS_MEMORY_ERROR) != 0)
                DPRINT1("hda: CORB Memory Error!\n");
        }
    }
#if 0
    if ((intrStatus & INTR_STATUS_STREAM_MASK) != 0) {
        for (uint32 index = 0; index < HDA_MAX_STREAMS; index++) {
            if ((intrStatus & (1 << index)) != 0) {
                if (controller->streams[index]) {
                    if (stream_handle_interrupt(controller,
                        controller->streams[index], index)) {
                        handled = B_INVOKE_SCHEDULER;
                    }
                }
                else {
                    dprintf("hda: Stream interrupt for unconfigured stream "
                        "%ld!\n", index);
                }
            }
        }
    }
#endif
    return TRUE;
}

VOID
NTAPI
HDA_DpcForIsr(
    _In_ PKDPC Dpc,
    _In_opt_ PDEVICE_OBJECT DeviceObject,
    _Inout_ PIRP Irp,
    _In_opt_ PVOID Context)
{
    PHDA_FDO_DEVICE_EXTENSION DeviceExtension;
    ULONG Response, ResponseFlags, Cad;
    USHORT WritePos;
    PHDA_CODEC_ENTRY Codec;

    /* get device extension */
    DeviceExtension = static_cast<PHDA_FDO_DEVICE_EXTENSION>(DeviceObject->DeviceExtension);
    ASSERT(DeviceExtension->IsFDO == TRUE);

    WritePos = (READ_REGISTER_USHORT((PUSHORT)(DeviceExtension->RegBase + HDAC_RIRB_WRITE_POS)) + 1) % DeviceExtension->RirbLength;

    for (; DeviceExtension->RirbReadPos != WritePos; DeviceExtension->RirbReadPos = (DeviceExtension->RirbReadPos + 1) % DeviceExtension->RirbLength)
    {
        Response = DeviceExtension->RirbBase[DeviceExtension->RirbReadPos].response;
        ResponseFlags = DeviceExtension->RirbBase[DeviceExtension->RirbReadPos].flags;
        Cad = ResponseFlags & RESPONSE_FLAGS_CODEC_MASK;
        DPRINT1("Response %lx ResponseFlags %lx Cad %lx\n", Response, ResponseFlags, Cad);

        /* get codec */
        Codec = DeviceExtension->Codecs[Cad];
        if (Codec == NULL)
        {
            DPRINT1("hda: response for unknown codec %x Response %x ResponseFlags %x\n", Cad, Response, ResponseFlags);
            continue;
        }

        /* check response count */
        if (Codec->ResponseCount >= MAX_CODEC_RESPONSES)
        {
            DPRINT1("too many responses for codec %x Response %x ResponseFlags %x\n", Cad, Response, ResponseFlags);
            continue;
        }

        // FIXME handle unsolicited responses
        ASSERT((ResponseFlags & RESPONSE_FLAGS_UNSOLICITED) == 0);

        /* store response */
        Codec->Responses[Codec->ResponseCount] = Response;
        Codec->ResponseCount++;
        KeReleaseSemaphore(&Codec->ResponseSemaphore, IO_NO_INCREMENT, 1, FALSE);
    }
}


NTSTATUS
HDA_SendVerbs(
    IN PDEVICE_OBJECT DeviceObject,
    IN PHDA_CODEC_ENTRY Codec,
    IN PULONG Verbs,
    OUT PULONG Responses,
    IN ULONG Count)
{
    PHDA_FDO_DEVICE_EXTENSION DeviceExtension;
    ULONG Sent = 0, ReadPosition, WritePosition, Queued;

    /* get device extension */
    DeviceExtension = (PHDA_FDO_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
    ASSERT(DeviceExtension->IsFDO);

    /* reset response count */
    Codec->ResponseCount = 0;

    while (Sent < Count) {
        ReadPosition = READ_REGISTER_USHORT((PUSHORT)(DeviceExtension->RegBase + HDAC_CORB_READ_POS));

        Queued = 0;

        while (Sent < Count) {
            WritePosition = (DeviceExtension->CorbWritePos + 1) % DeviceExtension->CorbLength;

            if (WritePosition == ReadPosition) {
                // There is no space left in the ring buffer; execute the
                // queued commands and wait until
                break;
            }

            DeviceExtension->CorbBase[WritePosition] = Verbs[Sent++];
            DeviceExtension->CorbWritePos = WritePosition;
            Queued++;
        }

        WRITE_REGISTER_USHORT((PUSHORT)(DeviceExtension->RegBase + HDAC_CORB_WRITE_POS), DeviceExtension->CorbWritePos);
    }

    while (Queued--)
    {
        LARGE_INTEGER Timeout;
        Timeout.QuadPart = -1000LL * 10000; // 1 sec

        NTSTATUS waitStatus = KeWaitForSingleObject(&Codec->ResponseSemaphore,
                                                    Executive,
                                                    KernelMode,
                                                    FALSE,
                                                    &Timeout);

        if (waitStatus == STATUS_TIMEOUT)
        {
            DPRINT1("HDA_SendVerbs: timeout! Queued: %u\n", Queued);
            return STATUS_INVALID_DEVICE_REQUEST;
        }
    }

    if (Responses != NULL) {
        memcpy(Responses, Codec->Responses, Codec->ResponseCount * sizeof(ULONG));
    }

    return STATUS_SUCCESS;
}

NTSTATUS
HDA_InitCodec(
    IN PDEVICE_OBJECT DeviceObject,
    IN ULONG codecAddress)
{
    PHDA_CODEC_ENTRY Entry;
    ULONG verbs[3];
    PHDA_FDO_DEVICE_EXTENSION DeviceExtension;
    CODEC_RESPONSE Response;
    ULONG NodeId, GroupType;
    NTSTATUS Status;
    PHDA_CODEC_AUDIO_GROUP AudioGroup;
    PHDA_PDO_DEVICE_EXTENSION ChildDeviceExtension;

    /* lets allocate the entry */
    Entry = (PHDA_CODEC_ENTRY)AllocateItem(NonPagedPool, sizeof(HDA_CODEC_ENTRY));
    if (!Entry)
    {
        DPRINT1("hda: failed to allocate memory\n");
        return STATUS_UNSUCCESSFUL;
    }

    /* init codec */
    Entry->Addr = codecAddress;
    KeInitializeSemaphore(&Entry->ResponseSemaphore, 0, MAX_CODEC_RESPONSES);

    /* get device extension */
    DeviceExtension = (PHDA_FDO_DEVICE_EXTENSION)DeviceObject->DeviceExtension;

    /* store codec */
    DeviceExtension->Codecs[codecAddress] = Entry;

    verbs[0] = MAKE_VERB(codecAddress, 0, VID_GET_PARAMETER, PID_VENDOR_ID);
    verbs[1] = MAKE_VERB(codecAddress, 0, VID_GET_PARAMETER, PID_REVISION_ID);
    verbs[2] = MAKE_VERB(codecAddress, 0, VID_GET_PARAMETER, PID_SUB_NODE_COUNT);

    /* get basic info */
    Status = HDA_SendVerbs(DeviceObject, Entry, verbs, (PULONG)&Response, 3);
    if (!NT_SUCCESS(Status))
    {
        FreeItem(Entry);
        DeviceExtension->Codecs[codecAddress] = NULL;
        return Status;
    }

    /* store codec details */
    Entry->Major = Response.major;
    Entry->Minor = Response.minor;
    Entry->ProductId = Response.device;
    Entry->Revision = Response.revision;
    Entry->Stepping = Response.stepping;
    Entry->VendorId = Response.vendor;

    DPRINT1("hda Codec %ld Vendor: %04lx Product: %04lx, Revision: %lu.%lu.%lu.%lu NodeStart %u NodeCount %u \n", codecAddress, Response.vendor,
        Response.device, Response.major, Response.minor, Response.revision, Response.stepping, Response.start, Response.count);

    for (NodeId = Response.start; NodeId < Response.start + Response.count; NodeId++) {

        /* get function type */
        verbs[0] = MAKE_VERB(codecAddress, NodeId, VID_GET_PARAMETER, PID_FUNCTION_GROUP_TYPE);

        Status = HDA_SendVerbs(DeviceObject, Entry, verbs, &GroupType, 1);
        DPRINT1("Status %x NodeId %u GroupType %x\n", Status, NodeId, GroupType);


        if (NT_SUCCESS(Status) &&
            (GroupType & FUNCTION_GROUP_NODETYPE_MASK) == FUNCTION_GROUP_NODETYPE_AUDIO)
        {
            if (Entry->AudioGroupCount >= HDA_MAX_AUDIO_GROUPS)
            {
                DPRINT1("Too many audio groups in node %u. Skipping.\n", NodeId);
                break;
            }

            AudioGroup = (PHDA_CODEC_AUDIO_GROUP)AllocateItem(NonPagedPool, sizeof(HDA_CODEC_AUDIO_GROUP));
            if (!AudioGroup)
            {
                DPRINT1("hda: insufficient memory\n");
                return STATUS_INSUFFICIENT_RESOURCES;
            }

            /* init audio group */
            AudioGroup->NodeId = NodeId;
            AudioGroup->FunctionGroup = FUNCTION_GROUP_NODETYPE_AUDIO;

            // Found an Audio Function Group!
            DPRINT1("NodeId %x found an audio function group!\n", NodeId);

            Status = IoCreateDevice(DeviceObject->DriverObject, sizeof(HDA_PDO_DEVICE_EXTENSION), NULL, FILE_DEVICE_SOUND, FILE_AUTOGENERATED_DEVICE_NAME, FALSE, &AudioGroup->ChildPDO);
            if (!NT_SUCCESS(Status))
            {
                FreeItem(AudioGroup);
                DPRINT1("hda failed to create device object %x\n", Status);
                return Status;
            }

            /* init child pdo*/
            ChildDeviceExtension = (PHDA_PDO_DEVICE_EXTENSION)AudioGroup->ChildPDO->DeviceExtension;
            ChildDeviceExtension->IsFDO = FALSE;
            ChildDeviceExtension->ReportedMissing = FALSE;
            ChildDeviceExtension->Codec = Entry;
            ChildDeviceExtension->AudioGroup = AudioGroup;
            ChildDeviceExtension->FDO = DeviceObject;

            /* setup flags */
            AudioGroup->ChildPDO->Flags |= DO_POWER_PAGABLE;
            AudioGroup->ChildPDO->Flags &= ~DO_DEVICE_INITIALIZING;

            /* add audio group*/
            Entry->AudioGroups[Entry->AudioGroupCount] = AudioGroup;
            Entry->AudioGroupCount++;
        }
    }
    return STATUS_SUCCESS;

}

NTSTATUS
NTAPI
HDA_InitCorbRirbPos(
    IN PDEVICE_OBJECT DeviceObject)
{
    PHDA_FDO_DEVICE_EXTENSION DeviceExtension;
    UCHAR corbSize, value, rirbSize;
    PHYSICAL_ADDRESS HighestPhysicalAddress, CorbPhysicalAddress;
    ULONG Index;
    USHORT corbReadPointer, rirbWritePointer, interruptValue, corbControl, rirbControl;

    /* get device extension */
    DeviceExtension = (PHDA_FDO_DEVICE_EXTENSION)DeviceObject->DeviceExtension;

    // Determine and set size of CORB
    corbSize = READ_REGISTER_UCHAR(DeviceExtension->RegBase + HDAC_CORB_SIZE);
    if ((corbSize & CORB_SIZE_CAP_256_ENTRIES) != 0) {
        DeviceExtension->CorbLength = 256;

        value = READ_REGISTER_UCHAR(DeviceExtension->RegBase + HDAC_CORB_SIZE) & ~HDAC_CORB_SIZE_MASK;
        WRITE_REGISTER_UCHAR(DeviceExtension->RegBase + HDAC_CORB_SIZE, value | CORB_SIZE_256_ENTRIES);
    }
    else if (corbSize & CORB_SIZE_CAP_16_ENTRIES) {
        DeviceExtension->CorbLength = 16;

        value = READ_REGISTER_UCHAR(DeviceExtension->RegBase + HDAC_CORB_SIZE) & ~HDAC_CORB_SIZE_MASK;
        WRITE_REGISTER_UCHAR(DeviceExtension->RegBase + HDAC_CORB_SIZE, value | CORB_SIZE_16_ENTRIES);
    }
    else if (corbSize & CORB_SIZE_CAP_2_ENTRIES) {
        DeviceExtension->CorbLength = 2;

        value = READ_REGISTER_UCHAR(DeviceExtension->RegBase + HDAC_CORB_SIZE) & ~HDAC_CORB_SIZE_MASK;
        WRITE_REGISTER_UCHAR(DeviceExtension->RegBase + HDAC_CORB_SIZE, value | CORB_SIZE_2_ENTRIES);
    }

    // Determine and set size of RIRB
    rirbSize = READ_REGISTER_UCHAR(DeviceExtension->RegBase + HDAC_RIRB_SIZE);
    if (rirbSize & RIRB_SIZE_CAP_256_ENTRIES) {
        DeviceExtension->RirbLength = 256;

        value = READ_REGISTER_UCHAR(DeviceExtension->RegBase + HDAC_RIRB_SIZE) & ~HDAC_RIRB_SIZE_MASK;
        WRITE_REGISTER_UCHAR(DeviceExtension->RegBase + HDAC_RIRB_SIZE, value | RIRB_SIZE_256_ENTRIES);
    }
    else if (rirbSize & RIRB_SIZE_CAP_16_ENTRIES) {
        DeviceExtension->RirbLength = 16;

        value = READ_REGISTER_UCHAR(DeviceExtension->RegBase + HDAC_RIRB_SIZE) & ~HDAC_RIRB_SIZE_MASK;
        WRITE_REGISTER_UCHAR(DeviceExtension->RegBase + HDAC_RIRB_SIZE, value | RIRB_SIZE_16_ENTRIES);
    }
    else if (rirbSize & RIRB_SIZE_CAP_2_ENTRIES) {
        DeviceExtension->RirbLength = 2;

        value = READ_REGISTER_UCHAR(DeviceExtension->RegBase + HDAC_RIRB_SIZE) & ~HDAC_RIRB_SIZE_MASK;
        WRITE_REGISTER_UCHAR(DeviceExtension->RegBase + HDAC_RIRB_SIZE, value | RIRB_SIZE_2_ENTRIES);
    }

    /* init corb */
    HighestPhysicalAddress.QuadPart = 0x00000000FFFFFFFF;
    DeviceExtension->CorbBase = (PULONG)MmAllocateContiguousMemory(PAGE_SIZE * 3, HighestPhysicalAddress);
    ASSERT(DeviceExtension->CorbBase != NULL);

    // FIXME align rirb 128bytes
    ASSERT(DeviceExtension->CorbLength == 256);
    ASSERT(DeviceExtension->RirbLength == 256);

    CorbPhysicalAddress = MmGetPhysicalAddress(DeviceExtension->CorbBase);
    ASSERT(CorbPhysicalAddress.QuadPart != 0LL);

    // Program CORB/RIRB for these locations
    WRITE_REGISTER_ULONG((PULONG)(DeviceExtension->RegBase + HDAC_CORB_BASE_LOWER), CorbPhysicalAddress.LowPart);
    WRITE_REGISTER_ULONG((PULONG)(DeviceExtension->RegBase + HDAC_CORB_BASE_UPPER), CorbPhysicalAddress.HighPart);

    DeviceExtension->RirbBase = (PRIRB_RESPONSE)((ULONG_PTR)DeviceExtension->CorbBase + PAGE_SIZE);
    CorbPhysicalAddress.QuadPart += PAGE_SIZE;
    WRITE_REGISTER_ULONG((PULONG)(DeviceExtension->RegBase + HDAC_RIRB_BASE_LOWER), CorbPhysicalAddress.LowPart);
    WRITE_REGISTER_ULONG((PULONG)(DeviceExtension->RegBase + HDAC_RIRB_BASE_UPPER), CorbPhysicalAddress.HighPart);

    // Program DMA position update
    DeviceExtension->StreamPositions = (PVOID)((ULONG_PTR)DeviceExtension->RirbBase + PAGE_SIZE);
    CorbPhysicalAddress.QuadPart += PAGE_SIZE;
    WRITE_REGISTER_ULONG((PULONG)(DeviceExtension->RegBase + HDAC_DMA_POSITION_BASE_LOWER), CorbPhysicalAddress.LowPart);
    WRITE_REGISTER_ULONG((PULONG)(DeviceExtension->RegBase + HDAC_DMA_POSITION_BASE_UPPER), CorbPhysicalAddress.HighPart);

    value = READ_REGISTER_USHORT((PUSHORT)(DeviceExtension->RegBase + HDAC_CORB_WRITE_POS)) & ~HDAC_CORB_WRITE_POS_MASK;
    WRITE_REGISTER_USHORT((PUSHORT)(DeviceExtension->RegBase + HDAC_CORB_WRITE_POS), value);

    // Reset CORB read pointer. Preserve bits marked as RsvdP.
    // After setting the reset bit, we must wait for the hardware
    // to acknowledge it, then manually unset it and wait for that
    // to be acknowledged as well.
    corbReadPointer = READ_REGISTER_USHORT((PUSHORT)(DeviceExtension->RegBase + HDAC_CORB_READ_POS));

    corbReadPointer |= CORB_READ_POS_RESET;
    WRITE_REGISTER_USHORT((PUSHORT)(DeviceExtension->RegBase + HDAC_CORB_READ_POS), corbReadPointer);

    for (Index = 0; Index < 10; Index++) {
        KeStallExecutionProcessor(100);
        corbReadPointer = READ_REGISTER_USHORT((PUSHORT)(DeviceExtension->RegBase + HDAC_CORB_READ_POS));
        if ((corbReadPointer & CORB_READ_POS_RESET) != 0)
            break;
    }
    if ((corbReadPointer & CORB_READ_POS_RESET) == 0) {
        DPRINT1("hda: CORB read pointer reset not acknowledged\n");

        // According to HDA spec v1.0a ch3.3.21, software must read the
        // bit as 1 to verify that the reset completed. However, at least
        // some nVidia HDA controllers do not update the bit after reset.
        // Thus don't fail here on nVidia controllers.
        //if (controller->pci_info.vendor_id != PCI_VENDOR_NVIDIA)
        //	return B_BUSY;
    }

    corbReadPointer &= ~CORB_READ_POS_RESET;
    WRITE_REGISTER_USHORT((PUSHORT)(DeviceExtension->RegBase + HDAC_CORB_READ_POS), corbReadPointer);
    for (Index = 0; Index < 10; Index++) {
        KeStallExecutionProcessor(100);
        corbReadPointer = READ_REGISTER_USHORT((PUSHORT)(DeviceExtension->RegBase + HDAC_CORB_READ_POS));
        if ((corbReadPointer & CORB_READ_POS_RESET) == 0)
            break;
    }
    if ((corbReadPointer & CORB_READ_POS_RESET) != 0) {
        DPRINT1("hda: CORB read pointer reset failed\n");
        return STATUS_UNSUCCESSFUL;
    }

    // Reset RIRB write pointer
    rirbWritePointer = READ_REGISTER_USHORT((PUSHORT)(DeviceExtension->RegBase + HDAC_RIRB_WRITE_POS)) & ~RIRB_WRITE_POS_RESET;
    WRITE_REGISTER_USHORT((PUSHORT)(DeviceExtension->RegBase + HDAC_RIRB_WRITE_POS), rirbWritePointer | RIRB_WRITE_POS_RESET);

    // Generate interrupt for every response
    interruptValue = READ_REGISTER_USHORT((PUSHORT)(DeviceExtension->RegBase + HDAC_RESPONSE_INTR_COUNT)) & ~HDAC_RESPONSE_INTR_COUNT_MASK;
    WRITE_REGISTER_USHORT((PUSHORT)(DeviceExtension->RegBase + HDAC_RESPONSE_INTR_COUNT), interruptValue | 1);

    // Setup cached read/write indices
    DeviceExtension->RirbReadPos = 1;
    DeviceExtension->CorbWritePos = 0;

    // Gentlemen, start your engines...
    corbControl = READ_REGISTER_USHORT((PUSHORT)(DeviceExtension->RegBase + HDAC_CORB_CONTROL)) & ~HDAC_CORB_CONTROL_MASK;
    WRITE_REGISTER_USHORT((PUSHORT)(DeviceExtension->RegBase + HDAC_CORB_CONTROL), corbControl | CORB_CONTROL_RUN | CORB_CONTROL_MEMORY_ERROR_INTR);

    rirbControl = READ_REGISTER_USHORT((PUSHORT)(DeviceExtension->RegBase + HDAC_RIRB_CONTROL)) & ~HDAC_RIRB_CONTROL_MASK;
    WRITE_REGISTER_USHORT((PUSHORT)(DeviceExtension->RegBase + HDAC_RIRB_CONTROL), rirbControl | RIRB_CONTROL_DMA_ENABLE | RIRB_CONTROL_OVERRUN_INTR | RIRB_CONTROL_RESPONSE_INTR);

    return STATUS_SUCCESS;
}

NTSTATUS
NTAPI
HDA_ResetController(
    IN PDEVICE_OBJECT DeviceObject)
{
    USHORT ValCapabilities;
    ULONG Index;
    PHDA_FDO_DEVICE_EXTENSION DeviceExtension;
    ULONG InputStreams, OutputStreams, BiDirStreams, Control;
    UCHAR corbControl, rirbControl;

    /* get device extension */
    DeviceExtension = (PHDA_FDO_DEVICE_EXTENSION)DeviceObject->DeviceExtension;

    /* read caps */
    ValCapabilities = READ_REGISTER_USHORT((PUSHORT)(DeviceExtension->RegBase + HDAC_GLOBAL_CAP));

    InputStreams = GLOBAL_CAP_INPUT_STREAMS(ValCapabilities);
    OutputStreams = GLOBAL_CAP_OUTPUT_STREAMS(ValCapabilities);
    BiDirStreams = GLOBAL_CAP_BIDIR_STREAMS(ValCapabilities);

    DPRINT1("NumInputStreams %u\n", InputStreams);
    DPRINT1("NumOutputStreams %u\n", OutputStreams);
    DPRINT1("NumBiDirStreams %u\n", BiDirStreams);

    /* stop all streams */
    for (Index = 0; Index < InputStreams; Index++)
    {
        WRITE_REGISTER_UCHAR(DeviceExtension->RegBase + HDAC_STREAM_CONTROL0 + HDAC_STREAM_BASE + HDAC_INPUT_STREAM_OFFSET(Index), 0);
        WRITE_REGISTER_UCHAR(DeviceExtension->RegBase + HDAC_STREAM_STATUS + HDAC_STREAM_BASE + HDAC_INPUT_STREAM_OFFSET(Index), 0);
    }

    for (Index = 0; Index < OutputStreams; Index++) {
        WRITE_REGISTER_UCHAR(DeviceExtension->RegBase + HDAC_STREAM_CONTROL0 + HDAC_STREAM_BASE + HDAC_OUTPUT_STREAM_OFFSET(InputStreams, Index), 0);
        WRITE_REGISTER_UCHAR(DeviceExtension->RegBase + HDAC_STREAM_STATUS + HDAC_STREAM_BASE + HDAC_OUTPUT_STREAM_OFFSET(InputStreams, Index), 0);
    }

    for (Index = 0; Index < BiDirStreams; Index++) {
        WRITE_REGISTER_UCHAR(DeviceExtension->RegBase + HDAC_STREAM_CONTROL0 + HDAC_STREAM_BASE + HDAC_BIDIR_STREAM_OFFSET(InputStreams, OutputStreams, Index), 0);
        WRITE_REGISTER_UCHAR(DeviceExtension->RegBase + HDAC_STREAM_STATUS + HDAC_STREAM_BASE + HDAC_BIDIR_STREAM_OFFSET(InputStreams, OutputStreams, Index), 0);
    }

    // stop DMA
    Control = READ_REGISTER_UCHAR(DeviceExtension->RegBase + HDAC_CORB_CONTROL) & ~HDAC_CORB_CONTROL_MASK;
    WRITE_REGISTER_UCHAR(DeviceExtension->RegBase + HDAC_CORB_CONTROL, Control);

    Control = READ_REGISTER_UCHAR(DeviceExtension->RegBase + HDAC_RIRB_CONTROL) & ~HDAC_RIRB_CONTROL_MASK;
    WRITE_REGISTER_UCHAR(DeviceExtension->RegBase + HDAC_RIRB_CONTROL, Control);

    for (int timeout = 0; timeout < 10; timeout++) {
        KeStallExecutionProcessor(100);

        corbControl = READ_REGISTER_UCHAR(DeviceExtension->RegBase + HDAC_CORB_CONTROL);
        rirbControl = READ_REGISTER_UCHAR(DeviceExtension->RegBase + HDAC_RIRB_CONTROL);
        if (corbControl == 0 && rirbControl == 0)
            break;
    }
    if (corbControl != 0 || rirbControl != 0) {
        DPRINT1("hda: unable to stop dma\n");
        return STATUS_UNSUCCESSFUL;
    }

    // reset DMA position buffer
    WRITE_REGISTER_ULONG((PULONG)(DeviceExtension->RegBase + HDAC_DMA_POSITION_BASE_LOWER), 0);
    WRITE_REGISTER_ULONG((PULONG)(DeviceExtension->RegBase + HDAC_DMA_POSITION_BASE_UPPER), 0);

    // Set reset bit - it must be asserted for at least 100us
    Control = READ_REGISTER_ULONG((PULONG)(DeviceExtension->RegBase + HDAC_GLOBAL_CONTROL));
    WRITE_REGISTER_ULONG((PULONG)(DeviceExtension->RegBase + HDAC_GLOBAL_CONTROL), Control & ~GLOBAL_CONTROL_RESET);

    for (int timeout = 0; timeout < 10; timeout++) {
        KeStallExecutionProcessor(100);

        Control = READ_REGISTER_ULONG((PULONG)(DeviceExtension->RegBase + HDAC_GLOBAL_CONTROL));
        if ((Control & GLOBAL_CONTROL_RESET) == 0)
            break;
    }
    if ((Control & GLOBAL_CONTROL_RESET) != 0)
    {
        DPRINT1("hda: unable to reset controller\n");
        return STATUS_UNSUCCESSFUL;
    }

    // Unset reset bit
    Control = READ_REGISTER_ULONG((PULONG)(DeviceExtension->RegBase + HDAC_GLOBAL_CONTROL));
    WRITE_REGISTER_ULONG((PULONG)(DeviceExtension->RegBase + HDAC_GLOBAL_CONTROL), Control | GLOBAL_CONTROL_RESET);

    for (int timeout = 0; timeout < 10; timeout++) {
        KeStallExecutionProcessor(100);

        Control = READ_REGISTER_ULONG((PULONG)(DeviceExtension->RegBase + HDAC_GLOBAL_CONTROL));
        if ((Control & GLOBAL_CONTROL_RESET) != 0)
            break;
    }
    if ((Control & GLOBAL_CONTROL_RESET) == 0) {
        DPRINT1("hda: unable to exit reset\n");
        return STATUS_UNSUCCESSFUL;
    }

    // Wait for codecs to finish their own reset (apparently needs more
    // time than documented in the specs)
    KeStallExecutionProcessor(1000);

    // Enable unsolicited responses
    Control = READ_REGISTER_ULONG((PULONG)(DeviceExtension->RegBase + HDAC_GLOBAL_CONTROL));
    WRITE_REGISTER_ULONG((PULONG)(DeviceExtension->RegBase + HDAC_GLOBAL_CONTROL), Control | GLOBAL_CONTROL_UNSOLICITED);

    return STATUS_SUCCESS;
}

NTSTATUS
NTAPI
HDA_FDOStartDevice(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp)
{
    PIO_STACK_LOCATION IoStack;
    NTSTATUS Status = STATUS_SUCCESS;
    PHDA_FDO_DEVICE_EXTENSION DeviceExtension;
    PCM_RESOURCE_LIST Resources;
    ULONG Index;
    USHORT Value;

    /* get device extension */
    DeviceExtension = (PHDA_FDO_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
    ASSERT(DeviceExtension->IsFDO == TRUE);

    /* forward irp to lower device */
    if (!IoForwardIrpSynchronously(DeviceExtension->LowerDevice, Irp))
    {
        ASSERT(FALSE);
        return STATUS_INVALID_DEVICE_REQUEST;
    }
    Status = Irp->IoStatus.Status;
    if (!NT_SUCCESS(Status))
    {
        // failed to start
        DPRINT1("HDA_StartDevice Lower device failed to start %x\n", Status);
        return Status;
    }

    /* get current irp stack location */
    IoStack = IoGetCurrentIrpStackLocation(Irp);

    Resources = IoStack->Parameters.StartDevice.AllocatedResourcesTranslated;
    for (Index = 0; Index < Resources->List[0].PartialResourceList.Count; Index++)
    {
        PCM_PARTIAL_RESOURCE_DESCRIPTOR Descriptor = &Resources->List[0].PartialResourceList.PartialDescriptors[Index];

        if (Descriptor->Type == CmResourceTypeMemory)
        {
            DeviceExtension->RegLength = Descriptor->u.Memory.Length;
            DeviceExtension->RegBase = (PUCHAR)MmMapIoSpace(Descriptor->u.Memory.Start, Descriptor->u.Memory.Length, MmNonCached);
            if (DeviceExtension->RegBase == NULL)
            {
                DPRINT1("[HDAB] Failed to map registers\n");
                Status = STATUS_UNSUCCESSFUL;
                break;
            }
        }
        else if (Descriptor->Type == CmResourceTypeInterrupt)
        {
            Status = IoConnectInterrupt(&DeviceExtension->Interrupt,
                HDA_InterruptService,
                DeviceObject,
                NULL,
                Descriptor->u.Interrupt.Vector,
                Descriptor->u.Interrupt.Level,
                Descriptor->u.Interrupt.Level,
                (KINTERRUPT_MODE)(Descriptor->Flags & CM_RESOURCE_INTERRUPT_LATCHED),
                (Descriptor->ShareDisposition != CmResourceShareDeviceExclusive),
                Descriptor->u.Interrupt.Affinity,
                FALSE);
            if (!NT_SUCCESS(Status))
            {
                DPRINT1("[HDAB] Failed to connect interrupt. Status=%lx\n", Status);
                break;
            }

        }
    }

    if (NT_SUCCESS(Status))
    {
        // Get controller into valid state
        Status = HDA_ResetController(DeviceObject);
        if (!NT_SUCCESS(Status)) return Status;

        // Setup CORB/RIRB/DMA POS
        Status = HDA_InitCorbRirbPos(DeviceObject);
        if (!NT_SUCCESS(Status)) return Status;


        // Don't enable codec state change interrupts. We don't handle
        // them, as we want to use the STATE_STATUS register to identify
        // available codecs. We'd have to clear that register in the interrupt
        // handler to 'ack' the codec change.
        Value = READ_REGISTER_USHORT((PUSHORT)(DeviceExtension->RegBase + HDAC_WAKE_ENABLE)) & ~HDAC_WAKE_ENABLE_MASK;
        WRITE_REGISTER_USHORT((PUSHORT)(DeviceExtension->RegBase + HDAC_WAKE_ENABLE), Value);

        // Enable controller interrupts
        WRITE_REGISTER_ULONG((PULONG)(DeviceExtension->RegBase + HDAC_INTR_CONTROL), INTR_CONTROL_GLOBAL_ENABLE | INTR_CONTROL_CONTROLLER_ENABLE);

        KeStallExecutionProcessor(1000);

        Value = READ_REGISTER_USHORT((PUSHORT)(DeviceExtension->RegBase + HDAC_STATE_STATUS));
        if (!Value) {
            DPRINT1("hda: bad codec status\n");
            return STATUS_UNSUCCESSFUL;
        }
        WRITE_REGISTER_USHORT((PUSHORT)(DeviceExtension->RegBase + HDAC_STATE_STATUS), Value);

        // Create codecs
        DPRINT1("Codecs %lx\n", Value);
        for (Index = 0; Index < HDA_MAX_CODECS; Index++) {
            if ((Value & (1 << Index)) != 0) {
                HDA_InitCodec(DeviceObject, Index);
            }
        }
    }

    return Status;
}

NTSTATUS
NTAPI
HDA_FDORemoveDevice(
    _In_ PDEVICE_OBJECT DeviceObject,
    _Inout_ PIRP Irp)
{
    NTSTATUS Status;
    PHDA_FDO_DEVICE_EXTENSION DeviceExtension;
    ULONG CodecIndex, AFGIndex;
    PHDA_CODEC_ENTRY CodecEntry;
    PDEVICE_OBJECT ChildPDO;
    PHDA_PDO_DEVICE_EXTENSION ChildDeviceExtension;

    /* get device extension */
    DeviceExtension = static_cast<PHDA_FDO_DEVICE_EXTENSION>(DeviceObject->DeviceExtension);
    ASSERT(DeviceExtension->IsFDO == TRUE);

    Irp->IoStatus.Status = STATUS_SUCCESS;
    IoSkipCurrentIrpStackLocation(Irp);
    Status = IoCallDriver(DeviceExtension->LowerDevice, Irp);

    IoDetachDevice(DeviceExtension->LowerDevice);

    if (DeviceExtension->RegBase != NULL)
    {
        MmUnmapIoSpace(DeviceExtension->RegBase,
                       DeviceExtension->RegLength);
    }
    if (DeviceExtension->Interrupt != NULL)
    {
        IoDisconnectInterrupt(DeviceExtension->Interrupt);
    }
    if (DeviceExtension->CorbBase != NULL)
    {
        MmFreeContiguousMemory(DeviceExtension->CorbBase);
    }

    for (CodecIndex = 0; CodecIndex < HDA_MAX_CODECS; CodecIndex++)
    {
        CodecEntry = DeviceExtension->Codecs[CodecIndex];
        if (CodecEntry == NULL)
        {
            continue;
        }

        ASSERT(CodecEntry->AudioGroupCount <= HDA_MAX_AUDIO_GROUPS);
        for (AFGIndex = 0; AFGIndex < CodecEntry->AudioGroupCount; AFGIndex++)
        {
            ChildPDO = CodecEntry->AudioGroups[AFGIndex]->ChildPDO;
            if (ChildPDO != NULL)
            {
                ChildDeviceExtension = static_cast<PHDA_PDO_DEVICE_EXTENSION>(ChildPDO->DeviceExtension);
                ChildDeviceExtension->Codec = NULL;
                ChildDeviceExtension->AudioGroup = NULL;
                ChildDeviceExtension->FDO = NULL;
                ChildDeviceExtension->ReportedMissing = TRUE;
                HDA_PDORemoveDevice(ChildPDO);
            }
            FreeItem(CodecEntry->AudioGroups[AFGIndex]);
        }
        FreeItem(CodecEntry);
    }

    IoDeleteDevice(DeviceObject);

    return Status;
}

NTSTATUS
NTAPI
HDA_FDOQueryBusRelations(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp)
{
    ULONG DeviceCount, CodecIndex, AFGIndex;
    PHDA_FDO_DEVICE_EXTENSION DeviceExtension;
    PHDA_CODEC_ENTRY Codec;
    PDEVICE_RELATIONS DeviceRelations;

    /* get device extension */
    DeviceExtension = (PHDA_FDO_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
    ASSERT(DeviceExtension->IsFDO == TRUE);

    DeviceCount = 0;
    for (CodecIndex = 0; CodecIndex < HDA_MAX_CODECS; CodecIndex++)
    {
        if (DeviceExtension->Codecs[CodecIndex] == NULL)
            continue;

        Codec = DeviceExtension->Codecs[CodecIndex];
        DeviceCount += Codec->AudioGroupCount;
    }

    if (DeviceCount == 0)
        return STATUS_UNSUCCESSFUL;

    DeviceRelations = (PDEVICE_RELATIONS)AllocateItem(NonPagedPool, sizeof(DEVICE_RELATIONS) + (DeviceCount > 1 ? sizeof(PDEVICE_OBJECT) * (DeviceCount - 1) : 0));
    if (!DeviceRelations)
        return STATUS_INSUFFICIENT_RESOURCES;

    DeviceRelations->Count = 0;
    for (CodecIndex = 0; CodecIndex < HDA_MAX_CODECS; CodecIndex++)
    {
        if (DeviceExtension->Codecs[CodecIndex] == NULL)
            continue;

        Codec = DeviceExtension->Codecs[CodecIndex];
        ASSERT(Codec->AudioGroupCount <= HDA_MAX_AUDIO_GROUPS);
        for (AFGIndex = 0; AFGIndex < Codec->AudioGroupCount; AFGIndex++)
        {
            DeviceRelations->Objects[DeviceRelations->Count] = Codec->AudioGroups[AFGIndex]->ChildPDO;
            ObReferenceObject(Codec->AudioGroups[AFGIndex]->ChildPDO);
            DeviceRelations->Count++;
        }
    }

    /* FIXME handle existing device relations */
    ASSERT(Irp->IoStatus.Information == 0);

    /* store device relations */
    Irp->IoStatus.Information = (ULONG_PTR)DeviceRelations;

    /* done */
    return STATUS_SUCCESS;
}
