/** @file
 *
*   Copyright (c) 2020, ARM Limited. All rights reserved.
 *  Copyright (c) 2017-2018, Andrey Warkentin <andrey.warkentin@gmail.com>
 *  Copyright (c) 2015-2016, Linaro Limited. All rights reserved.
 *
 *  SPDX-License-Identifier: BSD-2-Clause-Patent
 *
 **/

#include <IndustryStandard/Bcm2836.h>
#include "DwUsbHostDxe.h"
#include "DwcHw.h"

/*
 * Excessive (10s) timeout for reset.
 */
#define DW_HC_RESET_TIMEOUT_MS (10000)

  /*
   * TimerPeriodic to account for timeout processing
   * within DwHcTransfer.
   */
#define TimerForTransfer TimerRelative

/*
 * https://www.quicklogic.com/assets/pdf/data-sheets/QL-Hi-Speed-USB-2.0-OTG-Controller-Data-Sheet.pdf
 */

typedef enum {
  XFER_NOT_HALTED,
  XFER_ERROR,
  XFER_CSPLIT,
  XFER_NAK,
  XFER_STALL,
  XFER_FRMOVRUN,
  XFER_DONE
} CHANNEL_HALT_REASON;

typedef struct {
  BOOLEAN Splitting;
  BOOLEAN SplitStart;
  UINT32 Tries;
} SPLIT_CONTROL;

EFI_STATUS
DwHcInit (
  IN DWUSB_OTGHC_DEV *DwHc,
  IN EFI_EVENT       Timeout
  );

EFI_STATUS
DwCoreInit (
  IN DWUSB_OTGHC_DEV *DwHc,
  IN EFI_EVENT        Timeout
  );

EFI_STATUS
Wait4Bit (
  IN  EFI_EVENT Timeout,
  IN  UINT32    Reg,
  IN  UINT32    Mask,
  IN  BOOLEAN   Set
  )
{
  UINT32 Value;

  do {
    Value = MmioRead32 (Reg);
    if (!Set) {
      Value = ~Value;
    }

    if ((Value & Mask) == Mask) {
      return EFI_SUCCESS;
    }
  } while (EFI_ERROR (gBS->CheckEvent (Timeout)));

  return EFI_TIMEOUT;
}

CHANNEL_HALT_REASON
Wait4Chhltd (
  IN  DWUSB_OTGHC_DEV *DwHc,
  IN  EFI_EVENT       Timeout,
  IN  UINT32          Channel,
  IN  UINT32          *Sub,
  IN  UINT32          *Toggle,
  IN  BOOLEAN         IgnoreAck,
  IN  SPLIT_CONTROL   *Split
  )
{
  EFI_STATUS Status;
  UINT32  Hcint, Hctsiz;
  UINT32  HcintCompHltAck = DWC2_HCINT_XFERCOMP;

  MicroSecondDelay (100);
  Status = Wait4Bit (Timeout, DwHc->DwUsbBase + HCINT (Channel),
                     DWC2_HCINT_CHHLTD, 1);
  if (EFI_ERROR (Status)) {
    return XFER_NOT_HALTED;
  }

  MicroSecondDelay (100);
  Hcint = MmioRead32 (DwHc->DwUsbBase + HCINT (Channel));

  ASSERT ((Hcint & DWC2_HCINT_CHHLTD) != 0);
  Hcint &= ~DWC2_HCINT_CHHLTD;

  if (!IgnoreAck ||
      (Split->Splitting && Split->SplitStart)) {
    HcintCompHltAck |= DWC2_HCINT_ACK;
  } else {
    Hcint &= ~DWC2_HCINT_ACK;
  }

  if ((Hcint & DWC2_HCINT_XACTERR) != 0) {
    return XFER_ERROR;
  }

  if ((Hcint & DWC2_HCINT_NYET) != 0) {
    return XFER_CSPLIT;
  }

  if ((Hcint & DWC2_HCINT_NAK) != 0) {
    return XFER_NAK;
  }

  if ((Hcint & DWC2_HCINT_STALL) != 0) {
    return XFER_STALL;
  }

  if ((Hcint & DWC2_HCINT_FRMOVRUN) != 0) {
    return XFER_FRMOVRUN;
  }

  if (Split->Splitting &&
      Split->SplitStart &&
      ((Hcint & DWC2_HCINT_ACK) != 0)) {
    Split->SplitStart = FALSE;
    Split->Tries = 0;
    return XFER_CSPLIT;
  }

  if (Hcint != HcintCompHltAck) {
    DEBUG ((DEBUG_ERROR, "Wait4Chhltd: Channel %u HCINT 0x%x %a%a\n",
      Channel, Hcint,
      IgnoreAck ? "IgnoreAck " : "",
      Split->SplitStart ? "split start" :
      (Split->Splitting ? "split complete" : "")));
    return XFER_ERROR;
  }

  Hctsiz = MmioRead32 (DwHc->DwUsbBase + HCTSIZ (Channel));
  *Sub = (Hctsiz & DWC2_HCTSIZ_XFERSIZE_MASK) >> DWC2_HCTSIZ_XFERSIZE_OFFSET;
  *Toggle = (Hctsiz & DWC2_HCTSIZ_PID_MASK) >> DWC2_HCTSIZ_PID_OFFSET;

  return XFER_DONE;
}

VOID
DwOtgHcInit (
  IN  DWUSB_OTGHC_DEV    *DwHc,
  IN  UINT8              HcNum,
  IN  EFI_USB2_HC_TRANSACTION_TRANSLATOR  *Translator,
  IN  UINT8              DeviceSpeed,
  IN  UINT8              DevAddr,
  IN  UINT8              Endpoint,
  IN  UINT8              EpDir,
  IN  UINT8              EpType,
  IN  UINT16             MaxPacket,
  IN  SPLIT_CONTROL *SplitControl
  )
{
  UINT32 Split = 0;
  UINT32 Hcchar = (DevAddr << DWC2_HCCHAR_DEVADDR_OFFSET) |
    (Endpoint << DWC2_HCCHAR_EPNUM_OFFSET) |
    (EpDir << DWC2_HCCHAR_EPDIR_OFFSET) |
    (EpType << DWC2_HCCHAR_EPTYPE_OFFSET) |
    (MaxPacket << DWC2_HCCHAR_MPS_OFFSET) |
    ((DeviceSpeed == EFI_USB_SPEED_LOW) ? DWC2_HCCHAR_LSPDDEV : 0);

  MmioWrite32 (DwHc->DwUsbBase + HCINT (HcNum), 0x3FFF);

  MmioWrite32 (DwHc->DwUsbBase + HCCHAR (HcNum), Hcchar);

  if (SplitControl->Splitting) {
    Split = DWC2_HCSPLT_SPLTENA |
      ((Translator->TranslatorPortNumber) << DWC2_HCSPLT_PRTADDR_OFFSET) |
      ((Translator->TranslatorHubAddress) << DWC2_HCSPLT_HUBADDR_OFFSET);

    if (!SplitControl->SplitStart) {
      Split |= DWC2_HCSPLT_COMPSPLT;
    }
  }

  MmioWrite32 (DwHc->DwUsbBase + HCSPLT (HcNum), Split);
}

EFI_STATUS
DwCoreReset (
  IN  DWUSB_OTGHC_DEV *DwHc,
  IN  EFI_EVENT Timeout
  )
{
  EFI_STATUS Status;

  Status = Wait4Bit (Timeout, DwHc->DwUsbBase + GRSTCTL, DWC2_GRSTCTL_AHBIDLE, 1);
  if (Status) {
    DEBUG ((DEBUG_ERROR, "DwCoreReset: AHBIDLE Timeout!\n"));
    return Status;
  }

  MmioWrite32 (DwHc->DwUsbBase + GRSTCTL, DWC2_GRSTCTL_CSFTRST);

  Status = Wait4Bit (Timeout, DwHc->DwUsbBase + GRSTCTL, DWC2_GRSTCTL_CSFTRST, 0);
  if (Status) {
    DEBUG ((DEBUG_ERROR, "DwCoreReset: CSFTRST Timeout!\n"));
    return Status;
  }

  MicroSecondDelay (100000);
  return EFI_SUCCESS;
}

