| /** |
| * @file |
| * |
| * mtlrdimm3.c |
| * |
| * Technology initialization and control workd support for DDR3 LRDIMMS |
| * |
| * @xrefitem bom "File Content Label" "Release Content" |
| * @e project: AGESA |
| * @e sub-project: (Mem/Tech/DDR3) |
| * @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 "Ids.h" |
| #include "mm.h" |
| #include "mn.h" |
| #include "mu.h" |
| #include "mt.h" |
| #include "mt3.h" |
| #include "mtspd3.h" |
| #include "mtrci3.h" |
| #include "mtsdi3.h" |
| #include "mtlrdimm3.h" |
| #include "merrhdl.h" |
| #include "GeneralServices.h" |
| #include "Filecode.h" |
| CODE_GROUP (G1_PEICC) |
| RDATA_GROUP (G1_PEICC) |
| #define FILECODE PROC_MEM_TECH_DDR3_MTLRDIMM3_FILECODE |
| /*---------------------------------------------------------------------------- |
| * DEFINITIONS AND MACROS |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| |
| /*---------------------------------------------------------------------------- |
| * TYPEDEFS AND STRUCTURES |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| |
| /*---------------------------------------------------------------------------- |
| * PROTOTYPES OF LOCAL FUNCTIONS |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| |
| VOID |
| STATIC |
| MemTSendMBCtlWord3 ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN UINT8 Fn, |
| IN UINT8 Rcw, |
| IN UINT8 Value |
| ); |
| |
| VOID |
| STATIC |
| MemTSendExtMBCtlWord3 ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN UINT16 Addr, |
| IN UINT16 Data, |
| IN UINT8 Len |
| ); |
| |
| UINT8 |
| STATIC |
| MemTGetSpecialMBCtlWord3 ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN UINT8 Dimm, |
| IN UINT8 Fn, |
| IN UINT8 Rc |
| ); |
| |
| BOOLEAN |
| STATIC |
| MemTLrDimmControlRegInit3 ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN OUT VOID *OptParam |
| ); |
| |
| BOOLEAN |
| STATIC |
| MemTLrDimmFreqChgCtrlWrd3 ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN OUT VOID *OptParam |
| ); |
| |
| BOOLEAN |
| STATIC |
| MemTWLPrepareLrdimm3 ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN OUT VOID *Wl |
| ); |
| |
| BOOLEAN |
| STATIC |
| MemTSendAllMRCmdsLR3 ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN OUT VOID *CsPtr |
| ); |
| |
| VOID |
| STATIC |
| MemTEMRS1Lr3 ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN UINT8 ChipSel, |
| IN UINT8 PhyRank |
| ); |
| |
| VOID |
| STATIC |
| MemTEMRS2Lr3 ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN UINT8 ChipSel |
| ); |
| |
| |
| BOOLEAN |
| STATIC |
| MemTLrdimmRankMultiplication ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN OUT VOID *DimmID |
| ); |
| |
| BOOLEAN |
| STATIC |
| MemTLrdimmBuf2DramTrain3 ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN OUT VOID *OptParam |
| ); |
| |
| BOOLEAN |
| STATIC |
| MemTLrdimmSyncTrainedDlys ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN OUT VOID *OptParam |
| ); |
| |
| BOOLEAN |
| STATIC |
| MemTLrdimmPresence ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN OUT VOID *DimmID |
| ); |
| |
| UINT32 |
| STATIC |
| MemTLrDimmGetBufferID ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN UINT8 Dimm |
| ); |
| |
| VOID |
| STATIC |
| MemTLrdimmInitHook ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN LRDIMM_HOOK_ENTRYPOINT Entrypoint, |
| IN UINT8 Dimm, |
| IN OUT VOID *OptParam |
| ); |
| |
| /*---------------------------------------------------------------------------- |
| * EXPORTED FUNCTIONS |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| |
| /* -----------------------------------------------------------------------------*/ |
| /** |
| * |
| * This function initializes LRDIMM functions. |
| * |
| * @param[in,out] *TechPtr - Pointer to the MEM_TECH_BLOCK |
| * |
| */ |
| |
| BOOLEAN |
| MemTLrdimmConstructor3 ( |
| IN OUT MEM_TECH_BLOCK *TechPtr |
| ) |
| { |
| TechPtr->TechnologySpecificHook[LrdimmSendAllMRCmds] = MemTSendAllMRCmdsLR3; |
| TechPtr->TechnologySpecificHook[LrdimmControlRegInit] = MemTLrDimmControlRegInit3; |
| TechPtr->TechnologySpecificHook[LrdimmFreqChgCtrlWrd] = MemTLrDimmFreqChgCtrlWrd3; |
| TechPtr->TechnologySpecificHook[WlTrainingPrepareLrdimm] = MemTWLPrepareLrdimm3; |
| TechPtr->TechnologySpecificHook[LrdimmRankMultiplication] = MemTLrdimmRankMultiplication; |
| TechPtr->TechnologySpecificHook[LrdimmBuf2DramTrain] = MemTLrdimmBuf2DramTrain3; |
| TechPtr->TechnologySpecificHook[LrdimmSyncTrainedDlys] = MemTLrdimmSyncTrainedDlys; |
| TechPtr->TechnologySpecificHook[LrdimmPresence] = MemTLrdimmPresence; |
| return TRUE; |
| } |
| |
| /*---------------------------------------------------------------------------- |
| * LOCAL FUNCTIONS |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| |
| /* -----------------------------------------------------------------------------*/ |
| /** |
| * |
| * This function sends a Control word command to an LRDIMM Memory Buffer |
| * |
| * @param[in,out] *TechPtr - Pointer to the MEM_TECH_BLOCK |
| * @param[in] Fn - control word function |
| * @param[in] Rcw - control word number |
| * @param[in] Value - value to send |
| * |
| */ |
| |
| VOID |
| STATIC |
| MemTSendMBCtlWord3 ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN UINT8 Fn, |
| IN UINT8 Rcw, |
| IN UINT8 Value |
| ) |
| { |
| MEM_NB_BLOCK *NBPtr; |
| |
| NBPtr = TechPtr->NBPtr; |
| |
| ASSERT (Rcw != RCW_FN_SELECT); // RC7 can only be used for function select |
| IDS_HDT_CONSOLE (MEM_FLOW, "\t\tF%dRC%d = %x\n", Fn, Rcw, Value); |
| // |
| // Select the MB Function by sending the Fn number |
| // to the Function Select Control Word |
| // |
| MemUWait10ns (800, NBPtr->MemPtr); |
| MemTSendCtlWord3 (TechPtr, RCW_FN_SELECT, Fn); |
| // |
| // Send the value to the control word |
| // |
| MemUWait10ns (800, NBPtr->MemPtr); |
| MemTSendCtlWord3 (TechPtr, Rcw, Value); |
| } |
| |
| /* -----------------------------------------------------------------------------*/ |
| /** |
| * |
| * This function sends a an Extended Control word command to an LRDIMM Memory Buffer |
| * |
| * @param[in,out] *TechPtr - Pointer to the MEM_TECH_BLOCK |
| * @param[in] Addr - Extended Control Word Address |
| * Addr[15:12] Extended Control Workd Function Select |
| * Addr[11:0] Extended Control Word CSR Address |
| * @param[in] Data - value to send |
| * @param[in] Len - Length of data. 1 or 2 bytes |
| * |
| */ |
| |
| VOID |
| STATIC |
| MemTSendExtMBCtlWord3 ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN UINT16 Addr, |
| IN UINT16 Data, |
| IN UINT8 Len |
| ) |
| { |
| MEM_NB_BLOCK *NBPtr; |
| |
| NBPtr = TechPtr->NBPtr; |
| |
| ASSERT ((Len == 1) || (Len == 2)); |
| if (Len == 2 ) { |
| IDS_HDT_CONSOLE (MEM_FLOW, "\t\tExtRC_x%04x = %04x\n", Addr, Data); |
| } else { |
| IDS_HDT_CONSOLE (MEM_FLOW, "\t\tExtRC_x%04x = %02x\n", Addr, (UINT8) (Data & 0xFF) ); |
| } |
| // |
| // Select the MB Function by sending the Fn number |
| // to the Function Select Control Word |
| // |
| MemUWait10ns (800, NBPtr->MemPtr); |
| MemTSendCtlWord3 (TechPtr, RCW_FN_SELECT, 13); |
| // |
| // Send address via control words |
| // |
| MemUWait10ns (800, NBPtr->MemPtr); |
| MemTSendCtlWord3 (TechPtr, 9, (UINT8) (Addr >> 12)); |
| MemUWait10ns (800, NBPtr->MemPtr); |
| MemTSendCtlWord3 (TechPtr, 10, (UINT8) (Addr & 0xF)); |
| MemUWait10ns (800, NBPtr->MemPtr); |
| MemTSendCtlWord3 (TechPtr, 11, (UINT8) ((Addr >> 4) & 0x0F)); |
| MemUWait10ns (800, NBPtr->MemPtr); |
| MemTSendCtlWord3 (TechPtr, 12, (UINT8) ((Addr >> 8) & 0x0F)); |
| // |
| // Send the Lower Byte of Data |
| // |
| MemUWait10ns (800, NBPtr->MemPtr); |
| MemTSendCtlWord3 (TechPtr, 14, (UINT8) (Data & 0xF)); |
| MemUWait10ns (800, NBPtr->MemPtr); |
| MemTSendCtlWord3 (TechPtr, 15, (UINT8) ((Data >> 4) & 0x0F)); |
| // |
| // Send the Upper Byte of Data |
| // |
| if (Len == 2) { |
| MemUWait10ns (800, NBPtr->MemPtr); |
| MemTSendCtlWord3 (TechPtr, 10, (UINT8) ((Addr & 0xF) + 1)); |
| MemUWait10ns (800, NBPtr->MemPtr); |
| MemTSendCtlWord3 (TechPtr, 14, (UINT8) ((Data >> 8) & 0xF)); |
| MemUWait10ns (800, NBPtr->MemPtr); |
| MemTSendCtlWord3 (TechPtr, 15, (UINT8) ((Data >> 12) & 0xF)); |
| } |
| } |
| |
| /* -----------------------------------------------------------------------------*/ |
| /** |
| * |
| * This function gets the value of special RCW |
| * |
| * @param[in,out] *TechPtr - Pointer to the MEM_TECH_BLOCK |
| * @param[in] Dimm - Physical LR DIMM number |
| * @param[in] Fn - control word function |
| * @param[in] Rc - control word number |
| * |
| * @return Special RCW value |
| * |
| */ |
| |
| UINT8 |
| STATIC |
| MemTGetSpecialMBCtlWord3 ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN UINT8 Dimm, |
| IN UINT8 Fn, |
| IN UINT8 Rc |
| ) |
| { |
| CONST UINT8 F0RC13PhyRankTab[] = {3, 2, 0, 1, 0}; |
| UINT8 PhyRanks; |
| UINT8 LogRanks; |
| UINT8 DramCap; |
| UINT8 Value8; |
| UINT8 *SpdBufferPtr; |
| MEM_NB_BLOCK *NBPtr; |
| |
| NBPtr = TechPtr->NBPtr; |
| |
| MemTGetDimmSpdBuffer3 (TechPtr, &SpdBufferPtr, Dimm); |
| |
| Value8 = 0; |
| switch (Fn) { |
| case 0: |
| switch (Rc) { |
| case 8: |
| // F0RC8 |
| Value8 = NBPtr->PsPtr->F0RC8; |
| break; |
| case 10: |
| // F0RC10 |
| // 2:0 OperatingSpeed: operating speed. BIOS: Table 88. |
| if (NBPtr->DCTPtr->Timings.Speed == DDR667_FREQUENCY) { |
| Value8 = 0; |
| } else { |
| Value8 = (UINT8) (NBPtr->DCTPtr->Timings.Speed / 133) - 3; |
| } |
| break; |
| case 11: |
| // F0RC11 |
| // 3:2 ParityCalculation: partiy calculation. BIOS: Table. |
| // 1:0 OperatingVoltage: operating voltage. BIOS: IF(VDDIO == 1.5) THEN 00b ELSEIF (VDDIO == |
| // 1.35) THEN 01b ELSE 10b ENDIF. |
| DramCap = SpdBufferPtr[SPD_DENSITY] & 0xF; |
| if (NBPtr->PsPtr->LrdimmRowAddrBits[Dimm] > 16) { |
| Value8 = 8; |
| } else { |
| Value8 = 4; |
| } |
| Value8 |= CONVERT_VDDIO_TO_ENCODED (NBPtr->RefPtr->DDR3Voltage); |
| break; |
| case 13: |
| // F0RC13 |
| // 3:2 NumLogicalRanks: partiy calculation. BIOS: Table 90. |
| // 1:0 NumPhysicalRanks: operating voltage. BIOS: Table 89. |
| LogRanks = NBPtr->ChannelPtr->LrDimmLogicalRanks[Dimm] >> 1; |
| PhyRanks = F0RC13PhyRankTab[(SpdBufferPtr[SPD_RANKS] >> 3) & 7]; |
| Value8 = (LogRanks << 2) | PhyRanks; |
| break; |
| case 14: |
| // F0RC14 |
| // 3 DramBusWidth: DRAM bus width. BIOS: IF (DeviceWidth==0) THEN 0 ELSE 1 ENDIF. |
| // 2 MRSCommandControl: MRS command control. BIOS: IF (F0RC15[RankMultiplicationControl] |
| // > 0) THEN 1 ELSE 0 ENDIF. |
| // 1 RefreshPrechargeCommandControl: refresh and precharge command control. BIOS: IF |
| // (F0RC15[RankMultiplicationControl] > 0) THEN D18F2xA8_dct[1:0][LrDimmEnhRefEn] ELSE 0 ENDIF. |
| // 0 AddressMirror: address mirror. BIOS: RankMap. See D18F2x[5C:40]_dct[1:0][OnDimmMirror]. |
| if ((SpdBufferPtr[SPD_DEV_WIDTH] & 7) != 0) { |
| Value8 |= 8; |
| } |
| if (NBPtr->ChannelPtr->LrDimmRankMult[Dimm] > 1) { |
| Value8 |= 4; |
| if (NBPtr->GetBitField (NBPtr, BFLrDimmEnhRefEn) == 1) { |
| Value8 |= 2; |
| } |
| } |
| if ((SpdBufferPtr[SPD_ADDRMAP] & 1) != 0) { |
| Value8 |= 1; |
| } |
| break; |
| case 15: |
| // F0RC15 |
| // 3:0 RankMultiplicationControl: rank multiplication control. BIOS: Table 91. |
| DramCap = SpdBufferPtr[SPD_DENSITY] & 0xF; |
| ASSERT ((DramCap >= 2) && (DramCap <= 4)); // BKDG only lists 1Gb, 2Gb, and 4Gb |
| switch (NBPtr->ChannelPtr->LrDimmRankMult[Dimm]) { |
| case 1: |
| Value8 = 0; |
| break; |
| case 2: |
| Value8 = DramCap - 1; |
| break; |
| case 4: |
| Value8 = DramCap + 3; |
| break; |
| default: |
| ASSERT (FALSE); |
| } |
| break; |
| default: |
| ASSERT (FALSE); |
| } |
| break; |
| case 1: |
| switch (Rc) { |
| case 0: |
| // F1RC0 |
| Value8 = NBPtr->PsPtr->F1RC0; |
| Value8 |= (UINT8) NBPtr->GetBitField (NBPtr, BFCSTimingMux67) << 3; |
| break; |
| case 1: |
| // F1RC1 |
| Value8 = NBPtr->PsPtr->F1RC1; |
| break; |
| case 2: |
| // F1RC2 |
| Value8 = NBPtr->PsPtr->F1RC2; |
| break; |
| case 9: |
| // F1RC9 |
| if (NBPtr->GetBitField (NBPtr, BFLrDimmEnhRefEn) == 0) { |
| Value8 = 1; |
| } |
| break; |
| default: |
| ASSERT (FALSE); |
| } |
| break; |
| case 3: |
| switch (Rc) { |
| case 0: |
| // F3RC0 |
| // 3 TDQSControl: TDQS control. BIOS: 0. |
| // 2:0 RttNom: RttNom. BIOS: Table 57, Table 60 |
| Value8 = NBPtr->PsPtr->RttNom[Dimm << 1]; |
| break; |
| case 1: |
| // F3RC1 |
| // 3 Vref: Vref. BIOS: 0. |
| // 2:0 RttWr: RttWr. BIOS: Table 57, Table 60. |
| Value8 = NBPtr->PsPtr->RttWr[Dimm << 1]; |
| break; |
| case 6: |
| // F3RC6 |
| // IF (D18F2x90_dct[1:0][X4Dimm] == 0) THEN 1 ELSE 0 |
| if (NBPtr->GetBitField (NBPtr, BFX4Dimm) == 0) { |
| Value8 = 8; |
| } |
| break; |
| default: |
| ASSERT (FALSE); |
| } |
| break; |
| default: |
| ASSERT (FALSE); |
| } |
| |
| return Value8; |
| } |
| |
| /* -----------------------------------------------------------------------------*/ |
| /** |
| * |
| * This function sends LRDIMM Control Words to all LRDIMMS |
| * |
| * @param[in,out] *TechPtr - Pointer to the MEM_TECH_BLOCK |
| * @param[in,out] OptParam - Optional parameter |
| * |
| * @return TRUE |
| */ |
| |
| BOOLEAN |
| STATIC |
| MemTLrDimmControlRegInit3 ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN OUT VOID *OptParam |
| ) |
| { |
| CONST UINT8 RCWInitTable[] = { |
| // RCW, Mask, SPD |
| F0, RC0, 0x00, SPD_NONE, |
| F0, RC1, 0x00, SPD_NONE, |
| F0, RC2, 0x03, SPD_67, |
| F0, RC10, 0x00, SPECIAL_CASE, |
| F0, RC11, 0x00, SPECIAL_CASE, |
| |
| F1, RC8, 0x0F, SPD_69, |
| F1, RC11, 0xF0, SPD_69, |
| F1, RC12, 0x0F, SPD_70, |
| F1, RC13, 0xF0, SPD_70, |
| F1, RC14, 0x0F, SPD_71, |
| F1, RC15, 0xF0, SPD_71, |
| |
| WAIT_6US, 0, 0, 0, |
| |
| F0, RC3, 0xF0, SPD_67, |
| F0, RC4, 0x0F, SPD_68, |
| F0, RC5, 0xF0, SPD_68, |
| |
| F0, RC6, 0x00, SPD_NONE, |
| F0, RC8, 0x00, SPECIAL_CASE, |
| F0, RC9, 0x0C, SPD_NONE, |
| F0, RC13, 0x00, SPECIAL_CASE, |
| F0, RC14, 0x00, SPECIAL_CASE, |
| F0, RC15, 0x00, SPECIAL_CASE, |
| |
| F1, RC0, 0x00, SPECIAL_CASE, |
| F1, RC1, 0x00, SPECIAL_CASE, |
| F1, RC2, 0x00, SPECIAL_CASE, |
| F1, RC3, 0x00, SPD_NONE, |
| F1, RC9, 0x00, SPECIAL_CASE, |
| F1, RC10, 0x00, SPD_NONE, |
| |
| F2, RC0, 0x00, SPD_NONE, |
| F2, RC1, 0x00, SPD_NONE, |
| F2, RC2, 0x0F, SPD_NONE, |
| F2, RC3, 0x00, SPD_NONE, |
| |
| F3, RC0, 0x00, SPECIAL_CASE, |
| F3, RC1, 0x00, SPECIAL_CASE, |
| F3, RC2, 0x01, SPD_NONE, |
| F3, RC6, 0x00, SPECIAL_CASE |
| // |
| // F3 RC[8,9] are programmed in MDQ RC loop |
| // |
| // F[10:3] RC[11,10] are programmed in QxODT RC loop |
| // |
| // F[15,14] RC[15:0] are programmed in personality RC loop |
| }; |
| |
| UINT8 Dimm; |
| UINT16 i; |
| UINT16 DimmMask; |
| UINT8 Fn; |
| UINT8 Rc; |
| UINT8 Mask; |
| UINT8 Spd; |
| UINT8 *SpdBufferPtr; |
| UINT8 FreqDiffOffset; |
| UINT8 Value8; |
| UINT32 Temp32; |
| MEM_DATA_STRUCT *MemPtr; |
| MEM_NB_BLOCK *NBPtr; |
| |
| NBPtr = TechPtr->NBPtr; |
| MemPtr = NBPtr->MemPtr; |
| |
| if (NBPtr->MCTPtr->Status[SbLrdimms]) { |
| for (Dimm = 0; Dimm < MAX_DIMMS_PER_CHANNEL; Dimm++) { |
| DimmMask = (UINT16)1 << Dimm; |
| if ((NBPtr->ChannelPtr->LrDimmPresent & DimmMask) != 0) { |
| // |
| // Select the Target Chipselects |
| // |
| NBPtr->SetBitField (NBPtr, BFMrsChipSel, (Dimm << 1)); |
| NBPtr->SetBitField (NBPtr, BFCtrlWordCS, 3 << (Dimm << 1)); |
| |
| MemTGetDimmSpdBuffer3 (TechPtr, &SpdBufferPtr, Dimm); |
| |
| IDS_HDT_CONSOLE (MEM_FLOW, "\t\tSending LRDIMM Control Words: Dimm %02x\n", Dimm); |
| |
| for (i = 0; i < sizeof (RCWInitTable) ; i += 4) { |
| Fn = RCWInitTable[i]; |
| Rc = RCWInitTable[i + 1]; |
| Mask = RCWInitTable[i + 2]; |
| Spd = RCWInitTable[i + 3]; |
| |
| if (Fn == WAIT_6US) { |
| MemUWait10ns (600, MemPtr); // wait 6us for TSTAB |
| } else { |
| if (Spd == SPD_NONE) { |
| Value8 = Mask; |
| } else if (Spd == SPECIAL_CASE) { |
| Value8 = MemTGetSpecialMBCtlWord3 (TechPtr, Dimm, Fn, Rc); |
| } else { |
| Value8 = (Mask > 0x0F) ? ((SpdBufferPtr[Spd] & Mask) >> 4) : (SpdBufferPtr[Spd] & Mask); |
| } |
| MemTSendMBCtlWord3 (TechPtr, Fn, Rc, Value8); |
| } |
| } |
| |
| FreqDiffOffset = (UINT8) (SPD_FREQ_DIFF_OFFSET * (((NBPtr->DCTPtr->Timings.Speed / 133) - 3) / 2)); |
| // |
| // Send RCW to program MDQ termination and drive strength |
| // |
| for (Rc = 8; Rc <= 9; Rc++) { |
| Value8 = SpdBufferPtr[SPD_MDQ_800_1066 + FreqDiffOffset]; |
| Value8 = (Rc == 9) ? (Value8 >> 4) : Value8; |
| MemTSendMBCtlWord3 (TechPtr, 3, Rc, Value8 & 0x07); |
| } |
| // |
| // Send RCW to program QxODT |
| // |
| for (Fn = 3; Fn <= 10; Fn ++) { |
| for (Rc = 10; Rc <= 11; Rc++) { |
| Value8 = SpdBufferPtr[SPD_QXODT_800_1066 + FreqDiffOffset + ((Fn - 3) >> 1)]; |
| Value8 = (Rc == 11) ? (Value8 >> 4) : (Value8 & 0x0F); |
| Value8 = ((Fn & 1) == 0) ? (Value8 >> 2) : (Value8 & 0x03); |
| MemTSendMBCtlWord3 (TechPtr, Fn, Rc, Value8); |
| } |
| } |
| |
| MemTLrdimmInitHook (TechPtr, AFTER_TSTAB, Dimm, 0); |
| // |
| // Send Personality bytes from SPD |
| // |
| for (i = 0; i < 15; i ++) { |
| Value8 = SpdBufferPtr[SPD_PERSONALITY_BYTE + i]; |
| Fn = (UINT8) (14 + (i >> 3)); |
| Rc = (UINT8) ((i << 1) & 0x0F); |
| if ( i == 0) { |
| Value8 |= 0x01; |
| } else if ( i > 10) { |
| Rc += 2; |
| } |
| MemTSendMBCtlWord3 (TechPtr, Fn, Rc, (Value8 & 0x0F)); |
| if (i == 3) { |
| Fn++; |
| } else { |
| Rc++; |
| } |
| MemTSendMBCtlWord3 (TechPtr, Fn, Rc, (Value8 >> 4)); |
| } |
| // |
| // Send Extended Control Words to Buffer |
| // |
| // ExtRC_xAC |
| // |
| MemTSendExtMBCtlWord3 (TechPtr, 0x00AC, 0, 1); |
| // |
| // ExtRC_xB8-BF |
| // |
| Value8 = SpdBufferPtr[SPD_MR1_MR2_800_1066 + FreqDiffOffset]; |
| for (i = 0x00B8; i < 0x00C0; i++) { |
| MemTSendExtMBCtlWord3 (TechPtr, i, Value8, 1); |
| if (i == 0xB9) { |
| // |
| // Phys ranks > 1, Rtt_nom = 0 |
| // |
| Value8 &= 0xE3; |
| } |
| } |
| // ExtRC_xC8 |
| Value8 = (UINT8) (NBPtr->MemNGetMR0CL (NBPtr) & 0x000000FF); |
| Value8 = ((Value8 & 0xE0) | ((Value8 & 0x04) << 1)); |
| Value8 |= 1 << 2; // RBT |
| Value8 |= NBPtr->GetBitField (NBPtr, BFBurstCtrl); // BL |
| MemTSendExtMBCtlWord3 (TechPtr, 0x00C8 , Value8, 1); |
| // ExtRC_xC9 |
| // PPD |
| Value8 = (UINT8) (NBPtr->GetBitField (NBPtr, BFPchgPDModeSel) & 0x000000FF); |
| NBPtr->FamilySpecificHook[MR0_PPD] (NBPtr, &Value8); |
| IDS_OPTION_HOOK (IDS_MEM_MR0, &Value8, &TechPtr->NBPtr->MemPtr->StdHeader); |
| Value8 <<= 4; |
| // WR |
| Temp32 = NBPtr->MemNGetMR0WR (NBPtr); |
| Value8 |= (UINT8) ((Temp32 >> 8) & 0x000000FF); |
| MemTSendExtMBCtlWord3 (TechPtr, 0x00C9 , Value8, 1); |
| // ExtRC_xCA |
| MemTSendExtMBCtlWord3 (TechPtr, 0x00CA , 0, 1); |
| // ExtRC_xCB |
| MemTSendExtMBCtlWord3 (TechPtr, 0x00CB , 0, 1); |
| // ExtRC_xCC |
| // CWL |
| Value8 = (UINT8) (NBPtr->MemNGetMR2CWL (NBPtr) & 0x000000FF); |
| // SRT|ASR |
| Value8 |= 0x40; |
| MemTSendExtMBCtlWord3 (TechPtr, 0x00CC , Value8, 1); |
| // ExtRC_xCD |
| MemTSendExtMBCtlWord3 (TechPtr, 0x00CD , 0, 1); |
| // ExtRC_xCE |
| MemTSendExtMBCtlWord3 (TechPtr, 0x00CE , 0, 1); |
| // ExtRC_xCF |
| MemTSendExtMBCtlWord3 (TechPtr, 0x00CF , 0, 1); |
| } |
| } |
| } |
| return TRUE; |
| } |
| |
| /* -----------------------------------------------------------------------------*/ |
| /** |
| * |
| * This function sends LRDIMM Control Words to all LRDIMMS |
| * |
| * @param[in,out] *TechPtr - Pointer to the MEM_TECH_BLOCK |
| * @param[in,out] OptParam - Optional parameter |
| * |
| * @return FALSE - The current channel does not have LRDIMM populated |
| * TRUE - The current channel has LRDIMM populated |
| */ |
| BOOLEAN |
| STATIC |
| MemTLrDimmFreqChgCtrlWrd3 ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN OUT VOID *OptParam |
| ) |
| { |
| UINT8 Dct; |
| MEM_NB_BLOCK *NBPtr; |
| |
| NBPtr = TechPtr->NBPtr; |
| |
| if (NBPtr->MCTPtr->Status[SbLrdimms]) { |
| for (Dct = 0; Dct < NBPtr->DctCount; Dct++) { |
| MemNSwitchDCTNb (NBPtr, Dct); |
| if (NBPtr->DCTPtr->Timings.DctMemSize != 0) { |
| MemTLrDimmControlRegInit3 (TechPtr, NULL); |
| } |
| } |
| return TRUE; |
| } |
| return FALSE; |
| } |
| |
| /*----------------------------------------------------------------------------- |
| * |
| * |
| * This function prepares LRDIMMs for WL training. |
| * |
| * @param[in,out] *TechPtr - Pointer to the MEM_TECH_BLOCK |
| * @param[in,out] *Wl - Indicates if WL mode should be enabled |
| * |
| * @return TRUE - LRDIMMs present |
| * ---------------------------------------------------------------------------- |
| */ |
| BOOLEAN |
| STATIC |
| MemTWLPrepareLrdimm3 ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN OUT VOID *Wl |
| ) |
| { |
| UINT8 Dimm; |
| UINT8 Value8; |
| UINT16 MrsAddress; |
| MEM_NB_BLOCK *NBPtr; |
| NBPtr = TechPtr->NBPtr; |
| MrsAddress = 0; |
| if (NBPtr->MCTPtr->Status[SbLrdimms]) { |
| for (Dimm = 0; Dimm < MAX_DIMMS_PER_CHANNEL; Dimm++) { |
| if (*(BOOLEAN *) Wl == TRUE) { |
| // Program WrLvOdt |
| NBPtr->SetBitField (NBPtr, BFWrLvOdt, NBPtr->ChannelPtr->PhyWLODT[Dimm]); |
| } |
| if ((NBPtr->ChannelPtr->LrDimmPresent & ((UINT8) 1 << Dimm)) != 0) { |
| if (Dimm == TechPtr->TargetDIMM) { |
| if (*(BOOLEAN *) Wl == TRUE) { |
| // |
| // Select the Target Chipselects |
| // |
| NBPtr->SetBitField (NBPtr, BFMrsChipSel, (Dimm << 1)); |
| NBPtr->SetBitField (NBPtr, BFCtrlWordCS, 3 << (Dimm << 1)); |
| // Program F0RC12 to 1h |
| MemTSendMBCtlWord3 (TechPtr, F0, RC12, 0x01); |
| if (NBPtr->ChannelPtr->Dimms >= 2) { |
| // For two or more LRDIMMs per channel program the buffer RttNom to the |
| // corresponding specifed RttWr termination |
| Value8 = NBPtr->MemNGetDynDramTerm (NBPtr, Dimm << 2); |
| } else { |
| // Program RttNom as normal |
| Value8 = NBPtr->MemNGetDramTerm (NBPtr, Dimm << 2); |
| } |
| if ((Value8 & ((UINT8) 1 << 2)) != 0) { |
| MrsAddress |= ((UINT16) 1 << 9); |
| } |
| if ((Value8 & ((UINT8) 1 << 1)) != 0) { |
| MrsAddress |= ((UINT16) 1 << 6); |
| } |
| if ((Value8 & ((UINT8) 1 << 0)) != 0) { |
| MrsAddress |= ((UINT16) 1 << 2); |
| } |
| NBPtr->SetBitField (NBPtr, BFMrsAddress, MrsAddress); |
| } else { |
| // Program F0RC12 to 0h |
| MemTSendMBCtlWord3 (TechPtr, F0, RC12, 0x00); |
| } |
| } |
| } |
| } |
| return TRUE; |
| } else { |
| return FALSE; |
| } |
| } |
| |
| /* -----------------------------------------------------------------------------*/ |
| /** |
| * |
| * This send all MR commands to all physical ranks of an LRDIMM |
| * |
| * @param[in,out] *TechPtr - Pointer to the MEM_TECH_BLOCK |
| * @param[in] *CsPtr - Target Chip Select |
| * |
| * @return FALSE - The current channel does not have LRDIMM populated |
| * TRUE - The current channel has LRDIMM populated |
| */ |
| BOOLEAN |
| STATIC |
| MemTSendAllMRCmdsLR3 ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN OUT VOID *CsPtr |
| ) |
| { |
| UINT8 *SpdBufferPtr; |
| UINT8 Rank; |
| UINT8 PhyRank; |
| UINT8 ChipSel; |
| MEM_NB_BLOCK *NBPtr; |
| |
| NBPtr = TechPtr->NBPtr; |
| ChipSel = *((UINT8 *) CsPtr); |
| |
| if (NBPtr->MCTPtr->Status[SbLrdimms]) { |
| // |
| // For LRDIMMs, MR0, MR2, and MR3 can be broadcast to any physicall ranks behind |
| // each logical rank(CS) by setting MRSAddress[13]. MR1[Rtt_Nom] needs to be programmed |
| // differently per physical rank, so it must target a physical rank using MrsAddress[17:14]. |
| // The actual bits used to index the physical rank are determined by the DRAM Capacity. |
| // |
| // This function will be called once for each CS where CSPresent is set, so each time |
| // it only needs to handle the Physical ranks behind each CS. If a Dimm is not using some |
| // CS due to Rank Mux, those CSPresent bits will have been already cleared. |
| // |
| |
| // |
| // Select target chip select |
| // |
| NBPtr->SetBitField (NBPtr, BFMrsChipSel, ChipSel); |
| // |
| // 13.Send EMRS(2) |
| // |
| MemTEMRS2Lr3 (TechPtr, ChipSel); |
| NBPtr->SetBitField (NBPtr, BFMrsAddressHi, 1); // Set Address bit 13 to broadcast |
| NBPtr->SendMrsCmd (NBPtr); |
| // |
| // 14.Send EMRS(3). Ordinarily at this time, MrsAddress[2:0]=000b |
| // |
| MemTEMRS33 (TechPtr); |
| NBPtr->SetBitField (NBPtr, BFMrsAddressHi, 1); // Set Address bit 13 to broadcast |
| NBPtr->SendMrsCmd (NBPtr); |
| // |
| // 15.Send EMRS(1). Send to each physical rank. |
| // |
| MemTGetDimmSpdBuffer3 (TechPtr, &SpdBufferPtr, ChipSel >> 1); |
| // |
| // Determine first physical rank relative to the LRDIMM for this CS |
| // |
| PhyRank = ((((ChipSel & NBPtr->ChannelPtr->LrDimmLogicalRanks[ChipSel >> 1]) >> 1) & 2) | (ChipSel & 1)); |
| for (Rank = 0; Rank < NBPtr->ChannelPtr->LrDimmRankMult[ChipSel >> 1]; Rank++) { |
| MemTEMRS1Lr3 (TechPtr, ChipSel, PhyRank); |
| // |
| // Set Address bit 14, 15, 16, or 17 to select physical rank, relative to the CS, according to the device size |
| // |
| NBPtr->SetBitField (NBPtr, BFMrsAddressHi, Rank << ((SpdBufferPtr[SPD_DENSITY] & 0xF) - 1 ) ); |
| NBPtr->SendMrsCmd (NBPtr); |
| // |
| // Index to the next physical rank |
| // |
| PhyRank = PhyRank + NBPtr->ChannelPtr->LrDimmLogicalRanks[ChipSel >> 1]; |
| } |
| // |
| // 16.Send MRS with MrsAddress[8]=1(reset the DLL) |
| // |
| MemTMRS3 (TechPtr); |
| NBPtr->SetBitField (NBPtr, BFMrsAddressHi, 1); // Set Address bit 13 to broadcast |
| NBPtr->SendMrsCmd (NBPtr); |
| // |
| // If LRDIMM, return TRUE to skip sending regular MR commands. |
| // |
| return TRUE; |
| } |
| // |
| // If not LRDIMM, send regular MR commands. |
| // |
| return FALSE; |
| } |
| |
| /* -----------------------------------------------------------------------------*/ |
| /** |
| * |
| * This function calculates the EMRS1 value for an LRDIMM |
| * |
| * @param[in,out] *TechPtr - Pointer to the MEM_TECH_BLOCK |
| * @param[in] ChipSel - Chip select number |
| * @param[in] PhyRank - Physical rank number |
| */ |
| |
| VOID |
| STATIC |
| MemTEMRS1Lr3 ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN UINT8 ChipSel, |
| IN UINT8 PhyRank |
| ) |
| { |
| UINT16 MrsAddress; |
| UINT8 Value8; |
| UINT8 *SpdBufferPtr; |
| UINT8 FreqDiffOffset; |
| MEM_NB_BLOCK *NBPtr; |
| |
| NBPtr = TechPtr->NBPtr; |
| |
| MemTGetDimmSpdBuffer3 (TechPtr, &SpdBufferPtr, ChipSel >> 1); |
| FreqDiffOffset = (UINT8) (SPD_FREQ_DIFF_OFFSET * (((NBPtr->DCTPtr->Timings.Speed / 133) - 3) / 2)); |
| |
| // BA2=0,BA1=0,BA0=1 |
| NBPtr->SetBitField (NBPtr, BFMrsBank, 1); |
| |
| MrsAddress = 0; |
| |
| // program MrsAddress[5,1]=output driver impedance control (DIC): 01b |
| MrsAddress |= ((UINT16) 1 << 1); |
| |
| // program MrsAddress[5,1]=output driver impedance control (DIC): |
| // DIC is read from SPD byte 77, 83, or 89 depending on DDR speed |
| Value8 = SpdBufferPtr[SPD_MR1_MR2_800_1066 + FreqDiffOffset] & 3; |
| if ((Value8 & ((UINT8) 1 << 1)) != 0) { |
| MrsAddress |= ((UINT16) 1 << 5); |
| } |
| if ((Value8 & ((UINT8) 1 << 0)) != 0) { |
| MrsAddress |= ((UINT16) 1 << 1); |
| } |
| |
| // program MrsAddress[9,6,2]=nominal termination resistance of ODT (RTT): |
| // RttNom is read from SPD byte 77, 83, or 89 depending on DDR speed |
| if (PhyRank <= 1) { |
| Value8 = (SpdBufferPtr[SPD_MR1_MR2_800_1066 + FreqDiffOffset] >> 2) & 7; |
| if ((Value8 & ((UINT8) 1 << 2)) != 0) { |
| MrsAddress |= ((UINT16) 1 << 9); |
| } |
| if ((Value8 & ((UINT8) 1 << 1)) != 0) { |
| MrsAddress |= ((UINT16) 1 << 6); |
| } |
| if ((Value8 & ((UINT8) 1 << 0)) != 0) { |
| MrsAddress |= ((UINT16) 1 << 2); |
| } |
| } |
| |
| NBPtr->SetBitField (NBPtr, BFMrsAddress, MrsAddress); |
| } |
| |
| /* -----------------------------------------------------------------------------*/ |
| /** |
| * |
| * This function calculates the EMRS2 value for an LRDIMM |
| * |
| * @param[in,out] *TechPtr - Pointer to the MEM_TECH_BLOCK |
| * @param[in] ChipSel - Chip select number |
| */ |
| |
| VOID |
| STATIC |
| MemTEMRS2Lr3 ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN UINT8 ChipSel |
| ) |
| { |
| UINT8 RttWr; |
| UINT8 *SpdBufferPtr; |
| UINT8 FreqDiffOffset; |
| MEM_NB_BLOCK *NBPtr; |
| |
| NBPtr = TechPtr->NBPtr; |
| |
| // Save default RttWr |
| RttWr = NBPtr->PsPtr->RttWr[ChipSel]; |
| |
| // Override RttWr with the value read from SPD byte 77, 83, or 89 depending on DDR speed |
| MemTGetDimmSpdBuffer3 (TechPtr, &SpdBufferPtr, ChipSel >> 1); |
| FreqDiffOffset = (UINT8) (SPD_FREQ_DIFF_OFFSET * (((NBPtr->DCTPtr->Timings.Speed / 133) - 3) / 2)); |
| NBPtr->PsPtr->RttWr[ChipSel] = SpdBufferPtr[SPD_MR1_MR2_800_1066 + FreqDiffOffset] >> 6; |
| |
| // Call EMRS2 calculation |
| MemTEMRS23 (TechPtr); |
| |
| // Restore RttWr |
| NBPtr->PsPtr->RttWr[ChipSel] = RttWr; |
| } |
| |
| /*----------------------------------------------------------------------------- |
| * |
| * |
| * This function to determine the Rank Multiplication to use for an LRDIMM |
| * |
| * @param[in,out] *TechPtr - Pointer to the MEM_TECH_BLOCK |
| * @param[in,out] *DimmID - Dimm ID |
| * |
| * @return TRUE - LRDIMM Support is installed and LRDIMMs are present |
| * ---------------------------------------------------------------------------- |
| */ |
| BOOLEAN |
| STATIC |
| MemTLrdimmRankMultiplication ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN OUT VOID *DimmID |
| ) |
| { |
| BOOLEAN RetVal; |
| UINT8 *SpdBufferPtr; |
| UINT8 Dimm; |
| UINT8 NumDimmslots; |
| UINT8 DramCapacity; |
| UINT8 Ranks; |
| UINT8 Rows; |
| UINT8 RankMult; |
| MEM_NB_BLOCK *NBPtr; |
| CH_DEF_STRUCT *ChannelPtr; |
| |
| ASSERT (TechPtr != NULL); |
| ASSERT (DimmID != NULL); |
| |
| Dimm = *(UINT8*)DimmID; |
| ASSERT (Dimm < MAX_DIMMS_PER_CHANNEL); |
| |
| NBPtr = TechPtr->NBPtr; |
| ChannelPtr = NBPtr->ChannelPtr; |
| RetVal = FALSE; |
| RankMult = 0; |
| |
| if (!MemTGetDimmSpdBuffer3 (TechPtr, &SpdBufferPtr, Dimm)) { |
| ASSERT (FALSE); |
| } |
| |
| NumDimmslots = GetMaxDimmsPerChannel (NBPtr->RefPtr->PlatformMemoryConfiguration, |
| NBPtr->MCTPtr->SocketId, |
| ChannelPtr->ChannelID); |
| |
| if (NBPtr->MCTPtr->Status[SbLrdimms]) { |
| RetVal = TRUE; |
| // |
| // Determine LRDIMM Rank Multiplication |
| // |
| Ranks = ((SpdBufferPtr[SPD_RANKS] >> 3) & 0x07) + 1; |
| if (Ranks == 5) { |
| Ranks = 8; |
| } |
| DramCapacity = (SpdBufferPtr[SPD_DENSITY] & 0x0F); |
| Rows = 12 + ((SpdBufferPtr[SPD_ROW_SZ] >> 3) & 0x7); |
| |
| if (Ranks < 4) { |
| RankMult = 1; |
| } else if (Ranks == 4) { |
| RankMult = (NumDimmslots < 3) ? 1 : 2; |
| } else if (Ranks == 8) { |
| RankMult = ((NumDimmslots < 3) && (DramCapacity < 4)) ? 2 : 4; |
| } |
| // |
| // Save Rank Information |
| // |
| ChannelPtr->LrDimmRankMult[Dimm] = RankMult; |
| ChannelPtr->LrDimmLogicalRanks[Dimm] = Ranks / RankMult; |
| NBPtr->PsPtr->LrdimmRowAddrBits[Dimm] = Rows + (RankMult >> 1); |
| // |
| // Program RankDef |
| // |
| NBPtr->SetBitField (NBPtr, BFRankDef0 + Dimm, (RankMult == 4) ? 3 : RankMult); |
| // |
| // If LrdimmRowAddressBits > 16, then we must be using some CS signals for rank |
| // multiplication. If this is the case, then we want to clear the CSPresent bits |
| // that correspond to those chipselects. |
| // If there are 3 DIMMs per channel, then it will always be CS67, if there are |
| // 2DPCH, then DIMM0 will use CS45, and DIMM1 will use CS67. |
| // |
| if ((ChannelPtr->LrDimmLogicalRanks[Dimm] < 4) && (Dimm >= NumDimmslots)) { |
| NBPtr->DCTPtr->Timings.CsPresent &= ~(0x3 << (Dimm << 1)); |
| ChannelPtr->LrDimmRankMult[Dimm] = 0; |
| ChannelPtr->LrDimmLogicalRanks[Dimm] = 0; |
| NBPtr->PsPtr->LrdimmRowAddrBits[Dimm] = 0; |
| } else { |
| IDS_HDT_CONSOLE_DEBUG_CODE ( |
| if (Dimm < NumDimmslots) { |
| IDS_HDT_CONSOLE (MEM_FLOW,"\tDimm %d: Log. Ranks:%d Phys. Ranks:%d RowAddrBits:%d RankMult:%d\n", |
| Dimm, |
| ChannelPtr->LrDimmLogicalRanks[Dimm], |
| ChannelPtr->LrdimmPhysicalRanks[Dimm], |
| NBPtr->PsPtr->LrdimmRowAddrBits[Dimm], |
| RankMult |
| ); |
| } |
| ); |
| } |
| } |
| return RetVal; |
| } |
| |
| /* ----------------------------------------------------------------------------- |
| * |
| * This function performs buffer to DRAM training for LRDIMMs |
| * |
| * @param[in,out] *TechPtr - Pointer to the MEM_TECH_BLOCK |
| * @param[in,out] OptParam - Optional parameter |
| * |
| * @return TRUE |
| */ |
| |
| BOOLEAN |
| STATIC |
| MemTLrdimmBuf2DramTrain3 ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN OUT VOID *OptParam |
| ) |
| { |
| MEM_DATA_STRUCT *MemPtr; |
| MEM_NB_BLOCK *NBPtr; |
| UINT8 Dct; |
| UINT8 Dimm; |
| UINT8 ChipSel; |
| UINT16 DimmMask; |
| UINT8 i; |
| |
| NBPtr = TechPtr->NBPtr; |
| MemPtr = NBPtr->MemPtr; |
| |
| if (NBPtr->MCTPtr->Status[SbLrdimms]) { |
| IDS_HDT_CONSOLE (MEM_FLOW, "\nStart Buffer to DRAM training\n"); |
| for (Dct = 0; Dct < NBPtr->DctCount; Dct++) { |
| IDS_HDT_CONSOLE (MEM_STATUS, "\tDct %d\n", Dct); |
| NBPtr->SwitchDCT (NBPtr, Dct); |
| // |
| // ODM needs to be set after Dram Init |
| // |
| if (NBPtr->StartupSpeed == NBPtr->DCTPtr->Timings.Speed) { |
| for (ChipSel = 1; ChipSel < MAX_CS_PER_CHANNEL; ChipSel += 2) { |
| if ((NBPtr->DCTPtr->Timings.CsPresent & ((UINT16)1 << ChipSel)) != 0) { |
| if ((NBPtr->DCTPtr->Timings.DimmMirrorPresent & (1 << (ChipSel >> 1))) != 0) { |
| NBPtr->SetBitField (NBPtr, BFCSBaseAddr0Reg + ChipSel, ((NBPtr->GetBitField (NBPtr, BFCSBaseAddr0Reg + ChipSel)) | ((UINT32)1 << BFOnDimmMirror ))); |
| } |
| } |
| } |
| } |
| // |
| // Buffer to DRAM training |
| // |
| for (Dimm = 0; Dimm < MAX_DIMMS_PER_CHANNEL; Dimm++) { |
| DimmMask = (UINT16)1 << Dimm; |
| if ((NBPtr->ChannelPtr->LrDimmPresent & DimmMask) != 0) { |
| IDS_HDT_CONSOLE (MEM_STATUS, "\t\nDimm %d\n", Dimm); |
| // |
| // Select the Target Chipselects |
| // |
| NBPtr->SetBitField (NBPtr, BFMrsChipSel, (Dimm << 1)); |
| NBPtr->SetBitField (NBPtr, BFCtrlWordCS, 3 << (Dimm << 1)); |
| |
| NBPtr->SetBitField (NBPtr, BFLrDimmErrOutMonEn, 1); |
| MemTSendMBCtlWord3 (TechPtr, F2, RC3, 8); |
| // Send F0RC12 with data = 0010b. |
| MemTSendMBCtlWord3 (TechPtr, F0, RC12, 2); |
| // |
| // Wait until D18F2xA0_dct[1:0][RcvParErr]=0 or tCAL * the number of physical ranks expires. |
| // |
| IDS_HDT_CONSOLE (MEM_FLOW, "\t\tWaiting %d ms...\n", 10 * NBPtr->ChannelPtr->LrdimmPhysicalRanks[Dimm]); |
| for (i = 0; i < (NBPtr->ChannelPtr->LrdimmPhysicalRanks[Dimm] * 10); i++) { |
| MemUWait10ns (1000000, MemPtr); |
| // |
| // @todo: Provide option for polling RcvParErr to optimize DRAM bus timing. |
| // |
| } |
| IDS_HDT_CONSOLE (MEM_FLOW, "\n\t\tRcvParErr = %02x\n", NBPtr->GetBitField (NBPtr, BFRcvParErr)); |
| NBPtr->SetBitField (NBPtr, BFLrDimmErrOutMonEn, 0); |
| MemTSendMBCtlWord3 (TechPtr, F2, RC3, 0); |
| // Configure for normal operation: Send F0RC12 with data = 0000b. |
| MemTSendMBCtlWord3 (TechPtr, F0, RC12, 0); |
| } |
| } |
| } |
| IDS_HDT_CONSOLE (MEM_FLOW, "\nEnd Buffer to DRAM training\n"); |
| } |
| return TRUE; |
| } |
| |
| /* -----------------------------------------------------------------------------*/ |
| /** |
| * |
| * This function copies trained delays of the first rank of a QR LRDIMM to the third rank |
| * |
| * @param[in,out] *TechPtr - Pointer to the MEM_TECH_BLOCK |
| * @param[in,out] OptParam - Optional parameter |
| * |
| * @return TRUE |
| */ |
| |
| BOOLEAN |
| STATIC |
| MemTLrdimmSyncTrainedDlys ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN OUT VOID *OptParam |
| ) |
| { |
| UINT8 i; |
| UINT8 Dimm; |
| UINT8 Dct; |
| MEM_NB_BLOCK *NBPtr; |
| CH_DEF_STRUCT *ChannelPtr; |
| UINT16 WrDqsDly; |
| UINT16 RcvEnDly; |
| UINT16 RdDqsDly; |
| UINT16 WrDatDly; |
| UINT8 RdDqs2dDly; |
| NBPtr = TechPtr->NBPtr; |
| |
| if (NBPtr->MCTPtr->Status[SbLrdimms]) { |
| IDS_HDT_CONSOLE (MEM_STATUS, "\tSync LRDIMM Delays to remaining ranks.\n"); |
| for (Dct = 0; Dct < NBPtr->DctCount; Dct++) { |
| IDS_HDT_CONSOLE (MEM_STATUS, "\tDct %d\n", Dct); |
| NBPtr->SwitchDCT (NBPtr, Dct); |
| ChannelPtr = NBPtr->ChannelPtr; |
| for (Dimm = 0; Dimm < 2; Dimm++) { |
| if (ChannelPtr->LrDimmLogicalRanks[Dimm] > 2) { |
| // If logical QR LRDIMM, copy trained delays from first rank to third rank |
| IDS_HDT_CONSOLE (MEM_FLOW, "\t\tDimm %d -> Dimm %d\n",Dimm, Dimm + 2); |
| for (i = 0; i < TechPtr->DlyTableWidth (); i++) { |
| WrDqsDly = ChannelPtr->WrDqsDlys[Dimm * TechPtr->DlyTableWidth () + i]; |
| NBPtr->SetTrainDly (NBPtr, AccessWrDqsDly, DIMM_BYTE_ACCESS (Dimm + 2, i), WrDqsDly); |
| ChannelPtr->WrDqsDlys[(Dimm + 2) * TechPtr->DlyTableWidth () + i] = (UINT8)WrDqsDly; |
| |
| RcvEnDly = ChannelPtr->RcvEnDlys[Dimm * TechPtr->DlyTableWidth () + i]; |
| NBPtr->SetTrainDly (NBPtr, AccessRcvEnDly, DIMM_BYTE_ACCESS (Dimm + 2, i), RcvEnDly); |
| ChannelPtr->RcvEnDlys[(Dimm + 2) * TechPtr->DlyTableWidth () + i] = RcvEnDly; |
| |
| RdDqsDly = ChannelPtr->RdDqsDlys[Dimm * TechPtr->DlyTableWidth () + i]; |
| NBPtr->SetTrainDly (NBPtr, AccessRdDqsDly, DIMM_BYTE_ACCESS (Dimm + 2, i), RdDqsDly); |
| ChannelPtr->RdDqsDlys[(Dimm + 2) * TechPtr->DlyTableWidth () + i] = (UINT8)RdDqsDly; |
| |
| WrDatDly = ChannelPtr->WrDatDlys[Dimm * TechPtr->DlyTableWidth () + i]; |
| NBPtr->SetTrainDly (NBPtr, AccessWrDatDly, DIMM_BYTE_ACCESS (Dimm + 2, i), WrDatDly); |
| ChannelPtr->WrDatDlys[(Dimm + 2) * TechPtr->DlyTableWidth () + i] = (UINT8)WrDatDly; |
| } |
| if ((ChannelPtr->DimmNibbleAccess & (1 << Dimm)) != 0) { |
| // |
| // If 2D x4 (Not Currently POR for LRDIMMs) |
| // |
| for (i = 0; i < MAX_NUMBER_LANES; i++) { |
| if (ChannelPtr->LrDimmLogicalRanks[Dimm] > 2) { |
| // If logical QR LRDIMM, copy trained delays from first rank to third rank |
| RdDqs2dDly = ChannelPtr->RdDqs2dDlys[Dimm * MAX_NUMBER_LANES + i]; |
| NBPtr->SetTrainDly (NBPtr, excel845 , DIMM_NBBL_ACCESS (Dimm + 2, i), |
| ChannelPtr->RdDqs2dDlys[Dimm * MAX_NUMBER_LANES + i]); |
| ChannelPtr->RdDqs2dDlys[(Dimm + 2) * MAX_NUMBER_LANES + i] = (UINT8)RdDqs2dDly; |
| } |
| } |
| } |
| } |
| } |
| } |
| return TRUE; |
| } else { |
| return FALSE; |
| } |
| } |
| |
| /* -----------------------------------------------------------------------------*/ |
| /** |
| * |
| * This function performs LRDIMM specific tasks during Dimm Presence detection |
| * |
| * @param[in,out] *TechPtr - Pointer to the MEM_TECH_BLOCK |
| * @param[in,out] *DimmID - Dimm ID |
| * |
| * @return TRUE |
| * |
| */ |
| |
| BOOLEAN |
| STATIC |
| MemTLrdimmPresence ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN OUT VOID *DimmID |
| ) |
| { |
| MEM_NB_BLOCK *NBPtr; |
| UINT32 BufferID; |
| UINT8 Dimm; |
| NBPtr = TechPtr->NBPtr; |
| Dimm = *(UINT8*) DimmID; |
| |
| BufferID = MemTLrDimmGetBufferID (TechPtr, Dimm); |
| if ((BufferID == 0x0020B304) || (BufferID == 0x0020B380)) { |
| IDS_HDT_CONSOLE (MEM_FLOW, "\tDimm %d: Unsupported LRDIMM Buffer Revision\n", Dimm); |
| PutEventLog (AGESA_WARNING, MEM_WARNING_UNSUPPORTED_LRDIMM, NBPtr->Node, NBPtr->Dct, NBPtr->Channel, Dimm, &NBPtr->MemPtr->StdHeader); |
| NBPtr->DCTPtr->Timings.CsTestFail |= (UINT16)0x3 << (Dimm << 1); |
| } |
| return TRUE; |
| } |
| |
| /* -----------------------------------------------------------------------------*/ |
| /** |
| * |
| * This function returns LRDIMM Buffer ID Info from the SPD |
| * |
| * |
| * @param[in,out] *TechPtr - Pointer to the Technology Block |
| * @param[in] Dimm - Dimm number |
| * |
| * @return Buffer ID Information |
| * |
| */ |
| |
| UINT32 |
| STATIC |
| MemTLrDimmGetBufferID ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN UINT8 Dimm |
| ) |
| { |
| UINT8 *SpdBufferPtr; |
| UINT32 BufferID; |
| |
| BufferID = 0; |
| MemTGetDimmSpdBuffer3 (TechPtr, &SpdBufferPtr, Dimm); |
| BufferID = (SpdBufferPtr[64] << 16) | (SpdBufferPtr[66] << 8) | (SpdBufferPtr[65]); |
| return BufferID; |
| } |
| |
| /* -----------------------------------------------------------------------------*/ |
| /** |
| * |
| * This function implements special case Initialization hooks for LRDIMMs |
| * |
| * @param[in] TechPtr - Tech Block Pointer |
| * @param[in] Entrypoint - Entrypoint to indicate when this hook is called |
| * @param[in] Dimm - Dimm being configured when hook is called |
| * @param[in] OptParam - Not Used |
| */ |
| |
| VOID |
| STATIC |
| MemTLrdimmInitHook ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN LRDIMM_HOOK_ENTRYPOINT Entrypoint, |
| IN UINT8 Dimm, |
| IN OUT VOID *OptParam |
| ) |
| { |
| MEM_NB_BLOCK *NBPtr; |
| UINT8 i; |
| CONST UINT16 AfterTstabRcwTable[] = { |
| 0x0270, 0x0000, |
| 0x0122, 0x0074, |
| 0x0124, 0x009B, |
| 0x0126, 0x00C2, |
| 0x0128, 0x00E8, |
| 0x01D2, 0x5942, |
| 0x01D4, 0x836D, |
| 0x01CE, 0x5942, |
| 0x01D0, 0x836D, |
| 0x01D6, 0x017F, |
| 0x01D8, 0x0000, |
| 0x01F0, 0x008E, |
| 0x01F2, 0x00BA, |
| 0x01F4, 0x00E8, |
| 0x01F6, 0x0114, |
| 0x0B40, 0x7054, |
| 0x0B42, 0xA48A, |
| 0x0B3C, 0x7054, |
| 0x0B3E, 0xA48A, |
| 0x0B38, 0x0100, |
| 0x0B3A, 0x0000, |
| |
| 0x0274, 0x55AA, |
| 0x3012, 0x0080, |
| 0x3018, 0x6B80 |
| }; |
| if (MemTLrDimmGetBufferID (TechPtr, Dimm) != 0x0021B304) { |
| return; |
| } |
| NBPtr = TechPtr->NBPtr; |
| switch (Entrypoint) { |
| case AFTER_TSTAB: |
| MemTSendMBCtlWord3 (TechPtr, F14, RC0, 0xB); |
| for ( i = 0 ; i < (sizeof (AfterTstabRcwTable) / sizeof (UINT16)); i += 2 ) { |
| MemTSendExtMBCtlWord3 (TechPtr, AfterTstabRcwTable[i], AfterTstabRcwTable[i + 1], 2); |
| } |
| break; |
| default: |
| // |
| // If a hook entrypoint is called, it should have a case for it. |
| // |
| ASSERT (FALSE); |
| break; |
| } |
| } |