/** FIFO related helper functions
 *
 * These functions present a simpler interface to Xilinx' FIFO driver (XLlFifo_*)
 *
 * @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
 * @copyright 2017, Steffen Vogel
 **********************************************************************************/

#include <unistd.h>

#include "utils.h"
#include "plugin.h"

#include "fpga/ip.h"
#include "fpga/card.h"
#include "fpga/ips/fifo.h"
#include "fpga/ips/intc.h"

int fifo_start(struct fpga_ip *c)
{
	int ret;

	struct fpga_card *f = c->card;
	struct fifo *fifo = c->_vd;

	XLlFifo *xfifo = &fifo->inst;
	XLlFifo_Config fifo_cfg = {
		.BaseAddress = (uintptr_t) f->map + c->baseaddr,
		.Axi4BaseAddress = (uintptr_t) c->card->map + fifo->baseaddr_axi4,
		.Datainterface = (fifo->baseaddr_axi4 != -1) ? 1 : 0 /* use AXI4 for Data, AXI4-Lite for control */
	};

	ret = XLlFifo_CfgInitialize(xfifo, &fifo_cfg, (uintptr_t) c->card->map + c->baseaddr);
	if (ret != XST_SUCCESS)
		return -1;

	XLlFifo_IntEnable(xfifo, XLLF_INT_RC_MASK); /* Receive complete IRQ */

	return 0;
}

int fifo_stop(struct fpga_ip *c)
{
	struct fifo *fifo = c->_vd;

	XLlFifo *xfifo = &fifo->inst;

	XLlFifo_IntDisable(xfifo, XLLF_INT_RC_MASK); /* Receive complete IRQ */

	return 0;
}

ssize_t fifo_write(struct fpga_ip *c, char *buf, size_t len)
{
	struct fifo *fifo = c->_vd;

	XLlFifo *xllfifo = &fifo->inst;

	uint32_t tdfv;

	tdfv = XLlFifo_TxVacancy(xllfifo);
	if (tdfv < len)
		return -1;

	XLlFifo_Write(xllfifo, buf, len);
	XLlFifo_TxSetLen(xllfifo, len);

	return len;
}

ssize_t fifo_read(struct fpga_ip *c, char *buf, size_t len)
{
	struct fifo *fifo = c->_vd;

	XLlFifo *xllfifo = &fifo->inst;

	size_t nextlen = 0;
	uint32_t rxlen;

	while (!XLlFifo_IsRxDone(xllfifo))
		intc_wait(c->card->intc, c->irq);
	XLlFifo_IntClear(xllfifo, XLLF_INT_RC_MASK);

	/* Get length of next frame */
	rxlen = XLlFifo_RxGetLen(xllfifo);
	nextlen = MIN(rxlen, len);

	/* Read from FIFO */
	XLlFifo_Read(xllfifo, buf, nextlen);

	return nextlen;
}

int fifo_parse(struct fpga_ip *c, json_t *cfg)
{
	struct fifo *fifo = c->_vd;

	int baseaddr_axi4 = -1, ret;

	json_error_t err;

	fifo->baseaddr_axi4 = -1;

	ret = json_unpack_ex(cfg, &err, 0, "{ s?: i }", "baseaddr_axi4", &baseaddr_axi4);
	if (ret)
		jerror(&err, "Failed to parse configuration of FPGA IP '%s'", c->name);

	fifo->baseaddr_axi4 = baseaddr_axi4;

	return 0;
}

int fifo_reset(struct fpga_ip *c)
{
	struct fifo *fifo = c->_vd;

	XLlFifo_Reset(&fifo->inst);

	return 0;
}

static struct plugin p = {
	.name		= "Xilinx's AXI4 FIFO data mover",
	.description	= "",
	.type		= PLUGIN_TYPE_FPGA_IP,
	.ip		= {
		.vlnv	= { "xilinx.com", "ip", "axi_fifo_mm_s", NULL },
		.type	= FPGA_IP_TYPE_DM_FIFO,
		.start	= fifo_start,
		.stop	= fifo_stop,
		.parse	= fifo_parse,
		.reset	= fifo_reset,
		.size	= sizeof(struct fifo)
	}
};

REGISTER_PLUGIN(&p)