/************************************************************************
 *
 * Copyright (c) 2013-2015 Intel Corporation.
 *
* This program and the accompanying materials
* are licensed and made available under the terms and conditions of the BSD License
* which accompanies this distribution.  The full text of the license may be found at
* http://opensource.org/licenses/bsd-license.php
*
* THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
* WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
 *
 ***************************************************************************/

#include "mrc.h"
#include "memory_options.h"

#include "meminit_utils.h"
#include "hte.h"
#include "io.h"

void select_hte(
    MRCParams_t *mrc_params);

static uint8_t first_run = 0;

const uint8_t vref_codes[64] =
{ // lowest to highest
    0x3F, 0x3E, 0x3D, 0x3C, 0x3B, 0x3A, 0x39, 0x38, 0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31, 0x30, // 00 - 15
    0x2F, 0x2E, 0x2D, 0x2C, 0x2B, 0x2A, 0x29, 0x28, 0x27, 0x26, 0x25, 0x24, 0x23, 0x22, 0x21, 0x20, // 16 - 31
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, // 32 - 47
    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F  // 48 - 63
};

#ifdef EMU
// Track current post code for debugging purpose
uint32_t PostCode;
#endif

// set_rcvn:
//
// This function will program the RCVEN delays.
// (currently doesn't comprehend rank)
void set_rcvn(
    uint8_t channel,
    uint8_t rank,
    uint8_t byte_lane,
    uint32_t pi_count)
{
  uint32_t reg;
  uint32_t msk;
  uint32_t tempD;

  ENTERFN();
  DPF(D_TRN, "Rcvn ch%d rnk%d ln%d : pi=%03X\n", channel, rank, byte_lane, pi_count);

  // RDPTR (1/2 MCLK, 64 PIs)
  // BL0 -> B01PTRCTL0[11:08] (0x0-0xF)
  // BL1 -> B01PTRCTL0[23:20] (0x0-0xF)
  reg = B01PTRCTL0 + ((byte_lane >> 1) * DDRIODQ_BL_OFFSET) + (channel * DDRIODQ_CH_OFFSET);
  msk = (byte_lane & BIT0) ? (BIT23 | BIT22 | BIT21 | BIT20) : (BIT11 | BIT10 | BIT9 | BIT8);
  tempD = (byte_lane & BIT0) ? ((pi_count / HALF_CLK) << 20) : ((pi_count / HALF_CLK) << 8);
  isbM32m(DDRPHY, reg, tempD, msk);

  // Adjust PI_COUNT
  pi_count -= ((pi_count / HALF_CLK) & 0xF) * HALF_CLK;

  // PI (1/64 MCLK, 1 PIs)
  // BL0 -> B0DLLPICODER0[29:24] (0x00-0x3F)
  // BL1 -> B1DLLPICODER0[29:24] (0x00-0x3F)
  reg = (byte_lane & BIT0) ? (B1DLLPICODER0) : (B0DLLPICODER0);
  reg += (((byte_lane >> 1) * DDRIODQ_BL_OFFSET) + (channel * DDRIODQ_CH_OFFSET));
  msk = (BIT29 | BIT28 | BIT27 | BIT26 | BIT25 | BIT24);
  tempD = pi_count << 24;
  isbM32m(DDRPHY, reg, tempD, msk);

  // DEADBAND
  // BL0/1 -> B01DBCTL1[08/11] (+1 select)
  // BL0/1 -> B01DBCTL1[02/05] (enable)
  reg = B01DBCTL1 + ((byte_lane >> 1) * DDRIODQ_BL_OFFSET) + (channel * DDRIODQ_CH_OFFSET);
  msk = 0x00;
  tempD = 0x00;
  // enable
  msk |= (byte_lane & BIT0) ? (BIT5) : (BIT2);
  if ((pi_count < EARLY_DB) || (pi_count > LATE_DB))
  {
    tempD |= msk;
  }
  // select
  msk |= (byte_lane & BIT0) ? (BIT11) : (BIT8);
  if (pi_count < EARLY_DB)
  {
    tempD |= msk;
  }
  isbM32m(DDRPHY, reg, tempD, msk);

  // error check
  if (pi_count > 0x3F)
  {
    training_message(channel, rank, byte_lane);
    post_code(0xEE, 0xE0);
  }

  LEAVEFN();
  return;
}

// get_rcvn:
//
// This function will return the current RCVEN delay on the given channel, rank, byte_lane as an absolute PI count.
// (currently doesn't comprehend rank)
uint32_t get_rcvn(
    uint8_t channel,
    uint8_t rank,
    uint8_t byte_lane)
{
  uint32_t reg;
  uint32_t tempD;
  uint32_t pi_count;

  ENTERFN();

  // RDPTR (1/2 MCLK, 64 PIs)
  // BL0 -> B01PTRCTL0[11:08] (0x0-0xF)
  // BL1 -> B01PTRCTL0[23:20] (0x0-0xF)
  reg = B01PTRCTL0 + ((byte_lane >> 1) * DDRIODQ_BL_OFFSET) + (channel * DDRIODQ_CH_OFFSET);
  tempD = isbR32m(DDRPHY, reg);
  tempD >>= (byte_lane & BIT0) ? (20) : (8);
  tempD &= 0xF;

  // Adjust PI_COUNT
  pi_count = tempD * HALF_CLK;

  // PI (1/64 MCLK, 1 PIs)
  // BL0 -> B0DLLPICODER0[29:24] (0x00-0x3F)
  // BL1 -> B1DLLPICODER0[29:24] (0x00-0x3F)
  reg = (byte_lane & BIT0) ? (B1DLLPICODER0) : (B0DLLPICODER0);
  reg += (((byte_lane >> 1) * DDRIODQ_BL_OFFSET) + (channel * DDRIODQ_CH_OFFSET));
  tempD = isbR32m(DDRPHY, reg);
  tempD >>= 24;
  tempD &= 0x3F;

  // Adjust PI_COUNT
  pi_count += tempD;

  LEAVEFN();
  return pi_count;
}

// set_rdqs:
//
// This function will program the RDQS delays based on an absolute amount of PIs.
// (currently doesn't comprehend rank)
void set_rdqs(
    uint8_t channel,
    uint8_t rank,
    uint8_t byte_lane,
    uint32_t pi_count)
{
  uint32_t reg;
  uint32_t msk;
  uint32_t tempD;

  ENTERFN();
  DPF(D_TRN, "Rdqs ch%d rnk%d ln%d : pi=%03X\n", channel, rank, byte_lane, pi_count);

  // PI (1/128 MCLK)
  // BL0 -> B0RXDQSPICODE[06:00] (0x00-0x47)
  // BL1 -> B1RXDQSPICODE[06:00] (0x00-0x47)
  reg = (byte_lane & BIT0) ? (B1RXDQSPICODE) : (B0RXDQSPICODE);
  reg += (((byte_lane >> 1) * DDRIODQ_BL_OFFSET) + (channel * DDRIODQ_CH_OFFSET));
  msk = (BIT6 | BIT5 | BIT4 | BIT3 | BIT2 | BIT1 | BIT0);
  tempD = pi_count << 0;
  isbM32m(DDRPHY, reg, tempD, msk);

  // error check (shouldn't go above 0x3F)
  if (pi_count > 0x47)
  {
    training_message(channel, rank, byte_lane);
    post_code(0xEE, 0xE1);
  }

  LEAVEFN();
  return;
}

