blob: 6a9e873e94055ab43405c9846367e7116684ce0a [file] [log] [blame]
/* $NoKeywords:$ */
/**
* @file
*
* mfRdWr2DTraining.c
*
* Common Read/Write 2D Training Feature Function
*
* @xrefitem bom "File Content Label" "Release Content"
* @e project: AGESA
* @e sub-project: (Mem/Feat/RdWr2DTraining)
* @e \$Revision: 84150 $ @e \$Date: 2012-12-12 15:46:25 -0600 (Wed, 12 Dec 2012) $
*
**/
/*****************************************************************************
*
* Copyright (c) 2008 - 2013, 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 "AdvancedApi.h"
#include "GeneralServices.h"
#include "heapManager.h"
#include "mm.h"
#include "mn.h"
#include "mu.h"
#include "mt.h"
#include "mport.h"
#include "merrhdl.h"
#include "OptionMemory.h"
#include "mfRdWr2DTraining.h"
#include "Filecode.h"
CODE_GROUP (G1_PEICC)
RDATA_GROUP (G1_PEICC)
#define FILECODE PROC_MEM_FEAT_RDWR2DTRAINING_MFRDWR2DTRAINING_FILECODE
/*----------------------------------------------------------------------------
* DEFINITIONS AND MACROS
*
*----------------------------------------------------------------------------
*/
/*----------------------------------------------------------------------------
* TYPEDEFS AND STRUCTURES
*
*----------------------------------------------------------------------------
*/
/*----------------------------------------------------------------------------
* PROTOTYPES OF LOCAL FUNCTIONS
*
*----------------------------------------------------------------------------
*/
BOOLEAN
STATIC
MemFRdWr2DScaleVref (
IN OUT MEM_NB_BLOCK *NBPtr,
IN OUT VOID *Vref
);
/*----------------------------------------------------------------------------
* EXPORTED FUNCTIONS
*
*----------------------------------------------------------------------------
*/
extern MEM_PSC_FLOW_BLOCK* memPlatSpecFlowArray[];
/* -----------------------------------------------------------------------------*/
/**
*
* This function initializes the 2D Read/Write Training Feature Hooks.
*
* @param[in,out] *NBPtr - Pointer to the MEM_NB_BLOCK
*
* @return BOOLEAN
* TRUE - Function was implemented
*/
BOOLEAN
MemFRdWr2DTrainingInit (
IN OUT MEM_NB_BLOCK *NBPtr
)
{
ASSERT (NBPtr != NULL);
NBPtr->FamilySpecificHook[RdWr2DTraining] = MemFAmdRdWr2DTraining;
NBPtr->FamilySpecificHook[CheckRdWr2DTrainingPerConfig] = MemFCheckRdWr2DTrainingPerConfig;
NBPtr->FamilySpecificHook[RdWr2DSelectIntExtVref] = MemFRdWr2DProgramIntExtVrefSelect;
NBPtr->FamilySpecificHook[RdWr2DProgramVref] = MemFRdWr2DProgramVref;
NBPtr->FamilySpecificHook[RdWr2DScaleVref] = MemFRdWr2DScaleVref;
NBPtr->FamilySpecificHook[RdWr2DProgramDelays] = MemFRdWr2DProgramDelays;
NBPtr->FamilySpecificHook[RdWr2DDataCollection] = MemFRdWr2DEyeRimSearch;
NBPtr->FamilySpecificHook[RdWr2DInitVictim] = MemFRdWr2DInitVictim;
NBPtr->FamilySpecificHook[RdWr2DInitVictimChipSel] = MemFRdWr2DInitVictimChipSel;
NBPtr->FamilySpecificHook[RdWr2DStartVictim] = MemFRdWr2DStartVictim;
NBPtr->FamilySpecificHook[RdWr2DFinalizeVictim] = MemFRdWr2DFinalizeVictim;
NBPtr->FamilySpecificHook[RdWr2DCompareInPhase] = MemFRdWr2DCompareInPhase;
NBPtr->FamilySpecificHook[RdWr2DCompare180Phase] = MemFRdWr2DCompare180Phase;
NBPtr->FamilySpecificHook[RdWr2DProgramDataPattern] = MemFRdWr2DProgramDataPattern;
return TRUE;
}
/* -----------------------------------------------------------------------------*/
/**
*
* This function executes 2D training for Read DQS or Write DQ
*
* @param[in,out] *NBPtr - Pointer to the MEM_NB_BLOCK
* @param[in,out] *OptParam - Unused
*
* @return BOOLEAN
* TRUE - No Errors occurred
* FALSE - Errors occurred
*/
BOOLEAN
MemFAmdRdWr2DTraining (
IN OUT MEM_NB_BLOCK *NBPtr,
IN OUT VOID* OptParam
)
{
MEM_TECH_BLOCK *TechPtr;
MEM_DATA_STRUCT *MemPtr;
MEM_RD_WR_2D_ENTRY Data;
RD_WR_2D *VrefPtr;
PSO_TABLE *PsoTable;
ALLOCATE_HEAP_PARAMS AllocHeapParams;
UINT8 Dct;
UINT8 ChipSel;
UINT8 Lane;
UINT8 Vref;
UINT8 MaxLanes;
UINT8 TmpLanes;
UINT8 TotalDlyRange;
BOOLEAN Status;
//
// Initialize Pointers
//
ASSERT (NBPtr != NULL);
TechPtr = NBPtr->TechPtr;
MemPtr = NBPtr->MemPtr;
PsoTable = MemPtr->ParameterListPtr->PlatformMemoryConfiguration;
//
// Set environment settings before training
//
AGESA_TESTPOINT (TpProcMem2dRdDqsTraining, &(MemPtr->StdHeader));
MemTBeginTraining (TechPtr);
//
// Allocate heap for the 2D RdWR/Vref Data structure
//
MaxLanes = 0;
for (Dct = 0; Dct < NBPtr->DctCount; Dct++) {
NBPtr->SwitchDCT (NBPtr, Dct);
TmpLanes = MemFRdWr2DGetMaxLanes (NBPtr);
MaxLanes = MAX (MaxLanes, TmpLanes);
}
AllocHeapParams.RequestedBufferSize = MaxLanes * NBPtr->TotalMaxVrefRange * sizeof (RD_WR_2D);
AllocHeapParams.BufferHandle = AMD_MEM_2D_RD_WR_HANDLE;
AllocHeapParams.Persist = HEAP_LOCAL_CACHE;
if (HeapAllocateBuffer (&AllocHeapParams, &MemPtr->StdHeader) == AGESA_SUCCESS) {
VrefPtr = (RD_WR_2D *) AllocHeapParams.BufferPtr;
} else {
SetMemError (AGESA_FATAL, NBPtr->MCTPtr);
PutEventLog (AGESA_FATAL, MEM_ERROR_HEAP_ALLOCATE_FOR_2D, 0, 0, 0, 0, &MemPtr->StdHeader);
return TRUE;
}
for (Lane = 0; Lane < MaxLanes; Lane++) {
Data.Lane[Lane].Vref = &VrefPtr[Lane * NBPtr->TotalMaxVrefRange];
}
//
// Setup hardware training engine
//
TechPtr->TrainingType = TRN_DQS_POSITION;
NBPtr->FamilySpecificHook[SetupHwTrainingEngine] (NBPtr, &TechPtr->TrainingType);
Data.Vnom = NBPtr->TotalMaxVrefRange / 2; // Set Nominal Vref
TotalDlyRange = (TechPtr->Direction == DQS_READ_DIR) ? NBPtr->TotalRdDQSDlyRange : NBPtr->TotalWrDatDlyRange;
Data.MaxRdWrSweep = TotalDlyRange / 2; // Set Max Sweep Size
ASSERT (TotalDlyRange <= MAX_RD_WR_DLY_ENTRIES);
//
// Execute 2d Rd DQS training for all Dcts/Chipselects
//
for (Dct = 0; Dct < NBPtr->DctCount; Dct++) {
IDS_HDT_CONSOLE (MEM_STATUS, "\tDct %d\n", Dct);
NBPtr->SwitchDCT (NBPtr, Dct);
NBPtr->Vref = 0xFF;
Status = FALSE;
if (NBPtr->FamilySpecificHook[CheckRdWr2DTrainingPerConfig] (NBPtr, NULL)) {
for (ChipSel = 0; ChipSel < NBPtr->CsPerChannel; ChipSel = ChipSel + NBPtr->CsPerDelay ) {
if ( (NBPtr->MCTPtr->Status[SbLrdimms]) ? ((NBPtr->ChannelPtr->LrDimmPresent & ((UINT8) 1 << (ChipSel >> 1))) != 0) :
((NBPtr->DCTPtr->Timings.CsEnabled & ((UINT16) 1 << ChipSel)) != 0) ) {
//
//Initialize storage
//
for (Lane = 0; Lane < MemFRdWr2DGetMaxLanes (NBPtr); Lane++) {
for (Vref = 0; Vref < NBPtr->TotalMaxVrefRange; Vref++) {
Data.Lane[Lane].Vref[Vref].PosRdWrDly = 0;
Data.Lane[Lane].Vref[Vref].NegRdWrDly = 0;
}
}
TechPtr->ChipSel = ChipSel;
IDS_HDT_CONSOLE (MEM_FLOW,"\tChip Select: %02x \n", TechPtr->ChipSel);
//
// 1. Sample the data eyes for each channel:
//
TechPtr->RdWr2DData = &Data;
if (NBPtr->FamilySpecificHook[RdWr2DDataCollection] (NBPtr, NULL)) {
//
// 2. Process the array of results with a diamond convolution mask, summing the number passing sample points.
//
// Determine Diamond Mask Height
if (MemFRdWr2DHeight (NBPtr, &Data)) {
//
// Apply Mask
//
if (MemFRdWr2DApplyMask (NBPtr, &Data)) {
//
// Convolution
//
if (MemFRdWr2DProcessConvolution (NBPtr, &Data)) {
//
// 3. Program the final DQS delay values.
//
if (MemFRdWr2DProgramMaxDelays (NBPtr, &Data)) {
//
// Find the Smallest Positive or Negative Margin for current CS
//
if (MemFRdWr2DFindCsVrefMargin (NBPtr, &Data)) {
Status = TRUE;
}
}
}
}
}
}
if (Status == FALSE) {
SetMemError (AGESA_ERROR, NBPtr->MCTPtr);
PutEventLog (AGESA_ERROR, (TechPtr->Direction == DQS_READ_DIR) ? MEM_ERROR_2D_DQS_ERROR : MEM_ERROR_2D_WRDAT_ERROR, NBPtr->Node, NBPtr->Dct, NBPtr->Channel, 0, &NBPtr->MemPtr->StdHeader);
}
}
}
//
// Find the Max and Min Vref values for each DCT
//
if (Status == TRUE) {
if (MemFRdWr2DFinalVrefMargin (NBPtr, &Data)) {
//
// Program the Max Vref value
//
IDS_HDT_CONSOLE (MEM_FLOW, "\n\t\tProgramming Final Vref for channel\n");
NBPtr->FamilySpecificHook[RdWr2DProgramVref] (NBPtr, &NBPtr->ChannelPtr->MaxVref);
IDS_HDT_CONSOLE (MEM_FLOW, "\t\t\tActual Vref programmed = %02x\n",
( NBPtr->GetBitField (NBPtr, BFVrefDAC) >> TSEFO_END (NBPtr->NBRegTable[BFVrefDAC])) );
Status = TRUE;
} else {
SetMemError (AGESA_ERROR, NBPtr->MCTPtr);
PutEventLog (AGESA_ERROR, (TechPtr->Direction == DQS_READ_DIR) ? MEM_ERROR_2D_DQS_VREF_MARGIN_ERROR: MEM_ERROR_2D_WRDAT_VREF_MARGIN_ERROR, NBPtr->Node, NBPtr->Dct, NBPtr->Channel, 0, &NBPtr->MemPtr->StdHeader);
}
}
}
}
//
// Restore environment settings after training
//
if (HeapDeallocateBuffer (AMD_MEM_2D_RD_WR_HANDLE, &MemPtr->StdHeader) != AGESA_SUCCESS) {
SetMemError (AGESA_FATAL, NBPtr->MCTPtr);
PutEventLog (AGESA_FATAL, MEM_ERROR_HEAP_DEALLOCATE_FOR_2D, 0, 0, 0, 0, &MemPtr->StdHeader);
}
IDS_HDT_CONSOLE (MEM_STATUS, "\tEnd\n");
MemTEndTraining (TechPtr);
IDS_HDT_CONSOLE (MEM_FLOW, "\n\nEnd %s 2D training\n\n",(TechPtr->Direction == DQS_READ_DIR) ? "Read DQS":"Write DQ");
return (BOOLEAN) (NBPtr->MCTPtr->ErrCode < AGESA_FATAL);
}
/*----------------------------------------------------------------------------
* LOCAL FUNCTIONS
*
*----------------------------------------------------------------------------
*/
/* -----------------------------------------------------------------------------*/
/**
*
* This function determines whether the current configuration is a valid
* config for applying 2D Training
* @todo: Update to work for 2D WR and 2D RD training
*
* @param[in,out] *NBPtr - Pointer to the MEM_NB_BLOCK
* @param[in,out] *OptParam - Unused
*
* @return BOOLEAN
* TRUE - Configuration valid
* FALSE - Configuration invalid
*
*/
BOOLEAN
MemFCheckRdWr2DTrainingPerConfig (
IN OUT MEM_NB_BLOCK *NBPtr,
IN OUT VOID *OptParam
)
{
UINT8 i;
if (NBPtr->RefPtr->ForceTrainMode == FORCE_TRAIN_AUTO) {
i = 0;
while (memPlatSpecFlowArray[i] != NULL) {
if ((memPlatSpecFlowArray[i])->S2D (NBPtr, (memPlatSpecFlowArray[i])->EntryOfTables)) {
return TRUE;
}
i++;
}
}
return FALSE;
}
/* -----------------------------------------------------------------------------*/
/**
*
* This function determines the maximum number of lanes for puposes of 2D
* Read or Write training.
*
* @param[in,out] *NBPtr - Pointer to the MEM_NB_BLOCK
*
* @return UINT8 - Max Number of Lanes
*
*/
UINT8
MemFRdWr2DGetMaxLanes (
IN OUT MEM_NB_BLOCK *NBPtr
)
{
MEM_TECH_BLOCK *TechPtr;
UINT8 MaxLanes;
TechPtr = NBPtr->TechPtr;
if ((TechPtr->Direction == DQS_READ_DIR) && ((NBPtr->ChannelPtr->DimmNibbleAccess & (1 << (TechPtr->ChipSel >> 1))) != 0)) {
// Per Nibble
MaxLanes = (NBPtr->MCTPtr->Status[SbEccDimms] && (NBPtr->IsSupported[EccByteTraining] == TRUE)) ? 18 : 16;
} else {
// Per Byte
MaxLanes = (NBPtr->MCTPtr->Status[SbEccDimms] && (NBPtr->IsSupported[EccByteTraining] == TRUE)) ? 9 : 8;
}
return MaxLanes;
}
/* -----------------------------------------------------------------------------*/
/**
*
* This function programs Vref to internal or external control for 2D Read
* or Write Training
*
* @param[in,out] *NBPtr - Pointer to the MEM_NB_BLOCK
* @param[in,out] *OptParam - Unused
*
* @return BOOLEAN
* TRUE - External Vref was selected
* FALSE - Internal Vref was selected
*
*/
BOOLEAN
MemFRdWr2DProgramIntExtVrefSelect (
IN OUT MEM_NB_BLOCK *NBPtr,
IN OUT VOID *OptParam
)
{
if (NBPtr->TechPtr->Direction == DQS_READ_DIR) {
NBPtr->SetBitField (NBPtr, BFVrefSel, (NBPtr->RefPtr->ExternalVrefCtl ? 0x0000 : 0x0001));
}
return NBPtr->RefPtr->ExternalVrefCtl;
}
/* -----------------------------------------------------------------------------*/
/**
*
* This function scales Vref from the range used in Data Collection to
* the range that is programmed into the register.
*
* @param[in,out] *NBPtr - Pointer to the MEM_NB_BLOCK
* @param[in,out] *Vref - Pointer to UINT8 Vref Value to scale.
*
* @return BOOLEAN
* TRUE Function was implemented
*
*/
BOOLEAN
STATIC
MemFRdWr2DScaleVref (
IN OUT MEM_NB_BLOCK *NBPtr,
IN OUT VOID *Vref
)
{
*(UINT8*)Vref = *(UINT8*)Vref * 2;
return TRUE;
}
/* -----------------------------------------------------------------------------*/
/**
*
* This function programs Vref for 2D Read/Write Training
*
* @param[in,out] *NBPtr - Pointer to the MEM_NB_BLOCK
* @param[in] *VrefPtr - Pointer to Vref value
*
* @return BOOLEAN
* TRUE - Success
* FAIL (External Callout only)
*
*/
BOOLEAN
MemFRdWr2DProgramVref (
IN OUT MEM_NB_BLOCK *NBPtr,
IN VOID *VrefPtr
)
{
AGESA_STATUS Status;
MEM_DATA_STRUCT *MemPtr;
ID_INFO CallOutIdInfo;
VOLTAGE_ADJUST Va;
UINT8 Vref;
ASSERT (NBPtr != NULL);
ASSERT (VrefPtr != NULL);
MemPtr = NBPtr->MemPtr;
Vref = *(UINT8*)VrefPtr;
CallOutIdInfo.IdField.SocketId = NBPtr->MCTPtr->SocketId;
CallOutIdInfo.IdField.ModuleId = NBPtr->MCTPtr->DieId;
LibAmdMemCopy ((VOID *)&Va, (VOID *)MemPtr, (UINTN)sizeof (Va.StdHeader), &MemPtr->StdHeader);
Va.MemData = MemPtr;
Status = AGESA_SUCCESS;
if (NBPtr->TechPtr->Direction == DQS_READ_DIR) {
if (NBPtr->RefPtr->ExternalVrefCtl == FALSE) {
//
// Internal vref control
//
// This is 1/2 VrefDAC value Sign bit is shifted into place.
//
ASSERT (Vref < 32);
if (Vref < 15) {
Vref = (31 - Vref) << 1;
} else {
Vref = (Vref - 15) << 1;
}
NBPtr->SetBitField (NBPtr, BFVrefDAC, Vref << 2);
} else {
// External vref control
AGESA_TESTPOINT (TpProcMemBefore2dTrainExtVrefChange, &(NBPtr->MemPtr->StdHeader));
NBPtr->MemPtr->ParameterListPtr->ExternalVrefValue = Vref;
IDS_HDT_CONSOLE (MEM_FLOW, "\n2D Read Training External CPU Vref Callout \n");
Va.VoltageType = VTYPE_CPU_VREF;
Va.AdjustValue = Vref = (Vref - 15) << 1;
Status = AgesaExternalVoltageAdjust ((UINTN)CallOutIdInfo.IdInformation, &Va);
AGESA_TESTPOINT (TpProcMemAfter2dTrainExtVrefChange, &(NBPtr->MemPtr->StdHeader));
}
} else {
//
// DIMM Vref Control
//
Va.VoltageType = VTYPE_DIMM_VREF;
//
// Offset by 15 and multiply by 2.
//
Va.AdjustValue = Vref = (Vref - 15) << 1;
Status = AgesaExternalVoltageAdjust ((UINTN)CallOutIdInfo.IdInformation, &Va);
if (Status != AGESA_SUCCESS) {
IDS_HDT_CONSOLE (MEM_FLOW, "* Dimm Vref Callout Failed *");
}
IDS_HDT_CONSOLE (MEM_FLOW, "\n");
}
return (Status == AGESA_SUCCESS) ? TRUE : FALSE;
}
/* -----------------------------------------------------------------------------*/
/**
*
* This function programs Read DQS or Write DQ Delay values for Read/Write
* Training
*
* @param[in,out] *NBPtr - Pointer to the MEM_NB_BLOCK
* @param[in] *Delay - Pointer to UINT8 containing Delay value
*
* @return BOOLEAN
* TRUE
*
*/
BOOLEAN
MemFRdWr2DProgramDelays (
IN OUT MEM_NB_BLOCK *NBPtr,
IN VOID *Delay
)
{
UINT32 RdDqsTime;
UINT8 RdWrDly;
ASSERT (NBPtr != 0);
ASSERT (Delay != 0);
RdWrDly = *(UINT8*) Delay;
if (NBPtr->TechPtr->Direction == DQS_READ_DIR) {
// This function should only be used for read training
ASSERT (NBPtr->TechPtr->Direction == DQS_READ_DIR);
// Program BL registers for both nibble (x4) and bytes (x8, x16)
RdDqsTime = 0;
RdDqsTime = (RdWrDly & 0x1F) << 8;
RdDqsTime = RdDqsTime | (RdWrDly & 0x1F);
if ((NBPtr->TechPtr->ChipSel / NBPtr->CsPerDelay) == 0) {
NBPtr->SetBitField (NBPtr, BFDataByteRxDqsDLLDimm0Broadcast, RdDqsTime);
} else if ((NBPtr->TechPtr->ChipSel / NBPtr->CsPerDelay) == 1) {
NBPtr->SetBitField (NBPtr, BFDataByteRxDqsDLLDimm1Broadcast, RdDqsTime);
} else if ((NBPtr->TechPtr->ChipSel / NBPtr->CsPerDelay) == 2) {
NBPtr->SetBitField (NBPtr, BFDataByteRxDqsDLLDimm2Broadcast, RdDqsTime);
} else if ((NBPtr->TechPtr->ChipSel / NBPtr->CsPerDelay) == 3) {
NBPtr->SetBitField (NBPtr, BFDataByteRxDqsDLLDimm3Broadcast, RdDqsTime);
}
} else {
MemTSetDQSDelayAllCSR (NBPtr->TechPtr, RdWrDly);
}
return TRUE;
}
/* -----------------------------------------------------------------------------*/
/**
*
* This function stores data for 2D Read DQS and Write DQ Training
*
* @param[in,out] *NBPtr - Pointer to the MEM_NB_BLOCK
* @param[in] *Data - Pointer to Result data structure
* @param[in] *InPhaseResult[] - Array of inphase results
* @param[in] *PhaseResult180[] - Array of Phase 180 results
*
* @return BOOLEAN
* TRUE - No Errors occurred
* FALSE - Errors ccurred
*/
VOID
MemFRdWr2DStoreResult (
IN OUT MEM_NB_BLOCK *NBPtr,
IN MEM_RD_WR_2D_ENTRY *Data,
IN UINT32 InPhaseResult[],
IN UINT32 PhaseResult180[]
)
{
UINT8 Lane;
UINT8 Vref;
UINT8 RdWrDly;
UINT32 Result;
UINT32 Result180;
UINT8 Index;
Vref = NBPtr->Vref;
RdWrDly = Data->RdWrDly;
for (Lane = 0; Lane < MemFRdWr2DGetMaxLanes (NBPtr); Lane++) {
for (RdWrDly = 0; RdWrDly < Data->MaxRdWrSweep; RdWrDly++) {
if ((NBPtr->ChannelPtr->DimmNibbleAccess & (1 << (NBPtr->TechPtr->ChipSel >> 1))) == 0) {
// x8, so combine "Nibble X" and "Nibble X+1" results
Index = Lane * 2;
Result = (InPhaseResult[RdWrDly] >> Index) & 0x03;
Result180 = (PhaseResult180[RdWrDly] >> Index) & 0x03;
} else {
// x4, so use "Nibble" results
Result = (InPhaseResult[RdWrDly] >> Lane) & 0x01;
Result180 = (PhaseResult180[RdWrDly] >> Lane) & 0x01;
}
Data->Lane[Lane].Vref[Vref].PosRdWrDly |= (Result == 0) ? (1 << (Data->MaxRdWrSweep - 1 - RdWrDly)) : 0;
Data->Lane[Lane].Vref[Vref].NegRdWrDly |= (Result180 == 0) ? (1 << (Data->MaxRdWrSweep - 1 - RdWrDly)) : 0;
}
}
}
/* -----------------------------------------------------------------------------*/
/**
*
* This function determines the height of data for 2D Read and Write training
*
* @param[in,out] *NBPtr - Pointer to the MEM_NB_BLOCK
* @param[in] *Data - Pointer to Result data structure
*
* @return BOOLEAN
* TRUE - No Errors occurred
*/
BOOLEAN
MemFRdWr2DHeight (
IN OUT MEM_NB_BLOCK *NBPtr,
IN MEM_RD_WR_2D_ENTRY *Data
)
{
UINT8 Lane;
for (Lane = 0; Lane < MemFRdWr2DGetMaxLanes (NBPtr); Lane++) {
Data->Lane[Lane].HalfDiamondHeight = 0x0F;
}
IDS_HDT_CONSOLE_DEBUG_CODE (
IDS_HDT_CONSOLE (MEM_FLOW, "\n");
IDS_HDT_CONSOLE (MEM_FLOW, "\t\t\t Lane: ");
for (Lane = 0; Lane < MemFRdWr2DGetMaxLanes (NBPtr); Lane++) {
IDS_HDT_CONSOLE (MEM_FLOW, "%02x ", Lane);
}
IDS_HDT_CONSOLE (MEM_FLOW, "\n");
IDS_HDT_CONSOLE (MEM_FLOW, "\t\t\tHeight: ");
for (Lane = 0; Lane < MemFRdWr2DGetMaxLanes (NBPtr); Lane++) {
IDS_HDT_CONSOLE (MEM_FLOW, "%02x ", (2*(Data->Lane[Lane].HalfDiamondHeight) + 1));
}
IDS_HDT_CONSOLE (MEM_FLOW, "\n");
);
return TRUE;
}
/* -----------------------------------------------------------------------------*/
/**
*
* This function gets the width for 2D RdDQS and WrDat training
*
* @param[in,out] *NBPtr - Pointer to the MEM_NB_BLOCK
* @param[in] *Data - Pointer to Result data structure
*
* @return UINT8 Width
*/
UINT8
MemFGetRdWr2DWidth (
IN OUT MEM_NB_BLOCK *NBPtr,
IN MEM_RD_WR_2D_ENTRY *Data
)
{
if (NBPtr->TechPtr->Direction == DQS_READ_DIR) {
return NBPtr->DiamondWidthRd;
} else {
return NBPtr->DiamondWidthWr;
}
}
/* -----------------------------------------------------------------------------*/
/**
*
* This function gets the step height for the diamond mask for 2D RdDQS or
* WrDat Training
*
* @param[in,out] *NBPtr - Pointer to the MEM_NB_BLOCK
* @param[in] *Data - Pointer to Result data structure
* @param[in] Vref - current Vref value
* @param[in] Lane - current Lane
*
* @return BOOLEAN
* TRUE - Step found and value should be updated
* FALSE - Step not found and value should not be updated
*
*/
BOOLEAN
MemFCheckRdWr2DDiamondMaskStep (
IN OUT MEM_NB_BLOCK *NBPtr,
IN MEM_RD_WR_2D_ENTRY *Data,
IN UINT8 Vref,
IN UINT8 Lane
)
{
UINT8 M;
UINT8 VrefVal;
UINT8 width;
UINT8 i;
BOOLEAN status;
// m = -1 * height/width
// (y-b)/m = x
status = FALSE;
if (Vref > (Data->Vnom - 1)) {
VrefVal = (Vref + 1) - Data->Vnom;
} else {
VrefVal = Vref;
}
width = (MemFGetRdWr2DWidth (NBPtr, Data) - 1) / 2;
M = Data->Lane[Lane].HalfDiamondHeight / width;
i = 1;
while (i <= Data->Lane[Lane].HalfDiamondHeight) {
i = i + M;
if (VrefVal == i) {
status = TRUE;
}
}
return status;
}
/* -----------------------------------------------------------------------------*/
/**
*
* This function applies a mask for 2D RdDQS or WrDat training
*
* @param[in,out] *NBPtr - Pointer to the MEM_NB_BLOCK
* @param[in] *Data - Pointer to Result data structure
*
* @return BOOLEAN
* TRUE - No Errors occurred
* FALSE - Errors ccurred
*
*/
BOOLEAN
MemFRdWr2DApplyMask (
IN OUT MEM_NB_BLOCK *NBPtr,
IN MEM_RD_WR_2D_ENTRY *Data
)
{
MEM_TECH_BLOCK *TechPtr;
UINT8 RdWrDly;
UINT8 Lane;
UINT8 Height;
UINT8 Width;
UINT32 PosNegData;
UINT8 Vref;
UINT8 count;
UINT8 Dly;
UINT8 endWidth;
UINT8 startWidth;
UINT8 origEndWidth;
UINT8 origStartWidth;
UINT8 maxOverLapWidth;
UINT8 startOverLapWidth;
UINT8 TotalDlyRange;
BOOLEAN maxHeightExceeded;
BOOLEAN negVrefComplete;
BOOLEAN PosRdWrToNegRdWr;
BOOLEAN NegRdWrToPosRdWr;
TechPtr = NBPtr->TechPtr;
TotalDlyRange = (TechPtr->Direction == DQS_READ_DIR) ? NBPtr->TotalRdDQSDlyRange : NBPtr->TotalWrDatDlyRange;
//
// Initialize Convolution
//
for (Lane = 0; Lane < MemFRdWr2DGetMaxLanes (NBPtr); Lane++) {
for (RdWrDly = 0; RdWrDly < TotalDlyRange; RdWrDly++) {
Data->Lane[Lane].Convolution[RdWrDly] = 0;
NBPtr->FamilySpecificHook[Adjust2DDelayStepSize] (NBPtr, &RdWrDly);
}
}
endWidth = 0;
startWidth = 0;
origEndWidth = 0;
origStartWidth = 0;
startOverLapWidth = 0;
maxOverLapWidth = 0;
maxHeightExceeded = FALSE;
IDS_HDT_CONSOLE (MEM_FLOW, "\n\t\tDetermining Width");
//
// Get the Width of Diamond
//
Width = MemFGetRdWr2DWidth (NBPtr, Data);
IDS_HDT_CONSOLE (MEM_FLOW, "\n\t\t\tWidth: %02x\n", Width);
ASSERT (Width != 0);
IDS_HDT_CONSOLE (MEM_FLOW, "\n\t\tExecuting convolution function\n");
//
// Perform the convolution by sweeping the mask function centered at nominal Vref. Results in a one
// dimensional array with FOM values at each delay for each lane. Choose the delay setting at the peak
// FOM value.
//
for (Lane = 0; Lane < MemFRdWr2DGetMaxLanes (NBPtr); Lane++) {
Height = Data->Lane[Lane].HalfDiamondHeight;
ASSERT (Height < Data->Vnom);
//
// RdWrDly is divided around "Data->MaxRdWrSweep" into positive and negative directions
// Positive direction -> RdWrDly = 0 to (Data->MaxRdWrSweep - 1)
// Negative direction -> RdWrDly = Data->MaxRdWrSweep to (TotalDlyRange - 1)
//
for (RdWrDly = 0; RdWrDly < TotalDlyRange; RdWrDly++) {
// Vref loop is divided around "Data->Vnom - 1" into positive and negative directions
// Negative direction -> Vref = 0 ("Data->Vnom - 1") to Height("Data->Vnom - 1" - Height)
// Positive direction -> Vref = "Data->Vnom" to Height("Data->Vnom" + Height)
//
negVrefComplete = FALSE;
PosRdWrToNegRdWr = FALSE;
NegRdWrToPosRdWr = FALSE;
for (Vref = 0; Vref < (NBPtr->TotalMaxVrefRange - 1); Vref++) {
// Initial negative direction where Vref = 0 ("Data->Vnom - 1"), so we need to set
// initial startWidth and endWidth for +/- RdDqs
//
// Create common delay based on +/- RdDqs
if (RdWrDly > (Data->MaxRdWrSweep - 1)) {
Dly = RdWrDly - Data->MaxRdWrSweep;
} else {
Dly = RdWrDly;
}
if (Vref == 0 ) {
// Initialize -Vref
maxHeightExceeded = FALSE; // reset for start of -Vref
// Case 1: if +RdDqs - Check for lower bound (Width/2 > RdDqs > 0)
// : if -RdDqs - Check for lower bound (Width/2 + Data->MaxRdDqsSweep > RdDqs > Data->MaxRdDqsSweep)
if (Dly < Width / 2) {
endWidth = Dly + Width / 2 + 1;
startWidth = 0;
} else if ((Dly + Width / 2) > (Data->MaxRdWrSweep - 1)) {
// Case 2: if +RdWr - Check for upper bound ((Data->MaxRdWrSweep - 1) < RdWr < ((Data->MaxRdWrSweep - 1) - Width/2))
// : if -RdWr - Check for lower bound ((DatNBPtr->TotalRdWrDlyRange - 1) < RdWr < ((NBPtr->TotalRdWrDlyRange - 1) - Width/2))
endWidth = Data->MaxRdWrSweep;
startWidth = Dly - Width / 2;
} else {
// Set the initial "startWidth" and "endWidth" for +/- RdDqs
endWidth = Dly + Width / 2 + 1;
startWidth = Dly - Width / 2;
}
origEndWidth = endWidth;
origStartWidth = startWidth;
} else if (Vref == Data->Vnom) {
// Initialize +Vref
endWidth = origEndWidth;
startWidth = origStartWidth;
maxHeightExceeded = FALSE; // reset for start of +Vref
negVrefComplete = TRUE;
} else if ((Vref > (Data->Vnom + Height)) && negVrefComplete == TRUE) {
break; //switch to next RdDqs Dly if height exceeded for +vref and -vref complete
} else {
if (startWidth >= endWidth) {
if (RdWrDly == (TotalDlyRange - 1)) {
// Special condition for end of -RdDqs range
startWidth = Data->MaxRdWrSweep - 1;
endWidth = Data->MaxRdWrSweep;
} else {
// Width = 0, but Height not reached,
startWidth = Dly;
endWidth = Dly + 1;
}
} else {
// Check for Case 1 and Case 2 above
if ((RdWrDly + Width / 2) > (TotalDlyRange - 1)) {
endWidth = origEndWidth;
}
}
maxHeightExceeded = FALSE;
}
IDS_HDT_CONSOLE_DEBUG_CODE (
if (Lane == 0) {
if (RdWrDly == (Data->MaxRdWrSweep - (Width / 2)) ) {
Data->DiamondLeft[Vref] = startWidth;
Data->DiamondRight[Vref] = endWidth - 1;
}
}
);
//
// Determine the correct Delay (+/-) and Vref (+/-)direction
//
if (maxHeightExceeded == FALSE) {
if (RdWrDly < Data->MaxRdWrSweep) {
if (Vref > (Data->Vnom - 1)) {
PosNegData = Data->Lane[Lane].Vref[Vref].PosRdWrDly; // +RdWr Dly, +Vref
} else {
PosNegData = Data->Lane[Lane].Vref[(Data->Vnom - 1) - Vref].PosRdWrDly; // +RdDqs Dly, -Vref
}
} else {
if (Vref > (Data->Vnom - 1)) {
PosNegData = Data->Lane[Lane].Vref[Vref].NegRdWrDly; // -RdWr Dly, +Vref
} else {
PosNegData = Data->Lane[Lane].Vref[(Data->Vnom - 1) - Vref].NegRdWrDly; // -RdWr Dly, -Vref
}
}
//
// Case 1: Non-overlap condition:
// Count the number of passes from "startWidth" to "endWidth"
//
for (count = startWidth; count < endWidth; count++) {
Data->Lane[Lane].Convolution[RdWrDly] = (UINT8) ((PosNegData >> count) & 0x1) + Data->Lane[Lane].Convolution[RdWrDly];
}
// Case 2: Overlay between +RdWr and -RdWr starting from +RdWr
// Count the number of passes from "startWidth" to "endWidth"
//
if ((RdWrDly <= (Data->MaxRdWrSweep - 1) && (RdWrDly > ((Data->MaxRdWrSweep - 1) - Width / 2)))) {
startOverLapWidth = 0;
if (Vref == 0 || Vref == Data->Vnom) {
maxOverLapWidth = (RdWrDly + Width / 2) - (Data->MaxRdWrSweep - 1); // Initial overlap max width size
} else if (maxOverLapWidth == 0) {
maxOverLapWidth = startOverLapWidth; // Stop counting after overlap region complete
}
// Ensure that +/- vref is set correctly
if (Vref > (Data->Vnom - 1)) {
PosNegData = Data->Lane[Lane].Vref[Vref].NegRdWrDly;
} else {
PosNegData = Data->Lane[Lane].Vref[(Data->Vnom - 1) - Vref].NegRdWrDly;
}
// Need to count the number of passes when range extends from Pos RdDqs to Neg RdDqs
for (count = startOverLapWidth; count < maxOverLapWidth; count++) {
Data->Lane[Lane].Convolution[RdWrDly] = (UINT8) ((PosNegData >> count) & 0x1) + Data->Lane[Lane].Convolution[RdWrDly];
}
if (maxOverLapWidth > 0) {
if (MemFCheckRdWr2DDiamondMaskStep (NBPtr, Data, Vref, Lane) || (Vref == 1) || (Vref == Data->Vnom)) {
maxOverLapWidth--; // Reduce overlap width outside of diamond mask
}
PosRdWrToNegRdWr = TRUE;
}
}
if (((RdWrDly - Data->MaxRdWrSweep) < Width / 2) && (RdWrDly > (Data->MaxRdWrSweep - 1))) {
//
// Case 3: Overlay between -RdDqs and +RdDqs starting from -RdDqs
// Count the number of passes from "startWidth" to "endWidth"
//
maxOverLapWidth = Data->MaxRdWrSweep;
if (Vref == 0 || Vref == Data->Vnom) {
startOverLapWidth = RdWrDly - Width / 2; // Initial overlap start point
} else if (startOverLapWidth > maxOverLapWidth) {
maxOverLapWidth = maxOverLapWidth - 1; // Continue to count until MaxHeight excceded
}
// Ensure that vref + or - is set correctly
if (Vref > (Data->Vnom - 1)) {
PosNegData = Data->Lane[Lane].Vref[Vref].PosRdWrDly;
} else {
PosNegData = Data->Lane[Lane].Vref[(Data->Vnom - 1) - Vref].PosRdWrDly;
}
// Need to count the number of passes when range extends from Pos RdDqs to Neg RdDqs
for (count = startOverLapWidth; count < maxOverLapWidth; count++) {
Data->Lane[Lane].Convolution[RdWrDly] = (UINT8) ((PosNegData >> count) & 0x1) + Data->Lane[Lane].Convolution[RdWrDly];
}
if (startOverLapWidth < maxOverLapWidth) {
if (MemFCheckRdWr2DDiamondMaskStep (NBPtr, Data, Vref, Lane) || (Vref == 1) || (Vref == Data->Vnom)) {
startOverLapWidth++; // Reduce overlap width outside of diamond mask
}
NegRdWrToPosRdWr = TRUE;
}
}
}
if (MemFCheckRdWr2DDiamondMaskStep (NBPtr, Data, Vref, Lane) || (Vref == 1) || (Vref == Data->Vnom)) {
if (PosRdWrToNegRdWr) {
startWidth++;
endWidth = Data->MaxRdWrSweep;
PosRdWrToNegRdWr = FALSE;
} else if (NegRdWrToPosRdWr) {
startWidth = 0;
endWidth--;
NegRdWrToPosRdWr = FALSE;
} else {
startWidth++;
endWidth--;
}
}
NBPtr->FamilySpecificHook[Adjust2DVrefStepSize] (NBPtr, &Vref);
}
NBPtr->FamilySpecificHook[Adjust2DDelayStepSize] (NBPtr, &RdWrDly);
}
}
IDS_HDT_CONSOLE_DEBUG_CODE (
IDS_HDT_CONSOLE (MEM_FLOW, "\n");
IDS_HDT_CONSOLE (MEM_FLOW, "\t\t Diamond Shape: \n");
for (Vref = 0; Vref < (NBPtr->TotalMaxVrefRange - 1); Vref++) {
IDS_HDT_CONSOLE (MEM_FLOW, "\n");
for (RdWrDly = (Data->MaxRdWrSweep - Width); RdWrDly < Data->MaxRdWrSweep; RdWrDly++) {
if (Vref < (Data->Vnom - 1)) {
if (RdWrDly == Data->DiamondLeft[(NBPtr->TotalMaxVrefRange - 2) - Vref]) {
IDS_HDT_CONSOLE (MEM_FLOW, " | ");
} else if (RdWrDly == Data->DiamondRight[(NBPtr->TotalMaxVrefRange - 2) - Vref]) {
IDS_HDT_CONSOLE (MEM_FLOW, " | -> Width = %02x", (Data->DiamondRight[(NBPtr->TotalMaxVrefRange - 2) - Vref]) - (Data->DiamondLeft[(NBPtr->TotalMaxVrefRange - 2) - Vref]));
} else {
IDS_HDT_CONSOLE (MEM_FLOW, " ");
}
} else {
if (RdWrDly == Data->DiamondLeft[Vref - (Data->Vnom - 1)]) {
IDS_HDT_CONSOLE (MEM_FLOW, " | ");
} else if (RdWrDly == Data->DiamondRight[Vref - (Data->Vnom - 1)]) {
IDS_HDT_CONSOLE (MEM_FLOW, " | -> Width = %02x", (Data->DiamondRight[Vref - (Data->Vnom - 1)]) - (Data->DiamondLeft[Vref - (Data->Vnom - 1)]));
} else {
IDS_HDT_CONSOLE (MEM_FLOW, " ");
}
}
}
}
IDS_HDT_CONSOLE (MEM_FLOW, "\n");
IDS_HDT_CONSOLE (MEM_FLOW, "\n\t\t Convolution results after processing raw data:\n");
IDS_HDT_CONSOLE (MEM_FLOW, "\t\t Delay: ");
for (RdWrDly = 0; RdWrDly < TotalDlyRange; RdWrDly++) {
IDS_HDT_CONSOLE (MEM_FLOW, " %02x ", RdWrDly <= (Data->MaxRdWrSweep - 1) ? (Data->MaxRdWrSweep - 1) - RdWrDly : (TotalDlyRange - 1) - RdWrDly);
}
IDS_HDT_CONSOLE (MEM_FLOW, "\n");
for (Lane = 0; Lane < MemFRdWr2DGetMaxLanes (NBPtr); Lane++) {
IDS_HDT_CONSOLE (MEM_FLOW, "\t\tLane: %02x\n", Lane);
IDS_HDT_CONSOLE (MEM_FLOW, "\t\t\tConv: ");
for (RdWrDly = 0; RdWrDly < TotalDlyRange; RdWrDly++) {
IDS_HDT_CONSOLE (MEM_FLOW, "%03x ", Data->Lane[Lane].Convolution[RdWrDly]);
}
IDS_HDT_CONSOLE (MEM_FLOW, "\n");
}
);
return TRUE;
}
/* -----------------------------------------------------------------------------*/
/**
*
* This function Examines the convolution function and determines the Max Delay
* for 2D RdDQS and WrDat training
*
* @param[in,out] *NBPtr - Pointer to the MEM_NB_BLOCK
* @param[in] *Data - Pointer to Result data structure
*
* @return BOOLEAN
* TRUE - No Errors occurred
* FALSE - Errors ccurred
*/
BOOLEAN
MemFRdWr2DProcessConvolution (
IN OUT MEM_NB_BLOCK *NBPtr,
IN MEM_RD_WR_2D_ENTRY *Data
)
{
MEM_TECH_BLOCK *TechPtr;
UINT8 RdWrDly;
UINT8 Lane;
UINT16 MaxFOM;
UINT8 MaxRange;
UINT8 CurrRange;
UINT8 TotalDlyRange;
BOOLEAN status;
ASSERT (NBPtr != NULL);
TechPtr = NBPtr->TechPtr;
TotalDlyRange = (TechPtr->Direction == DQS_READ_DIR) ? NBPtr->TotalRdDQSDlyRange : NBPtr->TotalWrDatDlyRange;
status = TRUE;
IDS_HDT_CONSOLE (MEM_FLOW, "\n\t\tDetermining Delay based on Convolution function\n");
// Determine the Max RdDqs or WrDat Dly for the convolution function
// - Choose the delay setting at the peak FOM value.
for (Lane = 0; Lane < MemFRdWr2DGetMaxLanes (NBPtr); Lane++) {
// Find largest value as MaxFOM
MaxFOM = 0;
for (RdWrDly = 0; RdWrDly < TotalDlyRange; RdWrDly++) {
if (Data->Lane[Lane].Convolution[RdWrDly] > MaxFOM) {
MaxFOM = Data->Lane[Lane].Convolution[RdWrDly];
}
}
status = MaxFOM > 0 ? TRUE : FALSE; // It is an error if all convolution points are zero
// Then find the midpoint of the largest consecutive window w/ that MaxFOM
// In cases of an even number of consecutive points w/ that MaxFOM exists,
// choose the midpoint to the right
// All things being equal, favor the right side of a bi-modal eye
// Stressful SSO patterns shift the eye right!
MaxRange = 0;
CurrRange = 0;
for (RdWrDly = 0; (MaxFOM > 0) && RdWrDly < TotalDlyRange; RdWrDly++) {
if (Data->Lane[Lane].Convolution[RdWrDly] == MaxFOM) {
CurrRange++;
if (CurrRange >= MaxRange) {
Data->Lane[Lane].MaxRdWrDly = RdWrDly - ((CurrRange - 1) / 2);
MaxRange = CurrRange;
}
} else {
CurrRange = 0;
}
}
if (Data->Lane[Lane].MaxRdWrDly > Data->MaxRdWrSweep) {
status = FALSE; // Error
}
// Set Actual register value
if (Data->Lane[Lane].MaxRdWrDly < Data->MaxRdWrSweep) {
Data->Lane[Lane].MaxRdWrDly = (Data->MaxRdWrSweep - 1) - Data->Lane[Lane].MaxRdWrDly;
} else {
status = FALSE; // Error
}
}
IDS_HDT_CONSOLE_DEBUG_CODE (
IDS_HDT_CONSOLE (MEM_FLOW, "\n\t\t\t Cs %d Lane: ", TechPtr->ChipSel);
for (Lane = 0; Lane < MemFRdWr2DGetMaxLanes (NBPtr); Lane++) {
IDS_HDT_CONSOLE (MEM_FLOW, "%02x ", Lane);
}
IDS_HDT_CONSOLE (MEM_FLOW, "\n");
IDS_HDT_CONSOLE (MEM_FLOW, "\t\t\t Max %s Delay: ", TechPtr->Direction == DQS_READ_DIR ? "Rd Dqs" : "Wr DQ");
for (Lane = 0; Lane < MemFRdWr2DGetMaxLanes (NBPtr); Lane++) {
IDS_HDT_CONSOLE (MEM_FLOW, "%02x ", Data->Lane[Lane].MaxRdWrDly);
}
IDS_HDT_CONSOLE (MEM_FLOW, "\n\t\t\t1D Trained %s Delay: ", TechPtr->Direction == DQS_READ_DIR ? "Rd Dqs" : "Wr DQ");
for (Lane = 0; Lane < MemFRdWr2DGetMaxLanes (NBPtr); Lane++) {
if (TechPtr->Direction == DQS_READ_DIR) {
IDS_HDT_CONSOLE (MEM_FLOW, "%02x ", NBPtr->ChannelPtr->RdDqsDlys[(TechPtr->ChipSel / NBPtr->CsPerDelay) * MAX_DELAYS + Lane]);
} else {
IDS_HDT_CONSOLE (MEM_FLOW, "%02x ", (NBPtr->ChannelPtr->WrDatDlys[(TechPtr->ChipSel / NBPtr->CsPerDelay) * MAX_DELAYS + Lane] - \
NBPtr->ChannelPtr->WrDqsDlys[(TechPtr->ChipSel / NBPtr->CsPerDelay) * MAX_DELAYS + Lane]));
}
}
IDS_HDT_CONSOLE (MEM_FLOW, "\n");
);
if (status == FALSE) {
SetMemError (AGESA_FATAL, NBPtr->MCTPtr);
PutEventLog (AGESA_FATAL, MEM_ERROR_INVALID_2D_RDDQS_VALUE, 0, 0, 0, 0, &TechPtr->NBPtr->MemPtr->StdHeader);
}
return status;
}
/* -----------------------------------------------------------------------------*/
/**
*
* This function programs the Max Rd Dqs or Max Wr DQ for 2D training from
* convolution
*
* @param[in,out] *NBPtr - Pointer to the MEM_NB_BLOCK
* @param[in] *Data - Pointer to Result data structure
*
* @return BOOLEAN
* TRUE - No Errors occurred
* FALSE - Errors ccurred
*/
BOOLEAN
MemFRdWr2DProgramMaxDelays (
IN OUT MEM_NB_BLOCK *NBPtr,
IN MEM_RD_WR_2D_ENTRY *Data
)
{
MEM_TECH_BLOCK *TechPtr;
UINT8 Lane;
UINT8 LaneHighRdDqs2dDlys;
UINT8 LaneLowRdDqs2dDlys;
UINT8 MaxWrDatDly;
TechPtr = NBPtr->TechPtr;
IDS_HDT_CONSOLE (MEM_FLOW, "\n\t\tProgramming Max %s Delay per Lane\n\n", TechPtr->Direction == DQS_READ_DIR ? "Rd Dqs" : "Wr DQ");
for (Lane = 0; Lane < MemFRdWr2DGetMaxLanes (NBPtr); Lane++) {
if ( TechPtr->Direction == DQS_WRITE_DIR || (NBPtr->ChannelPtr->DimmNibbleAccess & (1 << (TechPtr->ChipSel >> 1))) == 0) {
// Program Byte based for x8 and x16
if ( TechPtr->Direction == DQS_READ_DIR) {
//
// Read DQS Training
//
NBPtr->SetTrainDly (NBPtr, AccessRdDqsDly, DIMM_BYTE_ACCESS ((TechPtr->ChipSel / NBPtr->CsPerDelay), Lane), (UINT16)Data->Lane[Lane].MaxRdWrDly);
NBPtr->ChannelPtr->RdDqsDlys[(TechPtr->ChipSel / NBPtr->CsPerDelay) * MAX_DELAYS + Lane] = Data->Lane[Lane].MaxRdWrDly;
} else {
//
// Write DQ Training
//
MaxWrDatDly = (UINT8) (Data->Lane[Lane].MaxRdWrDly + TechPtr->NBPtr->ChannelPtr->WrDqsDlys[(TechPtr->ChipSel / TechPtr->NBPtr->CsPerDelay) * MAX_DELAYS + Lane]);
NBPtr->SetTrainDly (NBPtr, AccessWrDatDly, DIMM_BYTE_ACCESS ((TechPtr->ChipSel / NBPtr->CsPerDelay), Lane), MaxWrDatDly);
NBPtr->ChannelPtr->WrDatDlys[(TechPtr->ChipSel / NBPtr->CsPerDelay) * MAX_DELAYS + Lane] = MaxWrDatDly;
}
} else {
ASSERT (TechPtr->Direction == DQS_READ_DIR);
// Program nibble based x4, so use "Nibble"
NBPtr->SetTrainDly (NBPtr, AccessRdDqs2dDly, DIMM_NBBL_ACCESS ((TechPtr->ChipSel / NBPtr->CsPerDelay), Lane), (UINT16)Data->Lane[Lane].MaxRdWrDly);
NBPtr->ChannelPtr->RdDqs2dDlys[(TechPtr->ChipSel / NBPtr->CsPerDelay) * MAX_NUMBER_LANES + Lane] = Data->Lane[Lane].MaxRdWrDly;
// For each pair of nibbles (high (Odd Nibble) and Low (Even nibble)), find the largest and use that as the RdDqsDly value
if ((Lane & 0x1) == 0) {
LaneHighRdDqs2dDlys = Data->Lane[Lane + 1].MaxRdWrDly;
LaneLowRdDqs2dDlys = Data->Lane[Lane].MaxRdWrDly;
if (LaneHighRdDqs2dDlys > LaneLowRdDqs2dDlys) {
NBPtr->ChannelPtr->RdDqsDlys[(TechPtr->ChipSel / NBPtr->CsPerDelay) * MAX_DELAYS + (Lane >> 1)] = LaneHighRdDqs2dDlys;
} else {
NBPtr->ChannelPtr->RdDqsDlys[(TechPtr->ChipSel / NBPtr->CsPerDelay) * MAX_DELAYS + (Lane >> 1)] = LaneLowRdDqs2dDlys;
}
}
NBPtr->DctCachePtr->Is2Dx4 = TRUE;
}
}
return TRUE;
}
/* -----------------------------------------------------------------------------*/
/**
*
* This function finds the Positive and negative Vref Margin for the current CS
* for 2D RdDQS or WrDat training
*
* @param[in,out] *NBPtr - Pointer to the MEM_NB_BLOCK
* @param[in] *Data - Pointer to Result data structure
*
* @return BOOLEAN
* TRUE - No Errors occurred
* FALSE - Errors ccurred
*/
BOOLEAN
MemFRdWr2DFindCsVrefMargin (
IN OUT MEM_NB_BLOCK *NBPtr,
IN MEM_RD_WR_2D_ENTRY *Data
)
{
UINT8 SmallestMaxVrefNeg;
UINT8 Lane;
UINT8 RdWrDly;
UINT8 Vref;
UINT8 MaxVrefPositive;
UINT8 MaxVrefNegative;
UINT8 SmallestMaxVrefPos;
UINT32 PosNegData;
SmallestMaxVrefPos = 0xFF;
SmallestMaxVrefNeg = 0;
MaxVrefPositive = 0;
MaxVrefNegative = 0xFF;
IDS_HDT_CONSOLE (MEM_FLOW, "\n\t\tFinding Smallest Max Positive and Negative Vref\n\n");
for (Lane = 0; Lane < MemFRdWr2DGetMaxLanes (NBPtr); Lane++) {
RdWrDly = (Data->MaxRdWrSweep - 1) - Data->Lane[Lane].MaxRdWrDly;
for (Vref = 0; Vref < (Data->Vnom - 1); Vref++) {
// Neg Vref - (searching from top of array down)
PosNegData = Data->Lane[Lane].Vref[Vref].PosRdWrDly;
if ((UINT8) ((PosNegData >> RdWrDly) & 0x1) == 1) {
MaxVrefNegative = Vref;
break;
}
NBPtr->FamilySpecificHook[Adjust2DVrefStepSize] (NBPtr, &Vref);
}
for (Vref = (Data->Vnom - 1); Vref < (NBPtr->TotalMaxVrefRange - 1); Vref++) {
// Pos Vref - (searching from Vnom + 1 of array down)
PosNegData = Data->Lane[Lane].Vref[Vref].PosRdWrDly;
if ((UINT8) ((PosNegData >> RdWrDly) & 0x1) == 0) {
// Convert to register setting
MaxVrefPositive = Vref - 1;// - Data->Vnom;
break;
} else {
// If Vref = 1F passes, then smallest Vref = 0x1F
if (Vref == ((NBPtr->TotalMaxVrefRange - 1) - 1)) {
MaxVrefPositive = 0x1E;
break;
}
}
NBPtr->FamilySpecificHook[Adjust2DVrefStepSize] (NBPtr, &Vref);
}
if (MaxVrefPositive < SmallestMaxVrefPos) {
// Find the smallest Max Pos Vref
SmallestMaxVrefPos = MaxVrefPositive;
}
if (MaxVrefNegative > SmallestMaxVrefNeg) {
// Find the largest Max Neg Vref
SmallestMaxVrefNeg = MaxVrefNegative;
}
}
if (SmallestMaxVrefPos != (Data->Vnom - 2)) {
Data->SmallestPosMaxVrefperCS[NBPtr->TechPtr->ChipSel] = SmallestMaxVrefPos - Data->Vnom + 1;
} else {
Data->SmallestPosMaxVrefperCS[NBPtr->TechPtr->ChipSel] = 0;
}
Data->SmallestNegMaxVrefperCS[NBPtr->TechPtr->ChipSel] = (Data->Vnom - 1) - SmallestMaxVrefNeg;
IDS_HDT_CONSOLE_DEBUG_CODE (
IDS_HDT_CONSOLE (MEM_FLOW, "\t\t\tSmallest Max Positive Vref Offset from V-Nom for ChipSel %02x = + %02x\n", NBPtr->TechPtr->ChipSel, Data->SmallestPosMaxVrefperCS[NBPtr->TechPtr->ChipSel]);
if (Data->SmallestPosMaxVrefperCS[NBPtr->TechPtr->ChipSel] == 0) {
IDS_HDT_CONSOLE (MEM_FLOW, "\t\t\tSmallest Max Negative Vref Offset from V-Nom for ChipSel %02x = 00\n");
} else {
IDS_HDT_CONSOLE (MEM_FLOW, "\t\t\tSmallest Max Negative Vref Offset from V-Nom for ChipSel %02x = - %02x\n", NBPtr->TechPtr->ChipSel, Data->SmallestNegMaxVrefperCS[NBPtr->TechPtr->ChipSel]);
}
);
return TRUE;
}
/* -----------------------------------------------------------------------------*/
/**
*
* This function finds the final Vref Margin for 2D RdDQS or WrDat training
*
* @param[in,out] *NBPtr - Pointer to the MEM_NB_BLOCK
* @param[in] *Data - Pointer to Result data structure
*
* @return BOOLEAN
* TRUE - No Errors occurred
* FALSE - Errors ccurred
*/
BOOLEAN
MemFRdWr2DFinalVrefMargin (
IN OUT MEM_NB_BLOCK *NBPtr,
IN MEM_RD_WR_2D_ENTRY *Data
)
{
UINT8 ChipSel;
UINT8 SmallestMaxPosVref;
UINT8 SmallestMaxNegVref;
UINT8 OffsetFromVref;
UINT8 Vnom;
SmallestMaxNegVref = 0x7F;
SmallestMaxPosVref = 0x7F;
Vnom = (Data->Vnom - 1);
IDS_HDT_CONSOLE (MEM_FLOW, "\n\t\tFinding Final Vref for channel\n\n");
for (ChipSel = 0; ChipSel < NBPtr->CsPerChannel; ChipSel = ChipSel + NBPtr->CsPerDelay ) {
if ( (NBPtr->MCTPtr->Status[SbLrdimms]) ? ((NBPtr->ChannelPtr->LrDimmPresent & ((UINT8) 1 << (ChipSel >> 1))) != 0) :
((NBPtr->DCTPtr->Timings.CsEnabled & ((UINT16) 1 << ChipSel)) != 0) ) {
if (Data->SmallestPosMaxVrefperCS[ChipSel] < SmallestMaxPosVref) {
SmallestMaxPosVref = Data->SmallestPosMaxVrefperCS[ChipSel];
}
if (Data->SmallestNegMaxVrefperCS[ChipSel] < SmallestMaxNegVref) {
SmallestMaxNegVref = Data->SmallestNegMaxVrefperCS[ChipSel];
}
}
}
//
// Synchronize minimum and Maximums with other Vref values
//
if (NBPtr->TechPtr->Direction == DQS_WRITE_DIR) {
if (NBPtr->SharedPtr->CommonSmallestMaxNegVref < SmallestMaxNegVref) {
SmallestMaxNegVref = NBPtr->SharedPtr->CommonSmallestMaxNegVref;
} else {
NBPtr->SharedPtr->CommonSmallestMaxNegVref = SmallestMaxNegVref;
}
if (NBPtr->SharedPtr->CommonSmallestMaxPosVref < SmallestMaxPosVref) {
SmallestMaxPosVref = NBPtr->SharedPtr->CommonSmallestMaxPosVref;
} else {
NBPtr->SharedPtr->CommonSmallestMaxPosVref = SmallestMaxPosVref;
}
}
NBPtr->FamilySpecificHook[RdWr2DScaleVref] (NBPtr, &SmallestMaxPosVref);
NBPtr->FamilySpecificHook[RdWr2DScaleVref] (NBPtr, &SmallestMaxNegVref);
NBPtr->FamilySpecificHook[RdWr2DScaleVref] (NBPtr, &Vnom);
IDS_HDT_CONSOLE (MEM_FLOW, "\t\t\tScaled Smallest Max Positive = + %02x\n", SmallestMaxPosVref);
IDS_HDT_CONSOLE (MEM_FLOW, "\t\t\tScaled Smallest Max Negative =%s%02x\n", ((SmallestMaxNegVref != 0) ? " - " : " "), SmallestMaxNegVref);
IDS_HDT_CONSOLE (MEM_FLOW, "\t\t\tScaled Vnom = %02x\n", Vnom);
if (SmallestMaxPosVref > SmallestMaxNegVref) {
OffsetFromVref = (SmallestMaxPosVref - SmallestMaxNegVref) / 2;
NBPtr->ChannelPtr->MaxVref = Vnom + OffsetFromVref;
} else {
OffsetFromVref = (SmallestMaxNegVref - SmallestMaxPosVref) / 2;
NBPtr->ChannelPtr->MaxVref = Vnom - OffsetFromVref;
}
IDS_HDT_CONSOLE (MEM_FLOW, "\t\t\tFinal Vref Offset From Vnom =%s%02x\n",
((OffsetFromVref != 0) ? ((SmallestMaxPosVref > SmallestMaxNegVref) ? " + ":" - "):" "), OffsetFromVref);
return TRUE;
}
/* -----------------------------------------------------------------------------*/
/**
*
* This function displays ther results of the 2D search
*
* @param[in,out] *NBPtr - Pointer to the MEM_NB_BLOCK
* @param[in] *Data - Pointer to Result data structure
*
*/
VOID
MemFRdWr2DDisplaySearch (
IN OUT MEM_NB_BLOCK *NBPtr,
IN MEM_RD_WR_2D_ENTRY *Data
)
{
IDS_HDT_CONSOLE_DEBUG_CODE (
UINT8 Lane;
INT8 Vref;
// Display data collected
IDS_HDT_CONSOLE (MEM_FLOW, "\t\tDisplaying Data collected\n\n");
for (Lane = 0; Lane < MemFRdWr2DGetMaxLanes (NBPtr); Lane++) {
IDS_HDT_CONSOLE (MEM_FLOW, "\t\t\tLane: %02x\n", Lane);
IDS_HDT_CONSOLE (MEM_FLOW, "\t\t\t Vref %s\n", (NBPtr->TechPtr->Direction == DQS_READ_DIR) ? "NegRdDqs PosRdDqs" : "PosWrDat");
for (Vref = NBPtr->TotalMaxVrefRange - 2; Vref >= 0; Vref--) {
if (Vref < (Data->Vnom - 1)) {
IDS_HDT_CONSOLE (MEM_FLOW, "\t\t\t - ");
IDS_HDT_CONSOLE (MEM_FLOW, "%02x ", ((Data->Vnom -1) - Vref));
} else if (Vref == (Data->Vnom - 1)) {
IDS_HDT_CONSOLE (MEM_FLOW, "\t\t\t 00 ");
} else {
IDS_HDT_CONSOLE (MEM_FLOW, "\t\t\t + ");
IDS_HDT_CONSOLE (MEM_FLOW, "%02x ", Vref - (Data->Vnom - 1));
}
if (NBPtr->TechPtr->Direction == DQS_READ_DIR) {
IDS_HDT_CONSOLE (MEM_FLOW, "%08x", Data->Lane[Lane].Vref[Vref].NegRdWrDly);
} else {
IDS_HDT_CONSOLE (MEM_FLOW, " ");
}
IDS_HDT_CONSOLE (MEM_FLOW, "%08x \n", Data->Lane[Lane].Vref[Vref].PosRdWrDly);
NBPtr->FamilySpecificHook[Adjust2DVrefStepSize] (NBPtr, &Vref);
}
}
)
}