STATIC
EFI_STATUS
DwHcTransfer (
  IN      DWUSB_OTGHC_DEV        *DwHc,
  IN      EFI_EVENT              Timeout,
  IN      UINT32                 Channel,
  IN      EFI_USB2_HC_TRANSACTION_TRANSLATOR *Translator,
  IN      UINT8                  DeviceSpeed,
  IN      UINT8                  DeviceAddress,
  IN      UINTN                  MaximumPacketLength,
  IN  OUT UINT32                *Pid,
  IN      UINT32                 TransferDirection,
  IN  OUT VOID                  *Data,
  IN  OUT UINTN                 *DataLength,
  IN      UINT32                 EpAddress,
  IN      UINT32                 EpType,
  OUT     UINT32                 *TransferResult,
  IN      BOOLEAN                IgnoreAck
  )
{
  UINT32                          TxferLen;
  UINT32                          Done = 0;
  UINT32                          NumPackets;
  UINT32                          Sub;
  UINT32                          Ret = 0;
  UINT32                          StopTransfer = 0;
  EFI_STATUS                      Status = EFI_SUCCESS;
  SPLIT_CONTROL                   Split = { 0 };

  EFI_TPL Tpl = gBS->RaiseTPL (TPL_NOTIFY);

  *TransferResult = EFI_USB_NOERROR;

  do {
  RestartXfer:
    if (DeviceSpeed == EFI_USB_SPEED_LOW ||
        DeviceSpeed == EFI_USB_SPEED_FULL) {
      Split.Splitting = TRUE;
      Split.SplitStart = TRUE;
      Split.Tries = 0;
    }

    TxferLen = *DataLength - Done;

    if (TxferLen > DWC2_MAX_TRANSFER_SIZE) {
      TxferLen = DWC2_MAX_TRANSFER_SIZE - MaximumPacketLength + 1;
    }

    if (TxferLen > DWC2_DATA_BUF_SIZE) {
      TxferLen = DWC2_DATA_BUF_SIZE - MaximumPacketLength + 1;
    }

    if (Split.Splitting || TxferLen == 0) {
      NumPackets = 1;
    } else {
      NumPackets = (TxferLen + MaximumPacketLength - 1) / MaximumPacketLength;
      if (NumPackets > DWC2_MAX_PACKET_COUNT) {
        NumPackets = DWC2_MAX_PACKET_COUNT;
        TxferLen = NumPackets * MaximumPacketLength;
      }
    }

    if (TransferDirection) { // in
      TxferLen = NumPackets * MaximumPacketLength;
    } else {
      CopyMem (DwHc->AlignedBuffer, Data + Done, TxferLen);
      ArmDataSynchronizationBarrier ();
    }

  RestartChannel:
    MmioWrite32 (DwHc->DwUsbBase + HCDMA (Channel),
      (UINTN)DwHc->AlignedBufferBusAddress);

    DwOtgHcInit (DwHc, Channel, Translator, DeviceSpeed,
      DeviceAddress, EpAddress,
      TransferDirection, EpType,
      MaximumPacketLength, &Split);

    MmioWrite32 (DwHc->DwUsbBase + HCTSIZ (Channel),
      (TxferLen << DWC2_HCTSIZ_XFERSIZE_OFFSET) |
      (NumPackets << DWC2_HCTSIZ_PKTCNT_OFFSET) |
      (*Pid << DWC2_HCTSIZ_PID_OFFSET));

    MmioAndThenOr32 (DwHc->DwUsbBase + HCCHAR (Channel),
      ~(DWC2_HCCHAR_MULTICNT_MASK |
        DWC2_HCCHAR_CHEN |
        DWC2_HCCHAR_CHDIS),
        ((1 << DWC2_HCCHAR_MULTICNT_OFFSET) |
          DWC2_HCCHAR_CHEN));

    Ret = Wait4Chhltd (DwHc, Timeout, Channel, &Sub, Pid, IgnoreAck, &Split);

    if (Ret == XFER_NOT_HALTED) {
      *TransferResult = EFI_USB_ERR_TIMEOUT;
      MmioOr32 (DwHc->DwUsbBase + HCCHAR (Channel), DWC2_HCCHAR_CHDIS);
      Status = gBS->SetTimer (Timeout, TimerRelative,
                              EFI_TIMER_PERIOD_MILLISECONDS (1));
      ASSERT_EFI_ERROR (Status);
      if (EFI_ERROR (Status)) {
        break;
      }
      Status = Wait4Bit (Timeout, DwHc->DwUsbBase + HCINT (Channel),
                         DWC2_HCINT_CHHLTD, 1);
      if (Status == EFI_SUCCESS) {
        Status = EFI_TIMEOUT;
      } else {
        DEBUG ((DEBUG_ERROR, "Channel %u did not halt\n", Channel));
        Status = EFI_DEVICE_ERROR;
      }
      break;
    } else if (Ret == XFER_STALL) {
      *TransferResult = EFI_USB_ERR_STALL;
      Status = EFI_DEVICE_ERROR;
      break;
    } else if (Ret == XFER_CSPLIT) {
      ASSERT (Split.Splitting);

      if (Split.Tries++ < 3) {
        goto RestartChannel;
      }

      goto RestartXfer;
    } else if (Ret == XFER_ERROR) {
      *TransferResult =
        EFI_USB_ERR_CRC |
        EFI_USB_ERR_TIMEOUT |
        EFI_USB_ERR_BITSTUFF |
        EFI_USB_ERR_SYSTEM;
      Status = EFI_DEVICE_ERROR;
      break;
    } else if (Ret == XFER_FRMOVRUN) {
      goto RestartChannel;
    } else if (Ret == XFER_NAK) {
      if (Split.Splitting &&
          (EpType == DWC2_HCCHAR_EPTYPE_CONTROL)) {
        goto RestartXfer;
      }

      *TransferResult = EFI_USB_ERR_NAK;
      Status = EFI_DEVICE_ERROR;
      break;
    }

    if (TransferDirection) { // in
      ArmDataSynchronizationBarrier ();
      TxferLen -= Sub;
      CopyMem (Data + Done, DwHc->AlignedBuffer, TxferLen);
      if (Sub) {
        StopTransfer = 1;
      }
    }

    Done += TxferLen;
  } while (Done < *DataLength && !StopTransfer);

  MmioWrite32 (DwHc->DwUsbBase + HCINTMSK (Channel), 0);
  MmioWrite32 (DwHc->DwUsbBase + HCINT (Channel), 0xFFFFFFFF);

  *DataLength = Done;

  gBS->RestoreTPL (Tpl);

  ASSERT (!EFI_ERROR (Status) || *TransferResult != EFI_USB_NOERROR);

  return Status;
}

STATIC
DWUSB_DEFERRED_REQ *
DwHcFindDeferredTransfer (
  IN  DWUSB_OTGHC_DEV *DwHc,
  IN  UINT8 DeviceAddress,
  IN  UINT8 EndPointAddress
  )
{
  LIST_ENTRY *Entry;

  EFI_LIST_FOR_EACH (Entry, &DwHc->DeferredList) {
    DWUSB_DEFERRED_REQ *Req = EFI_LIST_CONTAINER (Entry, DWUSB_DEFERRED_REQ, List);

    if (Req->DeviceAddress == DeviceAddress &&
        Req->EpAddress == (EndPointAddress & 0xF) &&
        Req->TransferDirection == ((EndPointAddress >> 7) & 0x01)) {
      return Req;
    }
  }

  return NULL;
}

STATIC
VOID
DwHcDeferredTransfer (
  IN  DWUSB_DEFERRED_REQ *Req
  )
{
  EFI_STATUS Status;
  EFI_EVENT TimeoutEvt = NULL;

  Status = gBS->CreateEvent (EVT_TIMER, 0, NULL, NULL, &TimeoutEvt);
  ASSERT_EFI_ERROR (Status);
  if (EFI_ERROR (Status)) {
    goto Exit;
  }

  Status = gBS->SetTimer (TimeoutEvt, TimerForTransfer,
                  EFI_TIMER_PERIOD_MILLISECONDS (Req->TimeOut));
  ASSERT_EFI_ERROR (Status);
  if (EFI_ERROR (Status)) {
    goto Exit;
  }

  Req->TransferResult = EFI_USB_NOERROR;
  Status = DwHcTransfer (Req->DwHc, TimeoutEvt,
             Req->Channel, Req->Translator,
             Req->DeviceSpeed, Req->DeviceAddress,
             Req->MaximumPacketLength, &Req->Pid,
             Req->TransferDirection, Req->Data, &Req->DataLength,
             Req->EpAddress, Req->EpType, &Req->TransferResult,
             Req->IgnoreAck);

  if (Req->EpType == DWC2_HCCHAR_EPTYPE_INTR &&
      Status == EFI_DEVICE_ERROR &&
      Req->TransferResult == EFI_USB_ERR_NAK) {
    /*
     * Swallow the NAK, the upper layer expects us to resubmit automatically.
     */
    goto Exit;
  }

  Req->CallbackFunction (Req->Data, Req->DataLength,
         Req->CallbackContext,
         Req->TransferResult);
Exit:
  if (TimeoutEvt != NULL) {
    gBS->CloseEvent (TimeoutEvt);
  }
}

