/* $NoKeywords:$ */
/**
 * @file
 *
 * mttdimmbt.c
 *
 * Technology Dimm Based Training
 *
 * @xrefitem bom "File Content Label" "Release Content"
 * @e project: AGESA
 * @e sub-project: (Mem/Tech)
 * @e \$Revision: 63425 $ @e \$Date: 2011-12-22 11:24:10 -0600 (Thu, 22 Dec 2011) $
 *
 **/
/*****************************************************************************
*
* Copyright 2008 - 2012 ADVANCED MICRO DEVICES, INC.  All Rights Reserved.
*
* AMD is granting you permission to use this software (the Materials)
* pursuant to the terms and conditions of your Software License Agreement
* with AMD.  This header does *NOT* give you permission to use the Materials
* or any rights under AMD's intellectual property.  Your use of any portion
* of these Materials shall constitute your acceptance of those terms and
* conditions.  If you do not agree to the terms and conditions of the Software
* License Agreement, please do not use any portion of these Materials.
*
* CONFIDENTIALITY:  The Materials and all other information, identified as
* confidential and provided to you by AMD shall be kept confidential in
* accordance with the terms and conditions of the Software License Agreement.
*
* LIMITATION OF LIABILITY: THE MATERIALS AND ANY OTHER RELATED INFORMATION
* PROVIDED TO YOU BY AMD ARE PROVIDED "AS IS" WITHOUT ANY EXPRESS OR IMPLIED
* WARRANTY OF ANY KIND, INCLUDING BUT NOT LIMITED TO WARRANTIES OF
* MERCHANTABILITY, NONINFRINGEMENT, TITLE, FITNESS FOR ANY PARTICULAR PURPOSE,
* OR WARRANTIES ARISING FROM CONDUCT, COURSE OF DEALING, OR USAGE OF TRADE.
* IN NO EVENT SHALL AMD OR ITS LICENSORS BE LIABLE FOR ANY DAMAGES WHATSOEVER
* (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF PROFITS, BUSINESS
* INTERRUPTION, OR LOSS OF INFORMATION) ARISING OUT OF AMD'S NEGLIGENCE,
* GROSS NEGLIGENCE, THE USE OF OR INABILITY TO USE THE MATERIALS OR ANY OTHER
* RELATED INFORMATION PROVIDED TO YOU BY AMD, EVEN IF AMD HAS BEEN ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGES.  BECAUSE SOME JURISDICTIONS PROHIBIT THE
* EXCLUSION OR LIMITATION OF LIABILITY FOR CONSEQUENTIAL OR INCIDENTAL DAMAGES,
* THE ABOVE LIMITATION MAY NOT APPLY TO YOU.
*
* AMD does not assume any responsibility for any errors which may appear in
* the Materials or any other related information provided to you by AMD, or
* result from use of the Materials or any related information.
*
* You agree that you will not reverse engineer or decompile the Materials.
*
* NO SUPPORT OBLIGATION: AMD is not obligated to furnish, support, or make any
* further information, software, technical information, know-how, or show-how
* available to you.  Additionally, AMD retains the right to modify the
* Materials at any time, without notice, and is not obligated to provide such
* modified Materials to you.
*
* U.S. GOVERNMENT RESTRICTED RIGHTS: The Materials are provided with
* "RESTRICTED RIGHTS." Use, duplication, or disclosure by the Government is
* subject to the restrictions as set forth in FAR 52.227-14 and
* DFAR252.227-7013, et seq., or its successor.  Use of the Materials by the
* Government constitutes acknowledgement of AMD's proprietary rights in them.
*
* EXPORT ASSURANCE:  You agree and certify that neither the Materials, nor any
* direct product thereof will be exported directly or indirectly, into any
* country prohibited by the United States Export Administration Act and the
* regulations thereunder, without the required authorization from the U.S.
* government nor will be used for any purpose prohibited by the same.
* ***************************************************************************
*
*/

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



#include "AGESA.h"
#include "Ids.h"
#include "mm.h"
#include "mn.h"
#include "mt.h"
#include "GeneralServices.h"
#include "heapManager.h"
#include "Filecode.h"
CODE_GROUP (G1_PEICC)
RDATA_GROUP (G1_PEICC)

#define FILECODE PROC_MEM_TECH_MTTDIMBT_FILECODE

/*----------------------------------------------------------------------------
 *                          DEFINITIONS AND MACROS
 *
 *----------------------------------------------------------------------------
 */

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

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

VOID
STATIC
MemTInitDqsPos4RcvrEnByte (
  IN OUT   MEM_TECH_BLOCK *TechPtr
  );

VOID
STATIC
MemTSetRcvrEnDlyByte (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
  IN       UINT8 Receiver,
  IN       UINT16 RcvEnDly
  );

VOID
STATIC
MemTLoadRcvrEnDlyByte (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
  IN       UINT8 Receiver
  );

BOOLEAN
STATIC
MemTSaveRcvrEnDlyByte (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
  IN       UINT8 Receiver,
  IN       UINT16 RcvEnDly,
  IN       UINT16 CmpResultRank0,
  IN       UINT16 CmpResultRank1
  );

VOID
STATIC
MemTResetDctWrPtrByte (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
  IN       UINT8 Receiver
  );

UINT16
STATIC
MemTCompare1ClPatternByte (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
  IN       UINT8 Buffer[],
  IN       UINT8 Pattern[]
  );

VOID
STATIC
MemTSkipChipSelPass1Byte (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
  IN OUT   UINT8 *ChipSelPtr
  );

VOID
STATIC
MemTSkipChipSelPass2Byte (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
  IN OUT   UINT8 *ChipSelPtr
  );

UINT8
STATIC
MemTMaxByteLanesByte ( VOID );

UINT8
STATIC
MemTDlyTableWidthByte ( VOID );

VOID
STATIC
MemTSetDqsDelayCsrByte (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
  IN       UINT8 ByteLane,
  IN       UINT8 Dly
  );

VOID
STATIC
MemTDqsWindowSaveByte (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
  IN       UINT8 ByteLane,
  IN       UINT8 DlyMin,
  IN       UINT8 DlyMax
  );

BOOLEAN
STATIC
MemTFindMaxRcvrEnDlyByte (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
     OUT   UINT8 *ChipSel
  );

UINT16
STATIC
MemTCompare1ClPatternOptByte (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
  IN       UINT8 Buffer[],
  IN       UINT8 Pattern[],
  IN       UINT8 Side,
  IN       UINT8 Receiver,
  IN       BOOLEAN Side1En
  );

VOID
STATIC
MemTLoadRcvrEnDlyOptByte (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
  IN       UINT8 Receiver
  );

VOID
STATIC
MemTSetRcvrEnDlyOptByte (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
  IN       UINT8 Receiver,
  IN       UINT16 RcvEnDly
  );

VOID
STATIC
MemTLoadInitialRcvEnDlyOptByte (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
  IN       UINT8 Receiver
  );

UINT8
STATIC
MemTFindMinMaxGrossDlyByte (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
  IN       TRN_DLY_TYPE TrnDlyType,
  IN       BOOLEAN IfMax
  );

BOOLEAN
exce856  (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
     OUT   UINT8 *ChipSel
  );

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


/* -----------------------------------------------------------------------------*/
/**
 *
 *   This function enables byte based training if called
 *
 *     @param[in,out]   *TechPtr   - Pointer to the MEM_TECH_BLOCK
 *
 */

