// SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause
/*
 * Copyright (C) 2019, STMicroelectronics - All Rights Reserved
 */
#include <common.h>
#include <console.h>
#include <watchdog.h>
#include <asm/io.h>
#include <linux/log2.h>
#include "stm32mp1_tests.h"

#define ADDR_INVALID	0xFFFFFFFF

DECLARE_GLOBAL_DATA_PTR;

static int get_bufsize(char *string, int argc, char *argv[], int arg_nb,
		       size_t *bufsize, size_t default_size)
{
	unsigned long value;

	if (argc > arg_nb) {
		if (strict_strtoul(argv[arg_nb], 0, &value) < 0) {
			sprintf(string, "invalid %d parameter %s",
				arg_nb, argv[arg_nb]);
			return -1;
		}
		if (value > STM32_DDR_SIZE || value == 0) {
			sprintf(string, "invalid size %s", argv[arg_nb]);
			return -1;
		}
		if (value & 0x3) {
			sprintf(string, "unaligned size %s",
				argv[arg_nb]);
			return -1;
		}
		*bufsize = value;
	} else {
		if (default_size != STM32_DDR_SIZE)
			*bufsize = default_size;
		else
			*bufsize = get_ram_size((long *)STM32_DDR_BASE,
						STM32_DDR_SIZE);
	}
	return 0;
}

static int get_nb_loop(char *string, int argc, char *argv[], int arg_nb,
		       u32 *nb_loop, u32 default_nb_loop)
{
	unsigned long value;

	if (argc > arg_nb) {
		if (strict_strtoul(argv[arg_nb], 0, &value) < 0) {
			sprintf(string, "invalid %d parameter %s",
				arg_nb, argv[arg_nb]);
			return -1;
		}
		if (value == 0)
			printf("WARNING: infinite loop requested\n");
		*nb_loop = value;
	} else {
		*nb_loop = default_nb_loop;
	}

	return 0;
}

static int get_addr(char *string, int argc, char *argv[], int arg_nb,
		    u32 *addr)
{
	unsigned long value;

	if (argc > arg_nb) {
		if (strict_strtoul(argv[arg_nb], 16, &value) < 0) {
			sprintf(string, "invalid %d parameter %s",
				arg_nb, argv[arg_nb]);
			return -1;
		}
		if (value < STM32_DDR_BASE) {
			sprintf(string, "too low address %s", argv[arg_nb]);
			return -1;
		}
		if (value & 0x3 && value != ADDR_INVALID) {
			sprintf(string, "unaligned address %s",
				argv[arg_nb]);
			return -1;
		}
		*addr = value;
	} else {
		*addr = STM32_DDR_BASE;
	}

	return 0;
}

static int get_pattern(char *string, int argc, char *argv[], int arg_nb,
		       u32 *pattern, u32 default_pattern)
{
	unsigned long value;

	if (argc > arg_nb) {
		if (strict_strtoul(argv[arg_nb], 16, &value) < 0) {
			sprintf(string, "invalid %d parameter %s",
				arg_nb, argv[arg_nb]);
			return -1;
		}
		*pattern = value;
	} else {
		*pattern = default_pattern;
	}

	return 0;
}

static u32 check_addr(u32 addr, u32 value)
{
	u32 data = readl(addr);

	if (value !=  data) {
		printf("0x%08x: 0x%08x <=> 0x%08x", addr, data, value);
		data = readl(addr);
		printf("(2nd read: 0x%08x)", data);
		if (value == data)
			printf("- read error");
		else
			printf("- write error");
		printf("\n");
		return -1;
	}
	return 0;
}

static int progress(u32 offset)
{
	if (!(offset & 0xFFFFFF)) {
		putc('.');
		if (ctrlc()) {
			printf("\ntest interrupted!\n");
			return 1;
		}
	}
	return 0;
}

static int test_loop_end(u32 *loop, u32 nb_loop, u32 progress)
{
	(*loop)++;
	if (nb_loop && *loop >= nb_loop)
		return 1;
	if ((*loop) % progress)
		return 0;
	/* allow to interrupt the test only for progress step */
	if (ctrlc()) {
		printf("test interrupted!\n");
		return 1;
	}
	printf("loop #%d\n", *loop);
	WATCHDOG_RESET();

	return 0;
}

/**********************************************************************
 *
 * Function:    memTestDataBus()
 *
 * Description: Test the data bus wiring in a memory region by
 *              performing a walking 1's test at a fixed address
 *              within that region.  The address is selected
 *              by the caller.
 *
 * Notes:
 *
 * Returns:     0 if the test succeeds.
 *              A non-zero result is the first pattern that failed.
 *
 **********************************************************************/
static u32 databus(u32 *address)
{
	u32 pattern;
	u32 read_value;

	/* Perform a walking 1's test at the given address. */
	for (pattern = 1; pattern != 0; pattern <<= 1) {
		/* Write the test pattern. */
		writel(pattern, address);

		/* Read it back (immediately is okay for this test). */
		read_value = readl(address);
		debug("%x: %x <=> %x\n",
		      (u32)address, read_value, pattern);

		if (read_value != pattern)
			return pattern;
	}

	return 0;
}

/**********************************************************************
 *
 * Function:    memTestAddressBus()
 *
 * Description: Test the address bus wiring in a memory region by
 *              performing a walking 1's test on the relevant bits
 *              of the address and checking for aliasing. This test
 *              will find single-bit address failures such as stuck
 *              -high, stuck-low, and shorted pins.  The base address
 *              and size of the region are selected by the caller.
 *
 * Notes:       For best results, the selected base address should
 *              have enough LSB 0's to guarantee single address bit
 *              changes.  For example, to test a 64-Kbyte region,
 *              select a base address on a 64-Kbyte boundary.  Also,
 *              select the region size as a power-of-two--if at all
 *              possible.
 *
 * Returns:     NULL if the test succeeds.
 *              A non-zero result is the first address at which an
 *              aliasing problem was uncovered.  By examining the
 *              contents of memory, it may be possible to gather
 *              additional information about the problem.
 *
 **********************************************************************/
