aboutsummaryrefslogtreecommitdiff
path: root/resources/libreboot/patch/kgpe-d16/0088-northbridge-amd-amdmct-mct_ddr3-Add-registered-and-x.patch
diff options
context:
space:
mode:
authorFrancis Rowe <info@gluglug.org.uk>2015-10-17 16:10:53 +0100
committerFrancis Rowe <info@gluglug.org.uk>2015-10-17 19:07:35 +0100
commit5999dba5f71f1c05040a551d2420ab8c7f3a9da4 (patch)
tree7313b1996a247bf938417d5cf2496f5f6625c0db /resources/libreboot/patch/kgpe-d16/0088-northbridge-amd-amdmct-mct_ddr3-Add-registered-and-x.patch
parent4d909153e79661e54999e51693668f6d1ecc1cca (diff)
downloadlibrebootfr-5999dba5f71f1c05040a551d2420ab8c7f3a9da4.tar.gz
librebootfr-5999dba5f71f1c05040a551d2420ab8c7f3a9da4.zip
New board: ASUS KGPE-D16
coreboot build errors: In file included from src/northbridge/amd/amdfam10/misc_control.c:35:0: src/include/option.h:13:27: error: static declaration of 'get_option' follows non-static declaration static inline enum cb_err get_option(void *dest, const char *name) ^ In file included from src/northbridge/amd/amdfam10/misc_control.c:34:0: src/include/pc80/mc146818rtc.h:176:13: note: previous declaration of 'get_option' was here enum cb_err get_option(void *dest, const char *name); Ping tpearson about this. Also ping him about the fact that there isn't actually an option to enable or disable native graphics initialization, but that the option MAINBOARD_HAS_NATIVE_VGA_INIT_TEXTMODECFG is in fact available and set to Y in the Kconfig file. I think this is probably since there isn't even an option ROM available for the machine, so it's pointless to offer the setting.
Diffstat (limited to 'resources/libreboot/patch/kgpe-d16/0088-northbridge-amd-amdmct-mct_ddr3-Add-registered-and-x.patch')
-rw-r--r--resources/libreboot/patch/kgpe-d16/0088-northbridge-amd-amdmct-mct_ddr3-Add-registered-and-x.patch1893
1 files changed, 1893 insertions, 0 deletions
diff --git a/resources/libreboot/patch/kgpe-d16/0088-northbridge-amd-amdmct-mct_ddr3-Add-registered-and-x.patch b/resources/libreboot/patch/kgpe-d16/0088-northbridge-amd-amdmct-mct_ddr3-Add-registered-and-x.patch
new file mode 100644
index 00000000..0aba7eb5
--- /dev/null
+++ b/resources/libreboot/patch/kgpe-d16/0088-northbridge-amd-amdmct-mct_ddr3-Add-registered-and-x.patch
@@ -0,0 +1,1893 @@
+From 46e87f3f6b6085488fa6eb8b5ad69ba2efdc3e4f Mon Sep 17 00:00:00 2001
+From: Timothy Pearson <kb9vqf@pearsoncomputing.net>
+Date: Tue, 28 Jul 2015 15:16:46 -0500
+Subject: [PATCH 088/146] northbridge/amd/amdmct/mct_ddr3: Add registered and
+ x4 DIMM support to Fam15h
+
+---
+ 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 28796bb..55cdd24 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);
+ }
+@@ -2315,7 +2319,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;
+@@ -3350,7 +3354,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.
+@@ -3454,7 +3458,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
+@@ -3733,10 +3737,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))
+@@ -3754,10 +3763,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 */
+@@ -3769,14 +3779,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) */
+@@ -4577,8 +4589,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;
+ }
+
+@@ -4639,6 +4652,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 */
+@@ -4653,6 +4668,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;
+ }
+
+@@ -4663,6 +4681,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;
+@@ -4683,6 +4703,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;
+ }
+
+@@ -4694,7 +4716,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)));
+@@ -5642,57 +5665,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__);
+ }
+@@ -5708,18 +5730,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 */
+@@ -5865,12 +5893,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;
+@@ -5892,6 +5922,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,
+@@ -6535,6 +6567,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
+@@ -6567,6 +6601,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);
+
+@@ -6582,6 +6618,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 */
+--
+1.7.9.5
+