/* ----------------------------------------------------------------------------
 *         SAM Software Package License
 * ----------------------------------------------------------------------------
 * Copyright (c) 2015, Atmel Corporation
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the disclaimer below.
 *
 * Atmel's name may not be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * DISCLAIMER: THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
 * DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * ----------------------------------------------------------------------------
 */

/*---------------------------------------------------------------------------
 *         Headers
 *---------------------------------------------------------------------------*/

#include "chip.h"
#include "trace.h"
#include "ring.h"

#include "peripherals/aic.h"
#include "peripherals/gmacd.h"
#include "peripherals/gmac.h"
#include "peripherals/l2cc.h"
#include "peripherals/pmc.h"

#include <string.h>
#include <assert.h>

//------------------------------------------------------------------------------
//         Definitions
//------------------------------------------------------------------------------

// Interrupt bits
#define GMAC_INT_RX_BITS     (GMAC_IER_RCOMP | GMAC_IER_RXUBR | GMAC_IER_ROVR)
#define GMAC_INT_TX_ERR_BITS (GMAC_IER_TUR | GMAC_IER_RLEX | GMAC_IER_TFC)
#define GMAC_INT_TX_BITS     (GMAC_INT_TX_ERR_BITS | GMAC_IER_TCOMP)

/*---------------------------------------------------------------------------
 *         Types
 *---------------------------------------------------------------------------*/

struct _gmacd_irq_handler {
	Gmac*           addr;
	struct _gmacd** gmacd;
	uint32_t        irq;
	aic_handler_t   handler;
};

/*---------------------------------------------------------------------------
 *         IRQ Handlers
 *---------------------------------------------------------------------------*/

#ifdef CONFIG_HAVE_GMAC_QUEUES
#if GMAC_NUM_QUEUES != 3
#error This driver assumes that GMAC_NUM_QUEUES is 3
#endif
#endif

static struct _gmacd* _gmacd0;
#ifdef GMAC1
static struct _gmacd* _gmacd1;
#endif

static void _gmacd_handler(struct _gmacd* gmacd, uint8_t queue);

static void _gmacd_gmac0_irq_handler(void)
{
	_gmacd_handler(_gmacd0, 0);
}

#ifdef CONFIG_HAVE_GMAC_QUEUES
static void _gmacd_gmac0q1_irq_handler(void)
{
	_gmacd_handler(_gmacd0, 1);
}

static void _gmacd_gmac0q2_irq_handler(void)
{
	_gmacd_handler(_gmacd0, 2);
}
#endif

#ifdef GMAC1
static void _gmacd_gmac1_irq_handler(void)
{
	_gmacd_handler(_gmacd1, 0);
}

#ifdef CONFIG_HAVE_GMAC_QUEUES
static void _gmacd_gmac1q1_irq_handler(void)
{
	_gmacd_handler(_gmacd1, 1);
}

static void _gmacd_gmac1q2_irq_handler(void)
{
	_gmacd_handler(_gmacd1, 2);
}
#endif
#endif

static const struct _gmacd_irq_handler _gmacd_irq_handlers[] = {
	{ GMAC0, &_gmacd0, ID_GMAC0,    _gmacd_gmac0_irq_handler },
#ifdef CONFIG_HAVE_GMAC_QUEUES
	{ GMAC0, &_gmacd0, ID_GMAC0_Q1, _gmacd_gmac0q1_irq_handler },
	{ GMAC0, &_gmacd0, ID_GMAC0_Q2, _gmacd_gmac0q2_irq_handler },
#endif
#ifdef GMAC1
	{ GMAC1, &_gmacd1, ID_GMAC1,    _gmacd_gmac1_irq_handler },
#ifdef CONFIG_HAVE_GMAC_QUEUES
	{ GMAC1, &_gmacd1, ID_GMAC1_Q1, _gmacd_gmac1q1_irq_handler },
	{ GMAC1, &_gmacd1, ID_GMAC1_Q2, _gmacd_gmac1q2_irq_handler },
#endif
#endif
};

/*---------------------------------------------------------------------------
 *         Dummy Buffers for unconfigured queues
 *---------------------------------------------------------------------------*/

#define DUMMY_BUFFERS 2
#define DUMMY_UNITSIZE 128

