/********************************************************************************
Copyright (C) 2016 Marvell International Ltd.

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

*******************************************************************************/

#include "ComPhyLib.h"

#include <Library/ArmSmcLib.h>
#include <Library/SampleAtResetLib.h>

#include <IndustryStandard/ArmStdSmc.h>

#define SD_LANE_ADDR_WIDTH          0x1000
#define HPIPE_ADDR_OFFSET           0x800
#define COMPHY_ADDR_LANE_WIDTH      0x28
#define SD_ADDR(base, Lane)         (base + SD_LANE_ADDR_WIDTH * Lane)
#define HPIPE_ADDR(base, Lane)      (SD_ADDR(base, Lane) + HPIPE_ADDR_OFFSET)
#define COMPHY_ADDR(base, Lane)     (base + COMPHY_ADDR_LANE_WIDTH * Lane)

STATIC
VOID
ComPhySataMacPowerDown (
  IN EFI_PHYSICAL_ADDRESS SataBase
)
{
  UINT32 Mask, Data;

  /*
   * MAC configuration - power down ComPhy
   * Use indirect address for vendor specific SATA control register
   */
  RegSet (SataBase + SATA3_VENDOR_ADDRESS,
    SATA_CONTROL_REG << SATA3_VENDOR_ADDR_OFSSET, SATA3_VENDOR_ADDR_MASK);

  /* SATA 0 power down */
  Mask = SATA3_CTRL_SATA0_PD_MASK;
  Data = 0x1 << SATA3_CTRL_SATA0_PD_OFFSET;

  /* SATA 1 power down */
  Mask |= SATA3_CTRL_SATA1_PD_MASK;
  Data |= 0x1 << SATA3_CTRL_SATA1_PD_OFFSET;

  /* SATA SSU disable */
  Mask |= SATA3_CTRL_SATA1_ENABLE_MASK;
  Data |= 0x0 << SATA3_CTRL_SATA1_ENABLE_OFFSET;

  /* SATA port 1 disable */
  Mask |= SATA3_CTRL_SATA_SSU_MASK;
  Data |= 0x0 << SATA3_CTRL_SATA_SSU_OFFSET;
  RegSet (SataBase + SATA3_VENDOR_DATA, Data, Mask);
}

STATIC
VOID
ComPhySataPhyPowerUp (
  IN EFI_PHYSICAL_ADDRESS SataBase
)
{
  UINT32 Data, Mask;

  /*
   * MAC configuration - power up ComPhy - power up PLL/TX/RX
   * Use indirect address for vendor specific SATA control register
   */
  RegSet (SataBase + SATA3_VENDOR_ADDRESS,
    SATA_CONTROL_REG << SATA3_VENDOR_ADDR_OFSSET, SATA3_VENDOR_ADDR_MASK);

  /* SATA 0 power up */
  Mask = SATA3_CTRL_SATA0_PD_MASK;
  Data = 0x0 << SATA3_CTRL_SATA0_PD_OFFSET;

  /* SATA 1 power up */
  Mask |= SATA3_CTRL_SATA1_PD_MASK;
  Data |= 0x0 << SATA3_CTRL_SATA1_PD_OFFSET;

  /* SATA SSU enable */
  Mask |= SATA3_CTRL_SATA1_ENABLE_MASK;
  Data |= 0x1 << SATA3_CTRL_SATA1_ENABLE_OFFSET;

  /* SATA port 1 enable */
  Mask |= SATA3_CTRL_SATA_SSU_MASK;
  Data |= 0x1 << SATA3_CTRL_SATA_SSU_OFFSET;
  RegSet (SataBase + SATA3_VENDOR_DATA, Data, Mask);

  /* MBUS request size and interface select register */
  RegSet (SataBase + SATA3_VENDOR_ADDRESS,
    SATA_MBUS_SIZE_SELECT_REG << SATA3_VENDOR_ADDR_OFSSET,
      SATA3_VENDOR_ADDR_MASK);

  /* Mbus regret enable */
  RegSet (SataBase + SATA3_VENDOR_DATA, 0x1 << SATA_MBUS_REGRET_EN_OFFSET,
    SATA_MBUS_REGRET_EN_MASK);
}

