/* $NoKeywords:$ */
/**
 * @file
 *
 * mfidendimm.c
 *
 * Translate physical system address to dimm identification.
 *
 * @xrefitem bom "File Content Label" "Release Content"
 * @e project: AGESA
 * @e sub-project: (Mem/Feat)
 *
 **/
/*****************************************************************************
*
 * Copyright (c) 2008 - 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.
* ***************************************************************************
*
*/

/*
 *----------------------------------------------------------------------------
 *                                MODULES USED
 *
 *----------------------------------------------------------------------------
 */



#include "AGESA.h"
#include "amdlib.h"
#include "mm.h"
#include "mn.h"
#include "Ids.h"
#include "OptionMemory.h"
#include "heapManager.h"
#include "mfidendimm.h"
#include "GeneralServices.h"
#include "Filecode.h"
CODE_GROUP (G2_PEI)
RDATA_GROUP (G2_PEI)

#define FILECODE PROC_MEM_FEAT_IDENDIMM_MFIDENDIMM_FILECODE
extern MEM_NB_SUPPORT memNBInstalled[];

/*----------------------------------------------------------------------------
 *                          DEFINITIONS AND MACROS
 *
 *----------------------------------------------------------------------------
 */
#define MAX_DCTS_PER_DIE        2   ///< Max DCTs per die
#define MAX_CHLS_PER_DCT        1   ///< Max Channels per DCT

/*----------------------------------------------------------------------------
 *                           TYPEDEFS AND STRUCTURES
 *
 *----------------------------------------------------------------------------
 */

/*----------------------------------------------------------------------------
 *                        PROTOTYPES OF LOCAL FUNCTIONS
 *
 *----------------------------------------------------------------------------
 */
AGESA_STATUS
STATIC
MemFTransSysAddrToCS (
  IN OUT   AMD_IDENTIFY_DIMM *AmdDimmIdentify,
  IN       MEM_MAIN_DATA_BLOCK *mmPtr
  );

UINT32
STATIC
MemFGetPCI (
  IN   MEM_NB_BLOCK *NBPtr,
  IN   UINT8 NodeID,
  IN   UINT8 DctNum,
  IN   BIT_FIELD_NAME BitFieldName
  );

UINT8
STATIC
MemFUnaryXOR (
  IN   UINT32 address
  );

/*----------------------------------------------------------------------------
 *                            EXPORTED FUNCTIONS
 *
 *----------------------------------------------------------------------------
 */
/*-----------------------------------------------------------------------------*/
/**
*
*   This function identifies the dimm on which the given memory address locates.
*
*   @param[in, out]   *AmdDimmIdentify - Pointer to the parameter structure AMD_IDENTIFY_DIMM
*
*   @retval           AGESA_SUCCESS - Successfully translate physical system address
*                                     to dimm identification.
*                     AGESA_BOUNDS_CHK - Targeted address is out of bound.
*
*/

