/** @file
*
*  Copyright (c) 2008 - 2009, Apple Inc. All rights reserved.
*  Copyright (c) 2011 - 2014, ARM Limited. All rights reserved.
*
*  SPDX-License-Identifier: BSD-2-Clause-Patent
*
**/

#include "MmcHostDxe.h"

EMBEDDED_EXTERNAL_DEVICE   *gTPS65950;
UINT8                      mMaxDataTransferRate = 0;
UINT32                     mRca = 0;
BOOLEAN                    mBitModeSet = FALSE;


typedef struct {
  VENDOR_DEVICE_PATH  Mmc;
  EFI_DEVICE_PATH     End;
} MMCHS_DEVICE_PATH;

MMCHS_DEVICE_PATH gMMCDevicePath = {
  {
    {
      HARDWARE_DEVICE_PATH,
      HW_VENDOR_DP,
      { (UINT8)(sizeof(VENDOR_DEVICE_PATH)), (UINT8)((sizeof(VENDOR_DEVICE_PATH)) >> 8) },
    },
    { 0xb615f1f5, 0x5088, 0x43cd, { 0x80, 0x9c, 0xa1, 0x6e, 0x52, 0x48, 0x7d, 0x00 } }
  },
  {
    END_DEVICE_PATH_TYPE,
    END_ENTIRE_DEVICE_PATH_SUBTYPE,
    { sizeof (EFI_DEVICE_PATH_PROTOCOL), 0 }
  }
};

BOOLEAN
IgnoreCommand (
  UINT32 Command
  )
{
  switch(Command) {
    case MMC_CMD12:
      return TRUE;
    case MMC_CMD13:
      return TRUE;
    default:
      return FALSE;
  }
}

UINT32
TranslateCommand (
  UINT32 Command
  )
{
  UINT32 Translation;

  switch(Command) {
    case MMC_CMD2:
      Translation = CMD2;
      break;
    case MMC_CMD3:
      Translation = CMD3;
      break;
    /*case MMC_CMD6:
      Translation = CMD6;
      break;*/
    case MMC_CMD7:
      Translation = CMD7;
      break;
    case MMC_CMD8:
      Translation = CMD8;
      break;
    case MMC_CMD9:
      Translation = CMD9;
      break;
    /*case MMC_CMD12:
      Translation = CMD12;
      break;
    case MMC_CMD13:
      Translation = CMD13;
      break;*/
    case MMC_CMD16:
      Translation = CMD16;
      break;
    case MMC_CMD17:
      Translation = 0x113A0014;//CMD17;
      break;
    case MMC_CMD24:
      Translation = CMD24 | 4;
      break;
    case MMC_CMD55:
      Translation = CMD55;
      break;
    case MMC_ACMD41:
      Translation = ACMD41;
      break;
    default:
      Translation = Command;
  }

  return Translation;
}

VOID
CalculateCardCLKD (
  UINTN *ClockFrequencySelect
  )
{
  UINTN    TransferRateValue = 0;
  UINTN    TimeValue = 0 ;
  UINTN    Frequency = 0;

  DEBUG ((DEBUG_BLKIO, "CalculateCardCLKD()\n"));

  // For SD Cards  we would need to send CMD6 to set
  // speeds abouve 25MHz. High Speed mode 50 MHz and up

  // Calculate Transfer rate unit (Bits 2:0 of TRAN_SPEED)
  switch (mMaxDataTransferRate & 0x7) { // 2
    case 0:
      TransferRateValue = 100 * 1000;
      break;

    case 1:
      TransferRateValue = 1 * 1000 * 1000;
      break;

    case 2:
      TransferRateValue = 10 * 1000 * 1000;
      break;

    case 3:
      TransferRateValue = 100 * 1000 * 1000;
      break;

    default:
      DEBUG ((DEBUG_BLKIO, "Invalid parameter.\n"));
      ASSERT(FALSE);
      return;
  }

  //Calculate Time value (Bits 6:3 of TRAN_SPEED)
  switch ((mMaxDataTransferRate >> 3) & 0xF) { // 6
    case 1:
      TimeValue = 10;
      break;

    case 2:
      TimeValue = 12;
      break;

    case 3:
      TimeValue = 13;
      break;

    case 4:
      TimeValue = 15;
      break;

    case 5:
      TimeValue = 20;
      break;

    case 6:
      TimeValue = 25;
      break;

    case 7:
      TimeValue = 30;
      break;

    case 8:
      TimeValue = 35;
      break;

    case 9:
      TimeValue = 40;
      break;

    case 10:
      TimeValue = 45;
      break;

    case 11:
      TimeValue = 50;
      break;

    case 12:
      TimeValue = 55;
      break;

    case 13:
      TimeValue = 60;
      break;

    case 14:
      TimeValue = 70;
      break;

    case 15:
      TimeValue = 80;
      break;

    default:
      DEBUG ((DEBUG_BLKIO, "Invalid parameter.\n"));
      ASSERT(FALSE);
      return;
  }

  Frequency = TransferRateValue * TimeValue/10;

  // Calculate Clock divider value to program in MMCHS_SYSCTL[CLKD] field.
  *ClockFrequencySelect = ((MMC_REFERENCE_CLK/Frequency) + 1);

  DEBUG ((DEBUG_BLKIO, "mMaxDataTransferRate: 0x%x, Frequency: %d KHz, ClockFrequencySelect: %x\n", mMaxDataTransferRate, Frequency/1000, *ClockFrequencySelect));
}

