/**
 * @file
 *
 * PCIE Late Initialization
 *
 *
 *
 * @xrefitem bom "File Content Label" "Release Content"
 * @e project:      CIMx-NB
 * @e sub-project:
 * @e \$Revision:$   @e \$Date:$
 *
 */
/*****************************************************************************
 *
 * Copyright (C) 2012 Advanced Micro Devices, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of Advanced Micro Devices, Inc. nor the names of
 *       its contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL ADVANCED MICRO DEVICES, INC. BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *
 ***************************************************************************/

/*----------------------------------------------------------------------------------------
 *                             M O D U L E S    U S E D
 *----------------------------------------------------------------------------------------
 */

#include "NbPlatform.h"
#include "amdDebugOutLib.h"
#include "amdSbLib.h"

/*----------------------------------------------------------------------------------------
 *                   D E F I N I T I O N S    A N D    M A C R O S
 *----------------------------------------------------------------------------------------
 */


/*----------------------------------------------------------------------------------------
 *                  T Y P E D E F S     A N D     S T R U C T U  R E S
 *----------------------------------------------------------------------------------------
 */

/*----------------------------------------------------------------------------------------
 *           P R O T O T Y P E S     O F     L O C A L     F U  N C T I O N S
 *----------------------------------------------------------------------------------------
 */

/*----------------------------------------------------------------------------------------
 *                          E X P O R T E D    F U N C T I O N S
 *----------------------------------------------------------------------------------------
 */

/*----------------------------------------------------------------------------------------*/
/**
 * Amd PCIE Late Init for all NB.
 *
 *
 * @param[in] ConfigPtr   Northbridges configuration block pointer.
 *
 */
AGESA_STATUS
AmdPcieLateInit (
  IN      AMD_NB_CONFIG_BLOCK *ConfigPtr
  )
{
  AGESA_STATUS  Status;

  Status = LibNbApiCall (PcieLateInit, ConfigPtr);
  return Status;
}

/*----------------------------------------------------------------------------------------*/
/**
 * Amd PCIE Late Init for all NB.
 *
 *
 * @param[in] ConfigPtr   Northbridges configuration block pointer.
 *
 */
AGESA_STATUS
AmdPcieLateInitWa (
  IN      AMD_NB_CONFIG_BLOCK *ConfigPtr
  )
{
  AGESA_STATUS  Status;

  Status = LibNbApiCall (PcieLateInitWa, ConfigPtr);
  return Status;
}


/*----------------------------------------------------------------------------------------*/
/**
 * Amd PCIE Special Init for all NB.
 *
 *
 * @param[in] ConfigPtr   Northbridges configuration block pointer.
 *
 */
AGESA_STATUS
AmdPcieValidatePortState (
  IN      AMD_NB_CONFIG_BLOCK *ConfigPtr
  )
{
  AGESA_STATUS  Status;

  Status = LibNbApiCall (PcieValidatePortState, ConfigPtr);
  return Status;
}

/*----------------------------------------------------------------------------------------*/
/**
 * Amd PCIE S3 Init fro all NB.
 *
 *
 * @param[in] ConfigPtr   Northbridges configuration block pointer.
 *
 */
AGESA_STATUS
AmdPcieS3Init (
  IN      AMD_NB_CONFIG_BLOCK *ConfigPtr
  )
{
  AGESA_STATUS  Status;

  Status = LibNbApiCall (PcieLateInit, ConfigPtr);
  return Status;
}

/*----------------------------------------------------------------------------------------*/
/**
 * NB PCIE Late Init.
 *    Extended  programming. Enable power management and misc capability.
 *    Prepare PCIE subsystem to boot to OS.
 *
 *
 * @param[in] NbConfigPtr  Northbridge configuration structure pointer.
 *
 */
AGESA_STATUS
PcieLateInit (
  IN      AMD_NB_CONFIG    *NbConfigPtr
  )
{
  AGESA_STATUS  Status;
  PcieLibUnHidePorts (NbConfigPtr);
  Status = PcieLateValidateConfiguration (NbConfigPtr);
  if (Status == AGESA_FATAL) {
    PcieLibHidePorts (NbConfigPtr);
    REPORT_EVENT (AGESA_FATAL, GENERAL_ERROR_BAD_CONFIGURATION, 0, 0, 0, 0, NbConfigPtr);
    CIMX_ASSERT (FALSE);
    return  Status;
  }
  PcieLibLateInit (NbConfigPtr);
  Status = PcieLateInitPorts (NbConfigPtr);
  Status = PcieLateInitCores (NbConfigPtr);
  PcieLibHidePorts (NbConfigPtr);
  return Status;
}

