#include "fw.h"
#include "io.h"
#include "sdcard.h"
#include "abcio.h"
#include "sys.h"
#include "console.h"

#define DEBUG     0
#define MINITESTS 1
#define DELAY     1

void __hot con_print_hex(unsigned int n)
{
    for (int i = 0; i < 8; i++) {
	unsigned int c = n >> 28;
	n <<= 4;
	con_putc(c + ((c >= 10) ? 'a'-10 : '0'));
    }
}

/* Don't mark no_return or gcc moves it to SDRAM */
static void __hot __text_hot killed(const char *how, size_t pc)
{
    /* Cannot use con_printf() here */
    const uint16_t *pcp;
    size_t mtval;

    asm volatile("csrr %0,mtval" : "=r" (mtval));

    /* Try to move back to the previous instruction (if not a jump...) */
    pc += -4 + (pc & 1);
    pcp = (const uint16_t *)pc;

    con_puts(hotstr("ERROR: "));
    con_puts(how);
    con_puts(hotstr(" at 0x"));
    con_print_hex(pc);
    con_puts(hotstr(" (0x"));
    con_print_hex((pcp[1] << 16) + pcp[0]);
    con_puts(hotstr(")\nBad address: 0x"));
    con_print_hex(mtval);
    con_putc('\n');

    for (int i = 0; i < 32; i += 8) {
	for (int j = 0; j < 8; j++) {
	    uint32_t v = rdxreg(i+j);
	    con_print_hex(v);
	    con_putc((j == 7) ? '\n' : ' ');
	}
    }

    con_flush();
    udelay(5000000);
    reset(SYS_RESET_SOFT);
}

IRQHANDLER(buserr,0)
{
    killed(hotstr("misaligned"), pc);
}

IRQHANDLER(ebreak,0)
{
    killed(hotstr("invalid instruction"), pc);
}

volatile __sbss uint32_t timer_irq_count;
IRQHANDLER(sysclock,0)
{
    uint32_t count = timer_irq_count;
    count++;
    timer_irq_count = count;
    if ( MINITESTS ) {
	static const char spinner[4] = "/-\\|";
	if (!(count & (TIMER_HZ-1))) {
	    uint32_t seconds = count >> TIMER_SHIFT;
	    CON_DATA = spinner[seconds & 3];
	    CON_DATA = '\b';
	}
    }
}

static void late_init(void);
static void hello_sdram(void);

#define TEST_DATA_0 0x01234567
#define TEST_DATA_1 0x98badcfe

static const volatile __dram_data
uint32_t test_data[2] = { TEST_DATA_0, TEST_DATA_1 };