static u32 *addressbus(u32 *address, u32 nb_bytes)
{
	u32 mask = (nb_bytes / sizeof(u32) - 1);
	u32 offset;
	u32 test_offset;
	u32 read_value;

	u32 pattern     = 0xAAAAAAAA;
	u32 antipattern = 0x55555555;

	/* Write the default pattern at each of the power-of-two offsets. */
	for (offset = 1; (offset & mask) != 0; offset <<= 1)
		writel(pattern, &address[offset]);

	/* Check for address bits stuck high. */
	test_offset = 0;
	writel(antipattern, &address[test_offset]);

	for (offset = 1; (offset & mask) != 0; offset <<= 1) {
		read_value = readl(&address[offset]);
		debug("%x: %x <=> %x\n",
		      (u32)&address[offset], read_value, pattern);
		if (read_value != pattern)
			return &address[offset];
	}

	writel(pattern, &address[test_offset]);

	/* Check for address bits stuck low or shorted. */
	for (test_offset = 1; (test_offset & mask) != 0; test_offset <<= 1) {
		writel(antipattern, &address[test_offset]);
		if (readl(&address[0]) != pattern)
			return &address[test_offset];

		for (offset = 1; (offset & mask) != 0; offset <<= 1) {
			if (readl(&address[offset]) != pattern &&
			    offset != test_offset)
				return &address[test_offset];
		}
		writel(pattern, &address[test_offset]);
	}

	return NULL;
}

/**********************************************************************
 *
 * Function:    memTestDevice()
 *
 * Description: Test the integrity of a physical memory device by
 *              performing an increment/decrement test over the
 *              entire region.  In the process every storage bit
 *              in the device is tested as a zero and a one.  The
 *              base address and the size of the region are
 *              selected by the caller.
 *
 * Notes:
 *
 * Returns:     NULL if the test succeeds.
 *
 *              A non-zero result is the first address at which an
 *              incorrect value was read back.  By examining the
 *              contents of memory, it may be possible to gather
 *              additional information about the problem.
 *
 **********************************************************************/
static u32 *memdevice(u32 *address, u32 nb_bytes)
{
	u32 offset;
	u32 nb_words = nb_bytes / sizeof(u32);

	u32 pattern;
	u32 antipattern;

	puts("Fill with pattern");
	/* Fill memory with a known pattern. */
	for (pattern = 1, offset = 0; offset < nb_words; pattern++, offset++) {
		writel(pattern, &address[offset]);
		if (progress(offset))
			return NULL;
	}

	puts("\nCheck and invert pattern");
	/* Check each location and invert it for the second pass. */
	for (pattern = 1, offset = 0; offset < nb_words; pattern++, offset++) {
		if (readl(&address[offset]) != pattern)
			return &address[offset];

		antipattern = ~pattern;
		writel(antipattern, &address[offset]);
		if (progress(offset))
			return NULL;
	}

	puts("\nCheck inverted pattern");
	/* Check each location for the inverted pattern and zero it. */
	for (pattern = 1, offset = 0; offset < nb_words; pattern++, offset++) {
		antipattern = ~pattern;
		if (readl(&address[offset]) != antipattern)
			return &address[offset];
		if (progress(offset))
			return NULL;
	}
	printf("\n");

	return NULL;
}

static enum test_result databuswalk0(struct stm32mp1_ddrctl *ctl,
				     struct stm32mp1_ddrphy *phy,
				     char *string, int argc, char *argv[])
{
	int i;
	u32 loop = 0, nb_loop;
	u32 addr;
	u32 error = 0;
	u32 data;

	if (get_nb_loop(string, argc, argv, 0, &nb_loop, 100))
		return TEST_ERROR;
	if (get_addr(string, argc, argv, 1, &addr))
		return TEST_ERROR;

	printf("running %d loops at 0x%x\n", nb_loop, addr);
	while (!error) {
		for (i = 0; i < 32; i++)
			writel(~(1 << i), addr + 4 * i);
		for (i = 0; i < 32; i++) {
			data = readl(addr + 4 * i);
			if (~(1 << i) !=  data) {
				error |= 1 << i;
				debug("%x: error %x expected %x => error:%x\n",
				      addr + 4 * i, data, ~(1 << i), error);
			}
		}
		if (test_loop_end(&loop, nb_loop, 1000))
			break;
		for (i = 0; i < 32; i++)
			writel(0, addr + 4 * i);
	}
	if (error) {
		sprintf(string, "loop %d: error for bits 0x%x",
			loop, error);
		return TEST_FAILED;
	}
	sprintf(string, "no error for %d loops", loop);
	return TEST_PASSED;
}