/**
   EFI_USB2_HC_PROTOCOL APIs
**/

EFI_STATUS
EFIAPI
DwHcGetCapability (
  IN  EFI_USB2_HC_PROTOCOL *This,
  OUT UINT8                *MaxSpeed,
  OUT UINT8                *PortNumber,
  OUT UINT8                *Is64BitCapable
  )
{
  if ((MaxSpeed == NULL) || (PortNumber == NULL) || (Is64BitCapable == NULL)) {
    return EFI_INVALID_PARAMETER;
  }

  *MaxSpeed = EFI_USB_SPEED_HIGH;
  *PortNumber = 1;
  *Is64BitCapable = 1;

  return EFI_SUCCESS;
}

EFI_STATUS
EFIAPI
DwHcReset (
  IN EFI_USB2_HC_PROTOCOL *This,
  IN UINT16               Attributes
  )
{
  EFI_STATUS Status;
  EFI_EVENT TimeoutEvt = NULL;

  DWUSB_OTGHC_DEV *DwHc;
  DwHc = DWHC_FROM_THIS (This);

  switch (Attributes) {
  case EFI_USB_HC_RESET_GLOBAL:
  case EFI_USB_HC_RESET_HOST_CONTROLLER:
    break;

  case EFI_USB_HC_RESET_GLOBAL_WITH_DEBUG:
  case EFI_USB_HC_RESET_HOST_WITH_DEBUG:
    Status = EFI_UNSUPPORTED;
    goto Exit;

  default:
    Status = EFI_INVALID_PARAMETER;
    goto Exit;
  }

  Status = gBS->CreateEvent (EVT_TIMER, 0, NULL, NULL, &TimeoutEvt);
  ASSERT_EFI_ERROR (Status);
  if (EFI_ERROR (Status)) {
    goto Exit;
  }

  Status = gBS->SetTimer (TimeoutEvt, TimerRelative,
                  EFI_TIMER_PERIOD_MILLISECONDS (DW_HC_RESET_TIMEOUT_MS));
  ASSERT_EFI_ERROR (Status);
  if (EFI_ERROR (Status)) {
    goto Exit;
  }

  Status = DwCoreInit (DwHc, TimeoutEvt);
  if (Status != EFI_SUCCESS) {
    DEBUG ((DEBUG_ERROR, "DwCoreInit failed\n"));
    goto Exit;
  }

  Status = DwHcInit (DwHc, TimeoutEvt);
  if (Status != EFI_SUCCESS) {
    DEBUG ((DEBUG_ERROR, "DwHcInit failed\n"));
    goto Exit;
  }

  MmioAndThenOr32 (DwHc->DwUsbBase + HPRT0,
    ~(DWC2_HPRT0_PRTENA | DWC2_HPRT0_PRTCONNDET |
      DWC2_HPRT0_PRTENCHNG | DWC2_HPRT0_PRTOVRCURRCHNG),
    DWC2_HPRT0_PRTRST);

  MicroSecondDelay (50000);

  MmioAnd32 (DwHc->DwUsbBase + HPRT0, ~(DWC2_HPRT0_PRTENA | DWC2_HPRT0_PRTCONNDET |
    DWC2_HPRT0_PRTENCHNG | DWC2_HPRT0_PRTOVRCURRCHNG |
    DWC2_HPRT0_PRTRST));

Exit:
  if (TimeoutEvt != NULL) {
    gBS->CloseEvent (TimeoutEvt);
  }

  return Status;
}

