mirror of
				https://github.com/smaeul/u-boot.git
				synced 2025-10-31 12:08:19 +00:00 
			
		
		
		
	This patch cleans up the quark MRC codes coding style by: - Remove BIT0/1../31 defines from mrc_util.h - Create names for the documented BITs and use them - For undocumented single BITs, use (1 << n) directly - For undocumented ORed BITs, use the hex number directly - Remove redundancy parenthesis all over the codes - Replace to use lower case hex numbers Signed-off-by: Bin Meng <bmeng.cn@gmail.com>
		
			
				
	
	
		
			1472 lines
		
	
	
		
			34 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1472 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 &= ~DCO_PMICTL;
 | |
| 	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 |= DCO_PMICTL;
 | |
| 	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 & 1) ? 0xf00000 : 0xf00;
 | |
| 	temp = (byte_lane & 1) ? (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 & 1) ? B1DLLPICODER0 : B0DLLPICODER0;
 | |
| 	reg += ((byte_lane >> 1) * DDRIODQ_BL_OFFSET +
 | |
| 		channel * DDRIODQ_CH_OFFSET);
 | |
| 	msk = 0x3f000000;
 | |
| 	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 & 1) ? (1 << 5) : (1 << 2);
 | |
| 	if ((pi_count < EARLY_DB) || (pi_count > LATE_DB))
 | |
| 		temp |= msk;
 | |
| 
 | |
| 	/* select */
 | |
| 	msk |= (byte_lane & 1) ? (1 << 11) : (1 << 8);
 | |
| 	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 & 1) ? 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 & 1) ? 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 & 1) ? B1RXDQSPICODE : B0RXDQSPICODE;
 | |
| 	reg += ((byte_lane >> 1) * DDRIODQ_BL_OFFSET +
 | |
| 		channel * DDRIODQ_CH_OFFSET);
 | |
| 	msk = 0x7f;
 | |
| 	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 & 1) ? 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 & 1) ? 0xf0000 : 0xf0;
 | |
| 	temp = pi_count / HALF_CLK;
 | |
| 	temp <<= (byte_lane & 1) ? 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 & 1) ? B1DLLPICODER0 : B0DLLPICODER0;
 | |
| 	reg += ((byte_lane >> 1) * DDRIODQ_BL_OFFSET +
 | |
| 		channel * DDRIODQ_CH_OFFSET);
 | |
| 	msk = 0x3f0000;
 | |
| 	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 & 1) ? (1 << 4) : (1 << 1);
 | |
| 	if ((pi_count < EARLY_DB) || (pi_count > LATE_DB))
 | |
| 		temp |= msk;
 | |
| 
 | |
| 	/* select */
 | |
| 	msk |= (byte_lane & 1) ? (1 << 10) : (1 << 7);
 | |
| 	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 & 1) ? 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 & 1) ? 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 & 1) ? 0xf000 : 0xf;
 | |
| 	temp = pi_count / HALF_CLK;
 | |
| 	temp <<= (byte_lane & 1) ? 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 & 1) ? B1DLLPICODER0 : B0DLLPICODER0;
 | |
| 	reg += ((byte_lane >> 1) * DDRIODQ_BL_OFFSET +
 | |
| 		channel * DDRIODQ_CH_OFFSET);
 | |
| 	msk = 0x3f00;
 | |
| 	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 & 1) ? (1 << 3) : (1 << 0);
 | |
| 	if ((pi_count < EARLY_DB) || (pi_count > LATE_DB))
 | |
| 		temp |= msk;
 | |
| 
 | |
| 	/* select */
 | |
| 	msk |= (byte_lane & 1) ? (1 << 9) : (1 << 6);
 | |
| 	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 & 1) ? 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 & 1) ? 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 = 0xf00;
 | |
| 	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 = 0x3f3f3f3f;
 | |
| 	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 |= (1 << 16);
 | |
| 	if ((pi_count < EARLY_DB) || (pi_count > LATE_DB))
 | |
| 		temp |= msk;
 | |
| 
 | |
| 	/* select */
 | |
| 	msk |= (1 << 17);
 | |
| 	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 = 0xff00;
 | |
| 	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 = 0x3f3f00;
 | |
| 	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 |= 0xf;
 | |
| 	if ((pi_count < EARLY_DB) || (pi_count > LATE_DB))
 | |
| 		temp |= msk;
 | |
| 
 | |
| 	/* select */
 | |
| 	msk |= 0xf00;
 | |
| 	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 = 0xff000000;
 | |
| 	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 = 0x3f000000;
 | |
| 	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 |= 0x30;
 | |
| 	if ((pi_count < EARLY_DB) || (pi_count > LATE_DB))
 | |
| 		temp |= msk;
 | |
| 
 | |
| 	/* select */
 | |
| 	msk |= 0x3000;
 | |
| 	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, 0xfc);
 | |
| 
 | |
| 	/*
 | |
| 	 * 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 = 32 * 1024 * 1024;	/* 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 ? (1 << 1) : (1 << 9);	/* BL0 */
 | |
| 	msk[1] = rcvn ? (1 << 0) : (1 << 8);	/* 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 & 1);
 | |
| 		bit = bit ^ ((lfsr & 2) >> 1);
 | |
| 		bit = bit ^ ((lfsr & 4) >> 2);
 | |
| 		bit = bit ^ ((lfsr & 0x400000) >> 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,
 | |
| 					   ~(1 << 8), (1 << 8));
 | |
| 
 | |
| 			mrc_alt_write_mask(DDRPHY,
 | |
| 					   B01PTRCTL1 +
 | |
| 					   channel * DDRIODQ_CH_OFFSET +
 | |
| 					   (bl >> 1) * DDRIODQ_BL_OFFSET,
 | |
| 					   (1 << 8), (1 << 8));
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	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");
 | |
| }
 |