/**
 * @file
 *
 * mtlrdimm3.c
 *
 * Technology initialization and control workd support for DDR3 LRDIMMS
 *
 * @xrefitem bom "File Content Label" "Release Content"
 * @e project: AGESA
 * @e sub-project: (Mem/Tech/DDR3)
 * @e \$Revision: 23714 $ @e \$Date: 2009-12-09 17:28:37 -0600 (Wed, 09 Dec 2009) $
 *
 **/
/*
 *****************************************************************************
 *
 * Copyright (c) 2011, 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 "Ids.h"
#include "mm.h"
#include "mn.h"
#include "mu.h"
#include "mt.h"
#include "mt3.h"
#include "mtspd3.h"
#include "mtrci3.h"
#include "mtsdi3.h"
#include "mtlrdimm3.h"
#include "merrhdl.h"
#include "Filecode.h"
CODE_GROUP (G1_PEICC)
RDATA_GROUP (G1_PEICC)
#define FILECODE PROC_MEM_TECH_DDR3_MTLRDIMM3_FILECODE
/*----------------------------------------------------------------------------
 *                          DEFINITIONS AND MACROS
 *
 *----------------------------------------------------------------------------
 */

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

/*----------------------------------------------------------------------------
 *                        PROTOTYPES OF LOCAL FUNCTIONS
 *
 *----------------------------------------------------------------------------
 */

VOID
STATIC
MemTSendMBCtlWord3 (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
  IN       UINT8 Fn,
  IN       UINT8 Rcw,
  IN       UINT8 Value
  );

UINT8
STATIC
MemTGetSpecialMBCtlWord3 (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
  IN       UINT8 Dimm,
  IN       UINT8 Fn,
  IN       UINT8 Rc
  );

BOOLEAN
STATIC
MemTLrDimmControlRegInit3 (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
  IN OUT   VOID *OptParam
  );

BOOLEAN
STATIC
MemTLrDimmFreqChgCtrlWrd3 (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
  IN OUT   VOID *OptParam
  );

BOOLEAN
STATIC
MemTWLPrepareLrdimm3 (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
  IN OUT   VOID *Wl
  );

BOOLEAN
STATIC
MemTSendAllMRCmdsLR3 (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
  IN OUT   VOID *CsPtr
  );

VOID
STATIC
MemTEMRS1Lr3 (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
  IN       UINT8 ChipSel,
  IN       UINT8 PhyRank
  );

VOID
STATIC
MemTEMRS2Lr3 (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
  IN       UINT8 ChipSel
  );


BOOLEAN
STATIC
MemTLrdimmRankMultiplication (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
  IN OUT   VOID *DimmID
  );

BOOLEAN
STATIC
MemTLrdimmBuf2DramTrain3 (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
  IN OUT   VOID *OptParam
  );

BOOLEAN
STATIC
MemTLrdimmSyncTrainedDlys (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
  IN OUT   VOID *OptParam
  );

/*----------------------------------------------------------------------------
 *                            EXPORTED FUNCTIONS
 *
 *----------------------------------------------------------------------------
 */

/* -----------------------------------------------------------------------------*/
/**
 *
 *   This function initializes LRDIMM functions.
 *
 *     @param[in,out]   *TechPtr   - Pointer to the MEM_TECH_BLOCK
 *
 */

BOOLEAN
MemTLrdimmConstructor3 (
  IN OUT   MEM_TECH_BLOCK *TechPtr
  )
{
  TechPtr->TechnologySpecificHook[LrdimmSendAllMRCmds]      = MemTSendAllMRCmdsLR3;
  TechPtr->TechnologySpecificHook[LrdimmControlRegInit]     = MemTLrDimmControlRegInit3;
  TechPtr->TechnologySpecificHook[LrdimmFreqChgCtrlWrd]     = MemTLrDimmFreqChgCtrlWrd3;
  TechPtr->TechnologySpecificHook[WlTrainingPrepareLrdimm]  = MemTWLPrepareLrdimm3;
  TechPtr->TechnologySpecificHook[LrdimmRankMultiplication] = MemTLrdimmRankMultiplication;
  TechPtr->TechnologySpecificHook[LrdimmBuf2DramTrain]      = MemTLrdimmBuf2DramTrain3;
  TechPtr->TechnologySpecificHook[LrdimmSyncTrainedDlys]    = MemTLrdimmSyncTrainedDlys;
  return TRUE;
}

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

/* -----------------------------------------------------------------------------*/
/**
 *
 *   This function sends a Control word command to an LRDIMM Memory Buffer
 *
 *     @param[in,out] *TechPtr - Pointer to the MEM_TECH_BLOCK
 *     @param[in]     Fn       - control word function
 *     @param[in]     Rcw      - control word number
 *     @param[in]     Value    - value to send
 *
 */