EFI_STATUS
EFIAPI
DwHcGetState (
  IN  EFI_USB2_HC_PROTOCOL *This,
  OUT EFI_USB_HC_STATE     *State
  )
{
  DWUSB_OTGHC_DEV *DwHc;

  DwHc = DWHC_FROM_THIS (This);

  if (State == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  *State = DwHc->DwHcState;

  return EFI_SUCCESS;
}

EFI_STATUS
EFIAPI
DwHcSetState (
  IN  EFI_USB2_HC_PROTOCOL *This,
  IN  EFI_USB_HC_STATE     State
  )
{
  DWUSB_OTGHC_DEV   *DwHc;
  EFI_STATUS        Status;

  DwHc = DWHC_FROM_THIS (This);

  switch (State) {
  case EfiUsbHcStateHalt:
  case EfiUsbHcStateOperational:
  case EfiUsbHcStateSuspend:
    DwHc->DwHcState = State;
    Status = EFI_SUCCESS;
    break;

  default:
    Status = EFI_INVALID_PARAMETER;
    break;
  }

  return Status;
}

EFI_STATUS
EFIAPI
DwHcGetRootHubPortStatus (
  IN  EFI_USB2_HC_PROTOCOL *This,
  IN  UINT8                PortNumber,
  OUT EFI_USB_PORT_STATUS  *PortStatus
  )
{
  DWUSB_OTGHC_DEV *DwHc;
  UINT32          Hprt0;

  if (PortNumber > DWC2_HC_PORT) {
    return EFI_INVALID_PARAMETER;
  }

  if (PortStatus == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  DwHc = DWHC_FROM_THIS (This);

  PortStatus->PortStatus = 0;
  PortStatus->PortChangeStatus = 0;
  Hprt0 = MmioRead32 (DwHc->DwUsbBase + HPRT0);

  if (Hprt0 & DWC2_HPRT0_PRTCONNSTS) {
    PortStatus->PortStatus |= USB_PORT_STAT_CONNECTION;
  }

  if (Hprt0 & DWC2_HPRT0_PRTENA) {
    PortStatus->PortStatus |= USB_PORT_STAT_ENABLE;
  }

  if (Hprt0 & DWC2_HPRT0_PRTSUSP) {
    PortStatus->PortStatus |= USB_PORT_STAT_SUSPEND;
  }

  if (Hprt0 & DWC2_HPRT0_PRTOVRCURRACT) {
    PortStatus->PortStatus |= USB_PORT_STAT_OVERCURRENT;
  }

  if (Hprt0 & DWC2_HPRT0_PRTRST) {
    PortStatus->PortStatus |= USB_PORT_STAT_RESET;
  }

  if (Hprt0 & DWC2_HPRT0_PRTPWR) {
    PortStatus->PortStatus |= USB_PORT_STAT_POWER;
  }

  PortStatus->PortStatus |= USB_PORT_STAT_HIGH_SPEED;

  if (Hprt0 & DWC2_HPRT0_PRTENCHNG) {
    //              PortStatus->PortChangeStatus |= USB_PORT_STAT_C_ENABLE;
  }

  if (Hprt0 & DWC2_HPRT0_PRTCONNDET) {
    PortStatus->PortChangeStatus |= USB_PORT_STAT_C_CONNECTION;
  }

  if (Hprt0 & DWC2_HPRT0_PRTOVRCURRCHNG) {
    PortStatus->PortChangeStatus |= USB_PORT_STAT_C_OVERCURRENT;
  }

  return EFI_SUCCESS;
}

EFI_STATUS
EFIAPI
DwHcSetRootHubPortFeature (
  IN  EFI_USB2_HC_PROTOCOL *This,
  IN  UINT8                PortNumber,
  IN  EFI_USB_PORT_FEATURE PortFeature
  )
{
  DWUSB_OTGHC_DEV         *DwHc;
  UINT32                  Hprt0;
  EFI_STATUS              Status = EFI_SUCCESS;

  if (PortNumber > DWC2_HC_PORT) {
    Status = EFI_INVALID_PARAMETER;
    goto Exit;
  }

  DwHc = DWHC_FROM_THIS (This);

  switch (PortFeature) {
  case EfiUsbPortEnable:
    break;
  case EfiUsbPortSuspend:
    Hprt0 = MmioRead32 (DwHc->DwUsbBase + HPRT0);
    Hprt0 &= ~(DWC2_HPRT0_PRTENA | DWC2_HPRT0_PRTCONNDET |
               DWC2_HPRT0_PRTENCHNG | DWC2_HPRT0_PRTOVRCURRCHNG);
    Hprt0 |= DWC2_HPRT0_PRTSUSP;
    MmioWrite32 (DwHc->DwUsbBase + HPRT0, Hprt0);
    break;
  case EfiUsbPortReset:
    MmioAndThenOr32 (DwHc->DwUsbBase + HPRT0,
      ~(DWC2_HPRT0_PRTENA | DWC2_HPRT0_PRTCONNDET |
        DWC2_HPRT0_PRTENCHNG | DWC2_HPRT0_PRTOVRCURRCHNG),
      DWC2_HPRT0_PRTRST);
    MicroSecondDelay (50000);
    MmioAnd32 (DwHc->DwUsbBase + HPRT0, ~DWC2_HPRT0_PRTRST);
    break;
  case EfiUsbPortPower:
    Hprt0 = MmioRead32 (DwHc->DwUsbBase + HPRT0);
    Hprt0 &= ~(DWC2_HPRT0_PRTENA | DWC2_HPRT0_PRTCONNDET |
               DWC2_HPRT0_PRTENCHNG | DWC2_HPRT0_PRTOVRCURRCHNG);
    Hprt0 |= DWC2_HPRT0_PRTPWR;
    MmioWrite32 (DwHc->DwUsbBase + HPRT0, Hprt0);
    break;
  case EfiUsbPortOwner:
    break;
  default:
    Status = EFI_INVALID_PARAMETER;
    break;
  }

Exit:
  return Status;
}

EFI_STATUS
EFIAPI
DwHcClearRootHubPortFeature (
  IN  EFI_USB2_HC_PROTOCOL *This,
  IN  UINT8                PortNumber,
  IN  EFI_USB_PORT_FEATURE PortFeature
  )
{
  DWUSB_OTGHC_DEV         *DwHc;
  UINT32                  Hprt0;
  EFI_STATUS              Status = EFI_SUCCESS;

  if (PortNumber > DWC2_HC_PORT) {
    Status = EFI_INVALID_PARAMETER;
    goto Exit;
  }

  DwHc = DWHC_FROM_THIS (This);

  switch (PortFeature) {
  case EfiUsbPortEnable:
    Hprt0 = MmioRead32 (DwHc->DwUsbBase + HPRT0);
    Hprt0 &= ~(DWC2_HPRT0_PRTENA | DWC2_HPRT0_PRTCONNDET |
               DWC2_HPRT0_PRTENCHNG | DWC2_HPRT0_PRTOVRCURRCHNG);
    Hprt0 |= DWC2_HPRT0_PRTENA;
    MmioWrite32 (DwHc->DwUsbBase + HPRT0, Hprt0);
    break;
  case EfiUsbPortReset:
    MmioAndThenOr32 (DwHc->DwUsbBase + HPRT0,
      ~(DWC2_HPRT0_PRTENA | DWC2_HPRT0_PRTCONNDET |
        DWC2_HPRT0_PRTENCHNG | DWC2_HPRT0_PRTOVRCURRCHNG),
      DWC2_HPRT0_PRTRST);
    MicroSecondDelay (50000);
    MmioAnd32 (DwHc->DwUsbBase + HPRT0, ~DWC2_HPRT0_PRTRST);
    break;
  case EfiUsbPortSuspend:
    MmioWrite32 (DwHc->DwUsbBase + PCGCCTL, 0);
    MicroSecondDelay (40000);
    Hprt0 = MmioRead32 (DwHc->DwUsbBase + HPRT0);
    Hprt0 &= ~(DWC2_HPRT0_PRTENA | DWC2_HPRT0_PRTCONNDET |
               DWC2_HPRT0_PRTENCHNG | DWC2_HPRT0_PRTOVRCURRCHNG);
    Hprt0 |= DWC2_HPRT0_PRTRES;
    MmioWrite32 (DwHc->DwUsbBase + HPRT0, Hprt0);
    Hprt0 &= ~DWC2_HPRT0_PRTSUSP;
    MicroSecondDelay (150000);
    Hprt0 &= ~DWC2_HPRT0_PRTRES;
    MmioWrite32 (DwHc->DwUsbBase + HPRT0, Hprt0);
    break;
  case EfiUsbPortPower:
    Hprt0 = MmioRead32 (DwHc->DwUsbBase + HPRT0);
    Hprt0 &= ~(DWC2_HPRT0_PRTENA | DWC2_HPRT0_PRTCONNDET |
               DWC2_HPRT0_PRTENCHNG | DWC2_HPRT0_PRTOVRCURRCHNG);
    Hprt0 &= ~DWC2_HPRT0_PRTPWR;
    MmioWrite32 (DwHc->DwUsbBase + HPRT0, Hprt0);
    break;
  case EfiUsbPortOwner:
    break;
  case EfiUsbPortConnectChange:
    Hprt0 = MmioRead32 (DwHc->DwUsbBase + HPRT0);
    Hprt0 &= ~DWC2_HPRT0_PRTCONNDET;
    MmioWrite32 (DwHc->DwUsbBase + HPRT0, Hprt0);
    break;
  case EfiUsbPortResetChange:
    break;
  case EfiUsbPortEnableChange:
    Hprt0 = MmioRead32 (DwHc->DwUsbBase + HPRT0);
    Hprt0 &= ~DWC2_HPRT0_PRTENCHNG;
    MmioWrite32 (DwHc->DwUsbBase + HPRT0, Hprt0);
    break;
  case EfiUsbPortSuspendChange:
    break;
  case EfiUsbPortOverCurrentChange:
    Hprt0 = MmioRead32 (DwHc->DwUsbBase + HPRT0);
    Hprt0 &= ~DWC2_HPRT0_PRTOVRCURRCHNG;
    MmioWrite32 (DwHc->DwUsbBase + HPRT0, Hprt0);
    break;
  default:
    Status = EFI_INVALID_PARAMETER;
    break;
  }

Exit:
  return Status;
}

EFI_STATUS
EFIAPI
DwHcControlTransfer (
  IN  EFI_USB2_HC_PROTOCOL               *This,
  IN  UINT8                              DeviceAddress,
  IN  UINT8                              DeviceSpeed,
  IN  UINTN                              MaximumPacketLength,
  IN  EFI_USB_DEVICE_REQUEST             *Request,
  IN  EFI_USB_DATA_DIRECTION             TransferDirection,
  IN  OUT VOID                           *Data,
  IN  OUT UINTN                          *DataLength,
  IN  UINTN                              TimeOut,
  IN  EFI_USB2_HC_TRANSACTION_TRANSLATOR *Translator,
  OUT UINT32                             *TransferResult
  )
{
  DWUSB_OTGHC_DEV         *DwHc;
  EFI_STATUS              Status;
  UINT32                  Pid;
  UINTN                   Length;
  EFI_USB_DATA_DIRECTION  StatusDirection;
  UINT32                  Direction;
  EFI_EVENT TimeoutEvt = NULL;

  if ((Request == NULL) || (TransferResult == NULL)) {
    return EFI_INVALID_PARAMETER;
  }

  if ((TransferDirection != EfiUsbDataIn) &&
      (TransferDirection != EfiUsbDataOut) &&
      (TransferDirection != EfiUsbNoData)) {
    return EFI_INVALID_PARAMETER;
  }

  if ((TransferDirection == EfiUsbNoData) &&
      ((Data != NULL) || (*DataLength != 0))) {
    return EFI_INVALID_PARAMETER;
  }

  if ((TransferDirection != EfiUsbNoData) &&
      ((Data == NULL) || (*DataLength == 0))) {
    return EFI_INVALID_PARAMETER;
  }

  if ((MaximumPacketLength != 8) && (MaximumPacketLength != 16) &&
      (MaximumPacketLength != 32) && (MaximumPacketLength != 64)) {
    return EFI_INVALID_PARAMETER;
  }

  if ((DeviceSpeed == EFI_USB_SPEED_LOW) && (MaximumPacketLength != 8)) {
    return EFI_INVALID_PARAMETER;
  }

  DwHc = DWHC_FROM_THIS (This);

  Status = gBS->CreateEvent (EVT_TIMER, 0, NULL, NULL, &TimeoutEvt);
  ASSERT_EFI_ERROR (Status);
  if (EFI_ERROR (Status)) {
    goto Exit;
  }

  Status = gBS->SetTimer (TimeoutEvt, TimerForTransfer,
                  EFI_TIMER_PERIOD_MILLISECONDS (TimeOut));
  ASSERT_EFI_ERROR (Status);
  if (EFI_ERROR (Status)) {
    goto Exit;
  }

  Pid = DWC2_HC_PID_SETUP;
  Length = 8;
  Status = DwHcTransfer (DwHc, TimeoutEvt,
             DWC2_HC_CHANNEL, Translator, DeviceSpeed,
             DeviceAddress, MaximumPacketLength, &Pid, 0,
             Request, &Length, 0, DWC2_HCCHAR_EPTYPE_CONTROL,
             TransferResult, 1);

  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "Setup Stage Error for device 0x%x: 0x%x\n",
      DeviceAddress, *TransferResult));
    goto Exit;
  }

  if (Data) {
    Pid = DWC2_HC_PID_DATA1;

    if (TransferDirection == EfiUsbDataIn) {
      Direction = 1;
    } else {
      Direction = 0;
    }

    Status = DwHcTransfer (DwHc, TimeoutEvt,
               DWC2_HC_CHANNEL, Translator, DeviceSpeed,
               DeviceAddress, MaximumPacketLength, &Pid,
               Direction, Data, DataLength, 0,
               DWC2_HCCHAR_EPTYPE_CONTROL,
               TransferResult, 0);

    if (EFI_ERROR (Status)) {
      DEBUG ((DEBUG_ERROR, "Data Stage Error for device 0x%x: 0x%x\n",
        DeviceAddress, *TransferResult));
      goto Exit;
    }
  }

  if ((TransferDirection == EfiUsbDataOut) ||
      (TransferDirection == EfiUsbNoData)) {
    StatusDirection = 1;
  } else {
    StatusDirection = 0;
  }

  Pid = DWC2_HC_PID_DATA1;
  Length = 0;
  Status = DwHcTransfer (DwHc, TimeoutEvt,
             DWC2_HC_CHANNEL, Translator, DeviceSpeed,
             DeviceAddress, MaximumPacketLength, &Pid,
             StatusDirection, DwHc->StatusBuffer, &Length, 0,
             DWC2_HCCHAR_EPTYPE_CONTROL, TransferResult, 1);

  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "Status Stage Error for 0x%x: 0x%x\n",
      DeviceAddress, *TransferResult));
    goto Exit;
  }

