[coreboot-gerrit] Patch set updated for coreboot: northbridge/amd/amdmct/mct_ddr3: Add registered and x4 DIMM support to Fam15h

Timothy Pearson (tpearson@raptorengineeringinc.com) gerrit at coreboot.org
Sun Oct 18 02:52:03 CEST 2015


Timothy Pearson (tpearson at raptorengineeringinc.com) just uploaded a new patch set to gerrit, which you can find at http://review.coreboot.org/12019

-gerrit

commit f53b690a44e778954d7d6156e99778c03bab4916
Author: Timothy Pearson <tpearson at raptorengineeringinc.com>
Date:   Tue Jul 28 15:16:46 2015 -0500

    northbridge/amd/amdmct/mct_ddr3: Add registered and x4 DIMM support to Fam15h
    
    Change-Id: I9ee0bb7346aa35f564fe535cdd337ec7f6148f2b
    Signed-off-by: Timothy Pearson <tpearson at raptorengineeringinc.com>
---
 src/northbridge/amd/amdmct/mct_ddr3/mct_d.c    | 186 +++++++-----
 src/northbridge/amd/amdmct/mct_ddr3/mct_d.h    |   2 +-
 src/northbridge/amd/amdmct/mct_ddr3/mctdqs_d.c |   4 +
 src/northbridge/amd/amdmct/mct_ddr3/mcthwl.c   |  17 +-
 src/northbridge/amd/amdmct/mct_ddr3/mctrci.c   | 191 ++++++++----
 src/northbridge/amd/amdmct/mct_ddr3/mctsdi.c   |  42 ++-
 src/northbridge/amd/amdmct/mct_ddr3/mctsrc.c   | 267 +++++++++-------
 src/northbridge/amd/amdmct/mct_ddr3/mctwl.c    |  16 +-
 src/northbridge/amd/amdmct/mct_ddr3/mhwlc_d.c  | 402 +++++++++++++++----------
 src/northbridge/amd/amdmct/mct_ddr3/mwlc_d.h   |  13 +-
 10 files changed, 706 insertions(+), 434 deletions(-)

diff --git a/src/northbridge/amd/amdmct/mct_ddr3/mct_d.c b/src/northbridge/amd/amdmct/mct_ddr3/mct_d.c
index f582b21..b3447c8 100644
--- a/src/northbridge/amd/amdmct/mct_ddr3/mct_d.c
+++ b/src/northbridge/amd/amdmct/mct_ddr3/mct_d.c
@@ -166,7 +166,7 @@ static void mct_EnDllShutdownSR(struct MCTStatStruc *pMCTstat,
 static void ChangeMemClk(struct MCTStatStruc *pMCTstat,
 					struct DCTStatStruc *pDCTstat);
 void SetTargetFreq(struct MCTStatStruc *pMCTstat,
-                                        struct DCTStatStruc *pDCTstat);
+                                        struct DCTStatStruc *pDCTstatA, uint8_t Node);
 
 static u32 mct_MR1Odt_RDimm(struct MCTStatStruc *pMCTstat,
 					struct DCTStatStruc *pDCTstat, u8 dct, u32 MrsChipSel);
@@ -1404,6 +1404,10 @@ static void precise_memclk_delay_fam15(struct MCTStatStruc *pMCTstat, struct DCT
 
 	memclk_freq = Get_NB32_DCT(pDCTstat->dev_dct, dct, 0x94) & 0x1f;
 
+	if (fam15h_freq_tab[memclk_freq] == 0) {
+		printk(BIOS_DEBUG, "ERROR: precise_memclk_delay_fam15 for DCT %d (delay %d clocks) failed to obtain valid memory frequency!"
+			" (pDCTstat: %p pDCTstat->dev_dct: %08x memclk_freq: %02x)\n", dct, clocks, pDCTstat, pDCTstat->dev_dct, memclk_freq);
+	}
 	delay_ns = (((uint64_t)clocks * 1000) / fam15h_freq_tab[memclk_freq]);
 	precise_ndelay_fam15(pMCTstat, delay_ns);
 }
