/** @file
 *
 *  Copyright (c) 2017, Andrei Warkentin <andrey.warkentin@gmail.com>
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *
 *  SPDX-License-Identifier: BSD-2-Clause-Patent
 *
 **/

#include <Uefi.h>
#include <Library/BaseLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/DebugLib.h>
#include <Library/DevicePathLib.h>
#include <Library/IoLib.h>
#include <Library/PcdLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/DmaLib.h>
#include <Library/TimerLib.h>

#include <Protocol/EmbeddedExternalDevice.h>
#include <Protocol/BlockIo.h>
#include <Protocol/DevicePath.h>
#include <Protocol/RpiMmcHost.h>
#include <Protocol/RpiFirmware.h>

#include <IndustryStandard/Bcm2836.h>
#include <IndustryStandard/RpiMbox.h>
#include <IndustryStandard/Bcm2836SdHost.h>

#define SDHOST_BLOCK_BYTE_LENGTH            512

// Driver Timing Parameters
#define CMD_STALL_AFTER_POLL_US             1
#define CMD_MIN_POLL_TOTAL_TIME_US          100000 // 100ms
#define CMD_MAX_POLL_COUNT                  (CMD_MIN_POLL_TOTAL_TIME_US / CMD_STALL_AFTER_POLL_US)
#define CMD_MAX_RETRY_COUNT                 3
#define CMD_STALL_AFTER_RETRY_US            20 // 20us
#define FIFO_MAX_POLL_COUNT                 1000000
#define STALL_TO_STABILIZE_US               10000 // 10ms

#define IDENT_MODE_SD_CLOCK_FREQ_HZ         400000 // 400KHz

// Macros adopted from MmcDxe internal header
#define SDHOST_R0_READY_FOR_DATA            BIT8
#define SDHOST_R0_CURRENTSTATE(Response)    ((Response >> 9) & 0xF)

#define DEBUG_MMCHOST_SD       DEBUG_VERBOSE
#define DEBUG_MMCHOST_SD_INFO  DEBUG_INFO
#define DEBUG_MMCHOST_SD_ERROR DEBUG_ERROR

STATIC RASPBERRY_PI_FIRMWARE_PROTOCOL   *mFwProtocol;

// Per Physical Layer Simplified Specs
#ifndef NDEBUG
STATIC CONST CHAR8* mStrSdState[] = { "idle", "ready", "ident", "stby",
                                      "tran", "data", "rcv", "prg", "dis",
                                      "ina" };
STATIC CONST CHAR8 *mFsmState[] = { "identmode", "datamode", "readdata",
                                    "writedata", "readwait", "readcrc",
                                    "writecrc", "writewait1", "powerdown",
                                    "powerup", "writestart1", "writestart2",
                                    "genpulses", "writewait2", "?",
                                    "startpowdown" };
#endif /* NDEBUG */
STATIC BOOLEAN mCardIsPresent = FALSE;
STATIC CARD_DETECT_STATE mCardDetectState = CardDetectRequired;
STATIC UINT32 mLastGoodCmd = MMC_GET_INDX (MMC_CMD0);

STATIC inline BOOLEAN
IsAppCmd (
  VOID
  )
{
  return mLastGoodCmd == MMC_CMD55;
}

STATIC BOOLEAN
IsBusyCmd (
  IN  UINT32 MmcCmd
  )
{
  if (IsAppCmd ()) {
    return FALSE;
  }

  return MmcCmd == MMC_CMD7 || MmcCmd == MMC_CMD12;
}

STATIC BOOLEAN
IsWriteCmd (
  IN  UINT32 MmcCmd
  )
{
  if (IsAppCmd ()) {
    return FALSE;
  }

  return MmcCmd == MMC_CMD24 || MmcCmd == MMC_CMD25;
}

STATIC BOOLEAN
IsReadCmd (
  IN  UINT32 MmcCmd,
  IN  UINT32 Argument
  )
{
  if (MmcCmd == MMC_CMD8 && !IsAppCmd ()) {
    if (Argument == CMD8_MMC_ARG) {
      DEBUG ((DEBUG_MMCHOST_SD, "Sending MMC CMD8 variant\n"));
      return TRUE;
    } else {
      ASSERT (Argument == CMD8_SD_ARG);
      DEBUG ((DEBUG_MMCHOST_SD, "Sending SD CMD8 variant\n"));
      return FALSE;
    }
  }

  return
    (MmcCmd == MMC_CMD6 && !IsAppCmd ()) ||
    (MmcCmd == MMC_CMD17 && !IsAppCmd ()) ||
    (MmcCmd == MMC_CMD18 && !IsAppCmd ()) ||
    (MmcCmd == MMC_CMD13 && IsAppCmd ()) ||
    (MmcCmd == MMC_ACMD22 && IsAppCmd ()) ||
    (MmcCmd == MMC_ACMD51 && IsAppCmd ());
}