Exit:
  if (TimeoutEvt != NULL) {
    gBS->CloseEvent (TimeoutEvt);
  }

  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "RequestType 0x%x\n", Request->RequestType));
    DEBUG ((DEBUG_ERROR, "Request 0x%x\n", Request->Request));
    DEBUG ((DEBUG_ERROR, "Value 0x%x\n", Request->Value));
    DEBUG ((DEBUG_ERROR, "Index 0x%x\n", Request->Index));
    DEBUG ((DEBUG_ERROR, "Length 0x%x\n", Request->Length));
  }
  return Status;
}

EFI_STATUS
EFIAPI
DwHcBulkTransfer (
  IN  EFI_USB2_HC_PROTOCOL               *This,
  IN  UINT8                              DeviceAddress,
  IN  UINT8                              EndPointAddress,
  IN  UINT8                              DeviceSpeed,
  IN  UINTN                              MaximumPacketLength,
  IN  UINT8                              DataBuffersNumber,
  IN  OUT VOID                           *Data[EFI_USB_MAX_BULK_BUFFER_NUM],
  IN  OUT UINTN                          *DataLength,
  IN  OUT UINT8                          *DataToggle,
  IN  UINTN                              TimeOut,
  IN  EFI_USB2_HC_TRANSACTION_TRANSLATOR *Translator,
  OUT UINT32                             *TransferResult
  )
{
  DWUSB_OTGHC_DEV         *DwHc;
  EFI_STATUS              Status;
  UINT8                   TransferDirection;
  UINT8                   EpAddress;
  UINT32                  Pid;
  EFI_EVENT TimeoutEvt = NULL;

  if ((Data == NULL) || (Data[0] == NULL) ||
      (DataLength == NULL) || (*DataLength == 0) ||
      (TransferResult == NULL)) {
    return EFI_INVALID_PARAMETER;
  }

  if ((*DataToggle != 0) && (*DataToggle != 1)) {
    return EFI_INVALID_PARAMETER;
  }

  if ((DeviceSpeed == EFI_USB_SPEED_LOW) ||
      (DeviceSpeed == EFI_USB_SPEED_SUPER)) {
    return EFI_INVALID_PARAMETER;
  }

  if (((DeviceSpeed == EFI_USB_SPEED_FULL) && (MaximumPacketLength > 64)) ||
      ((DeviceSpeed == EFI_USB_SPEED_HIGH) && (MaximumPacketLength > 512)))
    return EFI_INVALID_PARAMETER;

  DwHc = DWHC_FROM_THIS (This);

  Status = gBS->CreateEvent (EVT_TIMER, 0, NULL, NULL, &TimeoutEvt);
  ASSERT_EFI_ERROR (Status);
  if (EFI_ERROR (Status)) {
    goto Exit;
  }

  Status = gBS->SetTimer (TimeoutEvt, TimerForTransfer,
                  EFI_TIMER_PERIOD_MILLISECONDS (TimeOut));
  ASSERT_EFI_ERROR (Status);
  if (EFI_ERROR (Status)) {
    goto Exit;
  }

  Status = EFI_DEVICE_ERROR;
  TransferDirection = (EndPointAddress >> 7) & 0x01;
  EpAddress = EndPointAddress & 0x0F;
  Pid = (*DataToggle << 1);

  Status = DwHcTransfer (DwHc, TimeoutEvt,
             DWC2_HC_CHANNEL_BULK, Translator, DeviceSpeed,
             DeviceAddress, MaximumPacketLength, &Pid,
             TransferDirection, Data[0], DataLength, EpAddress,
             DWC2_HCCHAR_EPTYPE_BULK, TransferResult, 1);

  *DataToggle = (Pid >> 1);

Exit:
  if (TimeoutEvt != NULL) {
    gBS->CloseEvent (TimeoutEvt);
  }

  return Status;
}