// get_rdqs:
//
// This function will return the current RDQS delay on the given channel, rank, byte_lane as an absolute PI count.
// (currently doesn't comprehend rank)
uint32_t get_rdqs(
    uint8_t channel,
    uint8_t rank,
    uint8_t byte_lane)
{
  uint32_t reg;
  uint32_t tempD;
  uint32_t pi_count;

  ENTERFN();

  // PI (1/128 MCLK)
  // BL0 -> B0RXDQSPICODE[06:00] (0x00-0x47)
  // BL1 -> B1RXDQSPICODE[06:00] (0x00-0x47)
  reg = (byte_lane & BIT0) ? (B1RXDQSPICODE) : (B0RXDQSPICODE);
  reg += (((byte_lane >> 1) * DDRIODQ_BL_OFFSET) + (channel * DDRIODQ_CH_OFFSET));
  tempD = isbR32m(DDRPHY, reg);

  // Adjust PI_COUNT
  pi_count = tempD & 0x7F;

  LEAVEFN();
  return pi_count;
}

// set_wdqs:
//
// This function will program the WDQS delays based on an absolute amount of PIs.
// (currently doesn't comprehend rank)
void set_wdqs(
    uint8_t channel,
    uint8_t rank,
    uint8_t byte_lane,
    uint32_t pi_count)
{
  uint32_t reg;
  uint32_t msk;
  uint32_t tempD;

  ENTERFN();
  DPF(D_TRN, "Wdqs ch%d rnk%d ln%d : pi=%03X\n", channel, rank, byte_lane, pi_count);

  // RDPTR (1/2 MCLK, 64 PIs)
  // BL0 -> B01PTRCTL0[07:04] (0x0-0xF)
  // BL1 -> B01PTRCTL0[19:16] (0x0-0xF)
  reg = B01PTRCTL0 + ((byte_lane >> 1) * DDRIODQ_BL_OFFSET) + (channel * DDRIODQ_CH_OFFSET);
  msk = (byte_lane & BIT0) ? (BIT19 | BIT18 | BIT17 | BIT16) : (BIT7 | BIT6 | BIT5 | BIT4);
  tempD = pi_count / HALF_CLK;
  tempD <<= (byte_lane & BIT0) ? (16) : (4);
  isbM32m(DDRPHY, reg, tempD, msk);

  // Adjust PI_COUNT
  pi_count -= ((pi_count / HALF_CLK) & 0xF) * HALF_CLK;

  // PI (1/64 MCLK, 1 PIs)
  // BL0 -> B0DLLPICODER0[21:16] (0x00-0x3F)
  // BL1 -> B1DLLPICODER0[21:16] (0x00-0x3F)
  reg = (byte_lane & BIT0) ? (B1DLLPICODER0) : (B0DLLPICODER0);
  reg += (((byte_lane >> 1) * DDRIODQ_BL_OFFSET) + (channel * DDRIODQ_CH_OFFSET));
  msk = (BIT21 | BIT20 | BIT19 | BIT18 | BIT17 | BIT16);
  tempD = pi_count << 16;
  isbM32m(DDRPHY, reg, tempD, msk);

  // DEADBAND
  // BL0/1 -> B01DBCTL1[07/10] (+1 select)
  // BL0/1 -> B01DBCTL1[01/04] (enable)
  reg = B01DBCTL1 + ((byte_lane >> 1) * DDRIODQ_BL_OFFSET) + (channel * DDRIODQ_CH_OFFSET);
  msk = 0x00;
  tempD = 0x00;
  // enable
  msk |= (byte_lane & BIT0) ? (BIT4) : (BIT1);
  if ((pi_count < EARLY_DB) || (pi_count > LATE_DB))
  {
    tempD |= msk;
  }
  // select
  msk |= (byte_lane & BIT0) ? (BIT10) : (BIT7);
  if (pi_count < EARLY_DB)
  {
    tempD |= msk;
  }
  isbM32m(DDRPHY, reg, tempD, msk);

  // error check
  if (pi_count > 0x3F)
  {
    training_message(channel, rank, byte_lane);
    post_code(0xEE, 0xE2);
  }

  LEAVEFN();
  return;
}

// get_wdqs:
//
// This function will return the amount of WDQS delay on the given channel, rank, byte_lane as an absolute PI count.
// (currently doesn't comprehend rank)
uint32_t get_wdqs(
    uint8_t channel,
    uint8_t rank,
    uint8_t byte_lane)
{
  uint32_t reg;
  uint32_t tempD;
  uint32_t pi_count;

  ENTERFN();

  // RDPTR (1/2 MCLK, 64 PIs)
  // BL0 -> B01PTRCTL0[07:04] (0x0-0xF)
  // BL1 -> B01PTRCTL0[19:16] (0x0-0xF)
  reg = B01PTRCTL0 + ((byte_lane >> 1) * DDRIODQ_BL_OFFSET) + (channel * DDRIODQ_CH_OFFSET);
  tempD = isbR32m(DDRPHY, reg);
  tempD >>= (byte_lane & BIT0) ? (16) : (4);
  tempD &= 0xF;

  // Adjust PI_COUNT
  pi_count = (tempD * HALF_CLK);

  // PI (1/64 MCLK, 1 PIs)
  // BL0 -> B0DLLPICODER0[21:16] (0x00-0x3F)
  // BL1 -> B1DLLPICODER0[21:16] (0x00-0x3F)
  reg = (byte_lane & BIT0) ? (B1DLLPICODER0) : (B0DLLPICODER0);
  reg += (((byte_lane >> 1) * DDRIODQ_BL_OFFSET) + (channel * DDRIODQ_CH_OFFSET));
  tempD = isbR32m(DDRPHY, reg);
  tempD >>= 16;
  tempD &= 0x3F;

  // Adjust PI_COUNT
  pi_count += tempD;

  LEAVEFN();
  return pi_count;
}