STATIC VOID
SdHostDumpRegisters (
  VOID
  )
{
  DEBUG ((DEBUG_MMCHOST_SD, "SdHost: Registers Dump:\n"));
  DEBUG ((DEBUG_MMCHOST_SD, "  CMD:  0x%8.8X\n", MmioRead32 (SDHOST_CMD)));
  DEBUG ((DEBUG_MMCHOST_SD, "  ARG:  0x%8.8X\n", MmioRead32 (SDHOST_ARG)));
  DEBUG ((DEBUG_MMCHOST_SD, "  TOUT: 0x%8.8X\n", MmioRead32 (SDHOST_TOUT)));
  DEBUG ((DEBUG_MMCHOST_SD, "  CDIV: 0x%8.8X\n", MmioRead32 (SDHOST_CDIV)));
  DEBUG ((DEBUG_MMCHOST_SD, "  RSP0: 0x%8.8X\n", MmioRead32 (SDHOST_RSP0)));
  DEBUG ((DEBUG_MMCHOST_SD, "  RSP1: 0x%8.8X\n", MmioRead32 (SDHOST_RSP1)));
  DEBUG ((DEBUG_MMCHOST_SD, "  RSP2: 0x%8.8X\n", MmioRead32 (SDHOST_RSP2)));
  DEBUG ((DEBUG_MMCHOST_SD, "  RSP3: 0x%8.8X\n", MmioRead32 (SDHOST_RSP3)));
  DEBUG ((DEBUG_MMCHOST_SD, "  HSTS: 0x%8.8X\n", MmioRead32 (SDHOST_HSTS)));
  DEBUG ((DEBUG_MMCHOST_SD, "  VDD:  0x%8.8X\n", MmioRead32 (SDHOST_VDD)));
  DEBUG ((DEBUG_MMCHOST_SD, "  EDM:  0x%8.8X\n", MmioRead32 (SDHOST_EDM)));
  DEBUG ((DEBUG_MMCHOST_SD, "  HCFG: 0x%8.8X\n", MmioRead32 (SDHOST_HCFG)));
  DEBUG ((DEBUG_MMCHOST_SD, "  HBCT: 0x%8.8X\n", MmioRead32 (SDHOST_HBCT)));
  DEBUG ((DEBUG_MMCHOST_SD, "  HBLC: 0x%8.8X\n\n", MmioRead32 (SDHOST_HBLC)));
}

#ifndef NDEBUG
STATIC EFI_STATUS
SdHostGetSdStatus (
  UINT32* SdStatus
  )
{
  ASSERT (SdStatus != NULL);

  // On command completion with R1 or R1b response type
  // the SDCard status will be in RSP0
  UINT32 Rsp0 = MmioRead32 (SDHOST_RSP0);
  if (Rsp0 != 0xFFFFFFFF) {
    *SdStatus = Rsp0;
    return EFI_SUCCESS;
  }

  return EFI_NO_RESPONSE;
}
#endif /* NDEBUG */

STATIC VOID
SdHostDumpSdCardStatus (
  VOID
  )
{
#ifndef NDEBUG
  UINT32 SdCardStatus;
  EFI_STATUS Status = SdHostGetSdStatus (&SdCardStatus);
  if (!EFI_ERROR (Status)) {
    UINT32 CurrState = SDHOST_R0_CURRENTSTATE (SdCardStatus);
    DEBUG ((DEBUG_MMCHOST_SD,
      "SdHost: SdCardStatus 0x%8.8X: ReadyForData?%d, State[%d]: %a\n",
      SdCardStatus,
      ((SdCardStatus & SDHOST_R0_READY_FOR_DATA) ? 1 : 0),
      CurrState,
      ((CurrState < (sizeof (mStrSdState) / sizeof (*mStrSdState))) ?
        mStrSdState[CurrState] : "UNDEF")));
  }
#endif /* NDEBUG */
}

