mirror of
https://github.com/AsahiLinux/u-boot
synced 2025-01-24 02:45:10 +00:00
38ad43e436
Add various utility codes needed for Quark MRC. Signed-off-by: Bin Meng <bmeng.cn@gmail.com> Acked-by: Simon Glass <sjg@chromium.org>
1475 lines
34 KiB
C
1475 lines
34 KiB
C
/*
|
|
* Copyright (C) 2013, Intel Corporation
|
|
* Copyright (C) 2015, Bin Meng <bmeng.cn@gmail.com>
|
|
*
|
|
* Ported from Intel released Quark UEFI BIOS
|
|
* QuarkSocPkg/QuarkNorthCluster/MemoryInit/Pei
|
|
*
|
|
* SPDX-License-Identifier: Intel
|
|
*/
|
|
|
|
#include <common.h>
|
|
#include <asm/arch/device.h>
|
|
#include <asm/arch/mrc.h>
|
|
#include <asm/arch/msg_port.h>
|
|
#include "mrc_util.h"
|
|
#include "hte.h"
|
|
#include "smc.h"
|
|
|
|
static 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,
|
|
0x2F, 0x2E, 0x2D, 0x2C, 0x2B, 0x2A, 0x29, 0x28,
|
|
0x27, 0x26, 0x25, 0x24, 0x23, 0x22, 0x21, 0x20,
|
|
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
|
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
|
|
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
|
|
0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F
|
|
};
|
|
|
|
void mrc_write_mask(u32 unit, u32 addr, u32 data, u32 mask)
|
|
{
|
|
msg_port_write(unit, addr,
|
|
(msg_port_read(unit, addr) & ~(mask)) |
|
|
((data) & (mask)));
|
|
}
|
|
|
|
void mrc_alt_write_mask(u32 unit, u32 addr, u32 data, u32 mask)
|
|
{
|
|
msg_port_alt_write(unit, addr,
|
|
(msg_port_alt_read(unit, addr) & ~(mask)) |
|
|
((data) & (mask)));
|
|
}
|
|
|
|
void mrc_post_code(uint8_t major, uint8_t minor)
|
|
{
|
|
/* send message to UART */
|
|
DPF(D_INFO, "POST: 0x%01x%02x\n", major, minor);
|
|
|
|
/* error check */
|
|
if (major == 0xee)
|
|
hang();
|
|
}
|
|
|
|
/* Delay number of nanoseconds */
|
|
void delay_n(uint32_t ns)
|
|
{
|
|
/* 1000 MHz clock has 1ns period --> no conversion required */
|
|
uint64_t final_tsc = rdtsc();
|
|
|
|
final_tsc += ((get_tbclk_mhz() * ns) / 1000);
|
|
|
|
while (rdtsc() < final_tsc)
|
|
;
|
|
}
|
|
|
|
/* Delay number of microseconds */
|
|
void delay_u(uint32_t ms)
|
|
{
|
|
/* 64-bit math is not an option, just use loops */
|
|
while (ms--)
|
|
delay_n(1000);
|
|
}
|
|
|
|
/* Select Memory Manager as the source for PRI interface */
|
|
void select_mem_mgr(void)
|
|
{
|
|
u32 dco;
|
|
|
|
ENTERFN();
|
|
|
|
dco = msg_port_read(MEM_CTLR, DCO);
|
|
dco &= ~BIT28;
|
|
msg_port_write(MEM_CTLR, DCO, dco);
|
|
|
|
LEAVEFN();
|
|
}
|
|
|
|
/* Select HTE as the source for PRI interface */
|
|
void select_hte(void)
|
|
{
|
|
u32 dco;
|
|
|
|
ENTERFN();
|
|
|
|
dco = msg_port_read(MEM_CTLR, DCO);
|
|
dco |= BIT28;
|
|
msg_port_write(MEM_CTLR, DCO, dco);
|
|
|
|
LEAVEFN();
|
|
}
|
|
|
|
/*
|
|
* Send DRAM command
|
|
* data should be formated using DCMD_Xxxx macro or emrsXCommand structure
|
|
*/
|
|
void dram_init_command(uint32_t data)
|
|
{
|
|
pci_write_config_dword(QUARK_HOST_BRIDGE, MSG_DATA_REG, data);
|
|
pci_write_config_dword(QUARK_HOST_BRIDGE, MSG_CTRL_EXT_REG, 0);
|
|
msg_port_setup(MSG_OP_DRAM_INIT, MEM_CTLR, 0);
|
|
|
|
DPF(D_REGWR, "WR32 %03X %08X %08X\n", MEM_CTLR, 0, data);
|
|
}
|
|
|
|
/* Send DRAM wake command using special MCU side-band WAKE opcode */
|
|
void dram_wake_command(void)
|
|
{
|
|
ENTERFN();
|
|
|
|
msg_port_setup(MSG_OP_DRAM_WAKE, MEM_CTLR, 0);
|
|
|
|
LEAVEFN();
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
/*
|
|
* 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 temp;
|
|
|
|
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);
|
|
temp = (byte_lane & BIT0) ? ((pi_count / HALF_CLK) << 20) :
|
|
((pi_count / HALF_CLK) << 8);
|
|
mrc_alt_write_mask(DDRPHY, reg, temp, 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);
|
|
temp = pi_count << 24;
|
|
mrc_alt_write_mask(DDRPHY, reg, temp, 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;
|
|
temp = 0x00;
|
|
|
|
/* enable */
|
|
msk |= (byte_lane & BIT0) ? BIT5 : BIT2;
|
|
if ((pi_count < EARLY_DB) || (pi_count > LATE_DB))
|
|
temp |= msk;
|
|
|
|
/* select */
|
|
msk |= (byte_lane & BIT0) ? BIT11 : BIT8;
|
|
if (pi_count < EARLY_DB)
|
|
temp |= msk;
|
|
|
|
mrc_alt_write_mask(DDRPHY, reg, temp, msk);
|
|
|
|
/* error check */
|
|
if (pi_count > 0x3F) {
|
|
training_message(channel, rank, byte_lane);
|
|
mrc_post_code(0xee, 0xe0);
|
|
}
|
|
|
|
LEAVEFN();
|
|
}
|
|
|
|
/*
|
|
* 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 temp;
|
|
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);
|
|
temp = msg_port_alt_read(DDRPHY, reg);
|
|
temp >>= (byte_lane & BIT0) ? 20 : 8;
|
|
temp &= 0xF;
|
|
|
|
/* Adjust PI_COUNT */
|
|
pi_count = temp * 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));
|
|
temp = msg_port_alt_read(DDRPHY, reg);
|
|
temp >>= 24;
|
|
temp &= 0x3F;
|
|
|
|
/* Adjust PI_COUNT */
|
|
pi_count += temp;
|
|
|
|
LEAVEFN();
|
|
|
|
return pi_count;
|
|
}
|
|
|
|
/*
|
|
* 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 temp;
|
|
|
|
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);
|
|
temp = pi_count << 0;
|
|
mrc_alt_write_mask(DDRPHY, reg, temp, msk);
|
|
|
|
/* error check (shouldn't go above 0x3F) */
|
|
if (pi_count > 0x47) {
|
|
training_message(channel, rank, byte_lane);
|
|
mrc_post_code(0xee, 0xe1);
|
|
}
|
|
|
|
LEAVEFN();
|
|
}
|
|
|
|
/*
|
|
* 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 temp;
|
|
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));
|
|
temp = msg_port_alt_read(DDRPHY, reg);
|
|
|
|
/* Adjust PI_COUNT */
|
|
pi_count = temp & 0x7F;
|
|
|
|
LEAVEFN();
|
|
|
|
return pi_count;
|
|
}
|
|
|
|
/*
|
|
* 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 temp;
|
|
|
|
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);
|
|
temp = pi_count / HALF_CLK;
|
|
temp <<= (byte_lane & BIT0) ? 16 : 4;
|
|
mrc_alt_write_mask(DDRPHY, reg, temp, 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);
|
|
temp = pi_count << 16;
|
|
mrc_alt_write_mask(DDRPHY, reg, temp, 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;
|
|
temp = 0x00;
|
|
|
|
/* enable */
|
|
msk |= (byte_lane & BIT0) ? BIT4 : BIT1;
|
|
if ((pi_count < EARLY_DB) || (pi_count > LATE_DB))
|
|
temp |= msk;
|
|
|
|
/* select */
|
|
msk |= (byte_lane & BIT0) ? BIT10 : BIT7;
|
|
if (pi_count < EARLY_DB)
|
|
temp |= msk;
|
|
|
|
mrc_alt_write_mask(DDRPHY, reg, temp, msk);
|
|
|
|
/* error check */
|
|
if (pi_count > 0x3F) {
|
|
training_message(channel, rank, byte_lane);
|
|
mrc_post_code(0xee, 0xe2);
|
|
}
|
|
|
|
LEAVEFN();
|
|
}
|
|
|
|
/*
|
|
* 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 temp;
|
|
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);
|
|
temp = msg_port_alt_read(DDRPHY, reg);
|
|
temp >>= (byte_lane & BIT0) ? 16 : 4;
|
|
temp &= 0xF;
|
|
|
|
/* Adjust PI_COUNT */
|
|
pi_count = (temp * 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));
|
|
temp = msg_port_alt_read(DDRPHY, reg);
|
|
temp >>= 16;
|
|
temp &= 0x3F;
|
|
|
|
/* Adjust PI_COUNT */
|
|
pi_count += temp;
|
|
|
|
LEAVEFN();
|
|
|
|
return pi_count;
|
|
}
|
|
|
|
/*
|
|
* 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 temp;
|
|
|
|
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);
|
|
temp = pi_count / HALF_CLK;
|
|
temp <<= (byte_lane & BIT0) ? 12 : 0;
|
|
mrc_alt_write_mask(DDRPHY, reg, temp, 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);
|
|
temp = pi_count << 8;
|
|
mrc_alt_write_mask(DDRPHY, reg, temp, 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;
|
|
temp = 0x00;
|
|
|
|
/* enable */
|
|
msk |= (byte_lane & BIT0) ? BIT3 : BIT0;
|
|
if ((pi_count < EARLY_DB) || (pi_count > LATE_DB))
|
|
temp |= msk;
|
|
|
|
/* select */
|
|
msk |= (byte_lane & BIT0) ? BIT9 : BIT6;
|
|
if (pi_count < EARLY_DB)
|
|
temp |= msk;
|
|
|
|
mrc_alt_write_mask(DDRPHY, reg, temp, msk);
|
|
|
|
/* error check */
|
|
if (pi_count > 0x3F) {
|
|
training_message(channel, rank, byte_lane);
|
|
mrc_post_code(0xee, 0xe3);
|
|
}
|
|
|
|
LEAVEFN();
|
|
}
|
|
|
|
/*
|
|
* 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 temp;
|
|
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);
|
|
temp = msg_port_alt_read(DDRPHY, reg);
|
|
temp >>= (byte_lane & BIT0) ? (12) : (0);
|
|
temp &= 0xF;
|
|
|
|
/* Adjust PI_COUNT */
|
|
pi_count = temp * 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));
|
|
temp = msg_port_alt_read(DDRPHY, reg);
|
|
temp >>= 8;
|
|
temp &= 0x3F;
|
|
|
|
/* Adjust PI_COUNT */
|
|
pi_count += temp;
|
|
|
|
LEAVEFN();
|
|
|
|
return pi_count;
|
|
}
|
|
|
|
/*
|
|
* 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 temp;
|
|
|
|
ENTERFN();
|
|
|
|
/*
|
|
* RDPTR (1/2 MCLK, 64 PIs)
|
|
* CMDPTRREG[11:08] (0x0-0xF)
|
|
*/
|
|
reg = CMDPTRREG + (channel * DDRIOCCC_CH_OFFSET);
|
|
msk = (BIT11 | BIT10 | BIT9 | BIT8);
|
|
temp = pi_count / HALF_CLK;
|
|
temp <<= 8;
|
|
mrc_alt_write_mask(DDRPHY, reg, temp, 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);
|
|
|
|
temp = (pi_count << 24) | (pi_count << 16) |
|
|
(pi_count << 8) | (pi_count << 0);
|
|
|
|
mrc_alt_write_mask(DDRPHY, reg, temp, msk);
|
|
reg = CMDDLLPICODER0 + (channel * DDRIOCCC_CH_OFFSET); /* PO */
|
|
mrc_alt_write_mask(DDRPHY, reg, temp, msk);
|
|
|
|
/*
|
|
* DEADBAND
|
|
* CMDCFGREG0[17] (+1 select)
|
|
* CMDCFGREG0[16] (enable)
|
|
*/
|
|
reg = CMDCFGREG0 + (channel * DDRIOCCC_CH_OFFSET);
|
|
msk = 0x00;
|
|
temp = 0x00;
|
|
|
|
/* enable */
|
|
msk |= BIT16;
|
|
if ((pi_count < EARLY_DB) || (pi_count > LATE_DB))
|
|
temp |= msk;
|
|
|
|
/* select */
|
|
msk |= BIT17;
|
|
if (pi_count < EARLY_DB)
|
|
temp |= msk;
|
|
|
|
mrc_alt_write_mask(DDRPHY, reg, temp, msk);
|
|
|
|
/* error check */
|
|
if (pi_count > 0x3F)
|
|
mrc_post_code(0xee, 0xe4);
|
|
|
|
LEAVEFN();
|
|
}
|
|
|
|
/*
|
|
* 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 temp;
|
|
uint32_t pi_count;
|
|
|
|
ENTERFN();
|
|
|
|
/*
|
|
* RDPTR (1/2 MCLK, 64 PIs)
|
|
* CMDPTRREG[11:08] (0x0-0xF)
|
|
*/
|
|
reg = CMDPTRREG + (channel * DDRIOCCC_CH_OFFSET);
|
|
temp = msg_port_alt_read(DDRPHY, reg);
|
|
temp >>= 8;
|
|
temp &= 0xF;
|
|
|
|
/* Adjust PI_COUNT */
|
|
pi_count = temp * 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);
|
|
temp = msg_port_alt_read(DDRPHY, reg);
|
|
temp >>= 16;
|
|
temp &= 0x3F;
|
|
|
|
/* Adjust PI_COUNT */
|
|
pi_count += temp;
|
|
|
|
LEAVEFN();
|
|
|
|
return pi_count;
|
|
}
|
|
|
|
/*
|
|
* 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 temp;
|
|
|
|
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);
|
|
temp = ((pi_count / HALF_CLK) << 12) | ((pi_count / HALF_CLK) << 8);
|
|
mrc_alt_write_mask(DDRPHY, reg, temp, 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);
|
|
temp = (pi_count << 16) | (pi_count << 8);
|
|
mrc_alt_write_mask(DDRPHY, reg, temp, msk);
|
|
reg = rank ? ECCB1DLLPICODER1 : ECCB1DLLPICODER1;
|
|
reg += (channel * DDRIOCCC_CH_OFFSET);
|
|
mrc_alt_write_mask(DDRPHY, reg, temp, msk);
|
|
reg = rank ? ECCB1DLLPICODER2 : ECCB1DLLPICODER2;
|
|
reg += (channel * DDRIOCCC_CH_OFFSET);
|
|
mrc_alt_write_mask(DDRPHY, reg, temp, msk);
|
|
reg = rank ? ECCB1DLLPICODER3 : ECCB1DLLPICODER3;
|
|
reg += (channel * DDRIOCCC_CH_OFFSET);
|
|
mrc_alt_write_mask(DDRPHY, reg, temp, msk);
|
|
|
|
/*
|
|
* DEADBAND
|
|
* CCCFGREG1[11:08] (+1 select)
|
|
* CCCFGREG1[03:00] (enable)
|
|
*/
|
|
reg = CCCFGREG1 + (channel * DDRIOCCC_CH_OFFSET);
|
|
msk = 0x00;
|
|
temp = 0x00;
|
|
|
|
/* enable */
|
|
msk |= (BIT3 | BIT2 | BIT1 | BIT0);
|
|
if ((pi_count < EARLY_DB) || (pi_count > LATE_DB))
|
|
temp |= msk;
|
|
|
|
/* select */
|
|
msk |= (BIT11 | BIT10 | BIT9 | BIT8);
|
|
if (pi_count < EARLY_DB)
|
|
temp |= msk;
|
|
|
|
mrc_alt_write_mask(DDRPHY, reg, temp, msk);
|
|
|
|
/* error check */
|
|
if (pi_count > 0x3F)
|
|
mrc_post_code(0xee, 0xe5);
|
|
|
|
LEAVEFN();
|
|
}
|
|
|
|
/*
|
|
* 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 temp;
|
|
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);
|
|
temp = msg_port_alt_read(DDRPHY, reg);
|
|
temp >>= rank ? 12 : 8;
|
|
temp &= 0xF;
|
|
|
|
/* Adjust PI_COUNT */
|
|
pi_count = temp * 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);
|
|
temp = msg_port_alt_read(DDRPHY, reg);
|
|
temp >>= rank ? 16 : 8;
|
|
temp &= 0x3F;
|
|
|
|
pi_count += temp;
|
|
|
|
LEAVEFN();
|
|
|
|
return pi_count;
|
|
}
|
|
|
|
/*
|
|
* 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 temp;
|
|
|
|
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);
|
|
temp = ((pi_count / HALF_CLK) << 28) | ((pi_count / HALF_CLK) << 24);
|
|
mrc_alt_write_mask(DDRPHY, reg, temp, 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);
|
|
temp = (pi_count << 24);
|
|
mrc_alt_write_mask(DDRPHY, reg, temp, msk);
|
|
reg = ECCB1DLLPICODER1 + (channel * DDRIOCCC_CH_OFFSET);
|
|
mrc_alt_write_mask(DDRPHY, reg, temp, msk);
|
|
reg = ECCB1DLLPICODER2 + (channel * DDRIOCCC_CH_OFFSET);
|
|
mrc_alt_write_mask(DDRPHY, reg, temp, msk);
|
|
reg = ECCB1DLLPICODER3 + (channel * DDRIOCCC_CH_OFFSET);
|
|
mrc_alt_write_mask(DDRPHY, reg, temp, msk);
|
|
|
|
/*
|
|
* DEADBAND
|
|
* CCCFGREG1[13:12] (+1 select)
|
|
* CCCFGREG1[05:04] (enable)
|
|
*/
|
|
reg = CCCFGREG1 + (channel * DDRIOCCC_CH_OFFSET);
|
|
msk = 0x00;
|
|
temp = 0x00;
|
|
|
|
/* enable */
|
|
msk |= (BIT5 | BIT4);
|
|
if ((pi_count < EARLY_DB) || (pi_count > LATE_DB))
|
|
temp |= msk;
|
|
|
|
/* select */
|
|
msk |= (BIT13 | BIT12);
|
|
if (pi_count < EARLY_DB)
|
|
temp |= msk;
|
|
|
|
mrc_alt_write_mask(DDRPHY, reg, temp, msk);
|
|
|
|
/* error check */
|
|
if (pi_count > 0x3F)
|
|
mrc_post_code(0xee, 0xe6);
|
|
|
|
LEAVEFN();
|
|
}
|
|
|
|
/*
|
|
* 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 temp;
|
|
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);
|
|
temp = msg_port_alt_read(DDRPHY, reg);
|
|
temp >>= 24;
|
|
temp &= 0xF;
|
|
|
|
/* Adjust PI_COUNT */
|
|
pi_count = temp * HALF_CLK;
|
|
|
|
/*
|
|
* PI (1/64 MCLK, 1 PIs)
|
|
* ECCB1DLLPICODER?[29:24] (0x00-0x3F)
|
|
* ECCB1DLLPICODER?[29:24] (0x00-0x3F)
|
|
*/
|
|
reg = ECCB1DLLPICODER0 + (channel * DDRIOCCC_CH_OFFSET);
|
|
temp = msg_port_alt_read(DDRPHY, reg);
|
|
temp >>= 24;
|
|
temp &= 0x3F;
|
|
|
|
/* Adjust PI_COUNT */
|
|
pi_count += temp;
|
|
|
|
LEAVEFN();
|
|
|
|
return pi_count;
|
|
}
|
|
|
|
/*
|
|
* 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);
|
|
|
|
mrc_alt_write_mask(DDRPHY, (reg + (channel * DDRIODQ_CH_OFFSET) +
|
|
((byte_lane >> 1) * DDRIODQ_BL_OFFSET)),
|
|
(vref_codes[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();
|
|
}
|
|
|
|
/*
|
|
* 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 temp;
|
|
|
|
ENTERFN();
|
|
|
|
temp = msg_port_alt_read(DDRPHY, (reg + (channel * DDRIODQ_CH_OFFSET) +
|
|
((byte_lane >> 1) * DDRIODQ_BL_OFFSET)));
|
|
temp >>= 2;
|
|
temp &= 0x3F;
|
|
|
|
for (j = 0; j < sizeof(vref_codes); j++) {
|
|
if (vref_codes[j] == temp) {
|
|
ret_val = j;
|
|
break;
|
|
}
|
|
}
|
|
|
|
LEAVEFN();
|
|
|
|
return ret_val;
|
|
}
|
|
|
|
/*
|
|
* This function will return a 32-bit address in the desired
|
|
* channel and rank.
|
|
*/
|
|
uint32_t get_addr(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;
|
|
}
|
|
|
|
/*
|
|
* 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 32-bit date in which each bit corresponds to
|
|
* the sampled value on the byte lane.
|
|
*/
|
|
uint32_t sample_dqs(struct mrc_params *mrc_params, uint8_t channel,
|
|
uint8_t rank, bool rcvn)
|
|
{
|
|
uint8_t j; /* just a counter */
|
|
uint8_t bl; /* which BL in the module (always 2 per module) */
|
|
uint8_t bl_grp; /* which BL module */
|
|
/* byte lane divisor */
|
|
uint8_t bl_divisor = (mrc_params->channel_width == X16) ? 2 : 1;
|
|
uint32_t msk[2]; /* BLx in module */
|
|
/* DQTRAINSTS register contents for each sample */
|
|
uint32_t sampled_val[SAMPLE_SIZE];
|
|
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(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++) {
|
|
hte_mem_op(address, mrc_params->first_run,
|
|
rcvn ? 0 : 1);
|
|
mrc_params->first_run = 0;
|
|
|
|
/*
|
|
* record the contents of the proper
|
|
* DQTRAINSTS register
|
|
*/
|
|
sampled_val[j] = msg_port_alt_read(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 = 0; bl < 2; bl++) {
|
|
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])
|
|
num_1s++;
|
|
else
|
|
num_0s++;
|
|
}
|
|
if (num_1s > num_0s)
|
|
ret_val |= (1 << (bl + (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;
|
|
}
|
|
|
|
/* This function will find the rising edge transition on RCVN or WDQS */
|
|
void find_rising_edge(struct mrc_params *mrc_params, uint32_t delay[],
|
|
uint8_t channel, uint8_t rank, bool rcvn)
|
|
{
|
|
bool all_edges_found; /* determines stop condition */
|
|
bool direction[NUM_BYTE_LANES]; /* direction indicator */
|
|
uint8_t sample; /* sample counter */
|
|
uint8_t bl; /* byte lane counter */
|
|
/* byte lane divisor */
|
|
uint8_t bl_divisor = (mrc_params->channel_width == X16) ? 2 : 1;
|
|
uint32_t sample_result[SAMPLE_CNT]; /* results of sample_dqs() */
|
|
uint32_t temp;
|
|
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 = 0; sample < SAMPLE_CNT; sample++) {
|
|
/* program the desired delays for sample */
|
|
for (bl = 0; bl < (NUM_BYTE_LANES / bl_divisor); bl++) {
|
|
/* increase sample delay by 26 PI (0.2 CLK) */
|
|
if (rcvn) {
|
|
set_rcvn(channel, rank, bl,
|
|
delay[bl] + (sample * SAMPLE_DLY));
|
|
} else {
|
|
set_wdqs(channel, rank, bl,
|
|
delay[bl] + (sample * SAMPLE_DLY));
|
|
}
|
|
}
|
|
|
|
/* take samples (Tsample_i) */
|
|
sample_result[sample] = 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,
|
|
sample * SAMPLE_DLY, sample_result[sample]);
|
|
}
|
|
|
|
/*
|
|
* This pattern will help determine where we landed and ultimately
|
|
* how to place RCVEN/WDQS.
|
|
*/
|
|
for (bl = 0; bl < (NUM_BYTE_LANES / bl_divisor); bl++) {
|
|
/* build transition_pattern (MSB is 1st sample) */
|
|
transition_pattern = 0;
|
|
for (sample = 0; sample < SAMPLE_CNT; sample++) {
|
|
transition_pattern |=
|
|
((sample_result[sample] & (1 << bl)) >> bl) <<
|
|
(SAMPLE_CNT - 1 - sample);
|
|
}
|
|
|
|
DPF(D_TRN, "=== transition pattern %d\n", transition_pattern);
|
|
|
|
/*
|
|
* set up to look for rising edge based on
|
|
* transition_pattern
|
|
*/
|
|
switch (transition_pattern) {
|
|
case 0: /* sampled 0->0->0 */
|
|
/* move forward from T3 looking for 0->1 */
|
|
delay[bl] += 2 * SAMPLE_DLY;
|
|
direction[bl] = FORWARD;
|
|
break;
|
|
case 1: /* sampled 0->0->1 */
|
|
case 5: /* sampled 1->0->1 (bad duty cycle) *HSD#237503* */
|
|
/* move forward from T2 looking for 0->1 */
|
|
delay[bl] += 1 * SAMPLE_DLY;
|
|
direction[bl] = FORWARD;
|
|
break;
|
|
case 2: /* sampled 0->1->0 (bad duty cycle) *HSD#237503* */
|
|
case 3: /* sampled 0->1->1 */
|
|
/* move forward from T1 looking for 0->1 */
|
|
delay[bl] += 0 * SAMPLE_DLY;
|
|
direction[bl] = FORWARD;
|
|
break;
|
|
case 4: /* sampled 1->0->0 (assumes BL8, HSD#234975) */
|
|
/* move forward from T3 looking for 0->1 */
|
|
delay[bl] += 2 * SAMPLE_DLY;
|
|
direction[bl] = FORWARD;
|
|
break;
|
|
case 6: /* sampled 1->1->0 */
|
|
case 7: /* sampled 1->1->1 */
|
|
/* move backward from T1 looking for 1->0 */
|
|
delay[bl] += 0 * SAMPLE_DLY;
|
|
direction[bl] = BACKWARD;
|
|
break;
|
|
default:
|
|
mrc_post_code(0xee, 0xee);
|
|
break;
|
|
}
|
|
|
|
/* program delays */
|
|
if (rcvn)
|
|
set_rcvn(channel, rank, bl, delay[bl]);
|
|
else
|
|
set_wdqs(channel, rank, bl, delay[bl]);
|
|
}
|
|
|
|
/*
|
|
* 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 */
|
|
/* take a sample */
|
|
temp = sample_dqs(mrc_params, channel, rank, rcvn);
|
|
/* check all each byte lane for proper edge */
|
|
for (bl = 0; bl < (NUM_BYTE_LANES / bl_divisor); bl++) {
|
|
if (temp & (1 << bl)) {
|
|
/* sampled "1" */
|
|
if (direction[bl] == BACKWARD) {
|
|
/*
|
|
* keep looking for edge
|
|
* on this byte lane
|
|
*/
|
|
all_edges_found = false;
|
|
delay[bl] -= 1;
|
|
if (rcvn) {
|
|
set_rcvn(channel, rank,
|
|
bl, delay[bl]);
|
|
} else {
|
|
set_wdqs(channel, rank,
|
|
bl, delay[bl]);
|
|
}
|
|
}
|
|
} else {
|
|
/* sampled "0" */
|
|
if (direction[bl] == FORWARD) {
|
|
/*
|
|
* keep looking for edge
|
|
* on this byte lane
|
|
*/
|
|
all_edges_found = false;
|
|
delay[bl] += 1;
|
|
if (rcvn) {
|
|
set_rcvn(channel, rank,
|
|
bl, delay[bl]);
|
|
} else {
|
|
set_wdqs(channel, rank,
|
|
bl, delay[bl]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} 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();
|
|
}
|
|
|
|
/*
|
|
* This function will return a 32 bit mask that will be used to
|
|
* check for byte lane failures.
|
|
*/
|
|
uint32_t byte_lane_mask(struct mrc_params *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;
|
|
}
|
|
|
|
/*
|
|
* Check memory executing simple write/read/verify at the specified address.
|
|
*
|
|
* Bits in the result indicate failure on specific byte lane.
|
|
*/
|
|
uint32_t check_rw_coarse(struct mrc_params *mrc_params, uint32_t address)
|
|
{
|
|
uint32_t result = 0;
|
|
uint8_t first_run = 0;
|
|
|
|
if (mrc_params->hte_setup) {
|
|
mrc_params->hte_setup = 0;
|
|
first_run = 1;
|
|
select_hte();
|
|
}
|
|
|
|
result = hte_basic_write_read(mrc_params, address, first_run,
|
|
WRITE_TRAIN);
|
|
|
|
DPF(D_TRN, "check_rw_coarse result is %x\n", result);
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Check memory executing write/read/verify of many data patterns
|
|
* at the specified address. Bits in the result indicate failure
|
|
* on specific byte lane.
|
|
*/
|
|
uint32_t check_bls_ex(struct mrc_params *mrc_params, uint32_t address)
|
|
{
|
|
uint32_t result;
|
|
uint8_t first_run = 0;
|
|
|
|
if (mrc_params->hte_setup) {
|
|
mrc_params->hte_setup = 0;
|
|
first_run = 1;
|
|
select_hte();
|
|
}
|
|
|
|
result = hte_write_stress_bit_lanes(mrc_params, address, first_run);
|
|
|
|
DPF(D_TRN, "check_bls_ex result is %x\n", result);
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
int 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;
|
|
}
|
|
|
|
/* Clear the pointers in a given byte lane in a given channel */
|
|
void clear_pointers(void)
|
|
{
|
|
uint8_t channel;
|
|
uint8_t bl;
|
|
|
|
ENTERFN();
|
|
|
|
for (channel = 0; channel < NUM_CHANNELS; channel++) {
|
|
for (bl = 0; bl < NUM_BYTE_LANES; bl++) {
|
|
mrc_alt_write_mask(DDRPHY,
|
|
(B01PTRCTL1 +
|
|
(channel * DDRIODQ_CH_OFFSET) +
|
|
((bl >> 1) * DDRIODQ_BL_OFFSET)),
|
|
~BIT8, BIT8);
|
|
|
|
mrc_alt_write_mask(DDRPHY,
|
|
(B01PTRCTL1 +
|
|
(channel * DDRIODQ_CH_OFFSET) +
|
|
((bl >> 1) * DDRIODQ_BL_OFFSET)),
|
|
BIT8, BIT8);
|
|
}
|
|
}
|
|
|
|
LEAVEFN();
|
|
}
|
|
|
|
static void print_timings_internal(uint8_t algo, uint8_t channel, uint8_t rank,
|
|
uint8_t bl_divisor)
|
|
{
|
|
uint8_t bl;
|
|
|
|
switch (algo) {
|
|
case RCVN:
|
|
DPF(D_INFO, "\nRCVN[%02d:%02d]", channel, rank);
|
|
break;
|
|
case WDQS:
|
|
DPF(D_INFO, "\nWDQS[%02d:%02d]", channel, rank);
|
|
break;
|
|
case WDQX:
|
|
DPF(D_INFO, "\nWDQx[%02d:%02d]", channel, rank);
|
|
break;
|
|
case RDQS:
|
|
DPF(D_INFO, "\nRDQS[%02d:%02d]", channel, rank);
|
|
break;
|
|
case VREF:
|
|
DPF(D_INFO, "\nVREF[%02d:%02d]", channel, rank);
|
|
break;
|
|
case WCMD:
|
|
DPF(D_INFO, "\nWCMD[%02d:%02d]", channel, rank);
|
|
break;
|
|
case WCTL:
|
|
DPF(D_INFO, "\nWCTL[%02d:%02d]", channel, rank);
|
|
break;
|
|
case WCLK:
|
|
DPF(D_INFO, "\nWCLK[%02d:%02d]", channel, rank);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
for (bl = 0; bl < (NUM_BYTE_LANES / bl_divisor); bl++) {
|
|
switch (algo) {
|
|
case RCVN:
|
|
DPF(D_INFO, " %03d", get_rcvn(channel, rank, bl));
|
|
break;
|
|
case WDQS:
|
|
DPF(D_INFO, " %03d", get_wdqs(channel, rank, bl));
|
|
break;
|
|
case WDQX:
|
|
DPF(D_INFO, " %03d", get_wdq(channel, rank, bl));
|
|
break;
|
|
case RDQS:
|
|
DPF(D_INFO, " %03d", get_rdqs(channel, rank, bl));
|
|
break;
|
|
case VREF:
|
|
DPF(D_INFO, " %03d", get_vref(channel, bl));
|
|
break;
|
|
case WCMD:
|
|
DPF(D_INFO, " %03d", get_wcmd(channel));
|
|
break;
|
|
case WCTL:
|
|
DPF(D_INFO, " %03d", get_wctl(channel, rank));
|
|
break;
|
|
case WCLK:
|
|
DPF(D_INFO, " %03d", get_wclk(channel, rank));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void print_timings(struct mrc_params *mrc_params)
|
|
{
|
|
uint8_t algo;
|
|
uint8_t channel;
|
|
uint8_t rank;
|
|
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 = 0; algo < MAX_ALGOS; algo++) {
|
|
for (channel = 0; channel < NUM_CHANNELS; channel++) {
|
|
if (mrc_params->channel_enables & (1 << channel)) {
|
|
for (rank = 0; rank < NUM_RANKS; rank++) {
|
|
if (mrc_params->rank_enables &
|
|
(1 << rank)) {
|
|
print_timings_internal(algo,
|
|
channel, rank,
|
|
bl_divisor);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
DPF(D_INFO, "\n---------------------------");
|
|
DPF(D_INFO, "\n");
|
|
}
|