/*
 * Copyright (C) 2016 FAUmachine Team <info@faumachine.org>.
 * This program is free software. You can redistribute it and/or modify it
 * under the terms of the GNU General Public License, either version 2 of
 * the License, or (at your option) any later version. See COPYING.
 */

#define DEBUG_CONTROL_FLOW	0

#include <assert.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>

#include "system.h"
#include "conv_s19.h"
#include "umutil.h"
#include "glue.h"

#include "chip_numonyx_n25q128.h"

#define CHIP_(x) chip_numonyx_n25q128_ ## x

struct cpssp {
	struct sig_std_logic *port_dq1;
	unsigned int state_gnd;
	unsigned int state_vcc;
	unsigned int state_s;
	unsigned int state_c;
	unsigned int state_dq0;

	uint8_t flash[16*1024*1024];
	struct storage *media;

	uint8_t ibyte;
	uint8_t ibytelen;
	uint8_t obyte;
	uint8_t obytelen;

	uint8_t cmd;
	uint8_t cmdlen;
	uint32_t param;
	uint8_t paramlen;

	/* Legacy SPI Status Register, 6.1, 34 */
	/* ... */
	uint8_t wel;
	/* ... */
};

/*
 * Page Program (PP, 0x02)
 * 9.1.11, 90
 */
static uint8_t
CHIP_(pp_start)(struct cpssp *cpssp)
{
	uint8_t byte;

	if (DEBUG_CONTROL_FLOW) {
		fprintf(stderr, "%s: -> ", __FUNCTION__);
	}

	cpssp->param = 0;
	cpssp->paramlen = 0;

	/* No data. */
	byte = 0x00;

	if (DEBUG_CONTROL_FLOW) {
		fprintf(stderr, "0x%02x\n", byte);
	}

	return byte;
}

static uint8_t
CHIP_(pp_cont)(struct cpssp *cpssp, uint8_t byte)
{
	if (DEBUG_CONTROL_FLOW) {
		fprintf(stderr, "%s: param=0x%06x, byte=0x%02x -> ",
				__FUNCTION__, cpssp->param, byte);
	}

	if (cpssp->paramlen < 24) {
		cpssp->param <<= 8;
		cpssp->param |= byte;
		cpssp->paramlen += 8;

	} else {
		cpssp->flash[cpssp->param] = byte;
		cpssp->param = (cpssp->param + 1) % sizeof(cpssp->flash);

		cpssp->wel = 0; /* FIXME */
	}

	/* No data. */
	byte = 0x00;

	if (DEBUG_CONTROL_FLOW) {
		fprintf(stderr, "0x%02x\n", byte);
	}

	return byte;
}

/*
 * Read (READ, 0x03)
 */
static uint8_t
CHIP_(read_start)(struct cpssp *cpssp)
{
	uint8_t byte;

	if (DEBUG_CONTROL_FLOW) {
		fprintf(stderr, "%s: -> ", __FUNCTION__);
	}

	cpssp->param = 0;
	cpssp->paramlen = 0;

	byte = 0x00;

	if (DEBUG_CONTROL_FLOW) {
		fprintf(stderr, "0x%02x\n", byte);
	}

	return byte;
}

static uint8_t
CHIP_(read_cont)(struct cpssp *cpssp, uint8_t byte)
{
	if (DEBUG_CONTROL_FLOW) {
		fprintf(stderr, "%s: param=0x%06x, byte=0x%02x -> ",
				__FUNCTION__, cpssp->param, byte);
	}

	if (cpssp->paramlen < 24) {
		cpssp->param <<= 8;
		cpssp->param |= byte;
		cpssp->paramlen += 8;
	}
	if (cpssp->paramlen < 24) {
		byte = 0x00;

	} else {
		byte = cpssp->flash[cpssp->param];
		cpssp->param = (cpssp->param + 1) % sizeof(cpssp->flash);
	}

	if (DEBUG_CONTROL_FLOW) {
		fprintf(stderr, "0x%02x\n", byte);
	}

	return byte;
}