VOID
STATIC
MemTSendMBCtlWord3 (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
  IN       UINT8 Fn,
  IN       UINT8 Rcw,
  IN       UINT8 Value
  )
{
  MEM_NB_BLOCK  *NBPtr;

  NBPtr = TechPtr->NBPtr;

  ASSERT (Rcw != RCW_FN_SELECT);  // RC7 can only be used for function select
  IDS_HDT_CONSOLE (MEM_FLOW, "\t\tF%dRC%d = %x\n", Fn, Rcw, Value);
  //
  // Select the MB Function by sending the Fn number
  //  to the Function Select Control Word
  //
  MemUWait10ns (800, NBPtr->MemPtr);
  MemTSendCtlWord3 (TechPtr, RCW_FN_SELECT, Fn);
  //
  // Send the value to the control word
  //
  MemUWait10ns (800, NBPtr->MemPtr);
  MemTSendCtlWord3 (TechPtr, Rcw, Value);


}

/* -----------------------------------------------------------------------------*/
/**
 *
 *   This function gets the value of special RCW
 *
 *     @param[in,out] *TechPtr - Pointer to the MEM_TECH_BLOCK
 *     @param[in]     Dimm     - Physical LR DIMM number
 *     @param[in]     Fn       - control word function
 *     @param[in]     Rc       - control word number
 *
 *     @return      Special RCW value
 *
 */

UINT8
STATIC
MemTGetSpecialMBCtlWord3 (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
  IN       UINT8 Dimm,
  IN       UINT8 Fn,
  IN       UINT8 Rc
  )
{
  CONST UINT8   F0RC13PhyRankTab[] = {3, 2, 0, 1, 0};
  UINT8         PhyRanks;
  UINT8         LogRanks;
  UINT8         DramCap;
  UINT8         Value8;
  UINT8         *SpdBufferPtr;
  MEM_NB_BLOCK  *NBPtr;

  NBPtr = TechPtr->NBPtr;

  MemTGetDimmSpdBuffer3 (TechPtr, &SpdBufferPtr, Dimm);

  Value8 = 0;
  switch (Fn) {
  case 0:
    switch (Rc) {
    case 8:
      // F0RC8
      Value8 = NBPtr->PsPtr->F0RC8;
      break;
    case 10:
      // F0RC10
      // 2:0 OperatingSpeed: operating speed. BIOS: Table 88.
      if (NBPtr->DCTPtr->Timings.Speed == DDR667_FREQUENCY) {
        Value8 = 0;
      } else {
        Value8 = (UINT8) (NBPtr->DCTPtr->Timings.Speed / 133) - 3;
      }
      break;
    case 11:
      // F0RC11
      // 3:2 ParityCalculation: partiy calculation. BIOS: Table.
      // 1:0 OperatingVoltage: operating voltage. BIOS: IF(VDDIO == 1.5) THEN 00b ELSEIF (VDDIO ==
      //     1.35) THEN 01b ELSE 10b ENDIF.
      DramCap = SpdBufferPtr[SPD_DENSITY] & 0xF;
      if ((NBPtr->ChannelPtr->LrDimmRankMult[Dimm] + DramCap * 2) > 8) {
        Value8 = 8;
      } else {
        Value8 = 4;
      }
      Value8 |= NBPtr->RefPtr->DDR3Voltage;
      break;
    case 13:
      // F0RC13
      // 3:2 NumLogicalRanks: partiy calculation. BIOS: Table 90.
      // 1:0 NumPhysicalRanks: operating voltage. BIOS: Table 89.
      LogRanks = NBPtr->ChannelPtr->LrDimmLogicalRanks[Dimm] >> 1;
      PhyRanks = F0RC13PhyRankTab[(SpdBufferPtr[SPD_RANKS] >> 3) & 7];
      Value8 = (LogRanks << 2) | PhyRanks;
      break;
    case 14:
      // F0RC14
      // 3 DramBusWidth: DRAM bus width. BIOS: IF (DeviceWidth==0) THEN 0 ELSE 1 ENDIF.
      // 2 MRSCommandControl: MRS command control. BIOS: IF (F0RC15[RankMultiplicationControl]
      //   > 0) THEN 1 ELSE 0 ENDIF.
      // 1 RefreshPrechargeCommandControl: refresh and precharge command control. BIOS: IF
      //   (F0RC15[RankMultiplicationControl] > 0) THEN D18F2xA8_dct[1:0][LrDimmEnhRefEn] ELSE 0 ENDIF.
      // 0 AddressMirror: address mirror. BIOS: RankMap. See D18F2x[5C:40]_dct[1:0][OnDimmMirror].
      if ((SpdBufferPtr[SPD_DEV_WIDTH] & 7) != 0) {
        Value8 |= 8;
      }
      if (NBPtr->ChannelPtr->LrDimmRankMult[Dimm] > 1) {
        Value8 |= 4;
        if (NBPtr->GetBitField (NBPtr, BFLrDimmEnhRefEn) == 1) {
          Value8 |= 2;
        }
      }
      if ((SpdBufferPtr[SPD_ADDRMAP] & 1) != 0) {
        Value8 |= 1;
      }
      break;
    case 15:
      // F0RC15
      // 3:0 RankMultiplicationControl: rank multiplication control. BIOS: Table 91.
      DramCap = SpdBufferPtr[SPD_DENSITY] & 0xF;
      ASSERT ((DramCap >= 2) && (DramCap <= 4));            // BKDG only lists 1Gb, 2Gb, and 4Gb
      switch (NBPtr->ChannelPtr->LrDimmRankMult[Dimm]) {
      case 1:
        Value8 = 0;
        break;
      case 2:
        Value8 = DramCap - 1;
        break;
      case 4:
        Value8 = DramCap + 3;
        break;
      default:
        ASSERT (FALSE);
      }
      break;
    default:
      ASSERT (FALSE);
    }
    break;
  case 1:
    switch (Rc) {
    case 0:
      // F1RC0
      Value8 = NBPtr->PsPtr->F1RC0;
      Value8 |= (UINT8) NBPtr->GetBitField (NBPtr, BFCSTimingMux67) << 3;
      break;
    case 1:
      // F1RC1
      Value8 = NBPtr->PsPtr->F1RC1;
      break;
    case 2:
      // F1RC2
      Value8 = NBPtr->PsPtr->F1RC2;
      break;
    case 9:
      // F1RC9
      if (NBPtr->GetBitField (NBPtr, BFLrDimmEnhRefEn) == 0) {
        Value8 = 1;
      }
      break;
    default:
      ASSERT (FALSE);
    }
    break;
  case 3:
    switch (Rc) {
    case 0:
      // F3RC0
      // 3   TDQSControl: TDQS control. BIOS: 0.
      // 2:0 RttNom: RttNom. BIOS: Table 57, Table 60
      Value8 = NBPtr->PsPtr->RttNom[Dimm << 1];
      break;
    case 1:
      // F3RC1
      // 3   Vref: Vref. BIOS: 0.
      // 2:0 RttWr: RttWr. BIOS: Table 57, Table 60.
      Value8 = NBPtr->PsPtr->RttWr[Dimm << 1];
      break;
    default:
      ASSERT (FALSE);
    }
    break;
  default:
    ASSERT (FALSE);
  }

  return Value8;
}

