blob: c1f523eb3f4a1491f0bf40c822e4d3f8b117008a [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright Siemens AG 2023
*
* DDR signal integrity test
* Check signals on DDR lines
* - signals must be as fast as possible and generate long burst
* - signals must be unidirectional (to DDR or from DDR only)
*
* Set pattern: define 2^n 32-bit patterns (up to 4)
* Addresses: must be multiple of 16 to avoid checks in loops
* Test functions
* - write: write pattern to memory area for iteration times
* - read: write pattern once to memory area, read for iteration times
*/
#include <command.h>
#include <exports.h>
#include <time.h>
#if CONFIG_IS_ENABLED(AM33XX)
#include <asm/arch-am33xx/hardware_am33xx.h>
#include <asm/arch-am33xx/cpu.h>
#include <asm/io.h>
#endif
/* enable some print for debugging */
#ifdef PR_DEBUG
#define PDEBUG(fmt, args...) printf(fmt, ## args)
#else
#define PDEBUG(fmt, args...)
#endif
/* define 4 32-bit patterns */
#define MAX_PTN_SIZE (128)
#define PTN_ARRAY_SIZE (MAX_PTN_SIZE / (8 * sizeof(u32)))
/* define test direction */
#define DIR_READ 0
#define DIR_WRITE 1
static union {
u64 l[2];
u32 s[4];
} test_pattern;
static int num_ptn32;
#if CONFIG_IS_ENABLED(AM33XX)
static inline void wdt_disable(void)
{
struct wd_timer *wdtimer = (struct wd_timer *)WDT_BASE;
writel(0xAAAA, &wdtimer->wdtwspr);
while (readl(&wdtimer->wdtwwps) != 0x0)
;
writel(0x5555, &wdtimer->wdtwspr);
while (readl(&wdtimer->wdtwwps) != 0x0)
;
}
static inline void wdt_enable(void)
{
struct wd_timer *wdtimer = (struct wd_timer *)WDT_BASE;
writel(0xBBBB, &wdtimer->wdtwspr);
while (readl(&wdtimer->wdtwwps) != 0x0)
;
writel(0x4444, &wdtimer->wdtwspr);
while (readl(&wdtimer->wdtwwps) != 0x0)
;
}
#else /* ! */
static inline void wdt_disable(void) {}
static inline void wdt_enable(void) {}
#endif /* CONFIG_IS_ENABLED(AM33XX) */
static int do_ddr_set_ptn(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
{
int i, n;
if (argc < 1)
return CMD_RET_USAGE;
/* number of patterns: 2 exponent */
n = argc - 1;
if (n > PTN_ARRAY_SIZE || (n & (n - 1)))
return CMD_RET_USAGE;
num_ptn32 = n;
/* get patterns */
for (i = 0; i < n; i++)
test_pattern.s[i] = simple_strtoul(argv[i + 1], NULL, 0);
printf("Test pattern set\n");
return CMD_RET_SUCCESS;
}
static int do_ddr_show_ptn(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
{
if (!num_ptn32) {
printf("No pattern available\n");
} else {
u32 *buf = test_pattern.s;
int len = num_ptn32;
int i;
printf("Pattern: ");
for (i = 0 ; i < len; i++)
printf("0x%08X ", *buf++);
printf("\n");
}
return CMD_RET_SUCCESS;
}
static void ddr_read32(u64 start_addr, u64 n_word, unsigned long iter)
{
while (iter--) {
register volatile u32 *addr = (u32 *)start_addr;
register u64 count = n_word;
while (count) {
(void)*addr++;
PDEBUG("Read 0x%08X from 0x%p\n", val, addr - 1);
count--;
}
}
}
static void ddr_read64(u64 start_addr, u64 n_word, unsigned long iter)
{
while (iter--) {
register volatile u64 *addr = (u64 *)start_addr;
register u64 count = n_word;
if (num_ptn32 == 4)
count *= 2;
/*
* 64 & 128 bit pattern. Increase the nummber of read
* commands in the loop to generate longer burst signal
*/
while (count) {
(void)*addr++;
PDEBUG("Read 0x%016llX from 0x%p\n", val, addr - 1);
(void)*addr++;
PDEBUG("Read 0x%016llX from 0x%p\n", val, addr - 1);
(void)*addr++;
PDEBUG("Read 0x%016llX from 0x%p\n", val, addr - 1);
(void)*addr++;
PDEBUG("Read 0x%016llX from 0x%p\n", val, addr - 1);
(void)*addr++;
PDEBUG("Read 0x%016llX from 0x%p\n", val, addr - 1);
(void)*addr++;
PDEBUG("Read 0x%016llX from 0x%p\n", val, addr - 1);
(void)*addr++;
PDEBUG("Read 0x%016llX from 0x%p\n", val, addr - 1);
(void)*addr++;
PDEBUG("Read 0x%016llX from 0x%p\n", val, addr - 1);
/*
* underflow cannot happen since n_word = end -
* start, end & start addresses are checked to be
* multiple of 16
*/
count -= 8;
}
}
}
static void ddr_write32(u64 start_addr, u64 n_word, unsigned long iter)
{
while (iter--) {
register u32 *addr = (u32 *)start_addr;
register u32 ptn = *test_pattern.s;
register u64 count = n_word;
while (count) {
PDEBUG("Write 0x%08X to 0x%p\n", ptn, addr);
*addr++ = ptn;
count--;
}
}
}
static void ddr_write64(u64 start_addr, u64 n_word, unsigned long iter)
{
while (iter--) {
register u64 *addr = (u64 *)start_addr;
register u64 ptnA = test_pattern.l[0];
register u64 ptnB = test_pattern.l[1];
register u64 count = n_word;
if (num_ptn32 == 2)
ptnB = ptnA;
else
count *= 2;
/*
* 64 & 128 bit pattern. Increase the nummber of write
* commands in the loop to generate longer burst signal
*/
while (count) {
PDEBUG("Write 0x%016llX to 0x%p\n", ptnA, addr);
*addr++ = ptnA;
PDEBUG("Write 0x%016llX to 0x%p\n", ptnB, addr);
*addr++ = ptnB;
PDEBUG("Write 0x%016llX to 0x%p\n", ptnA, addr);
*addr++ = ptnA;
PDEBUG("Write 0x%016llX to 0x%p\n", ptnB, addr);
*addr++ = ptnB;
PDEBUG("Write 0x%016llX to 0x%p\n", ptnA, addr);
*addr++ = ptnA;
PDEBUG("Write 0x%016llX to 0x%p\n", ptnB, addr);
*addr++ = ptnB;
PDEBUG("Write 0x%016llX to 0x%p\n", ptnA, addr);
*addr++ = ptnA;
PDEBUG("Write 0x%016llX to 0x%p\n", ptnB, addr);
*addr++ = ptnB;
/*
* underflow cannot happen since n_word = end -
* start, end & start addresses are checked to be
* multiple of 16
*/
count -= 8;
}
}
}
static int do_ddr_si_test(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
{
u64 start_addr, end_addr, n_word;
u64 ts_start, ts_end;
unsigned long iteration, wr_iter;
int direction, i;
if (argc < 3 || argc > 4)
return CMD_RET_USAGE;
/* get arguments */
direction = strcmp(argv[0], "read") ? DIR_WRITE : DIR_READ;
start_addr = simple_strtoul(argv[1], NULL, 0);
end_addr = simple_strtoul(argv[2], NULL, 0);
iteration = simple_strtoul(argv[3], NULL, 10);
n_word = (end_addr - start_addr) / (num_ptn32 * 4);
printf("\nDDR signal integrity %s test: start\n", argv[0]);
/* checks */
if (start_addr & 0xF) {
printf("ERROR: start_address should be 16 bytes aligned\n\n");
return CMD_RET_USAGE;
}
if (end_addr & 0xF) {
printf("ERROR: end_address should be 16 bytes aligned\n\n");
return CMD_RET_USAGE;
}
if (start_addr >= end_addr) {
printf("ERROR: end_address is not bigger than start_address\n\n");
return CMD_RET_USAGE;
}
if (!iteration) {
printf("ERROR: no iteration specified\n\n");
return CMD_RET_USAGE;
}
if (!num_ptn32) {
printf("ERROR: no test pattern specified\n\n");
return CMD_RET_USAGE;
}
/* print parameters */
printf("start_address = 0x%016llX\n", start_addr);
printf("end_address = 0x%016llX\n", end_addr);
printf("iterations = %lu\n", iteration);
/* print pattern */
printf("test pattern 0x");
for (i = 0; i < num_ptn32; i++)
printf("%08X", test_pattern.s[i]);
printf("\n");
wdt_disable();
/* writing */
printf("Writing..\n");
ts_start = get_timer_us(0);
if (direction == DIR_READ)
wr_iter = 1;
else
wr_iter = iteration;
if (num_ptn32 == 1)
ddr_write32(start_addr, n_word, wr_iter);
else
ddr_write64(start_addr, n_word, wr_iter);
ts_end = get_timer_us(0);
/* reading */
if (direction == DIR_READ) {
printf("Reading..\n");
/* we need read time, just overwrite */
ts_start = get_timer_us(0);
if (num_ptn32 == 1)
ddr_read32(start_addr, n_word, iteration);
else
ddr_read64(start_addr, n_word, iteration);
ts_end = get_timer_us(0);
}
wdt_enable();
/* print stats */
printf("DONE.");
printf(" Bytes=%llu ", n_word * num_ptn32 * 4 * iteration);
printf(" Time=%llu us ", ts_end - ts_start);
printf("\nDDR signal integrity %s test: end\n", argv[0]);
return CMD_RET_SUCCESS;
}
static char ddr_si_help_text[] =
"- DDR signal integrity test\n\n"
"ddr_si setptn <pattern> [<pattern>] : set [1,2,4] 32-bit patterns\n"
"ddr_si showptn : show patterns\n"
"ddr_si read <start> <end> <iterations> : run test for reading\n"
"ddr_si write <start> <end> <iterations> : run test for writing\n"
"\nWith\n"
"\t<pattern>: 32-bit pattern in hex format\n"
"\t<start>: test start address in hex format\n"
"\t<end>: test end address in hex format\n"
"\t<iterations>: number of iterations\n";
U_BOOT_CMD_WITH_SUBCMDS(ddr_si, "DDR si test", ddr_si_help_text,
U_BOOT_SUBCMD_MKENT(setptn, 5, 0, do_ddr_set_ptn),
U_BOOT_SUBCMD_MKENT(showptn, 1, 0, do_ddr_show_ptn),
U_BOOT_SUBCMD_MKENT(read, 4, 0, do_ddr_si_test),
U_BOOT_SUBCMD_MKENT(write, 4, 0, do_ddr_si_test));