AGESA_STATUS
AmdIdentifyDimm (
  IN OUT   AMD_IDENTIFY_DIMM *AmdDimmIdentify
  )
{
  UINT8 i;
  AGESA_STATUS RetVal;
  MEM_MAIN_DATA_BLOCK mmData;             // Main Data block
  MEM_NB_BLOCK *NBPtr;
  MEM_DATA_STRUCT MemData;
  LOCATE_HEAP_PTR LocHeap;
  ALLOCATE_HEAP_PARAMS AllocHeapParams;
  UINT8 Node;
  UINT8 Dct;
  UINT8 Die;
  UINT8 DieCount;

  LibAmdMemCopy (&(MemData.StdHeader), &(AmdDimmIdentify->StdHeader), sizeof (AMD_CONFIG_PARAMS), &(AmdDimmIdentify->StdHeader));
  mmData.MemPtr = &MemData;
  RetVal = MemSocketScan (&mmData);
  if (RetVal == AGESA_FATAL) {
    return RetVal;
  }
  DieCount = mmData.DieCount;

  // Search for AMD_MEM_AUTO_HANDLE on the heap first.
  // Only apply for space on the heap if cannot find AMD_MEM_AUTO_HANDLE on the heap.
  LocHeap.BufferHandle = AMD_MEM_AUTO_HANDLE;
  if (HeapLocateBuffer (&LocHeap, &AmdDimmIdentify->StdHeader) == AGESA_SUCCESS) {
    // NB block has already been constructed by main block.
    // No need to construct it here.
    NBPtr = (MEM_NB_BLOCK *)LocHeap.BufferPtr;
    mmData.NBPtr = NBPtr;
  } else {
    AllocHeapParams.RequestedBufferSize = (DieCount * (sizeof (MEM_NB_BLOCK)));
    AllocHeapParams.BufferHandle = AMD_MEM_AUTO_HANDLE;
    AllocHeapParams.Persist = HEAP_SYSTEM_MEM;
    if (HeapAllocateBuffer (&AllocHeapParams, &AmdDimmIdentify->StdHeader) != AGESA_SUCCESS) {
      PutEventLog (AGESA_FATAL, MEM_ERROR_HEAP_ALLOCATE_FOR_IDENTIFY_DIMM_MEM_NB_BLOCK, 0, 0, 0, 0, &AmdDimmIdentify->StdHeader);
      ASSERT(FALSE); // Could not allocate heap space for NB block for Identify DIMM
      return AGESA_FATAL;
    }
    NBPtr = (MEM_NB_BLOCK *)AllocHeapParams.BufferPtr;
    mmData.NBPtr = NBPtr;
    // Construct each die.
    for (Die = 0; Die < DieCount; Die ++) {
      i = 0;
      while (memNBInstalled[i].MemIdentifyDimmConstruct != 0) {
        if (memNBInstalled[i].MemIdentifyDimmConstruct (&NBPtr[Die], &MemData, Die)) {
          break;
        }
        i++;
      };
      if (memNBInstalled[i].MemIdentifyDimmConstruct == 0) {
        PutEventLog (AGESA_FATAL, MEM_ERROR_NO_CONSTRUCTOR_FOR_IDENTIFY_DIMM, Die, 0, 0, 0, &AmdDimmIdentify->StdHeader);
        ASSERT(FALSE); // No Identify DIMM constructor found
        return AGESA_FATAL;
      }
    }
  }

  if ((RetVal = MemFTransSysAddrToCS (AmdDimmIdentify, &mmData)) == AGESA_SUCCESS) {
    // Translate Node, DCT and Chip select number to Socket, Channel and Dimm number.
    Node = AmdDimmIdentify->SocketId;
    Dct = AmdDimmIdentify->MemChannelId;
    AmdDimmIdentify->SocketId = MemData.DiesPerSystem[Node].SocketId;
    AmdDimmIdentify->MemChannelId = NBPtr[Node].GetSocketRelativeChannel (&NBPtr[Node], Dct, 0);
    AmdDimmIdentify->DimmId /= 2;
  }

  return RetVal;
}


/*----------------------------------------------------------------------------
 *                              LOCAL FUNCTIONS
 *
 *----------------------------------------------------------------------------
 */