/* -----------------------------------------------------------------------------*/
/**
 *
 *   This function sends LRDIMM Control Words to all LRDIMMS
 *
 *     @param[in,out]   *TechPtr   - Pointer to the MEM_TECH_BLOCK
 *     @param[in,out]   OptParam   - Optional parameter
 *
 *     @return    TRUE
 */

BOOLEAN
STATIC
MemTLrDimmControlRegInit3 (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
  IN OUT   VOID *OptParam
  )
{
  CONST UINT8 RCWInitTable[] = {
    //  RCW,  Mask, SPD
    F0, RC0,  0x00, SPD_NONE,
    F0, RC1,  0x00, SPD_NONE,
    F0, RC2,  0x03, SPD_67,
    F0, RC10, 0x00, SPECIAL_CASE,
    F0, RC11, 0x00, SPECIAL_CASE,

    F1, RC8,  0x0F, SPD_69,
    F1, RC11, 0xF0, SPD_69,
    F1, RC12, 0x0F, SPD_70,
    F1, RC13, 0xF0, SPD_70,
    F1, RC14, 0x0F, SPD_71,
    F1, RC15, 0xF0, SPD_71,

    WAIT_6US, 0, 0, 0,

    F0, RC3,  0xF0, SPD_67,
    F0, RC4,  0x0F, SPD_68,
    F0, RC5,  0xF0, SPD_68,

    F0, RC6,  0x00, SPD_NONE,
    F0, RC8,  0x00, SPECIAL_CASE,
    F0, RC9,  0x0C, SPD_NONE,
    F0, RC13, 0x00, SPECIAL_CASE,
    F0, RC14, 0x00, SPECIAL_CASE,
    F0, RC15, 0x00, SPECIAL_CASE,

    F1, RC0,  0x00, SPECIAL_CASE,
    F1, RC1,  0x00, SPECIAL_CASE,
    F1, RC2,  0x00, SPECIAL_CASE,
    F1, RC3,  0x00, SPD_NONE,
    F1, RC9,  0x00, SPECIAL_CASE,
    F1, RC10, 0x00, SPD_NONE,

    F2, RC0,  0x00, SPD_NONE,
    F2, RC1,  0x00, SPD_NONE,
    F2, RC2,  0x0F, SPD_NONE,
    F2, RC3,  0x00, SPD_NONE,

    F3, RC0,  0x00, SPECIAL_CASE,
    F3, RC1,  0x00, SPECIAL_CASE,
    F3, RC2,  0x01, SPD_NONE,
    F3, RC6,  0x08, SPD_NONE

    // F3 RC[8,9] are programmed in MDQ RC loop

    // F[10:3] RC[11,10] are programmed in QxODT RC loop

    // F[15,14] RC[15:0] are programmed in personality RC loop
  };

  UINT8           Dimm;
  UINT16          i;
  UINT16          DimmMask;
  UINT8           Fn;
  UINT8           Rc;
  UINT8           Mask;
  UINT8           Spd;
  UINT8           *SpdBufferPtr;
  UINT8           FreqDiffOffset;
  UINT8           Value8;
  MEM_DATA_STRUCT *MemPtr;
  MEM_NB_BLOCK    *NBPtr;

  NBPtr = TechPtr->NBPtr;
  MemPtr = NBPtr->MemPtr;

  if (NBPtr->MCTPtr->Status[SbLrdimms]) {
    for (Dimm = 0; Dimm < MAX_DIMMS_PER_CHANNEL; Dimm++) {
      DimmMask = (UINT16)1 << Dimm;
      if ((NBPtr->ChannelPtr->LrDimmPresent & DimmMask) != 0) {
        IDS_HDT_CONSOLE (MEM_FLOW, "\t\tSending LRDIMM Control Words: Dimm %02x\n", Dimm);
        //
        // Select the Target Chipselects
        //
        NBPtr->SetBitField (NBPtr, BFMrsChipSel, (Dimm << 1));
        NBPtr->SetBitField (NBPtr, BFCtrlWordCS, 3 << (Dimm << 1));

        MemTGetDimmSpdBuffer3 (TechPtr, &SpdBufferPtr, Dimm);
        for (i = 0; i < sizeof (RCWInitTable) ; i += 4) {
          Fn   = RCWInitTable[i];
          Rc   = RCWInitTable[i + 1];
          Mask = RCWInitTable[i + 2];
          Spd  = RCWInitTable[i + 3];

          if (Fn == WAIT_6US) {
            MemUWait10ns (600, MemPtr);   // wait 6us for TSTAB
          } else {
            if (Spd == SPD_NONE) {
              Value8 = Mask;
            } else if (Spd == SPECIAL_CASE) {
              Value8 = MemTGetSpecialMBCtlWord3 (TechPtr, Dimm, Fn, Rc);
            } else {
              Value8 = (Mask > 0x0F) ? ((SpdBufferPtr[Spd] & Mask) >> 4) : (SpdBufferPtr[Spd] & Mask);
            }
            MemTSendMBCtlWord3 (TechPtr, Fn, Rc, Value8);
          }
        }

        FreqDiffOffset = (UINT8) (SPD_FREQ_DIFF_OFFSET * (((NBPtr->DCTPtr->Timings.Speed / 133) - 3) / 2));
        //
        // Send RCW to program MDQ termination and drive strength
        //
        for (Rc = 8; Rc <= 9; Rc++) {
          Value8 = SpdBufferPtr[SPD_MDQ_800_1066 + FreqDiffOffset];
          Value8 = (Rc == 9) ? (Value8 >> 4) : Value8;
          MemTSendMBCtlWord3 (TechPtr, 3, Rc, Value8 & 0x07);
        }

        //
        // Send RCW to program QxODT
        //
        for (Fn = 3; Fn <= 10; Fn ++) {
          for (Rc = 10; Rc <= 11; Rc++) {
            Value8 = SpdBufferPtr[SPD_QXODT_800_1066 + FreqDiffOffset + ((Fn - 3) >> 1)];
            Value8 = (Rc == 11) ? (Value8 >> 4) : (Value8 & 0x0F);
            Value8 = ((Fn & 1) == 0) ? (Value8 >> 2) : (Value8 & 0x03);
            MemTSendMBCtlWord3 (TechPtr, Fn, Rc, Value8);
          }
        }

        //
        // Send Personality bytes from SPD
        //
        for (Fn = 14; Fn < 16; Fn ++) {
          for (Rc = 0; Rc < 16 ; Rc++) {
            Value8 = SpdBufferPtr[SPD_PERSONALITY_BYTE + ((Fn - 14) << 3) + (Rc >> 1)];
            if ((Fn == 14) && (Rc == 0)) {
              Value8 |= 0x01;   // Write global enable
            }
            if (Rc != 0x07) {
              MemTSendMBCtlWord3 (TechPtr, Fn, Rc, ((Rc & 1) != 0) ? (Value8 >> 4) : (Value8 & 0x0F));
            }
          }
        }
      }
    }
  }
  return TRUE;
}