/** TX descriptors list */
ALIGNED(8) SECTION(".region_ddr_nocache")
static struct _gmac_desc dummy_tx_desc[DUMMY_BUFFERS];

/** RX descriptors list */
ALIGNED(8) SECTION(".region_ddr_nocache")
static struct _gmac_desc dummy_rx_desc[DUMMY_BUFFERS];

/** Send Buffer */
ALIGNED(32)
static uint8_t dummy_buffer[DUMMY_BUFFERS * DUMMY_UNITSIZE];

/*---------------------------------------------------------------------------
 *         Local functions
 *---------------------------------------------------------------------------*/

/**
 *  \brief Disable TX & reset registers and descriptor list
 *  \param gmacd Pointer to GMAC Driver instance.
 */
static void _gmacd_reset_tx(struct _gmacd* gmacd, uint8_t queue)
{
	struct _gmacd_queue* q = &gmacd->queues[queue];
	uint32_t addr = (uint32_t)q->tx_buffer;
	uint32_t i;

	/* Disable TX */
	gmac_transmit_enable(gmacd->gmac, false);

	/* Setup the TX descriptors */
	RING_CLEAR(q->tx_head, q->tx_tail);
	for (i = 0; i < q->tx_size; i++) {
		q->tx_desc[i].addr = addr;
		DSB();
		q->tx_desc[i].status = GMAC_TX_STATUS_USED;
		addr += GMAC_TX_UNITSIZE;
	}
	q->tx_desc[q->tx_size - 1].status |= GMAC_TX_STATUS_WRAP;

	/* Transmit Buffer Queue Pointer Register */
	gmac_set_tx_desc(gmacd->gmac, queue, q->tx_desc);
}

/**
 *  \brief Disable RX & reset registers and descriptor list
 *  \param gmacd Pointer to GMAC Driver instance.
 */
static void _gmacd_reset_rx(struct _gmacd* gmacd, uint8_t queue)
{
	struct _gmacd_queue* q = &gmacd->queues[queue];
	uint32_t addr = (uint32_t)q->rx_buffer;
	uint32_t i;

	/* Disable RX */
	gmac_receive_enable(gmacd->gmac, false);

	/* Setup the RX descriptors */
	q->rx_head = 0;
	for (i = 0; i < q->rx_size; i++) {
		q->rx_desc[i].addr = addr & GMAC_RX_ADDR_MASK;
		DSB();
		q->rx_desc[i].status = 0;
		addr += GMAC_RX_UNITSIZE;
	}
	q->rx_desc[q->rx_size - 1].addr |= GMAC_RX_ADDR_WRAP;

	/* Receive Buffer Queue Pointer Register */
	gmac_set_rx_desc(gmacd->gmac, queue, q->rx_desc);
}

/**
 *  \brief Process successfully sent packets
 *  \param gmacd Pointer to GMAC Driver instance.
 */
static void _gmacd_tx_complete_handler(struct _gmacd* gmacd, uint8_t queue)
{
	Gmac* gmac = gmacd->gmac;
	struct _gmacd_queue* q = &gmacd->queues[queue];
	struct _gmac_desc *desc;
	gmacd_callback_t callback;
	uint32_t tsr;

	//printf("<TX>\r\n");

	/* Clear status */
	tsr = gmac_get_tx_status(gmac);
	gmac_clear_tx_status(gmac, tsr);

	while (!RING_EMPTY(q->tx_head, q->tx_tail)) {
		desc = &q->tx_desc[q->tx_tail];

		/* Exit if frame has not been sent yet:
		 * On TX completion, the GMAC set the USED bit only into the
		 * very first buffer descriptor of the sent frame.
		 * Otherwise it updates this descriptor with status error bits.
		 * This is the descriptor writeback.
		 */
		if ((desc->status & GMAC_TX_STATUS_USED) == 0)
			break;

		/* Process all buffers of the current transmitted frame */
		while ((desc->status & GMAC_TX_STATUS_LASTBUF) == 0) {
			RING_INC(q->tx_tail, q->tx_size);
			desc = &q->tx_desc[q->tx_tail];
		}

		/* Notify upper layer that a frame has been sent */
		if (q->tx_callbacks) {
			callback = q->tx_callbacks[q->tx_tail];
			if (callback)
				callback(queue, tsr);
		}

		/* Go to next frame */
		RING_INC(q->tx_tail, q->tx_size);
	}

	/* If a wakeup callback has been set, notify upper layer that it can
	   send more packets now */
	if (q->tx_wakeup_callback) {
		if (RING_SPACE(q->tx_head, q->tx_tail, q->tx_size) >=
				q->tx_wakeup_threshold) {
			q->tx_wakeup_callback(queue);
		}
	}
}

