]> git.proxmox.com Git - mirror_edk2.git/blobdiff - QuarkSocPkg/QuarkNorthCluster/MemoryInit/Pei/meminit_utils.c
QuarkSocPkg: Add new package for Quark SoC X1000
[mirror_edk2.git] / QuarkSocPkg / QuarkNorthCluster / MemoryInit / Pei / meminit_utils.c
diff --git a/QuarkSocPkg/QuarkNorthCluster/MemoryInit/Pei/meminit_utils.c b/QuarkSocPkg/QuarkNorthCluster/MemoryInit/Pei/meminit_utils.c
new file mode 100644 (file)
index 0000000..f0c8757
--- /dev/null
@@ -0,0 +1,1580 @@
+/************************************************************************\r
+ *\r
+ * Copyright (c) 2013-2015 Intel Corporation.\r
+ *\r
+* This program and the accompanying materials\r
+* are licensed and made available under the terms and conditions of the BSD License\r
+* which accompanies this distribution.  The full text of the license may be found at\r
+* http://opensource.org/licenses/bsd-license.php\r
+*\r
+* THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,\r
+* WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.\r
+ *\r
+ ***************************************************************************/\r
+\r
+#include "mrc.h"\r
+#include "memory_options.h"\r
+\r
+#include "meminit_utils.h"\r
+#include "hte.h"\r
+#include "io.h"\r
+\r
+void select_hte(\r
+    MRCParams_t *mrc_params);\r
+\r
+static uint8_t first_run = 0;\r
+\r
+const uint8_t vref_codes[64] =\r
+{ // lowest to highest\r
+    0x3F, 0x3E, 0x3D, 0x3C, 0x3B, 0x3A, 0x39, 0x38, 0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31, 0x30, // 00 - 15\r
+    0x2F, 0x2E, 0x2D, 0x2C, 0x2B, 0x2A, 0x29, 0x28, 0x27, 0x26, 0x25, 0x24, 0x23, 0x22, 0x21, 0x20, // 16 - 31\r
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, // 32 - 47\r
+    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F  // 48 - 63\r
+};\r
+\r
+#ifdef EMU\r
+// Track current post code for debugging purpose\r
+uint32_t PostCode;\r
+#endif\r
+\r
+// set_rcvn:\r
+//\r
+// This function will program the RCVEN delays.\r
+// (currently doesn't comprehend rank)\r
+void set_rcvn(\r
+    uint8_t channel,\r
+    uint8_t rank,\r
+    uint8_t byte_lane,\r
+    uint32_t pi_count)\r
+{\r
+  uint32_t reg;\r
+  uint32_t msk;\r
+  uint32_t tempD;\r
+\r
+  ENTERFN();\r
+  DPF(D_TRN, "Rcvn ch%d rnk%d ln%d : pi=%03X\n", channel, rank, byte_lane, pi_count);\r
+\r
+  // RDPTR (1/2 MCLK, 64 PIs)\r
+  // BL0 -> B01PTRCTL0[11:08] (0x0-0xF)\r
+  // BL1 -> B01PTRCTL0[23:20] (0x0-0xF)\r
+  reg = B01PTRCTL0 + ((byte_lane >> 1) * DDRIODQ_BL_OFFSET) + (channel * DDRIODQ_CH_OFFSET);\r
+  msk = (byte_lane & BIT0) ? (BIT23 | BIT22 | BIT21 | BIT20) : (BIT11 | BIT10 | BIT9 | BIT8);\r
+  tempD = (byte_lane & BIT0) ? ((pi_count / HALF_CLK) << 20) : ((pi_count / HALF_CLK) << 8);\r
+  isbM32m(DDRPHY, reg, tempD, msk);\r
+\r
+  // Adjust PI_COUNT\r
+  pi_count -= ((pi_count / HALF_CLK) & 0xF) * HALF_CLK;\r
+\r
+  // PI (1/64 MCLK, 1 PIs)\r
+  // BL0 -> B0DLLPICODER0[29:24] (0x00-0x3F)\r
+  // BL1 -> B1DLLPICODER0[29:24] (0x00-0x3F)\r
+  reg = (byte_lane & BIT0) ? (B1DLLPICODER0) : (B0DLLPICODER0);\r
+  reg += (((byte_lane >> 1) * DDRIODQ_BL_OFFSET) + (channel * DDRIODQ_CH_OFFSET));\r
+  msk = (BIT29 | BIT28 | BIT27 | BIT26 | BIT25 | BIT24);\r
+  tempD = pi_count << 24;\r
+  isbM32m(DDRPHY, reg, tempD, msk);\r
+\r
+  // DEADBAND\r
+  // BL0/1 -> B01DBCTL1[08/11] (+1 select)\r
+  // BL0/1 -> B01DBCTL1[02/05] (enable)\r
+  reg = B01DBCTL1 + ((byte_lane >> 1) * DDRIODQ_BL_OFFSET) + (channel * DDRIODQ_CH_OFFSET);\r
+  msk = 0x00;\r
+  tempD = 0x00;\r
+  // enable\r
+  msk |= (byte_lane & BIT0) ? (BIT5) : (BIT2);\r
+  if ((pi_count < EARLY_DB) || (pi_count > LATE_DB))\r
+  {\r
+    tempD |= msk;\r
+  }\r
+  // select\r
+  msk |= (byte_lane & BIT0) ? (BIT11) : (BIT8);\r
+  if (pi_count < EARLY_DB)\r
+  {\r
+    tempD |= msk;\r
+  }\r
+  isbM32m(DDRPHY, reg, tempD, msk);\r
+\r
+  // error check\r
+  if (pi_count > 0x3F)\r
+  {\r
+    training_message(channel, rank, byte_lane);\r
+    post_code(0xEE, 0xE0);\r
+  }\r
+\r
+  LEAVEFN();\r
+  return;\r
+}\r
+\r
+// get_rcvn:\r
+//\r
+// This function will return the current RCVEN delay on the given channel, rank, byte_lane as an absolute PI count.\r
+// (currently doesn't comprehend rank)\r
+uint32_t get_rcvn(\r
+    uint8_t channel,\r
+    uint8_t rank,\r
+    uint8_t byte_lane)\r
+{\r
+  uint32_t reg;\r
+  uint32_t tempD;\r
+  uint32_t pi_count;\r
+\r
+  ENTERFN();\r
+\r
+  // RDPTR (1/2 MCLK, 64 PIs)\r
+  // BL0 -> B01PTRCTL0[11:08] (0x0-0xF)\r
+  // BL1 -> B01PTRCTL0[23:20] (0x0-0xF)\r
+  reg = B01PTRCTL0 + ((byte_lane >> 1) * DDRIODQ_BL_OFFSET) + (channel * DDRIODQ_CH_OFFSET);\r
+  tempD = isbR32m(DDRPHY, reg);\r
+  tempD >>= (byte_lane & BIT0) ? (20) : (8);\r
+  tempD &= 0xF;\r
+\r
+  // Adjust PI_COUNT\r
+  pi_count = tempD * HALF_CLK;\r
+\r
+  // PI (1/64 MCLK, 1 PIs)\r
+  // BL0 -> B0DLLPICODER0[29:24] (0x00-0x3F)\r
+  // BL1 -> B1DLLPICODER0[29:24] (0x00-0x3F)\r
+  reg = (byte_lane & BIT0) ? (B1DLLPICODER0) : (B0DLLPICODER0);\r
+  reg += (((byte_lane >> 1) * DDRIODQ_BL_OFFSET) + (channel * DDRIODQ_CH_OFFSET));\r
+  tempD = isbR32m(DDRPHY, reg);\r
+  tempD >>= 24;\r
+  tempD &= 0x3F;\r
+\r
+  // Adjust PI_COUNT\r
+  pi_count += tempD;\r
+\r
+  LEAVEFN();\r
+  return pi_count;\r
+}\r
+\r
+// set_rdqs:\r
+//\r
+// This function will program the RDQS delays based on an absolute amount of PIs.\r
+// (currently doesn't comprehend rank)\r
+void set_rdqs(\r
+    uint8_t channel,\r
+    uint8_t rank,\r
+    uint8_t byte_lane,\r
+    uint32_t pi_count)\r
+{\r
+  uint32_t reg;\r
+  uint32_t msk;\r
+  uint32_t tempD;\r
+\r
+  ENTERFN();\r
+  DPF(D_TRN, "Rdqs ch%d rnk%d ln%d : pi=%03X\n", channel, rank, byte_lane, pi_count);\r
+\r
+  // PI (1/128 MCLK)\r
+  // BL0 -> B0RXDQSPICODE[06:00] (0x00-0x47)\r
+  // BL1 -> B1RXDQSPICODE[06:00] (0x00-0x47)\r
+  reg = (byte_lane & BIT0) ? (B1RXDQSPICODE) : (B0RXDQSPICODE);\r
+  reg += (((byte_lane >> 1) * DDRIODQ_BL_OFFSET) + (channel * DDRIODQ_CH_OFFSET));\r
+  msk = (BIT6 | BIT5 | BIT4 | BIT3 | BIT2 | BIT1 | BIT0);\r
+  tempD = pi_count << 0;\r
+  isbM32m(DDRPHY, reg, tempD, msk);\r
+\r
+  // error check (shouldn't go above 0x3F)\r
+  if (pi_count > 0x47)\r
+  {\r
+    training_message(channel, rank, byte_lane);\r
+    post_code(0xEE, 0xE1);\r
+  }\r
+\r
+  LEAVEFN();\r
+  return;\r
+}\r
+\r
+// get_rdqs:\r
+//\r
+// This function will return the current RDQS delay on the given channel, rank, byte_lane as an absolute PI count.\r
+// (currently doesn't comprehend rank)\r
+uint32_t get_rdqs(\r
+    uint8_t channel,\r
+    uint8_t rank,\r
+    uint8_t byte_lane)\r
+{\r
+  uint32_t reg;\r
+  uint32_t tempD;\r
+  uint32_t pi_count;\r
+\r
+  ENTERFN();\r
+\r
+  // PI (1/128 MCLK)\r
+  // BL0 -> B0RXDQSPICODE[06:00] (0x00-0x47)\r
+  // BL1 -> B1RXDQSPICODE[06:00] (0x00-0x47)\r
+  reg = (byte_lane & BIT0) ? (B1RXDQSPICODE) : (B0RXDQSPICODE);\r
+  reg += (((byte_lane >> 1) * DDRIODQ_BL_OFFSET) + (channel * DDRIODQ_CH_OFFSET));\r
+  tempD = isbR32m(DDRPHY, reg);\r
+\r
+  // Adjust PI_COUNT\r
+  pi_count = tempD & 0x7F;\r
+\r
+  LEAVEFN();\r
+  return pi_count;\r
+}\r
+\r
+// set_wdqs:\r
+//\r
+// This function will program the WDQS delays based on an absolute amount of PIs.\r
+// (currently doesn't comprehend rank)\r
+void set_wdqs(\r
+    uint8_t channel,\r
+    uint8_t rank,\r
+    uint8_t byte_lane,\r
+    uint32_t pi_count)\r
+{\r
+  uint32_t reg;\r
+  uint32_t msk;\r
+  uint32_t tempD;\r
+\r
+  ENTERFN();\r
+  DPF(D_TRN, "Wdqs ch%d rnk%d ln%d : pi=%03X\n", channel, rank, byte_lane, pi_count);\r
+\r
+  // RDPTR (1/2 MCLK, 64 PIs)\r
+  // BL0 -> B01PTRCTL0[07:04] (0x0-0xF)\r
+  // BL1 -> B01PTRCTL0[19:16] (0x0-0xF)\r
+  reg = B01PTRCTL0 + ((byte_lane >> 1) * DDRIODQ_BL_OFFSET) + (channel * DDRIODQ_CH_OFFSET);\r
+  msk = (byte_lane & BIT0) ? (BIT19 | BIT18 | BIT17 | BIT16) : (BIT7 | BIT6 | BIT5 | BIT4);\r
+  tempD = pi_count / HALF_CLK;\r
+  tempD <<= (byte_lane & BIT0) ? (16) : (4);\r
+  isbM32m(DDRPHY, reg, tempD, msk);\r
+\r
+  // Adjust PI_COUNT\r
+  pi_count -= ((pi_count / HALF_CLK) & 0xF) * HALF_CLK;\r
+\r
+  // PI (1/64 MCLK, 1 PIs)\r
+  // BL0 -> B0DLLPICODER0[21:16] (0x00-0x3F)\r
+  // BL1 -> B1DLLPICODER0[21:16] (0x00-0x3F)\r
+  reg = (byte_lane & BIT0) ? (B1DLLPICODER0) : (B0DLLPICODER0);\r
+  reg += (((byte_lane >> 1) * DDRIODQ_BL_OFFSET) + (channel * DDRIODQ_CH_OFFSET));\r
+  msk = (BIT21 | BIT20 | BIT19 | BIT18 | BIT17 | BIT16);\r
+  tempD = pi_count << 16;\r
+  isbM32m(DDRPHY, reg, tempD, msk);\r
+\r
+  // DEADBAND\r
+  // BL0/1 -> B01DBCTL1[07/10] (+1 select)\r
+  // BL0/1 -> B01DBCTL1[01/04] (enable)\r
+  reg = B01DBCTL1 + ((byte_lane >> 1) * DDRIODQ_BL_OFFSET) + (channel * DDRIODQ_CH_OFFSET);\r
+  msk = 0x00;\r
+  tempD = 0x00;\r
+  // enable\r
+  msk |= (byte_lane & BIT0) ? (BIT4) : (BIT1);\r
+  if ((pi_count < EARLY_DB) || (pi_count > LATE_DB))\r
+  {\r
+    tempD |= msk;\r
+  }\r
+  // select\r
+  msk |= (byte_lane & BIT0) ? (BIT10) : (BIT7);\r
+  if (pi_count < EARLY_DB)\r
+  {\r
+    tempD |= msk;\r
+  }\r
+  isbM32m(DDRPHY, reg, tempD, msk);\r
+\r
+  // error check\r
+  if (pi_count > 0x3F)\r
+  {\r
+    training_message(channel, rank, byte_lane);\r
+    post_code(0xEE, 0xE2);\r
+  }\r
+\r
+  LEAVEFN();\r
+  return;\r
+}\r
+\r
+// get_wdqs:\r
+//\r
+// This function will return the amount of WDQS delay on the given channel, rank, byte_lane as an absolute PI count.\r
+// (currently doesn't comprehend rank)\r
+uint32_t get_wdqs(\r
+    uint8_t channel,\r
+    uint8_t rank,\r
+    uint8_t byte_lane)\r
+{\r
+  uint32_t reg;\r
+  uint32_t tempD;\r
+  uint32_t pi_count;\r
+\r
+  ENTERFN();\r
+\r
+  // RDPTR (1/2 MCLK, 64 PIs)\r
+  // BL0 -> B01PTRCTL0[07:04] (0x0-0xF)\r
+  // BL1 -> B01PTRCTL0[19:16] (0x0-0xF)\r
+  reg = B01PTRCTL0 + ((byte_lane >> 1) * DDRIODQ_BL_OFFSET) + (channel * DDRIODQ_CH_OFFSET);\r
+  tempD = isbR32m(DDRPHY, reg);\r
+  tempD >>= (byte_lane & BIT0) ? (16) : (4);\r
+  tempD &= 0xF;\r
+\r
+  // Adjust PI_COUNT\r
+  pi_count = (tempD * HALF_CLK);\r
+\r
+  // PI (1/64 MCLK, 1 PIs)\r
+  // BL0 -> B0DLLPICODER0[21:16] (0x00-0x3F)\r
+  // BL1 -> B1DLLPICODER0[21:16] (0x00-0x3F)\r
+  reg = (byte_lane & BIT0) ? (B1DLLPICODER0) : (B0DLLPICODER0);\r
+  reg += (((byte_lane >> 1) * DDRIODQ_BL_OFFSET) + (channel * DDRIODQ_CH_OFFSET));\r
+  tempD = isbR32m(DDRPHY, reg);\r
+  tempD >>= 16;\r
+  tempD &= 0x3F;\r
+\r
+  // Adjust PI_COUNT\r
+  pi_count += tempD;\r
+\r
+  LEAVEFN();\r
+  return pi_count;\r
+}\r
+\r
+// set_wdq:\r
+//\r
+// This function will program the WDQ delays based on an absolute number of PIs.\r
+// (currently doesn't comprehend rank)\r
+void set_wdq(\r
+    uint8_t channel,\r
+    uint8_t rank,\r
+    uint8_t byte_lane,\r
+    uint32_t pi_count)\r
+{\r
+  uint32_t reg;\r
+  uint32_t msk;\r
+  uint32_t tempD;\r
+\r
+  ENTERFN();\r
+  DPF(D_TRN, "Wdq ch%d rnk%d ln%d : pi=%03X\n", channel, rank, byte_lane, pi_count);\r
+\r
+  // RDPTR (1/2 MCLK, 64 PIs)\r
+  // BL0 -> B01PTRCTL0[03:00] (0x0-0xF)\r
+  // BL1 -> B01PTRCTL0[15:12] (0x0-0xF)\r
+  reg = B01PTRCTL0 + ((byte_lane >> 1) * DDRIODQ_BL_OFFSET) + (channel * DDRIODQ_CH_OFFSET);\r
+  msk = (byte_lane & BIT0) ? (BIT15 | BIT14 | BIT13 | BIT12) : (BIT3 | BIT2 | BIT1 | BIT0);\r
+  tempD = pi_count / HALF_CLK;\r
+  tempD <<= (byte_lane & BIT0) ? (12) : (0);\r
+  isbM32m(DDRPHY, reg, tempD, msk);\r
+\r
+  // Adjust PI_COUNT\r
+  pi_count -= ((pi_count / HALF_CLK) & 0xF) * HALF_CLK;\r
+\r
+  // PI (1/64 MCLK, 1 PIs)\r
+  // BL0 -> B0DLLPICODER0[13:08] (0x00-0x3F)\r
+  // BL1 -> B1DLLPICODER0[13:08] (0x00-0x3F)\r
+  reg = (byte_lane & BIT0) ? (B1DLLPICODER0) : (B0DLLPICODER0);\r
+  reg += (((byte_lane >> 1) * DDRIODQ_BL_OFFSET) + (channel * DDRIODQ_CH_OFFSET));\r
+  msk = (BIT13 | BIT12 | BIT11 | BIT10 | BIT9 | BIT8);\r
+  tempD = pi_count << 8;\r
+  isbM32m(DDRPHY, reg, tempD, msk);\r
+\r
+  // DEADBAND\r
+  // BL0/1 -> B01DBCTL1[06/09] (+1 select)\r
+  // BL0/1 -> B01DBCTL1[00/03] (enable)\r
+  reg = B01DBCTL1 + ((byte_lane >> 1) * DDRIODQ_BL_OFFSET) + (channel * DDRIODQ_CH_OFFSET);\r
+  msk = 0x00;\r
+  tempD = 0x00;\r
+  // enable\r
+  msk |= (byte_lane & BIT0) ? (BIT3) : (BIT0);\r
+  if ((pi_count < EARLY_DB) || (pi_count > LATE_DB))\r
+  {\r
+    tempD |= msk;\r
+  }\r
+  // select\r
+  msk |= (byte_lane & BIT0) ? (BIT9) : (BIT6);\r
+  if (pi_count < EARLY_DB)\r
+  {\r
+    tempD |= msk;\r
+  }\r
+  isbM32m(DDRPHY, reg, tempD, msk);\r
+\r
+  // error check\r
+  if (pi_count > 0x3F)\r
+  {\r
+    training_message(channel, rank, byte_lane);\r
+    post_code(0xEE, 0xE3);\r
+  }\r
+\r
+  LEAVEFN();\r
+  return;\r
+}\r
+\r
+// get_wdq:\r
+//\r
+// This function will return the amount of WDQ delay on the given channel, rank, byte_lane as an absolute PI count.\r
+// (currently doesn't comprehend rank)\r
+uint32_t get_wdq(\r
+    uint8_t channel,\r
+    uint8_t rank,\r
+    uint8_t byte_lane)\r
+{\r
+  uint32_t reg;\r
+  uint32_t tempD;\r
+  uint32_t pi_count;\r
+\r
+  ENTERFN();\r
+\r
+  // RDPTR (1/2 MCLK, 64 PIs)\r
+  // BL0 -> B01PTRCTL0[03:00] (0x0-0xF)\r
+  // BL1 -> B01PTRCTL0[15:12] (0x0-0xF)\r
+  reg = B01PTRCTL0 + ((byte_lane >> 1) * DDRIODQ_BL_OFFSET) + (channel * DDRIODQ_CH_OFFSET);\r
+  tempD = isbR32m(DDRPHY, reg);\r
+  tempD >>= (byte_lane & BIT0) ? (12) : (0);\r
+  tempD &= 0xF;\r
+\r
+  // Adjust PI_COUNT\r
+  pi_count = (tempD * HALF_CLK);\r
+\r
+  // PI (1/64 MCLK, 1 PIs)\r
+  // BL0 -> B0DLLPICODER0[13:08] (0x00-0x3F)\r
+  // BL1 -> B1DLLPICODER0[13:08] (0x00-0x3F)\r
+  reg = (byte_lane & BIT0) ? (B1DLLPICODER0) : (B0DLLPICODER0);\r
+  reg += (((byte_lane >> 1) * DDRIODQ_BL_OFFSET) + (channel * DDRIODQ_CH_OFFSET));\r
+  tempD = isbR32m(DDRPHY, reg);\r
+  tempD >>= 8;\r
+  tempD &= 0x3F;\r
+\r
+  // Adjust PI_COUNT\r
+  pi_count += tempD;\r
+\r
+  LEAVEFN();\r
+  return pi_count;\r
+}\r
+\r
+// set_wcmd:\r
+//\r
+// This function will program the WCMD delays based on an absolute number of PIs.\r
+void set_wcmd(\r
+    uint8_t channel,\r
+    uint32_t pi_count)\r
+{\r
+  uint32_t reg;\r
+  uint32_t msk;\r
+  uint32_t tempD;\r
+\r
+  ENTERFN();\r
+  // RDPTR (1/2 MCLK, 64 PIs)\r
+  // CMDPTRREG[11:08] (0x0-0xF)\r
+  reg = CMDPTRREG + (channel * DDRIOCCC_CH_OFFSET);\r
+  msk = (BIT11 | BIT10 | BIT9 | BIT8);\r
+  tempD = pi_count / HALF_CLK;\r
+  tempD <<= 8;\r
+  isbM32m(DDRPHY, reg, tempD, msk);\r
+\r
+  // Adjust PI_COUNT\r
+  pi_count -= ((pi_count / HALF_CLK) & 0xF) * HALF_CLK;\r
+\r
+  // PI (1/64 MCLK, 1 PIs)\r
+  // CMDDLLPICODER0[29:24] -> CMDSLICE R3 (unused)\r
+  // CMDDLLPICODER0[21:16] -> CMDSLICE L3 (unused)\r
+  // CMDDLLPICODER0[13:08] -> CMDSLICE R2 (unused)\r
+  // CMDDLLPICODER0[05:00] -> CMDSLICE L2 (unused)\r
+  // CMDDLLPICODER1[29:24] -> CMDSLICE R1 (unused)\r
+  // CMDDLLPICODER1[21:16] -> CMDSLICE L1 (0x00-0x3F)\r
+  // CMDDLLPICODER1[13:08] -> CMDSLICE R0 (unused)\r
+  // CMDDLLPICODER1[05:00] -> CMDSLICE L0 (unused)\r
+  reg = CMDDLLPICODER1 + (channel * DDRIOCCC_CH_OFFSET);\r
+\r
+  msk = (BIT29 | BIT28 | BIT27 | BIT26 | BIT25 | BIT24) | (BIT21 | BIT20 | BIT19 | BIT18 | BIT17 | BIT16)\r
+      | (BIT13 | BIT12 | BIT11 | BIT10 | BIT9 | BIT8) | (BIT5 | BIT4 | BIT3 | BIT2 | BIT1 | BIT0);\r
+\r
+  tempD = (pi_count << 24) | (pi_count << 16) | (pi_count << 8) | (pi_count << 0);\r
+\r
+  isbM32m(DDRPHY, reg, tempD, msk);\r
+  reg = CMDDLLPICODER0 + (channel * DDRIOCCC_CH_OFFSET); // PO\r
+  isbM32m(DDRPHY, reg, tempD, msk);\r
+\r
+  // DEADBAND\r
+  // CMDCFGREG0[17] (+1 select)\r
+  // CMDCFGREG0[16] (enable)\r
+  reg = CMDCFGREG0 + (channel * DDRIOCCC_CH_OFFSET);\r
+  msk = 0x00;\r
+  tempD = 0x00;\r
+  // enable\r
+  msk |= BIT16;\r
+  if ((pi_count < EARLY_DB) || (pi_count > LATE_DB))\r
+  {\r
+    tempD |= msk;\r
+  }\r
+  // select\r
+  msk |= BIT17;\r
+  if (pi_count < EARLY_DB)\r
+  {\r
+    tempD |= msk;\r
+  }\r
+  isbM32m(DDRPHY, reg, tempD, msk);\r
+\r
+  // error check\r
+  if (pi_count > 0x3F)\r
+  {\r
+    post_code(0xEE, 0xE4);\r
+  }\r
+\r
+  LEAVEFN();\r
+  return;\r
+}\r
+\r
+// get_wcmd:\r
+//\r
+// This function will return the amount of WCMD delay on the given channel as an absolute PI count.\r
+uint32_t get_wcmd(\r
+    uint8_t channel)\r
+{\r
+  uint32_t reg;\r
+  uint32_t tempD;\r
+  uint32_t pi_count;\r
+\r
+  ENTERFN();\r
+  // RDPTR (1/2 MCLK, 64 PIs)\r
+  // CMDPTRREG[11:08] (0x0-0xF)\r
+  reg = CMDPTRREG + (channel * DDRIOCCC_CH_OFFSET);\r
+  tempD = isbR32m(DDRPHY, reg);\r
+  tempD >>= 8;\r
+  tempD &= 0xF;\r
+\r
+  // Adjust PI_COUNT\r
+  pi_count = tempD * HALF_CLK;\r
+\r
+  // PI (1/64 MCLK, 1 PIs)\r
+  // CMDDLLPICODER0[29:24] -> CMDSLICE R3 (unused)\r
+  // CMDDLLPICODER0[21:16] -> CMDSLICE L3 (unused)\r
+  // CMDDLLPICODER0[13:08] -> CMDSLICE R2 (unused)\r
+  // CMDDLLPICODER0[05:00] -> CMDSLICE L2 (unused)\r
+  // CMDDLLPICODER1[29:24] -> CMDSLICE R1 (unused)\r
+  // CMDDLLPICODER1[21:16] -> CMDSLICE L1 (0x00-0x3F)\r
+  // CMDDLLPICODER1[13:08] -> CMDSLICE R0 (unused)\r
+  // CMDDLLPICODER1[05:00] -> CMDSLICE L0 (unused)\r
+  reg = CMDDLLPICODER1 + (channel * DDRIOCCC_CH_OFFSET);\r
+  tempD = isbR32m(DDRPHY, reg);\r
+  tempD >>= 16;\r
+  tempD &= 0x3F;\r
+\r
+  // Adjust PI_COUNT\r
+  pi_count += tempD;\r
+\r
+  LEAVEFN();\r
+  return pi_count;\r
+}\r
+\r
+// set_wclk:\r
+//\r
+// This function will program the WCLK delays based on an absolute number of PIs.\r
+void set_wclk(\r
+    uint8_t channel,\r
+    uint8_t rank,\r
+    uint32_t pi_count)\r
+{\r
+  uint32_t reg;\r
+  uint32_t msk;\r
+  uint32_t tempD;\r
+\r
+  ENTERFN();\r
+  // RDPTR (1/2 MCLK, 64 PIs)\r
+  // CCPTRREG[15:12] -> CLK1 (0x0-0xF)\r
+  // CCPTRREG[11:08] -> CLK0 (0x0-0xF)\r
+  reg = CCPTRREG + (channel * DDRIOCCC_CH_OFFSET);\r
+  msk = (BIT15 | BIT14 | BIT13 | BIT12) | (BIT11 | BIT10 | BIT9 | BIT8);\r
+  tempD = ((pi_count / HALF_CLK) << 12) | ((pi_count / HALF_CLK) << 8);\r
+  isbM32m(DDRPHY, reg, tempD, msk);\r
+\r
+  // Adjust PI_COUNT\r
+  pi_count -= ((pi_count / HALF_CLK) & 0xF) * HALF_CLK;\r
+\r
+  // PI (1/64 MCLK, 1 PIs)\r
+  // ECCB1DLLPICODER0[13:08] -> CLK0 (0x00-0x3F)\r
+  // ECCB1DLLPICODER0[21:16] -> CLK1 (0x00-0x3F)\r
+  reg = (rank) ? (ECCB1DLLPICODER0) : (ECCB1DLLPICODER0);\r
+  reg += (channel * DDRIOCCC_CH_OFFSET);\r
+  msk = (BIT21 | BIT20 | BIT19 | BIT18 | BIT17 | BIT16) | (BIT13 | BIT12 | BIT11 | BIT10 | BIT9 | BIT8);\r
+  tempD = (pi_count << 16) | (pi_count << 8);\r
+  isbM32m(DDRPHY, reg, tempD, msk);\r
+  reg = (rank) ? (ECCB1DLLPICODER1) : (ECCB1DLLPICODER1);\r
+  reg += (channel * DDRIOCCC_CH_OFFSET);\r
+  isbM32m(DDRPHY, reg, tempD, msk);\r
+  reg = (rank) ? (ECCB1DLLPICODER2) : (ECCB1DLLPICODER2);\r
+  reg += (channel * DDRIOCCC_CH_OFFSET);\r
+  isbM32m(DDRPHY, reg, tempD, msk);\r
+  reg = (rank) ? (ECCB1DLLPICODER3) : (ECCB1DLLPICODER3);\r
+  reg += (channel * DDRIOCCC_CH_OFFSET);\r
+  isbM32m(DDRPHY, reg, tempD, msk);\r
+\r
+  // DEADBAND\r
+  // CCCFGREG1[11:08] (+1 select)\r
+  // CCCFGREG1[03:00] (enable)\r
+  reg = CCCFGREG1 + (channel * DDRIOCCC_CH_OFFSET);\r
+  msk = 0x00;\r
+  tempD = 0x00;\r
+  // enable\r
+  msk |= (BIT3 | BIT2 | BIT1 | BIT0); // only ??? matters\r
+  if ((pi_count < EARLY_DB) || (pi_count > LATE_DB))\r
+  {\r
+    tempD |= msk;\r
+  }\r
+  // select\r
+  msk |= (BIT11 | BIT10 | BIT9 | BIT8); // only ??? matters\r
+  if (pi_count < EARLY_DB)\r
+  {\r
+    tempD |= msk;\r
+  }\r
+  isbM32m(DDRPHY, reg, tempD, msk);\r
+\r
+  // error check\r
+  if (pi_count > 0x3F)\r
+  {\r
+    post_code(0xEE, 0xE5);\r
+  }\r
+\r
+  LEAVEFN();\r
+  return;\r
+}\r
+\r
+// get_wclk:\r
+//\r
+// This function will return the amout of WCLK delay on the given channel, rank as an absolute PI count.\r
+uint32_t get_wclk(\r
+    uint8_t channel,\r
+    uint8_t rank)\r
+{\r
+  uint32_t reg;\r
+  uint32_t tempD;\r
+  uint32_t pi_count;\r
+\r
+  ENTERFN();\r
+  // RDPTR (1/2 MCLK, 64 PIs)\r
+  // CCPTRREG[15:12] -> CLK1 (0x0-0xF)\r
+  // CCPTRREG[11:08] -> CLK0 (0x0-0xF)\r
+  reg = CCPTRREG + (channel * DDRIOCCC_CH_OFFSET);\r
+  tempD = isbR32m(DDRPHY, reg);\r
+  tempD >>= (rank) ? (12) : (8);\r
+  tempD &= 0xF;\r
+\r
+  // Adjust PI_COUNT\r
+  pi_count = tempD * HALF_CLK;\r
+\r
+  // PI (1/64 MCLK, 1 PIs)\r
+  // ECCB1DLLPICODER0[13:08] -> CLK0 (0x00-0x3F)\r
+  // ECCB1DLLPICODER0[21:16] -> CLK1 (0x00-0x3F)\r
+  reg = (rank) ? (ECCB1DLLPICODER0) : (ECCB1DLLPICODER0);\r
+  reg += (channel * DDRIOCCC_CH_OFFSET);\r
+  tempD = isbR32m(DDRPHY, reg);\r
+  tempD >>= (rank) ? (16) : (8);\r
+  tempD &= 0x3F;\r
+\r
+  pi_count += tempD;\r
+\r
+  LEAVEFN();\r
+  return pi_count;\r
+}\r
+\r
+// set_wctl:\r
+//\r
+// This function will program the WCTL delays based on an absolute number of PIs.\r
+// (currently doesn't comprehend rank)\r
+void set_wctl(\r
+    uint8_t channel,\r
+    uint8_t rank,\r
+    uint32_t pi_count)\r
+{\r
+  uint32_t reg;\r
+  uint32_t msk;\r
+  uint32_t tempD;\r
+\r
+  ENTERFN();\r
+\r
+  // RDPTR (1/2 MCLK, 64 PIs)\r
+  // CCPTRREG[31:28] (0x0-0xF)\r
+  // CCPTRREG[27:24] (0x0-0xF)\r
+  reg = CCPTRREG + (channel * DDRIOCCC_CH_OFFSET);\r
+  msk = (BIT31 | BIT30 | BIT29 | BIT28) | (BIT27 | BIT26 | BIT25 | BIT24);\r
+  tempD = ((pi_count / HALF_CLK) << 28) | ((pi_count / HALF_CLK) << 24);\r
+  isbM32m(DDRPHY, reg, tempD, msk);\r
+\r
+  // Adjust PI_COUNT\r
+  pi_count -= ((pi_count / HALF_CLK) & 0xF) * HALF_CLK;\r
+\r
+  // PI (1/64 MCLK, 1 PIs)\r
+  // ECCB1DLLPICODER?[29:24] (0x00-0x3F)\r
+  // ECCB1DLLPICODER?[29:24] (0x00-0x3F)\r
+  reg = ECCB1DLLPICODER0 + (channel * DDRIOCCC_CH_OFFSET);\r
+  msk = (BIT29 | BIT28 | BIT27 | BIT26 | BIT25 | BIT24);\r
+  tempD = (pi_count << 24);\r
+  isbM32m(DDRPHY, reg, tempD, msk);\r
+  reg = ECCB1DLLPICODER1 + (channel * DDRIOCCC_CH_OFFSET);\r
+  isbM32m(DDRPHY, reg, tempD, msk);\r
+  reg = ECCB1DLLPICODER2 + (channel * DDRIOCCC_CH_OFFSET);\r
+  isbM32m(DDRPHY, reg, tempD, msk);\r
+  reg = ECCB1DLLPICODER3 + (channel * DDRIOCCC_CH_OFFSET);\r
+  isbM32m(DDRPHY, reg, tempD, msk);\r
+\r
+  // DEADBAND\r
+  // CCCFGREG1[13:12] (+1 select)\r
+  // CCCFGREG1[05:04] (enable)\r
+  reg = CCCFGREG1 + (channel * DDRIOCCC_CH_OFFSET);\r
+  msk = 0x00;\r
+  tempD = 0x00;\r
+  // enable\r
+  msk |= (BIT5 | BIT4); // only ??? matters\r
+  if ((pi_count < EARLY_DB) || (pi_count > LATE_DB))\r
+  {\r
+    tempD |= msk;\r
+  }\r
+  // select\r
+  msk |= (BIT13 | BIT12); // only ??? matters\r
+  if (pi_count < EARLY_DB)\r
+  {\r
+    tempD |= msk;\r
+  }\r
+  isbM32m(DDRPHY, reg, tempD, msk);\r
+\r
+  // error check\r
+  if (pi_count > 0x3F)\r
+  {\r
+    post_code(0xEE, 0xE6);\r
+  }\r
+\r
+  LEAVEFN();\r
+  return;\r
+}\r
+\r
+// get_wctl:\r
+//\r
+// This function will return the amount of WCTL delay on the given channel, rank as an absolute PI count.\r
+// (currently doesn't comprehend rank)\r
+uint32_t get_wctl(\r
+    uint8_t channel,\r
+    uint8_t rank)\r
+{\r
+  uint32_t reg;\r
+  uint32_t tempD;\r
+  uint32_t pi_count;\r
+\r
+  ENTERFN();\r
+\r
+  // RDPTR (1/2 MCLK, 64 PIs)\r
+  // CCPTRREG[31:28] (0x0-0xF)\r
+  // CCPTRREG[27:24] (0x0-0xF)\r
+  reg = CCPTRREG + (channel * DDRIOCCC_CH_OFFSET);\r
+  tempD = isbR32m(DDRPHY, reg);\r
+  tempD >>= 24;\r
+  tempD &= 0xF;\r
+\r
+  // Adjust PI_COUNT\r
+  pi_count = tempD * HALF_CLK;\r
+\r
+  // PI (1/64 MCLK, 1 PIs)\r
+  // ECCB1DLLPICODER?[29:24] (0x00-0x3F)\r
+  // ECCB1DLLPICODER?[29:24] (0x00-0x3F)\r
+  reg = ECCB1DLLPICODER0 + (channel * DDRIOCCC_CH_OFFSET);\r
+  tempD = isbR32m(DDRPHY, reg);\r
+  tempD >>= 24;\r
+  tempD &= 0x3F;\r
+\r
+  // Adjust PI_COUNT\r
+  pi_count += tempD;\r
+\r
+  LEAVEFN();\r
+  return pi_count;\r
+}\r
+\r
+// set_vref:\r
+//\r
+// This function will program the internal Vref setting in a given byte lane in a given channel.\r
+void set_vref(\r
+    uint8_t channel,\r
+    uint8_t byte_lane,\r
+    uint32_t setting)\r
+{\r
+  uint32_t reg = (byte_lane & 0x1) ? (B1VREFCTL) : (B0VREFCTL);\r
+\r
+  ENTERFN();\r
+  DPF(D_TRN, "Vref ch%d ln%d : val=%03X\n", channel, byte_lane, setting);\r
+\r
+  isbM32m(DDRPHY, (reg + (channel * DDRIODQ_CH_OFFSET) + ((byte_lane >> 1) * DDRIODQ_BL_OFFSET)),\r
+      (vref_codes[setting] << 2), (BIT7 | BIT6 | BIT5 | BIT4 | BIT3 | BIT2));\r
+  //isbM32m(DDRPHY, (reg + (channel * DDRIODQ_CH_OFFSET) + ((byte_lane >> 1) * DDRIODQ_BL_OFFSET)), (setting<<2), (BIT7|BIT6|BIT5|BIT4|BIT3|BIT2));\r
+  // need to wait ~300ns for Vref to settle (check that this is necessary)\r
+  delay_n(300);\r
+  // ??? may need to clear pointers ???\r
+  LEAVEFN();\r
+  return;\r
+}\r
+\r
+// get_vref:\r
+//\r
+// This function will return the internal Vref setting for the given channel, byte_lane;\r
+uint32_t get_vref(\r
+    uint8_t channel,\r
+    uint8_t byte_lane)\r
+{\r
+  uint8_t j;\r
+  uint32_t ret_val = sizeof(vref_codes) / 2;\r
+  uint32_t reg = (byte_lane & 0x1) ? (B1VREFCTL) : (B0VREFCTL);\r
+\r
+  uint32_t tempD;\r
+\r
+  ENTERFN();\r
+  tempD = isbR32m(DDRPHY, (reg + (channel * DDRIODQ_CH_OFFSET) + ((byte_lane >> 1) * DDRIODQ_BL_OFFSET)));\r
+  tempD >>= 2;\r
+  tempD &= 0x3F;\r
+  for (j = 0; j < sizeof(vref_codes); j++)\r
+  {\r
+    if (vref_codes[j] == tempD)\r
+    {\r
+      ret_val = j;\r
+      break;\r
+    }\r
+  }\r
+  LEAVEFN();\r
+  return ret_val;\r
+}\r
+\r
+// clear_pointers:\r
+//\r
+// This function will be used to clear the pointers in a given byte lane in a given channel.\r
+void clear_pointers(\r
+    void)\r
+{\r
+  uint8_t channel_i;\r
+  uint8_t bl_i;\r
+\r
+  ENTERFN();\r
+  for (channel_i = 0; channel_i < NUM_CHANNELS; channel_i++)\r
+  {\r
+    for (bl_i = 0; bl_i < NUM_BYTE_LANES; bl_i++)\r
+    {\r
+      isbM32m(DDRPHY, (B01PTRCTL1 + (channel_i * DDRIODQ_CH_OFFSET) + ((bl_i >> 1) * DDRIODQ_BL_OFFSET)), ~(BIT8),\r
+          (BIT8));\r
+      //delay_m(1); // DEBUG\r
+      isbM32m(DDRPHY, (B01PTRCTL1 + (channel_i * DDRIODQ_CH_OFFSET) + ((bl_i >> 1) * DDRIODQ_BL_OFFSET)), (BIT8),\r
+          (BIT8));\r
+    }\r
+  }\r
+  LEAVEFN();\r
+  return;\r
+}\r
+\r
+// void enable_cache:\r
+void enable_cache(\r
+    void)\r
+{\r
+  // Cache control not used in Quark MRC\r
+  return;\r
+}\r
+\r
+// void disable_cache:\r
+void disable_cache(\r
+    void)\r
+{\r
+  // Cache control not used in Quark MRC\r
+  return;\r
+}\r
+\r
+// Send DRAM command, data should be formated\r
+// using DCMD_Xxxx macro or emrsXCommand structure.\r
+static void dram_init_command(\r
+    uint32_t data)\r
+{\r
+  Wr32(DCMD, 0, data);\r
+}\r
+\r
+// find_rising_edge:\r
+//\r
+// This function will find the rising edge transition on RCVN or WDQS.\r
+void find_rising_edge(\r
+    MRCParams_t *mrc_params,\r
+    uint32_t delay[],\r
+    uint8_t channel,\r
+    uint8_t rank,\r
+    bool rcvn)\r
+{\r
+\r
+#define SAMPLE_CNT 3   // number of sample points\r
+#define SAMPLE_DLY 26  // number of PIs to increment per sample\r
+#define FORWARD true   // indicates to increase delays when looking for edge\r
+#define BACKWARD false // indicates to decrease delays when looking for edge\r
+\r
+  bool all_edges_found; // determines stop condition\r
+  bool direction[NUM_BYTE_LANES]; // direction indicator\r
+  uint8_t sample_i; // sample counter\r
+  uint8_t bl_i; // byte lane counter\r
+  uint8_t bl_divisor = (mrc_params->channel_width == x16) ? 2 : 1; // byte lane divisor\r
+  uint32_t sample_result[SAMPLE_CNT]; // results of "sample_dqs()"\r
+  uint32_t tempD; // temporary DWORD\r
+  uint32_t transition_pattern;\r
+\r
+  ENTERFN();\r
+\r
+  // select hte and request initial configuration\r
+  select_hte(mrc_params);\r
+  first_run = 1;\r
+\r
+  // Take 3 sample points (T1,T2,T3) to obtain a transition pattern.\r
+  for (sample_i = 0; sample_i < SAMPLE_CNT; sample_i++)\r
+  {\r
+    // program the desired delays for sample\r
+    for (bl_i = 0; bl_i < (NUM_BYTE_LANES / bl_divisor); bl_i++)\r
+    {\r
+      // increase sample delay by 26 PI (0.2 CLK)\r
+      if (rcvn)\r
+      {\r
+        set_rcvn(channel, rank, bl_i, delay[bl_i] + (sample_i * SAMPLE_DLY));\r
+      }\r
+      else\r
+      {\r
+        set_wdqs(channel, rank, bl_i, delay[bl_i] + (sample_i * SAMPLE_DLY));\r
+      }\r
+    } // bl_i loop\r
+    // take samples (Tsample_i)\r
+    sample_result[sample_i] = sample_dqs(mrc_params, channel, rank, rcvn);\r
+\r
+    DPF(D_TRN, "Find rising edge %s ch%d rnk%d: #%d dly=%d dqs=%02X\n",\r
+        (rcvn ? "RCVN" : "WDQS"), channel, rank,\r
+        sample_i, sample_i * SAMPLE_DLY, sample_result[sample_i]);\r
+\r
+  } // sample_i loop\r
+\r
+  // This pattern will help determine where we landed and ultimately how to place RCVEN/WDQS.\r
+  for (bl_i = 0; bl_i < (NUM_BYTE_LANES / bl_divisor); bl_i++)\r
+  {\r
+    // build "transition_pattern" (MSB is 1st sample)\r
+    transition_pattern = 0x00;\r
+    for (sample_i = 0; sample_i < SAMPLE_CNT; sample_i++)\r
+    {\r
+      transition_pattern |= ((sample_result[sample_i] & (1 << bl_i)) >> bl_i) << (SAMPLE_CNT - 1 - sample_i);\r
+    } // sample_i loop\r
+\r
+    DPF(D_TRN, "=== transition pattern %d\n", transition_pattern);\r
+\r
+    // set up to look for rising edge based on "transition_pattern"\r
+    switch (transition_pattern)\r
+    {\r
+    case 0x00: // sampled 0->0->0\r
+      // move forward from T3 looking for 0->1\r
+      delay[bl_i] += 2 * SAMPLE_DLY;\r
+      direction[bl_i] = FORWARD;\r
+      break;\r
+    case 0x01: // sampled 0->0->1\r
+    case 0x05: // sampled 1->0->1 (bad duty cycle) *HSD#237503*\r
+      // move forward from T2 looking for 0->1\r
+      delay[bl_i] += 1 * SAMPLE_DLY;\r
+      direction[bl_i] = FORWARD;\r
+      break;\r
+// HSD#237503\r
+//      case 0x02: // sampled 0->1->0 (bad duty cycle)\r
+//        training_message(channel, rank, bl_i);\r
+//        post_code(0xEE, 0xE8);\r
+//        break;\r
+    case 0x02: // sampled 0->1->0 (bad duty cycle) *HSD#237503*\r
+    case 0x03: // sampled 0->1->1\r
+      // move forward from T1 looking for 0->1\r
+      delay[bl_i] += 0 * SAMPLE_DLY;\r
+      direction[bl_i] = FORWARD;\r
+      break;\r
+    case 0x04: // sampled 1->0->0 (assumes BL8, HSD#234975)\r
+      // move forward from T3 looking for 0->1\r
+      delay[bl_i] += 2 * SAMPLE_DLY;\r
+      direction[bl_i] = FORWARD;\r
+      break;\r
+// HSD#237503\r
+//      case 0x05: // sampled 1->0->1 (bad duty cycle)\r
+//        training_message(channel, rank, bl_i);\r
+//        post_code(0xEE, 0xE9);\r
+//        break;\r
+    case 0x06: // sampled 1->1->0\r
+    case 0x07: // sampled 1->1->1\r
+      // move backward from T1 looking for 1->0\r
+      delay[bl_i] += 0 * SAMPLE_DLY;\r
+      direction[bl_i] = BACKWARD;\r
+      break;\r
+    default:\r
+      post_code(0xEE, 0xEE);\r
+      break;\r
+    } // transition_pattern switch\r
+    // program delays\r
+    if (rcvn)\r
+    {\r
+      set_rcvn(channel, rank, bl_i, delay[bl_i]);\r
+    }\r
+    else\r
+    {\r
+      set_wdqs(channel, rank, bl_i, delay[bl_i]);\r
+    }\r
+  } // bl_i loop\r
+\r
+  // Based on the observed transition pattern on the byte lane,\r
+  // begin looking for a rising edge with single PI granularity.\r
+  do\r
+  {\r
+    all_edges_found = true; // assume all byte lanes passed\r
+    tempD = sample_dqs(mrc_params, channel, rank, rcvn); // take a sample\r
+    // check all each byte lane for proper edge\r
+    for (bl_i = 0; bl_i < (NUM_BYTE_LANES / bl_divisor); bl_i++)\r
+    {\r
+      if (tempD & (1 << bl_i))\r
+      {\r
+        // sampled "1"\r
+        if (direction[bl_i] == BACKWARD)\r
+        {\r
+          // keep looking for edge on this byte lane\r
+          all_edges_found = false;\r
+          delay[bl_i] -= 1;\r
+          if (rcvn)\r
+          {\r
+            set_rcvn(channel, rank, bl_i, delay[bl_i]);\r
+          }\r
+          else\r
+          {\r
+            set_wdqs(channel, rank, bl_i, delay[bl_i]);\r
+          }\r
+        }\r
+      }\r
+      else\r
+      {\r
+        // sampled "0"\r
+        if (direction[bl_i] == FORWARD)\r
+        {\r
+          // keep looking for edge on this byte lane\r
+          all_edges_found = false;\r
+          delay[bl_i] += 1;\r
+          if (rcvn)\r
+          {\r
+            set_rcvn(channel, rank, bl_i, delay[bl_i]);\r
+          }\r
+          else\r
+          {\r
+            set_wdqs(channel, rank, bl_i, delay[bl_i]);\r
+          }\r
+        }\r
+      }\r
+    } // bl_i loop\r
+  } while (!all_edges_found);\r
+\r
+  // restore DDR idle state\r
+  dram_init_command(DCMD_PREA(rank));\r
+\r
+  DPF(D_TRN, "Delay %03X %03X %03X %03X\n",\r
+      delay[0], delay[1], delay[2], delay[3]);\r
+\r
+  LEAVEFN();\r
+  return;\r
+}\r
+\r
+// sample_dqs:\r
+//\r
+// This function will sample the DQTRAINSTS registers in the given channel/rank SAMPLE_SIZE times looking for a valid '0' or '1'.\r
+// It will return an encoded DWORD in which each bit corresponds to the sampled value on the byte lane.\r
+uint32_t sample_dqs(\r
+    MRCParams_t *mrc_params,\r
+    uint8_t channel,\r
+    uint8_t rank,\r
+    bool rcvn)\r
+{\r
+  uint8_t j; // just a counter\r
+  uint8_t bl_i; // which BL in the module (always 2 per module)\r
+  uint8_t bl_grp; // which BL module\r
+  uint8_t bl_divisor = (mrc_params->channel_width == x16) ? 2 : 1; // byte lane divisor\r
+  uint32_t msk[2]; // BLx in module\r
+  uint32_t sampled_val[SAMPLE_SIZE]; // DQTRAINSTS register contents for each sample\r
+  uint32_t num_0s; // tracks the number of '0' samples\r
+  uint32_t num_1s; // tracks the number of '1' samples\r
+  uint32_t ret_val = 0x00; // assume all '0' samples\r
+  uint32_t address = get_addr(mrc_params, channel, rank);\r
+\r
+  // initialise "msk[]"\r
+  msk[0] = (rcvn) ? (BIT1) : (BIT9); // BL0\r
+  msk[1] = (rcvn) ? (BIT0) : (BIT8); // BL1\r
+\r
+\r
+  // cycle through each byte lane group\r
+  for (bl_grp = 0; bl_grp < (NUM_BYTE_LANES / bl_divisor) / 2; bl_grp++)\r
+  {\r
+    // take SAMPLE_SIZE samples\r
+    for (j = 0; j < SAMPLE_SIZE; j++)\r
+    {\r
+      HteMemOp(address, first_run, rcvn?0:1);\r
+      first_run = 0;\r
+\r
+      // record the contents of the proper DQTRAINSTS register\r
+      sampled_val[j] = isbR32m(DDRPHY, (DQTRAINSTS + (bl_grp * DDRIODQ_BL_OFFSET) + (channel * DDRIODQ_CH_OFFSET)));\r
+    }\r
+    // look for a majority value ( (SAMPLE_SIZE/2)+1 ) on the byte lane\r
+    // and set that value in the corresponding "ret_val" bit\r
+    for (bl_i = 0; bl_i < 2; bl_i++)\r
+    {\r
+      num_0s = 0x00; // reset '0' tracker for byte lane\r
+      num_1s = 0x00; // reset '1' tracker for byte lane\r
+      for (j = 0; j < SAMPLE_SIZE; j++)\r
+      {\r
+        if (sampled_val[j] & msk[bl_i])\r
+        {\r
+          num_1s++;\r
+        }\r
+        else\r
+        {\r
+          num_0s++;\r
+        }\r
+      }\r
+      if (num_1s > num_0s)\r
+      {\r
+        ret_val |= (1 << (bl_i + (bl_grp * 2)));\r
+      }\r
+    }\r
+  }\r
+\r
+  // "ret_val.0" contains the status of BL0\r
+  // "ret_val.1" contains the status of BL1\r
+  // "ret_val.2" contains the status of BL2\r
+  // etc.\r
+  return ret_val;\r
+}\r
+\r
+// get_addr:\r
+//\r
+// This function will return a 32 bit address in the desired channel and rank.\r
+uint32_t get_addr(\r
+    MRCParams_t *mrc_params,\r
+    uint8_t channel,\r
+    uint8_t rank)\r
+{\r
+  uint32_t offset = 0x02000000; // 32MB\r
+\r
+  // Begin product specific code\r
+  if (channel > 0)\r
+  {\r
+    DPF(D_ERROR, "ILLEGAL CHANNEL\n");\r
+    DEAD_LOOP();\r
+  }\r
+\r
+  if (rank > 1)\r
+  {\r
+    DPF(D_ERROR, "ILLEGAL RANK\n");\r
+    DEAD_LOOP();\r
+  }\r
+\r
+  // use 256MB lowest density as per DRP == 0x0003\r
+  offset += rank * (256 * 1024 * 1024);\r
+\r
+  return offset;\r
+}\r
+\r
+// byte_lane_mask:\r
+//\r
+// This function will return a 32 bit mask that will be used to check for byte lane failures.\r
+uint32_t byte_lane_mask(\r
+    MRCParams_t *mrc_params)\r
+{\r
+  uint32_t j;\r
+  uint32_t ret_val = 0x00;\r
+\r
+  // set "ret_val" based on NUM_BYTE_LANES such that you will check only BL0 in "result"\r
+  // (each bit in "result" represents a byte lane)\r
+  for (j = 0; j < MAX_BYTE_LANES; j += NUM_BYTE_LANES)\r
+  {\r
+    ret_val |= (1 << ((j / NUM_BYTE_LANES) * NUM_BYTE_LANES));\r
+  }\r
+\r
+  // HSD#235037\r
+  // need to adjust the mask for 16-bit mode\r
+  if (mrc_params->channel_width == x16)\r
+  {\r
+    ret_val |= (ret_val << 2);\r
+  }\r
+\r
+  return ret_val;\r
+}\r
+\r
+\r
+// read_tsc:\r
+//\r
+// This function will do some assembly to return TSC register contents as a uint64_t.\r
+uint64_t read_tsc(\r
+    void)\r
+{\r
+  volatile uint64_t tsc;  // EDX:EAX\r
+\r
+#if defined (SIM) || defined (GCC)\r
+  volatile uint32_t tscH; // EDX\r
+  volatile uint32_t tscL;// EAX\r
+\r
+  asm("rdtsc":"=a"(tscL),"=d"(tscH));\r
+  tsc = tscH;\r
+  tsc = (tsc<<32)|tscL;\r
+#else\r
+  tsc = __rdtsc();\r
+#endif\r
+\r
+  return tsc;\r
+}\r
+\r
+// get_tsc_freq:\r
+//\r
+// This function returns the TSC frequency in MHz\r
+uint32_t get_tsc_freq(\r
+    void)\r
+{\r
+  static uint32_t freq[] =\r
+  { 533, 400, 200, 100 };\r
+  uint32_t fuse;\r
+#if 0\r
+  fuse = (isbR32m(FUSE, 0) >> 12) & (BIT1|BIT0);\r
+#else\r
+  // todo!!! Fixed 533MHz for emulation or debugging\r
+  fuse = 0;\r
+#endif\r
+  return freq[fuse];\r
+}\r
+\r
+#ifndef SIM\r
+// delay_n:\r
+//\r
+// This is a simple delay function.\r
+// It takes "nanoseconds" as a parameter.\r
+void delay_n(\r
+    uint32_t nanoseconds)\r
+{\r
+  // 1000 MHz clock has 1ns period --> no conversion required\r
+  uint64_t final_tsc = read_tsc();\r
+  final_tsc += ((get_tsc_freq() * (nanoseconds)) / 1000);\r
+\r
+  while (read_tsc() < final_tsc)\r
+    ;\r
+  return;\r
+}\r
+#endif\r
+\r
+// delay_u:\r
+//\r
+// This is a simple delay function.\r
+// It takes "microseconds as a parameter.\r
+void delay_u(\r
+    uint32_t microseconds)\r
+{\r
+  // 64 bit math is not an option, just use loops\r
+  while (microseconds--)\r
+  {\r
+    delay_n(1000);\r
+  }\r
+  return;\r
+}\r
+\r
+// delay_m:\r
+//\r
+// This is a simple delay function.\r
+// It takes "milliseconds" as a parameter.\r
+void delay_m(\r
+    uint32_t milliseconds)\r
+{\r
+  // 64 bit math is not an option, just use loops\r
+  while (milliseconds--)\r
+  {\r
+    delay_u(1000);\r
+  }\r
+  return;\r
+}\r
+\r
+// delay_s:\r
+//\r
+// This is a simple delay function.\r
+// It takes "seconds" as a parameter.\r
+void delay_s(\r
+    uint32_t seconds)\r
+{\r
+  // 64 bit math is not an option, just use loops\r
+  while (seconds--)\r
+  {\r
+    delay_m(1000);\r
+  }\r
+  return;\r
+}\r
+\r
+// post_code:\r
+//\r
+// This function will output the POST CODE to the four 7-Segment LED displays.\r
+void post_code(\r
+    uint8_t major,\r
+    uint8_t minor)\r
+{\r
+#ifdef EMU\r
+  // Update global variable for execution tracking in debug env\r
+  PostCode = ((major << 8) | minor);\r
+#endif\r
+\r
+  // send message to UART\r
+  DPF(D_INFO, "POST: 0x%01X%02X\n", major, minor);\r
+\r
+  // error check:\r
+  if (major == 0xEE)\r
+  {\r
+    // todo!!! Consider updating error status and exit MRC\r
+#ifdef SIM\r
+    // enable Ctrl-C handling\r
+    for(;;) delay_n(100);\r
+#else\r
+    DEAD_LOOP();\r
+#endif\r
+  }\r
+}\r
+\r
+void training_message(\r
+    uint8_t channel,\r
+    uint8_t rank,\r
+    uint8_t byte_lane)\r
+{\r
+  // send message to UART\r
+  DPF(D_INFO, "CH%01X RK%01X BL%01X\n", channel, rank, byte_lane);\r
+  return;\r
+}\r
+\r
+void print_timings(\r
+    MRCParams_t *mrc_params)\r
+{\r
+  uint8_t algo_i;\r
+  uint8_t channel_i;\r
+  uint8_t rank_i;\r
+  uint8_t bl_i;\r
+  uint8_t bl_divisor = (mrc_params->channel_width == x16) ? 2 : 1;\r
+\r
+  DPF(D_INFO, "\n---------------------------");\r
+  DPF(D_INFO, "\nALGO[CH:RK] BL0 BL1 BL2 BL3");\r
+  DPF(D_INFO, "\n===========================");\r
+  for (algo_i = 0; algo_i < eMAX_ALGOS; algo_i++)\r
+  {\r
+    for (channel_i = 0; channel_i < NUM_CHANNELS; channel_i++)\r
+    {\r
+      if (mrc_params->channel_enables & (1 << channel_i))\r
+      {\r
+        for (rank_i = 0; rank_i < NUM_RANKS; rank_i++)\r
+        {\r
+          if (mrc_params->rank_enables & (1 << rank_i))\r
+          {\r
+            switch (algo_i)\r
+            {\r
+            case eRCVN:\r
+              DPF(D_INFO, "\nRCVN[%02d:%02d]", channel_i, rank_i);\r
+              break;\r
+            case eWDQS:\r
+              DPF(D_INFO, "\nWDQS[%02d:%02d]", channel_i, rank_i);\r
+              break;\r
+            case eWDQx:\r
+              DPF(D_INFO, "\nWDQx[%02d:%02d]", channel_i, rank_i);\r
+              break;\r
+            case eRDQS:\r
+              DPF(D_INFO, "\nRDQS[%02d:%02d]", channel_i, rank_i);\r
+              break;\r
+            case eVREF:\r
+              DPF(D_INFO, "\nVREF[%02d:%02d]", channel_i, rank_i);\r
+              break;\r
+            case eWCMD:\r
+              DPF(D_INFO, "\nWCMD[%02d:%02d]", channel_i, rank_i);\r
+              break;\r
+            case eWCTL:\r
+              DPF(D_INFO, "\nWCTL[%02d:%02d]", channel_i, rank_i);\r
+              break;\r
+            case eWCLK:\r
+              DPF(D_INFO, "\nWCLK[%02d:%02d]", channel_i, rank_i);\r
+              break;\r
+            default:\r
+              break;\r
+            } // algo_i switch\r
+            for (bl_i = 0; bl_i < (NUM_BYTE_LANES / bl_divisor); bl_i++)\r
+            {\r
+              switch (algo_i)\r
+              {\r
+              case eRCVN:\r
+                DPF(D_INFO, " %03d", get_rcvn(channel_i, rank_i, bl_i));\r
+                break;\r
+              case eWDQS:\r
+                DPF(D_INFO, " %03d", get_wdqs(channel_i, rank_i, bl_i));\r
+                break;\r
+              case eWDQx:\r
+                DPF(D_INFO, " %03d", get_wdq(channel_i, rank_i, bl_i));\r
+                break;\r
+              case eRDQS:\r
+                DPF(D_INFO, " %03d", get_rdqs(channel_i, rank_i, bl_i));\r
+                break;\r
+              case eVREF:\r
+                DPF(D_INFO, " %03d", get_vref(channel_i, bl_i));\r
+                break;\r
+              case eWCMD:\r
+                DPF(D_INFO, " %03d", get_wcmd(channel_i));\r
+                break;\r
+              case eWCTL:\r
+                DPF(D_INFO, " %03d", get_wctl(channel_i, rank_i));\r
+                break;\r
+              case eWCLK:\r
+                DPF(D_INFO, " %03d", get_wclk(channel_i, rank_i));\r
+                break;\r
+              default:\r
+                break;\r
+              } // algo_i switch\r
+            } // bl_i loop\r
+          } // if rank_i enabled\r
+        } // rank_i loop\r
+      } // if channel_i enabled\r
+    } // channel_i loop\r
+  } // algo_i loop\r
+  DPF(D_INFO, "\n---------------------------");\r
+  DPF(D_INFO, "\n");\r
+  return;\r
+}\r
+\r
+// 32 bit LFSR with characteristic polynomial:  X^32 + X^22 +X^2 + X^1\r
+// The function takes pointer to previous 32 bit value and modifies it to next value.\r
+void lfsr32(\r
+    uint32_t *lfsr_ptr)\r
+{\r
+  uint32_t bit;\r
+  uint32_t lfsr;\r
+  uint32_t i;\r
+\r
+  lfsr = *lfsr_ptr;\r
+\r
+  for (i = 0; i < 32; i++)\r
+  {\r
+    bit = 1 ^ (lfsr & BIT0);\r
+    bit = bit ^ ((lfsr & BIT1) >> 1);\r
+    bit = bit ^ ((lfsr & BIT2) >> 2);\r
+    bit = bit ^ ((lfsr & BIT22) >> 22);\r
+\r
+    lfsr = ((lfsr >> 1) | (bit << 31));\r
+  }\r
+\r
+  *lfsr_ptr = lfsr;\r
+  return;\r
+}\r
+\r
+// The purpose of this function is to ensure the SEC comes out of reset\r
+// and IA initiates the SEC enabling Memory Scrambling.\r
+void enable_scrambling(\r
+    MRCParams_t *mrc_params)\r
+{\r
+  uint32_t lfsr = 0;\r
+  uint8_t i;\r
+\r
+  if (mrc_params->scrambling_enables == 0)\r
+    return;\r
+\r
+  ENTERFN();\r
+\r
+  // 32 bit seed is always stored in BIOS NVM.\r
+  lfsr = mrc_params->timings.scrambler_seed;\r
+\r
+  if (mrc_params->boot_mode == bmCold)\r
+  {\r
+    // factory value is 0 and in first boot, a clock based seed is loaded.\r
+    if (lfsr == 0)\r
+    {\r
+      lfsr = read_tsc() & 0x0FFFFFFF; // get seed from system clock and make sure it is not all 1's\r
+    }\r
+    // need to replace scrambler\r
+    // get next 32bit LFSR 16 times which is the last part of the previous scrambler vector.\r
+    else\r
+    {\r
+      for (i = 0; i < 16; i++)\r
+      {\r
+        lfsr32(&lfsr);\r
+      }\r
+    }\r
+    mrc_params->timings.scrambler_seed = lfsr;  // save new seed.\r
+  } // if (cold_boot)\r
+\r
+  // In warm boot or S3 exit, we have the previous seed.\r
+  // In cold boot, we have the last 32bit LFSR which is the new seed.\r
+  lfsr32(&lfsr); // shift to next value\r
+  isbW32m(MCU, SCRMSEED, (lfsr & 0x0003FFFF));\r
+  for (i = 0; i < 2; i++)\r
+  {\r
+    isbW32m(MCU, SCRMLO + i, (lfsr & 0xAAAAAAAA));\r
+  }\r
+\r
+  LEAVEFN();\r
+  return;\r
+}\r
+\r
+// This function will store relevant timing data\r
+// This data will be used on subsequent boots to speed up boot times\r
+// and is required for Suspend To RAM capabilities.\r
+void store_timings(\r
+    MRCParams_t *mrc_params)\r
+{\r
+  uint8_t ch, rk, bl;\r
+  MrcTimings_t *mt = &mrc_params->timings;\r
+\r
+  for (ch = 0; ch < NUM_CHANNELS; ch++)\r
+  {\r
+    for (rk = 0; rk < NUM_RANKS; rk++)\r
+    {\r
+      for (bl = 0; bl < NUM_BYTE_LANES; bl++)\r
+      {\r
+        mt->rcvn[ch][rk][bl] = get_rcvn(ch, rk, bl); // RCVN\r
+        mt->rdqs[ch][rk][bl] = get_rdqs(ch, rk, bl); // RDQS\r
+        mt->wdqs[ch][rk][bl] = get_wdqs(ch, rk, bl); // WDQS\r
+        mt->wdq[ch][rk][bl] = get_wdq(ch, rk, bl);  // WDQ\r
+        if (rk == 0)\r
+        {\r
+          mt->vref[ch][bl] = get_vref(ch, bl);  // VREF (RANK0 only)\r
+        }\r
+      }\r
+      mt->wctl[ch][rk] = get_wctl(ch, rk); // WCTL\r
+    }\r
+    mt->wcmd[ch] = get_wcmd(ch); // WCMD\r
+  }\r
+\r
+  // need to save for a case of changing frequency after warm reset\r
+  mt->ddr_speed = mrc_params->ddr_speed;\r
+\r
+  return;\r
+}\r
+\r
+// This function will retrieve relevant timing data\r
+// This data will be used on subsequent boots to speed up boot times\r
+// and is required for Suspend To RAM capabilities.\r
+void restore_timings(\r
+    MRCParams_t *mrc_params)\r
+{\r
+  uint8_t ch, rk, bl;\r
+  const MrcTimings_t *mt = &mrc_params->timings;\r
+\r
+  for (ch = 0; ch < NUM_CHANNELS; ch++)\r
+  {\r
+    for (rk = 0; rk < NUM_RANKS; rk++)\r
+    {\r
+      for (bl = 0; bl < NUM_BYTE_LANES; bl++)\r
+      {\r
+        set_rcvn(ch, rk, bl, mt->rcvn[ch][rk][bl]); // RCVN\r
+        set_rdqs(ch, rk, bl, mt->rdqs[ch][rk][bl]); // RDQS\r
+        set_wdqs(ch, rk, bl, mt->wdqs[ch][rk][bl]); // WDQS\r
+        set_wdq(ch, rk, bl, mt->wdq[ch][rk][bl]);  // WDQ\r
+        if (rk == 0)\r
+        {\r
+          set_vref(ch, bl, mt->vref[ch][bl]); // VREF (RANK0 only)\r
+        }\r
+      }\r
+      set_wctl(ch, rk, mt->wctl[ch][rk]); // WCTL\r
+    }\r
+    set_wcmd(ch, mt->wcmd[ch]); // WCMD\r
+  }\r
+\r
+  return;\r
+}\r
+\r
+// Configure default settings normally set as part of read training\r
+// Some defaults have to be set earlier as they may affect earlier\r
+// training steps.\r
+void default_timings(\r
+    MRCParams_t *mrc_params)\r
+{\r
+  uint8_t ch, rk, bl;\r
+\r
+  for (ch = 0; ch < NUM_CHANNELS; ch++)\r
+  {\r
+    for (rk = 0; rk < NUM_RANKS; rk++)\r
+    {\r
+      for (bl = 0; bl < NUM_BYTE_LANES; bl++)\r
+      {\r
+        set_rdqs(ch, rk, bl, 24); // RDQS\r
+        if (rk == 0)\r
+        {\r
+          set_vref(ch, bl, 32); // VREF (RANK0 only)\r
+        }\r
+      }\r
+    }\r
+  }\r
+\r
+  return;\r
+}\r
+\r