STATIC VOID
SdHostDumpStatus (
  VOID
  )
{
  SdHostDumpRegisters ();

#ifndef NDEBUG
  UINT32 Hsts = MmioRead32 (SDHOST_HSTS);

  if (Hsts & SDHOST_HSTS_ERROR) {
    DEBUG ((DEBUG_MMCHOST_SD_ERROR, "SdHost: Diagnose HSTS: 0x%8.8X\n", Hsts));

    DEBUG ((DEBUG_MMCHOST_SD_ERROR, "SdHost: Last Good CMD = %u\n", MMC_GET_INDX (mLastGoodCmd)));
    if (Hsts & SDHOST_HSTS_FIFO_ERROR)
      DEBUG ((DEBUG_MMCHOST_SD_ERROR, "  - Fifo Error\n"));
    if (Hsts & SDHOST_HSTS_CRC7_ERROR)
      DEBUG ((DEBUG_MMCHOST_SD_ERROR, "  - CRC7 Error\n"));
    if (Hsts & SDHOST_HSTS_CRC16_ERROR)
      DEBUG ((DEBUG_MMCHOST_SD_ERROR, "  - CRC16 Error\n"));
    if (Hsts & SDHOST_HSTS_CMD_TIME_OUT)
      DEBUG ((DEBUG_MMCHOST_SD_ERROR, "  - CMD Timeout (TOUT %x)\n", MmioRead32 (SDHOST_TOUT)));
    if (Hsts & SDHOST_HSTS_REW_TIME_OUT)
      DEBUG ((DEBUG_MMCHOST_SD_ERROR, "  - Read/Erase/Write Transfer Timeout\n"));
  }

  UINT32 Edm = MmioRead32 (SDHOST_EDM);
  DEBUG (((Hsts & SDHOST_HSTS_ERROR) ? DEBUG_MMCHOST_SD_ERROR : DEBUG_MMCHOST_SD,
    "SdHost: Diagnose EDM: 0x%8.8X\n", Edm));
  DEBUG (((Hsts & SDHOST_HSTS_ERROR) ? DEBUG_MMCHOST_SD_ERROR : DEBUG_MMCHOST_SD,
    "  - FSM: 0x%x (%a)\n", (Edm & 0xF), mFsmState[Edm & 0xF]));
  DEBUG (((Hsts & SDHOST_HSTS_ERROR) ? DEBUG_MMCHOST_SD_ERROR : DEBUG_MMCHOST_SD,
    "  - Fifo Count: %d\n", ((Edm >> 4) & 0x1F)));
  DEBUG (((Hsts & SDHOST_HSTS_ERROR) ? DEBUG_MMCHOST_SD_ERROR : DEBUG_MMCHOST_SD,
    "  - Fifo Write Threshold: %d\n",
    ((Edm >> SDHOST_EDM_WRITE_THRESHOLD_SHIFT) & SDHOST_EDM_THRESHOLD_MASK)));
  DEBUG (((Hsts & SDHOST_HSTS_ERROR) ? DEBUG_MMCHOST_SD_ERROR : DEBUG_MMCHOST_SD,
     "  - Fifo Read Threshold: %d\n",
    ((Edm >> SDHOST_EDM_READ_THRESHOLD_SHIFT) & SDHOST_EDM_THRESHOLD_MASK)));
#endif

  SdHostDumpSdCardStatus ();
}

STATIC EFI_STATUS
SdHostSetClockFrequency (
  IN UINTN TargetSdFreqHz
  )
{
  EFI_STATUS Status;
  UINT32 CoreClockFreqHz = 0;

  // First figure out the core clock
  Status = mFwProtocol->GetClockRate (RPI_MBOX_CLOCK_RATE_CORE, &CoreClockFreqHz);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  ASSERT (CoreClockFreqHz != 0);

  // fSDCLK = fcore_pclk/(ClockDiv+2)
  UINT32 ClockDiv = (CoreClockFreqHz - (2 * TargetSdFreqHz)) / TargetSdFreqHz;
  UINT32 ActualSdFreqHz = CoreClockFreqHz / (ClockDiv + 2);

  DEBUG ((DEBUG_MMCHOST_SD_INFO,
    "SdHost: CoreClock=%dHz, CDIV=%d, Requested SdClock=%dHz, Actual SdClock=%dHz\n",
    CoreClockFreqHz, ClockDiv, TargetSdFreqHz, ActualSdFreqHz));

  MmioWrite32 (SDHOST_CDIV, ClockDiv);
  // Set timeout after 1 second, i.e ActualSdFreqHz SD clock cycles
  MmioWrite32 (SDHOST_TOUT, ActualSdFreqHz);

  gBS->Stall (STALL_TO_STABILIZE_US);

  return Status;
}

STATIC BOOLEAN
SdIsReadOnly (
  IN EFI_MMC_HOST_PROTOCOL *This
  )
{
  return FALSE;
}