// set_wdq:
//
// This function will program the WDQ delays based on an absolute number of PIs.
// (currently doesn't comprehend rank)
void set_wdq(
    uint8_t channel,
    uint8_t rank,
    uint8_t byte_lane,
    uint32_t pi_count)
{
  uint32_t reg;
  uint32_t msk;
  uint32_t tempD;

  ENTERFN();
  DPF(D_TRN, "Wdq ch%d rnk%d ln%d : pi=%03X\n", channel, rank, byte_lane, pi_count);

  // RDPTR (1/2 MCLK, 64 PIs)
  // BL0 -> B01PTRCTL0[03:00] (0x0-0xF)
  // BL1 -> B01PTRCTL0[15:12] (0x0-0xF)
  reg = B01PTRCTL0 + ((byte_lane >> 1) * DDRIODQ_BL_OFFSET) + (channel * DDRIODQ_CH_OFFSET);
  msk = (byte_lane & BIT0) ? (BIT15 | BIT14 | BIT13 | BIT12) : (BIT3 | BIT2 | BIT1 | BIT0);
  tempD = pi_count / HALF_CLK;
  tempD <<= (byte_lane & BIT0) ? (12) : (0);
  isbM32m(DDRPHY, reg, tempD, msk);

  // Adjust PI_COUNT
  pi_count -= ((pi_count / HALF_CLK) & 0xF) * HALF_CLK;

  // PI (1/64 MCLK, 1 PIs)
  // BL0 -> B0DLLPICODER0[13:08] (0x00-0x3F)
  // BL1 -> B1DLLPICODER0[13:08] (0x00-0x3F)
  reg = (byte_lane & BIT0) ? (B1DLLPICODER0) : (B0DLLPICODER0);
  reg += (((byte_lane >> 1) * DDRIODQ_BL_OFFSET) + (channel * DDRIODQ_CH_OFFSET));
  msk = (BIT13 | BIT12 | BIT11 | BIT10 | BIT9 | BIT8);
  tempD = pi_count << 8;
  isbM32m(DDRPHY, reg, tempD, msk);

  // DEADBAND
  // BL0/1 -> B01DBCTL1[06/09] (+1 select)
  // BL0/1 -> B01DBCTL1[00/03] (enable)
  reg = B01DBCTL1 + ((byte_lane >> 1) * DDRIODQ_BL_OFFSET) + (channel * DDRIODQ_CH_OFFSET);
  msk = 0x00;
  tempD = 0x00;
  // enable
  msk |= (byte_lane & BIT0) ? (BIT3) : (BIT0);
  if ((pi_count < EARLY_DB) || (pi_count > LATE_DB))
  {
    tempD |= msk;
  }
  // select
  msk |= (byte_lane & BIT0) ? (BIT9) : (BIT6);
  if (pi_count < EARLY_DB)
  {
    tempD |= msk;
  }
  isbM32m(DDRPHY, reg, tempD, msk);

  // error check
  if (pi_count > 0x3F)
  {
    training_message(channel, rank, byte_lane);
    post_code(0xEE, 0xE3);
  }

  LEAVEFN();
  return;
}

// get_wdq:
//
// This function will return the amount of WDQ delay on the given channel, rank, byte_lane as an absolute PI count.
// (currently doesn't comprehend rank)
uint32_t get_wdq(
    uint8_t channel,
    uint8_t rank,
    uint8_t byte_lane)
{
  uint32_t reg;
  uint32_t tempD;
  uint32_t pi_count;

  ENTERFN();

  // RDPTR (1/2 MCLK, 64 PIs)
  // BL0 -> B01PTRCTL0[03:00] (0x0-0xF)
  // BL1 -> B01PTRCTL0[15:12] (0x0-0xF)
  reg = B01PTRCTL0 + ((byte_lane >> 1) * DDRIODQ_BL_OFFSET) + (channel * DDRIODQ_CH_OFFSET);
  tempD = isbR32m(DDRPHY, reg);
  tempD >>= (byte_lane & BIT0) ? (12) : (0);
  tempD &= 0xF;

  // Adjust PI_COUNT
  pi_count = (tempD * HALF_CLK);

  // PI (1/64 MCLK, 1 PIs)
  // BL0 -> B0DLLPICODER0[13:08] (0x00-0x3F)
  // BL1 -> B1DLLPICODER0[13:08] (0x00-0x3F)
  reg = (byte_lane & BIT0) ? (B1DLLPICODER0) : (B0DLLPICODER0);
  reg += (((byte_lane >> 1) * DDRIODQ_BL_OFFSET) + (channel * DDRIODQ_CH_OFFSET));
  tempD = isbR32m(DDRPHY, reg);
  tempD >>= 8;
  tempD &= 0x3F;

  // Adjust PI_COUNT
  pi_count += tempD;

  LEAVEFN();
  return pi_count;
}

// set_wcmd:
//
// This function will program the WCMD delays based on an absolute number of PIs.
void set_wcmd(
    uint8_t channel,
    uint32_t pi_count)
{
  uint32_t reg;
  uint32_t msk;
  uint32_t tempD;

  ENTERFN();
  // RDPTR (1/2 MCLK, 64 PIs)
  // CMDPTRREG[11:08] (0x0-0xF)
  reg = CMDPTRREG + (channel * DDRIOCCC_CH_OFFSET);
  msk = (BIT11 | BIT10 | BIT9 | BIT8);
  tempD = pi_count / HALF_CLK;
  tempD <<= 8;
  isbM32m(DDRPHY, reg, tempD, msk);

  // Adjust PI_COUNT
  pi_count -= ((pi_count / HALF_CLK) & 0xF) * HALF_CLK;

  // PI (1/64 MCLK, 1 PIs)
  // CMDDLLPICODER0[29:24] -> CMDSLICE R3 (unused)
  // CMDDLLPICODER0[21:16] -> CMDSLICE L3 (unused)
  // CMDDLLPICODER0[13:08] -> CMDSLICE R2 (unused)
  // CMDDLLPICODER0[05:00] -> CMDSLICE L2 (unused)
  // CMDDLLPICODER1[29:24] -> CMDSLICE R1 (unused)
  // CMDDLLPICODER1[21:16] -> CMDSLICE L1 (0x00-0x3F)
  // CMDDLLPICODER1[13:08] -> CMDSLICE R0 (unused)
  // CMDDLLPICODER1[05:00] -> CMDSLICE L0 (unused)
  reg = CMDDLLPICODER1 + (channel * DDRIOCCC_CH_OFFSET);

  msk = (BIT29 | BIT28 | BIT27 | BIT26 | BIT25 | BIT24) | (BIT21 | BIT20 | BIT19 | BIT18 | BIT17 | BIT16)
      | (BIT13 | BIT12 | BIT11 | BIT10 | BIT9 | BIT8) | (BIT5 | BIT4 | BIT3 | BIT2 | BIT1 | BIT0);

  tempD = (pi_count << 24) | (pi_count << 16) | (pi_count << 8) | (pi_count << 0);

  isbM32m(DDRPHY, reg, tempD, msk);
  reg = CMDDLLPICODER0 + (channel * DDRIOCCC_CH_OFFSET); // PO
  isbM32m(DDRPHY, reg, tempD, msk);

  // DEADBAND
  // CMDCFGREG0[17] (+1 select)
  // CMDCFGREG0[16] (enable)
  reg = CMDCFGREG0 + (channel * DDRIOCCC_CH_OFFSET);
  msk = 0x00;
  tempD = 0x00;
  // enable
  msk |= BIT16;
  if ((pi_count < EARLY_DB) || (pi_count > LATE_DB))
  {
    tempD |= msk;
  }
  // select
  msk |= BIT17;
  if (pi_count < EARLY_DB)
  {
    tempD |= msk;
  }
  isbM32m(DDRPHY, reg, tempD, msk);

  // error check
  if (pi_count > 0x3F)
  {
    post_code(0xEE, 0xE4);
  }

  LEAVEFN();
  return;
}