/**
 *  \brief Reset TX queue when errors are detected
 *  \param gmacd Pointer to GMAC Driver instance.
 */
static void _gmacd_tx_error_handler(struct _gmacd* gmacd, uint8_t queue)
{
	Gmac *gmac = gmacd->gmac;
	struct _gmacd_queue* q = &gmacd->queues[queue];
	struct _gmac_desc* desc;
	gmacd_callback_t callback;
	uint32_t tsr;

	printf("<TXERR>\r\n");

	/* Clear TXEN bit into the Network Configuration Register:
	 * this is a workaround to recover from TX lockups that
	 * occur on sama5d4 gmac (r1p24f2) when using  scatter-gather.
	 * This issue has never been seen on sama5d4 gmac (r1p31).
	 */
	gmac_transmit_enable(gmac, false);

	/* The following step should be optional since this function is called
	 * directly by the IRQ handler. Indeed, according to Cadence
	 * documentation, the transmission is halted on errors such as
	 * too many retries or transmit under run.
	 * However it would become mandatory if the call of this function
	 * were scheduled as a task by the IRQ handler (this is how Linux
	 * driver works). Then this function might compete with GMACD_Send().
	 *
	 * Setting bit 10, tx_halt, of the Network Control Register is not enough:
	 * We should wait for bit 3, tx_go, of the Transmit Status Register to
	 * be cleared at transmit completion if a frame is being transmitted.
	 */
	gmac_halt_transmission(gmac);
	while (gmac_get_tx_status(gmac) & GMAC_TSR_TXGO);

	/* Treat frames in TX queue including the ones that caused the error. */
	while (!RING_EMPTY(q->tx_head, q->tx_tail)) {
		int tx_completed = 0;
		desc = &q->tx_desc[q->tx_tail];

		/* Check USED bit on the very first buffer descriptor to validate
		 * TX completion.
		 */
		if (desc->status & GMAC_TX_STATUS_USED)
			tx_completed = 1;

		/* Go to the last buffer descriptor of the frame */
		while ((desc->status & GMAC_TX_STATUS_LASTBUF) == 0) {
			RING_INC(q->tx_tail, q->tx_size);
			desc = &q->tx_desc[q->tx_tail];
		}

		/* Notify upper layer that a frame status */
		// TODO: which error to notify?
		if (q->tx_callbacks) {
			callback = q->tx_callbacks[q->tx_tail];
			if (callback)
				callback(queue, tx_completed ? GMAC_TSR_TXCOMP : 0);
		}

		/* Go to next frame */
		RING_INC(q->tx_tail, q->tx_size);
	}

	/* Reset TX queue */
	_gmacd_reset_tx(gmacd, queue);

	/* Clear status */
	tsr = gmac_get_tx_status(gmac);
	gmac_clear_tx_status(gmac, tsr);

	/* Now we are ready to start transmission again */
	gmac_transmit_enable(gmac, true);
	if (q->tx_wakeup_callback)
		q->tx_wakeup_callback(queue);
}

/*---------------------------------------------------------------------------
 *         Exported functions
 *---------------------------------------------------------------------------*/

/**
 *  \brief GMAC Interrupt handler
 *  \param gmacd Pointer to GMAC Driver instance.
 */
static void _gmacd_handler(struct _gmacd * gmacd, uint8_t queue)
{
	Gmac *gmac = gmacd->gmac;
	struct _gmacd_queue* q = &gmacd->queues[queue];
	uint32_t isr;
	uint32_t rsr;

	/* Interrupt Status Register is cleared on read */
	while ((isr = gmac_get_it_status(gmac, queue)) != 0) {
		/* RX packet */
		if (isr & GMAC_INT_RX_BITS) {
			/* Clear status */
			rsr = gmac_get_rx_status(gmac);
			gmac_clear_rx_status(gmac, rsr);

			/* Invoke callback */
			if (q->rx_callback)
				q->rx_callback(queue, rsr);
		}

		/* TX error */
		if (isr & GMAC_INT_TX_ERR_BITS) {
			_gmacd_tx_error_handler(gmacd, queue);
			break;
		}

		/* TX packet */
		if (isr & GMAC_IER_TCOMP) {
			_gmacd_tx_complete_handler(gmacd, queue);
		}

		/* HRESP not OK */
		if (isr & GMAC_IER_HRESP) {
			trace_error("HRESP not OK\n\r");
		}
	}
}