/*-----------------------------------------------------------------------------*/
/**
*
*   This function translates the given physical system address to
*   a node, channel select, chip select, bank, row, and column address.
*
*   @param[in, out]   *AmdDimmIdentify - Pointer to the parameter structure AMD_IDENTIFY_DIMM
*   @param[in, out]   *mmPtr - Pointer to the MEM_MAIN_DATA_BLOCK
*
*   @retval           AGESA_SUCCESS - The chip select address is found
*   @retval           AGESA_BOUNDS_CHK - Targeted address is out of bound.
*
*/
AGESA_STATUS
STATIC
MemFTransSysAddrToCS (
  IN OUT   AMD_IDENTIFY_DIMM *AmdDimmIdentify,
  IN       MEM_MAIN_DATA_BLOCK *mmPtr
  )
{
  BOOLEAN CSFound;
  BOOLEAN DctSelHiRngEn;
  BOOLEAN DctSelIntLvEn;
  BOOLEAN DctGangEn;
  BOOLEAN HiRangeSelected;
  BOOLEAN DramHoleValid;
  BOOLEAN CSEn;
  BOOLEAN SwapDone;
  BOOLEAN IntLvRgnSwapEn;
  UINT8 DctSelHi;
  UINT8 DramEn;
  UINT8 range;
  UINT8 IntlvEn;
  UINT8 IntlvSel;
  UINT8 ILog;
  UINT8 DctSelIntLvAddr;
  UINT8 DctNum;
  UINT8 cs;
  UINT8 BadDramCs;
  UINT8 spare;
  UINT8 IntLvRgnBaseAddr;
  UINT8 IntLvRgnLmtAddr;
  UINT8 IntLvRgnSize;
  UINT32 temp;
  UINT32 DramHoleOffset;
  UINT32 DramHoleBase;
  UINT64 DramBase;
  UINT64 DramLimit;
  UINT64 DramLimitSysAddr;
  UINT64 DctSelBaseAddr;
  UINT64 DctSelBaseOffset;
  UINT64 ChannelAddr;
  UINT64 CSBase;
  UINT64 CSMask;
  UINT64 InputAddr;
  UINT64 ChannelOffset;
  MEM_NB_BLOCK *NBPtr;
  UINT8 Die;

  UINT64 SysAddr;
  UINT8 *NodeID;
  UINT8 *ChannelSelect;
  UINT8 *ChipSelect;

  SysAddr = AmdDimmIdentify->MemoryAddress;
  NodeID = &(AmdDimmIdentify->SocketId);
  ChannelSelect = &(AmdDimmIdentify->MemChannelId);
  ChipSelect = &(AmdDimmIdentify->DimmId);
  CSFound = FALSE;
  ILog = 0;
  NBPtr = mmPtr->NBPtr;

  NBPtr->FamilySpecificHook[FixupSysAddr] (NBPtr, &SysAddr);

  // Loop to determine the dram range
  for (Die = 0; Die < mmPtr->DieCount; Die ++) {
    range = NBPtr[Die].Node;

    // DRAM Base
    temp = MemFGetPCI (NBPtr, 0, 0, BFDramBaseReg0 + range);
    DramEn = (UINT8) (temp & 0x3);
    IntlvEn = (UINT8) ((temp >> 8) & 0x7);

    DramBase = ((UINT64) (MemFGetPCI (NBPtr, 0, 0, BFDramBaseHiReg0 + range) & 0xFF) << 40) |
                 (((UINT64) temp & 0xFFFF0000) << 8);

    // DRAM Limit
    temp = MemFGetPCI (NBPtr, 0, 0, BFDramLimitReg0 + range);
    *NodeID = (UINT8) (temp & 0x7);
    IntlvSel = (UINT8) ((temp >> 8) & 0x7);
    DramLimit = ((UINT64) (MemFGetPCI (NBPtr, 0, 0, BFDramLimitHiReg0 + range) & 0xFF) << 40) |
                  (((UINT64) temp << 8) | 0xFFFFFF);
    DramLimitSysAddr = (((UINT64) MemFGetPCI (NBPtr, *NodeID, 0, BFDramLimitAddr)) << 27) | 0x7FFFFFF;
    ASSERT (DramLimit <= DramLimitSysAddr);

    if ((DramEn != 0) && (DramBase <= SysAddr) && (SysAddr <= DramLimitSysAddr) &&
        ((IntlvEn == 0) || (IntlvSel == ((SysAddr >> 12) & IntlvEn)))) {
      // Determine the number of bit positions consumed by Node Interleaving
      switch (IntlvEn) {

      case 0x0:
        ILog = 0;
        break;

      case 0x1:
        ILog = 1;
        break;

      case 0x3:
        ILog = 2;
        break;

      case 0x7:
        ILog = 3;
        break;

      default:
        IDS_ERROR_TRAP;
      }

      DramHoleOffset = MemFGetPCI (NBPtr, *NodeID, 0, BFDramHoleOffset) << 23;
      DramHoleValid = (BOOLEAN) MemFGetPCI (NBPtr, *NodeID, 0, BFDramHoleValid);
      DramHoleBase = MemFGetPCI (NBPtr, *NodeID, 0, BFDramHoleBase) << 24;
      // Address belongs to this node based on DramBase/Limit,
      // but is in the memory hole so it doesn't map to DRAM
      if (DramHoleValid && (DramHoleBase <= SysAddr) && (SysAddr < 0x100000000)) {
        return AGESA_BOUNDS_CHK;
      }

      // F2x10C Swapped Interleaved Region
      IntLvRgnSwapEn = (BOOLEAN) MemFGetPCI (NBPtr, *NodeID, 0, BFIntLvRgnSwapEn);
      if (IntLvRgnSwapEn) {
        IntLvRgnBaseAddr = (UINT8) MemFGetPCI (NBPtr, *NodeID, 0, BFIntLvRgnBaseAddr);
        IntLvRgnLmtAddr = (UINT8) MemFGetPCI (NBPtr, *NodeID, 0, BFIntLvRgnLmtAddr);
        IntLvRgnSize = (UINT8) MemFGetPCI (NBPtr, *NodeID, 0, BFIntLvRgnSize);
        ASSERT (IntLvRgnSize == (IntLvRgnLmtAddr - IntLvRgnBaseAddr + 1));
        if (((SysAddr >> 34) == 0) &&
          ((((SysAddr >> 27) >= IntLvRgnBaseAddr) && ((SysAddr >> 27) <= IntLvRgnLmtAddr))
           || ((SysAddr >> 27) < IntLvRgnSize))) {
          SysAddr ^= (UINT64) IntLvRgnBaseAddr << 27;
        }
      }

      // Extract variables from F2x110 DRAM Controller Select Low Register
      DctSelHiRngEn = (BOOLEAN) MemFGetPCI (NBPtr, *NodeID, 0, BFDctSelHiRngEn);
      DctSelHi = (UINT8) MemFGetPCI (NBPtr, *NodeID, 0, BFDctSelHi);
      DctSelIntLvEn = (BOOLEAN) MemFGetPCI (NBPtr, *NodeID, 0, BFDctSelIntLvEn);
      DctGangEn = (BOOLEAN) MemFGetPCI (NBPtr, *NodeID, 0, BFDctGangEn);
      DctSelIntLvAddr = (UINT8) MemFGetPCI (NBPtr, *NodeID, 0, BFDctSelIntLvAddr);
      DctSelBaseAddr = (UINT64) MemFGetPCI (NBPtr, *NodeID, 0, BFDctSelBaseAddr) << 27;
      DctSelBaseOffset = (UINT64) MemFGetPCI (NBPtr, *NodeID, 0, BFDctSelBaseOffset) << 26;


      // Determine if high DCT address range is being selected
      if (DctSelHiRngEn && !DctGangEn && (SysAddr >= DctSelBaseAddr)) {
        HiRangeSelected = TRUE;
      } else {
        HiRangeSelected = FALSE;
      }

      // Determine Channel
      if (DctGangEn) {
        *ChannelSelect = (UINT8) ((SysAddr >> 3) & 0x1);
      } else if (HiRangeSelected) {
        *ChannelSelect = DctSelHi;
      } else if (DctSelIntLvEn && (DctSelIntLvAddr == 0)) {
        *ChannelSelect = (UINT8) ((SysAddr >> 6) & 0x1);
      } else if (DctSelIntLvEn && (((DctSelIntLvAddr >> 1) & 0x1) != 0)) {
        temp = MemFUnaryXOR ((UINT32) ((SysAddr >> 16) & 0x1F));
        if ((DctSelIntLvAddr & 0x1) != 0) {
          *ChannelSelect = (UINT8) (((SysAddr >> 9) & 0x1) ^ temp);
        } else {
          *ChannelSelect = (UINT8) (((SysAddr >> 6) & 0x1) ^ temp);
        }
      } else if (DctSelIntLvEn) {
        *ChannelSelect = (UINT8) ((SysAddr >> (12 + ILog)) & 0x1);
      } else if (DctSelHiRngEn) {
        *ChannelSelect = ~DctSelHi & 0x1;
      } else {
        *ChannelSelect = 0;
      }
      ASSERT (*ChannelSelect < NBPtr[*NodeID].DctCount);

      // Determine base address offset
      if (HiRangeSelected) {
        if ((DctSelBaseAddr < DramHoleBase) && DramHoleValid && (SysAddr >= (UINT64) 0x100000000)) {
          ChannelOffset = (UINT64) DramHoleOffset;
        } else {
          ChannelOffset = DctSelBaseOffset;
        }
      } else {
        if (DramHoleValid && (SysAddr >= (UINT64) 0x100000000)) {
          ChannelOffset = (UINT64) DramHoleOffset;
        } else {
          ChannelOffset = DramBase;
        }
      }

      // Remove hoisting offset and normalize to DRAM bus addresses
      ChannelAddr = SysAddr - ChannelOffset;

      // Remove node interleaving
      if (IntlvEn != 0) {
        ChannelAddr = ((ChannelAddr >> (12 + ILog)) << 12) | (ChannelAddr & 0xFFF);
      }

      // Remove channel interleave
      if (DctSelIntLvEn && !HiRangeSelected && !DctGangEn) {
        if ((DctSelIntLvAddr & 1) != 1) {
          // A[6] Select or Hash 6
          ChannelAddr = ((ChannelAddr >> 7) << 6) | (ChannelAddr & 0x3F);
        } else if (DctSelIntLvAddr == 1) {
          // A[12]
          ChannelAddr = ((ChannelAddr >> 13) << 12) | (ChannelAddr & 0xFFF);
        } else {
          // Hash 9
          ChannelAddr = ((ChannelAddr >> 10) << 9) | (ChannelAddr & 0x1FF);
        }
      }

      // Determine the Chip Select
      for (cs = 0; cs < MAX_CS_PER_CHANNEL; ++ cs) {
        DctNum = DctGangEn ? 0 : *ChannelSelect;

        // Obtain the CS Base
        temp = MemFGetPCI (NBPtr, *NodeID, DctNum, BFCSBaseAddr0Reg + cs);
        CSEn = (BOOLEAN) (temp & 0x1);
        CSBase = ((UINT64) temp & NBPtr->CsRegMsk) << 8;

        // Obtain the CS Mask
        CSMask = ((UINT64) MemFGetPCI (NBPtr, *NodeID, DctNum, BFCSMask0Reg + (cs >> 1)) & NBPtr->CsRegMsk) << 8;

        // Adjust the Channel Addr for easy comparison
        InputAddr = ((ChannelAddr >> 8) & NBPtr->CsRegMsk) << 8;

        if (CSEn && ((InputAddr & ~CSMask) == (CSBase & ~CSMask))) {
          CSFound = TRUE;

          *ChipSelect = cs;

          temp = MemFGetPCI (NBPtr, *NodeID, 0, BFOnLineSpareControl);
          SwapDone = (BOOLEAN) ((temp >> (1 + 2 * (*ChannelSelect))) & 0x1);
          BadDramCs = (UINT8) ((temp >> (4 + 4 * (*ChannelSelect))) & 0x7);
          if (SwapDone && (cs == BadDramCs)) {
            // Find the spare rank for the channel
            for (spare = 0; spare < MAX_CS_PER_CHANNEL; ++spare) {
              if ((MemFGetPCI (NBPtr, *NodeID, DctNum, BFCSBaseAddr0Reg + spare) & 0x2) != 0) {
                *ChipSelect = spare;
                break;
              }
            }
          }
          ASSERT (*ChipSelect < MAX_CS_PER_CHANNEL);

          break;
        }
      }
    }
    if (CSFound) {
      break;
    }
  }

  // last ditch sanity check
  ASSERT (!CSFound || ((*NodeID < mmPtr->DieCount) && (*ChannelSelect < NBPtr[*NodeID].DctCount) && (*ChipSelect < MAX_CS_PER_CHANNEL)));
  if (CSFound) {
    return AGESA_SUCCESS;
  } else {
    return AGESA_BOUNDS_CHK;
  }

}


