| /* $NoKeywords:$ */ |
| /** |
| * @file |
| * |
| * mtspd2.c |
| * |
| * Technology SPD supporting functions for DDR2 |
| * |
| * @xrefitem bom "File Content Label" "Release Content" |
| * @e project: AGESA |
| * @e sub-project: (Mem/Tech/DDR2) |
| * @e \$Revision: 35136 $ @e \$Date: 2010-07-16 11:29:48 +0800 (Fri, 16 Jul 2010) $ |
| * |
| **/ |
| /* |
| ***************************************************************************** |
| * |
| * 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 "amdlib.h" |
| #include "Ids.h" |
| #include "mport.h" |
| #include "mm.h" |
| #include "mn.h" |
| #include "mt.h" |
| #include "mu.h" |
| #include "mt2.h" |
| #include "mtspd2.h" |
| #include "mftds.h" |
| #include "GeneralServices.h" |
| #include "Filecode.h" |
| CODE_GROUP (G1_PEICC) |
| RDATA_GROUP (G1_PEICC) |
| |
| #define FILECODE PROC_MEM_TECH_DDR2_MTSPD2_FILECODE |
| |
| /*---------------------------------------------------------------------------- |
| * DEFINITIONS AND MACROS |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| |
| /*---------------------------------------------------------------------------- |
| * TYPEDEFS AND STRUCTURES |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| |
| /*---------------------------------------------------------------------------- |
| * PROTOTYPES OF LOCAL FUNCTIONS |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| |
| UINT8 |
| STATIC |
| MemTSPDGetTCL2 ( |
| IN OUT MEM_TECH_BLOCK *TechPtr |
| ); |
| |
| BOOLEAN |
| STATIC |
| MemTSysCapability2 ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN UINT8 k, |
| IN UINT16 j |
| ); |
| |
| BOOLEAN |
| STATIC |
| MemTDimmSupports2 ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN UINT8 k, |
| IN UINT8 j, |
| IN UINT8 i |
| ); |
| |
| UINT8 |
| STATIC |
| MemTGetTk2 ( |
| IN UINT8 k |
| ); |
| |
| UINT8 |
| STATIC |
| MemTGetBankAddr2 ( |
| IN UINT8 k |
| ); |
| |
| /*---------------------------------------------------------------------------- |
| * 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 DDR2 |
| */ |
| |
| BOOLEAN |
| MemTSetDramMode2 ( |
| IN OUT MEM_TECH_BLOCK *TechPtr |
| ) |
| { |
| TechPtr->NBPtr->SetBitField (TechPtr->NBPtr, BFLegacyBiosMode, 0); |
| 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 |
| MemTDIMMPresence2 ( |
| IN OUT MEM_TECH_BLOCK *TechPtr |
| ) |
| { |
| UINT8 *SpdBufferPtr; |
| MEM_PARAMETER_STRUCT *RefPtr; |
| DIE_STRUCT *MCTPtr; |
| DCT_STRUCT *DCTPtr; |
| CH_DEF_STRUCT *ChannelPtr; |
| MEM_NB_BLOCK *NBPtr; |
| UINT16 Checksum; |
| UINT16 Value16; |
| UINT8 Dct; |
| UINT8 Channel; |
| UINT8 i; |
| UINT8 ByteNum; |
| UINT8 Devwidth; |
| UINT8 Value8; |
| UINT8 MaxDimms; |
| UINT8 DimmSlots; |
| UINT16 DimmMask; |
| BOOLEAN SPDCtrl; |
| |
| 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 |
| DimmSlots = GetMaxDimmsPerChannel (RefPtr->PlatformMemoryConfiguration, |
| MCTPtr->SocketId, |
| NBPtr->GetSocketRelativeChannel (NBPtr, Dct, Channel) |
| ); |
| MaxDimms = MAX_DIMMS_PER_CHANNEL; |
| for (i = 0; i < MaxDimms; i++) { |
| // Bitmask representing dimm #i. |
| DimmMask = (UINT16)1 << i; |
| |
| if ((ChannelPtr->DimmQrPresent & DimmMask) || (i < DimmSlots)) { |
| if (MemTGetDimmSpdBuffer2 (TechPtr, &SpdBufferPtr, i)) { |
| MCTPtr->DimmPresent |= DimmMask; |
| |
| // Start by computing checksum for this SPD |
| Checksum = 0; |
| for (ByteNum = 0; ByteNum < SPD_CHECKSUM; ByteNum++) { |
| Checksum = Checksum + (UINT16) SpdBufferPtr[ByteNum]; |
| } |
| // Check for valid checksum value |
| AGESA_TESTPOINT (TpProcMemSPDChecking, &(NBPtr->MemPtr->StdHeader)); |
| |
| if (SpdBufferPtr[SPD_TYPE] == JED_DDR2_SDRAM) { |
| ChannelPtr->ChDimmValid |= DimmMask; |
| MCTPtr->DimmValid |= DimmMask; |
| } else { |
| // Current socket is set up to only support DDR2 dimms. |
| IDS_ERROR_TRAP; |
| } |
| if ((SpdBufferPtr[SPD_CHECKSUM] != (UINT8)Checksum) && !SPDCtrl) { |
| // |
| // if 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_REG_ADC_MSK) { |
| ChannelPtr->RegDimmPresent |= DimmMask; |
| MCTPtr->RegDimmPresent |= DimmMask; |
| } |
| |
| if (SpdBufferPtr[SPD_DIMM_TYPE] & JED_SODIMM) { |
| ChannelPtr->SODimmPresent |= DimmMask; |
| } |
| |
| // Check error correction type |
| if (SpdBufferPtr[SPD_EDC_TYPE] & JED_ECC) { |
| MCTPtr->DimmEccPresent |= DimmMask; // Dimm has ECC |
| } |
| if (SpdBufferPtr[SPD_EDC_TYPE] & JED_ADRC_PAR) { |
| MCTPtr->DimmParPresent |= DimmMask; // Dimm has parity |
| } |
| |
| // Get the Dimm width data |
| Devwidth = SpdBufferPtr[SPD_DEV_WIDTH] & 0xFE; |
| if (Devwidth == 4) { |
| ChannelPtr->Dimmx4Present |= DimmMask; // Dimm has parity |
| } else if (Devwidth == 8) { |
| ChannelPtr->Dimmx8Present |= DimmMask; // Dimm has parity |
| } else if (Devwidth == 16) { |
| ChannelPtr->Dimmx16Present |= DimmMask; // Dimm has parity |
| } |
| |
| // Determine the page size. |
| // page_size = 2^COLBITS * Devwidth/8 |
| // |
| Value16 = (((UINT16)1 << SpdBufferPtr[SPD_COL_SZ]) * Devwidth) / 8; |
| if (!(Value16 >> 11)) { |
| DCTPtr->Timings.DIMM1KPage |= DimmMask; |
| } |
| |
| // Check for 'analysis probe installed' |
| if (SpdBufferPtr[SPD_ATTRIB] & JED_PROBE_MSK) { |
| MCTPtr->Status[SbDiagClks] = TRUE; |
| } |
| |
| // Determine the geometry of the DIMM module |
| if (SpdBufferPtr[SPD_DM_BANKS] & SP_DPL_BIT) { |
| ChannelPtr->DimmPlPresent |= DimmMask; // Dimm is planar |
| } |
| |
| // specify the number of ranks |
| Value8 = (SpdBufferPtr[SPD_DM_BANKS] & 0x07) + 1; |
| if (Value8 > 2) { |
| if (ChannelPtr->DimmQrPresent == 0) { |
| // if any DIMMs are QR, |
| // we have to make two passes through DIMMs |
| // |
| MaxDimms = MaxDimms << 1; |
| } |
| |
| if (i < DimmSlots) { |
| ChannelPtr->DimmQrPresent |= DimmMask; |
| ChannelPtr->DimmQrPresent |= (DimmMask << 2); |
| } |
| Value8 = 2; |
| } else if (Value8 == 2) { |
| ChannelPtr->DimmDrPresent |= DimmMask; // Dual 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 |
| if (Value8 == 2) { |
| Devwidth = Devwidth << 1; |
| } |
| |
| ChannelPtr->Ranks = ChannelPtr->Ranks + Value8; |
| ChannelPtr->Loads = ChannelPtr->Loads + Devwidth; |
| ChannelPtr->Dimms++; |
| |
| // Now examine the dimm packaging dates |
| Value8 = SpdBufferPtr[SPD_MAN_DATE_YR]; |
| if (Value8 < M_YEAR_06) { |
| ChannelPtr->DimmYr06 |= DimmMask; // Built before end of 2006 |
| ChannelPtr->DimmWk2406 |= DimmMask; // Built before end of week 24,2006 |
| } else if (Value8 == M_YEAR_06) { |
| ChannelPtr->DimmYr06 |= DimmMask; // Built before end of 2006 |
| if (SpdBufferPtr[SPD_MAN_DATE_WK] <= M_WEEK_24) { |
| ChannelPtr->DimmWk2406 |= DimmMask; // Built before end of week 24,2006 |
| } |
| } |
| } // if DIMM present |
| } // Quadrank |
| } // Dimm loop |
| |
| if (Channel == 0) { |
| DCTPtr->Timings.DctDimmValid = ChannelPtr->ChDimmValid; |
| DCTPtr->Timings.DimmSpdCse = ChannelPtr->DimmSpdCse; |
| DCTPtr->Timings.DimmQrPresent = ChannelPtr->DimmQrPresent; |
| DCTPtr->Timings.DimmDrPresent = ChannelPtr->DimmDrPresent; |
| 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->DimmEccPresent <<= 8; |
| MCTPtr->DimmParPresent <<= 8; |
| } |
| } // Channel loop |
| } // DCT loop |
| |
| |
| // If we have DIMMs, some further general characteristics checking |
| if (MCTPtr->DimmValid) { |
| // If there are registered dimms, all the dimms must be registered |
| if (MCTPtr->RegDimmPresent == MCTPtr->DimmValid) { |
| // All dimms registered |
| MCTPtr->Status[SbRegistered] = TRUE; |
| } else if (MCTPtr->RegDimmPresent) { |
| // 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 |
| } |
| |
| // check the parity capability of the DIMMs |
| if (MCTPtr->DimmParPresent == MCTPtr->DimmValid) { |
| MCTPtr->Status[SbParDimms] = TRUE; // All dimms parity capable |
| } |
| } else { |
| } |
| |
| NBPtr->SwitchDCT (NBPtr, 0); |
| NBPtr->SwitchChannel (NBPtr, 0); |
| return (BOOLEAN) (MCTPtr->ErrCode < AGESA_FATAL); |
| } |
| |
| |
| /* -----------------------------------------------------------------------------*/ |
| /** |
| * |
| * This function finds the best T and CL primary timing parameter pair, per Mfg.,for the given |
| * set of DIMMs, and store into DIE_STRUCT(.Speed and .Casl). |
| * See "Global relationship between index values and item values" for definition of |
| * CAS latency index (j) and Frequency index (k). |
| * |
| * @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 |
| MemTSPDGetTargetSpeed2 ( |
| IN OUT MEM_TECH_BLOCK *TechPtr |
| ) |
| { |
| CONST UINT16 SpeedCvt[] = { |
| DDR400_FREQUENCY, |
| DDR533_FREQUENCY, |
| DDR667_FREQUENCY, |
| DDR800_FREQUENCY, |
| DDR1066_FREQUENCY |
| }; |
| INT8 i; |
| INT8 j; |
| INT8 k; |
| INT8 Dct; |
| INT8 Channel; |
| UINT8 T1min; |
| UINT8 CL1min; |
| BOOLEAN IsSupported; |
| MEM_NB_BLOCK *NBPtr; |
| DIE_STRUCT *MCTPtr; |
| DCT_STRUCT *DCTPtr; |
| CH_DEF_STRUCT *ChannelPtr; |
| |
| NBPtr = TechPtr->NBPtr; |
| MCTPtr = TechPtr->NBPtr->MCTPtr; |
| |
| CL1min = 0xFF; |
| T1min = 0xFF; |
| |
| // For DDR2, run SyncTargetSpeed first to get frequency limit into DCTPtr->Timings.Speed |
| for (Dct = 0; Dct < NBPtr->DctCount; Dct++) { |
| NBPtr->SwitchDCT (NBPtr, Dct); |
| NBPtr->DCTPtr->Timings.TargetSpeed = 16; // initialized with big number |
| } |
| NBPtr->SyncTargetSpeed (NBPtr); |
| |
| // Find target frequency and Tcl |
| for (k = K_MAX; k >= K_MIN; k--) { |
| for (j = J_MIN; j <= J_MAX; j++) { |
| if (MemTSysCapability2 (TechPtr, k, j)) { |
| IsSupported = TRUE; |
| for (Dct = 0; Dct < NBPtr->DctCount; Dct++) { |
| NBPtr->SwitchDCT (NBPtr, Dct); |
| for (Channel = 0; Channel < NBPtr->ChannelCount; Channel++) { |
| NBPtr->SwitchChannel (NBPtr, Channel); |
| ChannelPtr = NBPtr->ChannelPtr; |
| for (i = 0; i < MAX_DIMMS_PER_CHANNEL; i++) { |
| if (ChannelPtr->ChDimmValid & ((UINT8)1 << i)) { |
| if (!MemTDimmSupports2 (TechPtr, k, j, i)) { |
| IsSupported = FALSE; |
| Dct = NBPtr->DctCount; |
| Channel = NBPtr->ChannelCount; |
| break; |
| } |
| } |
| } |
| } |
| } |
| |
| if (IsSupported) { |
| T1min = k; |
| CL1min = j; |
| // Kill the loops... |
| k = K_MIN - 1; |
| j = J_MAX + 1; |
| } |
| } |
| } |
| } |
| |
| if (T1min == 0xFF) { |
| // Failsafe values, running in minimum mode |
| PutEventLog (AGESA_FATAL, MEM_ERROR_MISMATCH_DIMM_CLOCKS, NBPtr->Node, NBPtr->Dct, NBPtr->Channel, 0, &NBPtr->MemPtr->StdHeader); |
| PutEventLog (AGESA_FATAL, MEM_ERROR_MINIMUM_MODE, NBPtr->Node, NBPtr->Dct, NBPtr->Channel, 0, &NBPtr->MemPtr->StdHeader); |
| SetMemError (AGESA_ERROR, MCTPtr); |
| |
| T1min = T_DEF; |
| CL1min = CL_DEF; |
| } |
| |
| for (Dct = 0; Dct < NBPtr->DctCount; Dct++) { |
| NBPtr->SwitchDCT (NBPtr, Dct); |
| DCTPtr = NBPtr->DCTPtr; |
| DCTPtr->Timings.TargetSpeed = SpeedCvt[T1min - 1]; |
| } |
| |
| // 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); |
| DCTPtr = NBPtr->DCTPtr; |
| DCTPtr->Timings.Speed = DCTPtr->Timings.TargetSpeed; |
| DCTPtr->Timings.CasL = CL1min + 2; // Convert to clocks |
| } |
| |
| return (BOOLEAN) (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 |
| MemTSPDCalcWidth2 ( |
| IN OUT MEM_TECH_BLOCK *TechPtr |
| ) |
| { |
| UINT8 *SpdBufferAPtr; |
| UINT8 *SpdBufferBPtr; |
| 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; |
| 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 (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) { |
| NBPtr->SwitchDCT (NBPtr, 0); |
| MemTGetDimmSpdBuffer2 (TechPtr, &SpdBufferAPtr, i); |
| NBPtr->SwitchDCT (NBPtr, 1); |
| MemTGetDimmSpdBuffer2 (TechPtr, &SpdBufferBPtr, i); |
| |
| if ((SpdBufferAPtr[SPD_ROW_SZ]&0x1F) != (SpdBufferBPtr[SPD_ROW_SZ]&0x1F)) { |
| break; |
| } |
| |
| if ((SpdBufferAPtr[SPD_COL_SZ]&0x1F) != (SpdBufferBPtr[SPD_COL_SZ]&0x1F)) { |
| break; |
| } |
| |
| if (SpdBufferAPtr[SPD_BANK_SZ] != SpdBufferBPtr[SPD_BANK_SZ]) { |
| break; |
| } |
| |
| if ((SpdBufferAPtr[SPD_DEV_WIDTH]&0x7F) != (SpdBufferBPtr[SPD_DEV_WIDTH]&0x7F)) { |
| break; |
| } |
| |
| if ((SpdBufferAPtr[SPD_DM_BANKS]&0x07) != (SpdBufferBPtr[SPD_DM_BANKS]&0x07)) { |
| 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 if (!UngangMode) { |
| 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 |
| MemTAutoCycTiming2 ( |
| 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 |
| }; |
| CONST UINT8 Multiples[] = {10, 10, 10, 40, 40, 10, 10, 10}; |
| |
| CONST UINT8 Tab1KTfawTK[] = {8, 10, 13, 14, 0, 20}; |
| CONST UINT8 Tab2KTfawTK[] = {10, 14, 17, 18, 0, 24}; |
| CONST UINT8 TabDefTrcK[] = {0x41, 0x3C, 0x3C, 0x3A, 0, 0x3A}; |
| |
| UINT8 MiniMaxTmg[GET_SIZE_OF (SpdIndexes)]; |
| UINT8 MiniMaxTrfc[4]; |
| |
| DIE_STRUCT *MCTPtr; |
| DCT_STRUCT *DCTPtr; |
| MEM_NB_BLOCK *NBPtr; |
| UINT16 DimmMask; |
| UINT16 Value16; |
| UINT16 Tk40; |
| UINT8 i; |
| UINT8 j; |
| UINT8 Value8; |
| UINT8 Temp8; |
| UINT8 *StatTmgPtr; |
| UINT16 *StatDimmTmgPtr; |
| BOOLEAN Is1066; |
| UINT8 *SpdBufferPtr; |
| |
| 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) { |
| MemTGetDimmSpdBuffer2 (TechPtr, &SpdBufferPtr, i); |
| for (j = 0; j < GET_SIZE_OF (SpdIndexes); j++) { |
| Value8 = SpdBufferPtr[SpdIndexes[j]]; |
| if (SpdIndexes[j] == SPD_TRC) { |
| if (Value8 == 0 || Value8 == 0xFF) { |
| PutEventLog (AGESA_WARNING, MEM_WARNING_NO_SPDTRC_FOUND, NBPtr->Node, NBPtr->Dct, NBPtr->Channel, i, &NBPtr->MemPtr->StdHeader); |
| SetMemError (AGESA_WARNING, MCTPtr); |
| Value8 = TabDefTrcK[(DCTPtr->Timings.Speed / 66) - 3]; |
| } |
| } |
| if (MiniMaxTmg[j] < Value8) { |
| MiniMaxTmg[j] = Value8; |
| } |
| } |
| |
| // get Trfc0 - Trfc3 values |
| Value8 = SpdBufferPtr[SPD_BANK_SZ]; |
| Temp8 = (Value8 << 3) | (Value8 >> 5); |
| Value8 = SpdBufferPtr[SPD_DEV_WIDTH]; |
| ASSERT (LibAmdBitScanReverse ((UINT32)Value8) <= 4); |
| Temp8 >>= 4 - LibAmdBitScanReverse ((UINT32)Value8); |
| Value8 = LibAmdBitScanReverse ((UINT32)Temp8); |
| if (MiniMaxTrfc[i] < Value8) { |
| MiniMaxTrfc[i] = Value8; |
| } |
| } |
| DimmMask <<= 1; |
| } |
| |
| // ====================================================================== |
| // Convert DRAM CycleTiming values and store into DCT structure |
| // ====================================================================== |
| // |
| Tk40 = 40000 / DCTPtr->Timings.Speed; |
| if (DCTPtr->Timings.Speed == DDR1066_FREQUENCY) { |
| Is1066 = TRUE; |
| } else { |
| Is1066 = FALSE; |
| } |
| // Notes: |
| // 1. All secondary time values given in SPDs are in binary with UINTs of ns. |
| // 2. Some time values are scaled by four, in order to have least count of 0.25 ns |
| // (more accuracy). JEDEC SPD spec. shows which ones are x1 and x4. |
| // 3. Internally to this SW, cycle time, Tk, is scaled by 10 to affect a |
| // least count of 0.1 ns (more accuracy). |
| // 4. SPD values not scaled are multiplied by 10 and then divided by 10T to find |
| // equivalent minimum number of bus clocks (a remainder causes round-up of clocks). |
| // 5. SPD values that are prescaled by 4 are multiplied by 10 and then divided by 40T to find |
| // equivalent minimum number of bus clocks (a remainder causes round-up of clocks). |
| // |
| StatDimmTmgPtr = &DCTPtr->Timings.DIMMTrcd; |
| StatTmgPtr = &DCTPtr->Timings.Trcd; |
| for (j = 0; j < GET_SIZE_OF (SpdIndexes); j++) { |
| Value16 = (UINT16)MiniMaxTmg[j] * Multiples[j]; |
| StatDimmTmgPtr[j] = Value16; |
| |
| MiniMaxTmg[j] = (UINT8) ((Value16 + Tk40 - 1) / Tk40); |
| if (SpdIndexes[j] == SPD_TRTP) { |
| MiniMaxTmg[j] = (DCTPtr->Timings.Speed <= DDR533_FREQUENCY) ? 2 : 3; // based on BL of 32 bytes |
| } |
| |
| StatTmgPtr[j] = 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 = MemTSPDGetTCL2 (TechPtr); |
| |
| if (DCTPtr->Timings.DIMM1KPage) { |
| DCTPtr->Timings.Tfaw = Tab1KTfawTK[(DCTPtr->Timings.Speed / 66) - 3]; |
| } else { |
| DCTPtr->Timings.Tfaw = Tab2KTfawTK[(DCTPtr->Timings.Speed / 66) - 3]; |
| } |
| if (Is1066) { |
| DCTPtr->Timings.Tfaw >>= 1; |
| } |
| |
| //====================================================================== |
| // 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 |
| MemTSPDSetBanks2 ( |
| IN OUT MEM_TECH_BLOCK *TechPtr |
| ) |
| { |
| UINT8 *SpdBufferPtr; |
| 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) { |
| 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)) { |
| MemTGetDimmSpdBuffer2 (TechPtr, &SpdBufferPtr, DimmID); |
| |
| // Get the basic data |
| Rows = SpdBufferPtr[SPD_ROW_SZ] & 0x1F; |
| Cols = SpdBufferPtr[SPD_COL_SZ] & 0x1F; |
| Banks = SpdBufferPtr[SPD_L_BANKS]; |
| Ranks = (SpdBufferPtr[SPD_DM_BANKS] & 0x07) + 1; |
| |
| // Configure the bank encoding |
| Value8 = (Cols - 9) << 3; |
| Value8 |= (Banks == 8) ? 4 : 0; |
| Value8 |= (Rows - 13); |
| |
| for (i = 0; i < 12; i++) { |
| if (Value8 == MemTGetBankAddr2 (i)) { |
| break; |
| } |
| } |
| |
| if (i < 12) { |
| BankAddrReg |= ((UINT32)i << (ChipSel << 1)); |
| |
| // Mask value=(2pow(rows+cols+banks+3)-1)>>8, |
| // or 2pow(rows+cols+banks-5)-1 |
| // |
| Value8 = Rows + Cols; |
| Value8 -= (Banks == 8) ? 2:3; |
| if (MCTPtr->Status[Sb128bitmode]) { |
| Value8++; |
| } |
| CsMask = ((UINT32)1 << Value8) - 1; |
| DCTPtr->Timings.CsPresent |= (UINT16)1 << ChipSel; |
| |
| if (Ranks >= 2) { |
| DCTPtr->Timings.CsPresent |= (UINT16)1 << (ChipSel + 1); |
| } |
| |
| // Update the DRAM CS Mask for this chipselect |
| NBPtr->SetBitField (NBPtr, BFCSMask0Reg + (ChipSel >> 1), (CsMask & 0x1FF83FE0)); |
| } |
| } |
| } |
| // 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 |
| MemTGetCSIntLvAddr2 ( |
| IN UINT8 BankEnc, |
| OUT UINT8 *LowBit, |
| OUT UINT8 *HiBit |
| ) |
| { |
| CONST UINT8 ArrCodesLo[] = {6, 7, 7, 8, 8, 8, 8, 8, 9, 9, 8, 9}; |
| CONST UINT8 ArrCodesHi[] = {19, 20, 21, 21, 21, 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 returns the CAS latency of the current frequency. |
| * |
| * @param[in,out] *TechPtr - Pointer to the MEM_TECH_BLOCK |
| * |
| * @return CAS Latency |
| */ |
| UINT8 |
| STATIC |
| MemTSPDGetTCL2 ( |
| IN OUT MEM_TECH_BLOCK *TechPtr |
| ) |
| { |
| return TechPtr->NBPtr->DCTPtr->Timings.CasL; |
| } |
| |
| /* -----------------------------------------------------------------------------*/ |
| /** |
| * |
| * Get max frequency from OEM platform definition, from |
| * any user override (limiting) of max frequency, and |
| * from any Si Revision Specific information. Return |
| * the least of these three in DIE_STRUCT.PresetmaxFreq. |
| * |
| * @param[in,out] *TechPtr - Pointer to the MEM_TECH_BLOCK |
| * @param[in] k - Frequency index |
| * @param[in] j - CAS Latency index |
| * |
| * @return TRUE - (k << 8) | j |
| * @return FALSE - 0 |
| */ |
| |
| BOOLEAN |
| STATIC |
| MemTSysCapability2 ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN UINT8 k, |
| IN UINT16 j |
| ) |
| { |
| if ((k > TechPtr->NBPtr->DCTPtr->Timings.TargetSpeed) || (j > J_MAX)) { |
| return FALSE; |
| } |
| |
| return TRUE; //(k << 8) | j; |
| } |
| |
| |
| /* -----------------------------------------------------------------------------*/ |
| /** |
| * |
| * Determine whether dimm(b,i) supports CL(j) and F(k) |
| * |
| * @param[in,out] *TechPtr - Pointer to the MEM_TECH_BLOCK |
| * @param[in] k - Frequency index |
| * @param[in] j - CAS Latency index |
| * @param[in] i - DIMM number |
| * |
| * @return TRUE - DIMM supports |
| * @return FALSE - DIMM does not support |
| */ |
| |
| BOOLEAN |
| STATIC |
| MemTDimmSupports2 ( |
| IN OUT MEM_TECH_BLOCK *TechPtr, |
| IN UINT8 k, |
| IN UINT8 j, |
| IN UINT8 i |
| ) |
| { |
| CONST UINT8 SpdBytesForCL[3] = { 9, 23, 25}; // SPD bytes for CL X, CL X-.5, and CL X-1 |
| UINT8 CLj; |
| UINT8 CLi; |
| UINT8 T1; |
| UINT8 T2; |
| UINT8 Tk; |
| UINT8 *SpdBufferPtr; |
| MEM_NB_BLOCK *NBPtr; |
| |
| NBPtr = TechPtr->NBPtr; |
| |
| MemTGetDimmSpdBuffer2 (TechPtr, &SpdBufferPtr, i); |
| CLj = (UINT8) 1 << (j + 2); |
| CLi = SpdBufferPtr[SPD_CAS_LAT]; |
| |
| if (CLj & CLi) { |
| // If this dimm supports the desired CAS latency... |
| // Determine the SPD location of the dimm speed UINT8 appropriate |
| // to the CAS latency indicated by Table_CL2_j. |
| // |
| T1 = LibAmdBitScanReverse ((UINT32)CLj); |
| T2 = LibAmdBitScanReverse ((UINT32)CLi); |
| ASSERT ((T2 - T1) < 3); |
| CLi = SpdBufferPtr[SpdBytesForCL[(T2 - T1)]]; |
| Tk = MemTGetTk2 (k); |
| if (CLi == 0) { |
| PutEventLog (AGESA_FATAL, MEM_ERROR_NO_CYC_TIME, NBPtr->Node, NBPtr->Dct, NBPtr->Channel, 0, &NBPtr->MemPtr->StdHeader); |
| SetMemError (AGESA_WARNING, NBPtr->MCTPtr); |
| } else if (Tk >= CLi) { |
| return TRUE; |
| } |
| } |
| return FALSE; |
| } |
| |
| /* -----------------------------------------------------------------------------*/ |
| /** |
| * |
| * This function returns the cycle time |
| * |
| * @param[in] k - CAS Latency index |
| * |
| * @return Tk as specified by JEDEC SPD byte 9. |
| */ |
| |
| UINT8 |
| STATIC |
| MemTGetTk2 ( |
| IN UINT8 k |
| ) |
| { |
| CONST UINT8 TableTK[] = {0x00, 0x50, 0x3D, 0x30, 0x25, 0x18}; |
| ASSERT (k < GET_SIZE_OF (TableTK)); |
| return TableTK[k]; |
| } |
| |
| /* -----------------------------------------------------------------------------*/ |
| /** |
| * |
| * This function returns the encoded value of bank address. |
| * |
| * @param[in] k value |
| * |
| * @return 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. |
| */ |
| |
| UINT8 |
| STATIC |
| MemTGetBankAddr2 ( |
| IN UINT8 k |
| ) |
| { |
| CONST UINT8 TabBankAddr[] = { |
| 0x00, 0x08, 0x09, 0x10, 0x0C, 0x0D, |
| 0x11, 0x0E, 0x15, 0x16, 0x0F, 0x17 |
| }; |
| ASSERT (k < GET_SIZE_OF (TabBankAddr)); |
| return TabBankAddr[k]; |
| } |
| |
| /* -----------------------------------------------------------------------------*/ |
| /** |
| * |
| * 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 |
| MemTGetDimmSpdBuffer2 ( |
| 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; |
| } |