/*----------------------------------------------------------------------------------------*/
/**
 * NB PCIE Late Init.
 *    Extended  programming. Enable power management and misc capability.
 *    Prepare PCIE subsystem to boot to OS.
 *
 *
 * @param[in] NbConfigPtr  Northbridge configuration structure pointer.
 *
 */
AGESA_STATUS
PcieLateInitWa (
  IN      AMD_NB_CONFIG    *NbConfigPtr
  )
{
  UINT32        Value;
  BOOLEAN       SmuWa;
  LibNbPciIndexRead (NbConfigPtr->NbPciAddress.AddressValue | NB_MISC_INDEX, NB_MISC_REG4A, AccessWidth32, &Value, NbConfigPtr);
  SmuWa = ((Value & BIT21) != 0) ? TRUE : FALSE;
  if (SmuWa) {
    UINT32 SmuWaData;
    LibNbMcuControl (AssertReset, NbConfigPtr);
    SmuWaData = LibNbReadMcuRam (0xFE74, NbConfigPtr);
    SmuWaData &= 0x00ff;
    LibNbLoadMcuFirmwareBlock (0xFE74, 0x1, &SmuWaData, NbConfigPtr);
    LibNbMcuControl (DeAssertReset, NbConfigPtr);
  }
  return AGESA_SUCCESS;
}

/*----------------------------------------------------------------------------------------*/
/**
 * Late init PCIE Ports
 *
 *
 *
*  @param[in] NbConfigPtr          Northbridge configuration structure pointer.
 *
 */
AGESA_STATUS
PcieLateInitPorts (
  IN     AMD_NB_CONFIG   *NbConfigPtr
  )
{
  AGESA_STATUS  Status;
  PCIE_CONFIG   *pPcieConfig;
  PORT          PortId;
  BOOLEAN       IsIommuEnabled;
  NB_INFO       NbInfo;
  CIMX_TRACE ((TRACE_DATA (GET_BLOCK_CONFIG_PTR (NbConfigPtr), CIMX_NBPCIE_TRACE), "[NBPCIE]PcieLateInitPorts Enter\n"));
  IsIommuEnabled = LibNbIsIommuEnabled (NbConfigPtr);
  NbInfo = LibNbGetRevisionInfo (NbConfigPtr);
  pPcieConfig = GET_PCIE_CONFIG_PTR (NbConfigPtr);
  Status = AGESA_SUCCESS;
  for (PortId = MIN_PORT_ID; PortId <= MAX_PORT_ID; PortId++) {
    CORE  CoreId;
    CoreId = PcieLibGetCoreId (PortId, NbConfigPtr);
    if (!PcieLibIsValidCoreId (CoreId, NbConfigPtr)) {
      PcieLibPowerOffPortLanes (PortId, PcieLinkWidth_x0, NbConfigPtr);
    } else if (PcieLibIsValidPortId (PortId, NbConfigPtr)) {
      PCIE_LINK_WIDTH LinkWidth;
      PCI_ADDR        Port;
      LinkWidth = PcieLibGetLinkWidth (PortId, NbConfigPtr);
      CoreId = PcieLibGetCoreId (PortId, NbConfigPtr);
      Port = PcieLibGetPortPciAddress (PortId, NbConfigPtr);
      PcieLateCommonPortInit (PortId, NbConfigPtr);
      if (pPcieConfig->PortConfiguration[PortId].PortDetected == ON) {
        if (pPcieConfig->PortConfiguration[PortId].PortLinkMode == PcieLinkModeGen2SoftwareInitiated) {
          PcieInitiateSoftwareGen2 (PortId, NbConfigPtr);
        }
        PcieAsmpEnableOnPort (PortId, (UINT8)pPcieConfig->PortConfiguration[PortId].PortAspm, NbConfigPtr);
      }
      LibNbPciIndexRMW (Port.AddressValue  | NB_BIF_INDEX, NB_BIFNBP_REG70 , AccessS3SaveWidth32, (UINT32)~BIT12, 0, NbConfigPtr); //PCIE should not ignore malformed packet error or ATS request
      if (pPcieConfig->PortConfiguration[PortId].PortCompliance == OFF &&
          pPcieConfig->PortConfiguration[PortId].PortHotplug == OFF &&
          pPcieConfig->CoreSetting[CoreId].PowerOffUnusedLanes == ON) {
          PcieLibPowerOffPortLanes (PortId, LinkWidth, NbConfigPtr);
      }
    }
  }
  CIMX_TRACE ((TRACE_DATA (GET_BLOCK_CONFIG_PTR (NbConfigPtr), CIMX_NBPCIE_TRACE), "[NBPCIE]PcieLateInitPorts Exit\n"));
  return Status;
}