/* -----------------------------------------------------------------------------*/
/**
 *
 *   This function sends LRDIMM Control Words to all LRDIMMS
 *
 *     @param[in,out]   *TechPtr   - Pointer to the MEM_TECH_BLOCK
 *     @param[in,out]   OptParam   - Optional parameter
 *
 *     @return    FALSE  - The current channel does not have LRDIMM populated
 *                TRUE   - The current channel has LRDIMM populated
 */

BOOLEAN
STATIC
MemTLrDimmFreqChgCtrlWrd3 (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
  IN OUT   VOID *OptParam
  )
{
  UINT8 Dct;
  MEM_NB_BLOCK    *NBPtr;

  NBPtr = TechPtr->NBPtr;

  if (NBPtr->MCTPtr->Status[SbLrdimms]) {
    for (Dct = 0; Dct < NBPtr->DctCount; Dct++) {
      MemNSwitchDCTNb (NBPtr, Dct);
      if (NBPtr->DCTPtr->Timings.DctMemSize != 0) {
        MemTLrDimmControlRegInit3 (TechPtr, NULL);
      }
    }
    return TRUE;
  }
  return FALSE;
}

/*-----------------------------------------------------------------------------
 *
 *
 *     This function prepares LRDIMMs for WL training.
 *
 *     @param[in,out]  *TechPtr     - Pointer to the MEM_TECH_BLOCK
 *     @param[in,out]  *Wl - Indicates if WL mode should be enabled
 *
 *     @return    TRUE - LRDIMMs present
 * ----------------------------------------------------------------------------
 */
