/* $NoKeywords:$ */ /** * @file * * mtspd3.c * * Technology SPD supporting functions for DDR3 * * @xrefitem bom "File Content Label" "Release Content" * @e project: AGESA * @e sub-project: (Mem/Tech/DDR3) * @e \$Revision: 49133 $ @e \$Date: 2011-03-17 16:54:42 +0800 (Thu, 17 Mar 2011) $ * **/ /***************************************************************************** * * Copyright (c) 2011, 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 "AdvancedApi.h" #include "Ids.h" #include "mport.h" #include "mm.h" #include "mn.h" #include "mt.h" #include "mt3.h" #include "mu.h" #include "mtspd3.h" #include "mftds.h" #include "GeneralServices.h" #include "Filecode.h" CODE_GROUP (G1_PEICC) RDATA_GROUP (G1_PEICC) #define FILECODE PROC_MEM_TECH_DDR3_MTSPD3_FILECODE /*---------------------------------------------------------------------------- * DEFINITIONS AND MACROS * *---------------------------------------------------------------------------- */ /*---------------------------------------------------------------------------- * TYPEDEFS AND STRUCTURES * *---------------------------------------------------------------------------- */ /*---------------------------------------------------------------------------- * PROTOTYPES OF LOCAL FUNCTIONS * *---------------------------------------------------------------------------- */ BOOLEAN STATIC MemTCRCCheck3 ( IN OUT UINT8 *SPDPtr ); UINT8 STATIC MemTSPDGetTCL3 ( IN OUT MEM_TECH_BLOCK *TechPtr ); BOOLEAN STATIC MemTCheckBankAddr3 ( IN UINT8 Encode, OUT UINT8 *Index ); /*---------------------------------------------------------------------------- * EXPORTED FUNCTIONS * *---------------------------------------------------------------------------- */ extern BUILD_OPT_CFG UserOptions; /* -----------------------------------------------------------------------------*/ /** * * This function sets the DRAM mode * * @param[in,out] *TechPtr - Pointer to the MEM_TECH_BLOCK * * @return TRUE - indicates that the DRAM mode is set to DDR3 */ BOOLEAN MemTSetDramMode3 ( IN OUT MEM_TECH_BLOCK *TechPtr ) { TechPtr->NBPtr->SetBitField (TechPtr->NBPtr, BFLegacyBiosMode, 0); TechPtr->NBPtr->SetBitField (TechPtr->NBPtr, BFDdr3Mode, 1); return TRUE; } /* -----------------------------------------------------------------------------*/ /** * * This function determines if DIMMs are present. It checks checksum and interrogates the SPDs * * @param[in,out] *TechPtr - Pointer to the MEM_TECH_BLOCK * * @return TRUE - indicates that a FATAL error has not occurred * @return FALSE - indicates that a FATAL error has occurred */ BOOLEAN MemTDIMMPresence3 ( IN OUT MEM_TECH_BLOCK *TechPtr ) { UINT8 Dct; UINT8 Channel; UINT8 i; MEM_PARAMETER_STRUCT *RefPtr; UINT8 *SpdBufferPtr = NULL; DIE_STRUCT *MCTPtr; DCT_STRUCT *DCTPtr; CH_DEF_STRUCT *ChannelPtr; MEM_NB_BLOCK *NBPtr; BOOLEAN SPDCtrl; UINT8 Devwidth; UINT8 MaxDimms; UINT8 Value8; UINT16 DimmMask; NBPtr = TechPtr->NBPtr; RefPtr = NBPtr->RefPtr; MCTPtr = NBPtr->MCTPtr; SPDCtrl = UserOptions.CfgIgnoreSpdChecksum; for (Dct = 0; Dct < NBPtr->DctCount; Dct++) { NBPtr->SwitchDCT (NBPtr, Dct); DCTPtr = NBPtr->DCTPtr; for (Channel = 0; Channel < NBPtr->ChannelCount; Channel++) { NBPtr->SwitchChannel (NBPtr, Channel); ChannelPtr = NBPtr->ChannelPtr; ChannelPtr->DimmQrPresent = 0; // // Get the maximum number of DIMMs // MaxDimms = MAX_DIMMS_PER_CHANNEL; for (i = 0; i < MaxDimms; i++) { // Bitmask representing dimm #i. DimmMask = (UINT16)1 << i; // if (MemTGetDimmSpdBuffer3 (TechPtr, &SpdBufferPtr, i)) { MCTPtr->DimmPresent |= DimmMask; // // Check for valid checksum value // AGESA_TESTPOINT (TpProcMemSPDChecking, &(NBPtr->MemPtr->StdHeader)); if (SpdBufferPtr[SPD_TYPE] == JED_DDR3SDRAM) { ChannelPtr->ChDimmValid |= DimmMask; MCTPtr->DimmValid |= DimmMask; } else { // Current socket is set up to only support DDR3 dimms. IDS_ERROR_TRAP; } if (!MemTCRCCheck3 (SpdBufferPtr) && !SPDCtrl) { // // NV_SPDCHK_RESTRT is set to 0, // cannot ignore faulty SPD checksum // // Indicate checksum error ChannelPtr->DimmSpdCse |= DimmMask; PutEventLog (AGESA_ERROR, MEM_ERROR_CHECKSUM_NV_SPDCHK_RESTRT_ERROR, NBPtr->Node, NBPtr->Dct, NBPtr->Channel, 0, &NBPtr->MemPtr->StdHeader); SetMemError (AGESA_ERROR, MCTPtr); } // // Check module type information. // if (SpdBufferPtr[SPD_DIMM_TYPE] == JED_LRDIMM) { // // LRDIMMS // ChannelPtr->LrDimmPresent |= DimmMask; MCTPtr->LrDimmPresent |= DimmMask; if (!UserOptions.CfgMemoryLRDimmCapable) { PutEventLog (AGESA_WARNING, MEM_WARNING_UNSUPPORTED_LRDIMM, NBPtr->Node, NBPtr->Dct, NBPtr->Channel, 0, &NBPtr->MemPtr->StdHeader); IDS_ERROR_TRAP; } } if (SpdBufferPtr[SPD_DIMM_TYPE] == JED_RDIMM || SpdBufferPtr[SPD_DIMM_TYPE] == JED_MINIRDIMM) { // // RDIMMS // ChannelPtr->RegDimmPresent |= DimmMask; MCTPtr->RegDimmPresent |= DimmMask; if (!UserOptions.CfgMemoryRDimmCapable) { PutEventLog (AGESA_WARNING, MEM_WARNING_UNSUPPORTED_RDIMM, NBPtr->Node, NBPtr->Dct, NBPtr->Channel, 0, &NBPtr->MemPtr->StdHeader); IDS_ERROR_TRAP; } } if ((SpdBufferPtr[SPD_DIMM_TYPE] == JED_UDIMM) && !UserOptions.CfgMemoryUDimmCapable) { PutEventLog (AGESA_WARNING, MEM_WARNING_UNSUPPORTED_UDIMM, NBPtr->Node, NBPtr->Dct, NBPtr->Channel, 0, &NBPtr->MemPtr->StdHeader); IDS_ERROR_TRAP; } if (SpdBufferPtr[SPD_DIMM_TYPE] == JED_SODIMM) { ChannelPtr->SODimmPresent |= DimmMask; if (!UserOptions.CfgMemorySODimmCapable) { PutEventLog (AGESA_WARNING, MEM_WARNING_UNSUPPORTED_SODIMM, NBPtr->Node, NBPtr->Dct, NBPtr->Channel, 0, &NBPtr->MemPtr->StdHeader); IDS_ERROR_TRAP; } } // // Check error correction type // if ((SpdBufferPtr[SPD_ECCBITS] & JED_ECC) != 0) { MCTPtr->DimmEccPresent |= DimmMask; // Dimm has ECC } // // Get the Dimm width data // Devwidth = SpdBufferPtr[SPD_DEV_WIDTH] & 0x7; switch (Devwidth) { case 0: ChannelPtr->Dimmx4Present |= DimmMask; Devwidth = 4; break; case 1: ChannelPtr->Dimmx8Present |= DimmMask; Devwidth = 8; break; case 2: ChannelPtr->Dimmx16Present |= DimmMask; Devwidth = 16; break; default: IDS_ERROR_TRAP; } // // Check for 'analysis probe installed' // if (SpdBufferPtr[SPD_ATTRIB] & JED_PROBE_MSK) // // Determine the geometry of the DIMM module // if (SpdBufferPtr[SPD_DM_BANKS] & SP_DPL_BIT) // // specify the number of ranks // Value8 = ((SpdBufferPtr[SPD_RANKS] >> 3) & 0x07) + 1; if (Value8 == 5) { // Octal Rank Value8 = 8; } // // For LRDIMMS we will assume that if there are at least 4 Physical ranks, then it Could be used // as a QR RDIMM with a rank Mux of x1 and therefore all four CS will be used. So an 8R LRDIMM will // be marked as a QR even if Rank multiplication allows it to use only 2 logical ranks. // if ((ChannelPtr->LrDimmPresent & DimmMask) != 0) { // // LRDIMM Physical Ranks // ChannelPtr->LrdimmPhysicalRanks[i] = Value8; } if (Value8 > 2) { if (!UserOptions.CfgMemoryQuadRankCapable) { PutEventLog (AGESA_WARNING, MEM_WARNING_UNSUPPORTED_QRDIMM, NBPtr->Node, NBPtr->Dct, NBPtr->Channel, 0, &NBPtr->MemPtr->StdHeader); } // // Mark this Dimm as Quad Rank // ChannelPtr->DimmQrPresent |= DimmMask; Value8 = 2; } else if (Value8 == 2) { ChannelPtr->DimmDrPresent |= DimmMask; // Dual rank dimms } else { ChannelPtr->DimmSRPresent |= DimmMask; // Single rank dimms } // // Calculate bus loading per Channel if (Devwidth == 16) { Devwidth = 4; } else if (Devwidth == 4) { Devwidth = 16; } // // Double Addr bus load value for dual rank DIMMs (Unless LRDIMM) // if (((ChannelPtr->LrDimmPresent & DimmMask) == 0) && (Value8 == 2) ) { Devwidth = Devwidth << 1; } // ChannelPtr->Ranks = ChannelPtr->Ranks + Value8; ChannelPtr->Loads = ChannelPtr->Loads + Devwidth; if ((i < 2) || ((ChannelPtr->DimmQrPresent & DimmMask) == 0)) { ChannelPtr->Dimms++; } // // Check address mirror support for Unbuffered Dimms or LRDimms // if ((ChannelPtr->RegDimmPresent & DimmMask) == 0) { if ((SpdBufferPtr[SPD_ADDRMAP] & 1) != 0) { ChannelPtr->DimmMirrorPresent |= DimmMask; } } // // Get byte62: Reference Raw Card information // ChannelPtr->RefRawCard[i] = SpdBufferPtr[SPD_RAWCARD] & 0x1F; // // Get control word values for RC3, RC4 and RC5 // ChannelPtr->CtrlWrd03[i] = SpdBufferPtr[SPD_CTLWRD03] >> 4; ChannelPtr->CtrlWrd04[i] = SpdBufferPtr[SPD_CTLWRD04] & 0x0F; ChannelPtr->CtrlWrd05[i] = SpdBufferPtr[SPD_CTLWRD05] >> 4; // // Temporarily store info. of SPD byte 63 into CtrlWrd02(s), // and they will be used late to calculate real RC2 and RC8 value // ChannelPtr->CtrlWrd02[i] = SpdBufferPtr[SPD_ADDRMAP] & 0x03; // // Copy the number of registers to the Ps Block to persist across frequency changes // NBPtr->PsPtr->NumOfReg[i] = SpdBufferPtr[SPD_ADDRMAP] & 0x03; // // Workaround for early revisions of DIMMs which SPD byte 63 is 0 // if (NBPtr->PsPtr->NumOfReg[i] == JED_UNDEFINED) { NBPtr->PsPtr->NumOfReg[i] = 1; } } // if DIMM present } // Dimm loop if (Channel == 0) { DCTPtr->Timings.DctDimmValid = ChannelPtr->ChDimmValid; DCTPtr->Timings.DimmMirrorPresent = ChannelPtr->DimmMirrorPresent; DCTPtr->Timings.DimmSpdCse = ChannelPtr->DimmSpdCse; DCTPtr->Timings.DimmQrPresent = ChannelPtr->DimmQrPresent; DCTPtr->Timings.DimmDrPresent = ChannelPtr->DimmDrPresent; DCTPtr->Timings.DimmSRPresent = ChannelPtr->DimmSRPresent; DCTPtr->Timings.Dimmx4Present = ChannelPtr->Dimmx4Present; DCTPtr->Timings.Dimmx8Present = ChannelPtr->Dimmx8Present; DCTPtr->Timings.Dimmx16Present = ChannelPtr->Dimmx16Present; } if ((Channel != 1) || (Dct != 1)) { MCTPtr->DimmPresent <<= 8; MCTPtr->DimmValid <<= 8; MCTPtr->RegDimmPresent <<= 8; MCTPtr->LrDimmPresent <<= 8; MCTPtr->DimmEccPresent <<= 8; MCTPtr->DimmParPresent <<= 8; } } // Channel loop } // DCT loop // If we have DIMMs, some further general characteristics checking if (MCTPtr->DimmValid != 0) { // If there are registered dimms, all the dimms must be registered if (MCTPtr->RegDimmPresent == MCTPtr->DimmValid) { // All dimms registered MCTPtr->Status[SbRegistered] = TRUE; MCTPtr->Status[SbParDimms] = TRUE; // All DDR3 RDIMMs are parity capable TechPtr->SetDqsEccTmgs = MemTSetDQSEccTmgsRDdr3; // Change the function pointer for DQS ECC timing } else if (MCTPtr->RegDimmPresent != 0) { // We have an illegal DIMM mismatch PutEventLog (AGESA_FATAL, MEM_ERROR_MODULE_TYPE_MISMATCH_DIMM, NBPtr->Node, NBPtr->Dct, NBPtr->Channel, 0, &NBPtr->MemPtr->StdHeader); SetMemError (AGESA_FATAL, MCTPtr); } // If there are LrDimms, all the dimms must be LrDimms if (MCTPtr->LrDimmPresent == MCTPtr->DimmValid) { // All dimms LRDIMMs MCTPtr->Status[SbLrdimms] = TRUE; } else if (MCTPtr->LrDimmPresent != 0) { // We have an illegal DIMM mismatch PutEventLog (AGESA_FATAL, MEM_ERROR_MODULE_TYPE_MISMATCH_DIMM, NBPtr->Node, NBPtr->Dct, NBPtr->Channel, 0, &NBPtr->MemPtr->StdHeader); SetMemError (AGESA_FATAL, MCTPtr); } // check the ECC capability of the DIMMs if (MCTPtr->DimmEccPresent == MCTPtr->DimmValid) { MCTPtr->Status[SbEccDimms] = TRUE; // All dimms ECC capable } } else { } NBPtr->SwitchDCT (NBPtr, 0); NBPtr->SwitchChannel (NBPtr, 0); return (BOOLEAN) (MCTPtr->ErrCode < AGESA_FATAL); } /* -----------------------------------------------------------------------------*/ /** * * This function finds the maximum frequency that each channel is capable to run at. * * @param[in,out] *TechPtr - Pointer to the MEM_TECH_BLOCK * * @return TRUE - indicates that a FATAL error has not occurred * @return FALSE - indicates that a FATAL error has occurred */ BOOLEAN MemTSPDGetTargetSpeed3 ( IN OUT MEM_TECH_BLOCK *TechPtr ) { UINT8 *SpdBufferPtr = NULL; UINT8 Dimm; UINT8 Dct; UINT8 Channel; INT32 MTB_ps; INT32 FTB_ps; INT32 TCKmin_ps; INT32 Value32; MEM_NB_BLOCK *NBPtr; DCT_STRUCT *DCTPtr; CH_DEF_STRUCT *ChannelPtr; NBPtr = TechPtr->NBPtr; for (Dct = 0; Dct < NBPtr->DctCount; Dct++) { NBPtr->SwitchDCT (NBPtr, Dct); DCTPtr = NBPtr->DCTPtr; TCKmin_ps = 0; for (Channel = 0; Channel < NBPtr->ChannelCount; Channel++) { NBPtr->SwitchChannel (NBPtr, Channel); ChannelPtr = NBPtr->ChannelPtr; for (Dimm = 0; Dimm < MAX_DIMMS_PER_CHANNEL; Dimm++) { if ((ChannelPtr->ChDimmValid & ((UINT8)1 << Dimm)) != 0) { MemTGetDimmSpdBuffer3 (TechPtr, &SpdBufferPtr, Dimm); // Determine tCKmin(all) which is the largest tCKmin // value for all modules on the memory Channel (SPD byte 12). // MTB_ps = ((INT32) SpdBufferPtr[SPD_DIVIDENT] * 1000) / SpdBufferPtr[SPD_DIVISOR]; FTB_ps = (SpdBufferPtr[SPD_FTB] >> 4) / (SpdBufferPtr[SPD_FTB] & 0xF); Value32 = (MTB_ps * SpdBufferPtr[SPD_TCK]) + (FTB_ps * (INT8) SpdBufferPtr[SPD_TCK_FTB]) ; if (TCKmin_ps < Value32) { TCKmin_ps = Value32; } } } } if (TCKmin_ps <= 1071) { DCTPtr->Timings.TargetSpeed = DDR1866_FREQUENCY; } else if (TCKmin_ps <= 1250) { DCTPtr->Timings.TargetSpeed = DDR1600_FREQUENCY; } else if (TCKmin_ps <= 1500) { DCTPtr->Timings.TargetSpeed = DDR1333_FREQUENCY; } else if (TCKmin_ps <= 1875) { DCTPtr->Timings.TargetSpeed = DDR1066_FREQUENCY; } else if (TCKmin_ps <= 2500) { DCTPtr->Timings.TargetSpeed = DDR800_FREQUENCY; } else { DCTPtr->Timings.TargetSpeed = DDR667_FREQUENCY; } } // Ensure the target speed can be applied to all channels of the current node NBPtr->SyncTargetSpeed (NBPtr); // Set the start-up frequency for (Dct = 0; Dct < NBPtr->DctCount; Dct++) { NBPtr->SwitchDCT (NBPtr, Dct); NBPtr->DCTPtr->Timings.Speed = TechPtr->NBPtr->StartupSpeed; } return (BOOLEAN) (NBPtr->MCTPtr->ErrCode < AGESA_FATAL); } /* -----------------------------------------------------------------------------*/ /** * * This function check the symmetry of DIMM pairs (DIMM on Channel A matching with * DIMM on Channel B), the overall DIMM population, and determine the width mode: * 64-bit, 64-bit muxed, 128-bit. * * @param[in,out] *TechPtr - Pointer to the MEM_TECH_BLOCK * * @return TRUE - indicates that a FATAL error has not occurred * @return FALSE - indicates that a FATAL error has occurred */ BOOLEAN MemTSPDCalcWidth3 ( IN OUT MEM_TECH_BLOCK *TechPtr ) { UINT8 *SpdBufferAPtr = NULL; UINT8 *SpdBufferBPtr = NULL; MEM_NB_BLOCK *NBPtr; DIE_STRUCT *MCTPtr; DCT_STRUCT *DCTPtr; UINT8 i; UINT16 DimmMask; UINT8 UngangMode; NBPtr = TechPtr->NBPtr; MCTPtr = NBPtr->MCTPtr; DCTPtr = NBPtr->DCTPtr; UngangMode = UserOptions.CfgMemoryModeUnganged; // Does not support ganged mode for DDR3 dimms ASSERT (UngangMode); IDS_OPTION_HOOK (IDS_GANGING_MODE, &UngangMode, &(NBPtr->MemPtr->StdHeader)); // Check symmetry of channel A and channel B dimms for 128-bit mode // capability. // AGESA_TESTPOINT (TpProcMemModeChecking, &(NBPtr->MemPtr->StdHeader)); i = 0; if (!UngangMode) { if (MCTPtr->DctData[0].Timings.DctDimmValid == MCTPtr->DctData[1].Timings.DctDimmValid) { for (; i < MAX_DIMMS_PER_CHANNEL; i++) { DimmMask = (UINT16)1 << i; if ((DCTPtr->Timings.DctDimmValid & DimmMask) != 0) { NBPtr->SwitchDCT (NBPtr, 0); MemTGetDimmSpdBuffer3 (TechPtr, &SpdBufferAPtr, i); NBPtr->SwitchDCT (NBPtr, 1); MemTGetDimmSpdBuffer3 (TechPtr, &SpdBufferBPtr, i); // compare rows and columns if ((SpdBufferAPtr[SPD_ROW_SZ]&0x3F) != (SpdBufferBPtr[SPD_ROW_SZ]&0x3F)) { break; } if ((SpdBufferAPtr[SPD_DENSITY]&0x0F) != (SpdBufferBPtr[SPD_DENSITY]&0x0F)) { break; } // compare ranks and devwidth if ((SpdBufferAPtr[SPD_DEV_WIDTH]&0x7F) != (SpdBufferBPtr[SPD_DEV_WIDTH]&0x7F)) { break; } } } } if (i < MAX_DIMMS_PER_CHANNEL) { PutEventLog (AGESA_ALERT, MEM_ALERT_ORG_MISMATCH_DIMM, NBPtr->Node, NBPtr->Dct, NBPtr->Channel, 0, &NBPtr->MemPtr->StdHeader); SetMemError (AGESA_ALERT, MCTPtr); } else { NBPtr->Ganged = TRUE; MCTPtr->GangedMode = TRUE; MCTPtr->Status[Sb128bitmode] = TRUE; NBPtr->SetBitField (NBPtr, BFDctGangEn, 1); } } return (BOOLEAN) (MCTPtr->ErrCode < AGESA_FATAL); } /* -----------------------------------------------------------------------------*/ /** * * Initialize DCT Timing registers as per DIMM SPD. * For primary timing (T, CL) use best case T value. * For secondary timing params., use most aggressive settings * of slowest DIMM. * * Note: * There are three components to determining "maximum frequency": SPD component, * Bus load component, and "Preset" max frequency component. * The SPD component is a function of the min cycle time specified by each DIMM, * and the interaction of cycle times from all DIMMs in conjunction with CAS * latency. The SPD component only applies when user timing mode is 'Auto'. * * The Bus load component is a limiting factor determined by electrical * characteristics on the bus as a result of varying number of device loads. The * Bus load component is specific to each platform but may also be a function of * other factors. The bus load component only applies when user timing mode is * ' Auto'. * * The Preset component is subdivided into three items and is the minimum of * the set: Silicon revision, user limit setting when user timing mode is 'Auto' and * memclock mode is 'Limit', OEM build specification of the maximum frequency. * The Preset component only applies when user timing mode is 'Auto'. * * @param[in,out] *TechPtr - Pointer to the MEM_TECH_BLOCK * * @return TRUE - indicates that a FATAL error has not occurred * @return FALSE - indicates that a FATAL error has occurred */ BOOLEAN MemTAutoCycTiming3 ( IN OUT MEM_TECH_BLOCK *TechPtr ) { CONST UINT8 SpdIndexes[] = { SPD_TRCD, SPD_TRP, SPD_TRTP, SPD_TRAS, SPD_TRC, SPD_TWR, SPD_TRRD, SPD_TWTR, SPD_TFAW }; CONST UINT8 SpdFTBIndexes[] = { SPD_TRCD_FTB, SPD_TRP_FTB, 0, 0, SPD_TRC_FTB, 0, 0, 0, 0 }; UINT8 *SpdBufferPtr = NULL; INT32 MiniMaxTmg[GET_SIZE_OF (SpdIndexes)]; UINT8 MiniMaxTrfc[4]; DIE_STRUCT *MCTPtr; DCT_STRUCT *DCTPtr; MEM_NB_BLOCK *NBPtr; UINT16 DimmMask; INT32 Value32; INT32 MTB_ps; INT32 FTB_ps; INT32 TCK_ps; UINT8 i; UINT8 j; UINT8 Value8; UINT8 *StatTmgPtr; UINT16 *StatDimmTmgPtr; NBPtr = TechPtr->NBPtr; MCTPtr = NBPtr->MCTPtr; DCTPtr = NBPtr->DCTPtr; // initialize mini-max arrays for (j = 0; j < GET_SIZE_OF (MiniMaxTmg); j++) { MiniMaxTmg[j] = 0; } for (j = 0; j < GET_SIZE_OF (MiniMaxTrfc); j++) { MiniMaxTrfc[j] = 0; } // ====================================================================== // Get primary timing (CAS Latency and Cycle Time) // ====================================================================== // Get OEM specific load variant max // //====================================================================== // Gather all DIMM mini-max values for cycle timing data //====================================================================== // DimmMask = 1; for (i = 0; i < (MAX_CS_PER_CHANNEL / 2); i++) { if ((DCTPtr->Timings.DctDimmValid & DimmMask) != 0) { MemTGetDimmSpdBuffer3 (TechPtr, &SpdBufferPtr, i); MTB_ps = ((INT32) SpdBufferPtr[SPD_DIVIDENT] * 1000) / SpdBufferPtr[SPD_DIVISOR]; FTB_ps = (SpdBufferPtr[SPD_FTB] >> 4) / (SpdBufferPtr[SPD_FTB] & 0xF); for (j = 0; j < GET_SIZE_OF (SpdIndexes); j++) { Value32 = (UINT16)SpdBufferPtr[SpdIndexes[j]]; if (SpdIndexes[j] == SPD_TRC) { Value32 |= ((UINT16)SpdBufferPtr[SPD_UPPER_TRC] & 0xF0) << 4; } else if (SpdIndexes[j] == SPD_TRAS) { Value32 |= ((UINT16)SpdBufferPtr[SPD_UPPER_TRAS] & 0x0F) << 8; } else if (SpdIndexes[j] == SPD_TFAW) { Value32 |= ((UINT16)SpdBufferPtr[SPD_UPPER_TFAW] & 0x0F) << 8; } Value32 *= MTB_ps; if (SpdFTBIndexes[j] != 0) { Value32 += (FTB_ps * (INT8) SpdBufferPtr[SpdFTBIndexes[j]]) ; } if (MiniMaxTmg[j] < Value32) { MiniMaxTmg[j] = Value32; } } // get Trfc0 - Trfc3 values Value8 = SpdBufferPtr[SPD_DENSITY] & 0x0F; if (MiniMaxTrfc[i] < Value8) { MiniMaxTrfc[i] = Value8; } } DimmMask <<= 1; } // ====================================================================== // Convert DRAM CycleTiming values and store into DCT structure // ====================================================================== // TCK_ps = 1000500 / DCTPtr->Timings.Speed; StatDimmTmgPtr = &DCTPtr->Timings.DIMMTrcd; StatTmgPtr = &DCTPtr->Timings.Trcd; for (j = 0; j < GET_SIZE_OF (SpdIndexes); j++) { Value32 = MiniMaxTmg[j]; MiniMaxTmg[j] = (MiniMaxTmg[j] + TCK_ps - 1) / TCK_ps; StatDimmTmgPtr[j] = (UINT16) (Value32 / (1000 / 40)); StatTmgPtr[j] = (UINT8) MiniMaxTmg[j]; } DCTPtr->Timings.Trfc0 = MiniMaxTrfc[0]; DCTPtr->Timings.Trfc1 = MiniMaxTrfc[1]; DCTPtr->Timings.Trfc2 = MiniMaxTrfc[2]; DCTPtr->Timings.Trfc3 = MiniMaxTrfc[3]; DCTPtr->Timings.CasL = MemTSPDGetTCL3 (TechPtr); //====================================================================== // Program DRAM Timing values //====================================================================== // NBPtr->ProgramCycTimings (NBPtr); MemFInitTableDrive (NBPtr, MTAfterAutoCycTiming); return (BOOLEAN) (MCTPtr->ErrCode < AGESA_FATAL); } /* -----------------------------------------------------------------------------*/ /** * * This function sets the bank addressing, program Mask values and build a chip-select population map. * This routine programs PCI 0:24N:2x80 config register. * This routine programs PCI 0:24N:2x60,64,68,6C config registers (CS Mask 0-3) * * @param[in,out] *TechPtr - Pointer to the MEM_TECH_BLOCK * * @return TRUE - indicates that a FATAL error has not occurred * @return FALSE - indicates that a FATAL error has occurred */ BOOLEAN MemTSPDSetBanks3 ( IN OUT MEM_TECH_BLOCK *TechPtr ) { UINT8 *SpdBufferPtr = NULL; UINT8 i; UINT8 ChipSel; UINT8 DimmID; UINT8 Value8; UINT8 Rows; UINT8 Cols; UINT8 Ranks; UINT8 Banks; UINT32 BankAddrReg; UINT32 CsMask; UINT16 CSSpdCSE; UINT16 CSExclude; UINT16 DimmQRDR; DIE_STRUCT *MCTPtr; DCT_STRUCT *DCTPtr; MEM_NB_BLOCK *NBPtr; NBPtr = TechPtr->NBPtr; MCTPtr = NBPtr->MCTPtr; DCTPtr = NBPtr->DCTPtr; BankAddrReg = 0; CSSpdCSE = 0; CSExclude = 0; for (ChipSel = 0; ChipSel < MAX_CS_PER_CHANNEL; ChipSel += 2) { DimmID = ChipSel >> 1; DimmQRDR = (DCTPtr->Timings.DimmQrPresent) | (DCTPtr->Timings.DimmDrPresent); if ((DCTPtr->Timings.DimmSpdCse & ((UINT16) 1 << DimmID)) != 0) { CSSpdCSE |= (UINT16) ((DimmQRDR & (UINT16) 1 << DimmID) ? 3 : 1) << ChipSel; } if ((DCTPtr->Timings.DimmExclude & ((UINT16) 1 << DimmID)) != 0) { CSExclude |= (UINT16) ((DimmQRDR & (UINT16) 1 << DimmID) ? 3: 1) << ChipSel; } if ((DCTPtr->Timings.DctDimmValid & ((UINT16)1 << DimmID)) != 0) { MemTGetDimmSpdBuffer3 (TechPtr, &SpdBufferPtr, DimmID); // Get the basic data Rows = (SpdBufferPtr[SPD_ROW_SZ] >> 3) & 0x7; Cols = SpdBufferPtr[SPD_COL_SZ] & 0x7; Banks = (SpdBufferPtr[SPD_L_BANKS] >> 4) & 0x7; Ranks = ((SpdBufferPtr[SPD_RANKS] >> 3) & 0x07) + 1; if (Ranks == 5) { Ranks = 8; } // // Configure the bank encoding // Use a 6-bit key into a lookup table. // Key (index) = RRRBCC, where CC is the number of Columns minus 9, // RRR is the number of Rows minus 12, and B is the number of banks // minus 3. // Value8 = Cols; Value8 |= (Banks == 1) ? 4 : 0; Value8 |= Rows << 3; if (MemTCheckBankAddr3 (Value8, &i)) { BankAddrReg |= ((UINT32)i << (ChipSel << 1)); // Mask value=(2pow(rows+cols+banks+3)-1)>>8, // or 2pow(rows+cols+banks-5)-1 // Value8 = (Rows + 12) + (Cols + 9) + (Banks + 3) + 3 - 8; if (MCTPtr->Status[Sb128bitmode]) { Value8++; } DCTPtr->Timings.CsPresent |= (UINT16)1 << ChipSel; if (Ranks >= 2) { DCTPtr->Timings.CsPresent |= (UINT16)1 << (ChipSel + 1); } // // Determine LRDIMM Rank Multiplication // if (TechPtr->TechnologySpecificHook[LrdimmRankMultiplication] (TechPtr, &DimmID)) { // // Increase the CS Size by the rank multiplication factor // Value8 += ((NBPtr->ChannelPtr->LrDimmRankMult[DimmID]) >> 1); } CsMask = ((UINT32)1 << Value8) - 1; // Update the DRAM CS Mask for this chipselect NBPtr->SetBitField (NBPtr, BFCSMask0Reg + (ChipSel >> 1), (CsMask & NBPtr->CsRegMsk)); } else { // Dimm is not supported, as no address mapping is found. DCTPtr->Timings.CsPresent |= (UINT16)1 << ChipSel; DCTPtr->Timings.CsTestFail |= (UINT16)1 << ChipSel; if (Ranks >= 2) { DCTPtr->Timings.CsPresent |= (UINT16)1 << (ChipSel + 1); DCTPtr->Timings.CsTestFail |= (UINT16)1 << (ChipSel + 1); } PutEventLog (AGESA_ERROR, MEM_ERROR_NO_ADDRESS_MAPPING, NBPtr->Node, NBPtr->Dct, NBPtr->Channel, DimmID, &NBPtr->MemPtr->StdHeader); SetMemError (AGESA_ERROR, MCTPtr); } } } // For ranks that need to be excluded, the loading of this rank should be considered // in timing, so need to set CsPresent before setting CsTestFail if ((CSSpdCSE != 0) || (CSExclude != 0)) { NBPtr->MemPtr->ErrorHandling (MCTPtr, NBPtr->Dct, (CSSpdCSE | CSExclude), &NBPtr->MemPtr->StdHeader); } // If there are no chip selects, we have an error situation. if (DCTPtr->Timings.CsPresent == 0) { PutEventLog (AGESA_ERROR, MEM_ERROR_NO_CHIPSELECT, NBPtr->Node, NBPtr->Dct, NBPtr->Channel, 0, &NBPtr->MemPtr->StdHeader); SetMemError (AGESA_ERROR, MCTPtr); } NBPtr->SetBitField (NBPtr, BFDramBankAddrReg, BankAddrReg); return (BOOLEAN) (MCTPtr->ErrCode < AGESA_FATAL); } /* -----------------------------------------------------------------------------*/ /** * * This function returns the low bit that will be swapped to enable CS interleaving * * @param[in] BankEnc - AddrMap Bank encoding from F2x80 * @param[in] *LowBit - pointer to low bit * @param[in] *HiBit - pointer hight bit * */ VOID MemTGetCSIntLvAddr3 ( IN UINT8 BankEnc, OUT UINT8 *LowBit, OUT UINT8 *HiBit ) { CONST UINT8 ArrCodesLo[] = {0, 8, 8, 0, 0, 8, 9, 8, 9, 9, 8, 9}; CONST UINT8 ArrCodesHi[] = {0, 20, 21, 0, 0, 22, 22, 23, 23, 24, 24, 25}; ASSERT (BankEnc < GET_SIZE_OF (ArrCodesLo)); ASSERT (BankEnc < GET_SIZE_OF (ArrCodesHi)); // return ArrCodes[BankEnc]; *LowBit = ArrCodesLo[BankEnc]; *HiBit = ArrCodesHi[BankEnc]; } /*---------------------------------------------------------------------------- * LOCAL FUNCTIONS * *---------------------------------------------------------------------------- */ /* -----------------------------------------------------------------------------*/ /** * * This function determines if the checksum is correct * * @param[in] *SPDPtr - Pointer to SPD data * * @return TRUE - CRC check passes * @return FALSE - CRC check fails */ BOOLEAN STATIC MemTCRCCheck3 ( IN OUT UINT8 *SPDPtr ) { UINT16 Crc; INT16 i; INT16 j; INT16 Count; if (SPDPtr[SPD_TYPE] == JED_DDR3SDRAM) { Count = (SPDPtr[SPD_BYTE_USED] & 0x80) ? 117 : 126; Crc = 0; for (j = 0; j < Count; j++) { Crc = Crc ^ ((UINT16)SPDPtr[j] << 8); for (i = 0; i < 8; i++) { if (Crc & 0x8000) { Crc = (Crc << 1) ^ 0x1021; } else { Crc = (Crc << 1); } } } if (*(UINT16 *) (SPDPtr + 126) == Crc) { return TRUE; } } return FALSE; } /* -----------------------------------------------------------------------------*/ /** * * This function returns the CAS latency of the current frequency (DCTPtr->Timings.Speed). * * @param[in,out] *TechPtr - Pointer to the MEM_TECH_BLOCK * * @return CAS Latency */ UINT8 STATIC MemTSPDGetTCL3 ( IN OUT MEM_TECH_BLOCK *TechPtr ) { UINT8 *SpdBufferPtr = NULL; UINT8 CLdesired; UINT8 CLactual; UINT8 Dimm; UINT8 Channel; UINT16 CASLat; UINT16 Mask16; INT32 MTB_ps; INT32 FTB_ps; INT32 TAAmin_ps; INT32 TCKproposed_ps; INT32 Value32; BOOLEAN CltFail; MEM_NB_BLOCK *NBPtr; DCT_STRUCT *DCTPtr; CH_DEF_STRUCT *ChannelPtr; NBPtr = TechPtr->NBPtr; DCTPtr = NBPtr->DCTPtr; CASLat = 0xFFFF; TAAmin_ps = 0; CltFail = FALSE; for (Channel = 0; Channel < NBPtr->ChannelCount; Channel++) { NBPtr->SwitchChannel (NBPtr, Channel); ChannelPtr = NBPtr->ChannelPtr; for (Dimm = 0; Dimm < MAX_DIMMS_PER_CHANNEL; Dimm++) { if ((ChannelPtr->ChDimmValid & ((UINT8)1 << Dimm)) != 0) { MemTGetDimmSpdBuffer3 (TechPtr, &SpdBufferPtr, Dimm); // Step 1: Determine the common set of supported CAS Latency // values for all modules on the memory Channel using the CAS // Latencies Supported in SPD bytes 14 and 15. // CASLat &= ((UINT16)SpdBufferPtr[SPD_CASHI] << 8) | SpdBufferPtr[SPD_CASLO]; // Step 2: Determine tAAmin(all) which is the largest tAAmin // value for all modules on the memory Channel (SPD byte 16). // MTB_ps = ((INT32) SpdBufferPtr[SPD_DIVIDENT] * 1000) / SpdBufferPtr[SPD_DIVISOR]; FTB_ps = (SpdBufferPtr[SPD_FTB] >> 4) / (SpdBufferPtr[SPD_FTB] & 0xF); Value32 = (MTB_ps * SpdBufferPtr[SPD_TAA]) + (FTB_ps * (INT8) SpdBufferPtr[SPD_TAA_FTB]) ; if (TAAmin_ps < Value32) { TAAmin_ps = Value32; } // Step 3: Determine tCKmin(all) which is the largest tCKmin // value for all modules on the memory Channel (SPD byte 12). // * This step has been done in SPDGetTargetSpeed } } } TCKproposed_ps = 1000500 / DCTPtr->Timings.Speed; // Step 4: For a proposed tCK value (tCKproposed) between tCKmin(all) and tCKmax, // determine the desired CAS Latency. If tCKproposed is not a standard JEDEC // value (2.5, 1.875, 1.5, or 1.25 ns) then tCKproposed must be adjusted to the // next lower standard tCK value for calculating CLdesired. // CLdesired = ceiling ( tAAmin(all) / tCKproposed ) // where tAAmin is defined in Byte 16. The ceiling function requires that the // quotient be rounded up always. // CLdesired = (UINT8) ((TAAmin_ps + TCKproposed_ps - 1) / TCKproposed_ps); // Step 5: Choose an actual CAS Latency (CLactual) that is greater than or equal // to CLdesired and is supported by all modules on the memory Channel as // determined in step 1. If no such value exists, choose a higher tCKproposed // value and repeat steps 4 and 5 until a solution is found. // CLactual = 4; for (Mask16 = 1; Mask16 < 0x8000; Mask16 <<= 1) { if (CASLat & Mask16) { if (CLdesired <= CLactual) { break; } } CLactual++; } if (Mask16 == 0x8000) { CltFail = TRUE; } // Step 6: Once the calculation of CLactual is completed, the BIOS must also // verify that this CAS Latency value does not exceed tAAmax, which is 20 ns // for all DDR3 speed grades, by multiplying CLactual times tCKproposed. If // not, choose a lower CL value and repeat steps 5 and 6 until a solution is found. // if ((TCKproposed_ps * CLactual) > 20000) { CltFail = TRUE; } if (!CltFail) { DCTPtr->Timings.CasL = CLactual; } else { // Fail to find supported Tcl, use 6 clocks since it is required for all DDR3 speed bin. DCTPtr->Timings.CasL = 6; } return DCTPtr->Timings.CasL; } /* -----------------------------------------------------------------------------*/ /** * * This function returns the encoded value of bank address. * * @param[in] Encode - RRRBCC, where CC is the number of Columns minus 9, * RRR is the number of Rows minus 12, and B is the number of banks * minus 3. * @param[out] *Index - index in bank address table * @return TRUE - encoded value is found. * FALSE - encoded value is not found. */ BOOLEAN STATIC MemTCheckBankAddr3 ( IN UINT8 Encode, OUT UINT8 *Index ) { UINT8 i; CONST UINT8 TabBankAddr[] = { 0x3F, 0x01, 0x09, 0x3F, 0x3F, 0x11, 0x0A, 0x19, 0x12, 0x1A, 0x21, 0x22 }; for (i = 0; i < GET_SIZE_OF (TabBankAddr); i++) { if (Encode == TabBankAddr[i]) { *Index = i; return TRUE; } } return FALSE; } /* -----------------------------------------------------------------------------*/ /** * * This function returns a pointer to the SPD Buffer of a specific dimm on * the current channel. * * @param[in,out] *TechPtr - Pointer to the MEM_TECH_BLOCK * @param[in,out] **SpdBuffer - Pointer to a pointer to a UINT8 Buffer * @param[in] Dimm - Dimm number * * * @return BOOLEAN - Value of DimmPresent * TRUE = Dimm is present, pointer is valid * FALSE = Dimm is not present, pointer has not been modified. */ BOOLEAN MemTGetDimmSpdBuffer3 ( IN OUT MEM_TECH_BLOCK *TechPtr, IN OUT UINT8 **SpdBuffer, IN UINT8 Dimm ) { CH_DEF_STRUCT *ChannelPtr; SPD_DEF_STRUCT *SPDPtr; BOOLEAN DimmPresent; DimmPresent = FALSE; ChannelPtr = TechPtr->NBPtr->ChannelPtr; ASSERT (Dimm < (sizeof (ChannelPtr->DimmSpdPtr) / sizeof (ChannelPtr->DimmSpdPtr[0]))) SPDPtr = ChannelPtr->DimmSpdPtr[Dimm]; if (SPDPtr != NULL) { DimmPresent = SPDPtr->DimmPresent; if (DimmPresent) { *SpdBuffer = SPDPtr->Data; } } return DimmPresent; }