EFI_STATUS
EFIAPI
DwHcAsyncInterruptTransfer (
  IN  EFI_USB2_HC_PROTOCOL               *This,
  IN  UINT8                              DeviceAddress,
  IN  UINT8                              EndPointAddress,
  IN  UINT8                              DeviceSpeed,
  IN  UINTN                              MaximumPacketLength,
  IN  BOOLEAN                            IsNewTransfer,
  IN  OUT UINT8                          *DataToggle,
  IN  UINTN                              PollingInterval,
  IN  UINTN                              DataLength,
  IN  EFI_USB2_HC_TRANSACTION_TRANSLATOR *Translator,
  IN  EFI_ASYNC_USB_TRANSFER_CALLBACK    CallbackFunction,
  IN  VOID                               *Context OPTIONAL
  )
{
  DWUSB_OTGHC_DEV                 *DwHc;
  EFI_STATUS                      Status;
  EFI_TPL                         PreviousTpl;
  VOID                            *Data = NULL;
  DWUSB_DEFERRED_REQ              *FoundReq = NULL;
  DWUSB_DEFERRED_REQ              *NewReq = NULL;

  if (!(EndPointAddress & USB_ENDPOINT_DIR_IN)) {
    return EFI_INVALID_PARAMETER;
  }

  DwHc = DWHC_FROM_THIS (This);

  PreviousTpl = gBS->RaiseTPL (TPL_NOTIFY);
  FoundReq = DwHcFindDeferredTransfer (DwHc, DeviceAddress, EndPointAddress);

  if (IsNewTransfer) {
    if (FoundReq != NULL) {
      Status = EFI_INVALID_PARAMETER;
      goto Done;
    }

    if (DataLength == 0) {
      Status = EFI_INVALID_PARAMETER;
      goto Done;
    }

    if ((*DataToggle != 1) && (*DataToggle != 0)) {
      Status = EFI_INVALID_PARAMETER;
      goto Done;
    }

    if ((PollingInterval > 255) || (PollingInterval < 1)) {
      Status = EFI_INVALID_PARAMETER;
      goto Done;
    }

    if (CallbackFunction == NULL) {
      Status = EFI_INVALID_PARAMETER;
      goto Done;
    }
  }

  if (!IsNewTransfer) {
    if (FoundReq == NULL) {
      DEBUG ((DEBUG_ERROR, "%u:%u> transfer not found\n", DeviceAddress, EndPointAddress & 0xF));
      Status = EFI_INVALID_PARAMETER;
      goto Done;
    }

    *DataToggle = FoundReq->Pid >> 1;
    FreePool (FoundReq->Data);

    RemoveEntryList (&FoundReq->List);
    FreePool (FoundReq);

    Status = EFI_SUCCESS;
    goto Done;
  }

  NewReq = AllocateZeroPool (sizeof *NewReq);
  if (NewReq == NULL) {
    DEBUG ((DEBUG_ERROR, "DwHcAsyncInterruptTransfer: failed to allocate req"));
    Status = EFI_OUT_OF_RESOURCES;
    goto Done;
  }

  Data = AllocateZeroPool (DataLength);
  if (Data == NULL) {
    DEBUG ((DEBUG_ERROR, "DwHcAsyncInterruptTransfer: failed to allocate buffer\n"));
    Status = EFI_OUT_OF_RESOURCES;
    goto Done;
  }

  InitializeListHead (&NewReq->List);

  NewReq->FrameInterval = PollingInterval;
  NewReq->TargetFrame = DwHc->CurrentFrame +
    NewReq->FrameInterval;

  NewReq->DwHc = DwHc;
  NewReq->Channel = DWC2_HC_CHANNEL_ASYNC;
  NewReq->Translator = Translator;
  NewReq->DeviceSpeed = DeviceSpeed;
  NewReq->DeviceAddress = DeviceAddress;
  NewReq->MaximumPacketLength = MaximumPacketLength;
  NewReq->TransferDirection = (EndPointAddress >> 7) & 0x01;
  NewReq->Data = Data;
  NewReq->DataLength = DataLength;
  NewReq->Pid = *DataToggle << 1;
  NewReq->EpAddress = EndPointAddress & 0x0F;
  NewReq->EpType = DWC2_HCCHAR_EPTYPE_INTR;
  NewReq->IgnoreAck = FALSE;
  NewReq->CallbackFunction = CallbackFunction;
  NewReq->CallbackContext = Context;
  NewReq->TimeOut = 1000; /* 1000 ms */

  InsertTailList (&DwHc->DeferredList, &NewReq->List);
  Status = EFI_SUCCESS;

Done:
  gBS->RestoreTPL (PreviousTpl);

  if (Status != EFI_SUCCESS) {
    if (Data != NULL) {
      FreePool (Data);
    }

    if (NewReq != NULL) {
      FreePool (NewReq);
    }
  }

  return Status;
}