BOOLEAN
STATIC
MemTWLPrepareLrdimm3 (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
  IN OUT   VOID *Wl
  )
{
  UINT8 Dimm;
  UINT8 Value8;
  UINT16 MrsAddress;
  MEM_NB_BLOCK  *NBPtr;
  NBPtr = TechPtr->NBPtr;
  MrsAddress = 0;
  if (NBPtr->MCTPtr->Status[SbLrdimms]) {
    for (Dimm = 0; Dimm < MAX_DIMMS_PER_CHANNEL; Dimm++) {
      if (*(BOOLEAN *) Wl == TRUE) {
        // Program WrLvOdt
        NBPtr->SetBitField (NBPtr, BFWrLvOdt, NBPtr->ChannelPtr->PhyWLODT[Dimm]);
      }
      if ((NBPtr->ChannelPtr->LrDimmPresent & ((UINT8) 1 << Dimm)) != 0) {
        if (Dimm == TechPtr->TargetDIMM) {
          if (*(BOOLEAN *) Wl == TRUE) {
            //
            // Select the Target Chipselects
            //
            NBPtr->SetBitField (NBPtr, BFMrsChipSel, (Dimm << 1));
            NBPtr->SetBitField (NBPtr, BFCtrlWordCS, 3 << (Dimm << 1));

            // Program F0RC12 to 1h
            MemTSendMBCtlWord3 (TechPtr, F0, RC12, 0x01);
            if (NBPtr->ChannelPtr->Dimms >= 2) {
              // For two or more LRDIMMs per channel program the buffer RttNom to the
              // corresponding specifed RttWr termination
              Value8 = NBPtr->MemNGetDynDramTerm (NBPtr, Dimm << 2);
            } else {
              // Program RttNom as normal
              Value8 = NBPtr->MemNGetDramTerm (NBPtr, Dimm << 2);
            }
            if ((Value8 & ((UINT8) 1 << 2)) != 0) {
              MrsAddress |= ((UINT16) 1 << 9);
            }
            if ((Value8 & ((UINT8) 1 << 1)) != 0) {
              MrsAddress |= ((UINT16) 1 << 6);
            }
            if ((Value8 & ((UINT8) 1 << 0)) != 0) {
              MrsAddress |= ((UINT16) 1 << 2);
            }
            NBPtr->SetBitField (NBPtr, BFMrsAddress, MrsAddress);
          } else {
            // Program F0RC12 to 0h
            MemTSendMBCtlWord3 (TechPtr, F0, RC12, 0x00);
          }
        }
      }
    }
    return TRUE;
  } else {
    return FALSE;
  }
}

/* -----------------------------------------------------------------------------*/
/**
 *
 *   This send all MR commands to all physical ranks of an LRDIMM
 *
 *     @param[in,out]   *TechPtr   - Pointer to the MEM_TECH_BLOCK
 *     @param[in]       *CsPtr     - Target Chip Select
 *
 *     @return    FALSE  - The current channel does not have LRDIMM populated
 *                TRUE   - The current channel has LRDIMM populated
 */

BOOLEAN
STATIC
MemTSendAllMRCmdsLR3 (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
  IN OUT   VOID *CsPtr
  )
{
  UINT8        *SpdBufferPtr;
  BOOLEAN       Skip;
  UINT8         Rank;
  UINT8         PhyRank;
  UINT8         ChipSel;
  MEM_NB_BLOCK  *NBPtr;

  NBPtr = TechPtr->NBPtr;
  ChipSel = *((UINT8 *) CsPtr);

  if (NBPtr->MCTPtr->Status[SbLrdimms]) {
    //
    // LRDIMM: MR0, MR2, and MR3 can be broadcasted.
    //         MR1[Rtt_Nom] needs to be programmed differently per physical ranks.
    //
    //    CS         0   1   2   3   4   5   6   7
    //    MR[0,2,3]  x       x       ?
    //    MR1        x   x   x   x   x   x   x   x
    //
    // ? If 3 DIMMs/ch, need to send to CS4 since it is on the 3rd physical DIMM.
    //
    Skip = TRUE;
    switch (ChipSel) {
    case 0:
    case 2:
      Skip = FALSE;
      break;
    case 4:
      if (GetMaxDimmsPerChannel (NBPtr->RefPtr->PlatformMemoryConfiguration,
            NBPtr->MCTPtr->SocketId,
            NBPtr->ChannelPtr->ChannelID) == 3) {
        Skip = FALSE;
      }
      break;
    }

    // Select target chip select
    NBPtr->SetBitField (NBPtr, BFMrsChipSel, ChipSel);

    if (!Skip) {
      // 13.Send EMRS(2)
      MemTEMRS2Lr3 (TechPtr, ChipSel);
      NBPtr->SetBitField (NBPtr, BFMrsAddressHi, 1);    // Set Address bit 13 to broadcast
      NBPtr->SendMrsCmd (NBPtr);

      // 14.Send EMRS(3). Ordinarily at this time, MrsAddress[2:0]=000b
      MemTEMRS33 (TechPtr);
      NBPtr->SetBitField (NBPtr, BFMrsAddressHi, 1);    // Set Address bit 13 to broadcast
      NBPtr->SendMrsCmd (NBPtr);
    }

    // 15.Send EMRS(1). Send to each physical rank.
    MemTGetDimmSpdBuffer3 (TechPtr, &SpdBufferPtr, ChipSel >> 1);
    for (Rank = 0; Rank < NBPtr->ChannelPtr->LrDimmRankMult[ChipSel >> 1]; Rank++) {
      PhyRank = (((ChipSel >> 1) & 2) | (ChipSel & 1)) + (Rank * NBPtr->ChannelPtr->LrDimmLogicalRanks[ChipSel >> 1]);
      MemTEMRS1Lr3 (TechPtr, ChipSel, PhyRank);
      // Set Address bit 14, 15, 16, or 17 to select physical rank according to the device size
      NBPtr->SetBitField (NBPtr, BFMrsAddressHi, Rank << (SpdBufferPtr[SPD_DENSITY] & 0xF));
      NBPtr->SendMrsCmd (NBPtr);
    }

    if (!Skip) {
      // 16.Send MRS with MrsAddress[8]=1(reset the DLL)
      MemTMRS3 (TechPtr);
      NBPtr->SetBitField (NBPtr, BFMrsAddressHi, 1);    // Set Address bit 13 to broadcast
      NBPtr->SendMrsCmd (NBPtr);
    }

    // If LRDIMM, return TRUE to skip sending regular MR commands.
    return TRUE;
  }
  // If not LRDIMM, send regular MR commands.
  return FALSE;
}