VOID
UpdateMMCHSClkFrequency (
  UINTN NewCLKD
  )
{
  DEBUG ((DEBUG_BLKIO, "UpdateMMCHSClkFrequency()\n"));

  // Set Clock enable to 0x0 to not provide the clock to the card
  MmioAnd32 (MMCHS_SYSCTL, ~CEN);

  // Set new clock frequency.
  MmioAndThenOr32 (MMCHS_SYSCTL, ~CLKD_MASK, NewCLKD << 6);

  // Poll till Internal Clock Stable
  while ((MmioRead32 (MMCHS_SYSCTL) & ICS_MASK) != ICS);

  // Set Clock enable to 0x1 to provide the clock to the card
  MmioOr32 (MMCHS_SYSCTL, CEN);
}

EFI_STATUS
InitializeMMCHS (
  VOID
  )
{
  UINT8      Data;
  EFI_STATUS Status;

  DEBUG ((DEBUG_BLKIO, "InitializeMMCHS()\n"));

  // Select Device group to belong to P1 device group in Power IC.
  Data = DEV_GRP_P1;
  Status = gTPS65950->Write (gTPS65950, EXTERNAL_DEVICE_REGISTER(I2C_ADDR_GRP_ID4, VMMC1_DEV_GRP), 1, &Data);
  ASSERT_EFI_ERROR(Status);

  // Configure voltage regulator for MMC1 in Power IC to output 3.0 voltage.
  Data = VSEL_3_00V;
  Status = gTPS65950->Write (gTPS65950, EXTERNAL_DEVICE_REGISTER(I2C_ADDR_GRP_ID4, VMMC1_DEDICATED_REG), 1, &Data);
  ASSERT_EFI_ERROR(Status);

  // After ramping up voltage, set VDDS stable bit to indicate that voltage level is stable.
  MmioOr32 (CONTROL_PBIAS_LITE, (PBIASLITEVMODE0 | PBIASLITEPWRDNZ0 | PBIASSPEEDCTRL0 | PBIASLITEVMODE1 | PBIASLITEWRDNZ1));

  // Enable WP GPIO
  MmioAndThenOr32 (GPIO1_BASE + GPIO_OE, ~BIT23, BIT23);

  // Enable Card Detect
  Data = CARD_DETECT_ENABLE;
  gTPS65950->Write (gTPS65950, EXTERNAL_DEVICE_REGISTER(I2C_ADDR_GRP_ID2, TPS65950_GPIO_CTRL), 1, &Data);

  return Status;
}

BOOLEAN
MMCIsCardPresent (
  IN EFI_MMC_HOST_PROTOCOL     *This
  )
{
  EFI_STATUS  Status;
  UINT8       Data;

  //
  // Card detect is a GPIO0 on the TPS65950
  //
  Status = gTPS65950->Read (gTPS65950, EXTERNAL_DEVICE_REGISTER(I2C_ADDR_GRP_ID2, GPIODATAIN1), 1, &Data);
  if (EFI_ERROR (Status)) {
    return FALSE;
  }

  return !(Data & CARD_DETECT_BIT);
}