static enum test_result databuswalk1(struct stm32mp1_ddrctl *ctl,
				     struct stm32mp1_ddrphy *phy,
				     char *string, int argc, char *argv[])
{
	int i;
	u32 loop = 0, nb_loop;
	u32 addr;
	u32 error = 0;
	u32 data;

	if (get_nb_loop(string, argc, argv, 0, &nb_loop, 100))
		return TEST_ERROR;
	if (get_addr(string, argc, argv, 1, &addr))
		return TEST_ERROR;
	printf("running %d loops at 0x%x\n", nb_loop, addr);
	while (!error) {
		for (i = 0; i < 32; i++)
			writel(1 << i, addr + 4 * i);
		for (i = 0; i < 32; i++) {
			data = readl(addr + 4 * i);
			if ((1 << i) !=  data) {
				error |= 1 << i;
				debug("%x: error %x expected %x => error:%x\n",
				      addr + 4 * i, data, (1 << i), error);
			}
		}
		if (test_loop_end(&loop, nb_loop, 1000))
			break;
		for (i = 0; i < 32; i++)
			writel(0, addr + 4 * i);
	}
	if (error) {
		sprintf(string, "loop %d: error for bits 0x%x",
			loop, error);
		return TEST_FAILED;
	}
	sprintf(string, "no error for %d loops", loop);
	return TEST_PASSED;
}

static enum test_result test_databus(struct stm32mp1_ddrctl *ctl,
				     struct stm32mp1_ddrphy *phy,
				     char *string, int argc, char *argv[])
{
	u32 addr;
	u32 error;

	if (get_addr(string, argc, argv, 0, &addr))
		return TEST_ERROR;
	error = databus((u32 *)addr);
	if (error) {
		sprintf(string, "0x%x: error for bits 0x%x",
			addr, error);
		return TEST_FAILED;
	}
	sprintf(string, "address 0x%x", addr);
	return TEST_PASSED;
}

static enum test_result test_addressbus(struct stm32mp1_ddrctl *ctl,
					struct stm32mp1_ddrphy *phy,
					char *string, int argc, char *argv[])
{
	u32 addr;
	u32 bufsize;
	u32 error;

	if (get_bufsize(string, argc, argv, 0, &bufsize, 4 * 1024))
		return TEST_ERROR;
	if (!is_power_of_2(bufsize)) {
		sprintf(string, "size 0x%x is not a power of 2",
			(u32)bufsize);
		return TEST_ERROR;
	}
	if (get_addr(string, argc, argv, 1, &addr))
		return TEST_ERROR;

	error = (u32)addressbus((u32 *)addr, bufsize);
	if (error) {
		sprintf(string, "0x%x: error for address 0x%x",
			addr, error);
		return TEST_FAILED;
	}
	sprintf(string, "address 0x%x, size 0x%x",
		addr, bufsize);
	return TEST_PASSED;
}

static enum test_result test_memdevice(struct stm32mp1_ddrctl *ctl,
				       struct stm32mp1_ddrphy *phy,
				       char *string, int argc, char *argv[])
{
	u32 addr;
	size_t bufsize;
	u32 error;

	if (get_bufsize(string, argc, argv, 0, &bufsize, 4 * 1024))
		return TEST_ERROR;
	if (get_addr(string, argc, argv, 1, &addr))
		return TEST_ERROR;
	error = (u32)memdevice((u32 *)addr, (unsigned long)bufsize);
	if (error) {
		sprintf(string, "0x%x: error for address 0x%x",
			addr, error);
		return TEST_FAILED;
	}
	sprintf(string, "address 0x%x, size 0x%x",
		addr, bufsize);
	return TEST_PASSED;
}

/**********************************************************************
 *
 * Function:    sso
 *
 * Description: Test the Simultaneous Switching Output.
 *              Verifies succes sive reads and writes to the same memory word,
 *              holding one bit constant while toggling all other data bits
 *              simultaneously
 *              => stress the data bus over an address range
 *
 *              The CPU writes to each address in the given range.
 *              For each bit, first the CPU holds the bit at 1 while
 *              toggling the other bits, and then the CPU holds the bit at 0
 *              while toggling the other bits.
 *              After each write, the CPU reads the address that was written
 *              to verify that it contains the correct data
 *
 **********************************************************************/
static enum test_result test_sso(struct stm32mp1_ddrctl *ctl,
				 struct stm32mp1_ddrphy *phy,
				 char *string, int argc, char *argv[])
{
	int i, j;
	u32 addr, bufsize, remaining, offset;
	u32 error = 0;
	u32 data;

	if (get_bufsize(string, argc, argv, 0, &bufsize, 4))
		return TEST_ERROR;
	if (get_addr(string, argc, argv, 1, &addr))
		return TEST_ERROR;

	printf("running sso at 0x%x length 0x%x", addr, bufsize);
	offset = addr;
	remaining = bufsize;
	while (remaining) {
		for (i = 0; i < 32; i++) {
			/* write pattern. */
			for (j = 0; j < 6; j++) {
				switch (j) {
				case 0:
				case 2:
					data = 1 << i;
					break;
				case 3:
				case 5:
					data = ~(1 << i);
					break;
				case 1:
					data = ~0x0;
					break;
				case 4:
					data = 0x0;
					break;
				}

				writel(data, offset);
				error = check_addr(offset, data);
				if (error)
					goto end;
			}
		}
		offset += 4;
		remaining -= 4;
		if (progress(offset << 7))
			goto end;
	}
	puts("\n");

end:
	if (error) {
		sprintf(string, "error for pattern 0x%x @0x%x",
			data, offset);
		return TEST_FAILED;
	}
	sprintf(string, "no error for sso at 0x%x length 0x%x", addr, bufsize);
	return TEST_PASSED;
}

/**********************************************************************
 *
 * Function:    Random
 *
 * Description: Verifies r/w with pseudo-ramdom value on one region
 *              + write the region (individual access)
 *              + memcopy to the 2nd region (try to use burst)
 *              + verify the 2 regions
 *
 **********************************************************************/
