| /* $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); |
| } |
| } |
| ) |
| } |