STATIC EFI_STATUS
SdBuildDevicePath (
  IN EFI_MMC_HOST_PROTOCOL       *This,
  IN EFI_DEVICE_PATH_PROTOCOL    **DevicePath
  )
{
  EFI_DEVICE_PATH_PROTOCOL *NewDevicePathNode;
  EFI_GUID DevicePathGuid = EFI_CALLER_ID_GUID;

  DEBUG ((DEBUG_MMCHOST_SD, "SdHost: SdBuildDevicePath()\n"));

  NewDevicePathNode = CreateDeviceNode (HARDWARE_DEVICE_PATH, HW_VENDOR_DP, sizeof (VENDOR_DEVICE_PATH));
  CopyGuid (&((VENDOR_DEVICE_PATH*)NewDevicePathNode)->Guid, &DevicePathGuid);
  *DevicePath = NewDevicePathNode;

  return EFI_SUCCESS;
}

STATIC EFI_STATUS
SdSendCommand (
  IN EFI_MMC_HOST_PROTOCOL    *This,
  IN MMC_CMD                  MmcCmd,
  IN UINT32                   Argument
  )
{
  UINT32 Hsts;

  //
  // Fail fast, CMD5 (CMD_IO_SEND_OP_COND)
  // is only valid for SDIO cards and thus
  // expected to always fail.
  //
  if (MmcCmd == MMC_CMD5) {
    DEBUG ((DEBUG_MMCHOST_SD, "SdHost: SdSendCommand(CMD%d, Argument: %08x) ignored\n",
      MMC_GET_INDX (MmcCmd), Argument));
    return EFI_UNSUPPORTED;
  }

  if (MmioRead32 (SDHOST_CMD) & SDHOST_CMD_NEW_FLAG) {
    DEBUG ((DEBUG_MMCHOST_SD_ERROR,
      "SdHost: SdSendCommand(): Failed to execute CMD%d, a CMD is already being executed.\n",
      MMC_GET_INDX (MmcCmd)));
    SdHostDumpStatus ();
    return EFI_DEVICE_ERROR;
  }

  // Write command argument
  MmioWrite32 (SDHOST_ARG, Argument);

  UINT32 SdCmd = 0;
  {
    // Set response type
    if (MmcCmd & MMC_CMD_WAIT_RESPONSE) {
      if (MmcCmd & MMC_CMD_LONG_RESPONSE) {
        SdCmd |= SDHOST_CMD_RESPONSE_CMD_LONG_RESP;
      }
    } else {
      SdCmd |= SDHOST_CMD_RESPONSE_CMD_NO_RESP;
    }

    if (IsBusyCmd (MmcCmd)) {
      SdCmd |= SDHOST_CMD_BUSY_CMD;
    }

    if (IsReadCmd (MmcCmd, Argument)) {
      SdCmd |= SDHOST_CMD_READ_CMD;
    }

    if (IsWriteCmd (MmcCmd)) {
      SdCmd |= SDHOST_CMD_WRITE_CMD;
    }

    SdCmd |= MMC_GET_INDX (MmcCmd);
  }

  if (IsReadCmd (MmcCmd, Argument) || IsWriteCmd (MmcCmd)) {
    if (IsAppCmd () && MmcCmd == MMC_ACMD22) {
      MmioWrite32 (SDHOST_HBCT, 0x4);
    } else if (IsAppCmd () && MmcCmd == MMC_ACMD51) {
      MmioWrite32 (SDHOST_HBCT, 0x8);
    } else if (!IsAppCmd () && MmcCmd == MMC_CMD6) {
      MmioWrite32 (SDHOST_HBCT, 0x40);
    } else {
      MmioWrite32 (SDHOST_HBCT, SDHOST_BLOCK_BYTE_LENGTH);
    }
  }

  DEBUG ((DEBUG_MMCHOST_SD,
    "SdHost: SdSendCommand(CMD%d, Argument: %08x): BUSY=%d, RESP=%d, WRITE=%d, READ=%d\n",
    MMC_GET_INDX (MmcCmd), Argument, ((SdCmd & SDHOST_CMD_BUSY_CMD) ? 1 : 0),
    ((SdCmd & (SDHOST_CMD_RESPONSE_CMD_LONG_RESP | SDHOST_CMD_RESPONSE_CMD_NO_RESP)) >> 9),
    ((SdCmd & SDHOST_CMD_WRITE_CMD) ? 1 : 0), ((SdCmd & SDHOST_CMD_READ_CMD) ? 1 : 0)));

  UINT32 PollCount = 0;
  UINT32 RetryCount = 0;
  BOOLEAN IsCmdExecuted = FALSE;
  EFI_STATUS Status = EFI_SUCCESS;

  // Keep retrying the command until it succeeds.
  while ((RetryCount < CMD_MAX_RETRY_COUNT) && !IsCmdExecuted) {
    Status = EFI_SUCCESS;

    // Clear prev cmd status
    MmioWrite32 (SDHOST_HSTS, SDHOST_HSTS_CLEAR);

    if (IsReadCmd (MmcCmd, Argument) || IsWriteCmd (MmcCmd)) {
      // Flush Fifo if this cmd will start a new transfer in case
      // there is stale bytes in the Fifo
      MmioOr32 (SDHOST_EDM, SDHOST_EDM_FIFO_CLEAR);
    }

    if (MmioRead32 (SDHOST_CMD) & SDHOST_CMD_NEW_FLAG) {
      DEBUG ((DEBUG_MMCHOST_SD_ERROR,
        "%a(%u): CMD%d is still being executed after %d trial(s)\n",
        __FUNCTION__, __LINE__, MMC_GET_INDX (MmcCmd), RetryCount));
    }

    // Write command and set it to start execution
    MmioWrite32 (SDHOST_CMD, SDHOST_CMD_NEW_FLAG | SdCmd);

    // Poll for the command status until it finishes execution
    while (PollCount < CMD_MAX_POLL_COUNT) {
      UINT32 CmdReg = MmioRead32 (SDHOST_CMD);

      // Read status of command response
      if (CmdReg & SDHOST_CMD_FAIL_FLAG) {
        Status = EFI_DEVICE_ERROR;
        /*
         * Must fall-through and wait for the command completion!
         */
      }

      // Check if command is completed.
      if (!(CmdReg & SDHOST_CMD_NEW_FLAG)) {
        IsCmdExecuted = TRUE;
        break;
      }

      ++PollCount;
      gBS->Stall (CMD_STALL_AFTER_POLL_US);
    }

    if (!IsCmdExecuted) {
      ++RetryCount;
      gBS->Stall (CMD_STALL_AFTER_RETRY_US);
    }
  }

  if (RetryCount == CMD_MAX_RETRY_COUNT) {
    Status = EFI_TIMEOUT;
  }

  Hsts = MmioRead32 (SDHOST_HSTS);
  if (EFI_ERROR (Status) ||
    (Hsts & SDHOST_HSTS_ERROR) != 0) {
    if (MmcCmd == MMC_CMD1 && (Hsts & SDHOST_HSTS_CRC7_ERROR) != 0) {
      /*
       * SdHost seems to have no way to specify
       * R3 as a transfer type.
       */
      IsCmdExecuted = TRUE;
      Status = EFI_SUCCESS;
      MmioWrite32 (SDHOST_HSTS, SDHOST_HSTS_CLEAR);
    } else if (MmcCmd == MMC_CMD7 && Argument == 0) {
      /*
       * Deselecting the SDCard with CMD7 and RCA=0x0
       * always timeout on SDHost.
       */
      Status = EFI_SUCCESS;
    } else {
      DEBUG ((DEBUG_MMCHOST_SD_ERROR, "%a(%u): CMD%d execution failed after %d trial(s)\n",
        __FUNCTION__, __LINE__, MMC_GET_INDX (MmcCmd), RetryCount));
      SdHostDumpStatus ();
    }

    MmioWrite32 (SDHOST_HSTS, SDHOST_HSTS_CLEAR);
  }

  if (IsCmdExecuted && !EFI_ERROR (Status)) {
    ASSERT (!(MmioRead32 (SDHOST_HSTS) & SDHOST_HSTS_ERROR));
    mLastGoodCmd = MmcCmd;
  }

  return Status;
}