/*
 * Write Disable (WRDI, 0x04)
 */
static uint8_t
CHIP_(wrdi_start)(struct cpssp *cpssp)
{
	uint8_t byte;

	if (DEBUG_CONTROL_FLOW) {
		fprintf(stderr, "%s: -> ", __FUNCTION__);
	}

	/* Write disable. */
	cpssp->wel = 0;

	/* No data. */
	byte = 0x00;

	if (DEBUG_CONTROL_FLOW) {
		fprintf(stderr, "0x%02x\n", byte);
	}

	return byte;
}

static uint8_t
CHIP_(wrdi_cont)(struct cpssp *cpssp, uint8_t byte)
{
	if (DEBUG_CONTROL_FLOW) {
		fprintf(stderr, "%s: param=0x%06x, byte=0x%02x -> ",
				__FUNCTION__, cpssp->param, byte);
	}

	/* No data. */
	byte = 0x00;

	if (DEBUG_CONTROL_FLOW) {
		fprintf(stderr, "0x%02x\n", byte);
	}

	return byte;
}

/*
 * Read Status Register (RDSR, 0x05)
 * 9.1.22, 103
 */
static uint8_t
CHIP_(rdsr_start)(struct cpssp *cpssp)
{
	uint8_t byte;

	if (DEBUG_CONTROL_FLOW) {
		fprintf(stderr, "%s: -> ", __FUNCTION__);
	}

	byte = 0;
	/* FIXME */
	byte |= cpssp->wel << 1;
	/* FIXME */

	if (DEBUG_CONTROL_FLOW) {
		fprintf(stderr, "0x%02x\n", byte);
	}

	return byte;
}

static uint8_t
CHIP_(rdsr_cont)(struct cpssp *cpssp, uint8_t byte)
{
	if (DEBUG_CONTROL_FLOW) {
		fprintf(stderr, "%s: param=0x%06x, byte=0x%02x -> ",
				__FUNCTION__, cpssp->param, byte);
	}

	byte = 0;
	/* FIXME */
	byte |= cpssp->wel << 1;
	/* FIXME */

	if (DEBUG_CONTROL_FLOW) {
		fprintf(stderr, "0x%02x\n", byte);
	}

	return byte;
}

/*
 * Write Enable (WREN, 0x06)
 */
static uint8_t
CHIP_(wren_start)(struct cpssp *cpssp)
{
	uint8_t byte;

	if (DEBUG_CONTROL_FLOW) {
		fprintf(stderr, "%s: -> ", __FUNCTION__);
	}

	/* Write enable. */
	cpssp->wel = 1;

	/* No data. */
	byte = 0x00;

	if (DEBUG_CONTROL_FLOW) {
		fprintf(stderr, "0x%02x\n", byte);
	}

	return byte;
}

static uint8_t
CHIP_(wren_cont)(struct cpssp *cpssp, uint8_t byte)
{
	if (DEBUG_CONTROL_FLOW) {
		fprintf(stderr, "%s: param=0x%06x, byte=0x%02x -> ",
				__FUNCTION__, cpssp->param, byte);
	}

	/* No data. */
	byte = 0x00;

	if (DEBUG_CONTROL_FLOW) {
		fprintf(stderr, "0x%02x\n", byte);
	}

	return byte;
}

/*
 * Fast Read (FAST_READ, 0x0b)
 */
static uint8_t
CHIP_(fast_read_start)(struct cpssp *cpssp)
{
	uint8_t byte;

	if (DEBUG_CONTROL_FLOW) {
		fprintf(stderr, "%s: -> ", __FUNCTION__);
	}

	cpssp->param = 0;
	cpssp->paramlen = 0;

	byte = 0x00;

	if (DEBUG_CONTROL_FLOW) {
		fprintf(stderr, "0x%02x\n", byte);
	}

	return byte;
}