static enum test_result test_random(struct stm32mp1_ddrctl *ctl,
				    struct stm32mp1_ddrphy *phy,
				    char *string, int argc, char *argv[])
{
	u32 addr, offset, value = 0;
	size_t bufsize;
	u32 loop = 0, nb_loop;
	u32 error = 0;
	unsigned int seed;

	if (get_bufsize(string, argc, argv, 0, &bufsize, 8 * 1024))
		return TEST_ERROR;
	if (get_nb_loop(string, argc, argv, 1, &nb_loop, 1))
		return TEST_ERROR;
	if (get_addr(string, argc, argv, 2, &addr))
		return TEST_ERROR;

	bufsize /= 2;
	printf("running %d loops copy from 0x%x to 0x%x (buffer size=0x%x)\n",
	       nb_loop, addr, addr + bufsize, bufsize);
	while (!error) {
		seed = rand();
		for (offset = 0; offset < bufsize; offset += 4)
			writel(rand(), addr + offset);

		memcpy((void *)addr + bufsize, (void *)addr, bufsize);

		srand(seed);
		for (offset = 0; offset < 2 * bufsize; offset += 4) {
			if (offset == bufsize)
				srand(seed);
			value = rand();
			error = check_addr(addr + offset, value);
			if (error)
				break;
			if (progress(offset))
				return TEST_FAILED;
		}
		if (test_loop_end(&loop, nb_loop, 100))
			break;
	}
	putc('\n');

	if (error) {
		sprintf(string,
			"loop %d: error for address 0x%x: 0x%x expected 0x%x",
			loop, offset, readl(offset), value);
		return TEST_FAILED;
	}
	sprintf(string, "no error for %d loops, size 0x%x",
		loop, bufsize);
	return TEST_PASSED;
}

/**********************************************************************
 *
 * Function:    noise
 *
 * Description: Verifies r/w while forcing switching of all data bus lines.
 *              optimised 4 iteration write/read/write/read cycles...
 *              for pattern and inversed pattern
 *
 **********************************************************************/
void do_noise(u32 addr, u32 pattern, u32 *result)
{
	__asm__("push {R0-R11}");
	__asm__("mov r0, %0" : : "r" (addr));
	__asm__("mov r1, %0" : : "r" (pattern));
	__asm__("mov r11, %0" : : "r" (result));

	__asm__("mvn r2, r1");

	__asm__("str r1, [r0]");
	__asm__("ldr r3, [r0]");
	__asm__("str r2, [r0]");
	__asm__("ldr r4, [r0]");

	__asm__("str r1, [r0]");
	__asm__("ldr r5, [r0]");
	__asm__("str r2, [r0]");
	__asm__("ldr r6, [r0]");

	__asm__("str r1, [r0]");
	__asm__("ldr r7, [r0]");
	__asm__("str r2, [r0]");
	__asm__("ldr r8, [r0]");

	__asm__("str r1, [r0]");
	__asm__("ldr r9, [r0]");
	__asm__("str r2, [r0]");
	__asm__("ldr r10, [r0]");

	__asm__("stmia R11!, {R3-R10}");

	__asm__("pop {R0-R11}");
}

static enum test_result test_noise(struct stm32mp1_ddrctl *ctl,
				   struct stm32mp1_ddrphy *phy,
				   char *string, int argc, char *argv[])
{
	u32 addr, pattern;
	u32 result[8];
	int i;
	enum test_result res = TEST_PASSED;

	if (get_pattern(string, argc, argv, 0, &pattern, 0xFFFFFFFF))
		return TEST_ERROR;
	if (get_addr(string, argc, argv, 1, &addr))
		return TEST_ERROR;

	printf("running noise for 0x%x at 0x%x\n", pattern, addr);

	do_noise(addr, pattern, result);

	for (i = 0; i < 0x8;) {
		if (check_addr((u32)&result[i++], pattern))
			res = TEST_FAILED;
		if (check_addr((u32)&result[i++], ~pattern))
			res = TEST_FAILED;
	}

	return res;
}

/**********************************************************************
 *
 * Function:    noise_burst
 *
 * Description: Verifies r/w while forcing switching of all data bus lines.
 *              optimised write loop witrh store multiple to use burst
 *              for pattern and inversed pattern
 *
 **********************************************************************/
void do_noise_burst(u32 addr, u32 pattern, size_t bufsize)
{
	__asm__("push {R0-R9}");
	__asm__("mov r0, %0" : : "r" (addr));
	__asm__("mov r1, %0" : : "r" (pattern));
	__asm__("mov r9, %0" : : "r" (bufsize));

	__asm__("mvn r2, r1");
	__asm__("mov r3, r1");
	__asm__("mov r4, r2");
	__asm__("mov r5, r1");
	__asm__("mov r6, r2");
	__asm__("mov r7, r1");
	__asm__("mov r8, r2");

	__asm__("loop1:");
	__asm__("stmia R0!, {R1-R8}");
	__asm__("stmia R0!, {R1-R8}");
	__asm__("stmia R0!, {R1-R8}");
	__asm__("stmia R0!, {R1-R8}");
	__asm__("subs r9, r9, #128");
	__asm__("bge loop1");
	__asm__("pop {R0-R9}");
}

