 /** @file
  SynQuacer DXE platform driver - eMMC support

  Copyright (c) 2017, Linaro, Ltd. All rights reserved.<BR>

  SPDX-License-Identifier: BSD-2-Clause-Patent
**/

#include "PlatformDxe.h"

// F_SDH30 extended Controller registers
#define F_SDH30_AHB_CONFIG        0x100
#define  F_SDH30_AHB_BIGED        BIT6
#define  F_SDH30_BUSLOCK_DMA      BIT5
#define  F_SDH30_BUSLOCK_EN       BIT4
#define  F_SDH30_SIN              BIT3
#define  F_SDH30_AHB_INCR_16      BIT2
#define  F_SDH30_AHB_INCR_8       BIT1
#define  F_SDH30_AHB_INCR_4       BIT0

#define F_SDH30_TUNING_SETTING    0x108
#define  F_SDH30_CMD_CHK_DIS      BIT16

#define F_SDH30_IO_CONTROL2       0x114
#define  F_SDH30_MSEL_O_1_8       BIT18
#define  F_SDH30_CRES_O_DN        BIT19

#define F_SDH30_ESD_CONTROL       0x124
#define  F_SDH30_EMMC_RST         BIT1
#define  F_SDH30_EMMC_HS200       BIT24
#define  F_SDH30_CMD_DAT_DELAY    BIT9

#define F_SDH30_TUNING_SETTING    0x108
#define  F_SDH30_CMD_CHK_DIS      BIT16

#define F_SDH30_IO_CONTROL2       0x114
#define  F_SDH30_MSEL_O_1_8       BIT18
#define  F_SDH30_CRES_O_DN        BIT19

#define F_SDH30_ESD_CONTROL       0x124
#define  F_SDH30_EMMC_RST         BIT1
#define  F_SDH30_EMMC_HS200       BIT24
#define  F_SDH30_CMD_DAT_DELAY    BIT9

#define SD_HC_CLOCK_CTRL          0x2C
#define SYNQUACER_CLOCK_CTRL_VAL  0xBC01

#define SD_HC_CAP_SDR104          BIT33
#define SD_HC_CAP_DDR50           BIT34

#define ESD_CONTROL_RESET_DELAY   (20 * 1000)
#define IO_CONTROL2_SETTLE_US     3000

STATIC EFI_HANDLE mSdMmcControllerHandle;

/**

  Override function for SDHCI capability bits

  @param[in]      PassThru              A pointer to the
                                        EFI_SD_MMC_PASS_THRU_PROTOCOL instance.
  @param[in]      ControllerHandle      The EFI_HANDLE of the controller.
  @param[in]      Slot                  The 0 based slot index.
  @param[in,out]  SdMmcHcSlotCapability The SDHCI capability structure.
  @param[in,out]  BaseClkFreq           The base clock frequency value that
                                        optionally can be updated.

  @retval EFI_SUCCESS           The override function completed successfully.
  @retval EFI_NOT_FOUND         The specified controller or slot does not exist.
  @retval EFI_INVALID_PARAMETER SdMmcHcSlotCapability is NULL

**/
STATIC
EFI_STATUS
EFIAPI
SynQuacerSdMmcCapability (
  IN      EFI_HANDLE                      ControllerHandle,
  IN      UINT8                           Slot,
  IN OUT  VOID                            *SdMmcHcSlotCapability,
  IN OUT  UINT32                          *BaseClkFreq
  )
{
  UINT64 Capability;

  if (ControllerHandle != mSdMmcControllerHandle) {
    return EFI_SUCCESS;
  }

  ASSERT (Slot == 0);

  //
  // Clear the SDR104 capability bit. This avoids the need for a HS200 tuning
  // quirk that is difficult to support using the generic driver.
  // Clear the DDR50 bit as well to work around an issue with the Kingston
  // EMMC08G-M325-A52 part that was fitted on 96board DeveloperBox samples.
  //
  Capability = ReadUnaligned64 (SdMmcHcSlotCapability);
  Capability &= ~(UINT64)(SD_HC_CAP_SDR104 | SD_HC_CAP_DDR50);
  WriteUnaligned64 (SdMmcHcSlotCapability, Capability);

  return EFI_SUCCESS;
}

