| /* $NoKeywords:$ */ |
| /** |
| * @file |
| * |
| * mttEdgeDetect.c |
| * |
| * DQS R/W position training utilizing Data Eye Edge Detection for optimization |
| * |
| * @xrefitem bom "File Content Label" "Release Content" |
| * @e project: AGESA |
| * @e sub-project: (Mem/Tech) |
| * @e \$Revision: 63425 $ @e \$Date: 2011-12-22 11:24:10 -0600 (Thu, 22 Dec 2011) $ |
| * |
| **/ |
| /***************************************************************************** |
| * |
| * Copyright (c) 2008 - 2012, 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 "mttEdgeDetect.h" |
| #include "OptionMemory.h" |
| #include "merrhdl.h" |
| #include "Filecode.h" |
| CODE_GROUP (G1_PEICC) |
| RDATA_GROUP (G1_PEICC) |
| |
| #define FILECODE PROC_MEM_TECH_MTTEDGEDETECT_FILECODE |
| /*---------------------------------------------------------------------------- |
| * DEFINITIONS AND MACROS |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| |
| |
| #define LAST_DELAY (-128) |
| #define INC_DELAY 1 |
| #define DEC_DELAY 0 |
| |
| |
| |
| /*---------------------------------------------------------------------------- |
| * TYPEDEFS AND STRUCTURES |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| |
| /*---------------------------------------------------------------------------- |
| * PROTOTYPES OF LOCAL FUNCTIONS |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| |
| /** |
| * Sweep Table For Byte Training without insertion delay |
| * |
| */ |
| DQS_POS_SWEEP_TABLE SweepTableByte[] = |
| { |
| // Begin End Inc/Dec Step EndResult Edge |
| { 0x00, 0x1F, INC_DELAY, 4, 0xFFFF, LEFT_EDGE}, /// For Left Edge, start from 0 and Increment to 0x1F by 4 until all PASS |
| { LAST_DELAY, 0x00, DEC_DELAY, -1, 0xFE00, LEFT_EDGE}, /// Then go back down to 0x00 by 1 until all FAIL |
| { 0x1F, 0x00, DEC_DELAY, -4, 0xFFFF, RIGHT_EDGE}, /// For Right Edge, start from 0x1F down to 0 until all PASS. |
| { LAST_DELAY, 0x1F, INC_DELAY, 1, 0xFE00, RIGHT_EDGE} /// Then go back up by 1 until all FAIL. |
| }; |
| /** |
| * Sweep Table For Byte Training with insertion delay |
| * |
| */ |
| DQS_POS_SWEEP_TABLE InsSweepTableByte[] = |
| { |
| // Begin End Inc/Dec Step EndResult Edge |
| { 0x00, -0x20, DEC_DELAY, -4, 0xFE00, LEFT_EDGE}, /// For Left Edge, start from 0 and Decrement to -0x20 by -4 until all FAIL |
| { LAST_DELAY, 0x1F, INC_DELAY, 1, 0xFFFF, LEFT_EDGE}, /// Then go back up to 0x1F by 1 until all PASS |
| { 0x1F, 0x00, DEC_DELAY, -4, 0xFFFF, RIGHT_EDGE}, /// For Right Edge, start from 0x1F down to 0 until all PASS. |
| { LAST_DELAY, 0x1F, INC_DELAY, 1, 0xFE00, RIGHT_EDGE} /// Then go back up by 1 until all FAIL. |
| }; |
| |
| BOOLEAN |
| STATIC |
| MemTTrainDQSRdWrEdgeDetect ( |
| IN OUT MEM_TECH_BLOCK *TechPtr |
| ); |
| |
| BOOLEAN |
| STATIC |
| MemTInitTestPatternAddress ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN OUT SWEEP_INFO *SweepPtr |
| ); |
| |
| BOOLEAN |
| STATIC |
| MemTContinueSweep ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN OUT SWEEP_INFO *SweepPtr |
| ); |
| |
| BOOLEAN |
| STATIC |
| MemTSetNextDelay ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN OUT SWEEP_INFO *SweepPtr |
| ); |
| |
| UINT8 |
| STATIC |
| MemTScaleDelayVal ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN INT8 Delay |
| ); |
| |
| BOOLEAN |
| STATIC |
| MemTDataEyeSave ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN OUT SWEEP_INFO *SweepPtr, |
| IN UINT8 ByteLane |
| ); |
| |
| /*---------------------------------------------------------------------------- |
| * EXPORTED FUNCTIONS |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| extern MEM_FEAT_TRAIN_SEQ memTrainSequenceDDR3[]; |
| /* -----------------------------------------------------------------------------*/ |
| /** |
| * |
| * This function executes DQS position training for all a Memory channel using |
| * the Edge Detection algorithm. |
| * |
| * @param[in,out] *TechPtr - Pointer to the MEM_TECH_BLOCK |
| * |
| */ |
| |
| BOOLEAN |
| MemTTrainDQSEdgeDetectSw ( |
| IN OUT MEM_TECH_BLOCK *TechPtr |
| ) |
| { |
| MEM_NB_BLOCK *NBPtr; |
| BOOLEAN Status; |
| |
| Status = FALSE; |
| NBPtr = TechPtr->NBPtr; |
| TechPtr->TrainingType = TRN_DQS_POSITION; |
| // |
| // Initialize the Pattern |
| // |
| if (AGESA_SUCCESS == NBPtr->TrainingPatternInit (NBPtr)) { |
| // |
| // Setup hardware training engine (if applicable) |
| // |
| NBPtr->FamilySpecificHook[SetupHwTrainingEngine] (NBPtr, &TechPtr->TrainingType); |
| // |
| // Start Edge Detection |
| // |
| Status |= MemTTrainDQSRdWrEdgeDetect (TechPtr); |
| // |
| // Finalize the Pattern |
| // |
| Status &= (AGESA_SUCCESS == NBPtr->TrainingPatternFinalize (NBPtr)); |
| } |
| return Status; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * LOCAL FUNCTIONS |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| |
| /* -----------------------------------------------------------------------------*/ |
| /** |
| * |
| * This Executes Read DQS and Write Data Position training on a chip select pair |
| * using the Edge Detection algorithm. |
| * |
| * @param[in,out] *TechPtr - Pointer to the MEM_TECH_BLOCK |
| * |
| * @return TRUE - No Errors occurred |
| * @return FALSE - Errors occurred |
| |
| */ |
| |
| BOOLEAN |
| STATIC |
| MemTTrainDQSRdWrEdgeDetect ( |
| IN OUT MEM_TECH_BLOCK *TechPtr |
| ) |
| { |
| MEM_DATA_STRUCT *MemPtr; |
| MEM_NB_BLOCK *NBPtr; |
| UINT8 WrDqDelay; |
| UINT8 Dct; |
| UINT8 ChipSel; |
| UINT8 i; |
| BOOLEAN Status; |
| UINT8 TimesFail; |
| UINT8 TimesRetrain; |
| |
| NBPtr = TechPtr->NBPtr; |
| MemPtr = NBPtr->MemPtr; |
| TimesRetrain = DEFAULT_TRAINING_TIMES; |
| IDS_OPTION_HOOK (IDS_MEM_RETRAIN_TIMES, &TimesRetrain, &MemPtr->StdHeader); |
| // |
| // Set environment settings before training |
| // |
| IDS_HDT_CONSOLE (MEM_STATUS, "\nStart Read/Write Data Eye Edge Detection.\n"); |
| MemTBeginTraining (TechPtr); |
| // |
| // Do Rd DQS /Wr Data Position 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); |
| // |
| // Chip Select Loop |
| // |
| TechPtr->RestartChipSel = -1; |
| for (ChipSel = 0; ChipSel < NBPtr->CsPerChannel; ChipSel = ChipSel + NBPtr->CsPerDelay ) { |
| // |
| // Init Bit Error Masks |
| // |
| LibAmdMemFill (&NBPtr->ChannelPtr->FailingBitMask[ (ChipSel * MAX_BYTELANES_PER_CHANNEL) ], |
| 0xFF, |
| (MAX_BYTELANES_PER_CHANNEL * NBPtr->CsPerDelay), |
| &MemPtr->StdHeader); |
| if ( (NBPtr->MCTPtr->Status[SbLrdimms]) ? ((NBPtr->ChannelPtr->LrDimmPresent & ((UINT8) 1 << (ChipSel >> 1))) != 0) : |
| ((NBPtr->DCTPtr->Timings.CsEnabled & ((UINT16) 1 << ChipSel)) != 0) ) { |
| |
| TechPtr->ChipSel = ChipSel; |
| IDS_HDT_CONSOLE (MEM_STATUS, "\t\tCS %d\n", ChipSel); |
| IDS_HDT_CONSOLE (MEM_FLOW, "\t\t\tIncrease WrDat, Train RdDqs:\n"); |
| |
| TechPtr->DqsRdWrPosSaved = 0; |
| // |
| // Use a list of Approximate Write Data delay values and train Read DQS Position for |
| // each until a valid Data eye is found. |
| // |
| Status = FALSE; |
| TimesFail = 0; |
| NBPtr->FamilySpecificHook[InitializeRxEnSeedlessTraining] (NBPtr, NBPtr); |
| ERROR_HANDLE_RETRAIN_BEGIN (TimesFail, TimesRetrain) { |
| i = 0; |
| while (NBPtr->GetApproximateWriteDatDelay (NBPtr, i, &WrDqDelay)) { |
| TechPtr->SmallDqsPosWindow = FALSE; |
| // |
| // Set Write Delay approximation |
| // |
| TechPtr->Direction = DQS_WRITE_DIR; |
| IDS_HDT_CONSOLE (MEM_FLOW, "\n\t\t\tWrite Delay: %02x", WrDqDelay); |
| MemTSetDQSDelayAllCSR (TechPtr, WrDqDelay); |
| // |
| // Attempt Read Training |
| // |
| TechPtr->Direction = DQS_READ_DIR; |
| Status = memTrainSequenceDDR3[NBPtr->TrainingSequenceIndex].MemTechFeatBlock->RdPosTraining (TechPtr); |
| if (Status) { |
| // |
| // If Read DQS Training was successful, Train Write Data (DQ) Position |
| // |
| TechPtr->DqsRdWrPosSaved = 0; |
| IDS_HDT_CONSOLE (MEM_FLOW, "\n\t\t\tTrain WrDat:\n\n"); |
| TechPtr->Direction = DQS_WRITE_DIR; |
| if (NBPtr->FamilySpecificHook[BeforeWrDatTrn] (NBPtr, &ChipSel)) { |
| Status = MemTTrainDQSEdgeDetect (TechPtr); |
| } |
| break; |
| } |
| i++; |
| } |
| ERROR_HANDLE_RETRAIN_END ((Status == FALSE), TimesFail) |
| } |
| |
| // |
| // If we went through the table, Fail. |
| // |
| if (Status == FALSE) { |
| // On training failure, check and record whether training fails due to small window or no window |
| if (TechPtr->SmallDqsPosWindow) { |
| NBPtr->MCTPtr->ErrStatus[EsbSmallDqs] = TRUE; |
| } else { |
| NBPtr->MCTPtr->ErrStatus[EsbNoDqsPos] = TRUE; |
| } |
| |
| SetMemError (AGESA_ERROR, NBPtr->MCTPtr); |
| if (TechPtr->Direction == DQS_READ_DIR) { |
| PutEventLog (AGESA_ERROR, MEM_ERROR_NO_DQS_POS_RD_WINDOW, NBPtr->Node, NBPtr->Dct, NBPtr->Channel, 0, &NBPtr->MemPtr->StdHeader); |
| } else { |
| PutEventLog (AGESA_ERROR, MEM_ERROR_NO_DQS_POS_WR_WINDOW, NBPtr->Node, NBPtr->Dct, NBPtr->Channel, 0, &NBPtr->MemPtr->StdHeader); |
| } |
| NBPtr->DCTPtr->Timings.CsTrainFail |= (UINT16)1 << ChipSel; |
| // If the even chip select failed training always fail the odd, if present. |
| if (((ChipSel & 0x01) == 0) && (NBPtr->CsPerDelay == 2)) { |
| if (NBPtr->DCTPtr->Timings.CsPresent & ((UINT16)1 << (ChipSel + 1))) { |
| NBPtr->DCTPtr->Timings.CsTrainFail |= (UINT16)1 << (ChipSel + 1); |
| } |
| } |
| if (!NBPtr->MemPtr->ErrorHandling (NBPtr->MCTPtr, NBPtr->Dct, NBPtr->DCTPtr->Timings.CsTrainFail, &NBPtr->MemPtr->StdHeader)) { |
| ASSERT (FALSE); |
| } |
| } |
| } else { |
| // |
| // Clear Bit Error Masks if these CS will not be trained. |
| // |
| LibAmdMemFill (&NBPtr->ChannelPtr->FailingBitMask[ (ChipSel * MAX_BYTELANES_PER_CHANNEL) ], |
| 0x00, |
| (MAX_BYTELANES_PER_CHANNEL * NBPtr->CsPerDelay), |
| &NBPtr->MemPtr->StdHeader); |
| } |
| } |
| } |
| // |
| // Restore environment settings after training |
| // |
| MemTEndTraining (TechPtr); |
| IDS_HDT_CONSOLE (MEM_FLOW, "End Read/Write Data Eye Edge Detection\n\n"); |
| return (BOOLEAN) (NBPtr->MCTPtr->ErrCode < AGESA_FATAL); |
| } |
| |
| /* -----------------------------------------------------------------------------*/ |
| /** |
| * |
| * This function executes DQS position training for both read and write, using |
| * the Edge Detection Algorithm. This method searches for the beginning and end |
| * of the Data Eye with out scanning every DSQ delay value. The following is a |
| * detailed description of the algorithm: |
| * |
| * Four-Stage Data Eye Sweep |
| * |
| * -Search starts at Delay value of 0. |
| * -Search left in steps of 4/32UI looking for all Byte lanes Passing. Left from zero rolls over to a negative value. |
| * -Negative values are translated to the high end of the delay range, but using Insertion delay comparison. |
| * -For each passing byte lane, freeze delay at first passing value, but set mask so next steps will not compare for byte lanes that previously passed |
| * -Switch to search right in steps of 1/32UI looking for fail. |
| * -For each lane, starting delay for 1/32 sweep right is first passing delay from 4/32 sweep left. |
| * -For each failing byte lane, freeze delay at first failing value, but set mask so next steps will not compare for byte lanes that previously failed |
| * -Search right until all byte lanes have failed |
| * -For each lane, right edge used by BIOS will be first failing delay value minus 1/32 |
| |
| * |
| * @param[in,out] *TechPtr - Pointer to the MEM_TECH_BLOCK |
| * |
| * @return TRUE - All bytelanes pass |
| * @return FALSE - Some bytelanes fail |
| */ |
| BOOLEAN |
| MemTTrainDQSEdgeDetect ( |
| IN OUT MEM_TECH_BLOCK *TechPtr |
| ) |
| { |
| MEM_NB_BLOCK *NBPtr; |
| DIE_STRUCT *MCTPtr; |
| DQS_POS_SWEEP_TABLE *SweepTablePtr; |
| UINT8 SweepTableSize; |
| SWEEP_INFO SweepData; |
| BOOLEAN Status; |
| UINT16 CurrentResult; |
| UINT16 AlignedResult; |
| UINT16 OffsetResult; |
| UINT8 StageIndex; |
| UINT8 CsIndex; |
| UINT8 i; |
| |
| Status = TRUE; |
| // |
| // Initialize Object Pointers |
| // |
| NBPtr = TechPtr->NBPtr; |
| MCTPtr = NBPtr->MCTPtr; |
| // |
| // Initialize stack variables |
| // |
| LibAmdMemFill (&SweepData, 0, sizeof (SWEEP_INFO), &NBPtr->MemPtr->StdHeader); |
| // |
| /// Get Pointer to Sweep Table |
| // |
| if (TechPtr->Direction == DQS_READ_DIR) { |
| SweepTablePtr = InsSweepTableByte; |
| SweepTableSize = GET_SIZE_OF (InsSweepTableByte); |
| } else { |
| SweepTablePtr = SweepTableByte; |
| SweepTableSize = GET_SIZE_OF (SweepTableByte); |
| } |
| |
| // |
| /// Set up the test Pattern, exit if no Memory |
| // |
| if (MemTInitTestPatternAddress (TechPtr, &SweepData) == FALSE) { |
| LibAmdMemFill (&NBPtr->ChannelPtr->FailingBitMask[ (TechPtr->ChipSel * MAX_BYTELANES_PER_CHANNEL) ], |
| 0, |
| (MAX_BYTELANES_PER_CHANNEL * NBPtr->CsPerDelay), |
| &NBPtr->MemPtr->StdHeader); |
| return FALSE; |
| } |
| // |
| // Clear Error Flag |
| // |
| SweepData.Error = FALSE; |
| NBPtr->FamilySpecificHook[InitialzeRxEnSeedlessByteLaneError] (NBPtr, NBPtr); |
| // |
| /// Process Sweep table, using entries from the table to determine Starting and Ending Delays |
| /// as well as the Step size and criteria for evaluating whether the correct result is found. |
| /// |
| /// Delay values at this level are an abstract range of values which gets scaled to the actual value |
| /// before it is written to the hardware. This allows NB specific code to handle the scaling as a |
| /// function of frequency or other conditions. |
| // |
| for (StageIndex = 0; (StageIndex < SweepTableSize) && (SweepData.Error == FALSE); StageIndex++) { |
| |
| IDS_HDT_CONSOLE (MEM_FLOW, "\t\t\tSTAGE: %d\t", StageIndex); |
| // |
| /// Initialize SweepData variables |
| // |
| SweepData.BeginDelay = SweepTablePtr->BeginDelay; |
| SweepData.EndDelay = SweepTablePtr->EndDelay; |
| SweepData.Step = 0; /// Step Value will be 0 to start. |
| SweepData.EndResult = SweepTablePtr->EndResult; |
| if (!(MCTPtr->Status[SbEccDimms] && NBPtr->IsSupported[EccByteTraining])) { |
| SweepData.EndResult |= 0x0100; |
| } |
| SweepData.Edge = SweepTablePtr->MinMax; |
| SweepData.InsertionDelayMsk = 0; |
| SweepData.ResultFound = 0x0000; |
| // |
| // Set Training Delays Pointer. |
| // |
| if (TechPtr->Direction == DQS_READ_DIR) { |
| SweepData.TrnDelays = (INT8 *) ((SweepData.Edge == RIGHT_EDGE) ? NBPtr->ChannelPtr->RdDqsMaxDlys : NBPtr->ChannelPtr->RdDqsMinDlys); |
| } else { |
| SweepData.TrnDelays = (INT8 *) ((SweepData.Edge == RIGHT_EDGE) ? NBPtr->ChannelPtr->WrDatMaxDlys : NBPtr->ChannelPtr->WrDatMinDlys); |
| }; |
| // |
| /// Set initial TrnDelay Values if necessary |
| // |
| IDS_HDT_CONSOLE (MEM_FLOW, "Sweeping %s DQS, %s from ", (TechPtr->Direction == DQS_READ_DIR) ?"Read":"Write", (SweepTablePtr->ScanDir == INC_DELAY) ? "incrementing":"decrementing"); |
| if (SweepData.BeginDelay != LAST_DELAY) { |
| IDS_HDT_CONSOLE (MEM_FLOW, "%02x", (UINT16) MemTScaleDelayVal (TechPtr, SweepData.BeginDelay)); |
| for (i = 0; i < ((MCTPtr->Status[SbEccDimms] && NBPtr->IsSupported[EccByteTraining]) ? 9 : 8); i++) { |
| SweepData.TrnDelays[i] = SweepData.BeginDelay; |
| } |
| } else { |
| IDS_HDT_CONSOLE (MEM_FLOW, "Current Delay"); |
| SweepData.Step = SweepTablePtr->Step; |
| } |
| IDS_HDT_CONSOLE (MEM_FLOW, " by %02x, until all bytelanes %s.\n\n", (UINT16) MemTScaleDelayVal (TechPtr, ABS (SweepTablePtr->Step)), (SweepData.EndResult == 0xFFFF)?"PASS":"FAIL"); |
| |
| //------------------------------------------------------------------- |
| // Sweep DQS Delays |
| // MemTContinueSweep function returns false to break out of loop. |
| // There are no other breaks out of this loop. |
| //------------------------------------------------------------------- |
| while (MemTContinueSweep (TechPtr, &SweepData)) { |
| IDS_HDT_CONSOLE (MEM_FLOW, "\t\t\t\tByte Lane : 08 07 06 05 04 03 02 01 00\n"); |
| IDS_HDT_CONSOLE (MEM_FLOW, "\t\t\t\tDQS Delays : %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", |
| (UINT16) MemTScaleDelayVal (TechPtr, SweepData.TrnDelays[8]), |
| (UINT16) MemTScaleDelayVal (TechPtr, SweepData.TrnDelays[7]), (UINT16) MemTScaleDelayVal (TechPtr, SweepData.TrnDelays[6]), |
| (UINT16) MemTScaleDelayVal (TechPtr, SweepData.TrnDelays[5]), (UINT16) MemTScaleDelayVal (TechPtr, SweepData.TrnDelays[4]), |
| (UINT16) MemTScaleDelayVal (TechPtr, SweepData.TrnDelays[3]), (UINT16) MemTScaleDelayVal (TechPtr, SweepData.TrnDelays[2]), |
| (UINT16) MemTScaleDelayVal (TechPtr, SweepData.TrnDelays[1]), (UINT16) MemTScaleDelayVal (TechPtr, SweepData.TrnDelays[0]) |
| ); |
| // |
| /// Set Step Value |
| // |
| SweepData.Step = SweepTablePtr->Step; |
| CurrentResult = 0xFFFF; |
| // |
| /// Chip Select Loop: Test the Pattern for all populated CS that are controlled by the current delay registers |
| // |
| for (CsIndex = 0; CsIndex < NBPtr->CsPerDelay ; CsIndex++, TechPtr->ChipSel++) { |
| ASSERT (CsIndex < MAX_CS_PER_CHANNEL); |
| ASSERT (TechPtr->ChipSel < MAX_CS_PER_CHANNEL); |
| if (SweepData.CsAddrValid[CsIndex] == TRUE) { |
| // |
| /// If this is a Write Dqs sweep, Write the pattern now. |
| // |
| if (TechPtr->Direction == DQS_WRITE_DIR) { |
| NBPtr->WritePattern (NBPtr, SweepData.TestAddrRJ16[CsIndex], TechPtr->PatternBufPtr, TechPtr->PatternLength); |
| } |
| // |
| /// Read the Pattern Back |
| // |
| NBPtr->ReadPattern (NBPtr, TechPtr->TestBufPtr, SweepData.TestAddrRJ16[CsIndex], TechPtr->PatternLength); |
| // |
| /// Compare the Pattern and Merge the results using InsertionDelayMsk |
| // |
| AlignedResult = NBPtr->CompareTestPattern (NBPtr, TechPtr->TestBufPtr, TechPtr->PatternBufPtr, TechPtr->PatternLength * 64); |
| CurrentResult &= AlignedResult | SweepData.InsertionDelayMsk; |
| if (SweepData.InsertionDelayMsk != 0) { |
| OffsetResult = NBPtr->InsDlyCompareTestPattern (NBPtr, TechPtr->TestBufPtr, TechPtr->PatternBufPtr, TechPtr->PatternLength * 64); |
| CurrentResult &= (OffsetResult | (~SweepData.InsertionDelayMsk)); |
| } |
| // |
| /// Flush the Test Pattern |
| // |
| NBPtr->FlushPattern (NBPtr, SweepData.TestAddrRJ16[CsIndex], TechPtr->PatternLength); |
| NBPtr->FamilySpecificHook[ResetRxFifoPtr] (NBPtr, NBPtr); |
| } |
| } /// End Chip Select Loop |
| TechPtr->ChipSel = TechPtr->ChipSel - CsIndex; |
| IDS_HDT_CONSOLE (MEM_FLOW, "\t\t\t\tResult : %c %c %c %c %c %c %c %c %c \n", |
| (SweepData.ResultFound & ((UINT16) 1 << (8))) ? ' ':(CurrentResult & ((UINT16) 1 << (8))) ? 'P':'.', |
| (SweepData.ResultFound & ((UINT16) 1 << (7))) ? ' ':(CurrentResult & ((UINT16) 1 << (7))) ? 'P':'.', |
| (SweepData.ResultFound & ((UINT16) 1 << (6))) ? ' ':(CurrentResult & ((UINT16) 1 << (6))) ? 'P':'.', |
| (SweepData.ResultFound & ((UINT16) 1 << (5))) ? ' ':(CurrentResult & ((UINT16) 1 << (5))) ? 'P':'.', |
| (SweepData.ResultFound & ((UINT16) 1 << (4))) ? ' ':(CurrentResult & ((UINT16) 1 << (4))) ? 'P':'.', |
| (SweepData.ResultFound & ((UINT16) 1 << (3))) ? ' ':(CurrentResult & ((UINT16) 1 << (3))) ? 'P':'.', |
| (SweepData.ResultFound & ((UINT16) 1 << (2))) ? ' ':(CurrentResult & ((UINT16) 1 << (2))) ? 'P':'.', |
| (SweepData.ResultFound & ((UINT16) 1 << (1))) ? ' ':(CurrentResult & ((UINT16) 1 << (1))) ? 'P':'.', |
| (SweepData.ResultFound & ((UINT16) 1 << (0))) ? ' ':(CurrentResult & ((UINT16) 1 << (0))) ? 'P':'.' |
| ); |
| // |
| /// Merge current result into cumulative result and make it positive. |
| // |
| SweepData.ResultFound |= ~(CurrentResult ^ SweepData.EndResult); |
| |
| IDS_HDT_CONSOLE (MEM_FLOW, "\t\t\t\tResultFound : %c %c %c %c %c %c %c %c %c \n\n", |
| (SweepData.ResultFound & ((UINT16) 1 << (8))) ? 'Y':' ', |
| (SweepData.ResultFound & ((UINT16) 1 << (7))) ? 'Y':' ', |
| (SweepData.ResultFound & ((UINT16) 1 << (6))) ? 'Y':' ', |
| (SweepData.ResultFound & ((UINT16) 1 << (5))) ? 'Y':' ', |
| (SweepData.ResultFound & ((UINT16) 1 << (4))) ? 'Y':' ', |
| (SweepData.ResultFound & ((UINT16) 1 << (3))) ? 'Y':' ', |
| (SweepData.ResultFound & ((UINT16) 1 << (2))) ? 'Y':' ', |
| (SweepData.ResultFound & ((UINT16) 1 << (1))) ? 'Y':' ', |
| (SweepData.ResultFound & ((UINT16) 1 << (0))) ? 'Y':' ' |
| ); |
| } /// End of Delay Sweep |
| // |
| /// Place Final delay values at last passing delay. |
| // |
| if (SweepData.ResultFound == 0xFFFF) { |
| if ( ABS (SweepData.Step) == 1) { |
| for (i = 0; i < ((MCTPtr->Status[SbEccDimms] && NBPtr->IsSupported[EccByteTraining]) ? 9 : 8) ; i++) { |
| if ((SweepData.EndResult & ((UINT16) (1 << i))) == 0) { |
| SweepData.TrnDelays[i] = SweepData.TrnDelays[i] - SweepData.Step; |
| } |
| } |
| } |
| } |
| // |
| // Update Pointer to Sweep Table |
| // |
| SweepTablePtr++; |
| }///End of Edge Detect loop |
| // |
| /// If No Errors are detected, Calculate Data Eye Width and Center |
| // |
| if (SweepData.Error == FALSE) { |
| IDS_HDT_CONSOLE (MEM_FLOW, "\t\tData Eye Results:\n\n"); |
| IDS_HDT_CONSOLE (MEM_FLOW, "\t\tByte Left Right\n"); |
| IDS_HDT_CONSOLE (MEM_FLOW, "\t\tLane Edge Edge Width Center\n"); |
| for (i = 0; i < ((MCTPtr->Status[SbEccDimms] && NBPtr->IsSupported[EccByteTraining]) ? 9 : 8) ; i++) { |
| IDS_HDT_CONSOLE (MEM_FLOW, "\t\t %0d", i); |
| TechPtr->Bytelane = i; |
| if (!MemTDataEyeSave (TechPtr, &SweepData, i)) { |
| break; |
| } |
| IDS_HDT_CONSOLE (MEM_FLOW, "\n"); |
| if (SweepData.Error == TRUE) { |
| Status = FALSE; |
| } |
| } |
| } else { |
| Status = FALSE; |
| IDS_HDT_CONSOLE (MEM_FLOW, "\t\t--DATA EYE NOT FOUND--\n\n"); |
| NBPtr->FamilySpecificHook[TrackRxEnSeedlessRdWrNoWindBLError] (NBPtr, &SweepData); |
| } |
| return Status; |
| } |
| |
| /* -----------------------------------------------------------------------------*/ |
| /** |
| * |
| * Initialize the Test Pattern Address for two chip selects and, if this |
| * is a Write Data Eye, write the initial test pattern. |
| * |
| * Test Address is stored in the Sweep info struct. If Memory is not present |
| * then return with False. |
| * |
| * @param[in,out] *TechPtr - Pointer to the MEM_TECH_BLOCK |
| * @param[in,out] *SweepPtr - Pointer to SWEEP_INFO structure. |
| * |
| * @return BOOLEAN |
| * TRUE - Memory is present |
| * FALSE - No memory present on this Chip Select pair. |
| * |
| ** |
| */ |
| BOOLEAN |
| STATIC |
| MemTInitTestPatternAddress ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN OUT SWEEP_INFO *SweepPtr |
| ) |
| { |
| MEM_NB_BLOCK *NBPtr; |
| UINT8 ChipSel; |
| UINT8 CsIndex; |
| BOOLEAN BanksPresent; |
| |
| NBPtr = TechPtr->NBPtr; |
| BanksPresent = FALSE; |
| ChipSel = TechPtr->ChipSel; |
| for (CsIndex = 0; CsIndex < NBPtr->CsPerDelay; ChipSel++, CsIndex++, TechPtr->ChipSel++) { |
| ASSERT (CsIndex < MAX_CS_PER_CHANNEL); |
| ASSERT (ChipSel < MAX_CS_PER_CHANNEL); |
| ASSERT (TechPtr->ChipSel < MAX_CS_PER_CHANNEL); |
| // |
| /// If memory is present on this cs, get the test addr |
| // |
| if (NBPtr->GetSysAddr (NBPtr, ChipSel, &(SweepPtr->TestAddrRJ16[CsIndex]))) { |
| if (!(NBPtr->MCTPtr->Status[SbLrdimms]) || ((NBPtr->ChannelPtr->LrDimmPresent & ((UINT8) 1 << (ChipSel >> 1))) != 0)) { |
| BanksPresent = TRUE; |
| SweepPtr->CsAddrValid[CsIndex] = TRUE; |
| // |
| /// If this is a Read Dqs sweep, Write the pattern now. |
| // |
| if (TechPtr->Direction == DQS_READ_DIR) { |
| IDS_HDT_CONSOLE (MEM_FLOW, "\tTestAddr: %x0000\n", SweepPtr->TestAddrRJ16[CsIndex]); |
| NBPtr->WritePattern (NBPtr, SweepPtr->TestAddrRJ16[CsIndex], TechPtr->PatternBufPtr, TechPtr->PatternLength); |
| } |
| } |
| } else { |
| SweepPtr->CsAddrValid[CsIndex] = FALSE; |
| } |
| } /// End Chip Select Loop |
| TechPtr->ChipSel = TechPtr->ChipSel - CsIndex; |
| // |
| /// return FALSE if no ChipSelects present. |
| // |
| return BanksPresent; |
| } |
| |
| /* -----------------------------------------------------------------------------*/ |
| /** |
| * Test Conditions for exiting the training loop, set the next delay value, |
| * and return status |
| * |
| * @param[in,out] *TechPtr - Pointer to the MEM_TECH_BLOCK |
| * @param[in,out] *SweepPtr - Pointer to SWEEP_INFO structure. |
| * |
| * @return BOOLEAN |
| * TRUE - Continue to test with next delay setting |
| * FALSE - Exit training loop. Either the result has been found or |
| * end of delay range has been reached. |
| */ |
| BOOLEAN |
| STATIC |
| MemTContinueSweep ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN OUT SWEEP_INFO *SweepPtr |
| ) |
| { |
| BOOLEAN Status; |
| Status = FALSE; |
| if (SweepPtr->ResultFound != 0xFFFF) { |
| Status = MemTSetNextDelay (TechPtr, SweepPtr); |
| } |
| TechPtr->NBPtr->FamilySpecificHook[RegAccessFence] (TechPtr->NBPtr, NULL); |
| return Status; |
| } |
| |
| /* -----------------------------------------------------------------------------*/ |
| /** |
| * |
| * This function sets the next delay value for each bytelane that needs to |
| * be advanced. It checks the bounds of the delay to see if we are at the |
| * end of the range. If we are to close to advance a whole step value, but |
| * not at the boundary, then we set the delay to the boundary. |
| * |
| * @param[in,out] *TechPtr - Pointer to the MEM_TECH_BLOCK |
| * @param[in,out] *SweepPtr - Pointer to SWEEP_INFO structure. |
| * |
| */ |
| |
| BOOLEAN |
| STATIC |
| MemTSetNextDelay ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN OUT SWEEP_INFO *SweepPtr |
| ) |
| { |
| DIE_STRUCT *MCTPtr; |
| UINT8 i; |
| |
| MCTPtr = TechPtr->NBPtr->MCTPtr; |
| // |
| ///< Loop through bytelanes |
| // |
| for (i = 0; i < ((MCTPtr->Status[SbEccDimms] && TechPtr->NBPtr->IsSupported[EccByteTraining]) ? 9 : 8) ; i++) { |
| // |
| /// Skip Bytelanes that have already reached the desired result |
| // |
| if ( (SweepPtr->ResultFound & ((UINT16)1 << i)) == 0) { |
| // |
| /// If a bytelane has reached the end, flag an error and exit |
| // |
| if (SweepPtr->TrnDelays[i] == SweepPtr->EndDelay) { |
| if ((SweepPtr->EndResult & ((UINT16) (1 << i))) != 0) { |
| MCTPtr->ErrStatus[EsbNoDqsPos] = TRUE; |
| SweepPtr->Error = TRUE; |
| } |
| return FALSE; |
| } |
| // |
| /// If the Current delay value is less than a step away from EndDelay, |
| // |
| if ( ABS (SweepPtr->EndDelay - SweepPtr->TrnDelays[i]) < ABS (SweepPtr->Step)) { |
| /// set to EndDelay. |
| // |
| SweepPtr->TrnDelays[i] = SweepPtr->EndDelay; |
| } else { |
| // |
| /// Otherwise, add the step value to it |
| SweepPtr->TrnDelays[i] = SweepPtr->TrnDelays[i] + SweepPtr->Step; |
| } |
| // |
| /// Set InsertionDelayMsk bit if Delay < 0 for this bytelane |
| // |
| if (SweepPtr->TrnDelays[i] < 0) { |
| SweepPtr->InsertionDelayMsk |= ((UINT16) 1 << i); |
| } else { |
| SweepPtr->InsertionDelayMsk &= ~((UINT16) 1 << i); |
| } |
| // |
| /// Write the scaled value to the Delay Register |
| // |
| TechPtr->SetDQSDelayCSR (TechPtr, i, MemTScaleDelayVal (TechPtr, SweepPtr->TrnDelays[i])); |
| } |
| } |
| return TRUE; |
| } |
| /* -----------------------------------------------------------------------------*/ |
| /** |
| * |
| * This function accepts a delay value in 32nd of a UI and converts it to an |
| * actual register value, taking into consideration NB type, rd/wr, |
| * and frequency. |
| * |
| * Delay = (Min + (Delay * ( (Max - Min) / TRN_DELAY_MAX) )) & Mask |
| * |
| * @param[in,out] *TechPtr - Pointer to the MEM_TECH_BLOCK |
| * @param[in] *Delay - INT8 of delay value; |
| * |
| * @return UINT8 of the adjusted delay value |
| */ |
| UINT8 |
| STATIC |
| MemTScaleDelayVal ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN INT8 Delay |
| ) |
| { |
| MEM_NB_BLOCK *NBPtr; |
| TRN_DLY_PARMS Parms; |
| TRN_DLY_TYPE DelayType; |
| UINT8 NewDelay; |
| INT8 Factor; |
| INT8 ScaledDelay; |
| |
| NBPtr = TechPtr->NBPtr; |
| // |
| // Determine Delay Type, Get Delay Parameters, and return scaled Delay value |
| // |
| DelayType = (TechPtr->Direction == DQS_WRITE_DIR) ? AccessWrDatDly : AccessRdDqsDly; |
| NBPtr->GetTrainDlyParms (NBPtr, DelayType, &Parms); |
| Factor = ((Parms.Max - Parms.Min) / TRN_DELAY_MAX); |
| ScaledDelay = Delay * Factor; |
| NewDelay = (Parms.Min + ScaledDelay) & Parms.Mask; |
| return NewDelay; |
| } |
| |
| |
| |
| |
| |
| /* -----------------------------------------------------------------------------*/ |
| /** |
| * |
| * This function calculates the Center of the Data eye for the specified byte lane |
| * and stores its DQS Delay value for reference. |
| * |
| * @param[in,out] *TechPtr - Pointer to the MEM_TECH_BLOCK |
| * @param[in,out] *SweepPtr - Pointer to SWEEP_INFO structure. |
| * @param[in] ByteLane - Bytelane number being targeted |
| * |
| */ |
| BOOLEAN |
| STATIC |
| MemTDataEyeSave ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN OUT SWEEP_INFO *SweepPtr, |
| IN UINT8 ByteLane |
| ) |
| { |
| MEM_NB_BLOCK *NBPtr; |
| UINT8 EyeCenter; |
| UINT8 DlyMin; |
| UINT8 DlyMax; |
| UINT8 EyeWidth; |
| UINT8 Dimm; |
| CH_DEF_STRUCT *ChanPtr; |
| |
| NBPtr = TechPtr->NBPtr; |
| ChanPtr = NBPtr->ChannelPtr; |
| |
| ASSERT (ByteLane < ((NBPtr->MCTPtr->Status[SbEccDimms] && NBPtr->IsSupported[EccByteTraining]) ? 9 : 8)); |
| // |
| // Calculate Data Eye edges, Width, and Center in real terms. |
| // |
| if (TechPtr->Direction == DQS_READ_DIR) { |
| DlyMin = MemTScaleDelayVal (TechPtr, ChanPtr->RdDqsMinDlys[ByteLane]); |
| DlyMax = MemTScaleDelayVal (TechPtr, ChanPtr->RdDqsMaxDlys[ByteLane]); |
| EyeWidth = MemTScaleDelayVal (TechPtr, (ChanPtr->RdDqsMaxDlys[ByteLane] - ChanPtr->RdDqsMinDlys[ByteLane])); |
| EyeCenter = MemTScaleDelayVal (TechPtr, ((ChanPtr->RdDqsMinDlys[ByteLane] + ChanPtr->RdDqsMaxDlys[ByteLane] + 1) / 2)); |
| if (!NBPtr->FamilySpecificHook[RdDqsDlyRestartChk] (NBPtr, &EyeCenter)) { |
| return FALSE; |
| } |
| ChanPtr->RdDqsMinDlys[ByteLane] = DlyMin; |
| ChanPtr->RdDqsMaxDlys[ByteLane] = DlyMax; |
| NBPtr->FamilySpecificHook[ForceRdDqsPhaseB] (NBPtr, &EyeCenter); |
| } else { |
| DlyMin = MemTScaleDelayVal (TechPtr, ChanPtr->WrDatMinDlys[ByteLane]); |
| DlyMax = MemTScaleDelayVal (TechPtr, ChanPtr->WrDatMaxDlys[ByteLane]); |
| EyeWidth = MemTScaleDelayVal (TechPtr, (ChanPtr->WrDatMaxDlys[ByteLane] - ChanPtr->WrDatMinDlys[ByteLane])); |
| EyeCenter = MemTScaleDelayVal (TechPtr, ((ChanPtr->WrDatMinDlys[ByteLane] + ChanPtr->WrDatMaxDlys[ByteLane] + 1) / 2)); |
| ChanPtr->WrDatMinDlys[ByteLane] = DlyMin; |
| ChanPtr->WrDatMaxDlys[ByteLane] = DlyMax; |
| } |
| // |
| // Flag error for small window. |
| // |
| if (EyeWidth < MemTScaleDelayVal (TechPtr, NBPtr->MinDataEyeWidth (NBPtr))) { |
| TechPtr->SmallDqsPosWindow = TRUE; |
| SweepPtr->Error = TRUE; |
| NBPtr->FamilySpecificHook[TrackRxEnSeedlessRdWrSmallWindBLError] (NBPtr, NULL); |
| } |
| |
| IDS_HDT_CONSOLE (MEM_FLOW, " %02x %02x %02x %02x", DlyMin, DlyMax, EyeWidth, EyeCenter); |
| |
| TechPtr->SetDQSDelayCSR (TechPtr, ByteLane, EyeCenter); |
| if (!SweepPtr->Error) { |
| TechPtr->DqsRdWrPosSaved |= (UINT8)1 << ByteLane; |
| } |
| TechPtr->DqsRdWrPosSaved |= 0xFE00; |
| |
| Dimm = (TechPtr->ChipSel / NBPtr->CsPerDelay) * TechPtr->DlyTableWidth () + ByteLane; |
| if (TechPtr->Direction == DQS_READ_DIR) { |
| ChanPtr->RdDqsDlys[Dimm] = EyeCenter; |
| } else { |
| ChanPtr->WrDatDlys[Dimm] = EyeCenter + ChanPtr->WrDqsDlys[Dimm]; |
| } |
| |
| return TRUE; |
| } |
| |