/*----------------------------------------------------------------------------------------*/
/**
 * Late init PCIE Cores. Core level feature/power management etc.
 *
 *
 *
 *  @param[in] pConfig          Northbridge configuration structure pointer.
 *
 */
AGESA_STATUS
PcieLateInitCores (
  IN     AMD_NB_CONFIG   *pConfig
  )
{
  AGESA_STATUS  Status;
  PCIE_CONFIG   *pPcieConfig;
  CORE          CoreId;

  CIMX_TRACE ((TRACE_DATA (GET_BLOCK_CONFIG_PTR (pConfig), CIMX_NBPCIE_TRACE), "[NBPCIE]PcieLateInitCores Enter\n"));
  Status = AGESA_SUCCESS;
  pPcieConfig = GET_PCIE_CONFIG_PTR (pConfig);
  for (CoreId = 0; CoreId <= MAX_CORE_ID; CoreId++) {
    CIMX_TRACE ((TRACE_DATA (GET_BLOCK_CONFIG_PTR (pConfig), CIMX_NBPCIE_TRACE), "   Init CoreId [%d]\n", CoreId));
    if (pPcieConfig->CoreSetting[CoreId].PowerOffPllInL1 == ON) {
      PcieLibEnablePllPowerOffInL1 (CoreId, pConfig);
    }
    if (pPcieConfig->CoreSetting[CoreId].PowerOffPll == ON) {
      PcieLibPowerOffPll (CoreId, pConfig);
    }
    PcieLibMiscLateCoreSetting (CoreId, pConfig);
    PcieLibManageTxClock   (CoreId, pConfig);
    PcieLibManageLclkClock (CoreId, pConfig);
  }
#ifndef VC1_SUPPORT_DISABLE
  if (NB_SBDFO == 0 && pPcieConfig->PcieConfiguration.NbSbVc1 == ON) {
    PcieNbSbSetupVc (pConfig);
  }
#endif
  CIMX_TRACE ((TRACE_DATA (GET_BLOCK_CONFIG_PTR (pConfig), CIMX_NBPCIE_TRACE), "[NBPCIE]PcieLateInitCores Exit\n"));
  return Status;
}


/*----------------------------------------------------------------------------------------*/
/*
 * Set up NB-SB virtual channel for audio traffic
 *
 *
 *
 *  @param[in] pConfig          Northbridge configuration structure pointer.
 *
 */
VOID
PcieNbSbSetupVc (
  IN     AMD_NB_CONFIG   *pConfig
  )
{
  UINT32    VCStatus;
  PCI_ADDR  Port;

  Port = PcieLibGetPortPciAddress (8, pConfig);
  if (PcieSbSetupVc (pConfig) == AGESA_SUCCESS) {
    LibNbPciRMW (Port.AddressValue | NB_PCIP_REG124, AccessS3SaveWidth8, 0x01, 0, pConfig);
    LibNbPciRMW (Port.AddressValue | NB_PCIP_REG130, AccessS3SaveWidth32, (UINT32)~(BIT24 + BIT25 + BIT26), 0xFE + BIT24, pConfig);
    LibNbPciRMW (Port.AddressValue | NB_PCIP_REG130, AccessS3SaveWidth32, 0xffffffff, BIT31, pConfig);
    do {
      STALL (GET_BLOCK_CONFIG_PTR (pConfig), 200, CIMX_S3_SAVE);
      LibNbPciRead (Port.AddressValue | NB_PCIP_REG134, AccessWidth32, &VCStatus, pConfig);
    } while (VCStatus & BIT17);
    PcieSbEnableVc (pConfig);
  }
}