BOOLEAN
MMCIsReadOnly (
  IN EFI_MMC_HOST_PROTOCOL     *This
  )
{
  /* Note:
   * On our BeagleBoard the SD card WP pin is always read as TRUE.
   * Probably something wrong with GPIO configuration.
   * BeagleBoard-xM uses microSD cards so there is no write protect at all.
   * Hence commenting out SD card WP pin read status.
   */
  //return (MmioRead32 (GPIO1_BASE + GPIO_DATAIN) & BIT23) == BIT23;
  return 0;

}

// TODO
EFI_GUID mPL180MciDevicePathGuid = EFI_CALLER_ID_GUID;

EFI_STATUS
MMCBuildDevicePath (
  IN EFI_MMC_HOST_PROTOCOL     *This,
  IN EFI_DEVICE_PATH_PROTOCOL  **DevicePath
  )
{
  EFI_DEVICE_PATH_PROTOCOL    *NewDevicePathNode;

  NewDevicePathNode = CreateDeviceNode(HARDWARE_DEVICE_PATH,HW_VENDOR_DP,sizeof(VENDOR_DEVICE_PATH));
  CopyGuid(&((VENDOR_DEVICE_PATH*)NewDevicePathNode)->Guid,&mPL180MciDevicePathGuid);
  *DevicePath = NewDevicePathNode;
  return EFI_SUCCESS;
}

EFI_STATUS
MMCSendCommand (
  IN EFI_MMC_HOST_PROTOCOL     *This,
  IN MMC_CMD                   MmcCmd,
  IN UINT32                    Argument
  )
{
  UINTN MmcStatus;
  UINTN RetryCount = 0;

  if (IgnoreCommand(MmcCmd))
    return EFI_SUCCESS;

  MmcCmd = TranslateCommand(MmcCmd);

  //DEBUG ((EFI_D_ERROR, "MMCSendCommand(%d)\n", MmcCmd));

  // Check if command line is in use or not. Poll till command line is available.
  while ((MmioRead32 (MMCHS_PSTATE) & DATI_MASK) == DATI_NOT_ALLOWED);

  // Provide the block size.
  MmioWrite32 (MMCHS_BLK, BLEN_512BYTES);

  // Setting Data timeout counter value to max value.
  MmioAndThenOr32 (MMCHS_SYSCTL, ~DTO_MASK, DTO_VAL);

  // Clear Status register.
  MmioWrite32 (MMCHS_STAT, 0xFFFFFFFF);

  // Set command argument register
  MmioWrite32 (MMCHS_ARG, Argument);

  //TODO: fix this
  //Enable interrupt enable events to occur
  //MmioWrite32 (MMCHS_IE, CmdInterruptEnableVal);

  // Send a command
  MmioWrite32 (MMCHS_CMD, MmcCmd);

  // Check for the command status.
  while (RetryCount < MAX_RETRY_COUNT) {
    do {
      MmcStatus = MmioRead32 (MMCHS_STAT);
    } while (MmcStatus == 0);

    // Read status of command response
    if ((MmcStatus & ERRI) != 0) {

      // Perform soft-reset for mmci_cmd line.
      MmioOr32 (MMCHS_SYSCTL, SRC);
      while ((MmioRead32 (MMCHS_SYSCTL) & SRC));

      //DEBUG ((EFI_D_INFO, "MmcStatus: 0x%x\n", MmcStatus));
      return EFI_DEVICE_ERROR;
    }

    // Check if command is completed.
    if ((MmcStatus & CC) == CC) {
      MmioWrite32 (MMCHS_STAT, CC);
      break;
    }

    RetryCount++;
  }

  if (RetryCount == MAX_RETRY_COUNT) {
    DEBUG ((DEBUG_BLKIO, "MMCSendCommand: Timeout\n"));
    return EFI_TIMEOUT;
  }

  return EFI_SUCCESS;
}