/* chunk size enough to allow interruption with Ctrl-C*/
#define CHUNK_SIZE	0x8000000
static enum test_result test_noise_burst(struct stm32mp1_ddrctl *ctl,
					 struct stm32mp1_ddrphy *phy,
					 char *string, int argc, char *argv[])
{
	u32 addr, offset, pattern;
	size_t bufsize, remaining, size;
	int i;
	enum test_result res = TEST_PASSED;

	if (get_bufsize(string, argc, argv, 0, &bufsize, 4 * 1024))
		return TEST_ERROR;
	if (get_pattern(string, argc, argv, 1, &pattern, 0xFFFFFFFF))
		return TEST_ERROR;
	if (get_addr(string, argc, argv, 2, &addr))
		return TEST_ERROR;

	printf("running noise burst for 0x%x at 0x%x + 0x%x",
	       pattern, addr, bufsize);

	offset = addr;
	remaining = bufsize;
	size = CHUNK_SIZE;
	while (remaining) {
		if (remaining < size)
			size = remaining;
		do_noise_burst(offset, pattern, size);
		remaining -= size;
		offset += size;
		if (progress(offset)) {
			res = TEST_FAILED;
			goto end;
		}
	}
	puts("\ncheck buffer");
	for (i = 0; i < bufsize;) {
		if (check_addr(addr + i, pattern))
			res = TEST_FAILED;
		i += 4;
		if (check_addr(addr + i, ~pattern))
			res = TEST_FAILED;
		i += 4;
		if (progress(i)) {
			res = TEST_FAILED;
			goto end;
		}
	}
end:
	puts("\n");
	return res;
}

/**********************************************************************
 *
 * Function:    pattern test
 *
 * Description: optimized loop for read/write pattern (array of 8 u32)
 *
 **********************************************************************/
#define PATTERN_SIZE	8
static enum test_result test_loop(const u32 *pattern, u32 *address,
				  const u32 bufsize)
{
	int i;
	int j;
	enum test_result res = TEST_PASSED;
	u32 offset, testsize, remaining;

	offset = (u32)address;
	remaining = bufsize;
	while (remaining) {
		testsize = bufsize > 0x1000000 ? 0x1000000 : bufsize;

		__asm__("push {R0-R10}");
		__asm__("mov r0, %0" : : "r" (pattern));
		__asm__("mov r1, %0" : : "r" (offset));
		__asm__("mov r2, %0" : : "r" (testsize));
		__asm__("ldmia r0!, {R3-R10}");

		__asm__("loop2:");
		__asm__("stmia r1!, {R3-R10}");
		__asm__("stmia r1!, {R3-R10}");
		__asm__("stmia r1!, {R3-R10}");
		__asm__("stmia r1!, {R3-R10}");
		__asm__("subs r2, r2, #128");
		__asm__("bge loop2");
		__asm__("pop {R0-R10}");

		offset += testsize;
		remaining -= testsize;
		if (progress((u32)offset)) {
			res = TEST_FAILED;
			goto end;
		}
	}

	puts("\ncheck buffer");
	for (i = 0; i < bufsize; i += PATTERN_SIZE * 4) {
		for (j = 0; j < PATTERN_SIZE; j++, address++)
			if (check_addr((u32)address, pattern[j])) {
				res = TEST_FAILED;
				goto end;
			}
		if (progress(i)) {
			res = TEST_FAILED;
			goto end;
		}
	}

end:
	puts("\n");
	return res;
}

const u32 pattern_div1_x16[PATTERN_SIZE] = {
	0x0000FFFF, 0x0000FFFF, 0x0000FFFF, 0x0000FFFF,
	0x0000FFFF, 0x0000FFFF, 0x0000FFFF, 0x0000FFFF
};

const u32 pattern_div2_x16[PATTERN_SIZE] = {
	0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000000,
	0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000000
};

const u32 pattern_div4_x16[PATTERN_SIZE] = {
	0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, 0x00000000,
	0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, 0x00000000
};

const u32 pattern_div4_x32[PATTERN_SIZE] = {
	0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
	0x00000000, 0x00000000, 0x00000000, 0x00000000
};

const u32 pattern_mostly_zero_x16[PATTERN_SIZE] = {
	0x00000000, 0x00000000, 0x00000000, 0x0000FFFF,
	0x00000000, 0x00000000, 0x00000000, 0x00000000
};

const u32 pattern_mostly_zero_x32[PATTERN_SIZE] = {
	0x00000000, 0x00000000, 0x00000000, 0xFFFFFFFF,
	0x00000000, 0x00000000, 0x00000000, 0x00000000
};

const u32 pattern_mostly_one_x16[PATTERN_SIZE] = {
	0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x0000FFFF,
	0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF
};

const u32 pattern_mostly_one_x32[PATTERN_SIZE] = {
	0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000,
	0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF
};

#define NB_PATTERN	5
static enum test_result test_freq_pattern(struct stm32mp1_ddrctl *ctl,
					  struct stm32mp1_ddrphy *phy,
					  char *string, int argc, char *argv[])
{
	const u32 * const patterns_x16[NB_PATTERN] = {
		pattern_div1_x16,
		pattern_div2_x16,
		pattern_div4_x16,
		pattern_mostly_zero_x16,
		pattern_mostly_one_x16,
	};
	const u32 * const patterns_x32[NB_PATTERN] = {
		pattern_div2_x16,
		pattern_div4_x16,
		pattern_div4_x32,
		pattern_mostly_zero_x32,
		pattern_mostly_one_x32
	};
	const char *patterns_comments[NB_PATTERN] = {
		"switching at frequency F/1",
		"switching at frequency F/2",
		"switching at frequency F/4",
		"mostly zero",
		"mostly one"
	};

	enum test_result res = TEST_PASSED, pattern_res;
	int i, bus_width;
	const u32 **patterns;
	u32 bufsize;

	if (get_bufsize(string, argc, argv, 0, &bufsize, 4 * 1024))
		return TEST_ERROR;

	switch (readl(&ctl->mstr) & DDRCTRL_MSTR_DATA_BUS_WIDTH_MASK) {
	case DDRCTRL_MSTR_DATA_BUS_WIDTH_HALF:
	case DDRCTRL_MSTR_DATA_BUS_WIDTH_QUARTER:
		bus_width = 16;
		break;
	default:
		bus_width = 32;
		break;
	}

	printf("running test pattern at 0x%08x length 0x%x width = %d\n",
	       STM32_DDR_BASE, bufsize, bus_width);

	patterns =
		(const u32 **)(bus_width == 16 ? patterns_x16 : patterns_x32);

	for (i = 0; i < NB_PATTERN; i++) {
		printf("test data pattern %s:", patterns_comments[i]);
		pattern_res = test_loop(patterns[i], (u32 *)STM32_DDR_BASE,
					bufsize);
		if (pattern_res != TEST_PASSED)	{
			printf("Failed\n");
			return pattern_res;
		}
		printf("Passed\n");
	}

	return res;
}