VOID
MemTDimmByteTrainInit (
  IN OUT   MEM_TECH_BLOCK *TechPtr
  )
{
  UINT8 Dct;
  UINT8 Channel;
  UINT8 DctCount;
  UINT8 ChannelCount;
  DIE_STRUCT *MCTPtr;
  ALLOCATE_HEAP_PARAMS AllocHeapParams;
  MEM_NB_BLOCK  *NBPtr;

  NBPtr = TechPtr->NBPtr;
  MCTPtr = NBPtr->MCTPtr;

  ASSERT ((NBPtr->CsPerDelay == 1) || (NBPtr->CsPerDelay == 2));

  TechPtr->InitDQSPos4RcvrEn = MemTInitDqsPos4RcvrEnByte;
  TechPtr->SetRcvrEnDly = MemTSetRcvrEnDlyByte;
  TechPtr->LoadRcvrEnDly = MemTLoadRcvrEnDlyByte;
  TechPtr->SaveRcvrEnDly = MemTSaveRcvrEnDlyByte;
  TechPtr->SaveRcvrEnDlyFilter = MemTSaveRcvrEnDlyByteFilterOpt;
  TechPtr->ResetDCTWrPtr = MemTResetDctWrPtrByte;
  TechPtr->Compare1ClPattern = MemTCompare1ClPatternByte;
  TechPtr->SkipChipSelPass1 = MemTSkipChipSelPass1Byte;
  TechPtr->SkipChipSelPass2 = MemTSkipChipSelPass2Byte;
  TechPtr->MaxByteLanes = MemTMaxByteLanesByte;
  TechPtr->DlyTableWidth = MemTDlyTableWidthByte;
  TechPtr->SetDQSDelayCSR = MemTSetDqsDelayCsrByte;
  TechPtr->DQSWindowSave = MemTDqsWindowSaveByte;
  TechPtr->FindMaxDlyForMaxRdLat = MemTFindMaxRcvrEnDlyByte;
  TechPtr->Compare1ClPatternOpt = MemTCompare1ClPatternOptByte;
  TechPtr->LoadRcvrEnDlyOpt = MemTLoadRcvrEnDlyOptByte;
  TechPtr->SetRcvrEnDlyOpt = MemTSetRcvrEnDlyOptByte;
  TechPtr->InitializeVariablesOpt = MemTInitializeVariablesOptByte;
  TechPtr->GetMaxValueOpt = MemTGetMaxValueOptByte;
  TechPtr->SetSweepErrorOpt = MemTSetSweepErrorOptByte;
  TechPtr->CheckRcvrEnDlyLimitOpt = MemTCheckRcvrEnDlyLimitOptByte;
  TechPtr->LoadInitialRcvrEnDlyOpt = MemTLoadInitialRcvEnDlyOptByte;
  TechPtr->GetMinMaxGrossDly = MemTFindMinMaxGrossDlyByte;
  // Dynamically allocate buffers for storing trained timings.
  DctCount = MCTPtr->DctCount;
  ChannelCount = MCTPtr->DctData[0].ChannelCount;
  AllocHeapParams.RequestedBufferSize = ((DctCount * ChannelCount) *
                                         ((MAX_DIMMS * MAX_DELAYS * NUMBER_OF_DELAY_TABLES) +
                                          (MAX_DELAYS * MAX_CS_PER_CHANNEL * NUMBER_OF_FAILURE_MASK_TABLES) +
                                           (MAX_DIMMS * MAX_NUMBER_LANES)
                                          )
                                         );

  if (NBPtr->MemPstateStage == MEMORY_PSTATE_1ST_STAGE) {
    AllocHeapParams.RequestedBufferSize *= 2;
  }

  AllocHeapParams.BufferHandle = GENERATE_MEM_HANDLE (ALLOC_TRN_DATA_HANDLE, MCTPtr->NodeId, 0, 0);
  AllocHeapParams.Persist = HEAP_LOCAL_CACHE;
  if (HeapAllocateBuffer (&AllocHeapParams, &NBPtr->MemPtr->StdHeader) == AGESA_SUCCESS) {
    for (Dct = 0; Dct < DctCount; Dct++) {
      for (Channel = 0; Channel < ChannelCount; Channel++) {
        MCTPtr->DctData[Dct].ChData[Channel].RowCount = MAX_DIMMS;
        MCTPtr->DctData[Dct].ChData[Channel].ColumnCount = MAX_DELAYS;

        MCTPtr->DctData[Dct].ChData[Channel].RcvEnDlys = (UINT16 *) AllocHeapParams.BufferPtr;
        AllocHeapParams.BufferPtr += (MAX_DIMMS * MAX_DELAYS) * 2;
        MCTPtr->DctData[Dct].ChData[Channel].WrDqsDlys = AllocHeapParams.BufferPtr;
        AllocHeapParams.BufferPtr += (MAX_DIMMS * MAX_DELAYS);
        MCTPtr->DctData[Dct].ChData[Channel].RdDqsDlys = AllocHeapParams.BufferPtr;
        AllocHeapParams.BufferPtr += (MAX_DIMMS * MAX_DELAYS);
        MCTPtr->DctData[Dct].ChData[Channel].WrDatDlys = AllocHeapParams.BufferPtr;
        AllocHeapParams.BufferPtr += (MAX_DIMMS * MAX_DELAYS);
        AllocHeapParams.BufferPtr += (MAX_DIMMS * MAX_NUMBER_LANES);
        MCTPtr->DctData[Dct].ChData[Channel].RdDqsMinDlys = AllocHeapParams.BufferPtr;
        AllocHeapParams.BufferPtr += (MAX_DIMMS * MAX_DELAYS);
        MCTPtr->DctData[Dct].ChData[Channel].RdDqsMaxDlys = AllocHeapParams.BufferPtr;
        AllocHeapParams.BufferPtr += (MAX_DIMMS * MAX_DELAYS);
        MCTPtr->DctData[Dct].ChData[Channel].WrDatMinDlys = AllocHeapParams.BufferPtr;
        AllocHeapParams.BufferPtr += (MAX_DIMMS * MAX_DELAYS);
        MCTPtr->DctData[Dct].ChData[Channel].WrDatMaxDlys = AllocHeapParams.BufferPtr;
        AllocHeapParams.BufferPtr += (MAX_DIMMS * MAX_DELAYS);
        MCTPtr->DctData[Dct].ChData[Channel].FailingBitMask = AllocHeapParams.BufferPtr;
        AllocHeapParams.BufferPtr += (MAX_CS_PER_CHANNEL * MAX_DELAYS);
        if (NBPtr->MemPstateStage == MEMORY_PSTATE_1ST_STAGE) {
          MCTPtr->DctData[Dct].ChData[Channel].RcvEnDlysMemPs1 = (UINT16 *) AllocHeapParams.BufferPtr;
          AllocHeapParams.BufferPtr += (MAX_DIMMS * MAX_DELAYS) * 2;
          MCTPtr->DctData[Dct].ChData[Channel].WrDqsDlysMemPs1 = AllocHeapParams.BufferPtr;
          AllocHeapParams.BufferPtr += (MAX_DIMMS * MAX_DELAYS);
          MCTPtr->DctData[Dct].ChData[Channel].RdDqsDlysMemPs1 = AllocHeapParams.BufferPtr;
          AllocHeapParams.BufferPtr += (MAX_DIMMS * MAX_DELAYS);
          MCTPtr->DctData[Dct].ChData[Channel].WrDatDlysMemPs1 = AllocHeapParams.BufferPtr;
          AllocHeapParams.BufferPtr += (MAX_DIMMS * MAX_DELAYS);
          AllocHeapParams.BufferPtr += (MAX_DIMMS * MAX_NUMBER_LANES);
          MCTPtr->DctData[Dct].ChData[Channel].RdDqsMinDlysMemPs1 = AllocHeapParams.BufferPtr;
          AllocHeapParams.BufferPtr += (MAX_DIMMS * MAX_DELAYS);
          MCTPtr->DctData[Dct].ChData[Channel].RdDqsMaxDlysMemPs1 = AllocHeapParams.BufferPtr;
          AllocHeapParams.BufferPtr += (MAX_DIMMS * MAX_DELAYS);
          MCTPtr->DctData[Dct].ChData[Channel].WrDatMinDlysMemPs1 = AllocHeapParams.BufferPtr;
          AllocHeapParams.BufferPtr += (MAX_DIMMS * MAX_DELAYS);
          MCTPtr->DctData[Dct].ChData[Channel].WrDatMaxDlysMemPs1 = AllocHeapParams.BufferPtr;
          AllocHeapParams.BufferPtr += (MAX_DIMMS * MAX_DELAYS);
          MCTPtr->DctData[Dct].ChData[Channel].FailingBitMaskMemPs1 = AllocHeapParams.BufferPtr;
          AllocHeapParams.BufferPtr += (MAX_CS_PER_CHANNEL * MAX_DELAYS);

        }
      }
    }
  } else {
    PutEventLog (AGESA_FATAL, MEM_ERROR_HEAP_ALLOCATE_DYN_STORING_OF_TRAINED_TIMINGS, NBPtr->Node, NBPtr->Dct, NBPtr->Channel, 0, &NBPtr->MemPtr->StdHeader);
    SetMemError (AGESA_FATAL, MCTPtr);
    ASSERT(FALSE);  // Could not dynamically allocate buffers for storing trained timings
  }
}