EFI_STATUS
MMCNotifyState (
  IN EFI_MMC_HOST_PROTOCOL    *This,
  IN MMC_STATE                State
  )
{
  EFI_STATUS              Status;
  UINTN                   FreqSel;

  switch(State) {
    case MmcInvalidState:
      ASSERT(0);
      break;
    case MmcHwInitializationState:
      mBitModeSet = FALSE;

      DEBUG ((DEBUG_BLKIO, "MMCHwInitializationState()\n"));
      Status = InitializeMMCHS ();
      if (EFI_ERROR(Status)) {
        DEBUG ((DEBUG_BLKIO, "Initialize MMC host controller fails. Status: %x\n", Status));
        return Status;
      }

      // Software reset of the MMCHS host controller.
      MmioWrite32 (MMCHS_SYSCONFIG, SOFTRESET);
      gBS->Stall(1000);
      while ((MmioRead32 (MMCHS_SYSSTATUS) & RESETDONE_MASK) != RESETDONE);

      // Soft reset for all.
      MmioWrite32 (MMCHS_SYSCTL, SRA);
      gBS->Stall(1000);
      while ((MmioRead32 (MMCHS_SYSCTL) & SRA) != 0x0);

      //Voltage capabilities initialization. Activate VS18 and VS30.
      MmioOr32 (MMCHS_CAPA, (VS30 | VS18));

      // Wakeup configuration
      MmioOr32 (MMCHS_SYSCONFIG, ENAWAKEUP);
      MmioOr32 (MMCHS_HCTL, IWE);

      // MMCHS Controller default initialization
      MmioOr32 (MMCHS_CON, (OD | DW8_1_4_BIT | CEATA_OFF));

      MmioWrite32 (MMCHS_HCTL, (SDVS_3_0_V | DTW_1_BIT | SDBP_OFF));

      // Enable internal clock
      MmioOr32 (MMCHS_SYSCTL, ICE);

      // Set the clock frequency to 80KHz.
      UpdateMMCHSClkFrequency (CLKD_80KHZ);

      // Enable SD bus power.
      MmioOr32 (MMCHS_HCTL, (SDBP_ON));

      // Poll till SD bus power bit is set.
      while ((MmioRead32 (MMCHS_HCTL) & SDBP_MASK) != SDBP_ON);

      // Enable interrupts.
      MmioWrite32 (MMCHS_IE, (BADA_EN | CERR_EN | DEB_EN | DCRC_EN | DTO_EN | CIE_EN |
        CEB_EN | CCRC_EN | CTO_EN | BRR_EN | BWR_EN | TC_EN | CC_EN));

      // Controller INIT procedure start.
      MmioOr32 (MMCHS_CON, INIT);
      MmioWrite32 (MMCHS_CMD, 0x00000000);
      while (!(MmioRead32 (MMCHS_STAT) & CC));

      // Wait for 1 ms
      gBS->Stall (1000);

      // Set CC bit to 0x1 to clear the flag
      MmioOr32 (MMCHS_STAT, CC);

      // Retry INIT procedure.
      MmioWrite32 (MMCHS_CMD, 0x00000000);
      while (!(MmioRead32 (MMCHS_STAT) & CC));

      // End initialization sequence
      MmioAnd32 (MMCHS_CON, ~INIT);

      MmioOr32 (MMCHS_HCTL, (SDVS_3_0_V | DTW_1_BIT | SDBP_ON));

      // Change clock frequency to 400KHz to fit protocol
      UpdateMMCHSClkFrequency(CLKD_400KHZ);

      MmioOr32 (MMCHS_CON, OD);
      break;
    case MmcIdleState:
      break;
    case MmcReadyState:
      break;
    case MmcIdentificationState:
      break;
    case MmcStandByState:
      CalculateCardCLKD (&FreqSel);
      UpdateMMCHSClkFrequency (FreqSel);
      break;
    case MmcTransferState:
      if (!mBitModeSet) {
        Status = MMCSendCommand (This, CMD55, mRca << 16);
        if (!EFI_ERROR (Status)) {
          // Set device into 4-bit data bus mode
          Status = MMCSendCommand (This, ACMD6, 0x2);
          if (!EFI_ERROR (Status)) {
            // Set host controler into 4-bit mode
            MmioOr32 (MMCHS_HCTL, DTW_4_BIT);
            DEBUG ((DEBUG_BLKIO, "SD Memory Card set to 4-bit mode\n"));
            mBitModeSet = TRUE;
          }
        }
      }
      break;
    case MmcSendingDataState:
      break;
    case MmcReceiveDataState:
      break;
    case MmcProgrammingState:
      break;
    case MmcDisconnectState:
    default:
      ASSERT(0);
  }
  return EFI_SUCCESS;
}