STATIC EFI_STATUS
SdReceiveResponse (
  IN EFI_MMC_HOST_PROTOCOL    *This,
  IN MMC_RESPONSE_TYPE        Type,
  IN UINT32*                  Buffer
  )
{
  if (Buffer == NULL) {
    DEBUG ((DEBUG_MMCHOST_SD_ERROR, "SdHost: SdReceiveResponse(): Input Buffer is NULL\n"));
    return EFI_INVALID_PARAMETER;
  }

  if ((Type == MMC_RESPONSE_TYPE_R1) ||
    (Type == MMC_RESPONSE_TYPE_R1b) ||
    (Type == MMC_RESPONSE_TYPE_R3) ||
    (Type == MMC_RESPONSE_TYPE_R6) ||
    (Type == MMC_RESPONSE_TYPE_R7)) {
    Buffer[0] = MmioRead32 (SDHOST_RSP0);
    DEBUG ((DEBUG_MMCHOST_SD, "SdHost: SdReceiveResponse(Type: %x), Buffer[0]: %08x\n",
      Type, Buffer[0]));

  } else if (Type == MMC_RESPONSE_TYPE_R2) {
    Buffer[0] = MmioRead32 (SDHOST_RSP0);
    Buffer[1] = MmioRead32 (SDHOST_RSP1);
    Buffer[2] = MmioRead32 (SDHOST_RSP2);
    Buffer[3] = MmioRead32 (SDHOST_RSP3);

    DEBUG ((DEBUG_MMCHOST_SD,
      "SdHost: SdReceiveResponse(Type: %x), Buffer[0-3]: %08x, %08x, %08x, %08x\n",
      Type, Buffer[0], Buffer[1], Buffer[2], Buffer[3]));
  }

  return EFI_SUCCESS;
}