uint32_t __sbss timer_irq_start;
void __hot init(void)
{
    static __string_hot const char hello[] =
	"\n\n*** Hello, World! ***\n"
	"MAX80 "
#ifdef TEST
	"testing"
#endif
	"firmware compiled on: ";

    timer_irq_start = rdtime();

    /* Start ROM copy engine, unmask timer and fatal exceptions */
    unmask_irqs((1U << ROMCOPY_IRQ)|(1U << EBREAK_IRQ)|
		(1U << BUSERR_IRQ)|(1U << SYSCLOCK_IRQ));

    con_puts(hello);
    con_puts(__datestamp);
    con_putc('\n');

    set_leds(7);
    wait_romcopy_done();

    if ( test_data[0] == TEST_DATA_0 && test_data[1] == TEST_DATA_1 ) {
	con_puts(hotstr("SDRAM seems ok - skipping test\n"));
    } else {
	extern uint32_t __dram_init_start[], __dram_init_end[];
	extern uint32_t __dram_bss_start[], __dram_bss_end[];
	volatile uint32_t *dp;
	uint32_t v;
	uint32_t wrerr;
	uint32_t not_zero, all_ones;
	uint32_t rx = 0x193ac604;

	v = wrerr = 0;
	all_ones  = -1;
	not_zero  = 0;

	for (dp = __dram_init_start; dp < __dram_init_end; dp++) {
	    uint32_t v1, v2;

	    v1 = *dp;
	    v += v1;

	    v2 = v1 ^ rx;
	    *dp = v2;

	    wrerr |= *dp ^ v2;

	    *dp = v1;

	    not_zero |= v1;
	    all_ones &= v1;
	    
	    rx *= 0xf4d5725f;
	}

	con_puts(hotstr("SDRAM data checksum: "));
	con_print_hex(v);
	con_puts(hotstr(" expected "));
	con_print_hex(__dram_checksum);
	con_putc('\n');

	con_puts(hotstr("Bits always set, clear: "));
	con_print_hex(all_ones);
	con_putc(' ');
	con_print_hex(~not_zero);
	con_putc('\n');

	con_puts(hotstr("Test data: "));
	con_print_hex(test_data[0]);
	con_putc(' ');
	con_print_hex(test_data[1]);
	con_putc('\n');
	
	if (wrerr)
	    con_puts(hotstr("SDRAM read/write error\n"));

	v = 0;
	for (dp = __dram_bss_start; dp < __dram_bss_end; dp++) {
	    uint32_t x = *dp;
	    v |= x;
	}

	if (v)
	    con_puts(hotstr("SDRAM .bss is not zero!\n"));
    }
    set_leds(6);

    con_flush();

    set_leds(5);
#if DELAY
    con_puts(hotstr("Waiting 5 s for testing..."));
    udelay(5000000);
    con_putc('\n');
    con_flush();
#endif

    if ( MINITESTS ) {
	const volatile uint32_t *p = (const volatile uint32_t *)hello_sdram;
	con_puts(hotstr("SDRAM jump test:"));
	for (int i = 0; i < 4; i++) {
	    con_putc(' ');
	    con_print_hex(p[i]);
	}
	con_puts(hotstr("\nJumping to SDRAM... "));
	hello_sdram();
	con_puts(hotstr("back in SRAM.\n"));
    }

    late_init();
}

static void __noinline hello_sdram(void)
{
    con_puts("in SDRAM... ");
}

volatile uint32_t __dram_bss test_dram[8];

static void __noinline late_init(void)
{
    /* This needs to be done as early as possible!!! */
    con_puts("Running abc_init_memmap: ");
    con_flush();
    abc_init_memmap();
    con_puts("ok\n");

    if (SYS_MAGIC != SYS_MAGIC_MAX80) {
	con_puts("Not a MAX80 board?!?!\n\n");
	_die();
    } else {
	con_printf("MAX80 ver %u.%u rework flags %02x fpga %u\n",
		   SYS_BOARDMAJOR, SYS_BOARDMINOR,
		   SYS_BOARDFIX, SYS_BOARDFPGA);
	if (SYS_BOARDMAJOR != SYS_BOARDFPGA) {
	    con_puts("Invalid FPGA firmware for this board revision\n");
	    _die();
	}
    }
    con_putc('\n');
    rom_print_serial();

    if ( MINITESTS ) {
	con_puts("Quick DRAM test:\n");
	for (int i = 0; i < 8; i++) {
	    uint32_t v = (i*0x11111111) + 0x44332211;
	    test_dram[i] = v;
	    (void)test_dram[i];	/* Force immediate readback */
	    con_printf("%08x ", v);
	}
	con_putc('\n');

	for (int i = 0; i < 8; i++) {
	    con_printf("%08x ", test_dram[i]);
	}
	con_puts("\n\nRandom number generator test:\n");
	for (int i = 0; i < 8; i++)
	    con_printf("%08x ", rdrand());
	con_puts("\n\n");
    }

    set_leds(4);

    read_rtc();
    rtc_abc_init();

    set_leds(3);

    sdcard_reset();
    disk_cache_init();
    abcdisk_init();

    set_leds(2);

    pun80_init();

    set_leds(1);


    abc_init();

    /* Release WAIT# if asserted */
    ABC_BUSCTL = 0;

    set_leds(0);
}