/**********************************************************************
 *
 * Function:    pattern test with size
 *
 * Description: loop for write pattern
 *
 **********************************************************************/

static enum test_result test_loop_size(const u32 *pattern, u32 size,
				       u32 *address,
				       const u32 bufsize)
{
	int i, j;
	enum test_result res = TEST_PASSED;
	u32 *p = address;

	for (i = 0; i < bufsize; i += size * 4) {
		for (j = 0; j < size ; j++, p++)
			*p = pattern[j];
		if (progress(i)) {
			res = TEST_FAILED;
			goto end;
		}
	}

	puts("\ncheck buffer");
	p = address;
	for (i = 0; i < bufsize; i += size * 4) {
		for (j = 0; j < size; j++, p++)
			if (check_addr((u32)p, pattern[j])) {
				res = TEST_FAILED;
				goto end;
			}
		if (progress(i)) {
			res = TEST_FAILED;
			goto end;
		}
	}

end:
	puts("\n");
	return res;
}

static enum test_result test_checkboard(struct stm32mp1_ddrctl *ctl,
					struct stm32mp1_ddrphy *phy,
					char *string, int argc, char *argv[])
{
	enum test_result res = TEST_PASSED;
	u32 bufsize, nb_loop, loop = 0, addr;
	int i;

	u32 checkboard[2] = {0x55555555, 0xAAAAAAAA};

	if (get_bufsize(string, argc, argv, 0, &bufsize, 4 * 1024))
		return TEST_ERROR;
	if (get_nb_loop(string, argc, argv, 1, &nb_loop, 1))
		return TEST_ERROR;
	if (get_addr(string, argc, argv, 2, &addr))
		return TEST_ERROR;

	printf("running %d loops at 0x%08x length 0x%x\n",
	       nb_loop, addr, bufsize);
	while (1) {
		for (i = 0; i < 2; i++) {
			res = test_loop_size(checkboard, 2, (u32 *)addr,
					     bufsize);
			if (res)
				return res;
			checkboard[0] = ~checkboard[0];
			checkboard[1] = ~checkboard[1];
		}
		if (test_loop_end(&loop, nb_loop, 1))
			break;
	}
	sprintf(string, "no error for %d loops at 0x%08x length 0x%x",
		loop, addr, bufsize);

	return res;
}

static enum test_result test_blockseq(struct stm32mp1_ddrctl *ctl,
				      struct stm32mp1_ddrphy *phy,
				      char *string, int argc, char *argv[])
{
	enum test_result res = TEST_PASSED;
	u32 bufsize, nb_loop, loop = 0, addr, value;
	int i;

	if (get_bufsize(string, argc, argv, 0, &bufsize, 4 * 1024))
		return TEST_ERROR;
	if (get_nb_loop(string, argc, argv, 1, &nb_loop, 1))
		return TEST_ERROR;
	if (get_addr(string, argc, argv, 2, &addr))
		return TEST_ERROR;

	printf("running %d loops at 0x%08x length 0x%x\n",
	       nb_loop, addr, bufsize);
	while (1) {
		for (i = 0; i < 256; i++) {
			value = i | i << 8 | i << 16 | i << 24;
			printf("pattern = %08x", value);
			res = test_loop_size(&value, 1, (u32 *)addr, bufsize);
			if (res != TEST_PASSED)
				return res;
		}
		if (test_loop_end(&loop, nb_loop, 1))
			break;
	}
	sprintf(string, "no error for %d loops at 0x%08x length 0x%x",
		loop, addr, bufsize);

	return res;
}

static enum test_result test_walkbit0(struct stm32mp1_ddrctl *ctl,
				      struct stm32mp1_ddrphy *phy,
				      char *string, int argc, char *argv[])
{
	enum test_result res = TEST_PASSED;
	u32 bufsize, nb_loop, loop = 0, addr, value;
	int i;

	if (get_bufsize(string, argc, argv, 0, &bufsize, 4 * 1024))
		return TEST_ERROR;
	if (get_nb_loop(string, argc, argv, 1, &nb_loop, 1))
		return TEST_ERROR;
	if (get_addr(string, argc, argv, 2, &addr))
		return TEST_ERROR;

	printf("running %d loops at 0x%08x length 0x%x\n",
	       nb_loop, addr, bufsize);
	while (1) {
		for (i = 0; i < 64; i++) {
			if (i < 32)
				value = 1 << i;
			else
				value = 1 << (63 - i);

			printf("pattern = %08x", value);
			res = test_loop_size(&value, 1, (u32 *)addr, bufsize);
			if (res != TEST_PASSED)
				return res;
		}
		if (test_loop_end(&loop, nb_loop, 1))
			break;
	}
	sprintf(string, "no error for %d loops at 0x%08x length 0x%x",
		loop, addr, bufsize);

	return res;
}