/* -----------------------------------------------------------------------------*/
/**
 *
 *  This function initializes the DQS Positions in preparation for Receiver Enable Training.
 *  Write Position is no delay, Read Position is 1/2 Memclock delay
 *
 *     @param[in,out]   *TechPtr   - Pointer to the MEM_TECH_BLOCK
 *
 */

VOID
STATIC
MemTInitDqsPos4RcvrEnByte (
  IN OUT   MEM_TECH_BLOCK *TechPtr
  )
{
  UINT8 Dimm;
  UINT8 ByteLane;
  UINT8 WrDqs;

  for (Dimm = 0; Dimm < MAX_DIMMS_PER_CHANNEL; Dimm++) {
    for (ByteLane = 0; ByteLane < MAX_DELAYS; ByteLane++) {
      WrDqs = TechPtr->NBPtr->ChannelPtr->WrDqsDlys[(Dimm * MAX_DELAYS) + ByteLane];
      TechPtr->NBPtr->SetTrainDly (TechPtr->NBPtr, AccessWrDatDly, DIMM_BYTE_ACCESS (Dimm, ByteLane), WrDqs);
      TechPtr->NBPtr->SetTrainDly (TechPtr->NBPtr, AccessRdDqsDly, DIMM_BYTE_ACCESS (Dimm, ByteLane), 0x3F);
    }
  }
}

/* -----------------------------------------------------------------------------*/
/**
 *
 *  This function programs DqsRcvEnDly to additional index for DQS receiver enabled training
 *
 *     @param[in,out]   *TechPtr   - Pointer to the MEM_TECH_BLOCK
 *     @param[in]       Receiver  - Current Chip select value
 *     @param[in]       RcvEnDly  - receiver enable delay to be saved
 */

VOID
STATIC
MemTSetRcvrEnDlyByte (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
  IN       UINT8 Receiver,
  IN       UINT16 RcvEnDly
  )
{
  UINT8 ByteLane;

  ASSERT (Receiver < MAX_CS_PER_CHANNEL);
  for (ByteLane = 0; ByteLane < MAX_BYTELANES; ByteLane++) {
    TechPtr->NBPtr->SetTrainDly (TechPtr->NBPtr, AccessRcvEnDly, DIMM_BYTE_ACCESS (Receiver >> 1, ByteLane), RcvEnDly);
  }
}

/* -----------------------------------------------------------------------------*/
/**
 *
 *  This function loads the DqsRcvEnDly from saved data and program to additional index
 *  for DQS receiver enabled training
 *
 *     @param[in,out]   *TechPtr   - Pointer to the MEM_TECH_BLOCK
 *     @param[in]       Receiver  - Current Chip select value
 *
 */

VOID
STATIC
MemTLoadRcvrEnDlyByte (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
  IN       UINT8 Receiver
  )
{
  UINT8 i;
  UINT8 Dimm;
  UINT16 Saved;
  CH_DEF_STRUCT *ChannelPtr;

  ASSERT (Receiver < MAX_CS_PER_CHANNEL);
  ChannelPtr = TechPtr->NBPtr->ChannelPtr;

  Dimm = Receiver >> 1;
  Saved = TechPtr->DqsRcvEnSaved;
  for (i = 0; i < MAX_BYTELANES; i++) {
    if (Saved & 1) {
      TechPtr->NBPtr->SetTrainDly (TechPtr->NBPtr, AccessRcvEnDly, DIMM_BYTE_ACCESS (Receiver >> 1, i),
      ChannelPtr->RcvEnDlys[Dimm * MAX_DELAYS + i]);
    }
    Saved >>= 1;
  }
}

/* -----------------------------------------------------------------------------*/
/**
 *
 *  This function saves passing DqsRcvEnDly values to the stack
 *
 *     @param[in,out]   *TechPtr   - Pointer to the MEM_TECH_BLOCK
 *     @param[in]       Receiver  - Current Chip select value
 *     @param[in]       RcvEnDly  - receiver enable delay to be saved
 *     @param[in]       CmpResultRank0 - compare result for Rank 0
 *     @param[in]       CmpResultRank1 - compare result for Rank 1
 *
 *     @return  TRUE - All bytelanes pass
 *     @return  FALSE - Some bytelanes fail
 */

BOOLEAN
STATIC
MemTSaveRcvrEnDlyByte (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
  IN       UINT8 Receiver,
  IN       UINT16 RcvEnDly,
  IN       UINT16 CmpResultRank0,
  IN       UINT16 CmpResultRank1
  )
{
  UINT8 i;
  UINT8 Passed;
  UINT8 Saved;
  UINT8 Mask;
  UINT8 Dimm;
  CH_DEF_STRUCT *ChannelPtr;

  ASSERT (Receiver < MAX_CS_PER_CHANNEL);
  ChannelPtr = TechPtr->NBPtr->ChannelPtr;

  Passed = (UINT8) ((CmpResultRank0 & CmpResultRank1) & 0xFF);

  Saved = (UINT8) (TechPtr->DqsRcvEnSaved & Passed); //@attention - false passes filter (subject to be replaced with a better solution)
  Dimm = Receiver >> 1;
  Mask = 1;
  for (i = 0; i < MAX_BYTELANES; i++) {
    if (Passed & Mask) {
      if (!(Saved & Mask)) {
        ChannelPtr->RcvEnDlys[Dimm * MAX_DELAYS + i] = RcvEnDly + 0x20;     // @attention -1 pass only
        IDS_HDT_CONSOLE (MEM_FLOW, "\t\t\tBL %d = %02x", i, RcvEnDly + 0x20);
      }
      Saved |= Mask;
    }
    Mask <<= 1;
  }
  TechPtr->DqsRcvEnSaved = Saved;

  if (Saved == 0xFF) {
    return TRUE;
  } else {
    return FALSE;
  }
}

/* -----------------------------------------------------------------------------*/
/**
 *
 *  This function performs a filtering functionality and saves passing DqsRcvEnDly
 *  values to the stack
 *
 *     @param[in,out]   *TechPtr   - Pointer to the MEM_TECH_BLOCK
 *     @param[in]       Receiver  - Current Chip select value
 *     @param[in]       RcvEnDly  - receiver enable delay to be saved
 *     @param[in]       CmpResultRank0 - compare result for Rank 0
 *     @param[in]       CmpResultRank1 - compare result for Rank 1
 *
 *     @return  TRUE - All bytelanes pass
 *     @return  FALSE - Some bytelanes fail
 */