// get_wcmd:
//
// This function will return the amount of WCMD delay on the given channel as an absolute PI count.
uint32_t get_wcmd(
    uint8_t channel)
{
  uint32_t reg;
  uint32_t tempD;
  uint32_t pi_count;

  ENTERFN();
  // RDPTR (1/2 MCLK, 64 PIs)
  // CMDPTRREG[11:08] (0x0-0xF)
  reg = CMDPTRREG + (channel * DDRIOCCC_CH_OFFSET);
  tempD = isbR32m(DDRPHY, reg);
  tempD >>= 8;
  tempD &= 0xF;

  // Adjust PI_COUNT
  pi_count = tempD * HALF_CLK;

  // PI (1/64 MCLK, 1 PIs)
  // CMDDLLPICODER0[29:24] -> CMDSLICE R3 (unused)
  // CMDDLLPICODER0[21:16] -> CMDSLICE L3 (unused)
  // CMDDLLPICODER0[13:08] -> CMDSLICE R2 (unused)
  // CMDDLLPICODER0[05:00] -> CMDSLICE L2 (unused)
  // CMDDLLPICODER1[29:24] -> CMDSLICE R1 (unused)
  // CMDDLLPICODER1[21:16] -> CMDSLICE L1 (0x00-0x3F)
  // CMDDLLPICODER1[13:08] -> CMDSLICE R0 (unused)
  // CMDDLLPICODER1[05:00] -> CMDSLICE L0 (unused)
  reg = CMDDLLPICODER1 + (channel * DDRIOCCC_CH_OFFSET);
  tempD = isbR32m(DDRPHY, reg);
  tempD >>= 16;
  tempD &= 0x3F;

  // Adjust PI_COUNT
  pi_count += tempD;

  LEAVEFN();
  return pi_count;
}

// set_wclk:
//
// This function will program the WCLK delays based on an absolute number of PIs.
void set_wclk(
    uint8_t channel,
    uint8_t rank,
    uint32_t pi_count)
{
  uint32_t reg;
  uint32_t msk;
  uint32_t tempD;

  ENTERFN();
  // RDPTR (1/2 MCLK, 64 PIs)
  // CCPTRREG[15:12] -> CLK1 (0x0-0xF)
  // CCPTRREG[11:08] -> CLK0 (0x0-0xF)
  reg = CCPTRREG + (channel * DDRIOCCC_CH_OFFSET);
  msk = (BIT15 | BIT14 | BIT13 | BIT12) | (BIT11 | BIT10 | BIT9 | BIT8);
  tempD = ((pi_count / HALF_CLK) << 12) | ((pi_count / HALF_CLK) << 8);
  isbM32m(DDRPHY, reg, tempD, msk);

  // Adjust PI_COUNT
  pi_count -= ((pi_count / HALF_CLK) & 0xF) * HALF_CLK;

  // PI (1/64 MCLK, 1 PIs)
  // ECCB1DLLPICODER0[13:08] -> CLK0 (0x00-0x3F)
  // ECCB1DLLPICODER0[21:16] -> CLK1 (0x00-0x3F)
  reg = (rank) ? (ECCB1DLLPICODER0) : (ECCB1DLLPICODER0);
  reg += (channel * DDRIOCCC_CH_OFFSET);
  msk = (BIT21 | BIT20 | BIT19 | BIT18 | BIT17 | BIT16) | (BIT13 | BIT12 | BIT11 | BIT10 | BIT9 | BIT8);
  tempD = (pi_count << 16) | (pi_count << 8);
  isbM32m(DDRPHY, reg, tempD, msk);
  reg = (rank) ? (ECCB1DLLPICODER1) : (ECCB1DLLPICODER1);
  reg += (channel * DDRIOCCC_CH_OFFSET);
  isbM32m(DDRPHY, reg, tempD, msk);
  reg = (rank) ? (ECCB1DLLPICODER2) : (ECCB1DLLPICODER2);
  reg += (channel * DDRIOCCC_CH_OFFSET);
  isbM32m(DDRPHY, reg, tempD, msk);
  reg = (rank) ? (ECCB1DLLPICODER3) : (ECCB1DLLPICODER3);
  reg += (channel * DDRIOCCC_CH_OFFSET);
  isbM32m(DDRPHY, reg, tempD, msk);

  // DEADBAND
  // CCCFGREG1[11:08] (+1 select)
  // CCCFGREG1[03:00] (enable)
  reg = CCCFGREG1 + (channel * DDRIOCCC_CH_OFFSET);
  msk = 0x00;
  tempD = 0x00;
  // enable
  msk |= (BIT3 | BIT2 | BIT1 | BIT0); // only ??? matters
  if ((pi_count < EARLY_DB) || (pi_count > LATE_DB))
  {
    tempD |= msk;
  }
  // select
  msk |= (BIT11 | BIT10 | BIT9 | BIT8); // only ??? matters
  if (pi_count < EARLY_DB)
  {
    tempD |= msk;
  }
  isbM32m(DDRPHY, reg, tempD, msk);

  // error check
  if (pi_count > 0x3F)
  {
    post_code(0xEE, 0xE5);
  }

  LEAVEFN();
  return;
}

// get_wclk:
//
// This function will return the amout of WCLK delay on the given channel, rank as an absolute PI count.
uint32_t get_wclk(
    uint8_t channel,
    uint8_t rank)
{
  uint32_t reg;
  uint32_t tempD;
  uint32_t pi_count;

  ENTERFN();
  // RDPTR (1/2 MCLK, 64 PIs)
  // CCPTRREG[15:12] -> CLK1 (0x0-0xF)
  // CCPTRREG[11:08] -> CLK0 (0x0-0xF)
  reg = CCPTRREG + (channel * DDRIOCCC_CH_OFFSET);
  tempD = isbR32m(DDRPHY, reg);
  tempD >>= (rank) ? (12) : (8);
  tempD &= 0xF;

  // Adjust PI_COUNT
  pi_count = tempD * HALF_CLK;

  // PI (1/64 MCLK, 1 PIs)
  // ECCB1DLLPICODER0[13:08] -> CLK0 (0x00-0x3F)
  // ECCB1DLLPICODER0[21:16] -> CLK1 (0x00-0x3F)
  reg = (rank) ? (ECCB1DLLPICODER0) : (ECCB1DLLPICODER0);
  reg += (channel * DDRIOCCC_CH_OFFSET);
  tempD = isbR32m(DDRPHY, reg);
  tempD >>= (rank) ? (16) : (8);
  tempD &= 0x3F;

  pi_count += tempD;

  LEAVEFN();
  return pi_count;
}

// set_wctl:
//
// This function will program the WCTL delays based on an absolute number of PIs.
// (currently doesn't comprehend rank)
void set_wctl(
    uint8_t channel,
    uint8_t rank,
    uint32_t pi_count)
{
  uint32_t reg;
  uint32_t msk;
  uint32_t tempD;

  ENTERFN();

  // RDPTR (1/2 MCLK, 64 PIs)
  // CCPTRREG[31:28] (0x0-0xF)
  // CCPTRREG[27:24] (0x0-0xF)
  reg = CCPTRREG + (channel * DDRIOCCC_CH_OFFSET);
  msk = (BIT31 | BIT30 | BIT29 | BIT28) | (BIT27 | BIT26 | BIT25 | BIT24);
  tempD = ((pi_count / HALF_CLK) << 28) | ((pi_count / HALF_CLK) << 24);
  isbM32m(DDRPHY, reg, tempD, msk);

  // Adjust PI_COUNT
  pi_count -= ((pi_count / HALF_CLK) & 0xF) * HALF_CLK;

  // PI (1/64 MCLK, 1 PIs)
  // ECCB1DLLPICODER?[29:24] (0x00-0x3F)
  // ECCB1DLLPICODER?[29:24] (0x00-0x3F)
  reg = ECCB1DLLPICODER0 + (channel * DDRIOCCC_CH_OFFSET);
  msk = (BIT29 | BIT28 | BIT27 | BIT26 | BIT25 | BIT24);
  tempD = (pi_count << 24);
  isbM32m(DDRPHY, reg, tempD, msk);
  reg = ECCB1DLLPICODER1 + (channel * DDRIOCCC_CH_OFFSET);
  isbM32m(DDRPHY, reg, tempD, msk);
  reg = ECCB1DLLPICODER2 + (channel * DDRIOCCC_CH_OFFSET);
  isbM32m(DDRPHY, reg, tempD, msk);
  reg = ECCB1DLLPICODER3 + (channel * DDRIOCCC_CH_OFFSET);
  isbM32m(DDRPHY, reg, tempD, msk);

  // DEADBAND
  // CCCFGREG1[13:12] (+1 select)
  // CCCFGREG1[05:04] (enable)
  reg = CCCFGREG1 + (channel * DDRIOCCC_CH_OFFSET);
  msk = 0x00;
  tempD = 0x00;
  // enable
  msk |= (BIT5 | BIT4); // only ??? matters
  if ((pi_count < EARLY_DB) || (pi_count > LATE_DB))
  {
    tempD |= msk;
  }
  // select
  msk |= (BIT13 | BIT12); // only ??? matters
  if (pi_count < EARLY_DB)
  {
    tempD |= msk;
  }
  isbM32m(DDRPHY, reg, tempD, msk);

  // error check
  if (pi_count > 0x3F)
  {
    post_code(0xEE, 0xE6);
  }

  LEAVEFN();
  return;
}

