#include "compiler.h"
#include "common.h"
#include "console.h"
#include "io.h"
#include "spiflash.h"
#include "ff.h"
#include "boardinfo_fpga.h"
#include <stdio.h>

#define SPIROM_DUAL_MODE 1

struct board_info board_info;
__dram_noinit union board_info_block board_info_raw;
static __dram_noinit union board_info_block board_info_rom;
volatile bool do_update_boardinfo;

static void boardinfo_init(void);

static void __hot romcopy_download_absolute(void *dst, size_t offset, size_t len)
{
    unsigned int cmd;
    unsigned int flags = ROMCOPY_SPI_CMDLEN(5) | ROMCOPY_WRITE_RAM;

    if (!len)
	return;

    if (SPIROM_DUAL_MODE) {
	cmd = ROM_FAST_READ_DUAL;
	flags |= ROMCOPY_SPI_DUAL;
    } else {
	cmd = ROM_FAST_READ;
    }

    ROMCOPY_RAMADDR = (size_t)dst;
    ROMCOPY_ROMCMD  = offset + (cmd << 24);
    ROMCOPY_DATALEN = len | flags;
}

void __hot romcopy_download(void *dst, size_t offset, size_t len)
{
    romcopy_download_absolute(dst, __rom_offset + offset, len);
}

void __hot romcopy_bzero(void *dst, size_t len)
{
    if (!len)
	return;

    ROMCOPY_RAMADDR = (size_t)dst;
    ROMCOPY_ROMCMD  = 0;
    ROMCOPY_DATALEN = len | ROMCOPY_ZERO_BUFFER | ROMCOPY_WRITE_RAM;
}

static void __hot romcopy_download_boardinfo(void *dst)
{
    romcopy_download_absolute(dst, BOARDINFO_ADDR, BOARDINFO_SIZE);
}

/*
 * Convert the MAC address in board_info_raw into a hexadecimal string
 * and set it as the USB serial number.
 */
static inline void rom_mangle_serial(void)
{
    volatile uint32_t *udp = &usbdesc_rom[2];
    const uint8_t *mac = board_info.mac[0];

    for (int i = 0; i < 6; i++) {
	unsigned int v = *mac++;
	unsigned int c;

	c = (v >> 4)+'0';
	if (c > '9')
	    c += 'A'-'9'-1;

	udp[0] = c;

	c = (v & 15)+'0';
	if (c > '9')
	    c += 'A'-'9'-1;

	udp[2] = c;

	/* Skip dash between third and fourth byte */
	udp += (i == 2) ? 6 : 4;
    }
}

static inline __hot void romcopy_config_flash(void)
{
    /* Enable writing volatile status register bits */
    ROMCOPY_ROMCMD = ROM_VOLATILE_SR_WRITE_ENABLE << 24;
    ROMCOPY_DATALEN = ROMCOPY_SPI_CMDLEN(1);
    waitfor(ROMCOPY_IRQ);

    /* Write SR3 = 0; this sets the drive strength to maximum */
    ROMCOPY_ROMCMD = ROM_WRITE_SR3 << 24;
    ROMCOPY_DATALEN = ROMCOPY_SPI_CMDLEN(2);
    waitfor(ROMCOPY_IRQ);
}

IRQHANDLER(romcopy,0)
{
    static __sbss unsigned int romcopy_state;
    size_t len;

    switch (romcopy_state++) {
    case 0:
	/* Clear esplink as quickly as possible */
	len = __esplink_end - __esplink_start;
	romcopy_bzero(__esplink_start, len);
	break;

    case 1:
	/* Condition flash ROM */
	romcopy_config_flash();

	/* Start copy DRAM data */
	len = __dram_init_end - __dram_init_start;
	romcopy_download(__dram_init_start, 0, len);
	break;

    case 2:
	/* Copy board_info from ROM into board_info_raw */
	romcopy_download_boardinfo(&board_info_raw);
	break;

    case 3:
	/* Copy reference copy of board_info ROM block into board_info_rom */
	romcopy_download_boardinfo(&board_info_rom);

	/* Create sanitized board_info structure */
	boardinfo_init();
	break;

    case 4:
	/* Zero .dram.bss */
	len = __dram_bss_end - __dram_bss_start;
	romcopy_bzero(__dram_bss_start, len);
	break;

    default:
	mask_irq(ROMCOPY_IRQ);
	break;
    }
}

/*
 * SPI flash parameters and routines (for spiflash.c)
 */
static const struct spiflash_ops max80_spiflash_ops;
static const struct spiflash_param max80_spiflash_param;

/*
 * SPI flash operations
 */
static int max80_spi_write_buffer(const void *buf, unsigned int buflen,
				  bool more)
{
    const uint8_t *p = buf;
    uint32_t cmd = 0;
    unsigned int bytecmd = 0;
    unsigned int bitpos = 24;

    while (buflen) {
	cmd |= *p++ << bitpos;
	buflen--;
	bitpos -= 8;
	bytecmd += ROMCOPY_SPI_CMDLEN(1);

	if (!(buflen & 3)) {
	    if (buflen || more)
		bytecmd |= ROMCOPY_SPI_MORE;
	    waitfor(ROMCOPY_IRQ);
	    ROMCOPY_ROMCMD  = cmd;
	    ROMCOPY_DATALEN = bytecmd;
	    bytecmd = cmd = 0;
	    bitpos = 24;
	}
    }

    return 0;
}