/*-----------------------------------------------------------------------------*/
/**
*
*   This function is the interface to call the PCI register access function
*   defined in NB block.
*
*   @param[in]   *NBPtr - Pointer to the parameter structure MEM_NB_BLOCK
*   @param[in]   NodeID - Node ID number of the target Northbridge
*   @param[in]   DctNum - DCT number if applicable, otherwise, put 0
*   @param[in]   BitFieldName - targeted bitfield
*
*   @retval      UINT32 - 32 bits PCI register value
*
*/
UINT32
STATIC
MemFGetPCI (
  IN   MEM_NB_BLOCK *NBPtr,
  IN   UINT8 NodeID,
  IN   UINT8 DctNum,
  IN   BIT_FIELD_NAME BitFieldName
  )
{
  MEM_NB_BLOCK *LocalNBPtr;
  UINT8 Die;

  // Find NBBlock that associates with node NodeID
  for (Die = 0; Die < MAX_NODES_SUPPORTED; Die ++)
    if (NBPtr[Die].Node == NodeID)
      break;

  ASSERT (Die < MAX_NODES_SUPPORTED);

  // Get the northbridge pointer for the targeted node.
  LocalNBPtr = &NBPtr[Die];
  LocalNBPtr->FamilySpecificHook[DCTSelectSwitch] (LocalNBPtr, &DctNum);
  LocalNBPtr->Dct = DctNum;
  // The caller of this function will take care of the ganged/unganged situation.
  // So Ganged is set to be false here, and do PCI read on the DCT specified by DctNum.
  return LocalNBPtr->GetBitField (LocalNBPtr, BitFieldName);
}

/*-----------------------------------------------------------------------------*/
/**
*
*   This function returns an even parity bit (making the total # of 1's even)
*   {0, 1} = number of set bits in argument is {even, odd}.
*
*   @param[in]   address - the address on which the parity bit will be calculated
*
*   @retval      UINT8 - parity bit
*
*/

UINT8
STATIC
MemFUnaryXOR (
  IN   UINT32 address
  )
{
  UINT8 parity;
  UINT8 index;
  parity = 0;
  for (index = 0; index < 32; ++ index) {
    parity = (UINT8) (parity ^ (address & 0x1));
    address = address >> 1;
  }
  return parity;
}