/**
 * \brief Initialize the GMAC with the Gmac controller address
 *  \param gmacd Pointer to GMAC Driver instance.
 *  \param gmac    Pointer to HW address for registers.
 *  \param enableCAF    Enable/Disable CopyAllFrame.
 *  \param enableNBC    Enable/Disable NoBroadCast.
 */
void gmacd_configure(struct _gmacd * gmacd,
	   Gmac * gmac, uint8_t enableCAF, uint8_t enableNBC)
{
	uint32_t ncfgr;
	int i;

	/* Initialize struct */
	gmacd->gmac = gmac;

	gmac_configure(gmac);

	uint32_t id = get_gmac_id_from_addr(gmac);
	for (i = 0; i < ARRAY_SIZE(_gmacd_irq_handlers); i++) {
		if (_gmacd_irq_handlers[i].addr == gmac) {
			*_gmacd_irq_handlers[i].gmacd = gmacd;
			aic_set_source_vector(_gmacd_irq_handlers[i].irq,
					_gmacd_irq_handlers[i].handler);
		}
	}
	aic_enable(id);

	/* Enable the copy of data into the buffers
	   ignore broadcasts, and don't copy FCS. */
	ncfgr = gmac_get_network_config_register(gmac);
	ncfgr |= GMAC_NCFGR_FD;
	if (enableCAF) {
		ncfgr |= GMAC_NCFGR_CAF;
	}
	if (enableNBC) {
		ncfgr |= GMAC_NCFGR_NBC;
	}
	gmac_set_network_config_register(gmac, ncfgr);

	for (i = 0; i < GMAC_NUM_QUEUES; i++) {
		gmacd_setup_queue(gmacd, i,
				DUMMY_BUFFERS, dummy_buffer, dummy_rx_desc,
				DUMMY_BUFFERS, dummy_buffer, dummy_tx_desc,
				NULL);
	}
}

/**
 * Initialize necessary allocated buffer lists for GMAC Driver to transfer data.
 * Must be invoked after GMACD_Init() but before RX/TX start.
 * \param gmacd Pointer to GMAC Driver instance.
 * \param rx_buffer Pointer to allocated buffer for RX. The address should
 *                  be 8-byte aligned and the size should be
 *                  GMAC_RX_UNITSIZE * wRxSize.
 * \param rx_desc      Pointer to allocated RX descriptor list.
 * \param wRxSize   RX size, in number of registered units (RX descriptors).
 * \param tx_buffer Pointer to allocated buffer for TX. The address should
 *                  be 8-byte aligned and the size should be
 *                  GMAC_TX_UNITSIZE * wTxSize.
 * \param tx_desc      Pointer to allocated TX descriptor list.
 * \param pTxCb     Pointer to allocated TX callback list.
 * \param wTxSize   TX size, in number of registered units (TX descriptors).
 * \return GMACD_OK or GMACD_PARAM.
 * \note If input address is not 8-byte aligned the address is automatically
 *       adjusted and the list size is reduced by one.
 */


