// Pandora2 SDRAM testing code.
//
// Based on the advice given at http://www.netrino.com/Articles/MemoryTesting/index.php
// and other sources.
//
// The SDRAM is arranged in four chips. The first pair of chips do the
// top and bottom sixteen bits of the first 128MB of RAM respectively
// and the second pair do the top and bottom sixteen bites of the
// second 128MB of RAM.
//
// (C) 2007 Roku LLC
//

//#include <stdio.h>
//#include <stdint.h>
#include <stdarg.h>
#include "common.h"

#define STRIZE(x) STRIZE2(x)
#define STRIZE2(x) #x
#define assert(x) do { if (!(x)) printf(__FILE__ ":" STRIZE(__LINE__) " Assertion failed: " STRIZE(x) "\n"); } while(0)
#define countof(x) (sizeof(x)/sizeof(x[0]))

//#define sdram_debug printf
#define sdram_debug(...) do {} while(0)

// Locked out region is where our code and data is. We sometimes
// kludge addresses in the region by adding a kludge amount to them.

#define LOCKOUT_BEGIN 	0x800000
#define LOCKOUT_END 	0x8dc000
#define LOCKOUT_KLUDGE 	0x100000

struct MemoryChip
{
    unsigned start;
    unsigned size;
};

struct MemoryChip chips[] =
{
    { 0xa0000000, 0x08000000 },
    { 0xa8000000, 0x08000000 },
};

static void sdram_error(const char *fmt, ...)
{
	va_list va;
	extern int serial_enabled;

	va_start(va, fmt);
	serial_enabled = 1;
	vprintf(fmt, va);
	va_end(va);
}

// Because the chips split the 32-bits among themselves automatically
// an SDRAM word is 32-bits.
typedef unsigned int sdram_word;
typedef volatile sdram_word *sdram_address;

// We don't want to corrupt arbitrary bits of SDRAM during this
// operation because any parts that are used for information that is
// useful to preserve after a crash will become corrupted. So, we just
// maintain a list of locations that we write to and their original
// values so we can write them back afterwards.
struct Fixup
{
	sdram_address address;
	sdram_word value;
};

// The code uses just over fifty of these.
#define MAX_FIXUP_COUNT 64

static struct Fixup fixups[MAX_FIXUP_COUNT];
static unsigned fixup_count;

static inline void before_write(sdram_address address)
{
	unsigned i;
	for(i = 0; i < fixup_count; ++i)
	{
		if (address == fixups[i].address)
		{
			// We've already seen this address - we shouldn't add it
			// again.
			return;
		}
	}

	if (fixup_count < MAX_FIXUP_COUNT)
	{
		sdram_debug("Adding fixup %2u %08x %08x\n", fixup_count, address, *address);
		fixups[fixup_count].address = address;
		fixups[fixup_count].value = *address;
		++fixup_count;
	}
}

static inline void ensure_before_write(sdram_address address)
{
#if 0
	unsigned i;
	for(i = 0; i < fixup_count; ++i)
	{
		if (address == fixups[i].address)
		{
			// We've already seen this address - we shouldn't add it
			// again.
			return;
		}
	}

	printf("Fixup not found for address 0x%08x\n", address);
#endif
}

static inline void apply_fixups(void)
{
	unsigned i;
	sdram_debug("Applying %u fixups\n", fixup_count);
	for(i = 0; i < fixup_count; ++i)
	{
		sdram_debug("%2u %08x = %08x\n", i, fixups[i].address, fixups[i].value);
		*(fixups[i].address) = fixups[i].value;
	}
	sdram_debug("Fixups applied.\n");
}

static sdram_address MakeSafeAddress(sdram_address base, unsigned long offset_in_words)
{
    sdram_address address = base + offset_in_words;
    if (((unsigned long)address >= LOCKOUT_BEGIN) && ((unsigned long)address < LOCKOUT_END))
    {
		address += LOCKOUT_KLUDGE;
    }
    if (((unsigned long)address >= LOCKOUT_BEGIN) && ((unsigned long)address < LOCKOUT_END))
    {
		assert(0);
    }
    return address;
}

static inline int NotLockedOut(sdram_address base, unsigned long offset_in_words)
{
    if (base + offset_in_words >= (sdram_address)LOCKOUT_END)
		return 1;
    if (base + offset_in_words < (sdram_address)LOCKOUT_BEGIN)
		return 1;
    return 0;
}

static unsigned int TestDataBusOnAddress(sdram_address address)
{
	unsigned failures = 0;
	
    sdram_word pattern;
    for(pattern = 1; pattern != 0; pattern <<= 1)
    {
		before_write(address);
		*address = pattern;		
		
		if (*address != pattern)
		{
			sdram_error("*** pattern FAIL address=0x%x, value=0x%x, expected=0x%x\n", address, *address, pattern);
			++failures;
		}

		const sdram_word antipattern = ~pattern;

		ensure_before_write(address);
		*address = antipattern;
		
		if (*address != antipattern)
		{
			sdram_error("*** antipattern FAIL address=0x%x, value=0x%x, expected=0x%x\n", address, *address, antipattern);
			++failures;
		}
    }
	
    return failures;
}

