// SPDX-License-Identifier: GPL-2.0+
|
/*
|
* Copyright (C) 2021 Rockchip Electronics Co., Ltd.
|
*/
|
|
#if defined(CONFIG_ROCKCHIP_RV1126) || defined(CONFIG_ROCKCHIP_RK3568)
|
|
#include <common.h>
|
#include <console.h>
|
#include <asm/io.h>
|
#include <asm/arch-rockchip/sdram_common.h>
|
#if defined(CONFIG_ROCKCHIP_RV1126)
|
#include <asm/arch/sdram_rv1126.h>
|
#elif defined(CONFIG_ROCKCHIP_RK3568)
|
#include <asm/arch/sdram_rk3568.h>
|
#endif
|
|
#define __version__ "0.0.6"
|
|
#define PRINT_LENGTH 64
|
#ifndef PRINT_STEP
|
#define PRINT_STEP 1
|
#endif
|
#define PRINT_RANGE ((PRINT_LENGTH) * (PRINT_STEP))
|
|
struct print_border {
|
u16 far_left;
|
u16 far_right;
|
};
|
|
struct rw_trn_result result;
|
|
static void calc_print_border(struct cs_rw_trn_result *result, u8 byte_en,
|
u16 deskew_num, struct print_border *print_border)
|
{
|
u16 far_left = deskew_num;
|
u16 far_right = 0;
|
u16 mid;
|
u8 dqs;
|
u8 dq;
|
|
if (deskew_num <= PRINT_RANGE) {
|
print_border->far_left = 0;
|
print_border->far_right = deskew_num - 1;
|
|
return;
|
}
|
|
for (dqs = 0; dqs < BYTE_NUM; dqs++) {
|
if ((byte_en & BIT(dqs)) == 0)
|
continue;
|
|
for (dq = 0; dq < 8; dq++) {
|
if (result->dqs[dqs].dq_min[dq] < far_left)
|
far_left = result->dqs[dqs].dq_min[dq];
|
if (result->dqs[dqs].dq_max[dq] > far_right)
|
far_right = result->dqs[dqs].dq_max[dq];
|
}
|
}
|
|
if (far_right - far_left + 1 > PRINT_RANGE) {
|
print_border->far_left = far_left & ~((u16)(PRINT_STEP * 4 - 1));
|
print_border->far_right = far_right | (PRINT_STEP * 4 - 1);
|
} else {
|
mid = (far_left + far_right) / 2;
|
if (mid < PRINT_RANGE / 2) {
|
print_border->far_left = 0;
|
print_border->far_right = PRINT_RANGE - 1;
|
} else if (mid > deskew_num - PRINT_RANGE / 2) {
|
print_border->far_left = deskew_num - PRINT_RANGE;
|
print_border->far_right = deskew_num - 1;
|
} else {
|
print_border->far_left = mid - PRINT_RANGE / 2;
|
print_border->far_right = mid + PRINT_RANGE / 2 - 1;
|
}
|
}
|
}
|
|
static void print_title_bar(struct print_border *print_border)
|
{
|
int i;
|
|
printf(" ");
|
for (i = print_border->far_left; i < print_border->far_right;
|
i += PRINT_STEP * 4)
|
printf("%-4d", i);
|
printf(" Margin_L Sample Margin_R Width DQS\n");
|
}
|
|
static void print_ddr_dq_eye(struct fsp_rw_trn_result *fsp_result, u8 cs,
|
u8 byte_en, u16 width_ref,
|
struct print_border *print_border)
|
{
|
u16 sample;
|
u16 min;
|
u16 max;
|
u16 dq_eye_width;
|
u8 dqs;
|
u8 dq;
|
int i;
|
struct cs_rw_trn_result *result = &fsp_result->cs[cs];
|
|
for (dqs = 0; dqs < BYTE_NUM; dqs++) {
|
if ((byte_en & BIT(dqs)) == 0)
|
continue;
|
|
for (dq = 0; dq < 8; dq++) {
|
sample = fsp_result->min_val +
|
result->dqs[dqs].dq_deskew[dq];
|
min = result->dqs[dqs].dq_min[dq];
|
max = result->dqs[dqs].dq_max[dq];
|
dq_eye_width = max >= min ? max - min + 1 : 0;
|
|
printf("DQ%-2d ", dqs * 8 + dq);
|
for (i = print_border->far_left;
|
i <= print_border->far_right; i += PRINT_STEP) {
|
if (i / PRINT_STEP == sample / PRINT_STEP)
|
printf("|");
|
else if (i / PRINT_STEP >= min / PRINT_STEP &&
|
i / PRINT_STEP <= max / PRINT_STEP)
|
printf("*");
|
else
|
printf("-");
|
}
|
|
printf(" %5d%8d%8d",
|
sample > min ? sample - min : 0, sample,
|
max > sample ? max - sample : 0);
|
if (dq_eye_width >= width_ref)
|
printf("%8d%8d\n", dq_eye_width,
|
fsp_result->min_val +
|
result->dqs[dqs].dqs_deskew);
|
else
|
printf(" [%3d]%7d\n", dq_eye_width,
|
fsp_result->min_val +
|
result->dqs[dqs].dqs_deskew);
|
}
|
}
|
printf("\n");
|
}
|
|
static u16 cs_eye_width_min(struct cs_rw_trn_result *result, u8 byte_en,
|
u16 deskew_num)
|
{
|
u16 min;
|
u16 max;
|
u16 dq_eye_width;
|
u16 cs_eye_width = deskew_num;
|
u8 dqs;
|
u8 dq;
|
|
for (dqs = 0; dqs < BYTE_NUM; dqs++) {
|
if ((byte_en & BIT(dqs)) == 0)
|
continue;
|
|
for (dq = 0; dq < 8; dq++) {
|
min = result->dqs[dqs].dq_min[dq];
|
max = result->dqs[dqs].dq_max[dq];
|
dq_eye_width = max >= min ? max - min + 1 : 0;
|
if (cs_eye_width > dq_eye_width)
|
cs_eye_width = dq_eye_width;
|
}
|
}
|
|
return cs_eye_width;
|
}
|
|
static int do_ddr_dq_eye(cmd_tbl_t *cmdtp, int flag, int argc,
|
char * const argv[])
|
{
|
unsigned long freq_mhz;
|
|
u32 ddr_type;
|
u16 rd_width = RD_DESKEW_NUM;
|
u16 wr_width = WR_DESKEW_NUM;
|
u16 cs_eye_width;
|
u16 rd_width_ref;
|
u16 wr_width_ref;
|
u16 width_ref_mhz;
|
u8 fsp = 0;
|
u8 cs;
|
int i;
|
struct print_border print_border;
|
|
printf("Rockchip DDR DQ Eye Tool v" __version__ "\n");
|
|
#if defined(CONFIG_ROCKCHIP_RV1126)
|
ddr_type = (readl(0xfe020208) >> 13) & 0x7;
|
#elif defined(CONFIG_ROCKCHIP_RK3568)
|
ddr_type = ((readl(0xfdc2020c) & (0x3 << 12)) >> 9) |
|
((readl(0xfdc20208) >> 13) & 0x7);
|
#else
|
printf("Rockchip DDR DQ Eye Tool only support RK3568/RK3566 and RV1126 now.\n");
|
return CMD_RET_FAILURE;
|
#endif
|
|
if (readl(RW_TRN_RESULT_ADDR) == DDR_DQ_EYE_FLAG) {
|
memcpy(&result, (void *)(RW_TRN_RESULT_ADDR), sizeof(result));
|
} else {
|
printf("Fail to get data of DDR DQ eye.\n");
|
printf("Please update the Loader.\n");
|
return CMD_RET_FAILURE;
|
}
|
|
if (argc == 1) {
|
/* use the max freq if no arg */
|
for (i = 0; i < FSP_NUM; i++) {
|
if (result.fsp_mhz[i] > result.fsp_mhz[fsp])
|
fsp = i;
|
}
|
} else if (argc > 1) {
|
if (strict_strtoul(argv[1], 0, &freq_mhz) < 0)
|
return CMD_RET_USAGE;
|
|
if (freq_mhz >= 0 && freq_mhz < FSP_NUM) {
|
/* when user enter the fsp rather than the freq_mhz */
|
fsp = (u8)freq_mhz;
|
} else {
|
for (fsp = 0; fsp < FSP_NUM; fsp++)
|
if (result.fsp_mhz[fsp] == freq_mhz ||
|
result.fsp_mhz[fsp] == (u16)(freq_mhz / MHZ))
|
break;
|
|
if (fsp >= FSP_NUM)
|
return CMD_RET_USAGE;
|
}
|
} else {
|
return CMD_RET_FAILURE;
|
}
|
|
printf("DDR type: ");
|
switch (ddr_type) {
|
case LPDDR4X:
|
if (result.fsp_mhz[fsp] <
|
(LP4_WIDTH_REF_MHZ_L + LP4_WIDTH_REF_MHZ_H) / 2) {
|
rd_width_ref = LP4_RD_WIDTH_REF_L;
|
wr_width_ref = LP4_WR_WIDTH_REF_L;
|
width_ref_mhz = LP4_WIDTH_REF_MHZ_L;
|
} else {
|
rd_width_ref = LP4_RD_WIDTH_REF_H;
|
wr_width_ref = LP4_WR_WIDTH_REF_H;
|
width_ref_mhz = LP4_WIDTH_REF_MHZ_H;
|
}
|
printf("LPDDR4X");
|
break;
|
case LPDDR4:
|
if (result.fsp_mhz[fsp] <
|
(LP4_WIDTH_REF_MHZ_L + LP4_WIDTH_REF_MHZ_H) / 2) {
|
rd_width_ref = LP4_RD_WIDTH_REF_L;
|
wr_width_ref = LP4_WR_WIDTH_REF_L;
|
width_ref_mhz = LP4_WIDTH_REF_MHZ_L;
|
} else {
|
rd_width_ref = LP4_RD_WIDTH_REF_H;
|
wr_width_ref = LP4_WR_WIDTH_REF_H;
|
width_ref_mhz = LP4_WIDTH_REF_MHZ_H;
|
}
|
printf("LPDDR4");
|
break;
|
case LPDDR3:
|
if (result.fsp_mhz[fsp] <
|
(LP4_WIDTH_REF_MHZ_L + LP4_WIDTH_REF_MHZ_H) / 2) {
|
rd_width_ref = LP3_RD_WIDTH_REF_L;
|
wr_width_ref = LP3_WR_WIDTH_REF_L;
|
width_ref_mhz = LP3_WIDTH_REF_MHZ_L;
|
} else {
|
rd_width_ref = LP3_RD_WIDTH_REF_H;
|
wr_width_ref = LP3_WR_WIDTH_REF_H;
|
width_ref_mhz = LP3_WIDTH_REF_MHZ_H;
|
}
|
printf("LPDDR3");
|
break;
|
case DDR4:
|
if (result.fsp_mhz[fsp] <
|
(DDR4_WIDTH_REF_MHZ_L + DDR4_WIDTH_REF_MHZ_H) / 2) {
|
rd_width_ref = DDR4_RD_WIDTH_REF_L;
|
wr_width_ref = DDR4_WR_WIDTH_REF_L;
|
width_ref_mhz = DDR4_WIDTH_REF_MHZ_L;
|
} else {
|
rd_width_ref = DDR4_RD_WIDTH_REF_H;
|
wr_width_ref = DDR4_WR_WIDTH_REF_H;
|
width_ref_mhz = DDR4_WIDTH_REF_MHZ_H;
|
}
|
printf("DDR4");
|
break;
|
case DDR3:
|
default:
|
if (result.fsp_mhz[fsp] <
|
(DDR3_WIDTH_REF_MHZ_L + DDR3_WIDTH_REF_MHZ_H) / 2) {
|
rd_width_ref = DDR3_RD_WIDTH_REF_L;
|
wr_width_ref = DDR3_WR_WIDTH_REF_L;
|
width_ref_mhz = DDR3_WIDTH_REF_MHZ_L;
|
} else {
|
rd_width_ref = DDR3_RD_WIDTH_REF_H;
|
wr_width_ref = DDR3_WR_WIDTH_REF_H;
|
width_ref_mhz = DDR3_WIDTH_REF_MHZ_H;
|
}
|
printf("DDR3");
|
break;
|
} /* switch (ddr_type) */
|
printf("\n");
|
|
for (cs = 0; cs < result.cs_num; cs++) {
|
calc_print_border(&result.rd_fsp[fsp].cs[cs], result.byte_en,
|
RD_DESKEW_NUM, &print_border);
|
printf("CS%d %dMHz read DQ eye:\n", cs, result.fsp_mhz[fsp]);
|
print_title_bar(&print_border);
|
print_ddr_dq_eye(&result.rd_fsp[fsp], cs, result.byte_en,
|
rd_width_ref, &print_border);
|
cs_eye_width = cs_eye_width_min(&result.rd_fsp[fsp].cs[cs],
|
result.byte_en, RD_DESKEW_NUM);
|
if (rd_width > cs_eye_width)
|
rd_width = cs_eye_width;
|
|
printf("CS%d %dMHz write DQ eye:\n", cs, result.fsp_mhz[fsp]);
|
calc_print_border(&result.wr_fsp[fsp].cs[cs], result.byte_en,
|
WR_DESKEW_NUM, &print_border);
|
print_title_bar(&print_border);
|
print_ddr_dq_eye(&result.wr_fsp[fsp], cs, result.byte_en,
|
wr_width_ref, &print_border);
|
cs_eye_width = cs_eye_width_min(&result.wr_fsp[fsp].cs[cs],
|
result.byte_en, WR_DESKEW_NUM);
|
if (wr_width > cs_eye_width)
|
wr_width = cs_eye_width;
|
}
|
printf("DQ eye width min: %d(read), %d(write)\n", rd_width, wr_width);
|
printf("DQ eye width reference: %d(read), %d(write) in %dMHz\n",
|
rd_width_ref, wr_width_ref, width_ref_mhz);
|
if (rd_width < rd_width_ref || wr_width < wr_width_ref)
|
printf("ERROR: DQ eye width may be unreliable, please check!\n");
|
|
return CMD_RET_SUCCESS;
|
}
|
|
U_BOOT_CMD(ddr_dq_eye, 2, 1, do_ddr_dq_eye,
|
"Rockchip DDR DQ Eye Tool\n",
|
"arg1: DDR freq in MHz, null for the max freq.\n"
|
"example:\n"
|
" ddr_dq_eye 1056: show the DDR DQ eye in 1056MHz."
|
);
|
|
#endif /* if defined(CONFIG_ROCKCHIP_RV1126) || defined(CONFIG_ROCKCHIP_RK3568) */
|