BOOLEAN
MemTSaveRcvrEnDlyByteFilter (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
  IN       UINT8 Receiver,
  IN       UINT16 RcvEnDly,
  IN       UINT16 CmpResultRank0,
  IN       UINT16 CmpResultRank1
  )
{
  UINT8 i;
  UINT8 Passed;
  UINT8 Saved;
  UINT8 Mask;
  UINT8 Dimm;
  UINT8 MaxFilterDly;
  CH_DEF_STRUCT *ChannelPtr;
  MEM_DCT_CACHE *DctCachePtr;

  ASSERT (Receiver < MAX_CS_PER_CHANNEL);
  ChannelPtr = TechPtr->NBPtr->ChannelPtr;
  DctCachePtr = TechPtr->NBPtr->DctCachePtr;

  MaxFilterDly = TechPtr->MaxFilterDly;
  Passed = (UINT8) ((CmpResultRank0 & CmpResultRank1) & 0xFF);

  Dimm = Receiver >> 1;
  Saved = (UINT8) TechPtr->DqsRcvEnSaved;
  Mask = 1;
  for (i = 0; i < MAX_BYTELANES; i++) {
    if ((Passed & Mask) != 0) {
      DctCachePtr->RcvEnDlyCounts [i] += 1;
      if ((Saved & Mask) == 0) {
        ChannelPtr->RcvEnDlys[Dimm * MAX_DELAYS + i] = RcvEnDly + 0x20;
        Saved |= Mask;
        IDS_HDT_CONSOLE (MEM_FLOW, "\t\t\tBL %d = %02x", i, RcvEnDly + 0x20);
      }
    } else {
      if (DctCachePtr->RcvEnDlyCounts [i] <= MaxFilterDly) {
        DctCachePtr->RcvEnDlyCounts [i] = 0;
        Saved &= ~Mask;
      }
    }
    Mask <<= 1;
  }

  //-----------------------
  TechPtr->DqsRcvEnSaved = (UINT16) Saved;

  Saved = 0;
  for (i = 0; i < MAX_BYTELANES; i++) {
    if (DctCachePtr->RcvEnDlyCounts [i] >= MaxFilterDly) {
      Saved |= (UINT8) 1 << i;
    }
  }

  if (Saved == 0xFF) {
    return TRUE;
  } else {
    return FALSE;
  }
}

/* -----------------------------------------------------------------------------*/
/**
 *
 *       This function compares test pattern with data in buffer and return a pass/fail bitmap
 *       for 8 Bytes
 *
 *     @param[in,out]   *TechPtr   - Pointer to the MEM_TECH_BLOCK
 *     @param[in]       Buffer[]  -  Buffer data from DRAM (Measured data from DRAM) to compare
 *     @param[in]       Pattern[]  - Pattern (Expected data in ROM/CACHE) to compare against
 *
 *     @return  PASS - Bit map of results of comparison
 */

UINT16
STATIC
MemTCompare1ClPatternByte (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
  IN       UINT8 Buffer[],
  IN       UINT8 Pattern[]
  )
{
  UINT16 i;
  UINT16 j;
  UINT16 Pass;
  DIE_STRUCT *MCTPtr;

  MCTPtr = TechPtr->NBPtr->MCTPtr;
  if (MCTPtr->GangedMode && MCTPtr->Dct) {
    j = 8;
  } else {
    j = 0;
  }

  Pass = 0xFFFF;
  IDS_HDT_CONSOLE (MEM_FLOW, " -");
  for (i = 0; i < 8; i++) {
    if (Buffer[j] != Pattern[j]) {
      // if bytelane n fails
      Pass &= ~((UINT16)1 << (j % 8));    // clear bit n
    }
    IDS_HDT_CONSOLE (MEM_FLOW, "  %c", (Buffer[j] == Pattern[j]) ? 'P' : '.');
    j++;
  }

  IDS_HDT_CONSOLE_DEBUG_CODE (
    IDS_HDT_CONSOLE (MEM_FLOW, "\n\t\t\t        -");
    for (i = 0, j -= 8; i < 8; i++, j++) {
      IDS_HDT_CONSOLE (MEM_FLOW, " %02x", Buffer[j]);
    }
    IDS_HDT_CONSOLE (MEM_FLOW, "\n\t\t\t        -");
    for (i = 0, j -= 8; i < 8; i++, j++) {
      IDS_HDT_CONSOLE (MEM_FLOW, " %02x", Pattern[j]);
    }
    IDS_HDT_CONSOLE (MEM_FLOW, "\n\n");
  );

  return Pass;
}

/* -----------------------------------------------------------------------------*/
/**
 *
 *       The function resets the DCT input buffer write pointer.
 *
 *     @param[in,out]   *TechPtr   - Pointer to the MEM_TECH_BLOCK
 *     @param[in]       Receiver - Chip select
 *
 */

VOID
STATIC
MemTResetDctWrPtrByte (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
  IN       UINT8 Receiver
  )
{
  UINT8 i;
  UINT16 RcvEnDly;

  ASSERT (Receiver < MAX_CS_PER_CHANNEL);
  for (i = 0; i < MAX_BYTELANES; i++) {
    RcvEnDly = (UINT16) TechPtr->NBPtr->GetTrainDly (TechPtr->NBPtr, AccessRcvEnDly, DIMM_BYTE_ACCESS (Receiver / 2, i));
    TechPtr->NBPtr->SetTrainDly (TechPtr->NBPtr, AccessRcvEnDly, DIMM_BYTE_ACCESS (Receiver / 2, i), RcvEnDly);
  }
}

/* -----------------------------------------------------------------------------*/
/**
 *
 *       This function skips odd chip select if training at 800MT or above.
 *
 *     @param[in,out]   *TechPtr   - Pointer to the MEM_TECH_BLOCK
 *     @param[in]       *ChipSelPtr - Pointer to variable contains Chip select index
 *
 */

VOID
STATIC
MemTSkipChipSelPass1Byte (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
  IN OUT   UINT8 *ChipSelPtr
  )
{
  MEM_NB_BLOCK *NBPtr;

  NBPtr = TechPtr->NBPtr;
  // if the even chip select failed training, need to set CsTrainFail for odd chip select if present.
  if (NBPtr->DCTPtr->Timings.CsPresent & ((UINT16)1 << ((*ChipSelPtr) + 1))) {
    if (NBPtr->DCTPtr->Timings.CsTrainFail & ((UINT16)1 << *ChipSelPtr)) {
      NBPtr->DCTPtr->Timings.CsTrainFail |= (UINT16)1 << ((*ChipSelPtr) + 1);
      if (!NBPtr->MemPtr->ErrorHandling (NBPtr->MCTPtr, NBPtr->Dct, NBPtr->DCTPtr->Timings.CsTrainFail, &NBPtr->MemPtr->StdHeader)) {
        ASSERT (FALSE);
      }
    }
  }
  (*ChipSelPtr)++;
}

/* -----------------------------------------------------------------------------*/
/**
 *
 *  MemTSkipChipSelPass2Byte:
 *
 *       This function skips odd chip select if training at 800MT or above.
 *
 *     @param[in,out]   *TechPtr   - Pointer to the MEM_TECH_BLOCK
 *     @param[in,out]   *ChipSelPtr - Pointer to variable contains Chip select index
 *
 */

VOID
STATIC
MemTSkipChipSelPass2Byte (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
  IN OUT   UINT8 *ChipSelPtr
  )
{
  if (*ChipSelPtr & 1) {
    *ChipSelPtr = MAX_CS_PER_CHANNEL;    // skip all successions
  }
}

/* -----------------------------------------------------------------------------*/
/**
 *
 *       This function determines the maximum number of byte lanes
 *
 *     @return  Max number of Bytelanes
 */

UINT8
STATIC
MemTMaxByteLanesByte ( VOID )
{
  return MAX_BYTELANES;
}

/* -----------------------------------------------------------------------------*/
/**
 *
 *       This function determines the width of the delay tables (eg. RcvEnDlys, WrDqsDlys,...)
 *
 *     @return  Delay table width in bytes
 */

UINT8
STATIC
MemTDlyTableWidthByte ( VOID )
{
  return MAX_DELAYS;
}

/* -----------------------------------------------------------------------------*/
/**
 *
 *       This function writes the Delay value to a certain byte lane
 *
 *     @param[in,out]   *TechPtr   - Pointer to the MEM_TECH_BLOCK
 *     @param[in]       ByteLane  -  Bytelane number being targeted
 *     @param[in]       Dly - Delay value
 *
 */