static uint8_t
CHIP_(fast_read_cont)(struct cpssp *cpssp, uint8_t byte)
{
	if (DEBUG_CONTROL_FLOW) {
		fprintf(stderr, "%s: param=0x%06x, byte=0x%02x -> ",
				__FUNCTION__, cpssp->param, byte);
	}

	if (cpssp->paramlen < 24) {
		cpssp->param <<= 8;
		cpssp->param |= byte;
		cpssp->paramlen += 8;
		byte = 0x00;

	} else if (cpssp->paramlen < 32) {
		/* One byte dummy... */
		cpssp->paramlen += 8;
	}
	if (cpssp->paramlen < 32) {
		byte = 0x00;

	} else {
		byte = cpssp->flash[cpssp->param];
		cpssp->param = (cpssp->param + 1) % sizeof(cpssp->flash);
	}

	if (DEBUG_CONTROL_FLOW) {
		fprintf(stderr, "0x%02x\n", byte);
	}

	return byte;
}

/*
 * Read Identification (RDID, 0x9e/0x9f)
 */
static const uint8_t CHIP_(rdid_data)[20] = {
	0x20, 0xba, 0x18, 0x10,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};

static uint8_t
CHIP_(rdid_start)(struct cpssp *cpssp)
{
	uint8_t byte;

	if (DEBUG_CONTROL_FLOW) {
		fprintf(stderr, "%s: -> ", __FUNCTION__);
	}

	cpssp->param = 0;
	cpssp->paramlen = 8;

	byte = CHIP_(rdid_data)[cpssp->param++];

	if (DEBUG_CONTROL_FLOW) {
		fprintf(stderr, "0x%02x\n", byte);
	}

	return byte;
}

static uint8_t
CHIP_(rdid_cont)(struct cpssp *cpssp, uint8_t byte)
{
	if (DEBUG_CONTROL_FLOW) {
		fprintf(stderr, "%s: param=0x%06x, byte=0x%02x -> ",
				__FUNCTION__, cpssp->param, byte);
	}

	if (cpssp->param < sizeof(CHIP_(rdid_data))) {
		byte = CHIP_(rdid_data)[cpssp->param++];

	} else {
		byte = 0x00;
	}

	if (DEBUG_CONTROL_FLOW) {
		fprintf(stderr, "0x%02x\n", byte);
	}

	return byte;
}

/*
 * Sector Erase (SE, 0xd8)
 */
static uint8_t
CHIP_(se_start)(struct cpssp *cpssp)
{
	uint8_t byte;

	if (DEBUG_CONTROL_FLOW) {
		fprintf(stderr, "%s: -> ", __FUNCTION__);
	}

	cpssp->paramlen = 0;

	/* No data. */
	byte = 0x00;

	if (DEBUG_CONTROL_FLOW) {
		fprintf(stderr, "0x%02x\n", byte);
	}

	return byte;
}

static uint8_t
CHIP_(se_cont)(struct cpssp *cpssp, uint8_t byte)
{
	if (DEBUG_CONTROL_FLOW) {
		fprintf(stderr, "%s: param=0x%06x, byte=0x%02x -> ",
				__FUNCTION__, cpssp->param, byte);
	}

	if (cpssp->paramlen < 24) {
		cpssp->param <<= 8;
		cpssp->param |= byte;
		cpssp->paramlen += 8;

		if (cpssp->paramlen == 24) {
			/* Erase... - FIXME */
			cpssp->wel = 0;
		}

	}

	/* No data. */
	byte = 0x00;

	if (DEBUG_CONTROL_FLOW) {
		fprintf(stderr, "0x%02x\n", byte);
	}
	
	return byte;
}