EFI_STATUS
MMCReceiveResponse (
  IN EFI_MMC_HOST_PROTOCOL     *This,
  IN MMC_RESPONSE_TYPE         Type,
  IN UINT32*                   Buffer
  )
{
  if (Buffer == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  if (Type == MMC_RESPONSE_TYPE_R2) {
    Buffer[0] = MmioRead32 (MMCHS_RSP10);
    Buffer[1] = MmioRead32 (MMCHS_RSP32);
    Buffer[2] = MmioRead32 (MMCHS_RSP54);
    Buffer[3] = MmioRead32 (MMCHS_RSP76);
  } else {
    Buffer[0] = MmioRead32 (MMCHS_RSP10);
  }

  if (Type == MMC_RESPONSE_TYPE_CSD) {
    mMaxDataTransferRate = Buffer[3] & 0xFF;
  } else if (Type == MMC_RESPONSE_TYPE_RCA) {
    mRca = Buffer[0] >> 16;
  }

  return EFI_SUCCESS;
}

EFI_STATUS
MMCReadBlockData (
  IN EFI_MMC_HOST_PROTOCOL      *This,
  IN EFI_LBA                    Lba,
  IN UINTN                      Length,
  IN UINT32*                    Buffer
  )
{
  UINTN MmcStatus;
  UINTN Count;
  UINTN RetryCount = 0;

  DEBUG ((DEBUG_BLKIO, "MMCReadBlockData(LBA: 0x%x, Length: 0x%x, Buffer: 0x%x)\n", Lba, Length, Buffer));

  // Check controller status to make sure there is no error.
  while (RetryCount < MAX_RETRY_COUNT) {
    do {
      // Read Status.
      MmcStatus = MmioRead32 (MMCHS_STAT);
    } while(MmcStatus == 0);

    // Check if Buffer read ready (BRR) bit is set?
    if (MmcStatus & BRR) {

      // Clear BRR bit
      MmioOr32 (MMCHS_STAT, BRR);

      for (Count = 0; Count < Length / 4; Count++) {
        *Buffer++ = MmioRead32(MMCHS_DATA);
      }
      break;
    }
    RetryCount++;
  }

  if (RetryCount == MAX_RETRY_COUNT) {
    return EFI_TIMEOUT;
  }

  return EFI_SUCCESS;
}

EFI_STATUS
MMCWriteBlockData (
  IN EFI_MMC_HOST_PROTOCOL    *This,
  IN EFI_LBA                  Lba,
  IN UINTN                    Length,
  IN UINT32*                  Buffer
  )
{
  UINTN MmcStatus;
  UINTN Count;
  UINTN RetryCount = 0;

  // Check controller status to make sure there is no error.
  while (RetryCount < MAX_RETRY_COUNT) {
    do {
      // Read Status.
      MmcStatus = MmioRead32 (MMCHS_STAT);
    } while(MmcStatus == 0);

    // Check if Buffer write ready (BWR) bit is set?
    if (MmcStatus & BWR) {

      // Clear BWR bit
      MmioOr32 (MMCHS_STAT, BWR);

      // Write block worth of data.
      for (Count = 0; Count < Length / 4; Count++) {
        MmioWrite32 (MMCHS_DATA, *Buffer++);
      }

      break;
    }
    RetryCount++;
  }

  if (RetryCount == MAX_RETRY_COUNT) {
    return EFI_TIMEOUT;
  }

  return EFI_SUCCESS;
}

EFI_MMC_HOST_PROTOCOL gMMCHost = {
  MMC_HOST_PROTOCOL_REVISION,
  MMCIsCardPresent,
  MMCIsReadOnly,
  MMCBuildDevicePath,
  MMCNotifyState,
  MMCSendCommand,
  MMCReceiveResponse,
  MMCReadBlockData,
  MMCWriteBlockData
};

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

  DEBUG ((DEBUG_BLKIO, "MMCInitialize()\n"));

  Status = gBS->LocateProtocol (&gEmbeddedExternalDeviceProtocolGuid, NULL, (VOID **)&gTPS65950);
  ASSERT_EFI_ERROR(Status);

  Status = gBS->InstallMultipleProtocolInterfaces (
                  &Handle,
                  &gEmbeddedMmcHostProtocolGuid,    &gMMCHost,
                  NULL
                  );
  ASSERT_EFI_ERROR (Status);

  return Status;
}
