// SPDX-License-Identifier: GPL-2.0+ /* * Copyright (C) 2021 Rockchip Electronics Co., Ltd. */ #if defined(CONFIG_ROCKCHIP_RV1126) || defined(CONFIG_ROCKCHIP_RK3568) #include #include #include #include #if defined(CONFIG_ROCKCHIP_RV1126) #include #elif defined(CONFIG_ROCKCHIP_RK3568) #include #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 RK356x 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) */