/*----------------------------------------------------------------------------------------*/
/*
 * Late common Port Init
 *
 *
 *  @param[in] PortId           Port Id
 *  @param[in] pConfig          Northbridge configuration structure pointer.
 *
 */
AGESA_STATUS
PcieLateCommonPortInit (
  IN     PORT        PortId,
  IN     AMD_NB_CONFIG   *pConfig
  )
{
  AGESA_STATUS  Status;
  Status = AGESA_SUCCESS;

  return Status;
}

/*----------------------------------------------------------------------------------------*/
/*
 * Initiate SW Gen2 switch
 *
 *
 *
 *  @param[in] PortId           Port Id.
 *  @param[in] pConfig          Northbridge configuration structure pointer.
 *
 */
VOID
PcieInitiateSoftwareGen2 (
  IN     PORT            PortId,
  IN     AMD_NB_CONFIG   *pConfig
  )
{
  UINT8     LinkSpeedCap;
  UINT8     PcieCapPtr;
  UINT8     SecondaryBus;
  UINT32    Value;
  UINT32    Counter;
  PCI_ADDR  Ep;
  PCI_ADDR  Port;

  CIMX_TRACE ((TRACE_DATA (GET_BLOCK_CONFIG_PTR (pConfig), CIMX_NBPCIE_TRACE), "[NBPCIE]PcieInitiateSoftwareGen2 PortId[%d] Enter\n", PortId));
  Counter = 5000;
  Port = PcieLibGetPortPciAddress (PortId, pConfig);
  LibNbPciRead (Port.AddressValue | NB_PCIP_REG19, AccessWidth8, &SecondaryBus, pConfig);
  Ep.AddressValue = 0;
  Ep.Address.Bus = SecondaryBus;
  CIMX_TRACE ((TRACE_DATA (GET_BLOCK_CONFIG_PTR (pConfig), CIMX_NBPCIE_TRACE), "[NBPCIE] SecondaryBus = 0x%x \n", SecondaryBus));
  PcieCapPtr = LibNbFindPciCapability (Ep.AddressValue, PCIE_CAP_ID, pConfig);
  LibNbPciRead (Ep.AddressValue | (PcieCapPtr + 0xC), AccessWidth8, &LinkSpeedCap, pConfig);
  CIMX_TRACE ((TRACE_DATA (GET_BLOCK_CONFIG_PTR (pConfig), CIMX_NBPCIE_TRACE), "[NBPCIE] PcieCapPtr = 0x%x \n", PcieCapPtr));
  if ((LinkSpeedCap & 0xf) < 2) {
    return;
  }
  PcieLibSetLinkMode (PortId, PcieLinkModeGen2, pConfig);
  LibNbPciIndexRMW (Port.AddressValue  | NB_BIF_INDEX, NB_BIFNBP_REGA4 , AccessS3SaveWidth32, (UINT32)~(BIT18), BIT18 , pConfig);
  do {
    STALL (GET_BLOCK_CONFIG_PTR (pConfig), 200, CIMX_S3_SAVE);
    LibNbPciIndexRead (Port.AddressValue | NB_PCIP_REGE0, NB_BIFNBP_REGA5, AccessWidth32, &Value, pConfig);
  } while ((UINT8)Value != 0x10 && Counter-- != 0);
  LibNbPciIndexRead (Port.AddressValue | NB_PCIP_REGE0, NB_BIFNBP_REGA4, AccessWidth32, &Value, pConfig);
  if ((Value & BIT24) != 0) {
  //Initiate link speed change
    LibNbPciIndexRMW (Port.AddressValue | NB_PCIP_REGE0, NB_BIFNBP_REGA4, AccessS3SaveWidth32, ((UINT32)~BIT7), BIT7, pConfig);
  }
  CIMX_TRACE ((TRACE_DATA (GET_BLOCK_CONFIG_PTR (pConfig), CIMX_NBPCIE_TRACE), "[NBPCIE]PcieInitiateSoftwareGen2 Exit\n"));
}

