提交 548cc109 编写于 作者: L Lukasz Majewski 提交者: Stefano Babic

ddr: vybrid: Provide code to perform on-boot calibration

This patch provides the code to calibrate the DDR's
DQS to DQ signals (RDLVL).

It is based on:
VFxxx Controller Reference Manual, Rev. 0, 10/2016, page 1600
10.1.6.16.4.1 "Software Read Leveling in MC Evaluation Mode"

and NXP's community thread:
"Vybrid: About DDR leveling feature on DDRMC."
https://community.nxp.com/thread/395323Signed-off-by: NLukasz Majewski <lukma@denx.de>
上级 c5b22a53
...@@ -78,3 +78,15 @@ config NXP_BOARD_REVISION ...@@ -78,3 +78,15 @@ config NXP_BOARD_REVISION
NXP boards based on i.MX6/7 contain the board revision information NXP boards based on i.MX6/7 contain the board revision information
stored in the fuses. Select this option if you want to be able to stored in the fuses. Select this option if you want to be able to
retrieve the board revision information. retrieve the board revision information.
config DDRMC_VF610_CALIBRATION
bool "Enable DDRMC (DDR3) on-chip calibration"
depends on ARCH_VF610
help
Vybrid (vf610) SoC provides some on-chip facility to tune the DDR3
memory parameters. Select this option if you want to calculate them
at boot time.
NOTE:
NXP does NOT recommend to perform this calibration at each boot. One
shall perform it on a new PCB and then use those values to program
the ddrmc_cr_setting on relevant board file.
...@@ -53,6 +53,7 @@ obj-$(CONFIG_SECURE_BOOT) += hab.o ...@@ -53,6 +53,7 @@ obj-$(CONFIG_SECURE_BOOT) += hab.o
endif endif
ifeq ($(SOC),$(filter $(SOC),vf610)) ifeq ($(SOC),$(filter $(SOC),vf610))
obj-y += ddrmc-vf610.o obj-y += ddrmc-vf610.o
obj-$(CONFIG_DDRMC_VF610_CALIBRATION) += ddrmc-vf610-calibration.o
endif endif
ifneq ($(CONFIG_SPL_BUILD),y) ifneq ($(CONFIG_SPL_BUILD),y)
obj-$(CONFIG_CMD_BMODE) += cmd_bmode.o obj-$(CONFIG_CMD_BMODE) += cmd_bmode.o
......
// SPDX-License-Identifier: GPL-2.0+
/*
* ddrmc DDR3 calibration code for NXP's VF610
*
* Copyright (C) 2018 DENX Software Engineering
* Lukasz Majewski, DENX Software Engineering, lukma@denx.de
*
*/
/* #define DEBUG */
#include <common.h>
#include <asm/io.h>
#include <asm/arch/imx-regs.h>
#include <linux/bitmap.h>
#include "ddrmc-vf610-calibration.h"
/*
* Documents:
*
* [1] "Vybrid: About DDR leveling feature on DDRMC."
* https://community.nxp.com/thread/395323
*
* [2] VFxxx Controller Reference Manual, Rev. 0, 10/2016
*
*
* NOTE
* ====
*
* NXP recommends setting 'fixed' parameters instead of performing the
* training at each boot.
*
* Use those functions to determine those values on new HW, read the
* calculated value from registers and add them to the board specific
* struct ddrmc_cr_setting.
*
* SW leveling supported operations - CR93[SW_LVL_MODE]:
*
* - 0x0 (b'00) - No leveling
*
* - 0x1 (b'01) - WRLVL_DL_X - It is not recommended to perform this tuning
* on HW designs utilizing non-flyback topology
* (Single DDR3 with x16).
* Instead the WRLVL_DL_0/1 fields shall be set
* based on trace length differences from their
* layout.
* Mismatches up to 25% or tCK (clock period) are
* allowed, so the value in the filed doesn’t have
* to be very accurate.
*
* - 0x2 (b'10) - RDLVL_DL_0/1 - refers to adjusting the DQS strobe in relation
* to the DQ signals so that the strobe edge is
* centered in the window of valid read data.
*
* - 0x3 (b'11) - RDLVL_GTDL_0/1 - refers to the delay the PHY uses to un-gate
* the Read DQS strobe pad from the time that the
* PHY enables the pad to input the strobe signal.
*
*/
static int ddr_cal_get_first_edge_index(unsigned long *bmap, enum edge e,
int samples, int start, int max)
{
int i, ret = -1;
/*
* We look only for the first value (and filter out
* some wrong data)
*/
switch (e) {
case RISING_EDGE:
for (i = start; i <= max - samples; i++) {
if (test_bit(i, bmap)) {
if (!test_bit(i - 1, bmap) &&
test_bit(i + 1, bmap) &&
test_bit(i + 2, bmap) &&
test_bit(i + 3, bmap)) {
return i;
}
}
}
break;
case FALLING_EDGE:
for (i = start; i <= max - samples; i++) {
if (!test_bit(i, bmap)) {
if (test_bit(i - 1, bmap) &&
test_bit(i - 2, bmap) &&
test_bit(i - 3, bmap)) {
return i;
}
}
}
}
return ret;
}
static void bitmap_print(unsigned long *bmap, int max)
{
int i;
debug("BITMAP [0x%p]:\n", bmap);
for (i = 0; i <= max; i++) {
debug("%d ", test_bit(i, bmap) ? 1 : 0);
if (i && (i % 32) == (32 - 1))
debug("\n");
}
debug("\n");
}
#define sw_leveling_op_done \
while (!(readl(&ddrmr->cr[94]) & DDRMC_CR94_SWLVL_OP_DONE))
#define sw_leveling_load_value \
do { clrsetbits_le32(&ddrmr->cr[93], DDRMC_CR93_SWLVL_LOAD, \
DDRMC_CR93_SWLVL_LOAD); } while (0)
#define sw_leveling_start \
do { clrsetbits_le32(&ddrmr->cr[93], DDRMC_CR93_SWLVL_START, \
DDRMC_CR93_SWLVL_START); } while (0)
#define sw_leveling_exit \
do { clrsetbits_le32(&ddrmr->cr[94], DDRMC_CR94_SWLVL_EXIT, \
DDRMC_CR94_SWLVL_EXIT); } while (0)
/*
* RDLVL_DL calibration:
*
* NXP is _NOT_ recommending performing the leveling at each
* boot. Instead - one shall run this procedure on new boards
* and then use hardcoded values.
*
*/
static int ddrmc_cal_dqs_to_dq(struct ddrmr_regs *ddrmr)
{
DECLARE_BITMAP(rdlvl_rsp, DDRMC_DQS_DQ_MAX_DELAY + 1);
int rdlvl_dl_0_min = -1, rdlvl_dl_0_max = -1;
int rdlvl_dl_1_min = -1, rdlvl_dl_1_max = -1;
int rdlvl_dl_0, rdlvl_dl_1;
u8 swlvl_rsp;
u32 tmp;
int i;
/* Read defaults */
u16 rdlvl_dl_0_def =
(readl(&ddrmr->cr[105]) >> DDRMC_CR105_RDLVL_DL_0_OFF) & 0xFFFF;
u16 rdlvl_dl_1_def = readl(&ddrmr->cr[110]) & 0xFFFF;
debug("\nRDLVL: ======================\n");
debug("RDLVL: DQS to DQ (RDLVL)\n");
debug("RDLVL: RDLVL_DL_0_DFL:\t 0x%x\n", rdlvl_dl_0_def);
debug("RDLVL: RDLVL_DL_1_DFL:\t 0x%x\n", rdlvl_dl_1_def);
/*
* Set/Read setup for calibration
*
* Values necessary for leveling from Vybrid RM [2] - page 1600
*/
writel(0x40703030, &ddrmr->cr[144]);
writel(0x40, &ddrmr->cr[145]);
writel(0x40, &ddrmr->cr[146]);
tmp = readl(&ddrmr->cr[144]);
debug("RDLVL: PHY_RDLVL_RES:\t 0x%x\n", (tmp >> 24) & 0xFF);// set 0x40
debug("RDLVL: PHY_RDLV_LOAD:\t 0x%x\n", (tmp >> 16) & 0xFF);// set 0x70
debug("RDLVL: PHY_RDLV_DLL:\t 0x%x\n", (tmp >> 8) & 0xFF); // set 0x30
debug("RDLVL: PHY_RDLV_EN:\t 0x%x\n", tmp & 0xFF); //set 0x30
tmp = readl(&ddrmr->cr[145]);
debug("RDLVL: PHY_RDLV_RR:\t 0x%x\n", tmp & 0x3FF); //set 0x40
tmp = readl(&ddrmr->cr[146]);
debug("RDLVL: PHY_RDLV_RESP:\t 0x%x\n", tmp); //set 0x40
/*
* Program/read the leveling edge RDLVL_EDGE = 0
*
* 0x00 is the correct output on SWLVL_RSP_X
* If by any chance 1s are visible -> wrong number read
*/
clrbits_le32(&ddrmr->cr[101], DDRMC_CR101_PHY_RDLVL_EDGE);
tmp = readl(&ddrmr->cr[101]);
debug("RDLVL: PHY_RDLVL_EDGE:\t 0x%x\n",
(tmp >> DDRMC_CR101_PHY_RDLVL_EDGE_OFF) & 0x1); //set 0
/* Program Leveling mode - CR93[SW_LVL_MODE] to ’b10 */
clrsetbits_le32(&ddrmr->cr[93], DDRMC_CR93_SW_LVL_MODE(0x3),
DDRMC_CR93_SW_LVL_MODE(0x2));
tmp = readl(&ddrmr->cr[93]);
debug("RDLVL: SW_LVL_MODE:\t 0x%x\n",
(tmp >> DDRMC_CR93_SW_LVL_MODE_OFF) & 0x3);
/* Start procedure - CR93[SWLVL_START] to ’b1 */
sw_leveling_start;
/* Poll CR94[SWLVL_OP_DONE] */
sw_leveling_op_done;
/*
* Program delays for RDLVL_DL_0
*
* The procedure is to increase the delay values from 0 to 0xFF
* and read the response from the DDRMC
*/
debug("\nRDLVL: ---> RDLVL_DL_0\n");
bitmap_zero(rdlvl_rsp, DDRMC_DQS_DQ_MAX_DELAY + 1);
for (i = 0; i <= DDRMC_DQS_DQ_MAX_DELAY; i++) {
clrsetbits_le32(&ddrmr->cr[105],
0xFFFF << DDRMC_CR105_RDLVL_DL_0_OFF,
i << DDRMC_CR105_RDLVL_DL_0_OFF);
/* Load values CR93[SWLVL_LOAD] to ’b1 */
sw_leveling_load_value;
/* Poll CR94[SWLVL_OP_DONE] */
sw_leveling_op_done;
/*
* Read Responses - SWLVL_RESP_0
*
* The 0x00 (correct response when PHY_RDLVL_EDGE = 0)
* -> 1 in the bit vector
*/
swlvl_rsp = (readl(&ddrmr->cr[94]) >>
DDRMC_CR94_SWLVL_RESP_0_OFF) & 0xF;
if (swlvl_rsp == 0)
generic_set_bit(i, rdlvl_rsp);
}
bitmap_print(rdlvl_rsp, DDRMC_DQS_DQ_MAX_DELAY);
/*
* First test for rising edge 0x0 -> 0x1 in bitmap
*/
rdlvl_dl_0_min = ddr_cal_get_first_edge_index(rdlvl_rsp, RISING_EDGE,
N_SAMPLES, N_SAMPLES,
DDRMC_DQS_DQ_MAX_DELAY);
/*
* Secondly test for falling edge 0x1 -> 0x0 in bitmap
*/
rdlvl_dl_0_max = ddr_cal_get_first_edge_index(rdlvl_rsp, FALLING_EDGE,
N_SAMPLES, rdlvl_dl_0_min,
DDRMC_DQS_DQ_MAX_DELAY);
debug("RDLVL: DL_0 min: %d [0x%x] DL_0 max: %d [0x%x]\n",
rdlvl_dl_0_min, rdlvl_dl_0_min, rdlvl_dl_0_max, rdlvl_dl_0_max);
rdlvl_dl_0 = (rdlvl_dl_0_max - rdlvl_dl_0_min) / 2;
if (rdlvl_dl_0_max == -1 || rdlvl_dl_0_min == -1 || rdlvl_dl_0 <= 0) {
debug("RDLVL: The DQS to DQ delay cannot be found!\n");
debug("RDLVL: Using default - slice 0: %d!\n", rdlvl_dl_0_def);
rdlvl_dl_0 = rdlvl_dl_0_def;
}
debug("\nRDLVL: ---> RDLVL_DL_1\n");
bitmap_zero(rdlvl_rsp, DDRMC_DQS_DQ_MAX_DELAY + 1);
for (i = 0; i <= DDRMC_DQS_DQ_MAX_DELAY; i++) {
clrsetbits_le32(&ddrmr->cr[110],
0xFFFF << DDRMC_CR110_RDLVL_DL_1_OFF,
i << DDRMC_CR110_RDLVL_DL_1_OFF);
/* Load values CR93[SWLVL_LOAD] to ’b1 */
sw_leveling_load_value;
/* Poll CR94[SWLVL_OP_DONE] */
sw_leveling_op_done;
/*
* Read Responses - SWLVL_RESP_1
*
* The 0x00 (correct response when PHY_RDLVL_EDGE = 0)
* -> 1 in the bit vector
*/
swlvl_rsp = (readl(&ddrmr->cr[95]) >>
DDRMC_CR95_SWLVL_RESP_1_OFF) & 0xF;
if (swlvl_rsp == 0)
generic_set_bit(i, rdlvl_rsp);
}
bitmap_print(rdlvl_rsp, DDRMC_DQS_DQ_MAX_DELAY);
/*
* First test for rising edge 0x0 -> 0x1 in bitmap
*/
rdlvl_dl_1_min = ddr_cal_get_first_edge_index(rdlvl_rsp, RISING_EDGE,
N_SAMPLES, N_SAMPLES,
DDRMC_DQS_DQ_MAX_DELAY);
/*
* Secondly test for falling edge 0x1 -> 0x0 in bitmap
*/
rdlvl_dl_1_max = ddr_cal_get_first_edge_index(rdlvl_rsp, FALLING_EDGE,
N_SAMPLES, rdlvl_dl_1_min,
DDRMC_DQS_DQ_MAX_DELAY);
debug("RDLVL: DL_1 min: %d [0x%x] DL_1 max: %d [0x%x]\n",
rdlvl_dl_1_min, rdlvl_dl_1_min, rdlvl_dl_1_max, rdlvl_dl_1_max);
rdlvl_dl_1 = (rdlvl_dl_1_max - rdlvl_dl_1_min) / 2;
if (rdlvl_dl_1_max == -1 || rdlvl_dl_1_min == -1 || rdlvl_dl_1 <= 0) {
debug("RDLVL: The DQS to DQ delay cannot be found!\n");
debug("RDLVL: Using default - slice 1: %d!\n", rdlvl_dl_1_def);
rdlvl_dl_1 = rdlvl_dl_1_def;
}
debug("RDLVL: CALIBRATED: rdlvl_dl_0: 0x%x\t rdlvl_dl_1: 0x%x\n",
rdlvl_dl_0, rdlvl_dl_1);
/* Write new delay values */
writel(DDRMC_CR105_RDLVL_DL_0(rdlvl_dl_0), &ddrmr->cr[105]);
writel(DDRMC_CR110_RDLVL_DL_1(rdlvl_dl_1), &ddrmr->cr[110]);
sw_leveling_load_value;
sw_leveling_op_done;
/* Exit procedure - CR94[SWLVL_EXIT] to ’b1 */
sw_leveling_exit;
/* Poll CR94[SWLVL_OP_DONE] */
sw_leveling_op_done;
return 0;
}
/*
* WRLVL_DL calibration:
*
* For non-flyback memory architecture - where one have a single DDR3 x16
* memory - it is NOT necessary to perform "Write Leveling"
* [3] 'Vybrid DDR3 write leveling' https://community.nxp.com/thread/429362
*
*/
int ddrmc_calibration(struct ddrmr_regs *ddrmr)
{
ddrmc_cal_dqs_to_dq(ddrmr);
return 0;
}
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* ddrmc DDR3 calibration code for NXP's VF610
*
* Copyright (C) 2018 DENX Software Engineering
* Lukasz Majewski, DENX Software Engineering, lukma@denx.de
*
*/
#ifndef __DDRMC_VF610_CALIBRATOIN_H_
#define __DDRMC_VF610_CALIBRATOIN_H_
/*
* Number of "samples" in the calibration bitmap
* to be considered during calibration.
*/
#define N_SAMPLES 3
/*
* Constants to indicate if we are looking for a rising or
* falling edge in the calibration bitmap
*/
enum edge {
FALLING_EDGE = 1,
RISING_EDGE
};
/*
* The max number of delay elements when DQS to DQ setting
*/
#define DDRMC_DQS_DQ_MAX_DELAY 0xFF
/**
* ddrmc_calibration - Vybrid's (VF610) DDR3 calibration code
*
* This function is calculating proper memory controller values
* during run time.
*
* @param ddrmr_regs - memory controller registers
*
* @return 0 on success, otherwise error code
*/
int ddrmc_calibration(struct ddrmr_regs *ddrmr);
#endif /* __DDRMC_VF610_CALIBRATOIN_H_ */
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册