uint8_t gmacd_setup_queue(struct _gmacd* gmacd, uint8_t queue,
		uint16_t rx_size, uint8_t* rx_buffer, struct _gmac_desc* rx_desc,
		uint16_t tx_size, uint8_t* tx_buffer, struct _gmac_desc* tx_desc,
		gmacd_callback_t *tx_callbacks)
{
	Gmac *gmac = gmacd->gmac;
	struct _gmacd_queue* q = &gmacd->queues[queue];

	if (rx_size <= 1 || tx_size <= 1)
		return GMACD_PARAM;

	/* Assign RX buffers */
	if (((uint32_t)rx_buffer & 0x7)
	    || ((uint32_t)rx_desc & 0x7)) {
		rx_size--;
		trace_debug("RX list address adjusted\n\r");
	}
	q->rx_buffer = (uint8_t*)((uint32_t)rx_buffer & 0xFFFFFFF8);
	q->rx_desc = (struct _gmac_desc *)((uint32_t)rx_desc & 0xFFFFFFF8);
	q->rx_size = rx_size;
	q->rx_callback = NULL;

	/* Assign TX buffers */
	if (((uint32_t)tx_buffer & 0x7)
	    || ((uint32_t)tx_desc & 0x7)) {
		tx_size--;
		trace_debug("TX list address adjusted\n\r");
	}
	q->tx_buffer = (uint8_t*)((uint32_t)tx_buffer & 0xFFFFFFF8);
	q->tx_desc = (struct _gmac_desc*)((uint32_t)tx_desc & 0xFFFFFFF8);
	q->tx_size = tx_size;
	q->tx_callbacks = tx_callbacks;
	q->tx_wakeup_callback = NULL;

	/* Reset TX & RX */
	_gmacd_reset_rx(gmacd, queue);
	_gmacd_reset_tx(gmacd, queue);

	/* Setup the interrupts for RX/TX completion (and errors) */
	gmac_enable_it(gmac, queue, GMAC_INT_RX_BITS | GMAC_INT_TX_BITS | GMAC_IER_HRESP);

	return GMACD_OK;
}

void gmacd_start(struct _gmacd * gmacd)
{
	/* Enable Rx and Tx, plus the stats register. */
	gmac_transmit_enable(gmacd->gmac, true);
	gmac_receive_enable(gmacd->gmac, true);
	gmac_enable_statistics_write(gmacd->gmac, true);
}

/**
 * Reset TX & RX queue & statistics
 * \param gmacd Pointer to GMAC Driver instance.
 */
void gmacd_reset(struct _gmacd* gmacd)
{
	int i;
	for (i = 0; i < GMAC_NUM_QUEUES; i++) {
		_gmacd_reset_rx(gmacd, i);
		_gmacd_reset_tx(gmacd, i);
	}

	gmac_set_network_control_register(gmacd->gmac, GMAC_NCR_TXEN | GMAC_NCR_RXEN |
			GMAC_NCR_WESTAT | GMAC_NCR_CLRSTAT);
}

/**
 * \brief Send a frame splitted into buffers. If the frame size is larger than transfer buffer size
 * error returned. If frame transfer status is monitored, specify callback for each frame.
 *  \param gmacd Pointer to GMAC Driver instance.
 *  \param sgl Pointer to a scatter-gather list describing the buffers of the ethernet frame.
 *  \param fTxCb Pointer to callback function.
 */
uint8_t gmacd_send_sg(struct _gmacd* gmacd, uint8_t queue,
		const struct _gmac_sg_list* sgl, gmacd_callback_t callback)
{
	Gmac* gmac = gmacd->gmac;
	struct _gmacd_queue* q = &gmacd->queues[queue];
	struct _gmac_desc* desc;
	uint16_t idx, tx_head;
	int i;

	if (callback && !q->tx_callbacks) {
		trace_error("Cannot set send callback, no tx_callbacks "\
				"buffer configured for queue %u", queue);
	}

	/* Check parameter */
	if (!sgl->size) {
		trace_error("gmacd_send_sg: ethernet frame is empty.\r\n");
		return GMACD_PARAM;
	}
	if (sgl->size >= q->tx_size) {
		trace_error("gmacd_send_sg: ethernet frame has too many buffers.\r\n");
		return GMACD_PARAM;
	}

	/* Check available space */
	if (RING_SPACE(q->tx_head, q->tx_tail, q->tx_size) < sgl->size) {
		trace_error("gmacd_send_sg: not enough free buffers in TX queue.\r\n");
		return GMACD_TX_BUSY;
	}

	/* Tag end of TX queue */
	tx_head = fixed_mod(q->tx_head + sgl->size, q->tx_size);
	idx = tx_head;
	if (q->tx_callbacks)
		q->tx_callbacks[idx] = NULL;
	desc = &q->tx_desc[idx];
	desc->status |= GMAC_TX_STATUS_USED;

	/* Update buffer descriptors in reverse order to avoid a race
	 * condition with hardware.
	 */
	for (i = sgl->size - 1; i >= 0; i--) {
		const struct _gmac_sg *sg = &sgl->entries[i];
		uint32_t status;

		if (sg->size > GMAC_TX_UNITSIZE) {
			trace_error("gmacd_send_sg: buffer size is too big.\r\n");
			return GMACD_PARAM;
		}

		RING_DEC(idx, q->tx_size);

		/* Reset TX callback */
		if (q->tx_callbacks)
			q->tx_callbacks[idx] = NULL;

		desc = &q->tx_desc[idx];

		/* Copy data into transmittion buffer */
		if (sg->buffer && sg->size) {
			memcpy((void*)desc->addr, sg->buffer, sg->size);
			l2cc_clean_region(desc->addr, desc->addr + sg->size);
		}

		/* Compute buffer descriptor status word */
		status = sg->size & GMAC_RX_STATUS_LENGTH_MASK;
		if (i == (sgl->size - 1)) {
			status |= GMAC_TX_STATUS_LASTBUF;
			if (q->tx_callbacks)
				q->tx_callbacks[idx] = callback;
		}
		if (idx == (q->tx_size - 1)) {
			status |= GMAC_TX_STATUS_WRAP;
		}

		/* Update buffer descriptor status word: clear USED bit */
		desc->status = status;
		DSB();
	}

	/* Update TX ring buffer pointers */
	q->tx_head = tx_head;

	/* Now start to transmit if it is not already done */
	gmac_start_transmission(gmac);

	return GMACD_OK;
}