// get_wctl:
//
// This function will return the amount of WCTL delay on the given channel, rank as an absolute PI count.
// (currently doesn't comprehend rank)
uint32_t get_wctl(
    uint8_t channel,
    uint8_t rank)
{
  uint32_t reg;
  uint32_t tempD;
  uint32_t pi_count;

  ENTERFN();

  // RDPTR (1/2 MCLK, 64 PIs)
  // CCPTRREG[31:28] (0x0-0xF)
  // CCPTRREG[27:24] (0x0-0xF)
  reg = CCPTRREG + (channel * DDRIOCCC_CH_OFFSET);
  tempD = isbR32m(DDRPHY, reg);
  tempD >>= 24;
  tempD &= 0xF;

  // Adjust PI_COUNT
  pi_count = tempD * HALF_CLK;

  // PI (1/64 MCLK, 1 PIs)
  // ECCB1DLLPICODER?[29:24] (0x00-0x3F)
  // ECCB1DLLPICODER?[29:24] (0x00-0x3F)
  reg = ECCB1DLLPICODER0 + (channel * DDRIOCCC_CH_OFFSET);
  tempD = isbR32m(DDRPHY, reg);
  tempD >>= 24;
  tempD &= 0x3F;

  // Adjust PI_COUNT
  pi_count += tempD;

  LEAVEFN();
  return pi_count;
}

// set_vref:
//
// This function will program the internal Vref setting in a given byte lane in a given channel.
void set_vref(
    uint8_t channel,
    uint8_t byte_lane,
    uint32_t setting)
{
  uint32_t reg = (byte_lane & 0x1) ? (B1VREFCTL) : (B0VREFCTL);

  ENTERFN();
  DPF(D_TRN, "Vref ch%d ln%d : val=%03X\n", channel, byte_lane, setting);

  isbM32m(DDRPHY, (reg + (channel * DDRIODQ_CH_OFFSET) + ((byte_lane >> 1) * DDRIODQ_BL_OFFSET)),
      (vref_codes[setting] << 2), (BIT7 | BIT6 | BIT5 | BIT4 | BIT3 | BIT2));
  //isbM32m(DDRPHY, (reg + (channel * DDRIODQ_CH_OFFSET) + ((byte_lane >> 1) * DDRIODQ_BL_OFFSET)), (setting<<2), (BIT7|BIT6|BIT5|BIT4|BIT3|BIT2));
  // need to wait ~300ns for Vref to settle (check that this is necessary)
  delay_n(300);
  // ??? may need to clear pointers ???
  LEAVEFN();
  return;
}

// get_vref:
//
// This function will return the internal Vref setting for the given channel, byte_lane;
uint32_t get_vref(
    uint8_t channel,
    uint8_t byte_lane)
{
  uint8_t j;
  uint32_t ret_val = sizeof(vref_codes) / 2;
  uint32_t reg = (byte_lane & 0x1) ? (B1VREFCTL) : (B0VREFCTL);

  uint32_t tempD;

  ENTERFN();
  tempD = isbR32m(DDRPHY, (reg + (channel * DDRIODQ_CH_OFFSET) + ((byte_lane >> 1) * DDRIODQ_BL_OFFSET)));
  tempD >>= 2;
  tempD &= 0x3F;
  for (j = 0; j < sizeof(vref_codes); j++)
  {
    if (vref_codes[j] == tempD)
    {
      ret_val = j;
      break;
    }
  }
  LEAVEFN();
  return ret_val;
}

// clear_pointers:
//
// This function will be used to clear the pointers in a given byte lane in a given channel.
void clear_pointers(
    void)
{
  uint8_t channel_i;
  uint8_t bl_i;

  ENTERFN();
  for (channel_i = 0; channel_i < NUM_CHANNELS; channel_i++)
  {
    for (bl_i = 0; bl_i < NUM_BYTE_LANES; bl_i++)
    {
      isbM32m(DDRPHY, (B01PTRCTL1 + (channel_i * DDRIODQ_CH_OFFSET) + ((bl_i >> 1) * DDRIODQ_BL_OFFSET)), ~(BIT8),
          (BIT8));
      //delay_m(1); // DEBUG
      isbM32m(DDRPHY, (B01PTRCTL1 + (channel_i * DDRIODQ_CH_OFFSET) + ((bl_i >> 1) * DDRIODQ_BL_OFFSET)), (BIT8),
          (BIT8));
    }
  }
  LEAVEFN();
  return;
}

// void enable_cache:
void enable_cache(
    void)
{
  // Cache control not used in Quark MRC
  return;
}

// void disable_cache:
void disable_cache(
    void)
{
  // Cache control not used in Quark MRC
  return;
}

// Send DRAM command, data should be formated
// using DCMD_Xxxx macro or emrsXCommand structure.
static void dram_init_command(
    uint32_t data)
{
  Wr32(DCMD, 0, data);
}