static unsigned int TestDataBus(void)
{
	unsigned int failures = 0;
	
    unsigned chip;
    for(chip = 0; chip < countof(chips); ++chip)
    {
		const unsigned int failure_count = TestDataBusOnAddress((sdram_address)chips[chip].start);
		if (failure_count)
		{
			sdram_error("  DATA BUS FAIL on chip %u\n", chip);
			failures += failure_count;
		}
    }
	return failures;
}

static unsigned int UniquePattern(unsigned int offset, unsigned char magic)
{
    const sdram_word pattern = 0xa5a5a5a5;
	return pattern ^ offset ^ (magic << 24);
}

static sdram_word UniqueAntipattern(unsigned int offset, unsigned char magic)
{
    const sdram_word antipattern = ~0xa5a5a5a5;
	return antipattern ^ offset ^ (magic << 24);
}

static unsigned int TestAddressBusOnChip(sdram_address start_address, unsigned num_bytes)
{
    const unsigned long address_mask = (num_bytes/sizeof(sdram_word))-1;
	const unsigned char magic = (((unsigned long)start_address) >> 24U);

	sdram_debug("Testing %u bytes at %x, mask=%x\n", num_bytes, start_address, address_mask);
    
    unsigned long offset;
    unsigned long test_offset;

	unsigned int failures = 0;

    // Write default pattern to power of two offsets.
    for(offset = 1; (offset & address_mask) != 0; offset <<= 1)
    {
		sdram_address target_address = MakeSafeAddress(start_address, offset);
		before_write(target_address);
		*target_address = UniquePattern(offset, magic);
    }

    test_offset = 0;
    *start_address = UniqueAntipattern(offset, magic);
    
    // Check for address bits stuck high
    for(offset = 1; (offset & address_mask) != 0; offset <<= 1)
    {
		sdram_address target_address = MakeSafeAddress(start_address, offset);
		sdram_debug("Stuck high test at %x+%x words = %x\n", start_address, offset, target_address);
		const sdram_word value = *target_address;
		if (value != UniquePattern(offset, magic))
		{
			sdram_error("*** Stuck high fail at 0x%x (read=0x%x, expected=0x%x)\n", target_address, value, UniquePattern(offset, magic));
			++failures;
		}
    }

	before_write(start_address);
    *start_address = UniquePattern(0, magic+1);
	
    for(test_offset = 1; (test_offset & address_mask) != 0; test_offset <<= 1)
    {
		sdram_address test_target_address = MakeSafeAddress(start_address, test_offset);

		before_write(test_target_address);
		*test_target_address = UniqueAntipattern(test_offset, magic+1);

		{
			const sdram_word value = *start_address;
			if (value != UniquePattern(0, magic+1))
			{
				sdram_error("*** Stuck low fail at 0x%x, test_offset=0x%x (read=0x%x, expected=0x%x)\n", test_target_address, test_offset, value, UniquePattern(0, magic+1));
				++failures;
			}
		}
		
		for(offset = 1; (offset & address_mask) != 0; offset <<= 1)
		{
			sdram_address target_address = MakeSafeAddress(start_address, offset);
			const sdram_word value = *target_address;

			if (offset != test_offset)
			{
				if (value != UniquePattern(offset, magic))
				{
					sdram_error("*** Short fail at 0x%x read=0x%x, expected=0x%x\n", test_target_address, value, UniquePattern(offset, magic));
					++failures;
				}
			}
			else
			{
				if (value != UniqueAntipattern(offset, magic+1))
				{
					sdram_error("*** Short fail with match at 0x%x read=0x%x, expected=0x%x\n", test_target_address, value, UniqueAntipattern(offset, magic+1));
					++failures;
				}
			}
		}

		ensure_before_write(test_target_address);
		*test_target_address = UniquePattern(test_offset, magic);
    }

	return failures;
}    

static unsigned int TestAddressBus(void)
{
	unsigned int failures = 0;
	
    unsigned chip;
    for(chip = 0; chip < countof(chips); ++chip)
    {
		unsigned int failure_count = TestAddressBusOnChip((sdram_address)chips[chip].start, chips[chip].size);
		if (failure_count != 0)
		{
			sdram_error("  FAIL on chip pair %u: Address 0x%x\n", chip);
			failures += failure_count;
		}
    }

	// Check aliasing
	failures += TestAddressBusOnChip((sdram_address)chips[0].start, chips[0].size + chips[1].size);

	return failures;
}

unsigned int sdram_MemoryTestQuick(void)
{
	unsigned int failures = 0;
	
	printf("Testing memory: ");
	
    failures += TestDataBus();
    failures += TestAddressBus();

	apply_fixups();

	printf("done\n");

	return failures;
}