@@ -2319,7 +2323,7 @@ static void DQSTiming_D(struct MCTStatStruc *pMCTstat,
 	nv_DQSTrainCTL = !allow_config_restore;
 
 	mct_BeforeDQSTrain_D(pMCTstat, pDCTstatA);
-	phyAssistedMemFnceTraining(pMCTstat, pDCTstatA);
+	phyAssistedMemFnceTraining(pMCTstat, pDCTstatA, -1);
 
 	if (is_fam15h()) {
 		uint8_t Node;
@@ -3356,7 +3360,7 @@ static void SPD2ndTiming(struct MCTStatStruc *pMCTstat,
 }
 
 static u8 AutoCycTiming_D(struct MCTStatStruc *pMCTstat,
-				struct DCTStatStruc *pDCTstat, u8 dct)
+				struct DCTStatStruc *pDCTstat, uint8_t dct)
 {
 	/* Initialize  DCT Timing registers as per DIMM SPD.
 	 * For primary timing (T, CL) use best case T value.
@@ -3460,7 +3464,7 @@ static void GetPresetmaxF_D(struct MCTStatStruc *pMCTstat,
 }
 
 static void SPDGetTCL_D(struct MCTStatStruc *pMCTstat,
-				struct DCTStatStruc *pDCTstat, u8 dct)
+				struct DCTStatStruc *pDCTstat, uint8_t dct)
 {
 	/* Find the best T and CL primary timing parameter pair, per Mfg.,
 	 * for the given set of DIMMs, and store into DCTStatStruc
@@ -3739,10 +3743,15 @@ static u8 AutoConfig_D(struct MCTStatStruc *pMCTstat,
 		dword++;
 	}
 
-	if (Status & (1 << SB_Registered))
-		DramConfigLo |= 1 << ParEn;		/* Registered DIMMs */
-	else
-		DramConfigLo |= 1 << UnBuffDimm;	/* Unbuffered DIMMs */
+	if (Status & (1 << SB_Registered)) {
+		/* Registered DIMMs */
+		if (!is_fam15h()) {
+			DramConfigLo |= 1 << ParEn;
+		}
+	} else {
+		/* Unbuffered DIMMs */
+		DramConfigLo |= 1 << UnBuffDimm;
+	}
 
 	if (mctGet_NVbits(NV_ECC_CAP))
 		if (Status & (1 << SB_ECCDIMMs))
@@ -3760,10 +3769,11 @@ static u8 AutoConfig_D(struct MCTStatStruc *pMCTstat,
 	DramConfigHi |= dword - offset;	/* get MemClk encoding */
 	DramConfigHi |= 1 << MemClkFreqVal;
 
-	if (Status & (1 << SB_Registered))
-		if ((pDCTstat->Dimmx4Present != 0) && (pDCTstat->Dimmx8Present != 0))
-			/* set only if x8 Registered DIMMs in System*/
-			DramConfigHi |= 1 << RDqsEn;
+	if (!is_fam15h())
+		if (Status & (1 << SB_Registered))
+			if ((pDCTstat->Dimmx4Present != 0) && (pDCTstat->Dimmx8Present != 0))
+				/* set only if x8 Registered DIMMs in System*/
+				DramConfigHi |= 1 << RDqsEn;
 
 	if (pDCTstat->LogicalCPUID & AMD_FAM15_ALL) {
 		DramConfigLo |= 1 << 25;	/* PendRefPaybackS3En = 1 */
@@ -3775,14 +3785,16 @@ static u8 AutoConfig_D(struct MCTStatStruc *pMCTstat,
 			DramConfigHi |= 1 << 16;
 	}
 
-	/* Control Bank Swizzle */
-	if (0) /* call back not needed mctBankSwizzleControl_D()) */
-		DramConfigHi &= ~(1 << BankSwizzleMode);
-	else
-		DramConfigHi |= 1 << BankSwizzleMode; /* recommended setting (default) */
+	if (!is_fam15h()) {
+		/* Control Bank Swizzle */
+		if (0) /* call back not needed mctBankSwizzleControl_D()) */
+			DramConfigHi &= ~(1 << BankSwizzleMode);
+		else
+			DramConfigHi |= 1 << BankSwizzleMode; /* recommended setting (default) */
+	}
 
 	/* Check for Quadrank DIMM presence */
-	if ( pDCTstat->DimmQRPresent != 0) {
+	if (pDCTstat->DimmQRPresent != 0) {
 		byte = mctGet_NVbits(NV_4RANKType);
 		if (byte == 2)
 			DramConfigHi |= 1 << 17;	/* S4 (4-Rank SO-DIMMs) */
@@ -4587,8 +4599,9 @@ static u8 mct_setMode(struct MCTStatStruc *pMCTstat,
 			Set_NB32(pDCTstat->dev_dct, reg, val);
 		}
 		if (byte)	/* NV_Unganged */
-			pDCTstat->ErrStatus &= ~(1 << SB_DimmMismatchO); /* Clear so that there is no DIMM missmatch error */
+			pDCTstat->ErrStatus &= ~(1 << SB_DimmMismatchO); /* Clear so that there is no DIMM mismatch error */
 	}
+
 	return pDCTstat->ErrCode;
 }
 
@@ -4649,6 +4662,8 @@ void Set_NB32_index_wait(u32 dev, u32 index_reg, u32 index, u32 data)
 static u8 mct_BeforePlatformSpec(struct MCTStatStruc *pMCTstat,
 					struct DCTStatStruc *pDCTstat, u8 dct)
 {
+	printk(BIOS_DEBUG, "%s: Start\n", __func__);
+
 	/* mct_checkForCxDxSupport_D */
 	if (pDCTstat->LogicalCPUID & AMD_DR_GT_Bx) {
 		/* Family 10h Errata 322: Address and Command Fine Delay Values May Be Incorrect */
@@ -4663,6 +4678,9 @@ static u8 mct_BeforePlatformSpec(struct MCTStatStruc *pMCTstat,
 		else
 			Set_NB32_index_wait_DCT(pDCTstat->dev_dct, dct, 0x98, 0x0D02E001, 0x90);
 	}
+
+	printk(BIOS_DEBUG, "%s: Done\n", __func__);
+
 	return pDCTstat->ErrCode;
 }
 
@@ -4673,6 +4691,8 @@ static u8 mct_PlatformSpec(struct MCTStatStruc *pMCTstat,
 	 * and program them into DCT.
 	 */
 
+	printk(BIOS_DEBUG, "%s: Start\n", __func__);
+
 	u32 dev = pDCTstat->dev_dct;
 	u32 index_reg;
 	u8 i, i_start, i_end;
@@ -4693,6 +4713,8 @@ static u8 mct_PlatformSpec(struct MCTStatStruc *pMCTstat,
 		printk(BIOS_SPEW, "Programmed DCT %d timing/termination pattern %08x %08x\n", dct, pDCTstat->CH_ADDR_TMG[i], pDCTstat->CH_ODC_CTL[i]);
 	}
 
+	printk(BIOS_DEBUG, "%s: Done\n", __func__);
+
 	return pDCTstat->ErrCode;
 }
 
@@ -4704,7 +4726,8 @@ static void mct_SyncDCTsReady(struct DCTStatStruc *pDCTstat)
 	if (pDCTstat->NodePresent) {
 		dev = pDCTstat->dev_dct;
 
-		if ((pDCTstat->DIMMValidDCT[0] ) || (pDCTstat->DIMMValidDCT[1])) {		/* This Node has dram */
+		if ((pDCTstat->DIMMValidDCT[0]) || (pDCTstat->DIMMValidDCT[1])) {
+			/* This Node has DRAM */
 			do {
 				val = Get_NB32(dev, 0x110);
 			} while (!(val & (1 << DramEnabled)));
@@ -5652,57 +5675,56 @@ static void InitDDRPhy(struct MCTStatStruc *pMCTstat,
 	/* Fam15h BKDG v3.14 section 2.10.5.3
 	 * The remainder of the Phy Initialization algorithm picks up in phyAssistedMemFnceTraining
 	 */
-	for (dct = 0; dct < 2; dct++) {
-		Set_NB32_index_wait_DCT(dev, dct, index_reg, 0x0000000b, 0x80000000);
-		Set_NB32_index_wait_DCT(dev, dct, index_reg, 0x0d0fe013, 0x00000118);
-
-		/* Program desired VDDIO level */
-		if (ddr_voltage_index & 0x4) {
-			/* 1.25V */
-			amd_voltage_level_index = 0x2;
-		} else if (ddr_voltage_index & 0x2) {
-			/* 1.35V */
-			amd_voltage_level_index = 0x1;
-		} else if (ddr_voltage_index & 0x1) {
-			/* 1.50V */
-			amd_voltage_level_index = 0x0;
-		}
-
-		/* D18F2x9C_x0D0F_0[F,8:0]1F_dct[1:0][RxVioLvl] */
-		for (index = 0; index < 0x9; index++) {
-			dword = Get_NB32_index_wait_DCT(dev, dct, index_reg, 0x0d0f001f | (index << 8));
-			dword &= ~(0x3 << 3);
-			dword |= (amd_voltage_level_index << 3);
-			Set_NB32_index_wait_DCT(dev, dct, index_reg, 0x0d0f001f | (index << 8), dword);
-		}
-
-		/* D18F2x9C_x0D0F_[C,8,2][2:0]1F_dct[1:0][RxVioLvl] */
-		for (index = 0; index < 0x3; index++) {
-			dword = Get_NB32_index_wait_DCT(dev, dct, index_reg, 0x0d0f201f | (index << 8));
-			dword &= ~(0x3 << 3);
-			dword |= (amd_voltage_level_index << 3);
-			Set_NB32_index_wait_DCT(dev, dct, index_reg, 0x0d0f201f | (index << 8), dword);
-		}
-		for (index = 0; index < 0x2; index++) {
-			dword = Get_NB32_index_wait_DCT(dev, dct, index_reg, 0x0d0f801f | (index << 8));
-			dword &= ~(0x3 << 3);
-			dword |= (amd_voltage_level_index << 3);
-			Set_NB32_index_wait_DCT(dev, dct, index_reg, 0x0d0f801f | (index << 8), dword);
-		}
-		for (index = 0; index < 0x1; index++) {
-			dword = Get_NB32_index_wait_DCT(dev, dct, index_reg, 0x0d0fc01f | (index << 8));
-			dword &= ~(0x3 << 3);
-			dword |= (amd_voltage_level_index << 3);
-			Set_NB32_index_wait_DCT(dev, dct, index_reg, 0x0d0fc01f | (index << 8), dword);
-		}
+	Set_NB32_index_wait_DCT(dev, dct, index_reg, 0x0000000b, 0x80000000);
+	Set_NB32_index_wait_DCT(dev, dct, index_reg, 0x0d0fe013, 0x00000118);
 
-		/* D18F2x9C_x0D0F_4009_dct[1:0][CmpVioLvl, ComparatorAdjust] */
-		dword = Get_NB32_index_wait_DCT(dev, dct, index_reg, 0x0d0f4009);
-		dword &= ~(0x0000c00c);
-		dword |= (amd_voltage_level_index << 14);
-		dword |= (amd_voltage_level_index << 2);
-		Set_NB32_index_wait_DCT(dev, dct, index_reg, 0x0d0f4009, dword);
-	}
+	/* Program desired VDDIO level */
+	if (ddr_voltage_index & 0x4) {
+		/* 1.25V */
+		amd_voltage_level_index = 0x2;
+	} else if (ddr_voltage_index & 0x2) {
+		/* 1.35V */
+		amd_voltage_level_index = 0x1;
+	} else if (ddr_voltage_index & 0x1) {
+		/* 1.50V */
+		amd_voltage_level_index = 0x0;
+	}
+
+	/* D18F2x9C_x0D0F_0[F,8:0]1F_dct[1:0][RxVioLvl] */
+	for (index = 0; index < 0x9; index++) {
+		dword = Get_NB32_index_wait_DCT(dev, dct, index_reg, 0x0d0f001f | (index << 8));
+		dword &= ~(0x3 << 3);
+		dword |= (amd_voltage_level_index << 3);
+		Set_NB32_index_wait_DCT(dev, dct, index_reg, 0x0d0f001f | (index << 8), dword);
+	}
+
+	/* D18F2x9C_x0D0F_[C,8,2][2:0]1F_dct[1:0][RxVioLvl] */
+	for (index = 0; index < 0x3; index++) {
+		dword = Get_NB32_index_wait_DCT(dev, dct, index_reg, 0x0d0f201f | (index << 8));
+		dword &= ~(0x3 << 3);
+		dword |= (amd_voltage_level_index << 3);
+		Set_NB32_index_wait_DCT(dev, dct, index_reg, 0x0d0f201f | (index << 8), dword);
+	}
+	for (index = 0; index < 0x2; index++) {
+		dword = Get_NB32_index_wait_DCT(dev, dct, index_reg, 0x0d0f801f | (index << 8));
+		dword &= ~(0x3 << 3);
+		dword |= (amd_voltage_level_index << 3);
+		Set_NB32_index_wait_DCT(dev, dct, index_reg, 0x0d0f801f | (index << 8), dword);
+	}
+	for (index = 0; index < 0x1; index++) {
+		dword = Get_NB32_index_wait_DCT(dev, dct, index_reg, 0x0d0fc01f | (index << 8));
+		dword &= ~(0x3 << 3);
+		dword |= (amd_voltage_level_index << 3);
+		Set_NB32_index_wait_DCT(dev, dct, index_reg, 0x0d0fc01f | (index << 8), dword);
+	}
+
+	/* D18F2x9C_x0D0F_4009_dct[1:0][CmpVioLvl, ComparatorAdjust] */
+	/* NOTE: CmpVioLvl and ComparatorAdjust only take effect when set on DCT 0 */
+	dword = Get_NB32_index_wait_DCT(dev, 0, index_reg, 0x0d0f4009);
+	dword &= ~(0x0000c00c);
+	dword |= (amd_voltage_level_index << 14);
+	dword |= (amd_voltage_level_index << 2);
+	Set_NB32_index_wait_DCT(dev, 0, index_reg, 0x0d0f4009, dword);
 
 	printk(BIOS_DEBUG, "%s: Done\n", __func__);
 }
@@ -5718,18 +5740,24 @@ static void InitPhyCompensation(struct MCTStatStruc *pMCTstat,
 	uint32_t dword;
 	const u8 *p;
 
-	printk(BIOS_DEBUG, "%s: Start\n", __func__);
+	printk(BIOS_DEBUG, "%s: DCT %d: Start\n", __func__, dct);
 
 	if (is_fam15h()) {
 		/* Algorithm detailed in the Fam15h BKDG Rev. 3.14 section 2.10.5.3.4 */
 		uint32_t tx_pre;
 		uint32_t drive_strength;
 
-		/* Program D18F2x9C_x0D0F_E003_dct[1:0][DisAutoComp, DisablePredriverCal] */
+		/* Program D18F2x9C_x0D0F_E003_dct[1:0][DisAutoComp] */
 		dword = Get_NB32_index_wait_DCT(dev, dct, index_reg, 0x0d0fe003);
-		dword |= (0x3 << 13);
+		dword |= (0x1 << 14);
 		Set_NB32_index_wait_DCT(dev, dct, index_reg, 0x0d0fe003, dword);
 
+		/* Program D18F2x9C_x0D0F_E003_dct[1:0][DisablePredriverCal] */
+		/* NOTE: DisablePredriverCal only takes effect when set on DCT 0 */
+		dword = Get_NB32_index_wait_DCT(dev, 0, index_reg, 0x0d0fe003);
+		dword |= (0x1 << 13);
+		Set_NB32_index_wait_DCT(dev, 0, index_reg, 0x0d0fe003, dword);
+
 		/* Determine TxPreP/TxPreN for data lanes (Stage 1) */
 		dword = Get_NB32_index_wait_DCT(dev, dct, index_reg, 0x00000000);
 		drive_strength = (dword >> 20) & 0x7;	/* DqsDrvStren */
@@ -5875,12 +5903,14 @@ static void InitPhyCompensation(struct MCTStatStruc *pMCTstat,
 		Set_NB32_index_wait_DCT(dev, dct, index_reg, 0x0a, dword);
 	}
 
-	printk(BIOS_DEBUG, "%s: Done\n", __func__);
+	printk(BIOS_DEBUG, "%s: DCT %d: Done\n", __func__, dct);
 }
 
 static void mct_EarlyArbEn_D(struct MCTStatStruc *pMCTstat,
 					struct DCTStatStruc *pDCTstat, u8 dct)
 {
+	printk(BIOS_DEBUG, "%s: Start\n", __func__);
+
 	if (!is_fam15h()) {
 		u32 reg;
 		u32 val;
@@ -5902,6 +5932,8 @@ static void mct_EarlyArbEn_D(struct MCTStatStruc *pMCTstat,
 
 		Set_NB32_DCT(dev, dct, reg, val);
 	}
+
+	printk(BIOS_DEBUG, "%s: Done\n", __func__);
 }
 
 static u8 CheckNBCOFEarlyArbEn(struct MCTStatStruc *pMCTstat,
@@ -6545,6 +6577,8 @@ void mct_SetDramConfigHi_D(struct MCTStatStruc *pMCTstat,
 
 	uint32_t dword;
 
+	printk(BIOS_DEBUG, "%s: Start\n", __func__);
+
 	if (is_fam15h()) {
 		/* Initial setup for frequency change
 		 * 9C_x0000_0004 must be configured before MemClkFreqVal is set
@@ -6577,6 +6611,8 @@ void mct_SetDramConfigHi_D(struct MCTStatStruc *pMCTstat,
 		mct_Wait(100);
 	}
 
+	printk(BIOS_DEBUG, "mct_SetDramConfigHi_D: DramConfigHi:    %08x\n", DramConfigHi);
+
 	/* Program the DRAM Configuration High register */
 	Set_NB32_DCT(dev, dct, 0x94, DramConfigHi);
 
@@ -6592,6 +6628,8 @@ void mct_SetDramConfigHi_D(struct MCTStatStruc *pMCTstat,
 		dword |= 0x0000000f;
 		Set_NB32_index_wait_DCT(pDCTstat->dev_dct, dct, index_reg, 0x0d0fe006, dword);
 	}
+
+	printk(BIOS_DEBUG, "%s: Done\n", __func__);
 }
 
 static void mct_BeforeDQSTrain_D(struct MCTStatStruc *pMCTstat,
diff --git a/src/northbridge/amd/amdmct/mct_ddr3/mct_d.h b/src/northbridge/amd/amdmct/mct_ddr3/mct_d.h
index e327d38..486b16c 100644
--- a/src/northbridge/amd/amdmct/mct_ddr3/mct_d.h
+++ b/src/northbridge/amd/amdmct/mct_ddr3/mct_d.h
@@ -1014,7 +1014,7 @@ void InterleaveNodes_D(struct MCTStatStruc *pMCTstat, struct DCTStatStruc *pDCTs
 void InterleaveChannels_D(struct MCTStatStruc *pMCTstat, struct DCTStatStruc *pDCTstatA);
 void mct_BeforeDQSTrain_Samp_D(struct MCTStatStruc *pMCTstat, struct DCTStatStruc *pDCTstat);
 
-void phyAssistedMemFnceTraining(struct MCTStatStruc *pMCTstat, struct DCTStatStruc *pDCTstatA);
+void phyAssistedMemFnceTraining(struct MCTStatStruc *pMCTstat, struct DCTStatStruc *pDCTstatA, int16_t Node);
 u8 mct_SaveRcvEnDly_D_1Pass(struct DCTStatStruc *pDCTstat, u8 pass);
 u8 mct_InitReceiver_D(struct DCTStatStruc *pDCTstat, u8 dct);
 void mct_Wait(u32 cycles);
diff --git a/src/northbridge/amd/amdmct/mct_ddr3/mctdqs_d.c b/src/northbridge/amd/amdmct/mct_ddr3/mctdqs_d.c
index 41b7244..bc9ac4b 100644
--- a/src/northbridge/amd/amdmct/mct_ddr3/mctdqs_d.c
+++ b/src/northbridge/amd/amdmct/mct_ddr3/mctdqs_d.c
@@ -1588,6 +1588,7 @@ static void TrainDQSReceiverEnCyc_D_Fam15(struct MCTStatStruc *pMCTstat,
 
 	for (dct = 0; dct < 2; dct++) {
 		/* Program D18F2x9C_x0D0F_E003_dct[1:0][DisAutoComp, DisablePredriverCal] */
+		/* NOTE: DisablePredriverCal only takes effect when set on DCT 0 */
 		dword = Get_NB32_index_wait_DCT(dev, dct, index_reg, 0x0d0fe003);
 		dword &= ~(0x3 << 13);
 		dword |= (0x1 << 13);
@@ -1627,6 +1628,9 @@ static void TrainDQSReceiverEnCyc_D_Fam15(struct MCTStatStruc *pMCTstat,
 				rx_en_offset = (initial_phy_phase_delay[lane] + 0x10) % 0x40;
 	
 				/* 2.10.5.8.3 (4) */
+#if DQS_TRAIN_DEBUG > 0
+				printk(BIOS_DEBUG, "TrainDQSReceiverEnCyc_D_Fam15 Receiver %d lane %d initial phy delay %04x: iterating from %04x to %04x\n", Receiver, lane, initial_phy_phase_delay[lane], rx_en_offset, 0x3ff);
+#endif
 				for (current_phy_phase_delay[lane] = rx_en_offset; current_phy_phase_delay[lane] < 0x3ff; current_phy_phase_delay[lane] += ren_step) {
 					/* 2.10.5.8.3 (4 A) */
 					write_dqs_receiver_enable_control_registers(current_phy_phase_delay, dev, dct, dimm, index_reg);
diff --git a/src/northbridge/amd/amdmct/mct_ddr3/mcthwl.c b/src/northbridge/amd/amdmct/mct_ddr3/mcthwl.c
index 539cb0d..1b81d15 100644
--- a/src/northbridge/amd/amdmct/mct_ddr3/mcthwl.c
+++ b/src/northbridge/amd/amdmct/mct_ddr3/mcthwl.c
@@ -21,7 +21,7 @@
 static uint8_t AgesaHwWlPhase1(struct MCTStatStruc *pMCTstat,
 					struct DCTStatStruc *pDCTstat, u8 dct, u8 dimm, u8 pass);
 static uint8_t AgesaHwWlPhase2(struct MCTStatStruc *pMCTstat,
-					struct DCTStatStruc *pDCTstat, u8 dct, u8 dimm, u8 pass);
+					struct DCTStatStruc *pDCTstat, uint8_t dct, uint8_t dimm, uint8_t pass);
 static uint8_t AgesaHwWlPhase3(struct MCTStatStruc *pMCTstat,
 					struct DCTStatStruc *pDCTstat, u8 dct, u8 dimm, u8 pass);
 static void EnableZQcalibration(struct MCTStatStruc *pMCTstat, struct DCTStatStruc *pDCTstat);
@@ -133,7 +133,7 @@ static uint8_t PhyWLPass1(struct MCTStatStruc *pMCTstat,
 }
 
 static uint8_t PhyWLPass2(struct MCTStatStruc *pMCTstat,
-					struct DCTStatStruc *pDCTstat, u8 dct)
+					struct DCTStatStruc *pDCTstat, uint8_t dct, uint8_t final)
 {
 	u8 dimm;
 	u16 DIMMValid;
@@ -187,12 +187,15 @@ static uint16_t fam15h_next_highest_memclk_freq(uint16_t memclk_freq)
  * Algorithm detailed in the Fam10h BKDG Rev. 3.62 section 2.8.9.9.1
  */
 static void WriteLevelization_HW(struct MCTStatStruc *pMCTstat,
-					struct DCTStatStruc *pDCTstat, uint8_t Pass)
+					struct DCTStatStruc *pDCTstatA, uint8_t Node, uint8_t Pass)
 {
 	uint8_t status;
 	uint8_t timeout;
 	uint16_t final_target_freq;
 
+	struct DCTStatStruc *pDCTstat;
+	pDCTstat = pDCTstatA + Node;
+
 	pDCTstat->C_MCTPtr  = &(pDCTstat->s_C_MCTPtr);
 	pDCTstat->C_DCTPtr[0] = &(pDCTstat->s_C_DCTPtr[0]);
 	pDCTstat->C_DCTPtr[1] = &(pDCTstat->s_C_DCTPtr[1]);
@@ -240,13 +243,13 @@ static void WriteLevelization_HW(struct MCTStatStruc *pMCTstat,
 					pDCTstat->TargetFreq = fam15h_next_highest_memclk_freq(pDCTstat->Speed);
 				else
 					pDCTstat->TargetFreq = final_target_freq;
-				SetTargetFreq(pMCTstat, pDCTstat);
+				SetTargetFreq(pMCTstat, pDCTstatA, Node);
 				timeout = 0;
 				do {
 					status = 0;
 					timeout++;
-					status |= PhyWLPass2(pMCTstat, pDCTstat, 0);
-					status |= PhyWLPass2(pMCTstat, pDCTstat, 1);
+					status |= PhyWLPass2(pMCTstat, pDCTstat, 0, (pDCTstat->TargetFreq == final_target_freq));
+					status |= PhyWLPass2(pMCTstat, pDCTstat, 1, (pDCTstat->TargetFreq == final_target_freq));
 					if (status)
 						printk(BIOS_INFO,
 							"%s: Retrying write levelling due to invalid value(s) detected in last phase\n",
@@ -290,7 +293,7 @@ void mct_WriteLevelization_HW(struct MCTStatStruc *pMCTstat,
 		if (pDCTstat->NodePresent) {
 			mctSMBhub_Init(Node);
 			Clear_OnDimmMirror(pMCTstat, pDCTstat);
-			WriteLevelization_HW(pMCTstat, pDCTstat, Pass);
+			WriteLevelization_HW(pMCTstat, pDCTstatA, Node, Pass);
 			Restore_OnDimmMirror(pMCTstat, pDCTstat);
 		}
 	}
diff --git a/src/northbridge/amd/amdmct/mct_ddr3/mctrci.c b/src/northbridge/amd/amdmct/mct_ddr3/mctrci.c
index 9617f84..624a543 100644
--- a/src/northbridge/amd/amdmct/mct_ddr3/mctrci.c
+++ b/src/northbridge/amd/amdmct/mct_ddr3/mctrci.c
@@ -18,6 +18,78 @@
  * Foundation, Inc.
  */
 
+static uint8_t fam15h_rdimm_rc2_control_code(struct DCTStatStruc *pDCTstat, uint8_t dct)
+{
+	uint8_t MaxDimmsInstallable = mctGet_NVbits(NV_MAX_DIMMS_PER_CH);
+
+	uint8_t package_type;
+	uint8_t control_code = 0;
+
+	package_type = mctGet_NVbits(NV_PACK_TYPE);
+	uint16_t MemClkFreq = Get_NB32_DCT(pDCTstat->dev_dct, dct, 0x94) & 0x1f;
+
+	/* Obtain number of DIMMs on channel */
+	uint8_t dimm_count = pDCTstat->MAdimms[dct];
+
+	/* FIXME
+	 * Assume there is only one register on the RDIMM for now
+	 */
+	uint8_t num_registers = 1;
+
+	if (package_type == PT_GR) {
+		/* Socket G34 */
+		/* Fam15h BKDG Rev. 3.14 section 2.10.5.7.1.2.1 Table 85 */
+		if (MaxDimmsInstallable == 1) {
+			if ((MemClkFreq == 0x4) || (MemClkFreq == 0x6)) {
+				/* DDR3-667 - DDR3-800 */
+				control_code = 0x1;
+			} else if ((MemClkFreq == 0xa) || (MemClkFreq == 0xe)) {
+				/* DDR3-1066 - DDR3-1333 */
+				if (num_registers == 1) {
+					control_code = 0x0;
+				} else {
+					control_code = 0x1;
+				}
+			} else if ((MemClkFreq == 0x12) || (MemClkFreq == 0x16)) {
+				/* DDR3-1600 - DDR3-1866 */
+				control_code = 0x0;
+			}
+		} else if (MaxDimmsInstallable == 2) {
+			if (dimm_count == 1) {
+				/* 1 DIMM detected */
+				if ((MemClkFreq == 0x4) || (MemClkFreq == 0x6)) {
+					/* DDR3-667 - DDR3-800 */
+					control_code = 0x1;
+				} else if ((MemClkFreq >= 0xa) && (MemClkFreq <= 0x12)) {
+					/* DDR3-1066 - DDR3-1600 */
+					if (num_registers == 1) {
+						control_code = 0x0;
+					} else {
+						control_code = 0x1;
+					}
+				}
+			} else if (dimm_count == 2) {
+				/* 2 DIMMs detected */
+				if (num_registers == 1) {
+					control_code = 0x1;
+				} else {
+					control_code = 0x8;
+				}
+			}
+		} else if (MaxDimmsInstallable == 3) {
+			/* TODO
+			 * 3 DIMM/channel support unimplemented
+			 */
+		}
+	} else {
+		/* TODO
+		 * Other socket support unimplemented
+		 */
+	}
+
+	return control_code;
+}
+
 static uint16_t memclk_to_freq(uint16_t memclk) {
 	uint16_t fam10h_freq_tab[] = {0, 0, 0, 400, 533, 667, 800};
 	uint16_t fam15h_freq_tab[] = {0, 0, 0, 0, 333, 0, 400, 0, 0, 0, 533, 0, 0, 0, 667, 0, 0, 0, 800, 0, 0, 0, 933};
@@ -37,36 +109,46 @@ static uint16_t memclk_to_freq(uint16_t memclk) {
 	return mem_freq;
 }
 
+static uint8_t rc_word_chip_select_lower_bit(void) {
+	if (is_fam15h()) {
+		return 21;
+	} else {
+		return 20;
+	}
+}
+
+static uint32_t rc_word_address_to_ctl_bits(uint32_t address) {
+	if (is_fam15h()) {
+		return (((address >> 3) & 0x1) << 2) << 18 | (address & 0x7);
+	} else {
+		return (((address >> 3) & 0x1) << 2) << 16 | (address & 0x7);
+	}
+}
+
 static uint32_t rc_word_value_to_ctl_bits(uint32_t value) {
-	return ((value >> 2) & 3) << 16 | ((value & 3) << 3);
+	if (is_fam15h()) {
+		return ((value >> 2) & 0x3) << 18 | ((value & 0x3) << 3);
+	} else {
+		return ((value >> 2) & 0x3) << 16 | ((value & 0x3) << 3);
+	}
 }
 
 static u32 mct_ControlRC(struct MCTStatStruc *pMCTstat,
-			struct DCTStatStruc *pDCTstat, u32 MrsChipSel, u32 CtrlWordNum)
+			struct DCTStatStruc *pDCTstat, uint8_t dct, u32 MrsChipSel, u32 CtrlWordNum)
 {
 	u8 Dimms, DimmNum;
 	u32 val;
-	u32 dct = 0;
 	uint8_t ddr_voltage_index;
 	uint16_t mem_freq;
 	uint8_t package_type = mctGet_NVbits(NV_PACK_TYPE);
 	uint8_t MaxDimmsInstallable = mctGet_NVbits(NV_MAX_DIMMS_PER_CH);
 
-	DimmNum = (MrsChipSel >> 20) & 0xFE;
+	DimmNum = (MrsChipSel >> rc_word_chip_select_lower_bit()) & 0xfe;
 
-	/* assume dct=0; */
-	/* if (dct == 1) */
-	/* DimmNum ++; */
-	/* cl +=8; */
+	if (dct == 1)
+		DimmNum++;
 
 	mem_freq = memclk_to_freq(pDCTstat->DIMMAutoSpeed);
-
-	if (pDCTstat->CSPresent_DCT[0] > 0) {
-		dct = 0;
-	} else if (pDCTstat->CSPresent_DCT[1] > 0 ) {
-		dct = 1;
-		DimmNum++;
-	}
 	Dimms = pDCTstat->MAdimms[dct];
 
 	ddr_voltage_index = dct_ddr_voltage_index(pDCTstat, dct);
@@ -76,21 +158,25 @@ static u32 mct_ControlRC(struct MCTStatStruc *pMCTstat,
 		val = 0x2;
 	else if (CtrlWordNum == 1) {
 		if (!((pDCTstat->DimmDRPresent | pDCTstat->DimmQRPresent) & (1 << DimmNum)))
-			val = 0xC; /* if single rank, set DBA1 and DBA0 */
+			val = 0xc; /* if single rank, set DBA1 and DBA0 */
 	} else if (CtrlWordNum == 2) {
-		if (package_type == PT_GR) {
-			/* Socket G34 */
-			if (MaxDimmsInstallable == 2) {
-				if (Dimms > 1)
-					val = 0x4;
+		if (is_fam15h()) {
+			val = fam15h_rdimm_rc2_control_code(pDCTstat, dct);
+		} else {
+			if (package_type == PT_GR) {
+				/* Socket G34 */
+				if (MaxDimmsInstallable == 2) {
+					if (Dimms > 1)
+						val = 0x4;
+				}
 			}
 		}
 	} else if (CtrlWordNum == 3) {
-		val = (pDCTstat->CtrlWrd3 >> (DimmNum << 2)) & 0xFF;
+		val = (pDCTstat->CtrlWrd3 >> (DimmNum << 2)) & 0xff;
 	} else if (CtrlWordNum == 4) {
-		val = (pDCTstat->CtrlWrd4 >> (DimmNum << 2)) & 0xFF;
+		val = (pDCTstat->CtrlWrd4 >> (DimmNum << 2)) & 0xff;
 	} else if (CtrlWordNum == 5) {
-		val = (pDCTstat->CtrlWrd5 >> (DimmNum << 2)) & 0xFF;
+		val = (pDCTstat->CtrlWrd5 >> (DimmNum << 2)) & 0xff;
 	} else if (CtrlWordNum == 8) {
 		if (package_type == PT_GR) {
 			/* Socket G34 */
@@ -99,7 +185,7 @@ static u32 mct_ControlRC(struct MCTStatStruc *pMCTstat,
 			}
 		}
 	} else if (CtrlWordNum == 9) {
-		val = 0xD;	/* DBA1, DBA0, DA3 = 0 */
+		val = 0xd;	/* DBA1, DBA0, DA3 = 0 */
 	} else if (CtrlWordNum == 10) {
 		val = 0x0;	/* Lowest operating frequency */
 	} else if (CtrlWordNum == 11) {
@@ -114,43 +200,30 @@ static u32 mct_ControlRC(struct MCTStatStruc *pMCTstat,
 	}
 	val &= 0xf;
 
-	printk(BIOS_SPEW, "Preparing to send DIMM RC%d: %02x\n", CtrlWordNum, val);
+	printk(BIOS_SPEW, "Preparing to send DCT %d DIMM RC%d: %02x\n", dct, CtrlWordNum, val);
 
 	val = MrsChipSel | rc_word_value_to_ctl_bits(val);
-
-	/* transfer Control word number to address [BA2,A2,A1,A0] */
-	if (CtrlWordNum > 7) {
-		val |= 1 << 18;
-		CtrlWordNum &= 7;
-	}
-	val |= CtrlWordNum;
+	val |= rc_word_address_to_ctl_bits(CtrlWordNum);
 
 	return val;
 }
 
 static void mct_SendCtrlWrd(struct MCTStatStruc *pMCTstat,
-			struct DCTStatStruc *pDCTstat, u32 val)
+			struct DCTStatStruc *pDCTstat, uint8_t dct, uint32_t val)
 {
-	uint8_t dct = 0;
 	u32 dev = pDCTstat->dev_dct;
 
-	if (pDCTstat->CSPresent_DCT[0] > 0) {
-		dct = 0;
-	} else if (pDCTstat->CSPresent_DCT[1] > 0 ){
-		dct = 1;
-	}
-
-	val |= Get_NB32_DCT(dev, dct, 0x7C) & ~0xFFFFFF;
+	val |= Get_NB32_DCT(dev, dct, 0x7c) & ~0xffffff;
 	val |= 1 << SendControlWord;
-	Set_NB32_DCT(dev, dct, 0x7C, val);
+	Set_NB32_DCT(dev, dct, 0x7c, val);
 
 	do {
-		val = Get_NB32_DCT(dev, dct, 0x7C);
+		val = Get_NB32_DCT(dev, dct, 0x7c);
 	} while (val & (1 << SendControlWord));
 }
 
 void mct_DramControlReg_Init_D(struct MCTStatStruc *pMCTstat,
-				struct DCTStatStruc *pDCTstat, u8 dct)
+				struct DCTStatStruc *pDCTstat, uint8_t dct)
 {
 	u8 MrsChipSel;
 	u32 dev = pDCTstat->dev_dct;
@@ -163,7 +236,7 @@ void mct_DramControlReg_Init_D(struct MCTStatStruc *pMCTstat,
 	for (MrsChipSel = 0; MrsChipSel < 8; MrsChipSel ++, MrsChipSel ++) {
 		if (pDCTstat->CSPresent & (1 << MrsChipSel)) {
 			val = Get_NB32_DCT(dev, dct, 0xa8);
-			val &= ~(0xF << 8);
+			val &= ~(0xf << 8);
 
 			switch (MrsChipSel) {
 				case 0:
@@ -184,8 +257,8 @@ void mct_DramControlReg_Init_D(struct MCTStatStruc *pMCTstat,
 			for (cw=0; cw <=15; cw ++) {
 				mct_Wait(1600);
 				if (!(cw==6 || cw==7)) {
-					val = mct_ControlRC(pMCTstat, pDCTstat, MrsChipSel << 20, cw);
-					mct_SendCtrlWrd(pMCTstat, pDCTstat, val);
+					val = mct_ControlRC(pMCTstat, pDCTstat, dct, MrsChipSel << rc_word_chip_select_lower_bit(), cw);
+					mct_SendCtrlWrd(pMCTstat, pDCTstat, dct, val);
 				}
 			}
 		}
@@ -195,7 +268,7 @@ void mct_DramControlReg_Init_D(struct MCTStatStruc *pMCTstat,
 }
 
 void FreqChgCtrlWrd(struct MCTStatStruc *pMCTstat,
-			struct DCTStatStruc *pDCTstat)
+			struct DCTStatStruc *pDCTstat, uint8_t dct)
 {
 	u32 SaveSpeed = pDCTstat->DIMMAutoSpeed;
 	u32 MrsChipSel;
@@ -208,10 +281,10 @@ void FreqChgCtrlWrd(struct MCTStatStruc *pMCTstat,
 	for (MrsChipSel=0; MrsChipSel < 8; MrsChipSel++, MrsChipSel++) {
 		if (pDCTstat->CSPresent & (1 << MrsChipSel)) {
 			/* 2. Program F2x[1, 0]A8[CtrlWordCS]=bit mask for target chip selects. */
-			val = Get_NB32_DCT(dev, 0, 0xA8); /* TODO: dct 0 / 1 select */
-			val &= ~(0xFF << 8);
-			val |= (0x3 << (MrsChipSel & 0xFE)) << 8;
-			Set_NB32_DCT(dev, 0, 0xA8, val); /* TODO: dct 0 / 1 select */
+			val = Get_NB32_DCT(dev, dct, 0xa8);
+			val &= ~(0xff << 8);
+			val |= (0x3 << (MrsChipSel & 0xfe)) << 8;
+			Set_NB32_DCT(dev, dct, 0xa8, val);
 
 			/* Resend control word 10 */
 			uint8_t freq_ctl_val = 0;
@@ -235,21 +308,21 @@ void FreqChgCtrlWrd(struct MCTStatStruc *pMCTstat,
 					break;
 			}
 
-			printk(BIOS_SPEW, "Preparing to send DIMM RC%d: %02x\n", 10, freq_ctl_val);
+			printk(BIOS_SPEW, "Preparing to send DCT %d DIMM RC%d: %02x\n", dct, 10, freq_ctl_val);
 
-			mct_SendCtrlWrd(pMCTstat, pDCTstat, MrsChipSel << 20 | 0x40002 | rc_word_value_to_ctl_bits(freq_ctl_val));
+			mct_SendCtrlWrd(pMCTstat, pDCTstat, dct, MrsChipSel << rc_word_chip_select_lower_bit() | rc_word_address_to_ctl_bits(10) | rc_word_value_to_ctl_bits(freq_ctl_val));
 
 			mct_Wait(1600);
 
 			/* Resend control word 2 */
-			val = mct_ControlRC(pMCTstat, pDCTstat, MrsChipSel << 20, 2);
-			mct_SendCtrlWrd(pMCTstat, pDCTstat, val);
+			val = mct_ControlRC(pMCTstat, pDCTstat, dct, MrsChipSel << rc_word_chip_select_lower_bit(), 2);
+			mct_SendCtrlWrd(pMCTstat, pDCTstat, dct, val);
 
 			mct_Wait(1600);
 
 			/* Resend control word 8 */
-			val = mct_ControlRC(pMCTstat, pDCTstat, MrsChipSel << 20, 8);
-			mct_SendCtrlWrd(pMCTstat, pDCTstat, val);
+			val = mct_ControlRC(pMCTstat, pDCTstat, dct, MrsChipSel << rc_word_chip_select_lower_bit(), 8);
+			mct_SendCtrlWrd(pMCTstat, pDCTstat, dct, val);
 
 			mct_Wait(1600);
 		}
diff --git a/src/northbridge/amd/amdmct/mct_ddr3/mctsdi.c b/src/northbridge/amd/amdmct/mct_ddr3/mctsdi.c
index 9ccf77e..09a5f68 100644
--- a/src/northbridge/amd/amdmct/mct_ddr3/mctsdi.c
+++ b/src/northbridge/amd/amdmct/mct_ddr3/mctsdi.c
@@ -445,13 +445,13 @@ static u32 mct_MR2(struct MCTStatStruc *pMCTstat,
 	u32 dev = pDCTstat->dev_dct;
 	u32 dword, ret;
 
+	/* The formula for chip select number is: CS = dimm*2+rank */
+	uint8_t dimm = MrsChipSel / 2;
+	uint8_t rank = MrsChipSel % 2;
+
 	if (is_fam15h()) {
 		uint8_t package_type = mctGet_NVbits(NV_PACK_TYPE);
 
-		/* The formula for chip select number is: CS = dimm*2+rank */
-		uint8_t dimm = MrsChipSel / 2;
-		uint8_t rank = MrsChipSel % 2;
-
 		/* FIXME: These parameters should be configurable
 		 * For now, err on the side of caution and enable automatic 2x refresh
 		 * when the DDR temperature rises above the internal limits
@@ -496,7 +496,7 @@ static u32 mct_MR2(struct MCTStatStruc *pMCTstat,
 		ret |= ((dword >> 10) & 3) << 9;
 	}
 
-	printk(BIOS_SPEW, "Going to send MR2 control word %08x\n", ret);
+	printk(BIOS_SPEW, "Going to send DCT %d DIMM %d rank %d MR2 control word %08x\n", dct, dimm, rank, ret);
 
 	return ret;
 }
@@ -507,6 +507,10 @@ static u32 mct_MR3(struct MCTStatStruc *pMCTstat,
 	u32 dev = pDCTstat->dev_dct;
 	u32 dword, ret;
 
+	/* The formula for chip select number is: CS = dimm*2+rank */
+	uint8_t dimm = MrsChipSel / 2;
+	uint8_t rank = MrsChipSel % 2;
+
 	if (is_fam15h()) {
 		ret = 0xc0000;
 		ret |= (MrsChipSel << 21);
@@ -527,7 +531,7 @@ static u32 mct_MR3(struct MCTStatStruc *pMCTstat,
 		ret |= (dword >> 24) & 7;
 	}
 
-	printk(BIOS_SPEW, "Going to send MR3 control word %08x\n", ret);
+	printk(BIOS_SPEW, "Going to send DCT %d DIMM %d rank %d MR3 control word %08x\n", dct, dimm, rank, ret);
 
 	return ret;
 }
@@ -538,6 +542,10 @@ static u32 mct_MR1(struct MCTStatStruc *pMCTstat,
 	u32 dev = pDCTstat->dev_dct;
 	u32 dword, ret;
 
+	/* The formula for chip select number is: CS = dimm*2+rank */
+	uint8_t dimm = MrsChipSel / 2;
+	uint8_t rank = MrsChipSel % 2;
+
 	if (is_fam15h()) {
 		uint8_t package_type = mctGet_NVbits(NV_PACK_TYPE);
 
@@ -553,10 +561,6 @@ static u32 mct_MR1(struct MCTStatStruc *pMCTstat,
 		ret = 0x40000;
 		ret |= (MrsChipSel << 21);
 
-		/* The formula for chip select number is: CS = dimm*2+rank */
-		uint8_t dimm = MrsChipSel / 2;
-		uint8_t rank = MrsChipSel % 2;
-
 		/* Determine if TQDS should be set */
 		if ((pDCTstat->Dimmx8Present & (1 << dimm))
 			&& (((dimm & 0x1)?(pDCTstat->Dimmx4Present&0x55):(pDCTstat->Dimmx4Present&0xaa)) != 0x0)
@@ -623,7 +627,7 @@ static u32 mct_MR1(struct MCTStatStruc *pMCTstat,
 			ret |= 1 << 12;
 	}
 
-	printk(BIOS_SPEW, "Going to send MR1 control word %08x\n", ret);
+	printk(BIOS_SPEW, "Going to send DCT %d DIMM %d rank %d MR1 control word %08x\n", dct, dimm, rank, ret);
 
 	return ret;
 }
@@ -634,6 +638,10 @@ static u32 mct_MR0(struct MCTStatStruc *pMCTstat,
 	u32 dev = pDCTstat->dev_dct;
 	u32 dword, ret, dword2;
 
+	/* The formula for chip select number is: CS = dimm*2+rank */
+	uint8_t dimm = MrsChipSel / 2;
+	uint8_t rank = MrsChipSel % 2;
+
 	if (is_fam15h()) {
 		ret = 0x00000;
 		ret |= (MrsChipSel << 21);
@@ -744,7 +752,7 @@ static u32 mct_MR0(struct MCTStatStruc *pMCTstat,
 		ret |= 1 << 8;
 	}
 
-	printk(BIOS_SPEW, "Going to send MR0 control word %08x\n", ret);
+	printk(BIOS_SPEW, "Going to send DCT %d DIMM %d rank %d MR0 control word %08x\n", dct, dimm, rank, ret);
 
 	return ret;
 }
@@ -811,6 +819,16 @@ void mct_DramInit_Sw_D(struct MCTStatStruc *pMCTstat,
 		/* 8.wait 360ns */
 		mct_Wait(80);
 
+		/* Set up address parity */
+		if ((pDCTstat->Status & (1 << SB_Registered))
+			|| (pDCTstat->Status & (1 << SB_LoadReduced))) {
+			if (is_fam15h()) {
+				dword = Get_NB32_DCT(dev, dct, 0x90);
+				dword |= 1 << ParEn;
+				Set_NB32_DCT(dev, dct, 0x90, dword);
+			}
+		}
+
 		/* The following steps are performed with registered DIMMs only and
 		 * must be done for each chip select pair */
 		if (pDCTstat->Status & (1 << SB_Registered))
diff --git a/src/northbridge/amd/amdmct/mct_ddr3/mctsrc.c b/src/northbridge/amd/amdmct/mct_ddr3/mctsrc.c
index abb5321..3af3eb2 100644
--- a/src/northbridge/amd/amdmct/mct_ddr3/mctsrc.c
+++ b/src/northbridge/amd/amdmct/mct_ddr3/mctsrc.c
@@ -1147,8 +1147,10 @@ static void dqsTrainRcvrEn_SW_Fam15(struct MCTStatStruc *pMCTstat,
 	uint8_t dimm;
 	uint8_t rank;
 	uint8_t lane;
+	uint8_t nibble;
 	uint8_t mem_clk;
 	uint16_t initial_seed;
+	uint8_t train_both_nibbles;
 	uint16_t current_total_delay[MAX_BYTE_LANES];
 	uint16_t dqs_ret_pass1_total_delay[MAX_BYTE_LANES];
 	uint16_t rank0_current_total_delay[MAX_BYTE_LANES];
@@ -1164,6 +1166,11 @@ static void dqsTrainRcvrEn_SW_Fam15(struct MCTStatStruc *pMCTstat,
 	print_debug_dqs("\nTrainRcvEn: Node", pDCTstat->Node_ID, 0);
 	print_debug_dqs("TrainRcvEn: Pass", Pass, 0);
 
+	train_both_nibbles = 0;
+	if (pDCTstat->Dimmx4Present)
+		if (is_fam15h())
+			train_both_nibbles = 1;
+
 	dev = pDCTstat->dev_dct;
 	index_reg = 0x98;
 	ch_start = 0;
@@ -1246,132 +1253,148 @@ static void dqsTrainRcvrEn_SW_Fam15(struct MCTStatStruc *pMCTstat,
 			else
 				_2Ranks = 0;
 			for (rank = 0; rank < (_2Ranks + 1); rank++) {
-				/* 2.10.5.8.2 (1)
-				 * Specify the target DIMM to be trained
-				 * Set TrNibbleSel = 0
-				 *
-				 * TODO: Add support for x4 DIMMs
-				 */
-				dword = Get_NB32_index_wait_DCT(dev, Channel, index_reg, 0x00000008);
-				dword &= ~(0x3 << 4);		/* TrDimmSel */
-				dword |= ((dimm & 0x3) << 4);
-				dword &= ~(0x1 << 2);		/* TrNibbleSel */
-				Set_NB32_index_wait_DCT(dev, Channel, index_reg, 0x00000008, dword);
-
-				/* 2.10.5.8.2 (2)
-				 * Retrieve gross and fine timing fields from write DQS registers
-				 */
-				read_dqs_write_timing_control_registers(current_total_delay, dev, Channel, dimm, index_reg);
-
-				/* 2.10.5.8.2.1
-				 * Generate the DQS Receiver Enable Training Seed Values
-				 */
-				if (Pass == FirstPass) {
-					initial_seed = fam15_receiver_enable_training_seed(pDCTstat, Channel, dimm, rank, package_type);
-
-					/* Adjust seed for the minimum platform supported frequency */
-					initial_seed = (uint16_t) (((((uint64_t) initial_seed) *
-						fam15h_freq_tab[mem_clk] * 100) / (mctGet_NVbits(NV_MIN_MEMCLK) * 100)));
-
-					for (lane = 0; lane < MAX_BYTE_LANES; lane++) {
-						uint16_t wl_pass1_delay;
-						wl_pass1_delay = current_total_delay[lane];
+				for (nibble = 0; nibble < (train_both_nibbles + 1); nibble++) {
+					/* 2.10.5.8.2 (1)
+					 * Specify the target DIMM and nibble to be trained
+					 */
+					dword = Get_NB32_index_wait_DCT(dev, Channel, index_reg, 0x00000008);
+					dword &= ~(0x3 << 4);		/* TrDimmSel = dimm */
+					dword |= ((dimm & 0x3) << 4);
+					dword &= ~(0x1 << 2);		/* TrNibbleSel = nibble */
+					dword |= ((nibble & 0x1) << 2);
+					Set_NB32_index_wait_DCT(dev, Channel, index_reg, 0x00000008, dword);
+
+					/* 2.10.5.8.2 (2)
+					 * Retrieve gross and fine timing fields from write DQS registers
+					 */
+					read_dqs_write_timing_control_registers(current_total_delay, dev, Channel, dimm, index_reg);
 
-						seed[lane] = initial_seed + wl_pass1_delay;
-					}
-				} else {
-					uint8_t addr_prelaunch = 0;		/* TODO: Fetch the correct value from RC2[0] */
-					uint16_t register_delay;
-					int16_t seed_prescaling;
-
-					memcpy(current_total_delay, dqs_ret_pass1_total_delay, sizeof(current_total_delay));
-					if ((pDCTstat->Status & (1 << SB_Registered))) {
-						if (addr_prelaunch)
-							register_delay = 0x30;
-						else
-							register_delay = 0x20;
-					} else if ((pDCTstat->Status & (1 << SB_LoadReduced))) {
-						/* TODO
-						* Load reduced DIMM support unimplemented
-						*/
-						register_delay = 0x0;
+					/* 2.10.5.8.2.1
+					 * Generate the DQS Receiver Enable Training Seed Values
+					 */
+					if (Pass == FirstPass) {
+						initial_seed = fam15_receiver_enable_training_seed(pDCTstat, Channel, dimm, rank, package_type);
+	
+						/* Adjust seed for the minimum platform supported frequency */
+						initial_seed = (uint16_t) (((((uint64_t) initial_seed) *
+							fam15h_freq_tab[mem_clk] * 100) / (mctGet_NVbits(NV_MIN_MEMCLK) * 100)));
+	
+						for (lane = 0; lane < MAX_BYTE_LANES; lane++) {
+							uint16_t wl_pass1_delay;
+							wl_pass1_delay = current_total_delay[lane];
+	
+							seed[lane] = initial_seed + wl_pass1_delay;
+						}
 					} else {
-						register_delay = 0x0;
+						uint8_t addr_prelaunch = 0;		/* TODO: Fetch the correct value from RC2[0] */
+						uint16_t register_delay;
+						int16_t seed_prescaling;
+	
+						memcpy(current_total_delay, dqs_ret_pass1_total_delay, sizeof(current_total_delay));
+						if ((pDCTstat->Status & (1 << SB_Registered))) {
+							if (addr_prelaunch)
+								register_delay = 0x30;
+							else
+								register_delay = 0x20;
+						} else if ((pDCTstat->Status & (1 << SB_LoadReduced))) {
+							/* TODO
+							 * Load reduced DIMM support unimplemented
+							 */
+							register_delay = 0x0;
+						} else {
+							register_delay = 0x0;
+						}
+	
+						for (lane = 0; lane < MAX_BYTE_LANES; lane++) {
+							seed_prescaling = current_total_delay[lane] - register_delay - 0x20;
+							seed[lane] = (uint16_t) (register_delay + ((((uint64_t) seed_prescaling) * fam15h_freq_tab[mem_clk] * 100) / (mctGet_NVbits(NV_MIN_MEMCLK) * 100)));
+						}
 					}
 
 					for (lane = 0; lane < MAX_BYTE_LANES; lane++) {
-						seed_prescaling = current_total_delay[lane] - register_delay - 0x20;
-						seed[lane] = (uint16_t) (register_delay + ((((uint64_t) seed_prescaling) * fam15h_freq_tab[mem_clk] * 100) / (mctGet_NVbits(NV_MIN_MEMCLK) * 100)));
+						seed_gross[lane] = (seed[lane] >> 5) & 0x1f;
+						seed_fine[lane] = seed[lane] & 0x1f;
+	
+						/*if (seed_gross[lane] == 0)
+							seed_pre_gross[lane] = 0;
+						else */if (seed_gross[lane] & 0x1)
+							seed_pre_gross[lane] = 1;
+						else
+							seed_pre_gross[lane] = 2;
+	
+						/* Calculate phase recovery delays */
+						phase_recovery_delays[lane] = ((seed_pre_gross[lane] & 0x1f) << 5) | (seed_fine[lane] & 0x1f);
+	
+						/* Set the gross delay.
+						* NOTE: While the BKDG states to only program DqsRcvEnGrossDelay, this appears
+						* to have been a misprint as DqsRcvEnFineDelay should be set to zero as well.
+						*/
+						current_total_delay[lane] = ((seed_gross[lane] & 0x1f) << 5);
 					}
-				}
-
-				for (lane = 0; lane < MAX_BYTE_LANES; lane++) {
-					seed_gross[lane] = (seed[lane] >> 5) & 0x1f;
-					seed_fine[lane] = seed[lane] & 0x1f;
 
-					/*if (seed_gross[lane] == 0)
-						seed_pre_gross[lane] = 0;
-					else */if (seed_gross[lane] & 0x1)
-						seed_pre_gross[lane] = 1;
-					else
-						seed_pre_gross[lane] = 2;
-
-					/* Calculate phase recovery delays */
-					phase_recovery_delays[lane] = ((seed_pre_gross[lane] & 0x1f) << 5) | (seed_fine[lane] & 0x1f);
-
-					/* Set the gross delay.
-					 * NOTE: While the BKDG states to only program DqsRcvEnGrossDelay, this appears
-					 * to have been a misprint as DqsRcvEnFineDelay should be set to zero as well.
+					/* 2.10.5.8.2 (2) / 2.10.5.8.2.1 (5 6)
+					 * Program PhRecFineDly and PhRecGrossDly
 					 */
-					current_total_delay[lane] = ((seed_gross[lane] & 0x1f) << 5);
-				}
+					write_dram_phase_recovery_control_registers(phase_recovery_delays, dev, Channel, dimm, index_reg);
 
-				/* 2.10.5.8.2 (2) / 2.10.5.8.2.1 (5 6)
-				 * Program PhRecFineDly and PhRecGrossDly
-				 */
-				write_dram_phase_recovery_control_registers(phase_recovery_delays, dev, Channel, dimm, index_reg);
-
-				/* 2.10.5.8.2 (2) / 2.10.5.8.2.1 (7)
-				 * Program the DQS Receiver Enable delay values for each lane
-				 */
-				write_dqs_receiver_enable_control_registers(current_total_delay, dev, Channel, dimm, index_reg);
+					/* 2.10.5.8.2 (2) / 2.10.5.8.2.1 (7)
+					 * Program the DQS Receiver Enable delay values for each lane
+					 */
+					write_dqs_receiver_enable_control_registers(current_total_delay, dev, Channel, dimm, index_reg);
 	
-				/* 2.10.5.8.2 (3)
-				 * Program DqsRcvTrEn = 1
-				 */
-				dword = Get_NB32_index_wait_DCT(dev, Channel, index_reg, 0x00000008);
-				dword |= (0x1 << 13);
-				Set_NB32_index_wait_DCT(dev, Channel, index_reg, 0x00000008, dword);
-
-				/* 2.10.5.8.2 (4)
-				 * Issue 192 read requests to the target rank
-				 */
-				generate_dram_receiver_enable_training_pattern_fam15(pMCTstat, pDCTstat, Channel, Receiver + (rank & 0x1));
+					/* 2.10.5.8.2 (3)
+					 * Program DqsRcvTrEn = 1
+					 */
+					dword = Get_NB32_index_wait_DCT(dev, Channel, index_reg, 0x00000008);
+					dword |= (0x1 << 13);
+					Set_NB32_index_wait_DCT(dev, Channel, index_reg, 0x00000008, dword);
+	
+					/* 2.10.5.8.2 (4)
+					 * Issue 192 read requests to the target rank
+					 */
+					generate_dram_receiver_enable_training_pattern_fam15(pMCTstat, pDCTstat, Channel, Receiver + (rank & 0x1));
+	
+					/* 2.10.5.8.2 (5)
+					 * Program DqsRcvTrEn = 0
+					 */
+					dword = Get_NB32_index_wait_DCT(dev, Channel, index_reg, 0x00000008);
+					dword &= ~(0x1 << 13);
+					Set_NB32_index_wait_DCT(dev, Channel, index_reg, 0x00000008, dword);
 
-				/* 2.10.5.8.2 (5)
-				 * Program DqsRcvTrEn = 0
-				 */
-				dword = Get_NB32_index_wait_DCT(dev, Channel, index_reg, 0x00000008);
-				dword &= ~(0x1 << 13);
-				Set_NB32_index_wait_DCT(dev, Channel, index_reg, 0x00000008, dword);
+					/* 2.10.5.8.2 (6)
+					 * Read PhRecGrossDly, PhRecFineDly
+					 */
+					read_dram_phase_recovery_control_registers(phase_recovery_delays, dev, Channel, dimm, index_reg);
 
-				/* 2.10.5.8.2 (6)
-				 * Read PhRecGrossDly, PhRecFineDly
-				 */
-				read_dram_phase_recovery_control_registers(phase_recovery_delays, dev, Channel, dimm, index_reg);
+					/* 2.10.5.8.2 (7)
+					 * Calculate and program the DQS Receiver Enable delay values
+					 */
+					for (lane = 0; lane < MAX_BYTE_LANES; lane++) {
+						current_total_delay[lane] = (phase_recovery_delays[lane] & 0x1f);
+						current_total_delay[lane] |= ((seed_gross[lane] + ((phase_recovery_delays[lane] >> 5) & 0x1f) - seed_pre_gross[lane] + 1) << 5);
+						if (nibble == 0) {
+							if (lane == 8)
+								pDCTstat->CH_D_BC_RCVRDLY[Channel][dimm] = current_total_delay[lane];
+							else
+								pDCTstat->CH_D_B_RCVRDLY[Channel][dimm][lane] = current_total_delay[lane];
+						} else {
+							/* 2.10.5.8.2 (1)
+							 * Average the trained values of both nibbles on x4 DIMMs
+							 */
+							if (lane == 8)
+								pDCTstat->CH_D_BC_RCVRDLY[Channel][dimm] = (pDCTstat->CH_D_BC_RCVRDLY[Channel][dimm] + current_total_delay[lane]) / 2;
+							else
+								pDCTstat->CH_D_B_RCVRDLY[Channel][dimm][lane] = (pDCTstat->CH_D_B_RCVRDLY[Channel][dimm][lane] + current_total_delay[lane]) / 2;
+						}
+					}
 
-				/* 2.10.5.8.2 (7)
-				 * Calculate and program the DQS Receiver Enable delay values
-				 */
-				for (lane = 0; lane < MAX_BYTE_LANES; lane++) {
-					current_total_delay[lane] = (phase_recovery_delays[lane] & 0x1f);
-					current_total_delay[lane] |= ((seed_gross[lane] + ((phase_recovery_delays[lane] >> 5) & 0x1f) - seed_pre_gross[lane] + 1) << 5);
-					if (lane == 8)
-						pDCTstat->CH_D_BC_RCVRDLY[Channel][dimm] = current_total_delay[lane];
-					else
-						pDCTstat->CH_D_B_RCVRDLY[Channel][dimm][lane] = current_total_delay[lane];
+#if DQS_TRAIN_DEBUG > 1
+					for (lane = 0; lane < 8; lane++)
+						printk(BIOS_DEBUG, "\t\tTrainRcvEn55: Channel: %d dimm: %d nibble: %d lane %d current_total_delay: %04x CH_D_B_RCVRDLY: %04x\n",
+							Channel, dimm, nibble, lane, current_total_delay[lane], pDCTstat->CH_D_B_RCVRDLY[Channel][dimm][lane]);
+#endif
+					write_dqs_receiver_enable_control_registers(current_total_delay, dev, Channel, dimm, index_reg);
 				}
-				write_dqs_receiver_enable_control_registers(current_total_delay, dev, Channel, dimm, index_reg);
 
 				if (rank == 0) {
 					/* Back up the Rank 0 delays for later use */
@@ -1396,7 +1419,7 @@ static void dqsTrainRcvrEn_SW_Fam15(struct MCTStatStruc *pMCTstat,
 
 #if DQS_TRAIN_DEBUG > 0
 			for (lane = 0; lane < 8; lane++)
-				print_debug_dqs_pair("\t\tTrainRcvEn55: Lane ", lane, " current_total_delay ", current_total_delay[lane], 2);
+				print_debug_dqs_pair("\t\tTrainRcvEn56: Lane ", lane, " current_total_delay ", current_total_delay[lane], 2);
 #endif
 		}
 	}
@@ -1816,15 +1839,23 @@ void mctSetEccDQSRcvrEn_D(struct MCTStatStruc *pMCTstat,
 }
 
 void phyAssistedMemFnceTraining(struct MCTStatStruc *pMCTstat,
-			struct DCTStatStruc *pDCTstatA)
+			struct DCTStatStruc *pDCTstatA, int16_t single_node_number)
 {
 	u8 Node = 0;
 	struct DCTStatStruc *pDCTstat;
 
 	printk(BIOS_DEBUG, "%s: Start\n", __func__);
 
+	uint8_t start_node = 0;
+	uint8_t end_node = MAX_NODES_SUPPORTED;
+
+	if (single_node_number >= 0) {
+		start_node = single_node_number;
+		end_node = single_node_number;
+	}
+
 	/* FIXME: skip for Ax */
-	for (Node = 0; Node < MAX_NODES_SUPPORTED; Node++) {
+	for (Node = start_node; Node < end_node; Node++) {
 		pDCTstat = pDCTstatA + Node;
 		if (!pDCTstat->NodePresent)
 			continue;
@@ -1848,6 +1879,8 @@ void phyAssistedMemFnceTraining(struct MCTStatStruc *pMCTstat,
 					if (!pDCTstat->DIMMValidDCT[dct])
 						continue;
 
+					printk(BIOS_SPEW, "%s: training node %d DCT %d\n", __func__, Node, dct);
+
 					/* Back up D18F2x9C_x0000_0004_dct[1:0] */
 					datc_backup = Get_NB32_index_wait_DCT(dev, dct, index_reg, 0x00000004);
 
@@ -1986,6 +2019,8 @@ void phyAssistedMemFnceTraining(struct MCTStatStruc *pMCTstat,
 
 					/* Restore D18F2x9C_x0000_0004_dct[1:0] */
 					Set_NB32_index_wait_DCT(dev, dct, index_reg, 0x00000004, datc_backup);
+
+					printk(BIOS_SPEW, "%s: done training node %d DCT %d\n", __func__, Node, dct);
 				}
 			} else {
 				fenceDynTraining_D(pMCTstat, pDCTstat, 0);
@@ -1998,7 +2033,7 @@ void phyAssistedMemFnceTraining(struct MCTStatStruc *pMCTstat,
 }
 
 static uint32_t fenceDynTraining_D(struct MCTStatStruc *pMCTstat,
-			struct DCTStatStruc *pDCTstat, u8 dct)
+			struct DCTStatStruc *pDCTstat, uint8_t dct)
 {
 	u16 avRecValue;
 	u32 val;
diff --git a/src/northbridge/amd/amdmct/mct_ddr3/mctwl.c b/src/northbridge/amd/amdmct/mct_ddr3/mctwl.c
index 6b63ba0..3153e46 100644
--- a/src/northbridge/amd/amdmct/mct_ddr3/mctwl.c
+++ b/src/northbridge/amd/amdmct/mct_ddr3/mctwl.c
@@ -19,7 +19,7 @@
  */
 
 static void FreqChgCtrlWrd(struct MCTStatStruc *pMCTstat,
-			struct DCTStatStruc *pDCTstat);
+			struct DCTStatStruc *pDCTstat, uint8_t dct);
 
 
 static void AgesaDelay(u32 msec)
@@ -353,11 +353,14 @@ static void ExitSelfRefresh(struct MCTStatStruc *pMCTstat,
 }
 
 void SetTargetFreq(struct MCTStatStruc *pMCTstat,
-					struct DCTStatStruc *pDCTstat)
+					struct DCTStatStruc *pDCTstatA, uint8_t Node)
 {
 	uint32_t dword;
 	uint8_t package_type = mctGet_NVbits(NV_PACK_TYPE);
 
+	struct DCTStatStruc *pDCTstat;
+	pDCTstat = pDCTstatA + Node;
+
 	if (is_fam15h()) {
 		/* Program F2x[1, 0]90[DisDllShutDownSR]=1. */
 		if (pDCTstat->DIMMValidDCT[0]) {
@@ -391,7 +394,7 @@ void SetTargetFreq(struct MCTStatStruc *pMCTstat,
 		uint8_t dct;
 		for (dct = 0; dct < 2; dct++) {
 			if (pDCTstat->DIMMValidDCT[dct]) {
-				phyAssistedMemFnceTraining(pMCTstat, pDCTstat);
+				phyAssistedMemFnceTraining(pMCTstat, pDCTstatA, Node);
 				InitPhyCompensation(pMCTstat, pDCTstat, dct);
 			}
 		}
@@ -438,7 +441,12 @@ void SetTargetFreq(struct MCTStatStruc *pMCTstat,
 		else
 			pDCTstat->CSPresent = pDCTstat->CSPresent_DCT[1];
 
-		FreqChgCtrlWrd(pMCTstat, pDCTstat);
+		if (pDCTstat->DIMMValidDCT[0]) {
+			FreqChgCtrlWrd(pMCTstat, pDCTstat, 0);
+		}
+		if (pDCTstat->DIMMValidDCT[1]) {
+			FreqChgCtrlWrd(pMCTstat, pDCTstat, 1);
+		}
 	}
 }
 
diff --git a/src/northbridge/amd/amdmct/mct_ddr3/mhwlc_d.c b/src/northbridge/amd/amdmct/mct_ddr3/mhwlc_d.c
index fb3b3d8..3391dcf 100644
--- a/src/northbridge/amd/amdmct/mct_ddr3/mhwlc_d.c
+++ b/src/northbridge/amd/amdmct/mct_ddr3/mhwlc_d.c
@@ -35,9 +35,9 @@ u32 swapBankBits(struct DCTStatStruc *pDCTstat, uint8_t dct, uint32_t MRSValue);
 void prepareDimms(struct MCTStatStruc *pMCTstat, struct DCTStatStruc *pDCTstat,
 	u8 dct, u8 dimm, BOOL wl);
 void programODT(struct MCTStatStruc *pMCTstat, struct DCTStatStruc *pDCTstat, uint8_t dct, u8 dimm);
-void procConfig(struct MCTStatStruc *pMCTstat, struct DCTStatStruc *pDCTstat, uint8_t dct, u8 dimm, u8 pass);
+void procConfig(struct MCTStatStruc *pMCTstat, struct DCTStatStruc *pDCTstat, uint8_t dct, uint8_t dimm, uint8_t pass, uint8_t nibble);
 void setWLByteDelay(struct DCTStatStruc *pDCTstat, uint8_t dct, u8 ByteLane, u8 dimm, u8 targetAddr, uint8_t pass);
-void getWLByteDelay(struct DCTStatStruc *pDCTstat, uint8_t dct, u8 ByteLane, u8 dimm, uint8_t pass);
+void getWLByteDelay(struct DCTStatStruc *pDCTstat, uint8_t dct, u8 ByteLane, u8 dimm, uint8_t pass, uint8_t nibble);
 
 static int32_t abs(int32_t val) {
 	if (val < 0)
@@ -76,6 +76,8 @@ uint8_t AgesaHwWlPhase1(struct MCTStatStruc *pMCTstat, struct DCTStatStruc *pDCT
 {
 	u8 ByteLane;
 	u32 Value, Addr;
+	uint8_t nibble = 0;
+	uint8_t train_both_nibbles;
 	u16 Addl_Data_Offset, Addl_Data_Port;
 	sMCTStruct *pMCTData = pDCTstat->C_MCTPtr;
 	sDCTStruct *pDCTData = pDCTstat->C_DCTPtr[dct];
@@ -88,98 +90,108 @@ uint8_t AgesaHwWlPhase1(struct MCTStatStruc *pMCTstat, struct DCTStatStruc *pDCT
 			DRAM_ADD_DCT_PHY_CONTROL_REG, TrDimmSelStart,
 			TrDimmSelEnd, (u32)dimm);
 
-	if (is_fam15h()) {
-		/* Set TrNibbleSel = 0
-		 *
-		 * TODO: Add support for x4 DIMMs
-		 */
-		set_DCT_ADDR_Bits(pDCTData, dct, pDCTData->NodeId, FUN_DCT,
-				DRAM_ADD_DCT_PHY_CONTROL_REG, 2,
-				2, (u32)0);
-	}
+	train_both_nibbles = 0;
+	if (pDCTstat->Dimmx4Present)
+		if (is_fam15h())
+			train_both_nibbles = 1;
 
-	/* 2. Prepare the DIMMs for write levelization using DDR3-defined
-	 * MR commands. */
-	prepareDimms(pMCTstat, pDCTstat, dct, dimm, TRUE);
+	for (nibble = 0; nibble < (train_both_nibbles + 1); nibble++) {
+		printk(BIOS_SPEW, "AgesaHwWlPhase1: training nibble %d\n", nibble);
 
-	/* 3. After the DIMMs are configured, BIOS waits 40 MEMCLKs to
-	 *    satisfy DDR3-defined internal DRAM timing.
-	 */
-	if (is_fam15h())
-		precise_memclk_delay_fam15(pMCTstat, pDCTstat, dct, 40);
-	else
-		pMCTData->AgesaDelay(40);
+		if (is_fam15h()) {
+			/* Program F2x[1, 0]9C_x08[WrtLvTrEn]=0 */
+			set_DCT_ADDR_Bits(pDCTData, dct, pDCTData->NodeId, FUN_DCT,
+					DRAM_ADD_DCT_PHY_CONTROL_REG, WrtLvTrEn, WrtLvTrEn, 0);
+
+			/* Set TrNibbleSel */
+			set_DCT_ADDR_Bits(pDCTData, dct, pDCTData->NodeId, FUN_DCT,
+					DRAM_ADD_DCT_PHY_CONTROL_REG, 2,
+					2, (uint32_t)nibble);
+		}
 
-	/* 4. Configure the processor's DDR phy for write levelization training: */
-	procConfig(pMCTstat, pDCTstat, dct, dimm, pass);
+		/* 2. Prepare the DIMMs for write levelization using DDR3-defined
+		 * MR commands. */
+		prepareDimms(pMCTstat, pDCTstat, dct, dimm, TRUE);
 
-	/* 5. Begin write levelization training:
-	 *  Program F2x[1, 0]9C_x08[WrtLvTrEn]=1. */
-	if (pDCTData->LogicalCPUID & (AMD_DR_Cx | AMD_DR_Dx | AMD_FAM15_ALL))
-	{
-		set_DCT_ADDR_Bits(pDCTData, dct, pDCTData->NodeId, FUN_DCT,
-				DRAM_ADD_DCT_PHY_CONTROL_REG, WrtLvTrEn, WrtLvTrEn, 1);
-	}
-	else
-	{
-		/* Broadcast write to all D3Dbyte chipset register offset 0xc
-		 * Set bit 0 (wrTrain)
-		 * Program bit 4 to nibble being trained (only matters for x4dimms)
-		 * retain value of 3:2 (Trdimmsel)
-		 * reset bit 5 (FrzPR)
+		/* 3. After the DIMMs are configured, BIOS waits 40 MEMCLKs to
+		 *    satisfy DDR3-defined internal DRAM timing.
 		 */
-		if (dct)
+		if (is_fam15h())
+			precise_memclk_delay_fam15(pMCTstat, pDCTstat, dct, 40);
+		else
+			pMCTData->AgesaDelay(40);
+
+		/* 4. Configure the processor's DDR phy for write levelization training: */
+		procConfig(pMCTstat, pDCTstat, dct, dimm, pass, nibble);
+
+		/* 5. Begin write levelization training:
+		 *  Program F2x[1, 0]9C_x08[WrtLvTrEn]=1. */
+		if (pDCTData->LogicalCPUID & (AMD_DR_Cx | AMD_DR_Dx | AMD_FAM15_ALL))
 		{
-			Addl_Data_Offset=0x198;
-			Addl_Data_Port=0x19C;
+			set_DCT_ADDR_Bits(pDCTData, dct, pDCTData->NodeId, FUN_DCT,
+					DRAM_ADD_DCT_PHY_CONTROL_REG, WrtLvTrEn, WrtLvTrEn, 1);
 		}
 		else
 		{
-			Addl_Data_Offset=0x98;
-			Addl_Data_Port=0x9C;
+			/* Broadcast write to all D3Dbyte chipset register offset 0xc
+			 * Set bit 0 (wrTrain)
+			 * Program bit 4 to nibble being trained (only matters for x4dimms)
+			 * retain value of 3:2 (Trdimmsel)
+			 * reset bit 5 (FrzPR)
+			 */
+			if (dct)
+			{
+				Addl_Data_Offset=0x198;
+				Addl_Data_Port=0x19C;
+			}
+			else
+			{
+				Addl_Data_Offset=0x98;
+				Addl_Data_Port=0x9C;
+			}
+			Addr=0x0D00000C;
+			AmdMemPCIWriteBits(MAKE_SBDFO(0,0,24+(pDCTData->NodeId),FUN_DCT,Addl_Data_Offset), 31, 0, &Addr);
+			while ((get_Bits(pDCTData,FUN_DCT,pDCTData->NodeId, FUN_DCT, Addl_Data_Offset,
+					DctAccessDone, DctAccessDone)) == 0);
+			AmdMemPCIReadBits(MAKE_SBDFO(0,0,24+(pDCTData->NodeId),FUN_DCT,Addl_Data_Port), 31, 0, &Value);
+			Value = bitTestSet(Value, 0);	/* enable WL training */
+			Value = bitTestReset(Value, 4); /* for x8 only */
+			Value = bitTestReset(Value, 5); /* for hardware WL training */
+			AmdMemPCIWriteBits(MAKE_SBDFO(0,0,24+(pDCTData->NodeId),FUN_DCT,Addl_Data_Port), 31, 0, &Value);
+			Addr=0x4D030F0C;
+			AmdMemPCIWriteBits(MAKE_SBDFO(0,0,24+(pDCTData->NodeId),FUN_DCT,Addl_Data_Offset), 31, 0, &Addr);
+			while ((get_Bits(pDCTData,FUN_DCT,pDCTData->NodeId, FUN_DCT, Addl_Data_Offset,
+					DctAccessDone, DctAccessDone)) == 0);
 		}
-		Addr=0x0D00000C;
-		AmdMemPCIWriteBits(MAKE_SBDFO(0,0,24+(pDCTData->NodeId),FUN_DCT,Addl_Data_Offset), 31, 0, &Addr);
-		while ((get_Bits(pDCTData,FUN_DCT,pDCTData->NodeId, FUN_DCT, Addl_Data_Offset,
-				DctAccessDone, DctAccessDone)) == 0);
-		AmdMemPCIReadBits(MAKE_SBDFO(0,0,24+(pDCTData->NodeId),FUN_DCT,Addl_Data_Port), 31, 0, &Value);
-		Value = bitTestSet(Value, 0);	/* enable WL training */
-		Value = bitTestReset(Value, 4); /* for x8 only */
-		Value = bitTestReset(Value, 5); /* for hardware WL training */
-		AmdMemPCIWriteBits(MAKE_SBDFO(0,0,24+(pDCTData->NodeId),FUN_DCT,Addl_Data_Port), 31, 0, &Value);
-		Addr=0x4D030F0C;
-		AmdMemPCIWriteBits(MAKE_SBDFO(0,0,24+(pDCTData->NodeId),FUN_DCT,Addl_Data_Offset), 31, 0, &Addr);
-		while ((get_Bits(pDCTData,FUN_DCT,pDCTData->NodeId, FUN_DCT, Addl_Data_Offset,
-				DctAccessDone, DctAccessDone)) == 0);
-	}
 
-	if (is_fam15h())
-		proc_MFENCE();
+		if (is_fam15h())
+			proc_MFENCE();
 
-	/* Wait 200 MEMCLKs. If executing pass 2, wait 32 MEMCLKs. */
-	if (is_fam15h())
-		precise_memclk_delay_fam15(pMCTstat, pDCTstat, dct, 200);
-	else
-		pMCTData->AgesaDelay(140);
+		/* Wait 200 MEMCLKs. If executing pass 2, wait 32 MEMCLKs. */
+		if (is_fam15h())
+			precise_memclk_delay_fam15(pMCTstat, pDCTstat, dct, 200);
+		else
+			pMCTData->AgesaDelay(140);
 
-	/* Program F2x[1, 0]9C_x08[WrtLevelTrEn]=0. */
-	set_DCT_ADDR_Bits(pDCTData, dct, pDCTData->NodeId, FUN_DCT,
-			DRAM_ADD_DCT_PHY_CONTROL_REG, WrtLvTrEn, WrtLvTrEn, 0);
+		/* Program F2x[1, 0]9C_x08[WrtLevelTrEn]=0. */
+		set_DCT_ADDR_Bits(pDCTData, dct, pDCTData->NodeId, FUN_DCT,
+				DRAM_ADD_DCT_PHY_CONTROL_REG, WrtLvTrEn, WrtLvTrEn, 0);
 
-	/* Read from registers F2x[1, 0]9C_x[51:50] and F2x[1, 0]9C_x52
-	 * to get the gross and fine delay settings
-	 * for the target DIMM and save these values. */
-	for (ByteLane = 0; ByteLane < MAX_BYTE_LANES; ByteLane++) {
-		getWLByteDelay(pDCTstat, dct, ByteLane, dimm, pass);
-	}
+		/* Read from registers F2x[1, 0]9C_x[51:50] and F2x[1, 0]9C_x52
+		 * to get the gross and fine delay settings
+		 * for the target DIMM and save these values. */
+		for (ByteLane = 0; ByteLane < MAX_BYTE_LANES; ByteLane++) {
+			getWLByteDelay(pDCTstat, dct, ByteLane, dimm, pass, nibble);
+		}
 
-	pDCTData->WLCriticalGrossDelayPrevPass = 0x1f;
+		pDCTData->WLCriticalGrossDelayPrevPass = 0x0;
+	}
 
 	return 0;
 }
 
 uint8_t AgesaHwWlPhase2(struct MCTStatStruc *pMCTstat, struct DCTStatStruc *pDCTstat,
-		u8 dct, u8 dimm, u8 pass)
+		uint8_t dct, uint8_t dimm, uint8_t pass)
 {
 	u8 ByteLane;
 	uint8_t status = 0;
@@ -190,6 +202,12 @@ uint8_t AgesaHwWlPhase2(struct MCTStatStruc *pMCTstat, struct DCTStatStruc *pDCT
 		int32_t cgd = pDCTData->WLCriticalGrossDelayPrevPass;
 		uint8_t index = (uint8_t)(MAX_BYTE_LANES * dimm);
 
+		printk(BIOS_SPEW, "\toriginal critical gross delay: %d\n", cgd);
+
+		/* FIXME
+		 * For now, disable CGD adjustment as it seems to interfere with registered DIMM training
+		 */
+
 		/* Calculate the Critical Gross Delay */
 		for (ByteLane = 0; ByteLane < MAX_BYTE_LANES; ByteLane++) {
 			/* Calculate the gross delay differential for this lane */
@@ -205,6 +223,8 @@ uint8_t AgesaHwWlPhase2(struct MCTStatStruc *pMCTstat, struct DCTStatStruc *pDCT
 				cgd = gross_diff[ByteLane];
 		}
 
+		printk(BIOS_SPEW, "\tnew critical gross delay: %d\n", cgd);
+
 		pDCTData->WLCriticalGrossDelayPrevPass = cgd;
 
 		if (pDCTstat->Speed != pDCTstat->TargetFreq) {
@@ -281,7 +301,7 @@ uint8_t AgesaHwWlPhase3(struct MCTStatStruc *pMCTstat, struct DCTStatStruc *pDCT
 				gross_diff[ByteLane] = pDCTData->WLSeedGrossDelay[index+ByteLane] + pDCTData->WLGrossDelay[index+ByteLane];
 				gross_diff[ByteLane] -= pDCTData->WLSeedPreGrossDelay[index+ByteLane];
 
-				/* Prevent underflow in the presence of noise / instability*/
+				/* Prevent underflow in the presence of noise / instability */
 				if (gross_diff[ByteLane] < cgd)
 					gross_diff[ByteLane] = cgd;
 
@@ -289,7 +309,8 @@ uint8_t AgesaHwWlPhase3(struct MCTStatStruc *pMCTstat, struct DCTStatStruc *pDCT
 			}
 		} else {
 			dword = Get_NB32_DCT(pDCTstat->dev_dct, dct, 0xa8);
-			dword &= ~(0x3 << 24);			/* WrDqDqsEarly = 0 */
+			dword &= ~(0x3 << 24);			/* WrDqDqsEarly = pDCTData->WrDqsGrossDlyBaseOffset */
+			dword |= ((pDCTData->WrDqsGrossDlyBaseOffset & 0x3) << 24);
 			Set_NB32_DCT(pDCTstat->dev_dct, dct, 0xa8, dword);
 		}
 	}
@@ -959,7 +980,7 @@ static uint16_t fam15h_next_lowest_memclk_freq(uint16_t memclk_freq)
 #endif
 
 /*-----------------------------------------------------------------------------
- * void procConfig(MCTStruct *MCTData,DCTStruct *DCTData, u8 Dimm, u8 Pass)
+ * void procConfig(MCTStruct *MCTData,DCTStruct *DCTData, u8 Dimm, u8 Pass, u8 Nibble)
  *
  *  Description:
  *       This function programs the ODT values for the NB
@@ -972,13 +993,14 @@ static uint16_t fam15h_next_lowest_memclk_freq(uint16_t memclk_freq)
  *       OUT
  * ----------------------------------------------------------------------------
  */
-void procConfig(struct MCTStatStruc *pMCTstat, struct DCTStatStruc *pDCTstat, uint8_t dct, u8 dimm, u8 pass)
+void procConfig(struct MCTStatStruc *pMCTstat, struct DCTStatStruc *pDCTstat, uint8_t dct, uint8_t dimm, uint8_t pass, uint8_t nibble)
 {
 	u8 ByteLane, MemClkFreq;
 	int32_t Seed_Gross;
 	int32_t Seed_Fine;
 	uint8_t Seed_PreGross;
 	u32 Value, Addr;
+	uint32_t dword;
 	u16 Addl_Data_Offset, Addl_Data_Port;
 	sMCTStruct *pMCTData = pDCTstat->C_MCTPtr;
 	sDCTStruct *pDCTData = pDCTstat->C_DCTPtr[dct];
@@ -1048,10 +1070,17 @@ void procConfig(struct MCTStatStruc *pMCTstat, struct DCTStatStruc *pDCTstat, ui
 			uint8_t AddrCmdPrelaunch = 0;		/* TODO: Fetch the correct value from RC2[0] */
 			uint8_t package_type = mctGet_NVbits(NV_PACK_TYPE);
 			uint16_t Seed_Total = 0;
+			pDCTData->WrDqsGrossDlyBaseOffset = 0x0;
 			if (package_type == PT_GR) {
 				/* Socket G34: Fam15h BKDG v3.14 Table 96 */
 				if (pDCTData->Status[DCT_STATUS_REGISTERED]) {
+					/* TODO
+					 * Implement mainboard-specific seed and
+					 * WrDqsGrossDly base overrides.
+					 * 0x41 and 0x0 are the "stock" values
+					 */
 					Seed_Total = 0x41;
+					pDCTData->WrDqsGrossDlyBaseOffset = 0x2;
 				} else if (pDCTData->Status[DCT_STATUS_LOAD_REDUCED]) {
 					Seed_Total = 0x0;
 				} else {
@@ -1133,15 +1162,16 @@ void procConfig(struct MCTStatStruc *pMCTstat, struct DCTStatStruc *pDCTstat, ui
 			printk(BIOS_SPEW, "\tLane %02x initial seed: %04x\n", ByteLane, ((Seed_Gross & 0x1f) << 5) | (Seed_Fine & 0x1f));
 		}
 	} else {
-		/* Pass 2 */
-		/* From BKDG, Write Leveling Seed Value. */
-		if (is_fam15h()) {
-			uint32_t RegisterDelay;
-			int32_t SeedTotal;
-			int32_t SeedTotalPreScaling;
-			uint8_t AddrCmdPrelaunch = 0;		/* TODO: Fetch the correct value from RC2[0] */
+		if (nibble == 0) {
+			/* Pass 2 */
+			/* From BKDG, Write Leveling Seed Value. */
+			if (is_fam15h()) {
+				uint32_t RegisterDelay;
+				int32_t SeedTotal[MAX_BYTE_LANES];
+				int32_t SeedTotalPreScaling[MAX_BYTE_LANES];
+				uint32_t WrDqDqsEarly;
+				uint8_t AddrCmdPrelaunch = 0;		/* TODO: Fetch the correct value from RC2[0] */
 
-			for (ByteLane = 0; ByteLane < MAX_BYTE_LANES; ByteLane++) {
 				if (pDCTData->Status[DCT_STATUS_REGISTERED]) {
 					if (AddrCmdPrelaunch)
 						RegisterDelay = 0x30;
@@ -1150,84 +1180,133 @@ void procConfig(struct MCTStatStruc *pMCTstat, struct DCTStatStruc *pDCTstat, ui
 				} else {
 					RegisterDelay = 0;
 				}
+
 				/* Retrieve WrDqDqsEarly */
-				AmdMemPCIReadBits(MAKE_SBDFO(0,0,24+(pDCTData->NodeId), FUN_DCT, 0xa8), 25, 24, &Value);
+				dword = Get_NB32_DCT(pDCTstat->dev_dct, dct, 0xa8);
+				WrDqDqsEarly = (dword >> 24) & 0x3;
 
-				/* Calculate adjusted seed values */
-				SeedTotal = (pDCTData->WLFineDelayPrevPass[MAX_BYTE_LANES*dimm+ByteLane] & 0x1f) |
-					((pDCTData->WLGrossDelayPrevPass[MAX_BYTE_LANES*dimm+ByteLane] & 0x1f) << 5);
-				SeedTotalPreScaling = (SeedTotal - RegisterDelay - (0x20 * Value));
-				SeedTotal = (int32_t) (RegisterDelay + ((((int64_t) SeedTotalPreScaling) *
-					fam15h_freq_tab[MemClkFreq] * 100) / (fam15h_freq_tab[pDCTData->WLPrevMemclkFreq] * 100)));
+				/* FIXME
+				 * Ignore WrDqDqsEarly for now to work around training issues
+				 */
+				WrDqDqsEarly = 0;
 
-				if (SeedTotal >= 0) {
-					Seed_Gross = SeedTotal / 32;
-					Seed_Fine = SeedTotal % 32;
-				} else {
-					Seed_Gross = (SeedTotal / 32) - 1;
-					Seed_Fine = (SeedTotal % 32) + 32;
+				/* Generate new seed values */
+				for (ByteLane = 0; ByteLane < MAX_BYTE_LANES; ByteLane++) {
+					/* Calculate adjusted seed values */
+					SeedTotal[ByteLane] = (pDCTData->WLFineDelayPrevPass[MAX_BYTE_LANES*dimm+ByteLane] & 0x1f) |
+						((pDCTData->WLGrossDelayPrevPass[MAX_BYTE_LANES*dimm+ByteLane] & 0x1f) << 5);
+					SeedTotalPreScaling[ByteLane] = (SeedTotal[ByteLane] - RegisterDelay - (0x20 * WrDqDqsEarly));
+					SeedTotal[ByteLane] = (int32_t) (RegisterDelay + ((((int64_t) SeedTotalPreScaling[ByteLane]) *
+						fam15h_freq_tab[MemClkFreq] * 100) / (fam15h_freq_tab[pDCTData->WLPrevMemclkFreq] * 100)));
 				}
 
-				if (Seed_Gross == 0)
-					Seed_PreGross = 0;
-				else if (Seed_Gross & 0x1)
-					Seed_PreGross = 1;
-				else
-					Seed_PreGross = 2;
+				/* Generate register values from seeds */
+				for (ByteLane = 0; ByteLane < MAX_BYTE_LANES; ByteLane++) {
+					printk(BIOS_SPEW, "\tLane %02x scaled delay: %04x\n", ByteLane, SeedTotal[ByteLane]);
 
-				/* Save seed values for later use */
-				pDCTData->WLSeedGrossDelay[MAX_BYTE_LANES*dimm+ByteLane] = Seed_Gross;
-				pDCTData->WLSeedFineDelay[MAX_BYTE_LANES*dimm+ByteLane] = Seed_Fine;
-				pDCTData->WLSeedPreGrossDelay[MAX_BYTE_LANES*dimm+ByteLane] = Seed_PreGross;
+					if (SeedTotal[ByteLane] >= 0) {
+						Seed_Gross = SeedTotal[ByteLane] / 32;
+						Seed_Fine = SeedTotal[ByteLane] % 32;
+					} else {
+						Seed_Gross = (SeedTotal[ByteLane] / 32) - 1;
+						Seed_Fine = (SeedTotal[ByteLane] % 32) + 32;
+					}
 
-				pDCTData->WLGrossDelay[MAX_BYTE_LANES*dimm+ByteLane] = Seed_PreGross;
-				pDCTData->WLFineDelay[MAX_BYTE_LANES*dimm+ByteLane] = Seed_Fine;
+					if (Seed_Gross == 0)
+						Seed_PreGross = 0;
+					else if (Seed_Gross & 0x1)
+						Seed_PreGross = 1;
+					else
+						Seed_PreGross = 2;
 
-				printk(BIOS_SPEW, "\tLane %02x new seed: %04x\n", ByteLane, ((Seed_Gross & 0x1f) << 5) | (Seed_Fine & 0x1f));
-			}
-		} else {
-			uint32_t RegisterDelay;
-			uint32_t SeedTotalPreScaling;
-			uint32_t SeedTotal;
-			uint8_t AddrCmdPrelaunch = 0;		/* TODO: Fetch the correct value from RC2[0] */
-			for (ByteLane = 0; ByteLane < MAX_BYTE_LANES; ByteLane++)
-			{
-				if (pDCTData->Status[DCT_STATUS_REGISTERED]) {
-					if (AddrCmdPrelaunch == 0)
-						RegisterDelay = 0x20;
+					/* The BKDG-recommended algorithm causes problems with registered DIMMs on some systems
+					 * due to the long register delays causing premature total delay wrap-around.
+					 * Attempt to work around this...
+					 */
+					Seed_PreGross = Seed_Gross;
+
+					/* Save seed values for later use */
+					pDCTData->WLSeedGrossDelay[MAX_BYTE_LANES*dimm+ByteLane] = Seed_Gross;
+					pDCTData->WLSeedFineDelay[MAX_BYTE_LANES*dimm+ByteLane] = Seed_Fine;
+					pDCTData->WLSeedPreGrossDelay[MAX_BYTE_LANES*dimm+ByteLane] = Seed_PreGross;
+	
+					pDCTData->WLGrossDelay[MAX_BYTE_LANES*dimm+ByteLane] = Seed_PreGross;
+					pDCTData->WLFineDelay[MAX_BYTE_LANES*dimm+ByteLane] = Seed_Fine;
+	
+					printk(BIOS_SPEW, "\tLane %02x new seed: %04x\n", ByteLane, ((pDCTData->WLGrossDelay[MAX_BYTE_LANES*dimm+ByteLane] & 0x1f) << 5) | (pDCTData->WLFineDelay[MAX_BYTE_LANES*dimm+ByteLane] & 0x1f));
+				}
+			} else {
+				uint32_t RegisterDelay;
+				uint32_t SeedTotalPreScaling;
+				uint32_t SeedTotal;
+				uint8_t AddrCmdPrelaunch = 0;		/* TODO: Fetch the correct value from RC2[0] */
+				for (ByteLane = 0; ByteLane < MAX_BYTE_LANES; ByteLane++)
+				{
+					if (pDCTData->Status[DCT_STATUS_REGISTERED]) {
+						if (AddrCmdPrelaunch == 0)
+							RegisterDelay = 0x20;
+						else
+							RegisterDelay = 0x30;
+					} else {
+						RegisterDelay = 0;
+					}
+					SeedTotalPreScaling = ((pDCTData->WLFineDelay[MAX_BYTE_LANES*dimm+ByteLane] & 0x1f) |
+						(pDCTData->WLGrossDelay[MAX_BYTE_LANES*dimm+ByteLane] << 5)) - RegisterDelay;
+					/* SeedTotalPreScaling = (the total delay value in F2x[1, 0]9C_x[4A:30] from pass 1 of write levelization
+					training) - RegisterDelay. */
+					SeedTotal = (uint16_t) ((((uint64_t) SeedTotalPreScaling) *
+										fam10h_freq_tab[MemClkFreq] * 100) / (fam10h_freq_tab[3] * 100));
+					Seed_Gross = SeedTotal / 32;
+					Seed_Fine = SeedTotal & 0x1f;
+					if (Seed_Gross == 0)
+						Seed_Gross = 0;
+					else if (Seed_Gross & 0x1)
+						Seed_Gross = 1;
 					else
-						RegisterDelay = 0x30;
-				} else {
-					RegisterDelay = 0;
+						Seed_Gross = 2;
+	
+					/* The BKDG-recommended algorithm causes problems with registered DIMMs on some systems
+					* due to the long register delays causing premature total delay wrap-around.
+					* Attempt to work around this...
+					*/
+					SeedTotal = ((Seed_Gross & 0x1f) << 5) | (Seed_Fine & 0x1f);
+					SeedTotal += RegisterDelay;
+					Seed_Gross = SeedTotal / 32;
+					Seed_Fine = SeedTotal & 0x1f;
+	
+					pDCTData->WLGrossDelay[MAX_BYTE_LANES*dimm+ByteLane] = Seed_Gross;
+					pDCTData->WLFineDelay[MAX_BYTE_LANES*dimm+ByteLane] = Seed_Fine;
+	
+					printk(BIOS_SPEW, "\tLane %02x new seed: %04x\n", ByteLane, ((pDCTData->WLGrossDelay[MAX_BYTE_LANES*dimm+ByteLane] & 0x1f) << 5) | (pDCTData->WLFineDelay[MAX_BYTE_LANES*dimm+ByteLane] & 0x1f));
 				}
-				SeedTotalPreScaling = ((pDCTData->WLFineDelay[MAX_BYTE_LANES*dimm+ByteLane] & 0x1f) |
-					(pDCTData->WLGrossDelay[MAX_BYTE_LANES*dimm+ByteLane] << 5)) - RegisterDelay;
-				/* SeedTotalPreScaling = (the total delay value in F2x[1, 0]9C_x[4A:30] from pass 1 of write levelization
-				training) - RegisterDelay. */
-				SeedTotal = (uint16_t) ((((uint64_t) SeedTotalPreScaling) *
-									fam10h_freq_tab[MemClkFreq] * 100) / (fam10h_freq_tab[3] * 100));
-				Seed_Gross = SeedTotal / 32;
-				Seed_Fine = SeedTotal & 0x1f;
-				if (Seed_Gross == 0)
-					Seed_Gross = 0;
-				else if (Seed_Gross & 0x1)
-					Seed_Gross = 1;
-				else
-					Seed_Gross = 2;
-
-				/* The BKDG-recommended algorithm causes problems with registered DIMMs on some systems
-				 * due to the long register delays causing premature total delay wrap-around.
-				 * Attempt to work around this...
-				 */
-				SeedTotal = ((Seed_Gross & 0x1f) << 5) | (Seed_Fine & 0x1f);
-				SeedTotal += RegisterDelay;
-				Seed_Gross = SeedTotal / 32;
-				Seed_Fine = SeedTotal & 0x1f;
+			}
 
-				pDCTData->WLGrossDelay[MAX_BYTE_LANES*dimm+ByteLane] = Seed_Gross;
-				pDCTData->WLFineDelay[MAX_BYTE_LANES*dimm+ByteLane] = Seed_Fine;
+			/* Save initial seeds for upper nibble pass */
+			for (ByteLane = 0; ByteLane < MAX_BYTE_LANES; ByteLane++) {
+				pDCTData->WLSeedPreGrossPrevNibble[MAX_BYTE_LANES*dimm+ByteLane] = pDCTData->WLSeedPreGrossDelay[MAX_BYTE_LANES*dimm+ByteLane];
+				pDCTData->WLSeedGrossPrevNibble[MAX_BYTE_LANES*dimm+ByteLane] = pDCTData->WLGrossDelay[MAX_BYTE_LANES*dimm+ByteLane];
+				pDCTData->WLSeedFinePrevNibble[MAX_BYTE_LANES*dimm+ByteLane] = pDCTData->WLFineDelay[MAX_BYTE_LANES*dimm+ByteLane];
+			}
+		} else {
+			/* Restore seed values from lower nibble pass */
+			if (is_fam15h()) {
+				for (ByteLane = 0; ByteLane < MAX_BYTE_LANES; ByteLane++) {
+					pDCTData->WLSeedGrossDelay[MAX_BYTE_LANES*dimm+ByteLane] = pDCTData->WLSeedGrossPrevNibble[MAX_BYTE_LANES*dimm+ByteLane];
+					pDCTData->WLSeedFineDelay[MAX_BYTE_LANES*dimm+ByteLane] = pDCTData->WLSeedFinePrevNibble[MAX_BYTE_LANES*dimm+ByteLane];
+					pDCTData->WLSeedPreGrossDelay[MAX_BYTE_LANES*dimm+ByteLane] = pDCTData->WLSeedPreGrossPrevNibble[MAX_BYTE_LANES*dimm+ByteLane];
+	
+					pDCTData->WLGrossDelay[MAX_BYTE_LANES*dimm+ByteLane] = pDCTData->WLSeedPreGrossPrevNibble[MAX_BYTE_LANES*dimm+ByteLane];
+					pDCTData->WLFineDelay[MAX_BYTE_LANES*dimm+ByteLane] = pDCTData->WLSeedFinePrevNibble[MAX_BYTE_LANES*dimm+ByteLane];
+
+					printk(BIOS_SPEW, "\tLane %02x new seed: %04x\n", ByteLane, ((pDCTData->WLGrossDelay[MAX_BYTE_LANES*dimm+ByteLane] & 0x1f) << 5) | (pDCTData->WLFineDelay[MAX_BYTE_LANES*dimm+ByteLane] & 0x1f));
+				}
+			} else {
+				for (ByteLane = 0; ByteLane < MAX_BYTE_LANES; ByteLane++) {
+					pDCTData->WLGrossDelay[MAX_BYTE_LANES*dimm+ByteLane] = pDCTData->WLSeedGrossPrevNibble[MAX_BYTE_LANES*dimm+ByteLane];
+					pDCTData->WLFineDelay[MAX_BYTE_LANES*dimm+ByteLane] = pDCTData->WLSeedFinePrevNibble[MAX_BYTE_LANES*dimm+ByteLane];
 
-				printk(BIOS_SPEW, "\tLane %02x new seed: %04x\n", ByteLane, ((Seed_Gross & 0x1f) << 5) | (Seed_Fine & 0x1f));
+					printk(BIOS_SPEW, "\tLane %02x new seed: %04x\n", ByteLane, ((pDCTData->WLGrossDelay[MAX_BYTE_LANES*dimm+ByteLane] & 0x1f) << 5) | (pDCTData->WLFineDelay[MAX_BYTE_LANES*dimm+ByteLane] & 0x1f));
+				}
 			}
 		}
 	}
@@ -1358,7 +1437,7 @@ void setWLByteDelay(struct DCTStatStruc *pDCTstat, uint8_t dct, u8 ByteLane, u8
 }
 
 /*-----------------------------------------------------------------------------
- *  void getWLByteDelay(struct DCTStatStruc *pDCTstat, uint8_t dct, u8 ByteLane, u8 Dimm)
+ *  void getWLByteDelay(struct DCTStatStruc *pDCTstat, uint8_t dct, u8 ByteLane, u8 Dimm, u8 Nibble)
  *
  *  Description:
  *       This function reads the write levelization byte delay from the Phase
@@ -1376,7 +1455,7 @@ void setWLByteDelay(struct DCTStatStruc *pDCTstat, uint8_t dct, u8 ByteLane, u8
  *
  *-----------------------------------------------------------------------------
  */
-void getWLByteDelay(struct DCTStatStruc *pDCTstat, uint8_t dct, u8 ByteLane, u8 dimm, uint8_t pass)
+void getWLByteDelay(struct DCTStatStruc *pDCTstat, uint8_t dct, u8 ByteLane, u8 dimm, uint8_t pass, uint8_t nibble)
 {
 	sDCTStruct *pDCTData = pDCTstat->C_DCTPtr[dct];
 	u8 fineStartLoc, fineEndLoc, grossStartLoc, grossEndLoc, tempB, tempB1, index;
@@ -1427,7 +1506,16 @@ void getWLByteDelay(struct DCTStatStruc *pDCTstat, uint8_t dct, u8 ByteLane, u8
 			fine = 0;
 		}
 	}
-	pDCTData->WLFineDelay[index+ByteLane] = (u8)fine;
-	pDCTData->WLGrossDelay[index+ByteLane] = (u8)gross;
-	printk(BIOS_SPEW, "\tLane %02x final adjusted value: %04x\n", ByteLane, ((gross & 0x1f) << 5) | (fine & 0x1f));
+	if (nibble == 0) {
+		pDCTData->WLFineDelay[index+ByteLane] = (uint8_t)fine;
+		pDCTData->WLGrossDelay[index+ByteLane] = (uint8_t)gross;
+	} else {
+		uint32_t WLTotalDelay = ((pDCTData->WLGrossDelay[index+ByteLane] & 0x1f) << 5) | (pDCTData->WLFineDelay[index+ByteLane] & 0x1f);
+		WLTotalDelay += ((gross & 0x1f) << 5) | (fine & 0x1f);
+		WLTotalDelay /= 2;
+		pDCTData->WLFineDelay[index+ByteLane] = (uint8_t)(WLTotalDelay & 0x1f);
+		pDCTData->WLGrossDelay[index+ByteLane] = (uint8_t)((WLTotalDelay >> 5) & 0x1f);
+	}
+
+	printk(BIOS_SPEW, "\tLane %02x adjusted value: %04x\n", ByteLane, ((pDCTData->WLGrossDelay[index+ByteLane] & 0x1f) << 5) | (pDCTData->WLFineDelay[index+ByteLane] & 0x1f));
 }
diff --git a/src/northbridge/amd/amdmct/mct_ddr3/mwlc_d.h b/src/northbridge/amd/amdmct/mct_ddr3/mwlc_d.h
index 12e7c4a..3337c14 100644
--- a/src/northbridge/amd/amdmct/mct_ddr3/mwlc_d.h
+++ b/src/northbridge/amd/amdmct/mct_ddr3/mwlc_d.h
@@ -119,16 +119,21 @@ typedef struct _sDCTStruct
 	u8 DctTrain;			/* Current DCT being trained */
 	u8 CurrDct;			/* Current DCT number (0 or 1) */
 	u8 DctCSPresent;		/* Current DCT CS mapping */
+	uint8_t WrDqsGrossDlyBaseOffset;
 	int32_t WLSeedGrossDelay[MAX_BYTE_LANES*MAX_LDIMMS];	/* Write Levelization Seed Gross Delay */
 								/* per byte Lane Per Logical DIMM*/
 	int32_t WLSeedFineDelay[MAX_BYTE_LANES*MAX_LDIMMS];	/* Write Levelization Seed Fine Delay */
 								/* per byte Lane Per Logical DIMM*/
 	int32_t WLSeedPreGrossDelay[MAX_BYTE_LANES*MAX_LDIMMS];	/* Write Levelization Seed Pre-Gross Delay */
 								/* per byte Lane Per Logical DIMM*/
-	u8 WLGrossDelay[MAX_BYTE_LANES*MAX_LDIMMS];	/* Write Levelization Gross Delay */
-							/* per byte Lane Per Logical DIMM*/
-	u8 WLFineDelay[MAX_BYTE_LANES*MAX_LDIMMS];	/* Write Levelization Fine Delay */
-							/* per byte Lane Per Logical DIMM*/
+	uint8_t WLSeedPreGrossPrevNibble[MAX_BYTE_LANES*MAX_LDIMMS];
+	uint8_t WLSeedGrossPrevNibble[MAX_BYTE_LANES*MAX_LDIMMS];
+	uint8_t WLSeedFinePrevNibble[MAX_BYTE_LANES*MAX_LDIMMS];
+								/* per byte Lane Per Logical DIMM*/
+	u8 WLGrossDelay[MAX_BYTE_LANES*MAX_LDIMMS];		/* Write Levelization Gross Delay */
+								/* per byte Lane Per Logical DIMM*/
+	u8 WLFineDelay[MAX_BYTE_LANES*MAX_LDIMMS];		/* Write Levelization Fine Delay */
+								/* per byte Lane Per Logical DIMM*/
 	u8 WLGrossDelayFirstPass[MAX_BYTE_LANES*MAX_LDIMMS];	/* First-Pass Write Levelization Gross Delay */
 								/* per byte Lane Per Logical DIMM*/
 	u8 WLFineDelayFirstPass[MAX_BYTE_LANES*MAX_LDIMMS];	/* First-Pass Write Levelization Fine Delay */



More information about the coreboot-gerrit mailing list