static enum test_result test_walkbit1(struct stm32mp1_ddrctl *ctl,
				      struct stm32mp1_ddrphy *phy,
				      char *string, int argc, char *argv[])
{
	enum test_result res = TEST_PASSED;
	u32 bufsize, nb_loop, loop = 0, addr, value;
	int i;

	if (get_bufsize(string, argc, argv, 0, &bufsize, 4 * 1024))
		return TEST_ERROR;
	if (get_nb_loop(string, argc, argv, 1, &nb_loop, 1))
		return TEST_ERROR;
	if (get_addr(string, argc, argv, 2, &addr))
		return TEST_ERROR;

	printf("running %d loops at 0x%08x length 0x%x\n",
	       nb_loop, addr, bufsize);
	while (1) {
		for (i = 0; i < 64; i++) {
			if (i < 32)
				value = ~(1 << i);
			else
				value = ~(1 << (63 - i));

			printf("pattern = %08x", value);
			res = test_loop_size(&value, 1, (u32 *)addr, bufsize);
			if (res != TEST_PASSED)
				return res;
		}
		if (test_loop_end(&loop, nb_loop, 1))
			break;
	}
	sprintf(string, "no error for %d loops at 0x%08x length 0x%x",
		loop, addr, bufsize);

	return res;
}

/*
 * try to catch bad bits which are dependent on the current values of
 * surrounding bits in either the same word32
 */
static enum test_result test_bitspread(struct stm32mp1_ddrctl *ctl,
				       struct stm32mp1_ddrphy *phy,
				       char *string, int argc, char *argv[])
{
	enum test_result res = TEST_PASSED;
	u32 bufsize, nb_loop, loop = 0, addr, bitspread[4];
	int i, j;

	if (get_bufsize(string, argc, argv, 0, &bufsize, 4 * 1024))
		return TEST_ERROR;
	if (get_nb_loop(string, argc, argv, 1, &nb_loop, 1))
		return TEST_ERROR;
	if (get_addr(string, argc, argv, 2, &addr))
		return TEST_ERROR;

	printf("running %d loops at 0x%08x length 0x%x\n",
	       nb_loop, addr, bufsize);
	while (1) {
		for (i = 1; i < 32; i++) {
			for (j = 0; j < i; j++) {
				if (i < 32)
					bitspread[0] = (1 << i) | (1 << j);
				else
					bitspread[0] = (1 << (63 - i)) |
						       (1 << (63 - j));
				bitspread[1] = bitspread[0];
				bitspread[2] = ~bitspread[0];
				bitspread[3] = ~bitspread[0];
				printf("pattern = %08x", bitspread[0]);

				res = test_loop_size(bitspread, 4, (u32 *)addr,
						     bufsize);
				if (res != TEST_PASSED)
					return res;
			}
		}
		if (test_loop_end(&loop, nb_loop, 1))
			break;
	}
	sprintf(string, "no error for %d loops at 0x%08x length 0x%x",
		loop, addr, bufsize);

	return res;
}

static enum test_result test_bitflip(struct stm32mp1_ddrctl *ctl,
				     struct stm32mp1_ddrphy *phy,
				     char *string, int argc, char *argv[])
{
	enum test_result res = TEST_PASSED;
	u32 bufsize, nb_loop, loop = 0, addr;
	int i;

	u32 bitflip[4];

	if (get_bufsize(string, argc, argv, 0, &bufsize, 4 * 1024))
		return TEST_ERROR;
	if (get_nb_loop(string, argc, argv, 1, &nb_loop, 1))
		return TEST_ERROR;
	if (get_addr(string, argc, argv, 2, &addr))
		return TEST_ERROR;

	printf("running %d loops at 0x%08x length 0x%x\n",
	       nb_loop, addr, bufsize);
	while (1) {
		for (i = 0; i < 32; i++) {
			bitflip[0] = 1 << i;
			bitflip[1] = bitflip[0];
			bitflip[2] = ~bitflip[0];
			bitflip[3] = bitflip[2];
			printf("pattern = %08x", bitflip[0]);

			res = test_loop_size(bitflip, 4, (u32 *)addr, bufsize);
			if (res != TEST_PASSED)
				return res;
		}
		if (test_loop_end(&loop, nb_loop, 1))
			break;
	}
	sprintf(string, "no error for %d loops at 0x%08x length 0x%x",
		loop, addr, bufsize);

	return res;
}

/**********************************************************************
 *
 * Function: infinite read access to DDR
 *
 * Description: continuous read the same pattern at the same address
 *
 **********************************************************************/
static enum test_result test_read(struct stm32mp1_ddrctl *ctl,
				  struct stm32mp1_ddrphy *phy,
				  char *string, int argc, char *argv[])
{
	u32 *addr;
	u32 data;
	u32 loop = 0;
	int i, size = 1024 * 1024;
	bool random = false;

	if (get_addr(string, argc, argv, 0, (u32 *)&addr))
		return TEST_ERROR;

	if (get_pattern(string, argc, argv, 1, &data, 0xA5A5AA55))
		return TEST_ERROR;

	if ((u32)addr == ADDR_INVALID) {
		printf("running random\n");
		random = true;
	} else {
		printf("running at 0x%08x with pattern=0x%08x\n",
		       (u32)addr, data);
		writel(data, addr);
	}

	while (1) {
		for (i = 0; i < size; i++) {
			if (random)
				addr = (u32 *)(STM32_DDR_BASE +
				       (rand() & (STM32_DDR_SIZE - 1) & ~0x3));
			data = readl(addr);
		}
		if (test_loop_end(&loop, 0, 1))
			break;
	}
	if (random)
		sprintf(string, "%d loops random", loop);
	else
		sprintf(string, "%d loops at 0x%x: %x", loop, (u32)addr, data);

	return TEST_PASSED;
}

/**********************************************************************
 *
 * Function: infinite write access to DDR
 *
 * Description: continuous write the same pattern at the same address
 *
 **********************************************************************/