// find_rising_edge:
//
// This function will find the rising edge transition on RCVN or WDQS.
void find_rising_edge(
    MRCParams_t *mrc_params,
    uint32_t delay[],
    uint8_t channel,
    uint8_t rank,
    bool rcvn)
{

#define SAMPLE_CNT 3   // number of sample points
#define SAMPLE_DLY 26  // number of PIs to increment per sample
#define FORWARD true   // indicates to increase delays when looking for edge
#define BACKWARD false // indicates to decrease delays when looking for edge

  bool all_edges_found; // determines stop condition
  bool direction[NUM_BYTE_LANES]; // direction indicator
  uint8_t sample_i; // sample counter
  uint8_t bl_i; // byte lane counter
  uint8_t bl_divisor = (mrc_params->channel_width == x16) ? 2 : 1; // byte lane divisor
  uint32_t sample_result[SAMPLE_CNT]; // results of "sample_dqs()"
  uint32_t tempD; // temporary DWORD
  uint32_t transition_pattern;

  ENTERFN();

  // select hte and request initial configuration
  select_hte(mrc_params);
  first_run = 1;

  // Take 3 sample points (T1,T2,T3) to obtain a transition pattern.
  for (sample_i = 0; sample_i < SAMPLE_CNT; sample_i++)
  {
    // program the desired delays for sample
    for (bl_i = 0; bl_i < (NUM_BYTE_LANES / bl_divisor); bl_i++)
    {
      // increase sample delay by 26 PI (0.2 CLK)
      if (rcvn)
      {
        set_rcvn(channel, rank, bl_i, delay[bl_i] + (sample_i * SAMPLE_DLY));
      }
      else
      {
        set_wdqs(channel, rank, bl_i, delay[bl_i] + (sample_i * SAMPLE_DLY));
      }
    } // bl_i loop
    // take samples (Tsample_i)
    sample_result[sample_i] = sample_dqs(mrc_params, channel, rank, rcvn);

    DPF(D_TRN, "Find rising edge %s ch%d rnk%d: #%d dly=%d dqs=%02X\n",
        (rcvn ? "RCVN" : "WDQS"), channel, rank,
        sample_i, sample_i * SAMPLE_DLY, sample_result[sample_i]);

  } // sample_i loop

  // This pattern will help determine where we landed and ultimately how to place RCVEN/WDQS.
  for (bl_i = 0; bl_i < (NUM_BYTE_LANES / bl_divisor); bl_i++)
  {
    // build "transition_pattern" (MSB is 1st sample)
    transition_pattern = 0x00;
    for (sample_i = 0; sample_i < SAMPLE_CNT; sample_i++)
    {
      transition_pattern |= ((sample_result[sample_i] & (1 << bl_i)) >> bl_i) << (SAMPLE_CNT - 1 - sample_i);
    } // sample_i loop

    DPF(D_TRN, "=== transition pattern %d\n", transition_pattern);

    // set up to look for rising edge based on "transition_pattern"
    switch (transition_pattern)
    {
    case 0x00: // sampled 0->0->0
      // move forward from T3 looking for 0->1
      delay[bl_i] += 2 * SAMPLE_DLY;
      direction[bl_i] = FORWARD;
      break;
    case 0x01: // sampled 0->0->1
    case 0x05: // sampled 1->0->1 (bad duty cycle) *HSD#237503*
      // move forward from T2 looking for 0->1
      delay[bl_i] += 1 * SAMPLE_DLY;
      direction[bl_i] = FORWARD;
      break;
// HSD#237503
//      case 0x02: // sampled 0->1->0 (bad duty cycle)
//        training_message(channel, rank, bl_i);
//        post_code(0xEE, 0xE8);
//        break;
    case 0x02: // sampled 0->1->0 (bad duty cycle) *HSD#237503*
    case 0x03: // sampled 0->1->1
      // move forward from T1 looking for 0->1
      delay[bl_i] += 0 * SAMPLE_DLY;
      direction[bl_i] = FORWARD;
      break;
    case 0x04: // sampled 1->0->0 (assumes BL8, HSD#234975)
      // move forward from T3 looking for 0->1
      delay[bl_i] += 2 * SAMPLE_DLY;
      direction[bl_i] = FORWARD;
      break;
// HSD#237503
//      case 0x05: // sampled 1->0->1 (bad duty cycle)
//        training_message(channel, rank, bl_i);
//        post_code(0xEE, 0xE9);
//        break;
    case 0x06: // sampled 1->1->0
    case 0x07: // sampled 1->1->1
      // move backward from T1 looking for 1->0
      delay[bl_i] += 0 * SAMPLE_DLY;
      direction[bl_i] = BACKWARD;
      break;
    default:
      post_code(0xEE, 0xEE);
      break;
    } // transition_pattern switch
    // program delays
    if (rcvn)
    {
      set_rcvn(channel, rank, bl_i, delay[bl_i]);
    }
    else
    {
      set_wdqs(channel, rank, bl_i, delay[bl_i]);
    }
  } // bl_i loop

  // Based on the observed transition pattern on the byte lane,
  // begin looking for a rising edge with single PI granularity.
  do
  {
    all_edges_found = true; // assume all byte lanes passed
    tempD = sample_dqs(mrc_params, channel, rank, rcvn); // take a sample
    // check all each byte lane for proper edge
    for (bl_i = 0; bl_i < (NUM_BYTE_LANES / bl_divisor); bl_i++)
    {
      if (tempD & (1 << bl_i))
      {
        // sampled "1"
        if (direction[bl_i] == BACKWARD)
        {
          // keep looking for edge on this byte lane
          all_edges_found = false;
          delay[bl_i] -= 1;
          if (rcvn)
          {
            set_rcvn(channel, rank, bl_i, delay[bl_i]);
          }
          else
          {
            set_wdqs(channel, rank, bl_i, delay[bl_i]);
          }
        }
      }
      else
      {
        // sampled "0"
        if (direction[bl_i] == FORWARD)
        {
          // keep looking for edge on this byte lane
          all_edges_found = false;
          delay[bl_i] += 1;
          if (rcvn)
          {
            set_rcvn(channel, rank, bl_i, delay[bl_i]);
          }
          else
          {
            set_wdqs(channel, rank, bl_i, delay[bl_i]);
          }
        }
      }
    } // bl_i loop
  } while (!all_edges_found);

  // restore DDR idle state
  dram_init_command(DCMD_PREA(rank));

  DPF(D_TRN, "Delay %03X %03X %03X %03X\n",
      delay[0], delay[1], delay[2], delay[3]);

  LEAVEFN();
  return;
}

// sample_dqs:
//
// This function will sample the DQTRAINSTS registers in the given channel/rank SAMPLE_SIZE times looking for a valid '0' or '1'.
// It will return an encoded DWORD in which each bit corresponds to the sampled value on the byte lane.
uint32_t sample_dqs(
    MRCParams_t *mrc_params,
    uint8_t channel,
    uint8_t rank,
    bool rcvn)
{
  uint8_t j; // just a counter
  uint8_t bl_i; // which BL in the module (always 2 per module)
  uint8_t bl_grp; // which BL module
  uint8_t bl_divisor = (mrc_params->channel_width == x16) ? 2 : 1; // byte lane divisor
  uint32_t msk[2]; // BLx in module
  uint32_t sampled_val[SAMPLE_SIZE]; // DQTRAINSTS register contents for each sample
  uint32_t num_0s; // tracks the number of '0' samples
  uint32_t num_1s; // tracks the number of '1' samples
  uint32_t ret_val = 0x00; // assume all '0' samples
  uint32_t address = get_addr(mrc_params, channel, rank);

  // initialise "msk[]"
  msk[0] = (rcvn) ? (BIT1) : (BIT9); // BL0
  msk[1] = (rcvn) ? (BIT0) : (BIT8); // BL1


  // cycle through each byte lane group
  for (bl_grp = 0; bl_grp < (NUM_BYTE_LANES / bl_divisor) / 2; bl_grp++)
  {
    // take SAMPLE_SIZE samples
    for (j = 0; j < SAMPLE_SIZE; j++)
    {
      HteMemOp(address, first_run, rcvn?0:1);
      first_run = 0;

      // record the contents of the proper DQTRAINSTS register
      sampled_val[j] = isbR32m(DDRPHY, (DQTRAINSTS + (bl_grp * DDRIODQ_BL_OFFSET) + (channel * DDRIODQ_CH_OFFSET)));
    }
    // look for a majority value ( (SAMPLE_SIZE/2)+1 ) on the byte lane
    // and set that value in the corresponding "ret_val" bit
    for (bl_i = 0; bl_i < 2; bl_i++)
    {
      num_0s = 0x00; // reset '0' tracker for byte lane
      num_1s = 0x00; // reset '1' tracker for byte lane
      for (j = 0; j < SAMPLE_SIZE; j++)
      {
        if (sampled_val[j] & msk[bl_i])
        {
          num_1s++;
        }
        else
        {
          num_0s++;
        }
      }
      if (num_1s > num_0s)
      {
        ret_val |= (1 << (bl_i + (bl_grp * 2)));
      }
    }
  }

  // "ret_val.0" contains the status of BL0
  // "ret_val.1" contains the status of BL1
  // "ret_val.2" contains the status of BL2
  // etc.
  return ret_val;
}