/* -----------------------------------------------------------------------------*/
/**
 *
 *   This function calculates the EMRS1 value for an LRDIMM
 *
 *     @param[in,out]   *TechPtr   - Pointer to the MEM_TECH_BLOCK
 *     @param[in]        ChipSel   - Chip select number
 *     @param[in]        PhyRank   - Physical rank number
 */

VOID
STATIC
MemTEMRS1Lr3 (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
  IN       UINT8 ChipSel,
  IN       UINT8 PhyRank
  )
{
  UINT16       MrsAddress;
  UINT8        Value8;
  UINT8        *SpdBufferPtr;
  UINT8        FreqDiffOffset;
  MEM_NB_BLOCK *NBPtr;

  NBPtr = TechPtr->NBPtr;

  MemTGetDimmSpdBuffer3 (TechPtr, &SpdBufferPtr, ChipSel >> 1);
  FreqDiffOffset = (UINT8) (SPD_FREQ_DIFF_OFFSET * (((NBPtr->DCTPtr->Timings.Speed / 133) - 3) / 2));

  // BA2=0,BA1=0,BA0=1
  NBPtr->SetBitField (NBPtr, BFMrsBank, 1);

  MrsAddress = 0;

  // program MrsAddress[5,1]=output driver impedance control (DIC): 01b
  MrsAddress |= ((UINT16) 1 << 1);

  // program MrsAddress[5,1]=output driver impedance control (DIC):
  // DIC is read from SPD byte 77, 83, or 89 depending on DDR speed
  Value8 = SpdBufferPtr[SPD_MR1_MR2_800_1066 + FreqDiffOffset] & 3;
  if ((Value8 & ((UINT8) 1 << 1)) != 0) {
    MrsAddress |= ((UINT16) 1 << 5);
  }
  if ((Value8 & ((UINT8) 1 << 0)) != 0) {
    MrsAddress |= ((UINT16) 1 << 1);
  }

  // program MrsAddress[9,6,2]=nominal termination resistance of ODT (RTT):
  // RttNom is read from SPD byte 77, 83, or 89 depending on DDR speed
  if (PhyRank <= 1) {
    Value8 = (SpdBufferPtr[SPD_MR1_MR2_800_1066 + FreqDiffOffset] >> 2) & 7;
    if ((Value8 & ((UINT8) 1 << 2)) != 0) {
      MrsAddress |= ((UINT16) 1 << 9);
    }
    if ((Value8 & ((UINT8) 1 << 1)) != 0) {
      MrsAddress |= ((UINT16) 1 << 6);
    }
    if ((Value8 & ((UINT8) 1 << 0)) != 0) {
      MrsAddress |= ((UINT16) 1 << 2);
    }
  }

  NBPtr->SetBitField (NBPtr, BFMrsAddress, MrsAddress);
}

/* -----------------------------------------------------------------------------*/
/**
 *
 *   This function calculates the EMRS2 value for an LRDIMM
 *
 *     @param[in,out]   *TechPtr   - Pointer to the MEM_TECH_BLOCK
 *     @param[in]        ChipSel   - Chip select number
 */

VOID
STATIC
MemTEMRS2Lr3 (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
  IN       UINT8 ChipSel
  )
{
  UINT8        RttWr;
  UINT8        *SpdBufferPtr;
  UINT8        FreqDiffOffset;
  MEM_NB_BLOCK *NBPtr;

  NBPtr = TechPtr->NBPtr;

  // Save default RttWr
  RttWr = NBPtr->PsPtr->RttWr[ChipSel];

  // Override RttWr with the value read from SPD byte 77, 83, or 89 depending on DDR speed
  MemTGetDimmSpdBuffer3 (TechPtr, &SpdBufferPtr, ChipSel >> 1);
  FreqDiffOffset = (UINT8) (SPD_FREQ_DIFF_OFFSET * (((NBPtr->DCTPtr->Timings.Speed / 133) - 3) / 2));
  NBPtr->PsPtr->RttWr[ChipSel] = SpdBufferPtr[SPD_MR1_MR2_800_1066 + FreqDiffOffset] >> 6;

  // Call EMRS2 calculation
  MemTEMRS23 (TechPtr);

  // Restore RttWr
  NBPtr->PsPtr->RttWr[ChipSel] = RttWr;
}