/**
 * \brief Send a packet with GMAC. If the packet size is larger than transfer buffer size
 * error returned. If packet transfer status is monitored, specify callback for each packet.
 *  \param gmacd Pointer to GMAC Driver instance.
 *  \param pBuffer   The buffer to be send
 *  \param size     The size of buffer to be send
 *  \param fTxCb Threshold Wakeup callback
 *  \return         OK, Busy or invalid packet
 */
uint8_t gmacd_send(struct _gmacd* gmacd, uint8_t queue, void *buffer,
		uint32_t size, gmacd_callback_t callback)
{
	struct _gmac_sg sg;
	struct _gmac_sg_list sgl;

	/* Init single entry scatter-gather list */
	sg.size = size;
	sg.buffer = buffer;
	sgl.size = 1;
	sgl.entries = &sg;

	return gmacd_send_sg(gmacd, queue, &sgl, callback);
}

/**
 * Return current load of TX.
 * \param gmacd   Pointer to GMAC Driver instance.
 */
uint32_t gmacd_get_tx_load(struct _gmacd* gmacd, uint8_t queue)
{
	struct _gmacd_queue* q = &gmacd->queues[queue];
	return RING_CNT(q->tx_head, q->tx_tail, q->tx_size);
}

/**
 * \brief Receive a packet with GMAC.
 * If not enough buffer for the packet, the remaining data is lost but right
 * frame length is returned.
 *  \param gmacd Pointer to GMAC Driver instance.
 *  \param pFrame           Buffer to store the frame
 *  \param frameSize        Size of the frame
 *  \param pRcvSize         Received size
 *  \return                 OK, no data, or frame too small
 */