// get_addr:
//
// This function will return a 32 bit address in the desired channel and rank.
uint32_t get_addr(
    MRCParams_t *mrc_params,
    uint8_t channel,
    uint8_t rank)
{
  uint32_t offset = 0x02000000; // 32MB

  // Begin product specific code
  if (channel > 0)
  {
    DPF(D_ERROR, "ILLEGAL CHANNEL\n");
    DEAD_LOOP();
  }

  if (rank > 1)
  {
    DPF(D_ERROR, "ILLEGAL RANK\n");
    DEAD_LOOP();
  }

  // use 256MB lowest density as per DRP == 0x0003
  offset += rank * (256 * 1024 * 1024);

  return offset;
}

// byte_lane_mask:
//
// This function will return a 32 bit mask that will be used to check for byte lane failures.
uint32_t byte_lane_mask(
    MRCParams_t *mrc_params)
{
  uint32_t j;
  uint32_t ret_val = 0x00;

  // set "ret_val" based on NUM_BYTE_LANES such that you will check only BL0 in "result"
  // (each bit in "result" represents a byte lane)
  for (j = 0; j < MAX_BYTE_LANES; j += NUM_BYTE_LANES)
  {
    ret_val |= (1 << ((j / NUM_BYTE_LANES) * NUM_BYTE_LANES));
  }

  // HSD#235037
  // need to adjust the mask for 16-bit mode
  if (mrc_params->channel_width == x16)
  {
    ret_val |= (ret_val << 2);
  }

  return ret_val;
}


// read_tsc:
//
// This function will do some assembly to return TSC register contents as a uint64_t.
uint64_t read_tsc(
    void)
{
  volatile uint64_t tsc;  // EDX:EAX

#if defined (SIM) || defined (GCC)
  volatile uint32_t tscH; // EDX
  volatile uint32_t tscL;// EAX

  asm("rdtsc":"=a"(tscL),"=d"(tscH));
  tsc = tscH;
  tsc = (tsc<<32)|tscL;
#else
  tsc = __rdtsc();
#endif

  return tsc;
}

// get_tsc_freq:
//
// This function returns the TSC frequency in MHz
uint32_t get_tsc_freq(
    void)
{
  static uint32_t freq[] =
  { 533, 400, 200, 100 };
  uint32_t fuse;
#if 0
  fuse = (isbR32m(FUSE, 0) >> 12) & (BIT1|BIT0);
#else
  // todo!!! Fixed 533MHz for emulation or debugging
  fuse = 0;
#endif
  return freq[fuse];
}

#ifndef SIM
// delay_n:
//
// This is a simple delay function.
// It takes "nanoseconds" as a parameter.
void delay_n(
    uint32_t nanoseconds)
{
  // 1000 MHz clock has 1ns period --> no conversion required
  uint64_t final_tsc = read_tsc();
  final_tsc += ((get_tsc_freq() * (nanoseconds)) / 1000);

  while (read_tsc() < final_tsc)
    ;
  return;
}
#endif

// delay_u:
//
// This is a simple delay function.
// It takes "microseconds as a parameter.
void delay_u(
    uint32_t microseconds)
{
  // 64 bit math is not an option, just use loops
  while (microseconds--)
  {
    delay_n(1000);
  }
  return;
}

// delay_m:
//
// This is a simple delay function.
// It takes "milliseconds" as a parameter.
void delay_m(
    uint32_t milliseconds)
{
  // 64 bit math is not an option, just use loops
  while (milliseconds--)
  {
    delay_u(1000);
  }
  return;
}

// delay_s:
//
// This is a simple delay function.
// It takes "seconds" as a parameter.
void delay_s(
    uint32_t seconds)
{
  // 64 bit math is not an option, just use loops
  while (seconds--)
  {
    delay_m(1000);
  }
  return;
}

// post_code:
//
// This function will output the POST CODE to the four 7-Segment LED displays.
void post_code(
    uint8_t major,
    uint8_t minor)
{
#ifdef EMU
  // Update global variable for execution tracking in debug env
  PostCode = ((major << 8) | minor);
#endif

  // send message to UART
  DPF(D_INFO, "POST: 0x%01X%02X\n", major, minor);

  // error check:
  if (major == 0xEE)
  {
    // todo!!! Consider updating error status and exit MRC
#ifdef SIM
    // enable Ctrl-C handling
    for(;;) delay_n(100);
#else
    DEAD_LOOP();
#endif
  }
}

void training_message(
    uint8_t channel,
    uint8_t rank,
    uint8_t byte_lane)
{
  // send message to UART
  DPF(D_INFO, "CH%01X RK%01X BL%01X\n", channel, rank, byte_lane);
  return;
}

void print_timings(
    MRCParams_t *mrc_params)
{
  uint8_t algo_i;
  uint8_t channel_i;
  uint8_t rank_i;
  uint8_t bl_i;
  uint8_t bl_divisor = (mrc_params->channel_width == x16) ? 2 : 1;

  DPF(D_INFO, "\n---------------------------");
  DPF(D_INFO, "\nALGO[CH:RK] BL0 BL1 BL2 BL3");
  DPF(D_INFO, "\n===========================");
  for (algo_i = 0; algo_i < eMAX_ALGOS; algo_i++)
  {
    for (channel_i = 0; channel_i < NUM_CHANNELS; channel_i++)
    {
      if (mrc_params->channel_enables & (1 << channel_i))
      {
        for (rank_i = 0; rank_i < NUM_RANKS; rank_i++)
        {
          if (mrc_params->rank_enables & (1 << rank_i))
          {
            switch (algo_i)
            {
            case eRCVN:
              DPF(D_INFO, "\nRCVN[%02d:%02d]", channel_i, rank_i);
              break;
            case eWDQS:
              DPF(D_INFO, "\nWDQS[%02d:%02d]", channel_i, rank_i);
              break;
            case eWDQx:
              DPF(D_INFO, "\nWDQx[%02d:%02d]", channel_i, rank_i);
              break;
            case eRDQS:
              DPF(D_INFO, "\nRDQS[%02d:%02d]", channel_i, rank_i);
              break;
            case eVREF:
              DPF(D_INFO, "\nVREF[%02d:%02d]", channel_i, rank_i);
              break;
            case eWCMD:
              DPF(D_INFO, "\nWCMD[%02d:%02d]", channel_i, rank_i);
              break;
            case eWCTL:
              DPF(D_INFO, "\nWCTL[%02d:%02d]", channel_i, rank_i);
              break;
            case eWCLK:
              DPF(D_INFO, "\nWCLK[%02d:%02d]", channel_i, rank_i);
              break;
            default:
              break;
            } // algo_i switch
            for (bl_i = 0; bl_i < (NUM_BYTE_LANES / bl_divisor); bl_i++)
            {
              switch (algo_i)
              {
              case eRCVN:
                DPF(D_INFO, " %03d", get_rcvn(channel_i, rank_i, bl_i));
                break;
              case eWDQS:
                DPF(D_INFO, " %03d", get_wdqs(channel_i, rank_i, bl_i));
                break;
              case eWDQx:
                DPF(D_INFO, " %03d", get_wdq(channel_i, rank_i, bl_i));
                break;
              case eRDQS:
                DPF(D_INFO, " %03d", get_rdqs(channel_i, rank_i, bl_i));
                break;
              case eVREF:
                DPF(D_INFO, " %03d", get_vref(channel_i, bl_i));
                break;
              case eWCMD:
                DPF(D_INFO, " %03d", get_wcmd(channel_i));
                break;
              case eWCTL:
                DPF(D_INFO, " %03d", get_wctl(channel_i, rank_i));
                break;
              case eWCLK:
                DPF(D_INFO, " %03d", get_wclk(channel_i, rank_i));
                break;
              default:
                break;
              } // algo_i switch
            } // bl_i loop
          } // if rank_i enabled
        } // rank_i loop
      } // if channel_i enabled
    } // channel_i loop
  } // algo_i loop
  DPF(D_INFO, "\n---------------------------");
  DPF(D_INFO, "\n");
  return;
}