STATIC EFI_STATUS
SdReadBlockData (
  IN EFI_MMC_HOST_PROTOCOL    *This,
  IN EFI_LBA                  Lba,
  IN UINTN                    Length,
  IN UINT32*                  Buffer
  )
{
  DEBUG ((DEBUG_MMCHOST_SD, "SdHost: SdReadBlockData(LBA: 0x%x, Length: 0x%x, Buffer: 0x%x)\n",
    (UINT32)Lba, Length, Buffer));

  ASSERT (Buffer != NULL);
  ASSERT (Length % 4 == 0);

  EFI_STATUS Status = EFI_SUCCESS;

  mFwProtocol->SetLed (TRUE);
  {
    UINT32 NumWords = Length / 4;
    UINT32 WordIdx;

    for (WordIdx = 0; WordIdx < NumWords; ++WordIdx) {
      UINT32 PollCount = 0;
      while (PollCount < FIFO_MAX_POLL_COUNT) {
        UINT32 Hsts = MmioRead32 (SDHOST_HSTS);
        if ((Hsts & SDHOST_HSTS_DATA_FLAG) != 0) {
          MmioWrite32 (SDHOST_HSTS, SDHOST_HSTS_DATA_FLAG);
          Buffer[WordIdx] = MmioRead32 (SDHOST_DATA);
          break;
        }

        ++PollCount;
        gBS->Stall (CMD_STALL_AFTER_RETRY_US);
      }

      if (PollCount == FIFO_MAX_POLL_COUNT) {
        DEBUG ((DEBUG_MMCHOST_SD_ERROR,
            "SdHost: SdReadBlockData(): Block Word%d read poll timed-out\n", WordIdx));
        SdHostDumpStatus ();
        MmioWrite32 (SDHOST_HSTS, SDHOST_HSTS_CLEAR);
        Status = EFI_TIMEOUT;
        break;
      }
    }
  }
  mFwProtocol->SetLed (FALSE);

  return Status;
}

STATIC EFI_STATUS
SdWriteBlockData (
  IN EFI_MMC_HOST_PROTOCOL    *This,
  IN EFI_LBA                  Lba,
  IN UINTN                    Length,
  IN UINT32*                  Buffer
  )
{
  DEBUG ((DEBUG_MMCHOST_SD,
    "SdHost: SdWriteBlockData(LBA: 0x%x, Length: 0x%x, Buffer: 0x%x)\n",
    (UINT32)Lba, Length, Buffer));

  ASSERT (Buffer != NULL);
  ASSERT (Length % SDHOST_BLOCK_BYTE_LENGTH == 0);

  EFI_STATUS Status = EFI_SUCCESS;

  mFwProtocol->SetLed (TRUE);
  {
    UINT32 NumWords = Length / 4;
    UINT32 WordIdx;

    for (WordIdx = 0; WordIdx < NumWords; ++WordIdx) {
      UINT32 PollCount = 0;
      while (PollCount < FIFO_MAX_POLL_COUNT) {
        if (MmioRead32 (SDHOST_HSTS) & SDHOST_HSTS_DATA_FLAG) {
          MmioWrite32 (SDHOST_HSTS, SDHOST_HSTS_DATA_FLAG);
          MmioWrite32 (SDHOST_DATA, Buffer[WordIdx]);
          break;
        }

        ++PollCount;
        gBS->Stall (CMD_STALL_AFTER_RETRY_US);
      }

      if (PollCount == FIFO_MAX_POLL_COUNT) {
        DEBUG ((DEBUG_MMCHOST_SD_ERROR,
          "SdHost: SdWriteBlockData(): Block Word%d write poll timed-out\n", WordIdx));
        SdHostDumpStatus ();
        MmioWrite32 (SDHOST_HSTS, SDHOST_HSTS_CLEAR);
        Status = EFI_TIMEOUT;
        break;
      }
    }
  }
  mFwProtocol->SetLed (FALSE);

  return Status;
}