uint8_t gmacd_poll(struct _gmacd* gmacd, uint8_t queue,
	uint8_t* buffer, uint32_t buffer_size, uint32_t* recv_size)
{
	struct _gmacd_queue* q = &gmacd->queues[queue];
	struct _gmac_desc *desc;
	uint32_t idx;
	uint32_t cur_frame_size = 0;
	uint8_t *cur_frame = 0;

	if (!buffer)
		return GMACD_PARAM;

	/* Set the default return value */
	*recv_size = 0;

	/* Process RX descriptors */
	idx = q->rx_head;
	desc = &q->rx_desc[idx];
	while (desc->addr & GMAC_RX_ADDR_OWN) {
		/* A start of frame has been received, discard previous fragments */
		if (desc->status & GMAC_RX_STATUS_SOF) {
			/* Skip previous fragment */
			while (idx != q->rx_head) {
				desc = &q->rx_desc[q->rx_head];
				desc->addr &= ~GMAC_RX_ADDR_OWN;
				RING_INC(q->rx_head, q->rx_size);
			}
			cur_frame = buffer;
			cur_frame_size = 0;
		}

		/* Increment the index */
		RING_INC(idx, q->rx_size);

		/* Copy data in the frame buffer */
		if (cur_frame) {
			if (idx == q->rx_head) {
				trace_info("no EOF (buffers probably too small)\r\n");

				do {
					desc = &q->rx_desc[q->rx_head];
					desc->addr &= ~GMAC_RX_ADDR_OWN;
					RING_INC(q->rx_head, q->rx_size);
				} while (idx != q->rx_head);
				return GMACD_RX_NULL;
			}

			/* Copy the buffer into the application frame */
			uint32_t length = GMAC_RX_UNITSIZE;
			if ((cur_frame_size + length) > buffer_size) {
				length = buffer_size - cur_frame_size;
			}

			uint32_t addr = desc->addr & GMAC_RX_ADDR_MASK;
			l2cc_invalidate_region(addr, addr + length);
			memcpy(cur_frame, (void*)addr, length);
			cur_frame += length;
			cur_frame_size += length;

			/* An end of frame has been received, return the data */
			if (desc->status & GMAC_RX_STATUS_EOF) {
				/* Frame size from the GMAC */
				*recv_size = desc->status & GMAC_RX_STATUS_LENGTH_MASK;

				/* Application frame buffer is too small all
				 * data have not been copied */
				if (cur_frame_size < *recv_size) {
					return GMACD_SIZE_TOO_SMALL;
				}

				/* All data have been copied in the application
				 * frame buffer => release descriptors */
				while (q->rx_head != idx) {
					desc = &q->rx_desc[q->rx_head];
					desc->addr &= ~GMAC_RX_ADDR_OWN;
					RING_INC(q->rx_head, q->rx_size);
				}

				return GMACD_OK;
			}
		}

		/* SOF has not been detected, skip the fragment */
		else {
			desc->addr &= ~GMAC_RX_ADDR_OWN;
			q->rx_head = idx;
		}

		/* Process the next buffer */
		desc = &q->rx_desc[idx];
	}
	return GMACD_RX_NULL;
}

/**
 * \brief Registers pRxCb callback. Callback will be invoked after the next received
 * frame. When GMAC_Poll() returns GMAC_RX_NO_DATA the application task call GMAC_Set_RxCb()
 * to register pRxCb() callback and enters suspend state. The callback is in charge
 * to resume the task once a new frame has been received. The next time GMAC_Poll()
 * is called, it will be successful.
 *  \param gmacd Pointer to GMAC Driver instance.
 *  \param fRxCb   Pointer to callback function
 *  \return        OK, no data, or frame too small
 */

void gmacd_set_rx_callback(struct _gmacd* gmacd, uint8_t queue, gmacd_callback_t callback)
{
	struct _gmacd_queue* q = &gmacd->queues[queue];
	if (!callback) {
		gmac_disable_it(gmacd->gmac, queue, GMAC_IDR_RCOMP);
		q->rx_callback = NULL;
	} else {
		q->rx_callback = callback;
		gmac_enable_it(gmacd->gmac, queue, GMAC_IER_RCOMP);
	}
}

/**
 * Register/Clear TX wakeup callback.
 *
 * When GMACD_Send() returns GMACD_TX_BUSY (all TD busy) the application
 * task calls GMACD_SetTxWakeupCallback() to register fWakeup() callback and
 * enters suspend state. The callback is in charge to resume the task once
 * several TD have been released. The next time GMACD_Send() will be called,
 * it shall be successful.
 *
 * This function is usually invoked with NULL callback from the TX wakeup
 * callback itself, to unregister. Once the callback has resumed the
 * application task, there is no need to invoke the callback again.
 *
 * \param gmacd   Pointer to GMAC Driver instance.
 * \param fWakeup     Wakeup callback.
 * \param bThreshold  Number of free TD before wakeup callback invoked.
 * \return GMACD_OK, GMACD_PARAM on parameter error.
 */
uint8_t gmacd_set_tx_wakeup_callback(struct _gmacd* gmacd, uint8_t queue,
			  gmacd_wakeup_cb_t callback, uint16_t threshold)
{
	struct _gmacd_queue* q = &gmacd->queues[queue];
	if (!callback) {
		q->tx_wakeup_callback = NULL;
	} else {
		if (threshold <= q->tx_size) {
			q->tx_wakeup_callback = callback;
			q->tx_wakeup_threshold = threshold;
		} else {
			return GMACD_PARAM;
		}
	}

	return GMACD_OK;
}

/**@}*/