/*-----------------------------------------------------------------------------
 *
 *
 *     This function to determine the Rank Multiplication to use for an LRDIMM
 *
 *     @param[in,out]  *TechPtr     - Pointer to the MEM_TECH_BLOCK
 *     @param[in,out]  *DimmID      - Dimm ID
 *
 *     @return    TRUE - LRDIMM Support is installed and LRDIMMs are present
 * ----------------------------------------------------------------------------
 */
BOOLEAN
STATIC
MemTLrdimmRankMultiplication (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
  IN OUT   VOID *DimmID
  )
{
  BOOLEAN RetVal;
  UINT8 *SpdBufferPtr;
  UINT8 Dimm;
  UINT8 NumDimmslots;
  UINT8 DramCapacity;
  UINT8 Ranks;
  UINT8 Rows;
  UINT8 RankMult;
  MEM_NB_BLOCK  *NBPtr;
  CH_DEF_STRUCT *ChannelPtr;

  ASSERT (TechPtr != NULL);
  ASSERT (DimmID != NULL);

  Dimm = *(UINT8*)DimmID;
  ASSERT (Dimm < MAX_DIMMS_PER_CHANNEL);

  NBPtr = TechPtr->NBPtr;
  ChannelPtr = NBPtr->ChannelPtr;
  RetVal = FALSE;
  RankMult = 0;

  if (!MemTGetDimmSpdBuffer3 (TechPtr, &SpdBufferPtr, Dimm)) {
    ASSERT (FALSE);
  }

  NumDimmslots = GetMaxDimmsPerChannel (NBPtr->RefPtr->PlatformMemoryConfiguration,
                                        NBPtr->MCTPtr->SocketId,
                                        ChannelPtr->ChannelID);

  if (NBPtr->MCTPtr->Status[SbLrdimms]) {
    RetVal = TRUE;
    //
    // Determine LRDIMM Rank Multiplication
    //
    Ranks = ((SpdBufferPtr[SPD_RANKS] >> 3) & 0x07) + 1;
    if (Ranks == 5) {
      Ranks = 8;
    }
    DramCapacity = (SpdBufferPtr[SPD_DENSITY] & 0x0F);
    Rows = 12 + (SpdBufferPtr[SPD_ROW_SZ] >> 3) & 0x7;

    if (Ranks < 4) {
      RankMult = 1;
    } else if (Ranks == 4) {
      RankMult = (NumDimmslots < 3) ? 1 : 2;
    } else if (Ranks == 8) {
      RankMult = ((NumDimmslots < 3) && (DramCapacity < 4)) ? 2 : 4;
    }
    //
    // Save Rank Information
    //
    ChannelPtr->LrDimmRankMult[Dimm] = RankMult;
    ChannelPtr->LrDimmLogicalRanks[Dimm] = Ranks / RankMult;
    NBPtr->PsPtr->LrdimmRowAddrBits[Dimm] = Rows + (RankMult >> 1);
    //
    // Program RankDef
    //
    NBPtr->SetBitField (NBPtr, BFRankDef0 + Dimm, (RankMult == 4) ? 3 : RankMult);
    //
    // If LrdimmRowAddressBits > 16, then we must be useing some CS signals for rank
    //   multiplication.  If this is the case, then we want to clear the CSPresent bits
    //   that correspond to those  chipselects.
    //   If there are 3 DIMMs per channel, then it will always be CS67, if there are
    //   2DPCH, then DIMM0 will use CS45, adn DIMM1 will use CS67.
    //
    if (NBPtr->PsPtr->LrdimmRowAddrBits[Dimm] > 16) {
      NBPtr->DCTPtr->Timings.CsPresent &= ~(0x30 << ((NumDimmslots > 2) ? 1 : Dimm) );
    }
  }

  return RetVal;

}

/* -----------------------------------------------------------------------------*/
/**
 *
 *   This function performs buffer to DRAM training for LRDIMMs
 *
 *     @param[in,out]   *TechPtr   - Pointer to the MEM_TECH_BLOCK
 *     @param[in,out]   OptParam   - Optional parameter
 *
 *     @return    TRUE
 */