static int max80_spi_read_buffer(void *buf, unsigned int buflen, bool more)
{
    uint8_t *p = buf;
    unsigned int bytecmd;
    uint32_t v;

    if (!buflen)
	return 0;

    waitfor(ROMCOPY_IRQ);
    ROMCOPY_ROMCMD = 0;

    while (buflen > 4) {
	ROMCOPY_DATALEN = ROMCOPY_SPI_CMDLEN(4) | ROMCOPY_SPI_MORE;
	waitfor(ROMCOPY_IRQ);
	v = ROMCOPY_INPUT;
	p[0] = v >> 24;
	p[1] = v >> 16;
	p[2] = v >> 8;
	p[3] = v;
	p += 4;
	buflen -= 4;
    }

    bytecmd = ROMCOPY_SPI_CMDLEN(buflen);
    if (more)
	bytecmd |= ROMCOPY_SPI_MORE;

    ROMCOPY_DATALEN = bytecmd;
    waitfor(ROMCOPY_IRQ);
    v = ROMCOPY_INPUT;
    while (buflen) {
	p[--buflen] = v;
	v >>= 8;
    }
    return 0;
}

static int max80_spi_write(void *cookie,
			   const void *cmd, unsigned int cmd_len,
			   const void *data, unsigned int data_len,
			   int tshsl)
{
    (void)cookie;
    (void)tshsl;		/* Enforced in hardware */

    waitfor(ROMCOPY_IRQ);
    udelay(1);

    if (cmd_len)
	max80_spi_write_buffer(cmd, cmd_len, !!data_len);

    if (data_len)
	max80_spi_write_buffer(data, data_len, false);

    return 0;
}

static int max80_spi_read(void *cookie,
		    const void *cmd, unsigned int cmd_len,
		    void *data, unsigned int data_len,
		    int tshsl)
{
    (void)cookie;
    (void)tshsl;		/* Enforced in hardware */

    waitfor(ROMCOPY_IRQ);
    udelay(1);

    if (cmd_len)
	max80_spi_write_buffer(cmd, cmd_len, !!data_len);

    if (data_len)
	max80_spi_read_buffer(data, data_len, false);

    return 0;
}

static const struct spiflash_ops max80_spiflash_ops = {
    .spi_write  = max80_spi_write,
    .spi_read   = max80_spi_read,
    .yield      = NULL		/* Nothing to yield to... */
};

/* Winbond W25Q128JV @ 3.3V */
#define NS(x) (((x)*(unsigned long long)CPU_HZ + 999999999ULL)/1000000000ULL)
#define US(x) (((x)*(unsigned long long)CPU_HZ + 999999ULL)/1000000ULL)
#define MS(x) (((x)*(unsigned long long)CPU_HZ + 999ULL)/1000ULL)
static const struct spiflash_param max80_spiflash_param = {
    /*
     * For W25Q128JV _DYNAMIC is equivalent to _24BIT, but specifying
     * it as dynamic would most of the time support larger parts
     * transparently.
     */
    .addr   = SPIFLASH_ADDR_DYNAMIC,

    .tshsl  = NS(3),
    .tshsl1 = NS(10),
    .tshsl2 = NS(50),

    .trst   = NS(30000),
    .tw     = MS(10),		/* persistent; typ, max = 15 */
    .tpp    = NS(400),		/* typ, max = 3000 */
    .tse    = MS(45),		/* typ, max = 400 */
    .tbe1   = MS(120),		/* typ, max = 1600 */
    .tbe2   = MS(150),		/* typ, max = 2000 */
    .tce    = MS(40000),	/* typ, max = 200000 */
};

static void rom_spiflash_init(struct spiflash *flash)
{
    memset(flash, 0, sizeof *flash);
    flash->target = board_info.version_str;
    flash->param  = &max80_spiflash_param;
    flash->ops    = &max80_spiflash_ops;
}

/*
 * Flash an image into SPI flash, and reload the FPGA if successful,
 * returns on failure only.
 */
void rom_flash_from_memory(void *buf, size_t buflen)
{
    struct spiflash max80_flash;

    rom_spiflash_init(&max80_flash);

    if (spiflash_flash_file(&max80_flash, buf, buflen)) {
	con_puts("update: flash update data invalid\n");
	return;
    }

    /* Now do it for real */
    max80_flash.ops = &max80_spiflash_ops;

    if (spiflash_flash_file(&max80_flash, buf, buflen)) {
	con_puts("update: flash update failed\n");
	return;
    }

    con_puts("update: flash complete, restarting in 500 ms...\n");
    udelay(500000);

    shutdown(SYS_RESET_RECONFIG);
}