/**

  Override function for SDHCI controller operations

  @param[in]      ControllerHandle      The EFI_HANDLE of the controller.
  @param[in]      Slot                  The 0 based slot index.
  @param[in]      PhaseType             The type of operation and whether the
                                        hook is invoked right before (pre) or
                                        right after (post)
  @param[in,out]  PhaseData             The pointer to a phase-specific data.

  @retval EFI_SUCCESS           The override function completed successfully.
  @retval EFI_NOT_FOUND         The specified controller or slot does not exist.
  @retval EFI_INVALID_PARAMETER PhaseType is invalid

**/
STATIC
EFI_STATUS
EFIAPI
SynQuacerSdMmcNotifyPhase (
  IN      EFI_HANDLE                      ControllerHandle,
  IN      UINT8                           Slot,
  IN      EDKII_SD_MMC_PHASE_TYPE         PhaseType,
  IN OUT  VOID                           *PhaseData
  )
{
  if (ControllerHandle != mSdMmcControllerHandle) {
    return EFI_SUCCESS;
  }

  ASSERT (Slot == 0);

  switch (PhaseType) {
  case EdkiiSdMmcResetPre:
    // Soft reset does not complete unless the clock is already enabled.
    MmioWrite16 (SYNQUACER_EMMC_BASE + SD_HC_CLOCK_CTRL,
      SYNQUACER_CLOCK_CTRL_VAL);
    break;

  case EdkiiSdMmcInitHostPre:
    // init vendor specific regs
    MmioAnd16 (SYNQUACER_EMMC_BASE + F_SDH30_AHB_CONFIG,
      ~(F_SDH30_AHB_BIGED | F_SDH30_BUSLOCK_EN));

    MmioOr16 (SYNQUACER_EMMC_BASE + F_SDH30_AHB_CONFIG,
      F_SDH30_SIN | F_SDH30_AHB_INCR_16 | F_SDH30_AHB_INCR_8 |
      F_SDH30_AHB_INCR_4);

    MmioAnd32 (SYNQUACER_EMMC_BASE + F_SDH30_ESD_CONTROL, ~F_SDH30_EMMC_RST);
    MemoryFence ();
    gBS->Stall (ESD_CONTROL_RESET_DELAY);

    MmioOr32 (SYNQUACER_EMMC_BASE + F_SDH30_ESD_CONTROL,
      F_SDH30_EMMC_RST | F_SDH30_CMD_DAT_DELAY | F_SDH30_EMMC_HS200);

    gBS->Stall (IO_CONTROL2_SETTLE_US);
    MmioOr32 (SYNQUACER_EMMC_BASE + F_SDH30_IO_CONTROL2, F_SDH30_CRES_O_DN);
    MemoryFence ();
    MmioOr32 (SYNQUACER_EMMC_BASE + F_SDH30_IO_CONTROL2, F_SDH30_MSEL_O_1_8);
    MemoryFence ();
    MmioAnd32 (SYNQUACER_EMMC_BASE + F_SDH30_IO_CONTROL2, ~F_SDH30_CRES_O_DN);
    MemoryFence ();
    gBS->Stall (IO_CONTROL2_SETTLE_US);

    MmioOr32 (SYNQUACER_EMMC_BASE + F_SDH30_TUNING_SETTING,
      F_SDH30_CMD_CHK_DIS);
    break;

  default:
    break;
  }
  return EFI_SUCCESS;
}

STATIC EDKII_SD_MMC_OVERRIDE mSdMmcOverride = {
  EDKII_SD_MMC_OVERRIDE_PROTOCOL_VERSION,
  SynQuacerSdMmcCapability,
  SynQuacerSdMmcNotifyPhase,
};

EFI_STATUS
EFIAPI
RegisterEmmc (
  VOID
  )
{
  EFI_STATUS                      Status;
  EFI_HANDLE                      Handle;

  Status = RegisterNonDiscoverableMmioDevice (
             NonDiscoverableDeviceTypeSdhci,
             NonDiscoverableDeviceDmaTypeCoherent,
             NULL,
             &mSdMmcControllerHandle,
             1,
             SYNQUACER_EMMC_BASE, SYNQUACER_EMMC_BASE_SZ);
  ASSERT_EFI_ERROR (Status);

  Handle = NULL;
  Status = gBS->InstallProtocolInterface (&Handle,
                  &gEdkiiSdMmcOverrideProtocolGuid,
                  EFI_NATIVE_INTERFACE, (VOID **)&mSdMmcOverride);
  ASSERT_EFI_ERROR (Status);

  return EFI_SUCCESS;
}