STATIC EFI_STATUS
SdSetIos (
  IN EFI_MMC_HOST_PROTOCOL      *This,
  IN  UINT32                    BusClockFreq,
  IN  UINT32                    BusWidth,
  IN  UINT32                    TimingMode
  )
{
  if (BusWidth != 0) {
    UINT32 Hcfg = MmioRead32 (SDHOST_HCFG);

    DEBUG ((DEBUG_MMCHOST_SD_INFO, "Setting BusWidth %u\n", BusWidth));
    if (BusWidth == 4) {
      Hcfg |= SDHOST_HCFG_WIDE_EXT_BUS;
    } else {
      Hcfg &= ~SDHOST_HCFG_WIDE_EXT_BUS;
    }

    Hcfg |= SDHOST_HCFG_WIDE_INT_BUS | SDHOST_HCFG_SLOW_CARD;
    MmioWrite32 (SDHOST_HCFG, Hcfg);
  }

  if (BusClockFreq != 0) {
    DEBUG ((DEBUG_MMCHOST_SD_INFO, "Setting Freq %u Hz\n", BusClockFreq));
    SdHostSetClockFrequency (BusClockFreq);
  }

  return EFI_SUCCESS;
}

STATIC EFI_STATUS
SdNotifyState (
  IN EFI_MMC_HOST_PROTOCOL    *This,
  IN MMC_STATE                State
  )
{
  DEBUG ((DEBUG_MMCHOST_SD, "SdHost: SdNotifyState(State: %d) ", State));

  // Stall all operations except init until card detection has occurred.
  if (State != MmcHwInitializationState && mCardDetectState != CardDetectCompleted) {
    return EFI_NOT_READY;
  }

  switch (State) {
  case MmcHwInitializationState:
    DEBUG ((DEBUG_MMCHOST_SD, "MmcHwInitializationState\n", State));

    // Turn-off SD Card power
    MmioWrite32 (SDHOST_VDD, 0);

    // Reset command and arg
    MmioWrite32 (SDHOST_CMD, 0);
    MmioWrite32 (SDHOST_ARG, 0);
    // Reset clock divider
    MmioWrite32 (SDHOST_CDIV, 0);
    // Default timeout
    MmioWrite32 (SDHOST_TOUT, 0xffffffff);
    // Clear status flags
    MmioWrite32 (SDHOST_HSTS, SDHOST_HSTS_CLEAR);;
    // Reset controller configs
    MmioWrite32 (SDHOST_HCFG, 0);
    MmioWrite32 (SDHOST_HBCT, 0);
    MmioWrite32 (SDHOST_HBLC, 0);

    gBS->Stall (STALL_TO_STABILIZE_US);

    // Turn-on SD Card power
    MmioWrite32 (SDHOST_VDD, 1);

    gBS->Stall (STALL_TO_STABILIZE_US);

    // Write controller configs
    UINT32 Hcfg = 0;
    Hcfg |= SDHOST_HCFG_WIDE_INT_BUS;
    Hcfg |= SDHOST_HCFG_SLOW_CARD; // Use all bits of CDIV in DataMode
    MmioWrite32 (SDHOST_HCFG, Hcfg);

    // Set default clock frequency
    EFI_STATUS Status = SdHostSetClockFrequency (IDENT_MODE_SD_CLOCK_FREQ_HZ);
    if (EFI_ERROR (Status)) {
      DEBUG ((DEBUG_MMCHOST_SD_ERROR,
        "SdHost: SdNotifyState(): Fail to initialize SD clock to %dHz\n",
        IDENT_MODE_SD_CLOCK_FREQ_HZ));
      SdHostDumpStatus ();
      return Status;
    }
    break;
  case MmcIdleState:
    DEBUG ((DEBUG_MMCHOST_SD, "MmcIdleState\n", State));
    break;
  case MmcReadyState:
    DEBUG ((DEBUG_MMCHOST_SD, "MmcReadyState\n", State));
    break;
  case MmcIdentificationState:
    DEBUG ((DEBUG_MMCHOST_SD, "MmcIdentificationState\n", State));
    break;
  case MmcStandByState:
    DEBUG ((DEBUG_MMCHOST_SD, "MmcStandByState\n", State));
    break;
  case MmcTransferState:
    DEBUG ((DEBUG_MMCHOST_SD, "MmcTransferState\n", State));
    break;
    break;
  case MmcSendingDataState:
    DEBUG ((DEBUG_MMCHOST_SD, "MmcSendingDataState\n", State));
    break;
  case MmcReceiveDataState:
    DEBUG ((DEBUG_MMCHOST_SD, "MmcReceiveDataState\n", State));
    break;
  case MmcProgrammingState:
    DEBUG ((DEBUG_MMCHOST_SD, "MmcProgrammingState\n", State));
    break;
  case MmcDisconnectState:
  case MmcInvalidState:
  default:
    DEBUG ((DEBUG_MMCHOST_SD_ERROR, "SdHost: SdNotifyState(): Invalid State: %d\n", State));
    ASSERT (0);
  }

  return EFI_SUCCESS;
}