static enum test_result test_write(struct stm32mp1_ddrctl *ctl,
				   struct stm32mp1_ddrphy *phy,
				   char *string, int argc, char *argv[])
{
	u32 *addr;
	u32 data;
	u32 loop = 0;
	int i, size = 1024 * 1024;
	bool random = false;

	if (get_addr(string, argc, argv, 0, (u32 *)&addr))
		return TEST_ERROR;

	if (get_pattern(string, argc, argv, 1, &data, 0xA5A5AA55))
		return TEST_ERROR;

	if ((u32)addr == ADDR_INVALID) {
		printf("running random\n");
		random = true;
	} else {
		printf("running at 0x%08x with pattern 0x%08x\n",
		       (u32)addr, data);
	}

	while (1) {
		for (i = 0; i < size; i++) {
			if (random) {
				addr = (u32 *)(STM32_DDR_BASE +
				       (rand() & (STM32_DDR_SIZE - 1) & ~0x3));
				data = rand();
			}
			writel(data, addr);
		}
		if (test_loop_end(&loop, 0, 1))
			break;
	}
	if (random)
		sprintf(string, "%d loops random", loop);
	else
		sprintf(string, "%d loops at 0x%x: %x", loop, (u32)addr, data);

	return TEST_PASSED;
}

#define NB_TEST_INFINITE 2
static enum test_result test_all(struct stm32mp1_ddrctl *ctl,
				 struct stm32mp1_ddrphy *phy,
				 char *string, int argc, char *argv[])
{
	enum test_result res = TEST_PASSED, result;
	int i, nb_error = 0;
	u32 loop = 0, nb_loop;

	if (get_nb_loop(string, argc, argv, 0, &nb_loop, 1))
		return TEST_ERROR;

	while (!nb_error) {
		/* execute all the test except the lasts which are infinite */
		for (i = 1; i < test_nb - NB_TEST_INFINITE; i++) {
			printf("execute %d:%s\n", (int)i, test[i].name);
			result = test[i].fct(ctl, phy, string, 0, NULL);
			printf("result %d:%s = ", (int)i, test[i].name);
			if (result != TEST_PASSED) {
				nb_error++;
				res = TEST_FAILED;
				puts("Failed");
			} else {
				puts("Passed");
			}
			puts("\n\n");
		}
		printf("loop %d: %d/%d test failed\n\n\n",
		       loop + 1, nb_error, test_nb - NB_TEST_INFINITE);
		if (test_loop_end(&loop, nb_loop, 1))
			break;
	}
	if (res != TEST_PASSED) {
		sprintf(string, "loop %d: %d/%d test failed", loop, nb_error,
			test_nb - NB_TEST_INFINITE);
	} else {
		sprintf(string, "loop %d: %d tests passed", loop,
			test_nb - NB_TEST_INFINITE);
	}
	return res;
}

/****************************************************************
 * TEST Description
 ****************************************************************/

const struct test_desc test[] = {
	{test_all, "All", "[loop]", "Execute all tests", 1 },
	{test_databus, "Simple DataBus", "[addr]",
	 "Verifies each data line by walking 1 on fixed address",
	 1
	 },
	{databuswalk0, "DataBusWalking0", "[loop] [addr]",
	 "Verifies each data bus signal can be driven low (32 word burst)",
	 2
	},
	{databuswalk1, "DataBusWalking1", "[loop] [addr]",
	 "Verifies each data bus signal can be driven high (32 word burst)",
	 2
	},
	{test_addressbus, "AddressBus", "[size] [addr]",
	 "Verifies each relevant bits of the address and checking for aliasing",
	 2
	 },
	{test_memdevice, "MemDevice", "[size] [addr]",
	 "Test the integrity of a physical memory (test every storage bit in the region)",
	 2
	 },
	{test_sso, "SimultaneousSwitchingOutput", "[size] [addr] ",
	 "Stress the data bus over an address range",
	 2
	},
	{test_noise, "Noise", "[pattern] [addr]",
	 "Verifies r/w while forcing switching of all data bus lines.",
	 3
	},
	{test_noise_burst, "NoiseBurst", "[size] [pattern] [addr]",
	 "burst transfers while forcing switching of the data bus lines",
	 3
	},
	{test_random, "Random", "[size] [loop] [addr]",
	 "Verifies r/w and memcopy(burst for pseudo random value.",
	 3
	},
	{test_freq_pattern, "FrequencySelectivePattern", "[size]",
	 "write & test patterns: Mostly Zero, Mostly One and F/n",
	 1
	},
	{test_blockseq, "BlockSequential", "[size] [loop] [addr]",
	 "test incremental pattern",
	 3
	},
	{test_checkboard, "Checkerboard", "[size] [loop] [addr]",
	 "test checker pattern",
	 3
	},
	{test_bitspread, "BitSpread", "[size] [loop] [addr]",
	 "test Bit Spread pattern",
	 3
	},
	{test_bitflip, "BitFlip", "[size] [loop] [addr]",
	 "test Bit Flip pattern",
	 3
	},
	{test_walkbit0, "WalkingOnes", "[size] [loop] [addr]",
	 "test Walking Ones pattern",
	 3
	},
	{test_walkbit1, "WalkingZeroes", "[size] [loop] [addr]",
	 "test Walking Zeroes pattern",
	 3
	},
	/* need to the the 2 last one (infinite) : skipped for test all */
	{test_read, "infinite read", "[addr] [pattern]",
	 "basic test : infinite read access (random: addr=0xFFFFFFFF)", 2},
	{test_write, "infinite write", "[addr] [pattern]",
	 "basic test : infinite write access (random: addr=0xFFFFFFFF)", 2},
};

const int test_nb = ARRAY_SIZE(test);