VOID
STATIC
MemTSetDqsDelayCsrByte (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
  IN       UINT8 ByteLane,
  IN       UINT8 Dly
  )
{
  UINT8 Reg;
  UINT8 Dimm;

  ASSERT (ByteLane <= MAX_BYTELANES);

  if (!(TechPtr->DqsRdWrPosSaved & ((UINT8)1 << ByteLane))) {
    Dimm = TechPtr->ChipSel / TechPtr->NBPtr->CsPerDelay;

    if (TechPtr->Direction == DQS_WRITE_DIR) {
      Dly = Dly + ((UINT8) TechPtr->NBPtr->ChannelPtr->WrDqsDlys[(Dimm * MAX_DELAYS) + ByteLane]);
      Reg = AccessWrDatDly;
    } else {
      Reg = AccessRdDqsDly;
    }

    TechPtr->NBPtr->SetTrainDly (TechPtr->NBPtr, Reg, DIMM_BYTE_ACCESS (Dimm, ByteLane), Dly);
  }
}

/* -----------------------------------------------------------------------------*/
/**
 *
 *       This function programs the trained DQS delay for the specified byte lane
 *       and stores its DQS window for reference.
 *
 *     @param[in,out]   *TechPtr   - Pointer to the MEM_TECH_BLOCK
 *     @param[in]       ByteLane  -  Bytelane number being targeted
 *     @param[in]       DlyMin - Minimum delay value
 *     @param[in]       DlyMax- Maximum delay value
 *
 */

VOID
STATIC
MemTDqsWindowSaveByte (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
  IN       UINT8 ByteLane,
  IN       UINT8 DlyMin,
  IN       UINT8 DlyMax
  )
{
  UINT8 DqsDelay;
  UINT8 Dimm;
  CH_DEF_STRUCT *ChanPtr;

  ASSERT (ByteLane <= MAX_BYTELANES);
  ChanPtr = TechPtr->NBPtr->ChannelPtr;

  DqsDelay = ((DlyMin + DlyMax + 1) / 2) & 0x3F;
  MemTSetDqsDelayCsrByte (TechPtr, ByteLane, DqsDelay);
  TechPtr->DqsRdWrPosSaved |= (UINT8)1 << ByteLane;
  TechPtr->DqsRdWrPosSaved |= 0xFF00;

  Dimm = (TechPtr->ChipSel / TechPtr->NBPtr->CsPerDelay) * MAX_DELAYS + ByteLane;
  if (TechPtr->Direction == DQS_READ_DIR) {
    ChanPtr->RdDqsDlys[Dimm] = DqsDelay;
  } else {
    ChanPtr->WrDatDlys[Dimm] = DqsDelay + ChanPtr->WrDqsDlys[Dimm];
  }

  if (TechPtr->Direction == DQS_READ_DIR) {
    ChanPtr->RdDqsMinDlys[ByteLane] = DlyMin;
    ChanPtr->RdDqsMaxDlys[ByteLane] = DlyMax;
  } else {
    ChanPtr->WrDatMinDlys[ByteLane] = DlyMin;
    ChanPtr->WrDatMaxDlys[ByteLane] = DlyMax;
  }

}


/* -----------------------------------------------------------------------------*/
/**
 *
 *      This function finds the DIMM that has the largest receiver enable delay.
 *
 *     @param[in,out]   *TechPtr   - Pointer to the MEM_TECH_BLOCK
 *     @param[out]      *ChipSel   - Pointer to the Chip select that has the largest receiver enable delay.
 *
 *     @return   TRUE - A chip select can be found.
 *     @return   FALSE - A chip select cannot be found.
 */

BOOLEAN
STATIC
MemTFindMaxRcvrEnDlyByte (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
     OUT   UINT8 *ChipSel
  )
{
  UINT8  ChipSelect;
  UINT8  ByteLane;
  UINT16 RcvEnDly;
  UINT16 MaxDly;
  UINT8  MaxDlyCs;
  BOOLEAN RetVal;

  MEM_NB_BLOCK  *NBPtr;
  CH_DEF_STRUCT *ChannelPtr;

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

  RetVal = FALSE;
  MaxDly = 0;
  MaxDlyCs = 0;
  for (ChipSelect = 0; ChipSelect < NBPtr->CsPerChannel; ChipSelect = ChipSelect + NBPtr->CsPerDelay) {
    if ((NBPtr->DCTPtr->Timings.CsEnabled & ((UINT16) ((NBPtr->CsPerDelay == 2)? 3 : 1) << ChipSelect)) != 0) {
      if ((NBPtr->DCTPtr->Timings.CsTrainFail & ((UINT16) ((NBPtr->CsPerDelay == 2)? 3 : 1) << ChipSelect)) == 0) {
        // Only choose the dimm that does not fail training
        for (ByteLane = 0; ByteLane < ((NBPtr->MCTPtr->Status[SbEccDimms] && NBPtr->IsSupported[EccByteTraining]) ? 9 : 8); ByteLane++) {
          RcvEnDly = ChannelPtr->RcvEnDlys[ChipSelect / NBPtr->CsPerDelay * MAX_DELAYS + ByteLane];
          if (RcvEnDly > MaxDly) {
            MaxDly = RcvEnDly;
            MaxDlyCs = ChipSelect;
            RetVal = TRUE;
          }
        }
      }
    }
  }

  if (NBPtr->MCTPtr->Status[Sb128bitmode] != 0) {
    //The RcvrEnDlys of DCT1 DIMMs should also be considered while ganging.
    NBPtr->SwitchDCT (NBPtr, 1);
    ChannelPtr = NBPtr->ChannelPtr;
    for (ChipSelect = 0; ChipSelect < NBPtr->CsPerChannel; ChipSelect = ChipSelect + NBPtr->CsPerDelay) {
      for (ByteLane = 0; ByteLane < ((NBPtr->MCTPtr->Status[SbEccDimms] && NBPtr->IsSupported[EccByteTraining]) ? 9 : 8); ByteLane++) {
        RcvEnDly = ChannelPtr->RcvEnDlys[ChipSelect / NBPtr->CsPerDelay * MAX_DELAYS + ByteLane];
        if (RcvEnDly > MaxDly) {
          MaxDly = RcvEnDly;
          MaxDlyCs = ChipSelect;
        }
      }
    }
    NBPtr->SwitchDCT (NBPtr, 0);
  }

  TechPtr->MaxDlyForMaxRdLat = MaxDly;
  *ChipSel = MaxDlyCs;
  return RetVal;
}

/* -----------------------------------------------------------------------------*/
/**
 *
 *      This function finds the DIMM that has the largest receiver enable delay + Read DQS Delay.
 *
 *     @param[in,out]   *TechPtr   - Pointer to the MEM_TECH_BLOCK
 *     @param[out]      *ChipSel   - Pointer to the Chip select that has the largest receiver enable delay
 *                                   + Read DQS Delay.
 *
 *     @return   TRUE - A chip select can be found.
 *     @return   FALSE - A chip select cannot be found.
 */