STATIC BOOLEAN
SdIsCardPresent (
  IN EFI_MMC_HOST_PROTOCOL *This
  )
{
  EFI_STATUS Status;

  //
  // If we are already in progress (we may get concurrent calls)
  // or completed the detection, just return the current value.
  //
  if (mCardDetectState != CardDetectRequired) {
    return mCardIsPresent;
  }

  mCardDetectState = CardDetectInProgress;
  mCardIsPresent = FALSE;

  //
  // The two following commands should succeed even if no card is present.
  //
  Status = SdNotifyState (This, MmcHwInitializationState);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "SdIsCardPresent: Error MmcHwInitializationState, Status=%r.\n", Status));
    // If we failed init, go back to requiring card detection
    mCardDetectState = CardDetectRequired;
    return FALSE;
  }

  Status = SdSendCommand (This, MMC_CMD0, 0);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "SdIsCardPresent: CMD0 Error, Status=%r.\n", Status));
    goto out;
  }

  //
  // CMD8 should tell us if an SD card is present.
  //
  Status = SdSendCommand (This, MMC_CMD8, CMD8_SD_ARG);
  if (!EFI_ERROR (Status)) {
     DEBUG ((DEBUG_INFO, "SdIsCardPresent: Maybe SD card detected.\n"));
     mCardIsPresent = TRUE;
     goto out;
  }

  //
  // MMC/eMMC won't accept CMD8, but we can try CMD1.
  //
  Status = SdSendCommand (This, MMC_CMD1, EMMC_CMD1_CAPACITY_GREATER_THAN_2GB);
  if (!EFI_ERROR (Status)) {
     DEBUG ((DEBUG_INFO, "SdIsCardPresent: Maybe MMC card detected.\n"));
     mCardIsPresent = TRUE;
     goto out;
  }

  DEBUG ((DEBUG_INFO, "SdIsCardPresent: Not detected, Status=%r.\n", Status));

out:
  mCardDetectState = CardDetectCompleted;
  return mCardIsPresent;
}

BOOLEAN
SdIsMultiBlock (
  IN EFI_MMC_HOST_PROTOCOL *This
  )
{
  return TRUE;
}

EFI_MMC_HOST_PROTOCOL gMmcHost =
  {
    MMC_HOST_PROTOCOL_REVISION,
    SdIsCardPresent,
    SdIsReadOnly,
    SdBuildDevicePath,
    SdNotifyState,
    SdSendCommand,
    SdReceiveResponse,
    SdReadBlockData,
    SdWriteBlockData,
    SdSetIos,
    SdIsMultiBlock
  };

EFI_STATUS
SdHostInitialize (
  IN EFI_HANDLE          ImageHandle,
  IN EFI_SYSTEM_TABLE    *SystemTable
  )
{
  EFI_STATUS Status;
  EFI_HANDLE Handle = NULL;

  if (PcdGet32 (PcdSdIsArasan)) {
    DEBUG ((DEBUG_INFO, "SD is not routed to SdHost\n"));
    return EFI_REQUEST_UNLOAD_IMAGE;
  }

  Status = gBS->LocateProtocol (&gRaspberryPiFirmwareProtocolGuid, NULL, (VOID**)&mFwProtocol);
  ASSERT_EFI_ERROR (Status);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  DEBUG ((DEBUG_MMCHOST_SD, "SdHost: Initialize\n"));
  DEBUG ((DEBUG_MMCHOST_SD, "Config:\n"));
  DEBUG ((DEBUG_MMCHOST_SD, " - FIFO_MAX_POLL_COUNT=%d\n", FIFO_MAX_POLL_COUNT));
  DEBUG ((DEBUG_MMCHOST_SD, " - CMD_STALL_AFTER_POLL_US=%dus\n", CMD_STALL_AFTER_POLL_US));
  DEBUG ((DEBUG_MMCHOST_SD, " - CMD_MIN_POLL_TOTAL_TIME_US=%dms\n", CMD_MIN_POLL_TOTAL_TIME_US / 1000));
  DEBUG ((DEBUG_MMCHOST_SD, " - CMD_MAX_POLL_COUNT=%d\n", CMD_MAX_POLL_COUNT));
  DEBUG ((DEBUG_MMCHOST_SD, " - CMD_MAX_RETRY_COUNT=%d\n", CMD_MAX_RETRY_COUNT));
  DEBUG ((DEBUG_MMCHOST_SD, " - CMD_STALL_AFTER_RETRY_US=%dus\n", CMD_STALL_AFTER_RETRY_US));

  Status = gBS->InstallMultipleProtocolInterfaces (
    &Handle,
    &gRaspberryPiMmcHostProtocolGuid,
    &gMmcHost,
    NULL
  );
  ASSERT_EFI_ERROR (Status);
  return Status;
}