static uint8_t
CHIP_(byte)(struct cpssp *cpssp, uint8_t byte)
{
	static const struct {
		uint8_t cmd;
		const char *name;
		uint8_t (*start)(struct cpssp *cpssp);
		uint8_t (*cont)(struct cpssp *cpssp, uint8_t byte);
	} table[] = {
		{ 0x02, "PP", CHIP_(pp_start), CHIP_(pp_cont), },
		{ 0x03, "READ", CHIP_(read_start), CHIP_(read_cont), },
		{ 0x04, "WRDI", CHIP_(wrdi_start), CHIP_(wrdi_cont), },
		{ 0x05, "RDSR", CHIP_(rdsr_start), CHIP_(rdsr_cont), },
		{ 0x06, "WREN", CHIP_(wren_start), CHIP_(wren_cont), },
		{ 0x0b, "FAST_READ", CHIP_(fast_read_start), CHIP_(fast_read_cont), },
		{ 0x9e, "RDID", CHIP_(rdid_start), CHIP_(rdid_cont), },
		{ 0x9f, "RDID", CHIP_(rdid_start), CHIP_(rdid_cont), },
		{ 0xd8, "SE", CHIP_(se_start), CHIP_(se_cont), },
	};
	int i;

	if (cpssp->cmdlen == 0) {
		cpssp->cmd = byte;
		cpssp->cmdlen = 8;
		for (i = 0; ; i++) {
			if (i == sizeof(table) / sizeof(table[0])) {
				fprintf(stderr, "%s: Unknown command 0x%02x.\n",
						__FUNCTION__, byte);
				assert(0); /* FIXME */
			}
			if (table[i].cmd == byte) {
				break;
			}
		}
		byte = (*table[i].start)(cpssp);

	} else {
		for (i = 0; ; i++) {
			if (i == sizeof(table) / sizeof(table[0])) {
				assert(0); /* FIXME */
			}
			if (table[i].cmd == cpssp->cmd) {
				break;
			}
		}
		byte = (*table[i].cont)(cpssp, byte);
	}

	return byte;
}

static void
CHIP_(reset)(struct cpssp *cpssp)
{
	cpssp->cmdlen = 0;
}

static void
CHIP_(gnd_set)(void *_cpssp, unsigned int val)
{
	struct cpssp *cpssp = _cpssp;

	cpssp->state_gnd = val;
}

static void
CHIP_(vcc_set)(void *_cpssp, unsigned int val)
{
	struct cpssp *cpssp = _cpssp;

	if (cpssp->state_vcc == val) {
		return;
	}
	cpssp->state_vcc = val;

	if (val) {
		CHIP_(reset)(cpssp);
	}
}

static void
CHIP_(n_s_set)(void *_cpssp, unsigned int nval)
{
	struct cpssp *cpssp = _cpssp;

	if (cpssp->state_s == ! nval) {
		return;
	}
	cpssp->state_s = ! nval;

	if (cpssp->state_s) {
		cpssp->cmd = 0x00;
		cpssp->cmdlen = 0;
		cpssp->param = 0x00000000;
		cpssp->paramlen = 0;
	}
}

static void
CHIP_(c_set)(void *_cpssp, unsigned int val)
{
	struct cpssp *cpssp = _cpssp;
	int bit;

	if (cpssp->state_c == val) {
		return;
	}
	cpssp->state_c = val;

	if (! cpssp->state_s) {
		/* Not selected... */
		return;
	}

	if (val) {
		/* Rising Edge */
		cpssp->ibyte <<= 1;
		cpssp->ibyte |= cpssp->state_dq0;
		cpssp->ibytelen++;
		if (cpssp->ibytelen == 8) {
			cpssp->obyte = CHIP_(byte)(cpssp, cpssp->ibyte);
			cpssp->ibytelen = 0;
			cpssp->obytelen = 8;
		}
	} else {
		/* Falling Edge */
		if (0 < cpssp->obytelen) {
			bit = (cpssp->obyte >> 7) & 1;
			cpssp->obyte <<= 1;
			cpssp->obytelen--;
		} else {
			bit = 0;
		}
		sig_std_logic_set(cpssp->port_dq1, cpssp,
				bit ? SIG_STD_LOGIC_1 : SIG_STD_LOGIC_0);
	}
}

static void
CHIP_(dq0_set)(void *_cpssp, unsigned int val)
{
	struct cpssp *cpssp = _cpssp;

	cpssp->state_dq0 = val;
}

