Changes for V4 ============== 1) Remove Unicode character from C source file 2) Move delete of QuarkSocPkg\QuarkNorthCluster\Binary\QuarkMicrocode from QuarkPlatformPkg commit to QuarkSocPkg commit Changes for V2 ============== 1) Sync with new APIs in SmmCpuFeaturesLib class 2) Use new generic PCI serial driver PciSioSerialDxe in MdeModulePkg 3) Remove PCI serial driver from QuarkSocPkg 4) Apply optimizations to MtrrLib from MtrrLib in UefiCpuPkg 5) Convert all UNI files to utf-8 6) Replace tabs with spaces and remove trailing spaces 7) Add License.txt Contributed-under: TianoCore Contribution Agreement 1.0 Signed-off-by: Michael Kinney <michael.d.kinney@intel.com> Acked-by: Jordan Justen <jordan.l.justen@intel.com> git-svn-id: https://svn.code.sf.net/p/edk2/code/trunk/edk2@19286 6f19259b-4bc3-4df7-8a09-765794883524
1581 lines
43 KiB
C
1581 lines
43 KiB
C
/************************************************************************
|
|
*
|
|
* 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;
|
|
}
|
|
|