static int rom_sdcard_read_data(void *cookie, void *buf, unsigned int bufsize)
{
    unsigned int bytesread;
    FRESULT fr = f_read(cookie, buf, bufsize, &bytesread);

    return fr == FR_OK ? bytesread : 0;
}

/*
 * Flash an image from an SD card, and reload the FPGA if successful
 */
static int rom_flash_from_sdcard_file(const char *filename)
{
    struct spiflash max80_flash;
    char *fw_file_name = NULL;
    FRESULT fr = FR_NO_FILE;
    FILINFO fno;
    FIL f;

    fr = f_stat(filename, &fno);
    if (fr != FR_OK || (fno.fattrib & AM_DIR))
	return 0;		/* No such firmware file found but no error */

    con_printf("update: firmware update file %s found\n", filename);

    /* Rename the firmware file to avoid repeated updates */
    size_t l = strlen(filename);
    fw_file_name = malloc(l+5);
    if (!fw_file_name)
	goto err;
    memcpy(fw_file_name, filename, l);

    for (unsigned int i = 1; i < 1000; i++) {
	snprintf(fw_file_name+l, 5, ".%03u", i);

	fr = f_rename(filename, fw_file_name);
	if (fr != FR_EXIST)
	    break;
    }

    if (fr != FR_OK) {
	con_puts("update: unable to rename update file, skipping\n");
	goto err;
    }

    con_printf("update: renamed %s -> %s\n", filename, fw_file_name);

    rom_spiflash_init(&max80_flash);
    max80_flash.read_data = rom_sdcard_read_data;
    max80_flash.cookie    = &f;

    fr = f_open(&f, fw_file_name, FA_READ);
    if (fr != FR_OK) {
	con_puts("update: cannot open file, terminating\n");
	goto err;
    }

    if (spiflash_flash_file(&max80_flash, NULL, 0)) {
	con_puts("update: flash update data invalid\n");
	goto err;
    }

    /* Now do it for real */
    f_rewind(&f);
    max80_flash.ops = &max80_spiflash_ops;

    if (spiflash_flash_file(&max80_flash, NULL, 0)) {
	con_puts("update: flash update failed\n");
	goto err;
    }

    f_close(&f);

    con_puts("update: flash complete, restarting in 500 ms...\n");
    udelay(500000);

    shutdown(SYS_RESET_RECONFIG);

err:
    if (fr == FR_OK)
	f_close(&f);
    if (fw_file_name)
	free(fw_file_name);
    return -1;
}

/*
 * Check for a firmware upgrade file on the sd card
 */
void rom_flash_from_sdcard(void)
{
    static char versioned_filename[] = "/max80/v?.fw";

    /* Look for version-specific firmware file */
    versioned_filename[8] = SYS_BOARDFPGA + '0';
    rom_flash_from_sdcard_file(versioned_filename);

    /* Look for universal firmware file */
    rom_flash_from_sdcard_file("/max80/max80.fw");
}

static bool boardinfo_valid(const struct board_info *bi)
{
    size_t len = bi->len;

    /* XXX: check CRC */

    return len >= 16 && len <= BOARDINFO_SIZE &&
	bi->magic[0] == BOARDINFO_MAGIC_1 &&
	bi->magic[1] == BOARDINFO_MAGIC_2;
}

/*
 * Initialize the board_info structure from board_info_raw
 */
static void boardinfo_init(void)
{
    const struct board_info *src = &board_info_raw.i;
    size_t len = src->len;

    if (!boardinfo_valid(src))
	len = 0;		/* Bad structure */

    size_t rlen = Min(len, sizeof board_info);
    memcpy(&board_info, src, rlen);

    /* Erase any fields not included in ROM */
    if (rlen < len)
	memset((char *)&board_info + rlen, 0, sizeof board_info - rlen);

    /*
     * If no board version is specified, use the FPGA version as a
     * resonable guess.  This might be useful if the FPGA firmware has
     * been uploaded via JTAG.
     */
    if (!board_info.version_str[0]) {
	snprintf(board_info.version_str, sizeof board_info.version_str,
		 "MAX80 v%u", SYS_BOARDFPGA);
    }

    /* Convert serial number and export to USB */
    rom_mangle_serial();
}

/*
 * Update board_info if board_info_raw has changed, and write it to flash
 * in that case.
 */
void rom_update_boardinfo(void)
{
    struct spiflash max80_flash;
    union board_info_block *src = &board_info_raw;

    do_update_boardinfo = false;

    if (!boardinfo_valid(&src->i)) {
	con_printf("rom: bad board_info received\n");
	return;
    }

    if (!memcmp(&board_info_rom, src, src->i.len)) {
	con_printf("rom: board_info in flash unchanged\n");
	return;
    }

    con_printf("rom: updating board_info in flash\n");

    memset(&src->b[src->i.len], 0xff, BOARDINFO_SIZE - src->i.len);

    rom_spiflash_init(&max80_flash);
    spiflash_flash_data(&max80_flash, BOARDINFO_ADDR, src, BOARDINFO_SIZE);
    romcopy_download_boardinfo(&board_info_rom);
    boardinfo_init();
    waitfor(ROMCOPY_IRQ);
}
