| /* $NoKeywords:$ */ |
| /** |
| * @file |
| * |
| * mttRdDqs2DTraining.c |
| * |
| * RD DQS 2 dimentional training |
| * |
| * @xrefitem bom "File Content Label" "Release Content" |
| * @e project: AGESA |
| * @e sub-project: (Mem/Tech) |
| * @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 "AdvancedApi.h" |
| #include "GeneralServices.h" |
| #include "Ids.h" |
| #include "heapManager.h" |
| #include "mm.h" |
| #include "mn.h" |
| #include "mu.h" |
| #include "mt.h" |
| #include "mport.h" |
| #include "merrhdl.h" |
| #include "Filecode.h" |
| #include "OptionMemory.h" |
| CODE_GROUP (G1_PEICC) |
| RDATA_GROUP (G1_PEICC) |
| |
| #define FILECODE PROC_MEM_TECH_MTRRDDQS2DTRAINING_FILECODE |
| /*---------------------------------------------------------------------------- |
| * DEFINITIONS AND MACROS |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| #define MAX_DELAYS 9 /* 8 data bytes + 1 ECC byte */ |
| /*---------------------------------------------------------------------------- |
| * TYPEDEFS AND STRUCTURES |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| |
| /*---------------------------------------------------------------------------- |
| * PROTOTYPES OF LOCAL FUNCTIONS |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| BOOLEAN |
| MemT2DRdDQSProcessConvolution ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN MEM_2D_ENTRY *Data |
| ); |
| |
| /*---------------------------------------------------------------------------- |
| * EXPORTED FUNCTIONS |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| extern MEM_PSC_FLOW_BLOCK* memPlatSpecFlowArray[]; |
| |
| /* -----------------------------------------------------------------------------*/ |
| /** |
| * |
| * This function executes 2D training for Rd DQS |
| * |
| * @param[in,out] *TechPtr - Pointer to the MEM_TECH_BLOCK |
| * |
| * @return TRUE - No Errors occurred |
| * @return FALSE - Errors occurred |
| */ |
| |
| BOOLEAN |
| MemTAmdRdDqs2DTraining ( |
| IN OUT MEM_TECH_BLOCK *TechPtr |
| ) |
| { |
| MEM_NB_BLOCK *NBPtr; |
| MEM_DATA_STRUCT *MemPtr; |
| UINT8 Dct; |
| UINT8 ChipSel; |
| MEM_2D_ENTRY Data; |
| UINT8 Lane; |
| UINT8 Vref; |
| BOOLEAN Status; |
| BOOLEAN DCT_x4Present; |
| UINT8 MaxLanes; |
| PSO_TABLE *PsoTable; |
| RD_DQS_2D *VrefPtr; |
| ALLOCATE_HEAP_PARAMS AllocHeapParams; |
| NBPtr = TechPtr->NBPtr; |
| MemPtr = NBPtr->MemPtr; |
| AGESA_TESTPOINT (TpProcMem2dRdDqsTraining, &(MemPtr->StdHeader)); |
| PsoTable = MemPtr->ParameterListPtr->PlatformMemoryConfiguration; |
| // |
| // Set environment settings before training |
| // |
| IDS_HDT_CONSOLE (MEM_STATUS, "\n\nStart RD DQS 2D training.\n\n"); |
| MemTBeginTraining (TechPtr); |
| // |
| // Allocate heap for the 2D RdDqs/Vref Data structure |
| // |
| DCT_x4Present = FALSE; |
| // Check DCTs for x4 DIMMs |
| for (Dct = 0; Dct < NBPtr->DctCount; Dct++) { |
| NBPtr->SwitchDCT (NBPtr, Dct); |
| if (NBPtr->ChannelPtr->DimmNibbleAccess != 0) { |
| DCT_x4Present = TRUE; |
| break; |
| } |
| } |
| |
| NBPtr->SwitchDCT (NBPtr, 0); |
| // If DCT0 or DCT1 have x4 DIMMs, additonal allocate space |
| if (DCT_x4Present == TRUE) { |
| // Per Nibble |
| MaxLanes = TechPtr->NBPtr->MCTPtr->Status[SbEccDimms] ? 18 : 16; |
| } else { |
| // Per Byte |
| MaxLanes = TechPtr->NBPtr->MCTPtr->Status[SbEccDimms] ? 9 : 8; |
| } |
| MaxLanes = 8; /// @todo Family specific hook |
| AllocHeapParams.RequestedBufferSize = MaxLanes * NBPtr->TotalMaxVrefRange * sizeof (RD_DQS_2D); |
| AllocHeapParams.BufferHandle = AMD_MEM_2D_RDQS_HANDLE; |
| AllocHeapParams.Persist = HEAP_LOCAL_CACHE; |
| if (HeapAllocateBuffer (&AllocHeapParams, &MemPtr->StdHeader) == AGESA_SUCCESS) { |
| VrefPtr = (RD_DQS_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->Direction = DQS_READ_DIR; |
| TechPtr->TrainingType = TRN_DQS_POSITION; |
| NBPtr->FamilySpecificHook[SetupHwTrainingEngine] (NBPtr, &TechPtr->TrainingType); |
| |
| Data.Vnom = NBPtr->TotalMaxVrefRange / 2; // Set Nominal Vref |
| Data.MaxRdDqsSweep = NBPtr->TotalRdDQSDlyRange / 2; // Set Nominal Vref |
| ASSERT (NBPtr->TotalRdDQSDlyRange <= MAX_RD_DQS_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); |
| Status = FALSE; |
| if (MemTCheck2DTrainingPerConfig (TechPtr)) { |
| 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 < MemT2DGetMaxLanes (TechPtr); Lane++) { |
| for (Vref = 0; Vref < NBPtr->TotalMaxVrefRange; Vref++) { |
| Data.Lane[Lane].Vref[Vref].PosRdDqsDly = 0; |
| Data.Lane[Lane].Vref[Vref].NegRdDqsDly = 0; |
| } |
| } |
| TechPtr->ChipSel = ChipSel; |
| IDS_HDT_CONSOLE (MEM_FLOW,"\tChip Select: %02x \n", TechPtr->ChipSel); |
| // |
| // 1. Sample the data eyes for each channel: |
| // |
| TechPtr->Local2DData = &Data; |
| if (TechPtr->NBPtr->MemN2DRdDQSDataCollection (NBPtr)) { |
| // |
| // 2. Process the array of results with a diamond convolution mask, summing the number passing sample points. |
| // |
| // Determine Diamond Mask Height |
| if (MemT2DRdDQSHeight (TechPtr, &Data)) { |
| // Apply Mask |
| if (MemT2DRdDQSApplyMask (TechPtr, &Data)) { |
| // Convolution |
| if (MemT2DRdDQSProcessConvolution (TechPtr, &Data)) { |
| // |
| // 3. Program the final DQS delay values. |
| // |
| if (MemT2DRdDQSPrograMaxRdDQSDly (TechPtr, &Data)) { |
| // |
| // Find the Smallest Positive or Negative Margin for current CS |
| // |
| if (MemT2DRdDQSFindCsVrefMargin (TechPtr, &Data)) { |
| Status = TRUE; |
| // |
| // DATAEYE - Allocate Temp storage for Generate Composite Eyes, |
| // Save composite eye for CS Pair into Allocated Storage. |
| TechPtr->TechnologySpecificHook[DataEyeSaveCompositeEyes] (TechPtr, &Data); |
| } |
| } |
| } |
| } |
| } |
| } |
| if (Status == FALSE) { |
| SetMemError (AGESA_ERROR, NBPtr->MCTPtr); |
| PutEventLog (AGESA_ERROR, MEM_ERROR_2D_DQS_ERROR, NBPtr->Node, NBPtr->Dct, NBPtr->Channel, 0, &NBPtr->MemPtr->StdHeader); |
| } |
| } // Chipselect |
| } // End of ChipSel for loop. |
| |
| // |
| // DATAEYE - Compress Eyes, Deallocate temp storage. Allocate memory for compressed eyes, |
| // copy eyes to new struct, fixup pointers. |
| // |
| TechPtr->TechnologySpecificHook[DataEyeCompressEyes] (TechPtr, &Data); |
| // |
| // Find the Max and Min Vref values for each DCT |
| // |
| if (Status == TRUE) { |
| if (MemT2DRdDQSFinalVrefMargin (TechPtr, &Data)) { |
| // |
| // Program the Max Vref value |
| // |
| IDS_HDT_CONSOLE (MEM_FLOW, "\n\t\tProgramming Final Vref for channel\n\n"); |
| MemT2DProgramVref (TechPtr, NBPtr->ChannelPtr->MaxVref); |
| } else { |
| SetMemError (AGESA_ERROR, NBPtr->MCTPtr); |
| PutEventLog (AGESA_ERROR, MEM_ERROR_2D_DQS_VREF_MARGIN_ERROR, NBPtr->Node, NBPtr->Dct, NBPtr->Channel, 0, &NBPtr->MemPtr->StdHeader); |
| } |
| } |
| } |
| } |
| // |
| // Restore environment settings after training |
| // |
| if (HeapDeallocateBuffer (AMD_MEM_2D_RDQS_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 RD DQS 2D training\n\n"); |
| return (BOOLEAN) (NBPtr->MCTPtr->ErrCode < AGESA_FATAL); |
| } |
| /*---------------------------------------------------------------------------- |
| * LOCAL FUNCTIONS |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| /* -----------------------------------------------------------------------------*/ |
| /** |
| * |
| * This function determines the maximum number of lanes to program 2D RdDQS training |
| * |
| * @param[in,out] *TechPtr - Pointer to the MEM_TECH_BLOCK |
| * |
| * @return TRUE - Configuration valid |
| * FALSE - Configuration invalid |
| */ |
| BOOLEAN |
| MemTCheck2DTrainingPerConfig ( |
| IN OUT MEM_TECH_BLOCK *TechPtr |
| ) |
| { |
| UINT8 i; |
| if (TechPtr->NBPtr->RefPtr->ForceTrainMode == FORCE_TRAIN_AUTO) { |
| i = 0; |
| while (memPlatSpecFlowArray[i] != NULL) { |
| if ((memPlatSpecFlowArray[i])->S2D (TechPtr->NBPtr, (memPlatSpecFlowArray[i])->EntryOfTables)) { |
| return TRUE; |
| } |
| i++; |
| } |
| } |
| return FALSE; |
| } |
| /* -----------------------------------------------------------------------------*/ |
| /** |
| * |
| * This function determines the maximum number of lanes to program 2D RdDQS training |
| * |
| * @param[in,out] *TechPtr - Pointer to the MEM_TECH_BLOCK |
| * |
| * @return TRUE - Max Number of Lanes |
| */ |
| UINT8 |
| MemT2DGetMaxLanes ( |
| IN OUT MEM_TECH_BLOCK *TechPtr |
| ) |
| { |
| UINT8 MaxLanes; |
| if ((TechPtr->NBPtr->ChannelPtr->DimmNibbleAccess & (1 << (TechPtr->ChipSel >> 1))) != 0) { |
| // Per Nibble |
| MaxLanes = TechPtr->NBPtr->MCTPtr->Status[SbEccDimms] ? 18 : 16; |
| } else { |
| // Per Byte |
| MaxLanes = TechPtr->NBPtr->MCTPtr->Status[SbEccDimms] ? 9 : 8; |
| } |
| MaxLanes = 8; |
| return MaxLanes; |
| } |
| /* -----------------------------------------------------------------------------*/ |
| /** |
| * |
| * This function programs Vref to internal or external control for 2D RdDQS training |
| * |
| * @param[in,out] *TechPtr - Pointer to the MEM_TECH_BLOCK |
| * |
| */ |
| VOID |
| MemT2DProgramIntExtVrefSelect ( |
| IN OUT MEM_TECH_BLOCK *TechPtr |
| ) |
| { |
| TechPtr->NBPtr->SetBitField (TechPtr->NBPtr, BFVrefSel, (TechPtr->NBPtr->RefPtr->ExternalVrefCtl ? 0x0000 : 0x0001)); |
| } |
| /* -----------------------------------------------------------------------------*/ |
| /** |
| * |
| * This function programs Vref for 2D RdDQS training |
| * |
| * @param[in,out] *TechPtr - Pointer to the MEM_TECH_BLOCK |
| * @param[in] Vref - Vref value |
| * |
| */ |
| VOID |
| MemT2DProgramVref ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN UINT8 Vref |
| ) |
| { |
| ID_INFO CallOutIdInfo; |
| CallOutIdInfo.IdField.SocketId = TechPtr->NBPtr->MCTPtr->SocketId; |
| CallOutIdInfo.IdField.ModuleId = TechPtr->NBPtr->MCTPtr->DieId; |
| if (TechPtr->NBPtr->RefPtr->ExternalVrefCtl == FALSE) { |
| // |
| // Internal vref control |
| /// @todo : Separate Family-Specific Funtionality |
| // |
| // |
| // 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; |
| } |
| TechPtr->NBPtr->SetBitField (TechPtr->NBPtr, BFVrefDAC, Vref << 2); |
| } else { |
| // External vref control |
| AGESA_TESTPOINT (TpProcMemBefore2dTrainExtVrefChange, &(TechPtr->NBPtr->MemPtr->StdHeader)); |
| TechPtr->NBPtr->MemPtr->ParameterListPtr->ExternalVrefValue = Vref; |
| IDS_HDT_CONSOLE (MEM_FLOW, "\n2D training External Vref callout \n"); |
| // |
| /// @todo: Implement UEFI DXE Callout for AgesaExternal2dTrainVrefChange before uncommenting this |
| // |
| // AgesaExternal2dTrainVrefChange ((UINTN) CallOutIdInfo.IdInformation, TechPtr->NBPtr->MemPtr); |
| AGESA_TESTPOINT (TpProcMemAfter2dTrainExtVrefChange, &(TechPtr->NBPtr->MemPtr->StdHeader)); |
| } |
| } |
| /* -----------------------------------------------------------------------------*/ |
| /** |
| * |
| * This function programs RdDQS for 2D RdDQS training |
| * |
| * @param[in,out] *TechPtr - Pointer to the MEM_TECH_BLOCK |
| * @param[in] RdDQSDly - RdDqs value |
| * |
| */ |
| VOID |
| MemT2DPrograRdDQSDly ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN UINT8 RdDQSDly |
| ) |
| { |
| UINT32 RdDqsTime; |
| // Program BL registers for both nibble (x4) and bytes (x8, x16) |
| RdDqsTime = 0; |
| RdDqsTime = (RdDQSDly & 0x1F) << 8; |
| RdDqsTime = RdDqsTime | (RdDQSDly & 0x1F); |
| if ((TechPtr->ChipSel / TechPtr->NBPtr->CsPerDelay) == 0) { |
| TechPtr->NBPtr->SetBitField (TechPtr->NBPtr, BFDataByteRxDqsDLLDimm0Broadcast, RdDqsTime); |
| } else if ((TechPtr->ChipSel / TechPtr->NBPtr->CsPerDelay) == 1) { |
| TechPtr->NBPtr->SetBitField (TechPtr->NBPtr, BFDataByteRxDqsDLLDimm1Broadcast, RdDqsTime); |
| } else if ((TechPtr->ChipSel / TechPtr->NBPtr->CsPerDelay) == 2) { |
| TechPtr->NBPtr->SetBitField (TechPtr->NBPtr, BFDataByteRxDqsDLLDimm2Broadcast, RdDqsTime); |
| } else if ((TechPtr->ChipSel / TechPtr->NBPtr->CsPerDelay) == 3) { |
| TechPtr->NBPtr->SetBitField (TechPtr->NBPtr, BFDataByteRxDqsDLLDimm3Broadcast, RdDqsTime); |
| } |
| } |
| /* -----------------------------------------------------------------------------*/ |
| /** |
| * |
| * This function stores data for 2D RdDQS training |
| * |
| * @param[in,out] *TechPtr - Pointer to the MEM_TECH_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 TRUE - No Errors occurred |
| */ |
| VOID |
| StoreResult ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN MEM_2D_ENTRY *Data, |
| IN UINT32 InPhaseResult[], |
| IN UINT32 PhaseResult180[] |
| ) |
| { |
| UINT8 Lane; |
| UINT8 Vref; |
| UINT8 RdDqsDly; |
| UINT32 Result; |
| UINT32 Result180; |
| UINT8 Index; |
| Vref = TechPtr->NBPtr->Vref; |
| RdDqsDly = TechPtr->NBPtr->RdDqsDly; |
| for (Lane = 0; Lane < MemT2DGetMaxLanes (TechPtr); Lane++) { |
| for (RdDqsDly = 0; RdDqsDly < Data->MaxRdDqsSweep; RdDqsDly++) { |
| if ((TechPtr->NBPtr->ChannelPtr->DimmNibbleAccess & (1 << (TechPtr->ChipSel >> 1))) == 0) { |
| // x8, so combine "Nibble X" and "Nibble X+1" results |
| Index = Lane * 2; |
| Result = (InPhaseResult[RdDqsDly] >> Index) & 0x03; |
| Result180 = (PhaseResult180[RdDqsDly] >> Index) & 0x03; |
| } else { |
| // x4, so use "Nibble" results |
| Result = (InPhaseResult[RdDqsDly] >> Lane) & 0x01; |
| Result180 = (PhaseResult180[RdDqsDly] >> Lane) & 0x01; |
| } |
| Data->Lane[Lane].Vref[Vref].PosRdDqsDly |= (Result == 0) ? (1 << (Data->MaxRdDqsSweep - 1 - RdDqsDly)) : 0; |
| Data->Lane[Lane].Vref[Vref].NegRdDqsDly |= (Result180 == 0) ? (1 << (Data->MaxRdDqsSweep - 1 - RdDqsDly)) : 0; |
| } |
| } |
| } |
| /* -----------------------------------------------------------------------------*/ |
| /** |
| * |
| * This function determines the height of data for 2D RdDQS training |
| * |
| * @param[in,out] *TechPtr - Pointer to the MEM_TECH_BLOCK |
| * @param[in] *Data - Pointer to Result data structure |
| * |
| * @return TRUE - No Errors occurred |
| * @return FALSE - Errors occurred |
| */ |
| BOOLEAN |
| MemT2DRdDQSHeight ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN MEM_2D_ENTRY *Data |
| ) |
| { |
| UINT8 Lane; |
| for (Lane = 0; Lane < MemT2DGetMaxLanes (TechPtr); 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 < MemT2DGetMaxLanes (TechPtr); 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 < MemT2DGetMaxLanes (TechPtr); 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 training |
| * |
| * @param[in,out] *TechPtr - Pointer to the MEM_TECH_BLOCK |
| * @param[in] *Data - Pointer to Result data structure |
| * |
| * @return width |
| */ |
| UINT8 |
| MemGet2dRdDQSWidth ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN MEM_2D_ENTRY *Data |
| ) |
| { |
| return TechPtr->NBPtr->DiamondWidthRd; |
| } |
| /* -----------------------------------------------------------------------------*/ |
| /** |
| * |
| * This function gets the step height for the dimond mask for 2D RdDQS training |
| * |
| * @param[in,out] *TechPtr - Pointer to the MEM_TECH_BLOCK |
| * @param[in] *Data - Pointer to Result data structure |
| * @param[in] Vref - current Vref value |
| * @param[in] Lane - current Lane |
| * |
| * @return TRUE - Step found and value should be updated |
| * FALSE - Step not found and value should not be updated |
| */ |
| BOOLEAN |
| MemCheck2dRdDQSDiamondMaskStep ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN MEM_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 = (MemGet2dRdDQSWidth (TechPtr, 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 fo 2D RdDQS training |
| * |
| * @param[in,out] *TechPtr - Pointer to the MEM_TECH_BLOCK |
| * @param[in] *Data - Pointer to Result data structure |
| * |
| * @return TRUE - No Errors occurred |
| * @return FALSE - Errors occurred |
| */ |
| BOOLEAN |
| MemT2DRdDQSApplyMask ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN MEM_2D_ENTRY *Data |
| ) |
| { |
| UINT8 RdDqsDly; |
| 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; |
| BOOLEAN maxHeightExceeded; |
| BOOLEAN negVrefComplete; |
| BOOLEAN PosRdDqsToNegRdDqs; |
| BOOLEAN NegRdDqsToPosRdDqs; |
| MEM_NB_BLOCK *NBPtr; |
| NBPtr = TechPtr->NBPtr; |
| // |
| // Initialize Convolution |
| // |
| for (Lane = 0; Lane < MemT2DGetMaxLanes (TechPtr); Lane++) { |
| for (RdDqsDly = 0; RdDqsDly < NBPtr->TotalRdDQSDlyRange; RdDqsDly++) { |
| Data->Lane[Lane].Convolution[RdDqsDly] = 0; |
| NBPtr->FamilySpecificHook[Adjust2DDelayStepSize] (NBPtr, &RdDqsDly); |
| } |
| } |
| 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 = MemGet2dRdDQSWidth (TechPtr, 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 < MemT2DGetMaxLanes (TechPtr); Lane++) { |
| Height = Data->Lane[Lane].HalfDiamondHeight; |
| ASSERT (Height < Data->Vnom); |
| // |
| // RdDqsDly is divided around "Data->MaxRdDqsSweep" into positive and negative directions |
| // Positive direction -> RdDqsDly = 0 to (Data->MaxRdDqsSweep - 1) |
| // Negative direction -> RdDqsDly = Data->MaxRdDqsSweep to (NBPtr->TotalRdDQSDlyRange - 1) |
| // |
| for (RdDqsDly = 0; RdDqsDly < NBPtr->TotalRdDQSDlyRange; RdDqsDly++) { |
| // 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; |
| PosRdDqsToNegRdDqs = FALSE; |
| NegRdDqsToPosRdDqs = 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 (RdDqsDly > (Data->MaxRdDqsSweep - 1)) { |
| Dly = RdDqsDly - Data->MaxRdDqsSweep; |
| } else { |
| Dly = RdDqsDly; |
| } |
| 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->MaxRdDqsSweep - 1)) { |
| // Case 2: if +RdDqs - Check for upper bound ((Data->MaxRdDqsSweep - 1) < RdDqs < ((Data->MaxRdDqsSweep - 1) - Width/2)) |
| // : if -RdDqs - Check for lower bound ((DatNBPtra->TotalRdDQSDlyRange - 1) < RdDqs < ((NBPtr->TotalRdDQSDlyRange - 1) - Width/2)) |
| endWidth = Data->MaxRdDqsSweep; |
| 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 (RdDqsDly == (NBPtr->TotalRdDQSDlyRange - 1)) { |
| // Special condition for end of -RdDqs range |
| startWidth = Data->MaxRdDqsSweep - 1; |
| endWidth = Data->MaxRdDqsSweep; |
| } else { |
| // Width = 0, but Height not reached, |
| startWidth = Dly; |
| endWidth = Dly + 1; |
| } |
| } else { |
| // Check for Case 1 and Case 2 above |
| if ((RdDqsDly + Width / 2) > (NBPtr->TotalRdDQSDlyRange - 1)) { |
| endWidth = origEndWidth; |
| } |
| } |
| maxHeightExceeded = FALSE; |
| } |
| IDS_HDT_CONSOLE_DEBUG_CODE ( |
| if (Lane == 0) { |
| if (RdDqsDly == (Data->MaxRdDqsSweep - (Width / 2)) ) { |
| Data->DiamondLeft[Vref] = startWidth; |
| Data->DiamondRight[Vref] = endWidth - 1; |
| } |
| } |
| ); |
| // |
| // Determine the correct RdDqs (+/-) and Vref (+/-)direction |
| // |
| if (maxHeightExceeded == FALSE) { |
| if (RdDqsDly < Data->MaxRdDqsSweep) { |
| if (Vref > (Data->Vnom - 1)) { |
| PosNegData = Data->Lane[Lane].Vref[Vref].PosRdDqsDly; // +RdDqs Dly, +Vref |
| } else { |
| PosNegData = Data->Lane[Lane].Vref[(Data->Vnom - 1) - Vref].PosRdDqsDly; // +RdDqs Dly, -Vref |
| } |
| } else { |
| if (Vref > (Data->Vnom - 1)) { |
| PosNegData = Data->Lane[Lane].Vref[Vref].NegRdDqsDly; // -RdDqs Dly, +Vref |
| } else { |
| PosNegData = Data->Lane[Lane].Vref[(Data->Vnom - 1) - Vref].NegRdDqsDly; // -RdDqs 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[RdDqsDly] = (UINT8) ((PosNegData >> count) & 0x1) + Data->Lane[Lane].Convolution[RdDqsDly]; |
| } |
| // Case 2: Overlay between +RdDqs and -RdDqs starting from +RdDqs |
| // Count the number of passes from "startWidth" to "endWidth" |
| // |
| if ((RdDqsDly <= (Data->MaxRdDqsSweep - 1) && (RdDqsDly > ((Data->MaxRdDqsSweep - 1) - Width / 2)))) { |
| startOverLapWidth = 0; |
| if (Vref == 0 || Vref == Data->Vnom) { |
| maxOverLapWidth = (RdDqsDly + Width / 2) - (Data->MaxRdDqsSweep - 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].NegRdDqsDly; |
| } else { |
| PosNegData = Data->Lane[Lane].Vref[(Data->Vnom - 1) - Vref].NegRdDqsDly; |
| } |
| // 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[RdDqsDly] = (UINT8) ((PosNegData >> count) & 0x1) + Data->Lane[Lane].Convolution[RdDqsDly]; |
| } |
| if (maxOverLapWidth > 0) { |
| if (MemCheck2dRdDQSDiamondMaskStep (TechPtr, Data, Vref, Lane) || (Vref == 1) || (Vref == Data->Vnom)) { |
| maxOverLapWidth--; // Reduce overlap width outside of diamond mask |
| } |
| PosRdDqsToNegRdDqs = TRUE; |
| } |
| } |
| if (((RdDqsDly - Data->MaxRdDqsSweep) < Width / 2) && (RdDqsDly > (Data->MaxRdDqsSweep - 1))) { |
| // |
| // Case 3: Overlay between -RdDqs and +RdDqs starting from -RdDqs |
| // Count the number of passes from "startWidth" to "endWidth" |
| // |
| maxOverLapWidth = Data->MaxRdDqsSweep; |
| if (Vref == 0 || Vref == Data->Vnom) { |
| startOverLapWidth = RdDqsDly - 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].PosRdDqsDly; |
| } else { |
| PosNegData = Data->Lane[Lane].Vref[(Data->Vnom - 1) - Vref].PosRdDqsDly; |
| } |
| // 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[RdDqsDly] = (UINT8) ((PosNegData >> count) & 0x1) + Data->Lane[Lane].Convolution[RdDqsDly]; |
| } |
| if (startOverLapWidth < maxOverLapWidth) { |
| if (MemCheck2dRdDQSDiamondMaskStep (TechPtr, Data, Vref, Lane) || (Vref == 1) || (Vref == Data->Vnom)) { |
| startOverLapWidth++; // Reduce overlap width outside of diamond mask |
| } |
| NegRdDqsToPosRdDqs = TRUE; |
| } |
| } |
| } |
| if (MemCheck2dRdDQSDiamondMaskStep (TechPtr, Data, Vref, Lane) || (Vref == 1) || (Vref == Data->Vnom)) { |
| if (PosRdDqsToNegRdDqs) { |
| startWidth++; |
| endWidth = Data->MaxRdDqsSweep; |
| PosRdDqsToNegRdDqs = FALSE; |
| } else if (NegRdDqsToPosRdDqs) { |
| startWidth = 0; |
| endWidth--; |
| NegRdDqsToPosRdDqs = FALSE; |
| } else { |
| startWidth++; |
| endWidth--; |
| } |
| } |
| NBPtr->FamilySpecificHook[Adjust2DVrefStepSize] (NBPtr, &Vref); |
| } |
| NBPtr->FamilySpecificHook[Adjust2DDelayStepSize] (NBPtr, &RdDqsDly); |
| } |
| } |
| 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 (RdDqsDly = (Data->MaxRdDqsSweep - Width); RdDqsDly < Data->MaxRdDqsSweep; RdDqsDly++) { |
| if (Vref < (Data->Vnom - 1)) { |
| if (RdDqsDly == Data->DiamondLeft[(NBPtr->TotalMaxVrefRange - 2) - Vref]) { |
| IDS_HDT_CONSOLE (MEM_FLOW, " | "); |
| } else if (RdDqsDly == 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 (RdDqsDly == Data->DiamondLeft[Vref - (Data->Vnom - 1)]) { |
| IDS_HDT_CONSOLE (MEM_FLOW, " | "); |
| } else if (RdDqsDly == 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 (RdDqsDly = 0; RdDqsDly < NBPtr->TotalRdDQSDlyRange; RdDqsDly++) { |
| IDS_HDT_CONSOLE (MEM_FLOW, " %02x ", RdDqsDly <= (Data->MaxRdDqsSweep - 1) ? (Data->MaxRdDqsSweep - 1) - RdDqsDly : (NBPtr->TotalRdDQSDlyRange - 1) - RdDqsDly); |
| } |
| IDS_HDT_CONSOLE (MEM_FLOW, "\n"); |
| for (Lane = 0; Lane < MemT2DGetMaxLanes (TechPtr); Lane++) { |
| IDS_HDT_CONSOLE (MEM_FLOW, "\t\tLane: %02x\n", Lane); |
| IDS_HDT_CONSOLE (MEM_FLOW, "\t\t\tConv: "); |
| for (RdDqsDly = 0; RdDqsDly < NBPtr->TotalRdDQSDlyRange; RdDqsDly++) { |
| IDS_HDT_CONSOLE (MEM_FLOW, "%03x ", Data->Lane[Lane].Convolution[RdDqsDly]); |
| } |
| IDS_HDT_CONSOLE (MEM_FLOW, "\n"); |
| } |
| ); |
| return TRUE; |
| } |
| /* -----------------------------------------------------------------------------*/ |
| /** |
| * |
| * This function Examines the convolution function and determines the Max RDqs for |
| * 2D RdDQS training |
| * |
| * @param[in,out] *TechPtr - Pointer to the MEM_TECH_BLOCK |
| * @param[in] *Data - Pointer to Result data structure |
| * |
| * @return TRUE - No Errors occurred |
| * @return FALSE - Errors occurred |
| */ |
| |
| BOOLEAN |
| MemT2DRdDQSProcessConvolution ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN MEM_2D_ENTRY *Data |
| ) |
| { |
| UINT8 RdDqsDly; |
| UINT8 Lane; |
| UINT16 MaxFOM; |
| UINT8 MaxRange; |
| UINT8 CurrRange; |
| MEM_NB_BLOCK *NBPtr; |
| BOOLEAN status; |
| |
| NBPtr = TechPtr->NBPtr; |
| status = TRUE; |
| IDS_HDT_CONSOLE (MEM_FLOW, "\n\t\tDetermining MaxRdDqs based on Convolution function\n"); |
| // Determine the MaxRdDqs Dly for the convolution function |
| // - Choose the delay setting at the peak FOM value. |
| for (Lane = 0; Lane < MemT2DGetMaxLanes (TechPtr); Lane++) { |
| // Find largest value as MaxFOM |
| MaxFOM = 0; |
| for (RdDqsDly = 0; RdDqsDly < NBPtr->TotalRdDQSDlyRange; RdDqsDly++) { |
| if (Data->Lane[Lane].Convolution[RdDqsDly] > MaxFOM) { |
| MaxFOM = Data->Lane[Lane].Convolution[RdDqsDly]; |
| } |
| } |
| 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 (RdDqsDly = 0; (MaxFOM > 0) && RdDqsDly < NBPtr->TotalRdDQSDlyRange; RdDqsDly++) { |
| if (Data->Lane[Lane].Convolution[RdDqsDly] == MaxFOM) { |
| CurrRange++; |
| if (CurrRange >= MaxRange) { |
| Data->Lane[Lane].MaxRdDqs = RdDqsDly - ((CurrRange - 1) / 2); |
| MaxRange = CurrRange; |
| } |
| } else { |
| CurrRange = 0; |
| } |
| } |
| |
| if (Data->Lane[Lane].MaxRdDqs > Data->MaxRdDqsSweep) { |
| status = FALSE; // Error |
| } |
| // Set Actual register value |
| if (Data->Lane[Lane].MaxRdDqs < Data->MaxRdDqsSweep) { |
| Data->Lane[Lane].MaxRdDqs = (Data->MaxRdDqsSweep - 1) - Data->Lane[Lane].MaxRdDqs; |
| } else { |
| status = FALSE; // Error |
| } |
| } |
| |
| IDS_HDT_CONSOLE_DEBUG_CODE ( |
| IDS_HDT_CONSOLE (MEM_FLOW, "\n\t\t\t Lane: "); |
| for (Lane = 0; Lane < MemT2DGetMaxLanes (TechPtr); Lane++) { |
| IDS_HDT_CONSOLE (MEM_FLOW, "%02x ", Lane); |
| } |
| IDS_HDT_CONSOLE (MEM_FLOW, "\n"); |
| IDS_HDT_CONSOLE (MEM_FLOW, "\t\t\tMaxRdDqs: "); |
| for (Lane = 0; Lane < MemT2DGetMaxLanes (TechPtr); Lane++) { |
| IDS_HDT_CONSOLE (MEM_FLOW, "%02x ", Data->Lane[Lane].MaxRdDqs); |
| } |
| 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 RDqs for 2D RdDQS training from convolution |
| * |
| * @param[in,out] *TechPtr - Pointer to the MEM_TECH_BLOCK |
| * @param[in] *Data - Pointer to Result data structure |
| * |
| * @return TRUE - No Errors occurred |
| * @return FALSE - Errors occurred |
| */ |
| BOOLEAN |
| MemT2DRdDQSPrograMaxRdDQSDly ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN MEM_2D_ENTRY *Data |
| ) |
| { |
| UINT8 Lane; |
| UINT8 LaneHighRdDqs2dDlys; |
| UINT8 LaneLowRdDqs2dDlys; |
| IDS_HDT_CONSOLE (MEM_FLOW, "\n\t\tProgramming MaxRdDysDly per Lane\n\n"); |
| for (Lane = 0; Lane < MemT2DGetMaxLanes (TechPtr); Lane++) { |
| if ((TechPtr->NBPtr->ChannelPtr->DimmNibbleAccess & (1 << (TechPtr->ChipSel >> 1))) == 0) { |
| // Program Byte based for x8 and x16 |
| TechPtr->NBPtr->SetTrainDly (TechPtr->NBPtr, AccessRdDqsDly, DIMM_BYTE_ACCESS ((TechPtr->ChipSel / TechPtr->NBPtr->CsPerDelay), Lane), (UINT16)Data->Lane[Lane].MaxRdDqs); |
| TechPtr->NBPtr->ChannelPtr->RdDqsDlys[(TechPtr->ChipSel / TechPtr->NBPtr->CsPerDelay) * MAX_DELAYS + Lane] = Data->Lane[Lane].MaxRdDqs; |
| } else { |
| // Program nibble based x4, so use "Nibble" |
| TechPtr->NBPtr->SetTrainDly (TechPtr->NBPtr, AccessRdDqs2dDly, DIMM_NBBL_ACCESS ((TechPtr->ChipSel / TechPtr->NBPtr->CsPerDelay), Lane), (UINT16)Data->Lane[Lane].MaxRdDqs); |
| TechPtr->NBPtr->ChannelPtr->RdDqs2dDlys[(TechPtr->ChipSel / TechPtr->NBPtr->CsPerDelay) * MAX_NUMBER_LANES + Lane] = Data->Lane[Lane].MaxRdDqs; |
| // 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].MaxRdDqs; |
| LaneLowRdDqs2dDlys = Data->Lane[Lane].MaxRdDqs; |
| if (LaneHighRdDqs2dDlys > LaneLowRdDqs2dDlys) { |
| TechPtr->NBPtr->ChannelPtr->RdDqsDlys[(TechPtr->ChipSel / TechPtr->NBPtr->CsPerDelay) * MAX_DELAYS + (Lane >> 1)] = LaneHighRdDqs2dDlys; |
| } else { |
| TechPtr->NBPtr->ChannelPtr->RdDqsDlys[(TechPtr->ChipSel / TechPtr->NBPtr->CsPerDelay) * MAX_DELAYS + (Lane >> 1)] = LaneLowRdDqs2dDlys; |
| } |
| } |
| TechPtr->NBPtr->DctCachePtr->Is2Dx4 = TRUE; |
| } |
| } |
| return TRUE; |
| } |
| /* -----------------------------------------------------------------------------*/ |
| /** |
| * |
| * This function finds the Positive and negative Vref Margin for the current CS |
| * for 2D RdDQS training |
| * |
| * @param[in,out] *TechPtr - Pointer to the MEM_TECH_BLOCK |
| * @param[in] *Data - Pointer to Result data structure |
| * |
| * @return TRUE - No Errors occurred |
| * @return FALSE - Errors occurred |
| */ |
| BOOLEAN |
| MemT2DRdDQSFindCsVrefMargin ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN MEM_2D_ENTRY *Data |
| ) |
| { |
| UINT8 SmallestMaxVrefNeg; |
| UINT8 Lane; |
| UINT8 RdDqsDly; |
| 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 < MemT2DGetMaxLanes (TechPtr); Lane++) { |
| RdDqsDly = (Data->MaxRdDqsSweep - 1) - Data->Lane[Lane].MaxRdDqs; |
| for (Vref = 0; Vref < (Data->Vnom - 1); Vref++) { |
| // Neg Vref - (searching from top of array down) |
| PosNegData = Data->Lane[Lane].Vref[Vref].PosRdDqsDly; |
| if ((UINT8) ((PosNegData >> RdDqsDly) & 0x1) == 1) { |
| MaxVrefNegative = Vref; |
| break; |
| } |
| TechPtr->NBPtr->FamilySpecificHook[Adjust2DVrefStepSize] (TechPtr->NBPtr, &Vref); |
| } |
| for (Vref = (Data->Vnom - 1); Vref < (TechPtr->NBPtr->TotalMaxVrefRange - 1); Vref++) { |
| // Pos Vref - (searching from Vnom + 1 of array down) |
| PosNegData = Data->Lane[Lane].Vref[Vref].PosRdDqsDly; |
| if ((UINT8) ((PosNegData >> RdDqsDly) & 0x1) == 0) { |
| // Convert to register setting |
| MaxVrefPositive = Vref - 1;// - Data->Vnom; |
| break; |
| } else { |
| // If Vref = 1F passes, then smallest Vref = 0x1F |
| if (Vref == ((TechPtr->NBPtr->TotalMaxVrefRange - 1) - 1)) { |
| MaxVrefPositive = 0x1E; |
| break; |
| } |
| } |
| TechPtr->NBPtr->FamilySpecificHook[Adjust2DVrefStepSize] (TechPtr->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[TechPtr->ChipSel] = SmallestMaxVrefPos - Data->Vnom + 1; |
| } else { |
| Data->SmallestPosMaxVrefperCS[TechPtr->ChipSel] = 0; |
| } |
| Data->SmallestNegMaxVrefperCS[TechPtr->ChipSel] = (Data->Vnom - 1) - SmallestMaxVrefNeg; |
| IDS_HDT_CONSOLE_DEBUG_CODE ( |
| IDS_HDT_CONSOLE (MEM_FLOW, "Smallest Max Positive Vref Offset from V-Nom for ChipSel %02x = + %02x\n", TechPtr->ChipSel, Data->SmallestPosMaxVrefperCS[TechPtr->ChipSel]); |
| if (Data->SmallestPosMaxVrefperCS[TechPtr->ChipSel] == 0) { |
| IDS_HDT_CONSOLE (MEM_FLOW, "Smallest Max Negative Vref Offset from V-Nom for ChipSel %02x = 00\n"); |
| } else { |
| IDS_HDT_CONSOLE (MEM_FLOW, "Smallest Max Negative Vref Offset from V-Nom for ChipSel %02x = - %02x\n", TechPtr->ChipSel, Data->SmallestNegMaxVrefperCS[TechPtr->ChipSel]); |
| } |
| ); |
| return TRUE; |
| } |
| /* -----------------------------------------------------------------------------*/ |
| /** |
| * |
| * This function finds the final Vref Margin for 2D RdDQS training |
| * |
| * @param[in,out] *TechPtr - Pointer to the MEM_TECH_BLOCK |
| * @param[in] *Data - Pointer to Result data structure |
| * |
| * @return TRUE - No Errors occurred |
| * @return FALSE - Errors occurred |
| */ |
| BOOLEAN |
| MemT2DRdDQSFinalVrefMargin ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN MEM_2D_ENTRY *Data |
| ) |
| { |
| UINT8 ChipSel; |
| UINT8 SmallestMaxPosVref; |
| UINT8 SmallestMaxNegVref; |
| UINT8 OffsetFromVref; |
| UINT8 MaxRegVref; |
| SmallestMaxNegVref = 0x7F; |
| SmallestMaxPosVref = 0x7F; |
| IDS_HDT_CONSOLE (MEM_FLOW, "\n\t\tFinding Final Vref for channel\n\n"); |
| for (ChipSel = 0; ChipSel < TechPtr->NBPtr->CsPerChannel; ChipSel = ChipSel + TechPtr->NBPtr->CsPerDelay ) { |
| if ( (TechPtr->NBPtr->MCTPtr->Status[SbLrdimms]) ? ((TechPtr->NBPtr->ChannelPtr->LrDimmPresent & ((UINT8) 1 << (ChipSel >> 1))) != 0) : |
| ((TechPtr->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]; |
| } |
| } |
| } |
| IDS_HDT_CONSOLE (MEM_FLOW, "Final Max Vref Offset From Vnom ="); |
| if (SmallestMaxPosVref > SmallestMaxNegVref) { |
| OffsetFromVref = (SmallestMaxPosVref - SmallestMaxNegVref) / 2; |
| if (OffsetFromVref != 0) { |
| IDS_HDT_CONSOLE (MEM_FLOW, " + "); |
| } |
| TechPtr->NBPtr->ChannelPtr->MaxVref = (Data->Vnom - 1) + OffsetFromVref; |
| } else { |
| OffsetFromVref = (SmallestMaxNegVref - SmallestMaxPosVref) / 2; |
| if (OffsetFromVref != 0) { |
| IDS_HDT_CONSOLE (MEM_FLOW, " - "); |
| } |
| TechPtr->NBPtr->ChannelPtr->MaxVref = (Data->Vnom - 1) - OffsetFromVref; |
| } |
| IDS_HDT_CONSOLE (MEM_FLOW, "%02x \n", OffsetFromVref); |
| MaxRegVref = TechPtr->NBPtr->ChannelPtr->MaxVref; |
| if (MaxRegVref <= ((TechPtr->NBPtr->TotalMaxVrefRange / 2) - 1)) { |
| MaxRegVref = (TechPtr->NBPtr->TotalMaxVrefRange - 1) - MaxRegVref; |
| } else { |
| MaxRegVref = MaxRegVref - (TechPtr->NBPtr->TotalMaxVrefRange / 2 - 1); |
| } |
| /// @todo: Need Family specific hook for MaxRegVref |
| MaxRegVref = ((MaxRegVref * 2) & 0x3F); |
| IDS_HDT_CONSOLE (MEM_FLOW, "Actual Max Vref programmed = %02x \n", MaxRegVref); |
| return TRUE; |
| } |
| /* -----------------------------------------------------------------------------*/ |
| /** |
| * |
| * This function displays ther results of the 2D search |
| * |
| * @param[in,out] *TechPtr - Pointer to the MEM_TECH_BLOCK |
| * @param[in] *Data - Pointer to Result data structure |
| * |
| * @return |
| */ |
| VOID |
| MemT2DRdDqsDisplaySearch ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN MEM_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 < MemT2DGetMaxLanes (TechPtr); Lane++) { |
| IDS_HDT_CONSOLE (MEM_FLOW, "\t\t\tLane: %02x\n", Lane); |
| IDS_HDT_CONSOLE (MEM_FLOW, "\t\t\t Vref NegRdDqs PosRdDqs\n"); |
| for (Vref = TechPtr->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)); |
| } |
| IDS_HDT_CONSOLE (MEM_FLOW, "%08x", TechPtr->Local2DData->Lane[Lane].Vref[Vref].NegRdDqsDly); // debug |
| IDS_HDT_CONSOLE (MEM_FLOW, "%08x \n", TechPtr->Local2DData->Lane[Lane].Vref[Vref].PosRdDqsDly); //debug |
| TechPtr->NBPtr->FamilySpecificHook[Adjust2DVrefStepSize] (TechPtr->NBPtr, &Vref); |
| } |
| } |
| ) |
| } |