// 32 bit LFSR with characteristic polynomial:  X^32 + X^22 +X^2 + X^1
// The function takes pointer to previous 32 bit value and modifies it to next value.
void lfsr32(
    uint32_t *lfsr_ptr)
{
  uint32_t bit;
  uint32_t lfsr;
  uint32_t i;

  lfsr = *lfsr_ptr;

  for (i = 0; i < 32; i++)
  {
    bit = 1 ^ (lfsr & BIT0);
    bit = bit ^ ((lfsr & BIT1) >> 1);
    bit = bit ^ ((lfsr & BIT2) >> 2);
    bit = bit ^ ((lfsr & BIT22) >> 22);

    lfsr = ((lfsr >> 1) | (bit << 31));
  }

  *lfsr_ptr = lfsr;
  return;
}

// The purpose of this function is to ensure the SEC comes out of reset
// and IA initiates the SEC enabling Memory Scrambling.
void enable_scrambling(
    MRCParams_t *mrc_params)
{
  uint32_t lfsr = 0;
  uint8_t i;

  if (mrc_params->scrambling_enables == 0)
    return;

  ENTERFN();

  // 32 bit seed is always stored in BIOS NVM.
  lfsr = mrc_params->timings.scrambler_seed;

  if (mrc_params->boot_mode == bmCold)
  {
    // factory value is 0 and in first boot, a clock based seed is loaded.
    if (lfsr == 0)
    {
      lfsr = read_tsc() & 0x0FFFFFFF; // get seed from system clock and make sure it is not all 1's
    }
    // need to replace scrambler
    // get next 32bit LFSR 16 times which is the last part of the previous scrambler vector.
    else
    {
      for (i = 0; i < 16; i++)
      {
        lfsr32(&lfsr);
      }
    }
    mrc_params->timings.scrambler_seed = lfsr;  // save new seed.
  } // if (cold_boot)

  // In warm boot or S3 exit, we have the previous seed.
  // In cold boot, we have the last 32bit LFSR which is the new seed.
  lfsr32(&lfsr); // shift to next value
  isbW32m(MCU, SCRMSEED, (lfsr & 0x0003FFFF));
  for (i = 0; i < 2; i++)
  {
    isbW32m(MCU, SCRMLO + i, (lfsr & 0xAAAAAAAA));
  }

  LEAVEFN();
  return;
}

// This function will store relevant timing data
// This data will be used on subsequent boots to speed up boot times
// and is required for Suspend To RAM capabilities.
void store_timings(
    MRCParams_t *mrc_params)
{
  uint8_t ch, rk, bl;
  MrcTimings_t *mt = &mrc_params->timings;

  for (ch = 0; ch < NUM_CHANNELS; ch++)
  {
    for (rk = 0; rk < NUM_RANKS; rk++)
    {
      for (bl = 0; bl < NUM_BYTE_LANES; bl++)
      {
        mt->rcvn[ch][rk][bl] = get_rcvn(ch, rk, bl); // RCVN
        mt->rdqs[ch][rk][bl] = get_rdqs(ch, rk, bl); // RDQS
        mt->wdqs[ch][rk][bl] = get_wdqs(ch, rk, bl); // WDQS
        mt->wdq[ch][rk][bl] = get_wdq(ch, rk, bl);  // WDQ
        if (rk == 0)
        {
          mt->vref[ch][bl] = get_vref(ch, bl);  // VREF (RANK0 only)
        }
      }
      mt->wctl[ch][rk] = get_wctl(ch, rk); // WCTL
    }
    mt->wcmd[ch] = get_wcmd(ch); // WCMD
  }

  // need to save for a case of changing frequency after warm reset
  mt->ddr_speed = mrc_params->ddr_speed;

  return;
}

// This function will retrieve relevant timing data
// This data will be used on subsequent boots to speed up boot times
// and is required for Suspend To RAM capabilities.
void restore_timings(
    MRCParams_t *mrc_params)
{
  uint8_t ch, rk, bl;
  const MrcTimings_t *mt = &mrc_params->timings;

  for (ch = 0; ch < NUM_CHANNELS; ch++)
  {
    for (rk = 0; rk < NUM_RANKS; rk++)
    {
      for (bl = 0; bl < NUM_BYTE_LANES; bl++)
      {
        set_rcvn(ch, rk, bl, mt->rcvn[ch][rk][bl]); // RCVN
        set_rdqs(ch, rk, bl, mt->rdqs[ch][rk][bl]); // RDQS
        set_wdqs(ch, rk, bl, mt->wdqs[ch][rk][bl]); // WDQS
        set_wdq(ch, rk, bl, mt->wdq[ch][rk][bl]);  // WDQ
        if (rk == 0)
        {
          set_vref(ch, bl, mt->vref[ch][bl]); // VREF (RANK0 only)
        }
      }
      set_wctl(ch, rk, mt->wctl[ch][rk]); // WCTL
    }
    set_wcmd(ch, mt->wcmd[ch]); // WCMD
  }

  return;
}

// Configure default settings normally set as part of read training
// Some defaults have to be set earlier as they may affect earlier
// training steps.
void default_timings(
    MRCParams_t *mrc_params)
{
  uint8_t ch, rk, bl;

  for (ch = 0; ch < NUM_CHANNELS; ch++)
  {
    for (rk = 0; rk < NUM_RANKS; rk++)
    {
      for (bl = 0; bl < NUM_BYTE_LANES; bl++)
      {
        set_rdqs(ch, rk, bl, 24); // RDQS
        if (rk == 0)
        {
          set_vref(ch, bl, 32); // VREF (RANK0 only)
        }
      }
    }
  }

  return;
}