/*----------------------------------------------------------------------------------------*/
/**
 * Validate input parameters configuration for PCie Late Init call.
 *
 *
 *
 *  @param[in] pConfig          Northbridge configuration structure pointer.
 *
 */
AGESA_STATUS
PcieLateValidateConfiguration (
  IN     AMD_NB_CONFIG   *pConfig
  )
{
  PCIE_CONFIG *pPcieConfig;
  NB_INFO     NbInfo;

  CIMX_TRACE ((TRACE_DATA (GET_BLOCK_CONFIG_PTR (pConfig), CIMX_NBPCIE_TRACE), "[NBPCIE]PcieLateValidateConfiguration Enter\n"));
  pPcieConfig = GET_PCIE_CONFIG_PTR (pConfig);
  NbInfo = LibNbGetRevisionInfo (pConfig);
  if (pPcieConfig == NULL) {
    REPORT_EVENT (AGESA_FATAL, GENERAL_ERROR_BAD_CONFIGURATION, 0, 0, 0, 0, pConfig);
    CIMX_ASSERT (FALSE);
    return  AGESA_FATAL;
  }
  if (pPcieConfig->sHeader.InitializerID != INITIALIZED_BY_INITIALIZER) {
    PcieLibInitializer (pConfig);
  }
  CIMX_TRACE ((TRACE_DATA (GET_BLOCK_CONFIG_PTR (pConfig), CIMX_NBPCIE_TRACE), "[NBPCIE]PcieLateValidateConfiguration Exit\n"));
  return AGESA_SUCCESS;
}

/*----------------------------------------------------------------------------------------*/
/**
 * PcieValidatePortState
 *    Port disable or port visibility control
 *
 *
 * @param[in] NbConfigPtr  Northbridge configuration structure pointer.
 *
 */
AGESA_STATUS
PcieValidatePortState (
  IN     AMD_NB_CONFIG   *NbConfigPtr
  )
{
  AGESA_STATUS  Status;

  Status = AGESA_SUCCESS;
  PcieLibUnHidePorts (NbConfigPtr);
  PcieLibValidatePortStateInit (NbConfigPtr);
  PcieForcePortsVisibleOrDisable (NbConfigPtr);
  PcieLibHidePorts (NbConfigPtr);
  return Status;
}


/*----------------------------------------------------------------------------------------*/
/**
 * PciePortsVisibleOrDisable
 *    Set ports always visible or disable based on input parameter
 *
 *
 *
*  @param[in] NbConfigPtr          Northbridge configuration structure pointer.
 *
 */
VOID
PcieForcePortsVisibleOrDisable (
  IN     AMD_NB_CONFIG   *NbConfigPtr
  )
{
  PCIE_CONFIG   *pPcieConfig;
  PORT          PortId;
  PCI_ADDR      Port;

  pPcieConfig = GET_PCIE_CONFIG_PTR (NbConfigPtr);
  for (PortId = MIN_PORT_ID; PortId <= MAX_PORT_ID; PortId++) {
    if (PcieLibIsValidPortId (PortId, NbConfigPtr)) {
      Port = PcieLibGetPortPciAddress (PortId, NbConfigPtr);
      if (pPcieConfig->PortConfiguration[PortId].ForcePortDisable == ON ) {
        pPcieConfig->PortConfiguration[PortId].PortPresent = OFF;
        pPcieConfig->PortConfiguration[PortId].PortDetected = OFF;
      }
      if (pPcieConfig->PortConfiguration[PortId].PortAlwaysVisible == ON) {
        LibNbPciIndexRMW (Port.AddressValue | NB_BIF_INDEX, NB_BIFNBP_REG70, AccessWidth32, (UINT32)~BIT19, BIT19, NbConfigPtr);
        pPcieConfig->PortConfiguration[PortId].PortPresent = ON;
        pPcieConfig->PortConfiguration[PortId].PortDetected = ON;
      }
      LibNbPciIndexWrite (Port.AddressValue | NB_BIF_INDEX, NB_BIFNBP_REG01,  AccessWidth32, (UINT32*)&pPcieConfig->PortConfiguration[PortId], NbConfigPtr);
    }
  }
}