Add the AMD Family10 Agesa code
[coreboot.git] / src / vendorcode / amd / agesa / f10 / Proc / Mem / Tech / DDR2 / mtspd2.c
diff --git a/src/vendorcode/amd/agesa/f10/Proc/Mem/Tech/DDR2/mtspd2.c b/src/vendorcode/amd/agesa/f10/Proc/Mem/Tech/DDR2/mtspd2.c
new file mode 100755 (executable)
index 0000000..9e1a892
--- /dev/null
@@ -0,0 +1,1112 @@
+/**
+ * @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: 44323 $ @e \$Date: 2010-12-22 01:24:58 -0700 (Wed, 22 Dec 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"
+#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;
+}