BOOLEAN
MemTFindMaxRcvrEnDlyRdDqsDlyByte (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
     OUT   UINT8 *ChipSel
  )
{
  UINT8  ChipSelect;
  UINT8  ByteLane;
  UINT16 RcvEnDly;
  UINT16 RdDqsDly;
  UINT16 TotalDly;
  UINT16 MaxDly;
  UINT8  MaxDlyCs;
  BOOLEAN RetVal;

  MEM_NB_BLOCK  *NBPtr;
  CH_DEF_STRUCT *ChannelPtr;

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

  RetVal = FALSE;
  MaxDly = 0;
  MaxDlyCs = 0;
  for (ChipSelect = 0; ChipSelect < NBPtr->CsPerChannel; ChipSelect = ChipSelect + NBPtr->CsPerDelay) {
    if ((NBPtr->DCTPtr->Timings.CsTrainFail & ((UINT16) ((NBPtr->CsPerDelay == 2)? 3 : 1) << ChipSelect)) == 0) {
      // Only choose the dimm that does not fail training
      for (ByteLane = 0; ByteLane < MAX_BYTELANES; ByteLane++) {
        RcvEnDly = ChannelPtr->RcvEnDlys[ChipSelect / NBPtr->CsPerDelay * MAX_DELAYS + ByteLane];
        // Before Dqs Position Training, this value is 0. So the maximum value for
        // RdDqsDly needs to be added later when calculating the MaxRdLatency value
        // after RcvEnDly training but before DQS Position Training.
        RdDqsDly = ChannelPtr->RdDqsDlys[ChipSelect / NBPtr->CsPerDelay * MAX_DELAYS + ByteLane];
        TotalDly = RcvEnDly + (RdDqsDly >> 1);
        if (TotalDly > MaxDly) {
          MaxDly = TotalDly;
          MaxDlyCs = ChipSelect;
          RetVal = TRUE;
        }
      }
    }
  }

  TechPtr->MaxDlyForMaxRdLat = MaxDly;
  *ChipSel = MaxDlyCs;
  return RetVal;
}

/* -----------------------------------------------------------------------------*/
/**
 *
 *      This function finds the DIMM that has the largest receiver enable delay + Read DQS Delay for UNB
 *
 *     @param[in,out]   *TechPtr   - Pointer to the MEM_TECH_BLOCK
 *     @param[out]      *ChipSel   - Pointer to the Chip select that has the largest receiver enable delay
 *                                   + Read DQS Delay.
 *
 *     @return   TRUE - A chip select can be found.
 *     @return   FALSE - A chip select cannot be found.
 */

BOOLEAN
MemTFindMaxRcvrEnDlyRdDqsDlyByteUnb (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
     OUT   UINT8 *ChipSel
  )
{
  UINT8  ChipSelect;
  UINT8  ByteLane;
  UINT16 RcvEnDly;
  UINT16 RdDqsDly;
  UINT16 TotalDly;
  UINT16 MaxDly;
  UINT8  MaxDlyCs;
  BOOLEAN RetVal;
  UINT16 *RcvEnDlyPtr;
  UINT8 *RdDqsDlyPtr;

  MEM_NB_BLOCK  *NBPtr;
  CH_DEF_STRUCT *ChannelPtr;

  NBPtr = TechPtr->NBPtr;
  ChannelPtr = NBPtr->ChannelPtr;
  RcvEnDlyPtr = ChannelPtr->RcvEnDlys;
  RdDqsDlyPtr = ChannelPtr->RdDqsDlys;
  if (NBPtr->MemPstate == MEMORY_PSTATE1) {
    RcvEnDlyPtr = ChannelPtr->RcvEnDlysMemPs1;
    RdDqsDlyPtr = ChannelPtr->RdDqsDlysMemPs1;
  }

  RetVal = FALSE;
  MaxDly = 0;
  MaxDlyCs = 0;
  for (ChipSelect = 0; ChipSelect < NBPtr->CsPerChannel; ChipSelect = ChipSelect + NBPtr->CsPerDelay) {
    if ((NBPtr->DCTPtr->Timings.CsTrainFail & ((UINT16) ((NBPtr->CsPerDelay == 2)? 3 : 1) << ChipSelect)) == 0) {
      // Only choose the dimm that does not fail training
      for (ByteLane = 0; ByteLane < MAX_BYTELANES; ByteLane++) {
        RcvEnDly = RcvEnDlyPtr[ChipSelect / NBPtr->CsPerDelay * MAX_DELAYS + ByteLane];
        // Before Dqs Position Training, this value is 0. So the maximum value for
        // RdDqsDly needs to be added later when calculating the MaxRdLatency value
        // after RcvEnDly training but before DQS Position Training.
        RdDqsDly = RdDqsDlyPtr[ChipSelect / NBPtr->CsPerDelay * MAX_DELAYS + ByteLane];
        TotalDly = RcvEnDly + RdDqsDly;
        if (TotalDly > MaxDly) {
          MaxDly = TotalDly;
          MaxDlyCs = ChipSelect;
          RetVal = TRUE;
        }
      }
    }
  }

  TechPtr->MaxDlyForMaxRdLat = MaxDly;
  *ChipSel = MaxDlyCs;
  return RetVal;
}

/* -----------------------------------------------------------------------------*/
/**
 *
 *      This function finds the minimum or maximum gross dly among all the bytes.
 *
 *     @param[in,out]   *TechPtr   - Pointer to the MEM_TECH_BLOCK
 *     @param[in]       TrnDlyType - Target Dly type
 *     @param[in]       IfMax - If this is for maximum value or minimum
 *
 *     @return   minimum gross dly
 */
UINT8
STATIC
MemTFindMinMaxGrossDlyByte (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
  IN       TRN_DLY_TYPE TrnDlyType,
  IN       BOOLEAN IfMax
  )
{
  UINT8 ChipSelect;
  UINT8 ByteLane;
  UINT16 CsEnabled;
  UINT8 MinMaxGrossDly;
  UINT8 TrnDly;
  MEM_NB_BLOCK *NBPtr;

  NBPtr = TechPtr->NBPtr;
  CsEnabled = NBPtr->DCTPtr->Timings.CsEnabled;
  MinMaxGrossDly = IfMax ? 0 : 0xFF;

  for (ChipSelect = 0; ChipSelect < NBPtr->CsPerChannel; ChipSelect = ChipSelect + NBPtr->CsPerDelay) {
    if ((CsEnabled & ((UINT16) ((NBPtr->CsPerDelay == 2)? 3 : 1) << ChipSelect)) != 0) {
      for (ByteLane = 0; ByteLane < ((NBPtr->MCTPtr->Status[SbEccDimms] && NBPtr->IsSupported[EccByteTraining]) ? 9 : 8); ByteLane++) {
        TrnDly = (UINT8) (GetTrainDlyFromHeapNb (NBPtr, TrnDlyType, DIMM_BYTE_ACCESS (ChipSelect / NBPtr->CsPerDelay, ByteLane)) >> 5);
        if ((IfMax && (TrnDly > MinMaxGrossDly)) || (!IfMax && (TrnDly < MinMaxGrossDly))) {
          MinMaxGrossDly = TrnDly;
        }
      }
    }
  }

  return MinMaxGrossDly;
}

/* -----------------------------------------------------------------------------*/
/**
 *
 *       This function compares test pattern with data in buffer and return a pass/fail bitmap
 *       for 8 Bytes for optimized receiver enable training
 *
 *     @param[in,out]   *TechPtr   - Pointer to the MEM_TECH_BLOCK
 *     @param[in]       Buffer[]  -  Buffer data from DRAM (Measured data from DRAM) to compare
 *     @param[in]       Pattern[]  - Pattern (Expected data in ROM/CACHE) to compare against
 *     @param[in]       Side - current side being targeted
 *     @param[in]       Receiver - Current receiver value
 *     @param[in]       Side1En - Indicates if the second side of the DIMM is being used
 *     @return  PASS - Bit map of results of comparison
 */