EFI_STATUS
EFIAPI
DwHcSyncInterruptTransfer (
  IN  EFI_USB2_HC_PROTOCOL               *This,
  IN  UINT8                              DeviceAddress,
  IN  UINT8                              EndPointAddress,
  IN  UINT8                              DeviceSpeed,
  IN  UINTN                              MaximumPacketLength,
  IN  OUT VOID                           *Data,
  IN  OUT UINTN                          *DataLength,
  IN  OUT UINT8                          *DataToggle,
  IN  UINTN                              TimeOut,
  IN  EFI_USB2_HC_TRANSACTION_TRANSLATOR *Translator,
  OUT UINT32                             *TransferResult
  )
{
  DWUSB_OTGHC_DEV *DwHc;
  EFI_STATUS Status;
  EFI_EVENT TimeoutEvt;
  UINT8 TransferDirection;
  UINT8 EpAddress;
  UINT32 Pid;

  DwHc = DWHC_FROM_THIS (This);

  if (Data == NULL ||
      DataLength == NULL ||
      DataToggle == NULL ||
      TransferResult == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  if (*DataLength == 0) {
    return EFI_INVALID_PARAMETER;
  }

  if ((*DataToggle != 0) && (*DataToggle != 1)) {
    return EFI_INVALID_PARAMETER;
  }

  if (((DeviceSpeed == EFI_USB_SPEED_LOW) && (MaximumPacketLength != 8))  ||
      ((DeviceSpeed == EFI_USB_SPEED_FULL) && (MaximumPacketLength > 64)) ||
      ((DeviceSpeed == EFI_USB_SPEED_HIGH) && (MaximumPacketLength > 3072))) {
    return EFI_INVALID_PARAMETER;
  }

  Status = gBS->CreateEvent (EVT_TIMER, 0, NULL, NULL, &TimeoutEvt);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Status = gBS->SetTimer (TimeoutEvt, TimerForTransfer,
                  EFI_TIMER_PERIOD_MILLISECONDS (TimeOut));
  if (EFI_ERROR (Status)) {
    goto Exit;
  }

  TransferDirection = (EndPointAddress >> 7) & 0x01;
  EpAddress = EndPointAddress & 0x0F;
  Pid = (*DataToggle << 1);
  Status = DwHcTransfer (DwHc, TimeoutEvt,
             DWC2_HC_CHANNEL_SYNC, Translator,
             DeviceSpeed, DeviceAddress,
             MaximumPacketLength,
             &Pid, TransferDirection, Data,
             DataLength, EpAddress,
             DWC2_HCCHAR_EPTYPE_INTR,
             TransferResult, 0);
  *DataToggle = (Pid >> 1);

Exit:
  if (TimeoutEvt != NULL) {
    gBS->CloseEvent (TimeoutEvt);
  }
  return Status;
}

EFI_STATUS
EFIAPI
DwHcIsochronousTransfer (
  IN  EFI_USB2_HC_PROTOCOL               *This,
  IN  UINT8                              DeviceAddress,
  IN  UINT8                              EndPointAddress,
  IN  UINT8                              DeviceSpeed,
  IN  UINTN                              MaximumPacketLength,
  IN  UINT8                              DataBuffersNumber,
  IN  OUT VOID                           *Data[EFI_USB_MAX_ISO_BUFFER_NUM],
  IN  UINTN                              DataLength,
  IN  EFI_USB2_HC_TRANSACTION_TRANSLATOR *Translator,
  OUT UINT32                             *TransferResult
  )
{
  DEBUG ((DEBUG_ERROR, "Iso\n"));
  return EFI_UNSUPPORTED;
}

EFI_STATUS
EFIAPI
DwHcAsyncIsochronousTransfer (
  IN  EFI_USB2_HC_PROTOCOL               *This,
  IN  UINT8                              DeviceAddress,
  IN  UINT8                              EndPointAddress,
  IN  UINT8                              DeviceSpeed,
  IN  UINTN                              MaximumPacketLength,
  IN  UINT8                              DataBuffersNumber,
  IN  OUT VOID                           *Data[EFI_USB_MAX_ISO_BUFFER_NUM],
  IN  UINTN                              DataLength,
  IN  EFI_USB2_HC_TRANSACTION_TRANSLATOR *Translator,
  IN  EFI_ASYNC_USB_TRANSFER_CALLBACK    IsochronousCallBack,
  IN  VOID                               *Context
  )
{
  DEBUG ((DEBUG_ERROR, "AsyncIso\n"));
  return EFI_UNSUPPORTED;
}

/**
   Supported Functions
**/

VOID
InitFslspClkSel (
  IN  DWUSB_OTGHC_DEV *DwHc
  )
{
  UINT32  PhyClk;

  PhyClk = DWC2_HCFG_FSLSPCLKSEL_30_60_MHZ;

  MmioAndThenOr32 (DwHc->DwUsbBase + HCFG,
    ~DWC2_HCFG_FSLSPCLKSEL_MASK,
    PhyClk << DWC2_HCFG_FSLSPCLKSEL_OFFSET);
}

VOID
DwFlushTxFifo (
  IN  DWUSB_OTGHC_DEV *DwHc,
  IN EFI_EVENT Timeout,
  IN INT32 Num
  )
{
  EFI_STATUS Status;

  MmioWrite32 (DwHc->DwUsbBase + GRSTCTL, DWC2_GRSTCTL_TXFFLSH |
    (Num << DWC2_GRSTCTL_TXFNUM_OFFSET));

  Status = Wait4Bit (Timeout, DwHc->DwUsbBase + GRSTCTL, DWC2_GRSTCTL_TXFFLSH, 0);
  if (Status)
    DEBUG ((DEBUG_ERROR, "DwFlushTxFifo: Timeout!\n"));

  MicroSecondDelay (1);
}

VOID
DwFlushRxFifo (
  IN  DWUSB_OTGHC_DEV *DwHc,
  IN  EFI_EVENT Timeout
  )
{
  EFI_STATUS Status;

  MmioWrite32 (DwHc->DwUsbBase + GRSTCTL, DWC2_GRSTCTL_RXFFLSH);

  Status = Wait4Bit (Timeout, DwHc->DwUsbBase + GRSTCTL, DWC2_GRSTCTL_RXFFLSH, 0);
  if (Status)
    DEBUG ((DEBUG_ERROR, "DwFlushRxFifo: Timeout!\n"));

  MicroSecondDelay (1);
}

EFI_STATUS
DwHcInit (
  IN  DWUSB_OTGHC_DEV *DwHc,
  IN  EFI_EVENT Timeout
  )
{
  UINT32 NpTxFifoSz = 0;
  UINT32 pTxFifoSz = 0;
  UINT32 Hprt0 = 0;
  INT32  i, Status, NumChannels;

  MmioWrite32 (DwHc->DwUsbBase + PCGCCTL, 0);

  InitFslspClkSel (DwHc);

  MmioWrite32 (DwHc->DwUsbBase + GRXFSIZ, DWC2_HOST_RX_FIFO_SIZE);

  NpTxFifoSz |= DWC2_HOST_NPERIO_TX_FIFO_SIZE << DWC2_FIFOSIZE_DEPTH_OFFSET;
  NpTxFifoSz |= DWC2_HOST_RX_FIFO_SIZE << DWC2_FIFOSIZE_STARTADDR_OFFSET;
  MmioWrite32 (DwHc->DwUsbBase + GNPTXFSIZ, NpTxFifoSz);

  pTxFifoSz |= DWC2_HOST_PERIO_TX_FIFO_SIZE << DWC2_FIFOSIZE_DEPTH_OFFSET;
  pTxFifoSz |= (DWC2_HOST_RX_FIFO_SIZE + DWC2_HOST_NPERIO_TX_FIFO_SIZE) <<
    DWC2_FIFOSIZE_STARTADDR_OFFSET;
  MmioWrite32 (DwHc->DwUsbBase + HPTXFSIZ, pTxFifoSz);

  MmioAnd32 (DwHc->DwUsbBase + GOTGCTL, ~(DWC2_GOTGCTL_HSTSETHNPEN));

  DwFlushTxFifo (DwHc, Timeout, 0x10);
  DwFlushRxFifo (DwHc, Timeout);

  NumChannels = MmioRead32 (DwHc->DwUsbBase + GHWCFG2);
  NumChannels &= DWC2_HWCFG2_NUM_HOST_CHAN_MASK;
  NumChannels >>= DWC2_HWCFG2_NUM_HOST_CHAN_OFFSET;
  NumChannels += 1;
  DEBUG ((DEBUG_INFO, "Host has %u channels\n", NumChannels));

  for (i = 0; i < NumChannels; i++)
    MmioAndThenOr32 (DwHc->DwUsbBase + HCCHAR (i),
      ~(DWC2_HCCHAR_CHEN | DWC2_HCCHAR_EPDIR),
      DWC2_HCCHAR_CHDIS);

  for (i = 0; i < NumChannels; i++) {
    MmioAndThenOr32 (DwHc->DwUsbBase + HCCHAR (i),
      ~DWC2_HCCHAR_EPDIR,
      (DWC2_HCCHAR_CHEN | DWC2_HCCHAR_CHDIS));
    Status = Wait4Bit (Timeout, DwHc->DwUsbBase + HCCHAR (i), DWC2_HCCHAR_CHEN, 0);
    if (Status) {
      DEBUG ((DEBUG_ERROR, "DwHcInit: Timeout!\n"));
      return Status;
    }
  }

  if (MmioRead32 (DwHc->DwUsbBase + GINTSTS) & DWC2_GINTSTS_CURMODE_HOST) {
    Hprt0 = MmioRead32 (DwHc->DwUsbBase + HPRT0);
    Hprt0 &= ~(DWC2_HPRT0_PRTENA | DWC2_HPRT0_PRTCONNDET);
    Hprt0 &= ~(DWC2_HPRT0_PRTENCHNG | DWC2_HPRT0_PRTOVRCURRCHNG);

    if (!(Hprt0 & DWC2_HPRT0_PRTPWR)) {
      Hprt0 |= DWC2_HPRT0_PRTPWR;
      MmioWrite32 (DwHc->DwUsbBase + HPRT0, Hprt0);
    }
  }

  return EFI_SUCCESS;
}

EFI_STATUS
DwCoreInit (
  IN  DWUSB_OTGHC_DEV *DwHc,
  IN  EFI_EVENT Timeout
  )
{
  UINT32 AhbCfg = 0;
  UINT32 UsbCfg = 0;
  EFI_STATUS Status;

  UsbCfg = MmioRead32 (DwHc->DwUsbBase + GUSBCFG);

  UsbCfg |= DWC2_GUSBCFG_ULPI_EXT_VBUS_DRV;
  UsbCfg &= ~DWC2_GUSBCFG_TERM_SEL_DL_PULSE;

  MmioWrite32 (DwHc->DwUsbBase + GUSBCFG, UsbCfg);

  Status = DwCoreReset (DwHc, Timeout);
  if (Status != EFI_SUCCESS) {
    DEBUG ((DEBUG_ERROR, "DwCoreReset failed\n"));
    return Status;
  }

  UsbCfg &= ~(DWC2_GUSBCFG_ULPI_UTMI_SEL | DWC2_GUSBCFG_PHYIF);
  UsbCfg |= CONFIG_DWC2_PHY_TYPE << DWC2_GUSBCFG_ULPI_UTMI_SEL_OFFSET;
  UsbCfg &= ~DWC2_GUSBCFG_DDRSEL;

  MmioWrite32 (DwHc->DwUsbBase + GUSBCFG, UsbCfg);

  Status = DwCoreReset (DwHc, Timeout);
  if (Status != EFI_SUCCESS) {
    DEBUG ((DEBUG_ERROR, "DwCoreReset 2 failed\n"));
    return Status;
  }

  UsbCfg = MmioRead32 (DwHc->DwUsbBase + GUSBCFG);

  UsbCfg &= ~(DWC2_GUSBCFG_ULPI_FSLS | DWC2_GUSBCFG_ULPI_CLK_SUS_M);
  MmioWrite32 (DwHc->DwUsbBase + GUSBCFG, UsbCfg);

  AhbCfg &= ~DWC2_GAHBCFG_AXI_BURST4_MASK;
  AhbCfg |= DWC2_GAHBCFG_DMAENABLE | DWC2_GAHBCFG_WAIT_AXI_WRITES;

  MmioWrite32 (DwHc->DwUsbBase + GAHBCFG, AhbCfg);
  MmioAnd32 (DwHc->DwUsbBase + GUSBCFG, ~(DWC2_GUSBCFG_HNPCAP | DWC2_GUSBCFG_SRPCAP));

  return EFI_SUCCESS;
}

VOID
DestroyDwUsbHc (
  IN  DWUSB_OTGHC_DEV *DwHc
  )
{
  UINT32 Pages;
  EFI_TPL PreviousTpl;

  if (DwHc == NULL) {
    return;
  }

  if (DwHc->PeriodicEvent != NULL) {
    PreviousTpl = gBS->RaiseTPL (TPL_NOTIFY);
    gBS->CloseEvent (DwHc->PeriodicEvent);
    gBS->RestoreTPL (PreviousTpl);
  }

  if (DwHc->ExitBootServiceEvent != NULL) {
    gBS->CloseEvent (DwHc->ExitBootServiceEvent);
  }

  Pages = EFI_SIZE_TO_PAGES (DWC2_DATA_BUF_SIZE);
  DmaUnmap (DwHc->AlignedBufferMapping);
  DmaFreeBuffer (Pages, DwHc->AlignedBuffer);

  Pages = EFI_SIZE_TO_PAGES (DWC2_STATUS_BUF_SIZE);
  FreePages (DwHc->StatusBuffer, Pages);

  gBS->FreePool (DwHc);
}

STATIC
VOID
EFIAPI
DwUsbHcExitBootService (
  IN  EFI_EVENT Event,
  IN  VOID *Context
  )
{
  DWUSB_OTGHC_DEV *DwHc;

  DwHc = (DWUSB_OTGHC_DEV*)Context;
  DwHcQuiesce (DwHc);
}

STATIC
UINT32
FramesPassed (
  IN  DWUSB_OTGHC_DEV *DwHc
  )
{
  UINT32 MicroFrameStart = DwHc->LastMicroFrame;
  UINT32 MicroFrameEnd =
    MmioRead32 (DwHc->DwUsbBase + HFNUM) &
    DWC2_HFNUM_FRNUM_MASK;
  UINT32 MicroFramesPassed;

  DwHc->LastMicroFrame = (UINT16)MicroFrameEnd;

  if (MicroFrameEnd < MicroFrameStart) {
    /*
     * Being delayed by 0x8000 microframes is 262 seconds.
     * Unlikely. Also, we can't really do better unless we
     * start polling time (which is tedious in EFI...).
     */
    MicroFrameEnd += DWC2_HFNUM_FRNUM_MASK + 1;
  }

  MicroFramesPassed = MicroFrameEnd - MicroFrameStart;

  /*
   * Round up. We're supposedly getting called every
   * 8 microframes anyway. This means we'll end up
   * going a bit faster, which is okay.
   */
  return ALIGN_VALUE (MicroFramesPassed, 8) / 8;
}

STATIC
VOID
DwHcPeriodicHandler (
  IN EFI_EVENT Event,
  IN VOID      *Context
  )
{
  UINT32 Frame;
  LIST_ENTRY *Entry;
  LIST_ENTRY *NextEntry;
  DWUSB_OTGHC_DEV *DwHc = Context;

  DwHc->CurrentFrame += FramesPassed (DwHc);
  Frame = DwHc->CurrentFrame;

  EFI_LIST_FOR_EACH_SAFE (Entry, NextEntry,
    &DwHc->DeferredList) {
    DWUSB_DEFERRED_REQ *Req = EFI_LIST_CONTAINER (Entry, DWUSB_DEFERRED_REQ, List);

    if (Frame >= Req->TargetFrame) {
      Req->TargetFrame = Frame + Req->FrameInterval;
      DwHcDeferredTransfer (Req);
    }
  }
}

EFI_STATUS
CreateDwUsbHc (
  OUT DWUSB_OTGHC_DEV **OutDwHc
  )
{
  DWUSB_OTGHC_DEV *DwHc;
  UINT32          Pages;
  UINTN           BufferSize;
  EFI_STATUS      Status;

  DwHc = AllocateZeroPool (sizeof (DWUSB_OTGHC_DEV));
  if (DwHc == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  DwHc->Signature                                 = DWUSB_OTGHC_DEV_SIGNATURE;
  DwHc->DwUsbOtgHc.GetCapability                  = DwHcGetCapability;
  DwHc->DwUsbOtgHc.Reset                          = DwHcReset;
  DwHc->DwUsbOtgHc.GetState                       = DwHcGetState;
  DwHc->DwUsbOtgHc.SetState                       = DwHcSetState;
  DwHc->DwUsbOtgHc.ControlTransfer                = DwHcControlTransfer;
  DwHc->DwUsbOtgHc.BulkTransfer                   = DwHcBulkTransfer;
  DwHc->DwUsbOtgHc.AsyncInterruptTransfer         = DwHcAsyncInterruptTransfer;
  DwHc->DwUsbOtgHc.SyncInterruptTransfer          = DwHcSyncInterruptTransfer;
  DwHc->DwUsbOtgHc.IsochronousTransfer            = DwHcIsochronousTransfer;
  DwHc->DwUsbOtgHc.AsyncIsochronousTransfer       = DwHcAsyncIsochronousTransfer;
  DwHc->DwUsbOtgHc.GetRootHubPortStatus           = DwHcGetRootHubPortStatus;
  DwHc->DwUsbOtgHc.SetRootHubPortFeature          = DwHcSetRootHubPortFeature;
  DwHc->DwUsbOtgHc.ClearRootHubPortFeature        = DwHcClearRootHubPortFeature;
  DwHc->DwUsbOtgHc.MajorRevision                  = 0x02;
  DwHc->DwUsbOtgHc.MinorRevision                  = 0x00;
  DwHc->DwUsbBase                                 = BCM2836_USB_BASE_ADDRESS;

  Pages = EFI_SIZE_TO_PAGES (DWC2_STATUS_BUF_SIZE);
  DwHc->StatusBuffer = AllocatePages (Pages);
  if (DwHc->StatusBuffer == NULL) {
    DEBUG ((DEBUG_ERROR, "CreateDwUsbHc: No pages available for StatusBuffer\n"));
    return EFI_OUT_OF_RESOURCES;
  }

  Pages = EFI_SIZE_TO_PAGES (DWC2_DATA_BUF_SIZE);
  Status = DmaAllocateBuffer (EfiBootServicesData, Pages, (VOID**)&DwHc->AlignedBuffer);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "CreateDwUsbHc: DmaAllocateBuffer: %r\n", Status));
    return Status;
  }

  BufferSize = EFI_PAGES_TO_SIZE (Pages);
  Status = DmaMap (MapOperationBusMasterCommonBuffer, DwHc->AlignedBuffer, &BufferSize,
             &DwHc->AlignedBufferBusAddress, &DwHc->AlignedBufferMapping);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "CreateDwUsbHc: DmaMap: %r\n", Status));
    return Status;
  }

  InitializeListHead (&DwHc->DeferredList);

  Status = gBS->CreateEventEx (
                  EVT_NOTIFY_SIGNAL,
                  TPL_NOTIFY,
                  DwUsbHcExitBootService,
                  DwHc,
                  &gEfiEventExitBootServicesGuid,
                  &DwHc->ExitBootServiceEvent
                );

  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "CreateDwUsbHc: DwUsbHcExitBootService: %r\n", Status));
    return Status;
  }

  Status = gBS->CreateEvent (
                  EVT_TIMER | EVT_NOTIFY_SIGNAL,
                  TPL_NOTIFY,
                  DwHcPeriodicHandler,
                  DwHc, &DwHc->PeriodicEvent
                );
  if (Status != EFI_SUCCESS) {
    DEBUG ((DEBUG_ERROR, "CreateDwUsbHc: DwHcPeriodicHandler: %r\n", Status));
    return Status;
  }

  Status = gBS->SetTimer (DwHc->PeriodicEvent, TimerPeriodic,
                  EFI_TIMER_PERIOD_MILLISECONDS (1));
  if (Status != EFI_SUCCESS) {
    DEBUG ((DEBUG_ERROR, "CreateDwUsbHc: PeriodicEvent: %r\n", Status));
    return Status;
  }

  *OutDwHc = DwHc;
  return EFI_SUCCESS;
}

VOID
DwHcQuiesce (
  IN  DWUSB_OTGHC_DEV *DwHc
  )
{
  if (DwHc->PeriodicEvent != NULL) {
    EFI_TPL PreviousTpl;
    PreviousTpl = gBS->RaiseTPL (TPL_NOTIFY);
    gBS->CloseEvent (DwHc->PeriodicEvent);
    DwHc->PeriodicEvent = NULL;
    gBS->RestoreTPL (PreviousTpl);
  }

  MmioAndThenOr32 (DwHc->DwUsbBase + HPRT0,
    ~(DWC2_HPRT0_PRTENA | DWC2_HPRT0_PRTCONNDET |
      DWC2_HPRT0_PRTENCHNG | DWC2_HPRT0_PRTOVRCURRCHNG),
    DWC2_HPRT0_PRTRST);

  MicroSecondDelay (50000);

  MmioWrite32 (DwHc->DwUsbBase + GRSTCTL, DWC2_GRSTCTL_CSFTRST);
  MicroSecondDelay (100000);
}