STATIC
EFI_STATUS
ComPhySmc (
    IN UINT32 FunctionId,
    EFI_PHYSICAL_ADDRESS ComPhyBaseAddr,
    IN UINT32 Lane,
    IN UINT32 Mode
    )
{
  ARM_SMC_ARGS  SmcRegs = {0};

  SmcRegs.Arg0 = FunctionId;
  SmcRegs.Arg1 = (UINTN)ComPhyBaseAddr;
  SmcRegs.Arg2 = Lane;
  SmcRegs.Arg3 = Mode;
  ArmCallSmc (&SmcRegs);

  if (SmcRegs.Arg0 != 0) {
    return EFI_DEVICE_ERROR;
  }

  return EFI_SUCCESS;
}

STATIC
UINTN
ComPhySataPowerUp (
  IN UINTN ChipId,
  IN UINT32 Lane,
  IN EFI_PHYSICAL_ADDRESS HpipeBase,
  IN EFI_PHYSICAL_ADDRESS ComPhyBase,
  IN MV_BOARD_AHCI_DESC *Desc
  )
{
  EFI_STATUS Status;

  DEBUG ((DEBUG_INFO, "ComPhySata: Initialize SATA PHYs\n"));

  DEBUG((DEBUG_INFO, "ComPhySataPowerUp: stage: MAC configuration - power down ComPhy\n"));

  ComPhySataMacPowerDown (Desc[ChipId].SoC->AhciBaseAddress);

  Status = ComPhySmc (MV_SMC_ID_COMPHY_POWER_ON,
             ComPhyBase,
             Lane,
             COMPHY_FW_FORMAT (COMPHY_SATA_MODE,
               Desc[ChipId].SoC->AhciId,
               COMPHY_SPEED_DEFAULT));
  if (EFI_ERROR (Status)) {
    return Status;
  }

  ComPhySataPhyPowerUp (Desc[ChipId].SoC->AhciBaseAddress);

  Status = ComPhySmc (MV_SMC_ID_COMPHY_PLL_LOCK,
             ComPhyBase,
             Lane,
             COMPHY_FW_FORMAT (COMPHY_SATA_MODE,
               Desc[ChipId].SoC->AhciId,
               COMPHY_SPEED_DEFAULT));
  if (EFI_ERROR (Status)) {
    return Status;
  }

  return Status;
}