BOOLEAN
STATIC
MemTLrdimmBuf2DramTrain3 (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
  IN OUT   VOID *OptParam
  )
{
  UINT8  Dct;
  UINT8  Dimm;
  UINT8  ChipSel;
  UINT16 DimmMask;
  UINT8  i;
  MEM_DATA_STRUCT *MemPtr;
  MEM_NB_BLOCK    *NBPtr;

  NBPtr = TechPtr->NBPtr;
  MemPtr = NBPtr->MemPtr;

  if (NBPtr->MCTPtr->Status[SbLrdimms]) {
    IDS_HDT_CONSOLE (MEM_FLOW, "\nStart Buffer to DRAM training\n");

    for (Dct = 0; Dct < NBPtr->DctCount; Dct++) {
      IDS_HDT_CONSOLE (MEM_STATUS, "\tDct %d\n", Dct);
      NBPtr->SwitchDCT (NBPtr, Dct);

      //
      // ODM needs to be set after Dram Init
      //
      if (NBPtr->StartupSpeed == NBPtr->DCTPtr->Timings.Speed) {
        for (ChipSel = 1; ChipSel < MAX_CS_PER_CHANNEL; ChipSel += 2) {
          if ((NBPtr->DCTPtr->Timings.CsPresent & ((UINT16)1 << ChipSel)) != 0) {
            if ((NBPtr->DCTPtr->Timings.DimmMirrorPresent & (1 << (ChipSel >> 1))) != 0) {
              NBPtr->SetBitField (NBPtr, BFCSBaseAddr0Reg + ChipSel, ((NBPtr->GetBitField (NBPtr, BFCSBaseAddr0Reg + ChipSel)) | ((UINT32)1 << BFOnDimmMirror )));
            }
          }
        }
      }

      //
      // Buffer to DRAM training
      //
      for (Dimm = 0; Dimm < MAX_DIMMS_PER_CHANNEL; Dimm++) {
        DimmMask = (UINT16)1 << Dimm;
        if ((NBPtr->ChannelPtr->LrDimmPresent & DimmMask) != 0) {
          IDS_HDT_CONSOLE (MEM_STATUS, "\t\nDimm %d\n", Dimm);
          // Select the Target Chipselects
          NBPtr->SetBitField (NBPtr, BFMrsChipSel, (Dimm << 1));
          NBPtr->SetBitField (NBPtr, BFCtrlWordCS, 3 << (Dimm << 1));

          // Send F0RC12 with data = 0010b.
          MemTSendMBCtlWord3 (TechPtr, F0, RC12, 2);

          // Wait until D18F2xA0_dct[1:0][RcvParErr]=0 or tCAL * the number of physical ranks expires.
          IDS_HDT_CONSOLE (MEM_FLOW, "\t\tWaiting %d ms...\n", 10 * NBPtr->ChannelPtr->LrDimmRankMult[Dimm]);
          for (i = 0; i < (NBPtr->ChannelPtr->LrdimmPhysicalRanks[Dimm] * 10); i++) {
            MemUWait10ns (1000000, MemPtr);
          }

          // Configure for normal operation: Send F0RC12 with data = 0000b.
          MemTSendMBCtlWord3 (TechPtr, F0, RC12, 0);
        }
      }
    }
    IDS_HDT_CONSOLE (MEM_FLOW, "\nEnd Buffer to DRAM training\n");
  }
  return TRUE;
}

/* -----------------------------------------------------------------------------*/
/**
 *
 *   This function copies trained delays of the first rank of a QR LRDIMM to the third rank
 *
 *     @param[in,out]   *TechPtr   - Pointer to the MEM_TECH_BLOCK
 *     @param[in,out]   OptParam   - Optional parameter
 *
 *     @return    TRUE
 */

BOOLEAN
STATIC
MemTLrdimmSyncTrainedDlys (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
  IN OUT   VOID *OptParam
  )
{
  UINT8 i;
  UINT8 Dimm;
  MEM_NB_BLOCK *NBPtr;
  CH_DEF_STRUCT *ChannelPtr;

  NBPtr = TechPtr->NBPtr;
  ChannelPtr = NBPtr->ChannelPtr;

  if (NBPtr->MCTPtr->Status[SbLrdimms]) {
    for (Dimm = 0; Dimm < MAX_DIMMS_PER_CHANNEL; Dimm++) {
      for (i = 0; i < TechPtr->DlyTableWidth (); i++) {
        if (ChannelPtr->LrDimmLogicalRanks[Dimm] > 2) {
          // If logical QR LRDIMM, copy trained delays from first rank to third rank
          NBPtr->SetTrainDly (NBPtr, AccessWrDqsDly, DIMM_BYTE_ACCESS (Dimm + 2, i),
                              ChannelPtr->WrDqsDlys[Dimm * TechPtr->DlyTableWidth () + i]);
          NBPtr->SetTrainDly (NBPtr, AccessRcvEnDly, DIMM_BYTE_ACCESS (Dimm + 2, i),
                              ChannelPtr->RcvEnDlys[Dimm * TechPtr->DlyTableWidth () + i]);
          NBPtr->SetTrainDly (NBPtr, AccessRdDqsDly, DIMM_BYTE_ACCESS (Dimm + 2, i),
                              ChannelPtr->RdDqsDlys[Dimm * TechPtr->DlyTableWidth () + i]);
          NBPtr->SetTrainDly (NBPtr, AccessWrDqsDly, DIMM_BYTE_ACCESS (Dimm + 2, i),
                              ChannelPtr->WrDatDlys[Dimm * TechPtr->DlyTableWidth () + i]);
        }
      }
    }
    return TRUE;
  } else {
    return FALSE;
  }
}