void *
CHIP_(create)(
	const char *name,
	const char *img,
	struct sig_manage *manage,
	struct sig_std_logic *port_gnd,
	struct sig_std_logic *port_vcc,
	struct sig_std_logic *port_n_s,
	struct sig_std_logic *port_c,
	struct sig_std_logic *port_dq0,
	struct sig_std_logic *port_dq1,
	struct sig_std_logic *port_n_w_dq2,
	struct sig_std_logic *port_n_hold_dq3
)
{
	static const struct sig_std_logic_funcs gnd_funcs = {
		.boolean_or_set = CHIP_(gnd_set),
	};
	static const struct sig_std_logic_funcs vcc_funcs = {
		.boolean_or_set = CHIP_(vcc_set),
	};
	static const struct sig_std_logic_funcs n_s_funcs = {
		.boolean_or_set = CHIP_(n_s_set),
	};
	static const struct sig_std_logic_funcs c_funcs = {
		.boolean_or_set = CHIP_(c_set),
	};
	static const struct sig_std_logic_funcs dq0_funcs = {
		.boolean_or_set = CHIP_(dq0_set),
	};
	struct cpssp *cpssp;
	int ret;

	cpssp = shm_alloc(sizeof(*cpssp));
	assert(cpssp);

	system_name_push(name);

	cpssp->media = storage_create(system_path(),
			sizeof(cpssp->flash), buildpath(ROMDIR, img),
			conv_s19_open, conv_s19_close, conv_s19_read);
	assert(cpssp->media);

	ret = storage_read(cpssp->media, cpssp->flash,
			sizeof(cpssp->flash), 0);
	assert(ret == sizeof(cpssp->flash));

	/* Hack to set MAC address for EV3. FIXME */
	cpssp->flash[sizeof(cpssp->flash) - 64*1024 + 0] = 0x02; /* Don't set bit 0! */
	cpssp->flash[sizeof(cpssp->flash) - 64*1024 + 1] = 0x03;
	cpssp->flash[sizeof(cpssp->flash) - 64*1024 + 2] = 0x04;
	cpssp->flash[sizeof(cpssp->flash) - 64*1024 + 3] = 0x05;
	cpssp->flash[sizeof(cpssp->flash) - 64*1024 + 4] = 0x06;
	cpssp->flash[sizeof(cpssp->flash) - 64*1024 + 5] = 0x07;

	/* Call */
	/* Out */
	cpssp->port_dq1 = port_dq1;
	sig_std_logic_connect_out(port_dq1, cpssp, SIG_STD_LOGIC_Z);

	/* In */
	cpssp->state_gnd = 0;
	sig_std_logic_connect_in(port_gnd, cpssp, &gnd_funcs);

	cpssp->state_vcc = 0;
	sig_std_logic_connect_in(port_vcc, cpssp, &vcc_funcs);

	cpssp->state_s = 0;
	sig_std_logic_connect_in(port_n_s, cpssp, &n_s_funcs);

	cpssp->state_c = 0;
	sig_std_logic_connect_in(port_c, cpssp, &c_funcs);

	cpssp->state_dq0 = 0;
	sig_std_logic_connect_in(port_dq0, cpssp, &dq0_funcs);

	system_name_pop();

	return cpssp;
}

void
CHIP_(destroy)(void *_cpssp)
{
	struct cpssp *cpssp = _cpssp;
	int ret;

	ret = storage_write(cpssp->media, cpssp->flash,
			sizeof(cpssp->flash), 0);
	assert(ret == sizeof(cpssp->flash));

	ret = storage_destroy(cpssp->media);
	assert(0 <= ret);

	shm_free(cpssp);
}

void
CHIP_(suspend)(void *_cpssp, FILE *fp)
{
	struct cpssp *cpssp = _cpssp;

	generic_suspend(cpssp, sizeof(*cpssp), fp);
}

void
CHIP_(resume)(void *_cpssp, FILE *fp)
{
	struct cpssp *cpssp = _cpssp;

	generic_resume(cpssp, sizeof(*cpssp), fp);
}

#undef DEBUG_CONTROL_FLOW