VOID
ComPhyCp110Init (
  IN CHIP_COMPHY_CONFIG *PtrChipCfg
  )
{
  EFI_STATUS Status;
  COMPHY_MAP *PtrComPhyMap, *SerdesMap;
  EFI_PHYSICAL_ADDRESS ComPhyBaseAddr, HpipeBaseAddr;
  MARVELL_BOARD_DESC_PROTOCOL *BoardDescProtocol;
  MV_BOARD_AHCI_DESC *AhciBoardDesc;
  UINT32 ComPhyMaxCount, Lane;
  UINT32 PcieWidth = 0;
  UINT8 ChipId;

  ComPhyMaxCount = PtrChipCfg->LanesCount;
  ComPhyBaseAddr = PtrChipCfg->ComPhyBaseAddr;
  HpipeBaseAddr = PtrChipCfg->Hpipe3BaseAddr;
  SerdesMap = PtrChipCfg->MapData;
  ChipId = PtrChipCfg->ChipId;

  /* Check if the first 4 Lanes configured as By-4 */
  for (Lane = 0, PtrComPhyMap = SerdesMap; Lane < 4; Lane++, PtrComPhyMap++) {
    if (PtrComPhyMap->Type != COMPHY_TYPE_PCIE0) {
      break;
    }
    PcieWidth++;
  }

  for (Lane = 0, PtrComPhyMap = SerdesMap; Lane < ComPhyMaxCount;
       Lane++, PtrComPhyMap++) {
    DEBUG((DEBUG_INFO, "ComPhy: Initialize serdes number %d\n", Lane));
    DEBUG((DEBUG_INFO, "ComPhy: Serdes Type = 0x%x\n", PtrComPhyMap->Type));

    if (Lane >= 4) {
      /* PCIe lanes above the first 4 lanes, can be only by1 */
      PcieWidth = 1;
    }

    switch (PtrComPhyMap->Type) {
    case COMPHY_TYPE_UNCONNECTED:
      continue;
      break;
    case COMPHY_TYPE_PCIE0:
    case COMPHY_TYPE_PCIE1:
    case COMPHY_TYPE_PCIE2:
    case COMPHY_TYPE_PCIE3:
      Status = ComPhySmc (MV_SMC_ID_COMPHY_POWER_ON,
                 PtrChipCfg->ComPhyBaseAddr,
                 Lane,
                 COMPHY_FW_PCIE_FORMAT (PcieWidth,
                   COMPHY_PCIE_MODE,
                   PtrComPhyMap->Speed));
      break;
    case COMPHY_TYPE_SATA0:
    case COMPHY_TYPE_SATA1:
    case COMPHY_TYPE_SATA2:
    case COMPHY_TYPE_SATA3:
      /* Obtain AHCI board description */
      Status = gBS->LocateProtocol (&gMarvellBoardDescProtocolGuid,
                      NULL,
                      (VOID **)&BoardDescProtocol);
      if (EFI_ERROR (Status)) {
        break;
      }

      Status = BoardDescProtocol->BoardDescAhciGet (BoardDescProtocol,
                                    &AhciBoardDesc);
      if (EFI_ERROR (Status)) {
        break;
      }

      Status = ComPhySataPowerUp (ChipId,
                 Lane,
                 HpipeBaseAddr,
                 ComPhyBaseAddr,
                 AhciBoardDesc);

      BoardDescProtocol->BoardDescFree (AhciBoardDesc);
      break;
    case COMPHY_TYPE_USB3_HOST0:
    case COMPHY_TYPE_USB3_HOST1:
      Status = ComPhySmc (MV_SMC_ID_COMPHY_POWER_ON,
                 PtrChipCfg->ComPhyBaseAddr,
                 Lane,
                 COMPHY_FW_MODE_FORMAT (COMPHY_USB3H_MODE));
      break;
    case COMPHY_TYPE_SGMII0:
    case COMPHY_TYPE_SGMII1:
    case COMPHY_TYPE_SGMII2:
    case COMPHY_TYPE_SGMII3:
      Status = ComPhySmc (MV_SMC_ID_COMPHY_POWER_ON,
                 PtrChipCfg->ComPhyBaseAddr,
                 Lane,
                 COMPHY_FW_FORMAT (COMPHY_SGMII_MODE,
                   (PtrComPhyMap->Type - COMPHY_TYPE_SGMII0),
                   PtrComPhyMap->Speed));
      break;
    case COMPHY_TYPE_SFI:
      Status = ComPhySmc (MV_SMC_ID_COMPHY_POWER_ON,
                 PtrChipCfg->ComPhyBaseAddr,
                 Lane,
                 COMPHY_FW_FORMAT (COMPHY_SFI_MODE,
                   COMPHY_UNIT_ID0,
                   PtrComPhyMap->Speed));
      break;
    case COMPHY_TYPE_RXAUI0:
    case COMPHY_TYPE_RXAUI1:
      Status = ComPhySmc (MV_SMC_ID_COMPHY_POWER_ON,
                 PtrChipCfg->ComPhyBaseAddr,
                 Lane,
                 COMPHY_FW_MODE_FORMAT (COMPHY_RXAUI_MODE));
      break;
    default:
      DEBUG((DEBUG_ERROR, "Unknown SerDes Type, skip initialize SerDes %d\n",
        Lane));
      Status = EFI_INVALID_PARAMETER;
      ASSERT (FALSE);
      break;
    }
    if (EFI_ERROR(Status)) {
      DEBUG ((DEBUG_ERROR, "Failed to initialize Lane %d\n with Status = 0x%x", Lane, Status));
      PtrComPhyMap->Type = COMPHY_TYPE_UNCONNECTED;
    }
  }
}
