aboutsummaryrefslogtreecommitdiff
path: root/src/northbridge/amd/amdmct/mct_ddr3/mctecc_d.c
diff options
context:
space:
mode:
authorZheng Bao <zheng.bao@amd.com>2010-04-23 17:32:48 +0000
committerStefan Reinauer <stepan@openbios.org>2010-04-23 17:32:48 +0000
commiteb75f652d392d2f4f257194e112f3f0db7479145 (patch)
treeaa972907734abcba4ca52f2a3a71f8d81d4bdce0 /src/northbridge/amd/amdmct/mct_ddr3/mctecc_d.c
parentfe6c2cda6e6977894d9b668af9509b983c850f68 (diff)
DDR3 support for AMD Fam10.
Signed-off-by: Zheng Bao <zheng.bao@amd.com> Acked-by: Stefan Reinauer <stepan@coresystems.de> git-svn-id: svn://svn.coreboot.org/coreboot/trunk@5481 2b7e53f0-3cfb-0310-b3e9-8179ed1497e1
Diffstat (limited to 'src/northbridge/amd/amdmct/mct_ddr3/mctecc_d.c')
-rw-r--r--src/northbridge/amd/amdmct/mct_ddr3/mctecc_d.c268
1 files changed, 268 insertions, 0 deletions
diff --git a/src/northbridge/amd/amdmct/mct_ddr3/mctecc_d.c b/src/northbridge/amd/amdmct/mct_ddr3/mctecc_d.c
new file mode 100644
index 0000000000..288093c73b
--- /dev/null
+++ b/src/northbridge/amd/amdmct/mct_ddr3/mctecc_d.c
@@ -0,0 +1,268 @@
+/*
+ * This file is part of the coreboot project.
+ *
+ * Copyright (C) 2010 Advanced Micro Devices, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "mct_d.h"
+
+static void setSyncOnUnEccEn_D(struct MCTStatStruc *pMCTstat,
+ struct DCTStatStruc *pDCTstatA);
+static u8 isDramECCEn_D(struct DCTStatStruc *pDCTstat);
+
+/* Initialize ECC modes of Integrated Dram+Memory Controllers of a network of
+ * Hammer processors. Use Dram background scrubber to fast initialize ECC bits
+ * of all dram.
+ *
+ * Notes:
+ *
+ * Order that items are set:
+ * 1. eccen bit in NB
+ * 2. Scrub Base
+ * 3. Temp Node Base
+ * 4. Temp Node Limit
+ * 5. Redir bit in NB
+ * 6. Scrub CTL
+ *
+ * Conditions for setting background scrubber.
+ * 1. node is present
+ * 2. node has dram functioning (WE=RE=1)
+ * 3. all eccdimms (or bit 17 of offset 90,fn 2)
+ * 4. no chip-select gap exists
+ *
+ * The dram background scrubber is used under very controlled circumstances to
+ * initialize all the ECC bits on the DIMMs of the entire dram address map
+ * (including hidden or lost dram and dram above 4GB). We will turn the scrub
+ * rate up to maximum, which should clear 4GB of dram in about 2.7 seconds.
+ * We will activate the scrubbers of all nodes with ecc dram and let them run in
+ * parallel, thereby reducing even further the time required to condition dram.
+ * Finally, we will go through each node and either disable background scrubber,
+ * or set the scrub rate to the user setup specified rate.
+ *
+ * To allow the NB to scrub, we need to wait a time period long enough to
+ * guarantee that the NB scrubs the entire dram on its node. Do do this, we
+ * simply sample the scrub ADDR once, for an initial value, then we sample and poll until the polled value of scrub ADDR
+ * has wrapped around at least once: Scrub ADDRi+1 < Scrub ADDRi. Since we let all
+ * Nodes run in parallel, we need to gaurantee that all nodes have wrapped. To do
+ * this efficiently, we need only to sample one of the nodes, the node with the
+ * largest ammount of dram populated is the one which will take the longest amount
+ * of time (the scrub rate is set to max, the same rate, on all nodes). So,
+ * during setup of scrub Base, we determine how much memory and which node has
+ * the largest memory installed.
+ *
+ * Scrubbing should not ordinarily be enabled on a Node with a chip-select gap
+ * (aka SW memhole, cs hoisting, etc..).To init ECC memory on this node, the
+ * scrubber is used in two steps. First, the Dram Limit for the node is adjusted
+ * down to the bottom of the gap, and that ECC dram is initialized. Second, the
+ * orignal Limit is restored, the Scrub base is set to 4GB, and scrubber is
+ * allowed to run until the Scrub Addr wraps around to zero.
+ */
+u8 ECCInit_D(struct MCTStatStruc *pMCTstat, struct DCTStatStruc *pDCTstatA)
+{
+ u8 Node;
+ u8 AllECC;
+ u16 OB_NBECC;
+ u32 curBase;
+ u16 OB_ECCRedir;
+ u32 LDramECC;
+ u32 OF_ScrubCTL;
+ u16 OB_ChipKill;
+ u8 MemClrECC;
+
+ u32 dev;
+ u32 reg;
+ u32 val;
+ u16 nvbits;
+
+ mctHookBeforeECC();
+
+ /* Construct these booleans, based on setup options, for easy handling
+ later in this procedure */
+ OB_NBECC = mctGet_NVbits(NV_NBECC); /* MCA ECC (MCE) enable bit */
+
+ OB_ECCRedir = mctGet_NVbits(NV_ECCRedir); /* ECC Redirection */
+
+ OB_ChipKill = mctGet_NVbits(NV_ChipKill); /* ECC Chip-kill mode */
+
+ OF_ScrubCTL = 0; /* Scrub CTL for Dcache, L2, and dram */
+ nvbits = mctGet_NVbits(NV_DCBKScrub);
+ /* mct_AdjustScrub_D(pDCTstatA, &nvbits); */ /* Need not adjust */
+ OF_ScrubCTL |= (u32) nvbits << 16;
+
+ nvbits = mctGet_NVbits(NV_L2BKScrub);
+ OF_ScrubCTL |= (u32) nvbits << 8;
+
+ nvbits = mctGet_NVbits(NV_DramBKScrub);
+ OF_ScrubCTL |= nvbits;
+
+ AllECC = 1;
+ MemClrECC = 0;
+ for (Node = 0; Node < MAX_NODES_SUPPORTED; Node++) {
+ struct DCTStatStruc *pDCTstat;
+ pDCTstat = pDCTstatA + Node;
+ LDramECC = 0;
+ if (NodePresent_D(Node)) { /*If Node is present */
+ dev = pDCTstat->dev_map;
+ reg = 0x40+(Node << 3); /* Dram Base Node 0 + index */
+ val = Get_NB32(dev, reg);
+
+ /* WE/RE is checked */
+ if((val & 3)==3) { /* Node has dram populated */
+ /* Negate 'all nodes/dimms ECC' flag if non ecc
+ memory populated */
+ if( pDCTstat->Status & (1<<SB_ECCDIMMs)) {
+ LDramECC = isDramECCEn_D(pDCTstat);
+ if(pDCTstat->ErrCode != SC_RunningOK) {
+ pDCTstat->Status &= ~(1 << SB_ECCDIMMs);
+ if (OB_NBECC) {
+ pDCTstat->ErrStatus |= (1 << SB_DramECCDis);
+ }
+ AllECC = 0;
+ LDramECC =0;
+ }
+ } else {
+ AllECC = 0;
+ }
+ if(LDramECC) { /* if ECC is enabled on this dram */
+ if (OB_NBECC) {
+ mct_EnableDatIntlv_D(pMCTstat, pDCTstat);
+ dev = pDCTstat->dev_nbmisc;
+ reg =0x44; /* MCA NB Configuration */
+ val = Get_NB32(dev, reg);
+ val |= 1 << 22; /* EccEn */
+ Set_NB32(dev, reg, val);
+ DCTMemClr_Init_D(pMCTstat, pDCTstat);
+ MemClrECC = 1;
+ }
+ } /* this node has ECC enabled dram */
+ } else {
+ LDramECC = 0;
+ } /* Node has Dram */
+
+ if (MemClrECC) {
+ MCTMemClrSync_D(pMCTstat, pDCTstatA);
+ }
+ } /* if Node present */
+ }
+
+ if(AllECC)
+ pMCTstat->GStatus |= 1<<GSB_ECCDIMMs;
+ else
+ pMCTstat->GStatus &= ~(1<<GSB_ECCDIMMs);
+
+ /* Program the Dram BKScrub CTL to the proper (user selected) value.*/
+ /* Reset MC4_STS. */
+ for (Node = 0; Node < MAX_NODES_SUPPORTED; Node++) {
+ struct DCTStatStruc *pDCTstat;
+ pDCTstat = pDCTstatA + Node;
+ LDramECC = 0;
+ if (NodePresent_D(Node)) { /* If Node is present */
+ reg = 0x40+(Node<<3); /* Dram Base Node 0 + index */
+ val = Get_NB32(pDCTstat->dev_map, reg);
+ curBase = val & 0xffff0000;
+ /*WE/RE is checked because memory config may have been */
+ if((val & 3)==3) { /* Node has dram populated */
+ if (isDramECCEn_D(pDCTstat)) { /* if ECC is enabled on this dram */
+ dev = pDCTstat->dev_nbmisc;
+ val = curBase << 8;
+ if(OB_ECCRedir) {
+ val |= (1<<0); /* enable redirection */
+ }
+ Set_NB32(dev, 0x5C, val); /* Dram Scrub Addr Low */
+ val = curBase>>24;
+ Set_NB32(dev, 0x60, val); /* Dram Scrub Addr High */
+ Set_NB32(dev, 0x58, OF_ScrubCTL); /*Scrub Control */
+
+ /* Divisor should not be set deeper than
+ * divide by 16 when Dcache scrubber or
+ * L2 scrubber is enabled.
+ */
+ if ((OF_ScrubCTL & (0x1F << 16)) || (OF_ScrubCTL & (0x1F << 8))) {
+ val = Get_NB32(dev, 0x84);
+ if ((val & 0xE0000000) > 0x80000000) { /* Get F3x84h[31:29]ClkDivisor for C1 */
+ val &= 0x1FFFFFFF; /* If ClkDivisor is deeper than divide-by-16 */
+ val |= 0x80000000; /* set it to divide-by-16 */
+ Set_NB32(dev, 0x84, val);
+ }
+ }
+ } /* this node has ECC enabled dram */
+ } /*Node has Dram */
+ } /*if Node present */
+ }
+
+ if(mctGet_NVbits(NV_SyncOnUnEccEn))
+ setSyncOnUnEccEn_D(pMCTstat, pDCTstatA);
+
+ mctHookAfterECC();
+ return MemClrECC;
+}
+
+static void setSyncOnUnEccEn_D(struct MCTStatStruc *pMCTstat,
+ struct DCTStatStruc *pDCTstatA)
+{
+ u32 Node;
+ u32 reg;
+ u32 dev;
+ u32 val;
+
+ for (Node = 0; Node < MAX_NODES_SUPPORTED; Node++) {
+ struct DCTStatStruc *pDCTstat;
+ pDCTstat = pDCTstatA + Node;
+ if (NodePresent_D(Node)) { /* If Node is present*/
+ reg = 0x40+(Node<<3); /* Dram Base Node 0 + index*/
+ val = Get_NB32(pDCTstat->dev_map, reg);
+ /*WE/RE is checked because memory config may have been*/
+ if((val & 3)==3) { /* Node has dram populated*/
+ if( isDramECCEn_D(pDCTstat)) {
+ /*if ECC is enabled on this dram*/
+ dev = pDCTstat->dev_nbmisc;
+ reg = 0x44; /* MCA NB Configuration*/
+ val = Get_NB32(dev, reg);
+ val |= (1<<SyncOnUcEccEn);
+ Set_NB32(dev, reg, val);
+ }
+ } /* Node has Dram*/
+ } /* if Node present*/
+ }
+}
+
+static u8 isDramECCEn_D(struct DCTStatStruc *pDCTstat)
+{
+ u32 reg;
+ u32 val;
+ u8 i;
+ u32 dev = pDCTstat->dev_dct;
+ u8 ch_end;
+ u8 isDimmECCEn = 0;
+
+ if(pDCTstat->GangedMode) {
+ ch_end = 1;
+ } else {
+ ch_end = 2;
+ }
+ for(i=0; i<ch_end; i++) {
+ if(pDCTstat->DIMMValidDCT[i] > 0){
+ reg = 0x90 + i * 0x100; /* Dram Config Low */
+ val = Get_NB32(dev, reg);
+ if(val & (1<<DimmEcEn)) {
+ /* set local flag 'dram ecc capable' */
+ isDimmECCEn = 1;
+ break;
+ }
+ }
+ }
+ return isDimmECCEn;
+}