/*
 * File: HdaController.c
 *
 * Copyright (c) 2018 John Davis
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include "HdaController.h"
#include "HdaControllerComponentName.h"

#include <Library/OcHdaDevicesLib.h>
#include <Library/OcStringLib.h>
#include <Protocol/VMwareHda.h>

VOID
EFIAPI
HdaControllerStreamPollTimerHandler(
  IN EFI_EVENT Event,
  IN VOID *Context) {

  // Create variables.
  EFI_STATUS Status;
  HDA_STREAM *HdaStream = (HDA_STREAM*)Context;
  EFI_PCI_IO_PROTOCOL *PciIo = HdaStream->HdaControllerDev->PciIo;
  UINT8 HdaStreamSts = 0;
  UINT32 HdaStreamDmaPos;
  UINTN HdaSourceLength;
  UINTN HdaCurrentBlock;
  UINTN HdaNextBlock;

  // Get stream status.
  Status = PciIo->Mem.Read(PciIo, EfiPciIoWidthFifoUint8, PCI_HDA_BAR, HDA_REG_SDNSTS(HdaStream->Index), 1, &HdaStreamSts);
  ASSERT_EFI_ERROR(Status);

  // If there was a FIFO error or DESC error, halt.
  ASSERT ((HdaStreamSts & (HDA_REG_SDNSTS_FIFOE | HDA_REG_SDNSTS_DESE)) == 0);

  // Has the completion bit been set?
  if (HdaStreamSts & HDA_REG_SDNSTS_BCIS) {
    // Are we done playing the stream? If so we can stop now.
    if (HdaStream->BufferSourceDone) {
      // Stop stream.
      Status = HdaControllerSetStream(HdaStream, FALSE);
      ASSERT_EFI_ERROR(Status);

      // Stop timer.
      Status = gBS->SetTimer(HdaStream->PollTimer, TimerCancel, 0);
      ASSERT_EFI_ERROR(Status);

      // Trigger callback.
      if (HdaStream->Callback)
        HdaStream->Callback(HdaStream->Output ? EfiHdaIoTypeOutput : EfiHdaIoTypeInput,
          HdaStream->CallbackContext1, HdaStream->CallbackContext2, HdaStream->CallbackContext3);
      goto CLEAR_BIT;
    }

    // Get stream DMA position.
    HdaStreamDmaPos = HdaStream->HdaControllerDev->DmaPositions[HdaStream->Index].Position;
    HdaCurrentBlock = HdaStreamDmaPos / HDA_BDL_BLOCKSIZE;
    HdaNextBlock = HdaCurrentBlock + 1;
    HdaNextBlock %= HDA_BDL_ENTRY_COUNT;

    // Have we reached the end of the source buffer? If so the stream will stop on the next block.
    if (HdaStream->BufferSourcePosition >= HdaStream->BufferSourceLength) {
      // Zero out next block.
      ZeroMem(HdaStream->BufferData + (HdaNextBlock * HDA_BDL_BLOCKSIZE), HDA_BDL_BLOCKSIZE);

      // Set flag to stop stream on the next block.
      HdaStream->BufferSourceDone = TRUE;
      DEBUG((DEBUG_VERBOSE, "Block %u of %u is the last! (current position 0x%X, buffer 0x%X)\n",
        HdaStreamDmaPos / HDA_BDL_BLOCKSIZE, HDA_BDL_ENTRY_COUNT, HdaStreamDmaPos, HdaStream->BufferSourcePosition));
      goto CLEAR_BIT;
    }

    // Determine number of bytes to pull from or push to source data.
    HdaSourceLength = HDA_BDL_BLOCKSIZE;
    if ((HdaStream->BufferSourcePosition + HdaSourceLength) > HdaStream->BufferSourceLength)
      HdaSourceLength = HdaStream->BufferSourceLength - HdaStream->BufferSourcePosition;

    // Is this an output stream (copy data to)?
    if (HdaStream->Output) {
      // Copy data to DMA buffer.
      if (HdaSourceLength < HDA_BDL_BLOCKSIZE)
        ZeroMem(HdaStream->BufferData + (HdaNextBlock * HDA_BDL_BLOCKSIZE), HDA_BDL_BLOCKSIZE);
      CopyMem(HdaStream->BufferData + (HdaNextBlock * HDA_BDL_BLOCKSIZE), HdaStream->BufferSource + HdaStream->BufferSourcePosition, HdaSourceLength);
    } else { // Input stream (copy data from).
      // Copy data from DMA buffer.
      CopyMem(HdaStream->BufferSource + HdaStream->BufferSourcePosition, HdaStream->BufferData + (HdaNextBlock * HDA_BDL_BLOCKSIZE), HdaSourceLength);
    }

    // Increase source position.
    HdaStream->BufferSourcePosition += HdaSourceLength;
    DEBUG((DEBUG_VERBOSE, "Block %u of %u filled! (current position 0x%X, buffer 0x%X)\n",
      HdaStreamDmaPos / HDA_BDL_BLOCKSIZE, HDA_BDL_ENTRY_COUNT, HdaStreamDmaPos, HdaStream->BufferSourcePosition));

CLEAR_BIT:
    // Reset completion bit.
    HdaStreamSts = HDA_REG_SDNSTS_BCIS;
    Status = PciIo->Mem.Write(PciIo, EfiPciIoWidthUint8, PCI_HDA_BAR, HDA_REG_SDNSTS(HdaStream->Index), 1, &HdaStreamSts);
    ASSERT_EFI_ERROR(Status);
  }
}

EFI_STATUS
EFIAPI
HdaControllerInitPciHw(
  IN HDA_CONTROLLER_DEV *HdaControllerDev) {
  DEBUG((DEBUG_VERBOSE, "HdaControllerInitPciHw(): start\n"));

  // Create variables.
  EFI_STATUS Status;
  EFI_PCI_IO_PROTOCOL *PciIo = HdaControllerDev->PciIo;
  UINT64 PciSupports;
  UINT8 HdaTcSel;
  UINT16 HdaDevC;

  PciSupports = 0;

  // Get original PCI I/O attributes.
  Status = PciIo->Attributes(PciIo, EfiPciIoAttributeOperationGet, 0, &HdaControllerDev->OriginalPciAttributes);
  if (EFI_ERROR(Status))
    return Status;
  HdaControllerDev->OriginalPciAttributesSaved = TRUE;

  // Get currently supported PCI I/O attributes.
  Status = PciIo->Attributes(PciIo, EfiPciIoAttributeOperationSupported, 0, &PciSupports);
  if (EFI_ERROR(Status))
    return Status;

  // Enable the PCI device.
  PciSupports &= EFI_PCI_DEVICE_ENABLE;
  Status = PciIo->Attributes(PciIo, EfiPciIoAttributeOperationEnable, PciSupports, NULL);
  if (EFI_ERROR(Status))
    return Status;

  // Get vendor and device IDs of PCI device.
  Status = PciIo->Pci.Read(PciIo, EfiPciIoWidthUint32, PCI_VENDOR_ID_OFFSET, 1, &HdaControllerDev->VendorId);
  if (EFI_ERROR (Status))
    return Status;

  DEBUG((DEBUG_VERBOSE, "HdaControllerInitPciHw(): controller %4X:%4X\n",
    GET_PCI_VENDOR_ID(HdaControllerDev->VendorId), GET_PCI_DEVICE_ID(HdaControllerDev->VendorId)));

  // Is this an Intel controller?
  if (GET_PCI_VENDOR_ID(HdaControllerDev->VendorId) == VEN_INTEL_ID) {
    // Set TC0 in TCSEL register.
    Status = PciIo->Pci.Read(PciIo, EfiPciIoWidthUint8, PCI_HDA_TCSEL_OFFSET, 1, &HdaTcSel);
    if (EFI_ERROR (Status))
      return Status;
    HdaTcSel &= PCI_HDA_TCSEL_TC0_MASK;
    Status = PciIo->Pci.Write(PciIo, EfiPciIoWidthUint8, PCI_HDA_TCSEL_OFFSET, 1, &HdaTcSel);
    if (EFI_ERROR (Status))
      return Status;
  }

  // Get device control PCI register.
  Status = PciIo->Pci.Read(PciIo, EfiPciIoWidthUint16, PCI_HDA_DEVC_OFFSET, 1, &HdaDevC);
  if (EFI_ERROR (Status))
    return Status;

  // If No Snoop is currently enabled, disable it.
  if (HdaDevC & PCI_HDA_DEVC_NOSNOOPEN) {
    HdaDevC &= ~PCI_HDA_DEVC_NOSNOOPEN;
    Status = PciIo->Pci.Write(PciIo, EfiPciIoWidthUint16, PCI_HDA_DEVC_OFFSET, 1, &HdaDevC);
    if (EFI_ERROR (Status))
      return Status;
  }

  // Get major/minor version.
  Status = PciIo->Mem.Read(PciIo, EfiPciIoWidthUint8, PCI_HDA_BAR, HDA_REG_VMAJ, 1, &HdaControllerDev->MajorVersion);
  if (EFI_ERROR(Status))
    return Status;
  Status = PciIo->Mem.Read(PciIo, EfiPciIoWidthUint8, PCI_HDA_BAR, HDA_REG_VMIN, 1, &HdaControllerDev->MinorVersion);
  if (EFI_ERROR(Status))
    return Status;

  // Validate version. If invalid abort.
  DEBUG((DEBUG_INFO, "HdaControllerInitPciHw(): controller version %u.%u\n",
    HdaControllerDev->MajorVersion, HdaControllerDev->MinorVersion));
  if (HdaControllerDev->MajorVersion < HDA_VERSION_MIN_MAJOR) {
    Status = EFI_UNSUPPORTED;
    return Status;
  }

  // Get capabilities.
  Status = PciIo->Mem.Read(PciIo, EfiPciIoWidthUint16, PCI_HDA_BAR, HDA_REG_GCAP, 1, &HdaControllerDev->Capabilities);
  if (EFI_ERROR(Status))
    return Status;
  DEBUG((DEBUG_INFO, "HdaControllerInitPciHw(): capabilities:\n  64-bit: %s  Serial Data Out Signals: %u\n",
    HdaControllerDev->Capabilities & HDA_REG_GCAP_64OK ? L"Yes" : L"No",
    HDA_REG_GCAP_NSDO(HdaControllerDev->Capabilities)));
  DEBUG((DEBUG_INFO, "  Bidir streams: %u  Input streams: %u  Output streams: %u\n",
    HDA_REG_GCAP_BSS(HdaControllerDev->Capabilities), HDA_REG_GCAP_ISS(HdaControllerDev->Capabilities),
    HDA_REG_GCAP_OSS(HdaControllerDev->Capabilities)));

  // Success.
  return EFI_SUCCESS;
}

VOID
EFIAPI
HdaControllerGetName(
  IN HDA_CONTROLLER_DEV *HdaControllerDev) {
  DEBUG((DEBUG_VERBOSE, "HdaControllerGetName(): start\n"));

  // Try to match controller name.
  HdaControllerDev->Name = AsciiStrCopyToUnicode (OcHdaControllerGetName (HdaControllerDev->VendorId), 0);
  DEBUG((DEBUG_INFO, "HdaControllerGetName(): controller is %s\n", HdaControllerDev->Name));
}

EFI_STATUS
EFIAPI
HdaControllerReset(
  IN HDA_CONTROLLER_DEV *HdaControllerDev) {
  DEBUG((DEBUG_VERBOSE, "HdaControllerReset(): start\n"));

  // Create variables.
  EFI_STATUS Status;
  EFI_PCI_IO_PROTOCOL *PciIo = HdaControllerDev->PciIo;
  UINT32 HdaGCtl;
  UINT64 Tmp;

  // Get value of CRST bit.
  Status = PciIo->Mem.Read(PciIo, EfiPciIoWidthUint32, PCI_HDA_BAR, HDA_REG_GCTL, 1, &HdaGCtl);
  if (EFI_ERROR(Status))
    return Status;

  // Check if the controller is already in reset. If not, clear bit.
  if (!(HdaGCtl & HDA_REG_GCTL_CRST)) {
    HdaGCtl &= ~HDA_REG_GCTL_CRST;
    Status = PciIo->Mem.Write(PciIo, EfiPciIoWidthUint32, PCI_HDA_BAR, HDA_REG_GCTL, 1, &HdaGCtl);
    if (EFI_ERROR(Status))
      return Status;
  }

  // Set CRST bit to begin the process of coming out of reset.
  HdaGCtl |= HDA_REG_GCTL_CRST;
  Status = PciIo->Mem.Write(PciIo, EfiPciIoWidthUint32, PCI_HDA_BAR, HDA_REG_GCTL, 1, &HdaGCtl);
  if (EFI_ERROR(Status))
    return Status;

  // Wait for bit to be set. Once bit is set, the controller is ready.
  Status = PciIo->PollMem(PciIo, EfiPciIoWidthUint32, PCI_HDA_BAR, HDA_REG_GCTL,
    HDA_REG_GCTL_CRST, HDA_REG_GCTL_CRST, MS_TO_NANOSECOND(100), &Tmp);
  if (EFI_ERROR(Status))
    return Status;

  // Wait 100ms to ensure all codecs have also reset.
  gBS->Stall(MS_TO_MICROSECOND(100));

  // Controller is reset.
  DEBUG((DEBUG_VERBOSE, "HdaControllerReset(): done\n"));
  return EFI_SUCCESS;
}

EFI_STATUS
EFIAPI
HdaControllerScanCodecs(
  IN HDA_CONTROLLER_DEV *HdaControllerDev) {
  DEBUG((DEBUG_VERBOSE, "HdaControllerScanCodecs(): start\n"));

  // Create variables.
  EFI_STATUS Status;
  EFI_PCI_IO_PROTOCOL *PciIo = HdaControllerDev->PciIo;
  UINT16 HdaStatests;
  EFI_HDA_IO_VERB_LIST HdaCodecVerbList;
  UINT32 VendorVerb;
  UINT32 VendorResponse;

  // Streams.
  UINTN CurrentOutputStreamIndex = 0;
  UINTN CurrentInputStreamIndex = 0;

  // Protocols.
  HDA_IO_PRIVATE_DATA *HdaIoPrivateData;
  VOID *TmpProtocol;

  if (!HdaControllerDev) {
    return EFI_INVALID_PARAMETER;
  }

  // Get STATESTS register.
  Status = PciIo->Mem.Read(PciIo, EfiPciIoWidthUint16, PCI_HDA_BAR, HDA_REG_STATESTS, 1, &HdaStatests);
  if (EFI_ERROR(Status))
    return Status;

  // Create verb list with single item.
  VendorVerb = HDA_CODEC_VERB(HDA_VERB_GET_PARAMETER, HDA_PARAMETER_VENDOR_ID);
  ZeroMem(&HdaCodecVerbList, sizeof(EFI_HDA_IO_VERB_LIST));
  HdaCodecVerbList.Count = 1;
  HdaCodecVerbList.Verbs = &VendorVerb;
  HdaCodecVerbList.Responses = &VendorResponse;

  // Iterate through register looking for active codecs.
  for (UINT8 i = 0; i < HDA_MAX_CODECS; i++) {
    // Do we have a codec at this address?
    if (HdaStatests & (1 << i)) {
      DEBUG((DEBUG_VERBOSE, "HdaControllerScanCodecs(): found codec @ 0x%X\n", i));

      // Try to get the vendor ID. If this fails, ignore the codec.
      VendorResponse = 0;
      Status = HdaControllerSendCommands(HdaControllerDev, i, HDA_NID_ROOT, &HdaCodecVerbList);
      if ((EFI_ERROR(Status)) || (VendorResponse == 0))
        continue;

      // Create HDA I/O protocol private data structure.
      HdaIoPrivateData = AllocateZeroPool(sizeof(HDA_IO_PRIVATE_DATA));
      if (HdaIoPrivateData == NULL) {
        Status = EFI_OUT_OF_RESOURCES;
        goto FREE_CODECS;
      }

      // Fill HDA I/O protocol private data structure.
      HdaIoPrivateData->Signature = HDA_CONTROLLER_PRIVATE_DATA_SIGNATURE;
      HdaIoPrivateData->HdaCodecAddress = i;
      HdaIoPrivateData->HdaControllerDev = HdaControllerDev;
      HdaIoPrivateData->HdaIo.GetAddress = HdaControllerHdaIoGetAddress;
      HdaIoPrivateData->HdaIo.SendCommand = HdaControllerHdaIoSendCommand;
      HdaIoPrivateData->HdaIo.SetupStream = HdaControllerHdaIoSetupStream;
      HdaIoPrivateData->HdaIo.CloseStream = HdaControllerHdaIoCloseStream;
      HdaIoPrivateData->HdaIo.GetStream = HdaControllerHdaIoGetStream;
      HdaIoPrivateData->HdaIo.StartStream = HdaControllerHdaIoStartStream;
      HdaIoPrivateData->HdaIo.StopStream = HdaControllerHdaIoStopStream;

      // Assign output stream.
      if (CurrentOutputStreamIndex < HdaControllerDev->OutputStreamsCount) {
        DEBUG((DEBUG_VERBOSE, "Assigning output stream %u to codec\n", CurrentOutputStreamIndex));
        HdaIoPrivateData->HdaOutputStream = HdaControllerDev->OutputStreams + CurrentOutputStreamIndex;
        CurrentOutputStreamIndex++;
      }

      // Assign input stream.
      if (CurrentInputStreamIndex < HdaControllerDev->InputStreamsCount) {
        DEBUG((DEBUG_VERBOSE, "Assigning input stream %u to codec\n", CurrentInputStreamIndex));
        HdaIoPrivateData->HdaInputStream = HdaControllerDev->InputStreams + CurrentInputStreamIndex;
        CurrentInputStreamIndex++;
      }

      // Add to array.
      HdaControllerDev->HdaIoChildren[i].PrivateData = HdaIoPrivateData;
    }
  }

  // Clear STATESTS register.
  HdaStatests = HDA_REG_STATESTS_CLEAR;
  Status = PciIo->Mem.Write(PciIo, EfiPciIoWidthUint16, PCI_HDA_BAR, HDA_REG_STATESTS, 1, &HdaStatests);
  if (EFI_ERROR(Status))
    return Status;

  // Install protocols on each codec.
  for (UINT8 i = 0; i < HDA_MAX_CODECS; i++) {
    // Do we have a codec at this address?
    if (HdaControllerDev->HdaIoChildren[i].PrivateData != NULL) {
      // Create Device Path for codec.
      EFI_HDA_IO_DEVICE_PATH HdaIoDevicePathNode; //EFI_HDA_IO_DEVICE_PATH_TEMPLATE;
      HdaIoDevicePathNode.Header.Type = MESSAGING_DEVICE_PATH;
      HdaIoDevicePathNode.Header.SubType = MSG_VENDOR_DP;
      HdaIoDevicePathNode.Header.Length[0] = (UINT8)(sizeof(EFI_HDA_IO_DEVICE_PATH));
      HdaIoDevicePathNode.Header.Length[1] = (UINT8)((sizeof(EFI_HDA_IO_DEVICE_PATH)) >> 8);
      HdaIoDevicePathNode.Guid = gEfiHdaIoDevicePathGuid;
      HdaIoDevicePathNode.Address = i;
      HdaControllerDev->HdaIoChildren[i].DevicePath = AppendDevicePathNode(HdaControllerDev->DevicePath, (EFI_DEVICE_PATH_PROTOCOL*)&HdaIoDevicePathNode);
      if (HdaControllerDev->HdaIoChildren[i].DevicePath == NULL) {
        Status = EFI_INVALID_PARAMETER;
        goto FREE_CODECS;
      }

      // Install protocols for the codec. The codec driver will later bind to this.
      HdaControllerDev->HdaIoChildren[i].Handle = NULL;
      Status = gBS->InstallMultipleProtocolInterfaces(&HdaControllerDev->HdaIoChildren[i].Handle,
        &gEfiDevicePathProtocolGuid, HdaControllerDev->HdaIoChildren[i].DevicePath,
        &gEfiHdaIoProtocolGuid, &HdaControllerDev->HdaIoChildren[i].PrivateData->HdaIo, NULL);
      if (EFI_ERROR(Status))
        goto FREE_CODECS;

      // Connect child to parent.
      Status = gBS->OpenProtocol(HdaControllerDev->ControllerHandle, &gEfiPciIoProtocolGuid, &TmpProtocol,
        HdaControllerDev->DriverBinding->DriverBindingHandle, HdaControllerDev->HdaIoChildren[i].Handle, EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER);
      if (EFI_ERROR(Status))
        goto FREE_CODECS;
    }
  }

  return EFI_SUCCESS;

FREE_CODECS:
  //DEBUG((DEBUG_INFO, "HdaControllerScanCodecs(): failed to load driver for codec @ 0x%X\n", i));

  return Status;
}

EFI_STATUS
EFIAPI
HdaControllerSendCommands(
  IN HDA_CONTROLLER_DEV *HdaDev,
  IN UINT8 CodecAddress,
  IN UINT8 Node,
  IN EFI_HDA_IO_VERB_LIST *Verbs) {
  //DEBUG((DEBUG_INFO, "HdaControllerSendCommands(): start\n"));

  // Create variables.
  EFI_STATUS Status;
  EFI_PCI_IO_PROTOCOL *PciIo = HdaDev->PciIo;
  UINT32 *HdaCorb;
  UINT64 *HdaRirb;

  // Ensure parameters are valid.
  if (CodecAddress >= HDA_MAX_CODECS || Verbs == NULL || Verbs->Count < 1)
    return EFI_INVALID_PARAMETER;

  // Get pointers to CORB and RIRB.
  HdaCorb = HdaDev->CorbBuffer;
  HdaRirb = HdaDev->RirbBuffer;

  UINT32 RemainingVerbs;
  UINT32 RemainingResponses;
  UINT16 HdaCorbReadPointer;
  UINT16 HdaRirbWritePointer;
  BOOLEAN ResponseReceived;
  UINT8 ResponseTimeout;
  UINT64 RirbResponse;
  UINT32 VerbCommand;
  BOOLEAN Retry = FALSE;

  // Lock.
  AcquireSpinLock(&HdaDev->SpinLock);

START:
  RemainingVerbs = Verbs->Count;
  RemainingResponses = Verbs->Count;
  do {
    // Keep sending verbs until they are all sent.
    if (RemainingVerbs) {
      // Get current CORB read pointer.
      Status = PciIo->Mem.Read(PciIo, EfiPciIoWidthUint16, PCI_HDA_BAR, HDA_REG_CORBRP, 1, &HdaCorbReadPointer);
      if (EFI_ERROR(Status))
        goto DONE;
      //DEBUG((DEBUG_INFO, "old RP: 0x%X\n", HdaCorbReadPointer));

      // Add verbs to CORB until all of them are added or the CORB becomes full.
      while (RemainingVerbs && ((HdaDev->CorbWritePointer + 1 % HdaDev->CorbEntryCount) != HdaCorbReadPointer)) {
        // Move write pointer and write verb to CORB.
        HdaDev->CorbWritePointer++;
        HdaDev->CorbWritePointer %= HdaDev->CorbEntryCount;
        VerbCommand = HDA_CORB_VERB(CodecAddress, Node, Verbs->Verbs[Verbs->Count - RemainingVerbs]);
        HdaCorb[HdaDev->CorbWritePointer] = VerbCommand;

        // Move to next verb.
        RemainingVerbs--;
      }

      // Set CORB write pointer.
      Status = PciIo->Mem.Write(PciIo, EfiPciIoWidthUint16, PCI_HDA_BAR, HDA_REG_CORBWP, 1, &HdaDev->CorbWritePointer);
      if (EFI_ERROR(Status))
        goto DONE;
    }

    // Get responses from RIRB.
    ResponseReceived = FALSE;
    ResponseTimeout = 10;
    while (!ResponseReceived) {
      // Get current RIRB write pointer.
      Status = PciIo->Mem.Read(PciIo, EfiPciIoWidthUint16, PCI_HDA_BAR, HDA_REG_RIRBWP, 1, &HdaRirbWritePointer);
      if (EFI_ERROR(Status))
        goto DONE;

      // If the read and write pointers differ, there are responses waiting.
      while (HdaDev->RirbReadPointer != HdaRirbWritePointer) {
        // Increment RIRB read pointer.
        HdaDev->RirbReadPointer++;
        HdaDev->RirbReadPointer %= HdaDev->RirbEntryCount;

        // Get response and ensure it belongs to the current codec.
        RirbResponse = HdaRirb[HdaDev->RirbReadPointer];
        if (HDA_RIRB_CAD(RirbResponse) != CodecAddress || HDA_RIRB_UNSOL(RirbResponse)) {
          DEBUG((DEBUG_INFO, "Unknown response!\n"));
          continue;
        }

        // Add response to list.
        Verbs->Responses[Verbs->Count - RemainingResponses] = HDA_RIRB_RESP(RirbResponse);
        RemainingResponses--;
        ResponseReceived = TRUE;
      }

      // If no response still, wait a bit.
      if (!ResponseReceived) {
        // If timeout reached, fail.
        if (!ResponseTimeout) {
          DEBUG((DEBUG_INFO, "Command: 0x%X\n", VerbCommand));
          Status = EFI_TIMEOUT;
          goto TIMEOUT;
        }

        ResponseTimeout--;
        gBS->Stall(MS_TO_MICROSECOND(5));
        if (ResponseTimeout < 5)
          DEBUG((DEBUG_INFO, "%u timeouts reached while waiting for response!\n", ResponseTimeout));
      }
    }

  } while (RemainingVerbs || RemainingResponses);
  Status = EFI_SUCCESS;
  goto DONE;

TIMEOUT:
  DEBUG((DEBUG_INFO, "Timeout!\n"));
  if (!Retry) {
    DEBUG((DEBUG_INFO, "Stall detected, restarting CORB and RIRB!\n"));
    Status = HdaControllerSetCorb(HdaDev, FALSE);
    if (EFI_ERROR(Status))
      goto DONE;
    Status = HdaControllerSetRirb(HdaDev, FALSE);
    if (EFI_ERROR(Status))
      goto DONE;
    Status = HdaControllerSetCorb(HdaDev, TRUE);
    if (EFI_ERROR(Status))
      goto DONE;
    Status = HdaControllerSetRirb(HdaDev, TRUE);
    if (EFI_ERROR(Status))
      goto DONE;

    // Try again.
    Retry = TRUE;
    goto START;
  }

DONE:
  ReleaseSpinLock(&HdaDev->SpinLock);
  return Status;
}

EFI_STATUS
EFIAPI
HdaControllerInstallProtocols(
  IN HDA_CONTROLLER_DEV *HdaControllerDev) {
  DEBUG((DEBUG_INFO, "HdaControllerInstallProtocols(): start\n"));

  // Create variables.
  HDA_CONTROLLER_INFO_PRIVATE_DATA *HdaControllerInfoData;

  // Allocate space for info protocol data.
  HdaControllerInfoData = AllocateZeroPool(sizeof(HDA_CONTROLLER_INFO_PRIVATE_DATA));
  if (HdaControllerInfoData == NULL)
    return EFI_OUT_OF_RESOURCES;

  // Populate data.
  HdaControllerInfoData->Signature = HDA_CONTROLLER_PRIVATE_DATA_SIGNATURE;
  HdaControllerInfoData->HdaControllerDev = HdaControllerDev;
  HdaControllerInfoData->HdaControllerInfo.GetName = HdaControllerInfoGetName;

  // Install protocols.
  HdaControllerDev->HdaControllerInfoData = HdaControllerInfoData;
  return gBS->InstallMultipleProtocolInterfaces(&HdaControllerDev->ControllerHandle,
    &gEfiHdaControllerInfoProtocolGuid, &HdaControllerInfoData->HdaControllerInfo,
    &gEfiCallerIdGuid, HdaControllerDev, NULL);
}

VOID
EFIAPI
HdaControllerCleanup(
  IN HDA_CONTROLLER_DEV *HdaControllerDev) {
  DEBUG((DEBUG_VERBOSE, "HdaControllerCleanup(): start\n"));

  // If controller device is already free, we are done.
  if (HdaControllerDev == NULL)
    return;

  // Create variables.
  EFI_STATUS Status;
  EFI_PCI_IO_PROTOCOL *PciIo = HdaControllerDev->PciIo;
  UINT32 HdaGCtl;

  // Clean HDA Controller info protocol.
  if (HdaControllerDev->HdaControllerInfoData != NULL) {
    // Uninstall protocol.
    DEBUG((DEBUG_VERBOSE, "HdaControllerCleanup(): clean HDA Controller Info\n"));
    Status = gBS->UninstallProtocolInterface(HdaControllerDev->ControllerHandle,
      &gEfiHdaControllerInfoProtocolGuid, &HdaControllerDev->HdaControllerInfoData->HdaControllerInfo);
    ASSERT_EFI_ERROR(Status);

    // Free data.
    FreePool(HdaControllerDev->HdaControllerInfoData);
  }

  // Clean HDA I/O children.
  for (UINT8 i = 0; i < HDA_MAX_CODECS; i++) {
    // Clean Device Path protocol.
    if (HdaControllerDev->HdaIoChildren[i].DevicePath != NULL) {
      // Uninstall protocol.
      DEBUG((DEBUG_VERBOSE, "HdaControllerCleanup(): clean Device Path index %u\n", i));
      Status = gBS->UninstallProtocolInterface(HdaControllerDev->HdaIoChildren[i].Handle,
        &gEfiDevicePathProtocolGuid, HdaControllerDev->HdaIoChildren[i].DevicePath);
      ASSERT_EFI_ERROR(Status);

      // Free Device Path.
      FreePool(HdaControllerDev->HdaIoChildren[i].DevicePath);
    }

    // Clean HDA I/O protocol.
    if (HdaControllerDev->HdaIoChildren[i].PrivateData != NULL) {
      // Uninstall protocol.
      DEBUG((DEBUG_VERBOSE, "HdaControllerCleanup(): clean HDA I/O index %u\n", i));
      Status = gBS->UninstallProtocolInterface(HdaControllerDev->HdaIoChildren[i].Handle,
        &gEfiHdaIoProtocolGuid, &HdaControllerDev->HdaIoChildren[i].PrivateData->HdaIo);
      ASSERT_EFI_ERROR(Status);

      // Free private data.
      FreePool(HdaControllerDev->HdaIoChildren[i].PrivateData);
    }
  }

  // Cleanup streams.
  HdaControllerCleanupStreams(HdaControllerDev);

  // Stop and cleanup CORB and RIRB.
  HdaControllerCleanupCorb(HdaControllerDev);
  HdaControllerCleanupRirb(HdaControllerDev);

  // Get value of CRST bit.
  Status = PciIo->Mem.Read(PciIo, EfiPciIoWidthUint32, PCI_HDA_BAR, HDA_REG_GCTL, 1, &HdaGCtl);

  // Place controller into a reset state to stop it.
  if (!(EFI_ERROR(Status))) {
    HdaGCtl &= ~HDA_REG_GCTL_CRST;
    Status = PciIo->Mem.Write(PciIo, EfiPciIoWidthUint32, PCI_HDA_BAR, HDA_REG_GCTL, 1, &HdaGCtl);
  }

  // Free controller device.
  gBS->UninstallProtocolInterface(HdaControllerDev->ControllerHandle,
    &gEfiCallerIdGuid, HdaControllerDev);
  FreePool(HdaControllerDev);
}

EFI_STATUS
EFIAPI
HdaControllerDriverBindingSupported (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   ControllerHandle,
  IN EFI_DEVICE_PATH_PROTOCOL     *RemainingDevicePath OPTIONAL
  )
{
  EFI_STATUS             Status;
  EFI_PCI_IO_PROTOCOL    *PciIo;
  HDA_PCI_CLASSREG       HdaClassReg;

  //
  // Open PCI I/O protocol. If this fails, it's not a PCI device.
  //
  Status = gBS->OpenProtocol (
    ControllerHandle,
    &gEfiPciIoProtocolGuid,
    (VOID**) &PciIo,
    This->DriverBindingHandle,
    ControllerHandle,
    EFI_OPEN_PROTOCOL_GET_PROTOCOL
    );

  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Read class code from PCI.
  //
  Status = PciIo->Pci.Read (
    PciIo,
    EfiPciIoWidthUint8,
    PCI_CLASSCODE_OFFSET,
    sizeof (HDA_PCI_CLASSREG),
    &HdaClassReg
    );

  //
  // Check class code, ignore everything but HDA controllers.
  //
  if (!EFI_ERROR (Status)
    && (HdaClassReg.Class != PCI_CLASS_MEDIA
      || HdaClassReg.SubClass != PCI_CLASS_MEDIA_MIXED_MODE)) {
    Status = EFI_UNSUPPORTED;
  }

  return Status;
}

EFI_STATUS
EFIAPI
HdaControllerDriverBindingStart (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   ControllerHandle,
  IN EFI_DEVICE_PATH_PROTOCOL     *RemainingDevicePath OPTIONAL
  )
{
  EFI_STATUS                Status;
  EFI_PCI_IO_PROTOCOL       *PciIo;
  EFI_DEVICE_PATH_PROTOCOL  *HdaControllerDevicePath;
  HDA_CONTROLLER_DEV        *HdaControllerDev;
  VOID                      *VMwareHda;
  UINT32                    OpenMode;

  DEBUG ((DEBUG_INFO, "HDA: Starting for %p\n", ControllerHandle));

  OpenMode = EFI_OPEN_PROTOCOL_BY_DRIVER;

  do {
    //
    // Open PCI I/O protocol.
    //
    Status = gBS->OpenProtocol (
      ControllerHandle,
      &gEfiPciIoProtocolGuid,
      (VOID**) &PciIo,
      This->DriverBindingHandle,
      ControllerHandle,
      OpenMode
      );

    if (EFI_ERROR (Status)) {
      if (Status == EFI_ACCESS_DENIED && OpenMode == EFI_OPEN_PROTOCOL_BY_DRIVER) {
        Status = gBS->HandleProtocol (
          ControllerHandle,
          &gVMwareHdaProtocolGuid,
          &VMwareHda
          );
        if (!EFI_ERROR (Status)) {
          DEBUG ((DEBUG_INFO, "HDA: Found VMware protocol, using GET mode\n"));
          OpenMode = EFI_OPEN_PROTOCOL_GET_PROTOCOL;
          continue;
        }
      }

      return Status;
    }
  } while (EFI_ERROR (Status));

  //
  // Open Device Path protocol.
  //
  Status = gBS->OpenProtocol (
    ControllerHandle,
    &gEfiDevicePathProtocolGuid,
    (VOID**) &HdaControllerDevicePath,
    This->DriverBindingHandle,
    ControllerHandle,
    OpenMode
    );
  if (EFI_ERROR (Status)) {
    goto CLOSE_PCIIO;
  }

  //
  // Allocate controller device.
  //
  HdaControllerDev = AllocateZeroPool (sizeof (HDA_CONTROLLER_DEV));
  if (HdaControllerDev == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto CLOSE_PCIIO;
  }

  //
  // Fill controller device data.
  //
  HdaControllerDev->Signature        = HDA_CONTROLLER_PRIVATE_DATA_SIGNATURE;
  HdaControllerDev->PciIo            = PciIo;
  HdaControllerDev->DevicePath       = HdaControllerDevicePath;
  HdaControllerDev->DriverBinding    = This;
  HdaControllerDev->ControllerHandle = ControllerHandle;
  HdaControllerDev->OpenMode         = OpenMode;
  InitializeSpinLock (&HdaControllerDev->SpinLock);

  //
  // Setup PCI hardware.
  //
  Status = HdaControllerInitPciHw (HdaControllerDev);
  if (EFI_ERROR (Status)) {
    goto FREE_CONTROLLER;
  }

  //
  // Get controller name.
  //
  HdaControllerGetName (HdaControllerDev);

  //
  // Reset controller.
  //
  Status = HdaControllerReset (HdaControllerDev);
  if (EFI_ERROR (Status)) {
    goto FREE_CONTROLLER;
  }

  //
  // Install info protocol.
  //
  Status = HdaControllerInstallProtocols (HdaControllerDev);
  if (EFI_ERROR (Status)) {
    goto FREE_CONTROLLER;
  }

  //
  // Initialize CORB and RIRB.
  //
  Status = HdaControllerInitCorb (HdaControllerDev);
  if (EFI_ERROR (Status)) {
    goto FREE_CONTROLLER;
  }

  Status = HdaControllerInitRirb (HdaControllerDev);
  if (EFI_ERROR (Status)) {
    goto FREE_CONTROLLER;
  }

  // needed for QEMU.
  // UINT16 dd = 0xFF;
  // PciIo->Mem.Write(PciIo, EfiPciIoWidthUint16, PCI_HDA_BAR, HDA_REG_RINTCNT, 1, &dd);

  //
  // Start CORB and RIRB.
  //
  Status = HdaControllerSetCorb (HdaControllerDev, TRUE);
  if (EFI_ERROR (Status)) {
    goto FREE_CONTROLLER;
  }

  Status = HdaControllerSetRirb (HdaControllerDev, TRUE);
  if (EFI_ERROR (Status)) {
    goto FREE_CONTROLLER;
  }

  //
  // Init streams.
  //
  Status = HdaControllerInitStreams (HdaControllerDev);
  if (EFI_ERROR (Status)) {
    goto FREE_CONTROLLER;
  }

  //
  // Scan for codecs.
  //
  Status = HdaControllerScanCodecs (HdaControllerDev);
  ASSERT_EFI_ERROR (Status);

  DEBUG ((DEBUG_INFO, "HDA: Starting done for %p\n", ControllerHandle));
  return EFI_SUCCESS;

FREE_CONTROLLER:

  //
  // Restore PCI attributes if needed.
  //
  if (HdaControllerDev->OriginalPciAttributesSaved) {
    PciIo->Attributes (
      PciIo,
      EfiPciIoAttributeOperationSet,
      HdaControllerDev->OriginalPciAttributes,
      NULL
      );
  }

  //
  // Free controller device.
  //
  HdaControllerCleanup (HdaControllerDev);

CLOSE_PCIIO:
  
  //
  // Close protocols.
  //
  if (OpenMode == EFI_OPEN_PROTOCOL_BY_DRIVER) {
    gBS->CloseProtocol (
      ControllerHandle,
      &gEfiDevicePathProtocolGuid,
      This->DriverBindingHandle,
      ControllerHandle
      );
    gBS->CloseProtocol (
      ControllerHandle,
      &gEfiPciIoProtocolGuid,
      This->DriverBindingHandle,
      ControllerHandle
      );
  }

  return Status;
}

EFI_STATUS
EFIAPI
HdaControllerDriverBindingStop (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   ControllerHandle,
  IN UINTN                        NumberOfChildren,
  IN EFI_HANDLE                   *ChildHandleBuffer OPTIONAL
  )
{
  EFI_STATUS          Status;
  HDA_CONTROLLER_DEV  *HdaControllerDev;
  UINT32              OpenMode;

  DEBUG ((DEBUG_INFO, "HDA: Stopping for %p\n", ControllerHandle));

  OpenMode = EFI_OPEN_PROTOCOL_BY_DRIVER;

  //
  // Get codec device.
  //
  Status = gBS->OpenProtocol (
    ControllerHandle,
    &gEfiCallerIdGuid,
    (VOID**) &HdaControllerDev,
    This->DriverBindingHandle,
    ControllerHandle,
    EFI_OPEN_PROTOCOL_GET_PROTOCOL
    );

  if (!EFI_ERROR (Status)) {
    //
    // Gather open mode.
    //
    OpenMode = HdaControllerDev->OpenMode;

    //
    // Ensure controller device is valid.
    //
    if (HdaControllerDev->Signature != HDA_CONTROLLER_PRIVATE_DATA_SIGNATURE) {
      return EFI_INVALID_PARAMETER;
    }

    //
    // Restore PCI attributes if needed.
    //
    if (HdaControllerDev->OriginalPciAttributesSaved) {
      HdaControllerDev->PciIo->Attributes (
        HdaControllerDev->PciIo,
        EfiPciIoAttributeOperationSet,
        HdaControllerDev->OriginalPciAttributes,
        NULL
        );
    }

    //
    // Cleanup controller.
    //
    HdaControllerCleanup (HdaControllerDev);
  }

  //
  // Close protocols.
  //
  if (OpenMode == EFI_OPEN_PROTOCOL_BY_DRIVER) {
    gBS->CloseProtocol (
      ControllerHandle,
      &gEfiDevicePathProtocolGuid,
      This->DriverBindingHandle,
      ControllerHandle
      );
    gBS->CloseProtocol (
      ControllerHandle,
      &gEfiPciIoProtocolGuid,
      This->DriverBindingHandle,
      ControllerHandle
      );
  }

  DEBUG ((DEBUG_INFO, "HDA: Stopping done for %p\n", ControllerHandle));

  return EFI_SUCCESS;
}