UINT16
STATIC
MemTCompare1ClPatternOptByte (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
  IN       UINT8 Buffer[],
  IN       UINT8 Pattern[],
  IN       UINT8 Side,
  IN       UINT8 Receiver,
  IN       BOOLEAN  Side1En
  )
{
  UINT16 i;
  UINT16 j;
  UINT16 Pass;
  DIE_STRUCT *MCTPtr;
  CH_DEF_STRUCT *ChannelPtr;

  ASSERT (Receiver < MAX_CS_PER_CHANNEL);
  ChannelPtr = TechPtr->NBPtr->ChannelPtr;
  MCTPtr = TechPtr->NBPtr->MCTPtr;

  if (MCTPtr->GangedMode && MCTPtr->Dct) {
    j = 8;
  } else {
    j = 0;
  }

  Pass = 0xFFFF;
  IDS_HDT_CONSOLE (MEM_FLOW, "\t\t\tDelay[BL] -");
  for (i = 0; i < 8; i++) {
    IDS_HDT_CONSOLE (MEM_FLOW, " %02x", TechPtr->RcvrEnDlyOpt[i] & 0xFF);
    if (Buffer[j] != Pattern[j]) {
      // if bytelane n fails
      Pass &= ~((UINT16)1 << (j % 8));    // clear bit n
      TechPtr->DqsRcvEnFirstPassValOpt[i] = 0;
      TechPtr->GetFirstPassValOpt[i] = FALSE;
      TechPtr->IncBy1ForNextCountOpt[i] = FALSE;
      TechPtr->DqsRcvEnSavedOpt[i] = FALSE;
      if (TechPtr->FilterStatusOpt[i] != DONE_FILTER) {
        if (Side == ((Side1En ? 4 : 2) - 1)) {
          TechPtr->RcvrEnDlyOpt[i] += FILTER_FIRST_STAGE_COUNT;
        }
      }
    } else {
      if (TechPtr->FilterSidePassCountOpt[i] == ((Side1En ? 4 : 2) - 1)) {
        //Only apply filter if all sides have passed
        if (TechPtr->FilterStatusOpt[i] != DONE_FILTER) {
          if (TechPtr->GetFirstPassValOpt[i] == FALSE) {
            // This is the first Pass, mark the start of filter check
            TechPtr->DqsRcvEnFirstPassValOpt[i] = TechPtr->RcvrEnDlyOpt[i];
            TechPtr->GetFirstPassValOpt[i] = TRUE;
            TechPtr->IncBy1ForNextCountOpt[i] = FALSE;
            TechPtr->RcvrEnDlyOpt[i]++;
          } else {
            if ((TechPtr->RcvrEnDlyOpt[i] - TechPtr->DqsRcvEnFirstPassValOpt[i]) < FILTER_WINDOW_SIZE) {
              if (TechPtr->IncBy1ForNextCountOpt[i] == FALSE) {
                TechPtr->RcvrEnDlyOpt[i] += FILTER_SECOND_STAGE_COUNT;
                TechPtr->IncBy1ForNextCountOpt[i] = TRUE;
              } else {
                TechPtr->RcvrEnDlyOpt[i]++;
                TechPtr->IncBy1ForNextCountOpt[i] = FALSE;
              }
            } else {
              // End sweep and add offset to first pass
              TechPtr->MaxRcvrEnDlyBlOpt[i] = TechPtr->DqsRcvEnFirstPassValOpt[i];
              TechPtr->RcvrEnDlyOpt[i] = TechPtr->DqsRcvEnFirstPassValOpt[i] + FILTER_OFFSET_VALUE;
              TechPtr->FilterStatusOpt[i] = DONE_FILTER;
              TechPtr->FilterCountOpt++;
            }
          }
        } else {
          TechPtr->FilterSidePassCountOpt[i]++;
        }
      } else {
        if (TechPtr->GetFirstPassValOpt[i] == FALSE) {
          if (Side == ((Side1En ? 4 : 2) - 1)) {
            TechPtr->RcvrEnDlyOpt[i] += FILTER_FIRST_STAGE_COUNT;
          }
        }
        TechPtr->FilterSidePassCountOpt[i]++;
      }
      TechPtr->DqsRcvEnSavedOpt[i] = TRUE;
      ChannelPtr->RcvEnDlys[(Receiver >> 1) * MAX_DELAYS + i] = TechPtr->RcvrEnDlyOpt[i];
    }
    if (Side == ((Side1En ? 4 : 2) - 1)) {
      TechPtr->FilterSidePassCountOpt[i] = 0;
    }
    if (TechPtr->RcvrEnDlyOpt[i] >= TechPtr->RcvrEnDlyLimitOpt[i]) {
      TechPtr->FilterCountOpt++;
    }

    j++;
  }

  IDS_HDT_CONSOLE_DEBUG_CODE (
    IDS_HDT_CONSOLE (MEM_FLOW, "\n\t\t\tPass/Fail -");
    for (i = 0, j -= 8; i < 8; i++, j++) {
      IDS_HDT_CONSOLE (MEM_FLOW, "  %c", (Buffer[j] == Pattern[j]) ? 'P' : '.');
    }
    IDS_HDT_CONSOLE (MEM_FLOW, "\n\t\t\t Measured -");
    for (i = 0, j -= 8; i < 8; i++, j++) {
      IDS_HDT_CONSOLE (MEM_FLOW, " %02x", Buffer[j]);
    }
    IDS_HDT_CONSOLE (MEM_FLOW, "\n\t\t\t Expected -");
    for (i = 0, j -= 8; i < 8; i++, j++) {
      IDS_HDT_CONSOLE (MEM_FLOW, " %02x", Pattern[j]);
    }
    IDS_HDT_CONSOLE (MEM_FLOW, "\n\n");
  );

  return Pass;
}
/*-----------------------------------------------------------------------------
 *
 *  This function initializes variables for optimized training.
 *
 *     @param[in,out]   *TechPtr   - Pointer to the MEM_TECH_BLOCK
 *
 * ----------------------------------------------------------------------------
 */
VOID
MemTInitializeVariablesOptByte (
  IN OUT   MEM_TECH_BLOCK *TechPtr
  )
{
  UINT8 ByteLane;
  for (ByteLane = 0; ByteLane < MAX_BYTELANES_PER_CHANNEL; ByteLane++) {
    TechPtr->RcvrEnDlyLimitOpt[ByteLane] = FILTER_MAX_REC_EN_DLY_VALUE;      // @attention - limit depends on proc type
    TechPtr->DqsRcvEnSavedOpt[ByteLane] = FALSE;
    TechPtr->RcvrEnDlyOpt[ByteLane] = FILTER_NEW_RECEIVER_START_VALUE;
    TechPtr->GetFirstPassValOpt[ByteLane] = FALSE;
    TechPtr->DqsRcvEnFirstPassValOpt[ByteLane] = 0;
    TechPtr->RevertPassValOpt[ByteLane] = FALSE;
    TechPtr->MaxRcvrEnDlyBlOpt[ByteLane] = 0;
    TechPtr->FilterStatusOpt[ByteLane] = START_FILTER;
    TechPtr->FilterCountOpt = 0;
    TechPtr->FilterSidePassCountOpt[ByteLane] = 0;
    TechPtr->IncBy1ForNextCountOpt[ByteLane] = FALSE;
  }
}

/* -----------------------------------------------------------------------------*/
/**
 *
 *  This function loads the DqsRcvEnDly from saved data and program to additional index
 *  for optimized DQS receiver enabled training
 *
 *     @param[in,out]   *TechPtr   - Pointer to the MEM_TECH_BLOCK
 *     @param[in]       Receiver  - Current Chip select value
 *
 */

VOID
STATIC
MemTLoadRcvrEnDlyOptByte (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
  IN       UINT8 Receiver
  )
{
  UINT8 i;
  UINT8 Dimm;
  CH_DEF_STRUCT *ChannelPtr;

  ASSERT (Receiver < MAX_CS_PER_CHANNEL);
  ChannelPtr = TechPtr->NBPtr->ChannelPtr;

  Dimm = Receiver >> 1;
  for (i = 0; i < 8; i++) {
    if (TechPtr->DqsRcvEnSavedOpt[i]) {
      TechPtr->NBPtr->SetTrainDly (TechPtr->NBPtr, AccessRcvEnDly, DIMM_BYTE_ACCESS (Receiver >> 1, i),
      ChannelPtr->RcvEnDlys[Dimm * MAX_DELAYS + i]);
    }
  }
}

/* -----------------------------------------------------------------------------*/
/**
 *
 *  This function programs DqsRcvEnDly to additional index for DQS receiver enabled training
 *
 *     @param[in,out]   *TechPtr   - Pointer to the MEM_TECH_BLOCK
 *     @param[in]       Receiver  - Current Chip select value
 *     @param[in]       RcvEnDly  - receiver enable delay to be saved
 */

VOID
STATIC
MemTSetRcvrEnDlyOptByte (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
  IN       UINT8 Receiver,
  IN       UINT16 RcvEnDly
  )
{
  UINT8 ByteLane;

  ASSERT (Receiver < MAX_CS_PER_CHANNEL);

  for (ByteLane = 0; ByteLane < 8; ByteLane++) {
    if (TechPtr->FilterStatusOpt[ByteLane] != DONE_FILTER) {
      TechPtr->NBPtr->SetTrainDly (TechPtr->NBPtr, AccessRcvEnDly, DIMM_BYTE_ACCESS (Receiver >> 1, ByteLane), TechPtr->RcvrEnDlyOpt[ByteLane]);
    }
  }
}
/*-----------------------------------------------------------------------------
 *
 *  This sets any Errors generated from Dly sweep
 *
 *     @param[in,out]   *TechPtr   - Pointer to the MEM_TECH_BLOCK
 *     @param[in]   DCT   - current DCT
 *     @param[in]   Receiver   - current receiver
 *
 *     @return     FALSE - Fatal error occurs.
 *     @return     TRUE  - No fatal error occurs.
 * ----------------------------------------------------------------------------
 */
BOOLEAN
MemTSetSweepErrorOptByte (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
  IN       UINT8 Receiver,
  IN       UINT8 Dct,
  IN       BOOLEAN ErrorCheck
  )
{
  UINT8 ByteLane;
  MEM_DATA_STRUCT *MemPtr;
  DIE_STRUCT *MCTPtr;
  DCT_STRUCT *DCTPtr;
  MEM_NB_BLOCK  *NBPtr;

  NBPtr = TechPtr->NBPtr;
  MemPtr = NBPtr->MemPtr;
  MCTPtr = NBPtr->MCTPtr;
  DCTPtr = NBPtr->DCTPtr;
  for (ByteLane = 0; ByteLane < MAX_BYTELANES_PER_CHANNEL; ByteLane++) {
    if (TechPtr->RcvrEnDlyOpt[ByteLane] == TechPtr->RcvrEnDlyLimitOpt[ByteLane]) {
      // no passing window
      if (ErrorCheck) {
        return FALSE;
      }
      PutEventLog (AGESA_ERROR, MEM_ERROR_RCVR_EN_NO_PASSING_WINDOW, NBPtr->Node, NBPtr->Dct, NBPtr->Channel, ByteLane, &NBPtr->MemPtr->StdHeader);
      SetMemError (AGESA_ERROR, MCTPtr);
    }
    if (TechPtr->RcvrEnDlyOpt[ByteLane] > (TechPtr->RcvrEnDlyLimitOpt[ByteLane] - 1)) {
      // passing window too narrow, too far delayed
      if (ErrorCheck) {
        return FALSE;
      }
      PutEventLog (AGESA_ERROR, MEM_ERROR_RCVR_EN_VALUE_TOO_LARGE, NBPtr->Node, NBPtr->Dct, NBPtr->Channel, ByteLane, &NBPtr->MemPtr->StdHeader);
      SetMemError (AGESA_ERROR, MCTPtr);
      DCTPtr->Timings.CsTrainFail |= (UINT16) (3 << Receiver) & DCTPtr->Timings.CsPresent;
      MCTPtr->ChannelTrainFail |= (UINT32)1 << Dct;
      if (!NBPtr->MemPtr->ErrorHandling (MCTPtr, NBPtr->Dct, DCTPtr->Timings.CsTrainFail, &MemPtr->StdHeader)) {
        ASSERT (FALSE);
        return FALSE;
      }
    }
  }
  return TRUE;
}

/*-----------------------------------------------------------------------------
 *
 *  This function determines the maximum receiver delay value
 *
 *     @param[in,out]   *TechPtr   - Pointer to the MEM_TECH_BLOCK
 *
 *     @retval  MaxRcvrValue - Maximum receiver delay value for all bytelanes
 * ----------------------------------------------------------------------------
 */

UINT16
MemTGetMaxValueOptByte (
  IN OUT   MEM_TECH_BLOCK *TechPtr
  )
{
  UINT8 ByteLane;
  UINT16 MaxRcvrValue;
  MaxRcvrValue = 0;
  for (ByteLane = 0; ByteLane < MAX_BYTELANES_PER_CHANNEL; ByteLane++) {
    if (TechPtr->MaxRcvrEnDlyBlOpt[ByteLane] > MaxRcvrValue) {
      MaxRcvrValue = TechPtr->MaxRcvrEnDlyBlOpt[ByteLane];
    }
  }
  MaxRcvrValue += FILTER_OFFSET_VALUE;
  return MaxRcvrValue;
}
/*-----------------------------------------------------------------------------
 *
 *  This function determines if the sweep loop should complete.
 *
 *     @param[in,out]   *TechPtr   - Pointer to the MEM_TECH_BLOCK
 *
 *     @retval  TRUE - All bytelanes pass
 *              FALSE - Some bytelanes fail
 * ----------------------------------------------------------------------------
 */

BOOLEAN
MemTCheckRcvrEnDlyLimitOptByte (
  IN OUT   MEM_TECH_BLOCK *TechPtr
  )
{
  if (TechPtr->FilterCountOpt >= (UINT16)MAX_CS_PER_CHANNEL) {
    return TRUE;
  } else {
    return FALSE;
  }
}

/* -----------------------------------------------------------------------------*/
/**
 *
 *  This function load the result of write levelization training into RcvrEnDlyOpt,
 *  using it as the initial value for Receiver DQS training.
 *
 *     @param[in,out]   *TechPtr   - Pointer to the MEM_TECH_BLOCK
 *     @param[in]       Receiver  - Current Chip select value
 */
VOID
STATIC
MemTLoadInitialRcvEnDlyOptByte (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
  IN       UINT8 Receiver
  )
{
  UINT8 ByteLane;
  MEM_NB_BLOCK *NBPtr;

  NBPtr = TechPtr->NBPtr;
  for (ByteLane = 0; ByteLane < MAX_BYTELANES_PER_CHANNEL; ByteLane++) {
    TechPtr->RcvrEnDlyOpt[ByteLane] = NBPtr->ChannelPtr->WrDqsDlys[((Receiver >> 1) * TechPtr->DlyTableWidth ()) + ByteLane];
  }
}

/* -----------------------------------------------------------------------------*/
/**
 *
 *     This function finds the DIMM that has the largest receiver enable delay that are trained by PMU
 *
 *     @param[in,out]   *TechPtr   - Pointer to the MEM_TECH_BLOCK
 *     @param[out]      *ChipSel   - Pointer to the Chip select that has the largest receiver enable delay
 *                                   + Read DQS Delay.
 *
 *     @return   TRUE - A chip select can be found.
 *     @return   FALSE - A chip select cannot be found.
 */

BOOLEAN
exce856  (
  IN OUT   MEM_TECH_BLOCK *TechPtr,
     OUT   UINT8 *ChipSel
  )
{
  UINT8   ChipSelect;
  BOOLEAN RetVal;
  UINT16  MaxDly;
  UINT8   MaxDlyCs;

  MEM_NB_BLOCK  *NBPtr;

  NBPtr = TechPtr->NBPtr;

  RetVal = FALSE;
  MaxDly = 0;
  MaxDlyCs = 0;
  for (ChipSelect = 0; ChipSelect < NBPtr->CsPerChannel; ChipSelect = ChipSelect + NBPtr->CsPerDelay) {
    /// @todo Fix this when BKDG has updated algorithm
    if ((NBPtr->DCTPtr->Timings.CsPresent & ((UINT16) ((NBPtr->CsPerDelay == 2)? 3 : 1) << ChipSelect)) != 0) {
      MaxDly = 0;
      MaxDlyCs = ChipSelect;
      RetVal = TRUE;
    }
  }

  TechPtr->MaxDlyForMaxRdLat = MaxDly;
  *ChipSel = MaxDlyCs;
  return RetVal;
}
