 /**
 * \file
 *
 * \brief GMAC (Ethernet MAC) driver for SAM.
 *
 * Copyright (c) 2013 Atmel Corporation. All rights reserved.
 *
 * \asf_license_start
 *
 * \page License
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * 3. The name of Atmel may not be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * 4. This software may only be redistributed and used in connection with an
 *    Atmel microcontroller product.
 *
 * 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
 * EXPRESSLY AND SPECIFICALLY 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.
 *
 * \asf_license_stop
 *
 */

/* Standard includes. */
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

/* FreeRTOS includes. */
#include "FreeRTOS.h"
#include "task.h"

#include "FreeRTOSIPConfig.h"

#include "compiler.h"
#include "instance/gmac.h"
#include "ethernet_phy.h"

/// @cond 0
/**INDENT-OFF**/
#ifdef __cplusplus
extern "C" {
#endif
/**INDENT-ON**/
/// @endcond

#ifndef ARRAY_SIZE
#define ARRAY_SIZE(x)  (int)( sizeof(x) / sizeof(x)[0] )
#endif
/**
 * \defgroup gmac_group Ethernet Media Access Controller
 *
 * See \ref gmac_quickstart.
 *
 * Driver for the GMAC (Ethernet Media Access Controller).
 * This file contains basic functions for the GMAC, with support for all modes, settings
 * and clock speeds.
 *
 * \section dependencies Dependencies
 * This driver does not depend on other modules.
 *
 * @{
 */

/** TX descriptor lists */
COMPILER_ALIGNED(8)
static gmac_tx_descriptor_t gs_tx_desc[ GMAC_TX_BUFFERS ];
#if( GMAC_USES_TX_CALLBACK != 0 )
/** TX callback lists */
static gmac_dev_tx_cb_t gs_tx_callback[ GMAC_TX_BUFFERS ];
#endif
/** RX descriptors lists */
COMPILER_ALIGNED(8)
static gmac_rx_descriptor_t gs_rx_desc[ GMAC_RX_BUFFERS ];

#if( ipconfigZERO_COPY_TX_DRIVER == 0 )
	/** Send Buffer. Section 3.6 of AMBA 2.0 spec states that burst should not cross the
	 * 1K Boundaries. Receive buffer manager write operations are burst of 2 words => 3 lsb bits
	 * of the address shall be set to 0.
	 */
	COMPILER_ALIGNED(8)
	static uint8_t gs_uc_tx_buffer[ GMAC_TX_BUFFERS * GMAC_TX_UNITSIZE ];
#endif /* ipconfigZERO_COPY_TX_DRIVER */

/** Receive Buffer */
COMPILER_ALIGNED(8)
static uint8_t gs_uc_rx_buffer[ GMAC_RX_BUFFERS * GMAC_RX_UNITSIZE ];

/**
 * GMAC device memory management struct.
 */
typedef struct gmac_dev_mem {
	/* Pointer to allocated buffer for RX. The address should be 8-byte aligned
	and the size should be GMAC_RX_UNITSIZE * wRxSize. */
	uint8_t *p_rx_buffer;
	/* Pointer to allocated RX descriptor list. */
	gmac_rx_descriptor_t *p_rx_dscr;
	/* RX size, in number of registered units (RX descriptors). */
	/* Increased size from 16- to 32-bits, because it's more efficient */
	uint32_t us_rx_size;
	/* Pointer to allocated buffer for TX. The address should be 8-byte aligned
	and the size should be GMAC_TX_UNITSIZE * wTxSize. */
	uint8_t *p_tx_buffer;
	/* Pointer to allocated TX descriptor list. */
	gmac_tx_descriptor_t *p_tx_dscr;
	/* TX size, in number of registered units (TX descriptors). */
	uint32_t us_tx_size;
} gmac_dev_mem_t;

/** Return count in buffer */
#define CIRC_CNT( head, tail, size )		( ( ( head ) - ( tail ) ) % ( size ) )

/*
 * Return space available, from 0 to size-1.
 * Always leave one free char as a completely full buffer that has (head == tail),
 * which is the same as empty.
 */
#define CIRC_SPACE( head, tail, size )		CIRC_CNT( ( tail ), ( ( head ) + 1 ), ( size ) )

/** Circular buffer is empty ? */
#define CIRC_EMPTY( head, tail )			( head == tail )
/** Clear circular buffer */
#define CIRC_CLEAR( head, tail )			do { ( head ) = 0; ( tail ) = 0; } while( ipFALSE_BOOL )

/** Increment head or tail */
static __inline void circ_inc32( int32_t *lHeadOrTail, uint32_t ulSize )
{
	( *lHeadOrTail ) ++;
    if( ( *lHeadOrTail ) >= ( int32_t )ulSize )
	{
		( *lHeadOrTail ) = 0;
	}
}

/**
 * \brief Wait PHY operation to be completed.
 *
 * \param p_gmac HW controller address.
 * \param ul_retry The retry times, 0 to wait forever until completeness.
 *
 * Return GMAC_OK if the operation is completed successfully.
 */
static uint8_t gmac_wait_phy(Gmac* p_gmac, const uint32_t ul_retry)
{
	volatile uint32_t ul_retry_count = 0;
	const uint32_t xPHYPollDelay = pdMS_TO_TICKS( 1ul );

	while (!gmac_is_phy_idle(p_gmac)) {
		if (ul_retry == 0) {
			continue;
		}

		ul_retry_count++;

		if (ul_retry_count >= ul_retry) {
			return GMAC_TIMEOUT;
		}

		/* Block the task to allow other tasks to execute while the PHY
		is not connected. */
		vTaskDelay( xPHYPollDelay );
	}
	return GMAC_OK;
}

/**
 * \brief Disable transfer, reset registers and descriptor lists.
 *
 * \param p_dev Pointer to GMAC driver instance.
 *
 */
static void gmac_reset_tx_mem(gmac_device_t* p_dev)
{
	Gmac *p_hw = p_dev->p_hw;
	uint8_t *p_tx_buff = p_dev->p_tx_buffer;
	gmac_tx_descriptor_t *p_td = p_dev->p_tx_dscr;

	uint32_t ul_index;
	uint32_t ul_address;

	/* Disable TX */
	gmac_enable_transmit(p_hw, 0);

	/* Set up the TX descriptors */
	CIRC_CLEAR(p_dev->l_tx_head, p_dev->l_tx_tail);
	for( ul_index = 0; ul_index < p_dev->ul_tx_list_size; ul_index++ )
	{
		#if( ipconfigZERO_COPY_TX_DRIVER != 0 )
		{
			ul_address = (uint32_t) 0u;
		}
		#else
		{
			ul_address = (uint32_t) (&(p_tx_buff[ul_index * GMAC_TX_UNITSIZE]));
		}
		#endif /* ipconfigZERO_COPY_TX_DRIVER */
		p_td[ul_index].addr = ul_address;
		p_td[ul_index].status.val = GMAC_TXD_USED;
	}
	p_td[p_dev->ul_tx_list_size - 1].status.val =
			GMAC_TXD_USED | GMAC_TXD_WRAP;

	/* Set transmit buffer queue */
	gmac_set_tx_queue(p_hw, (uint32_t) p_td);
}

/**
 * \brief Disable receiver, reset registers and descriptor list.
 *
 * \param p_drv Pointer to GMAC Driver instance.
 */
static void gmac_reset_rx_mem(gmac_device_t* p_dev)
{
	Gmac *p_hw = p_dev->p_hw;
	uint8_t *p_rx_buff = p_dev->p_rx_buffer;
	gmac_rx_descriptor_t *pRd = p_dev->p_rx_dscr;

	uint32_t ul_index;
	uint32_t ul_address;

	/* Disable RX */
	gmac_enable_receive(p_hw, 0);

	/* Set up the RX descriptors */
	p_dev->ul_rx_idx = 0;
	for( ul_index = 0; ul_index < p_dev->ul_rx_list_size; ul_index++ )
	{
		ul_address = (uint32_t) (&(p_rx_buff[ul_index * GMAC_RX_UNITSIZE]));
		pRd[ul_index].addr.val = ul_address & GMAC_RXD_ADDR_MASK;
		pRd[ul_index].status.val = 0;
	}
	pRd[p_dev->ul_rx_list_size - 1].addr.val |= GMAC_RXD_WRAP;

	/* Set receive buffer queue */
	gmac_set_rx_queue(p_hw, (uint32_t) pRd);
}


/**
 * \brief Initialize the allocated buffer lists for GMAC driver to transfer data.
 * Must be invoked after gmac_dev_init() but before RX/TX starts.
 *
 * \note If input address is not 8-byte aligned, the address is automatically
 *       adjusted and the list size is reduced by one.
 *
 * \param p_gmac Pointer to GMAC instance.
 * \param p_gmac_dev Pointer to GMAC device instance.
 * \param p_dev_mm Pointer to the GMAC memory management control block.
 * \param p_tx_cb Pointer to allocated TX callback list.
 *
 * \return GMAC_OK or GMAC_PARAM.
 */
static uint8_t gmac_init_mem(Gmac* p_gmac, gmac_device_t* p_gmac_dev,
		gmac_dev_mem_t* p_dev_mm
#if( GMAC_USES_TX_CALLBACK != 0 )
		, gmac_dev_tx_cb_t* p_tx_cb
#endif
		)
{
	if (p_dev_mm->us_rx_size <= 1 || p_dev_mm->us_tx_size <= 1
#if( GMAC_USES_TX_CALLBACK != 0 )
		|| p_tx_cb == NULL
#endif
		) {
		return GMAC_PARAM;
	}

	/* Assign RX buffers */
	if (((uint32_t) p_dev_mm->p_rx_buffer & 0x7)
			|| ((uint32_t) p_dev_mm->p_rx_dscr & 0x7)) {
		p_dev_mm->us_rx_size--;
	}
	p_gmac_dev->p_rx_buffer =
			(uint8_t *) ((uint32_t) p_dev_mm->p_rx_buffer & 0xFFFFFFF8);
	p_gmac_dev->p_rx_dscr =
			(gmac_rx_descriptor_t *) ((uint32_t) p_dev_mm->p_rx_dscr
			& 0xFFFFFFF8);
	p_gmac_dev->ul_rx_list_size = p_dev_mm->us_rx_size;

	/* Assign TX buffers */
	if (((uint32_t) p_dev_mm->p_tx_buffer & 0x7)
			|| ((uint32_t) p_dev_mm->p_tx_dscr & 0x7)) {
		p_dev_mm->us_tx_size--;
	}
	p_gmac_dev->p_tx_buffer =
			(uint8_t *) ((uint32_t) p_dev_mm->p_tx_buffer & 0xFFFFFFF8);
	p_gmac_dev->p_tx_dscr =
			(gmac_tx_descriptor_t *) ((uint32_t) p_dev_mm->p_tx_dscr
			& 0xFFFFFFF8);
	p_gmac_dev->ul_tx_list_size = p_dev_mm->us_tx_size;
#if( GMAC_USES_TX_CALLBACK != 0 )
	p_gmac_dev->func_tx_cb_list = p_tx_cb;
#endif
	/* Reset TX & RX */
	gmac_reset_rx_mem(p_gmac_dev);
	gmac_reset_tx_mem(p_gmac_dev);

	/* Enable Rx and Tx, plus the statistics register */
	gmac_enable_transmit(p_gmac, true);
	gmac_enable_receive(p_gmac, true);
	gmac_enable_statistics_write(p_gmac, true);

	/* Set up the interrupts for transmission and errors */
	gmac_enable_interrupt(p_gmac,
			GMAC_IER_RXUBR | /* Enable receive used bit read interrupt. */
			GMAC_IER_TUR   | /* Enable transmit underrun interrupt. */
			GMAC_IER_RLEX  | /* Enable retry limit  exceeded interrupt. */
			GMAC_IER_TFC   | /* Enable transmit buffers exhausted in mid-frame interrupt. */
			GMAC_IER_TCOMP | /* Enable transmit complete interrupt. */
			GMAC_IER_ROVR  | /* Enable receive overrun interrupt. */
			GMAC_IER_HRESP | /* Enable Hresp not OK interrupt. */
			GMAC_IER_PFNZ  | /* Enable pause frame received interrupt. */
			GMAC_IER_PTZ);   /* Enable pause time zero interrupt. */

	return GMAC_OK;
}

/**
 * \brief Read the PHY register.
 *
 * \param p_gmac   Pointer to the GMAC instance.
 * \param uc_phy_address PHY address.
 * \param uc_address Register address.
 * \param p_value Pointer to a 32-bit location to store read data.
 *
 * \Return GMAC_OK if successfully, GMAC_TIMEOUT if timeout.
 */
uint8_t gmac_phy_read(Gmac* p_gmac, uint8_t uc_phy_address, uint8_t uc_address,
		uint32_t* p_value)
{
	gmac_maintain_phy(p_gmac, uc_phy_address, uc_address, 1, 0);

	if (gmac_wait_phy(p_gmac, MAC_PHY_RETRY_MAX) == GMAC_TIMEOUT) {
		return GMAC_TIMEOUT;
	}
	*p_value = gmac_get_phy_data(p_gmac);
	return GMAC_OK;
}

/**
 * \brief Write the PHY register.
 *
 * \param p_gmac   Pointer to the GMAC instance.
 * \param uc_phy_address PHY Address.
 * \param uc_address Register Address.
 * \param ul_value Data to write, actually 16-bit data.
 *
 * \Return GMAC_OK if successfully, GMAC_TIMEOUT if timeout.
 */
uint8_t gmac_phy_write(Gmac* p_gmac, uint8_t uc_phy_address,
		uint8_t uc_address, uint32_t ul_value)
{
	gmac_maintain_phy(p_gmac, uc_phy_address, uc_address, 0, ul_value);

	if (gmac_wait_phy(p_gmac, MAC_PHY_RETRY_MAX) == GMAC_TIMEOUT) {
		return GMAC_TIMEOUT;
	}
	return GMAC_OK;
}

/**
 * \brief Initialize the GMAC driver.
 *
 * \param p_gmac   Pointer to the GMAC instance.
 * \param p_gmac_dev Pointer to the GMAC device instance.
 * \param p_opt GMAC configure options.
 */
void gmac_dev_init(Gmac* p_gmac, gmac_device_t* p_gmac_dev,
		gmac_options_t* p_opt)
{
	gmac_dev_mem_t gmac_dev_mm;

	/* Disable TX & RX and more */
	gmac_network_control(p_gmac, 0);
	gmac_disable_interrupt(p_gmac, ~0u);


	gmac_clear_statistics(p_gmac);

	/* Clear all status bits in the receive status register. */
	gmac_clear_rx_status(p_gmac, GMAC_RSR_RXOVR | GMAC_RSR_REC | GMAC_RSR_BNA);

	/* Clear all status bits in the transmit status register */
	gmac_clear_tx_status(p_gmac, GMAC_TSR_UBR | GMAC_TSR_COL | GMAC_TSR_RLE
			| GMAC_TSR_TFC | GMAC_TSR_TXCOMP | GMAC_TSR_UND);

	/* Clear interrupts */
	gmac_get_interrupt_status(p_gmac);
#if !defined(ETHERNET_CONF_DATA_OFFSET)
	/*  Receive Buffer Offset
	 * Indicates the number of bytes by which the received data
	 * is offset from the start of the receive buffer
	 * which can be handy for alignment reasons */
	/* Note: FreeRTOS+TCP wants to have this offset set to 2 bytes */
	#error ETHERNET_CONF_DATA_OFFSET not defined, assuming 0
#endif
	/* Enable the copy of data into the buffers
	   ignore broadcasts, and not copy FCS. */

	gmac_set_configure(p_gmac,
			( gmac_get_configure(p_gmac) & ~GMAC_NCFGR_RXBUFO_Msk ) |
			GMAC_NCFGR_RFCS |   /*  Remove FCS, frame check sequence (last 4 bytes) */
			GMAC_NCFGR_PEN |    /* Pause Enable */
			GMAC_NCFGR_RXBUFO( ETHERNET_CONF_DATA_OFFSET ) |
			GMAC_RXD_RXCOEN );

	/*
	 * GMAC_DCFGR_TXCOEN: (GMAC_DCFGR) Transmitter Checksum Generation Offload Enable.
	 * Note: tha SAM4E does have RX checksum offloading
	 * but TX checksum offloading has NOT been implemented.
	 */

	gmac_set_dma(p_gmac,
			gmac_get_dma(p_gmac) | GMAC_DCFGR_TXCOEN );

	gmac_enable_copy_all(p_gmac, p_opt->uc_copy_all_frame);
	gmac_disable_broadcast(p_gmac, p_opt->uc_no_boardcast);

	/* Fill in GMAC device memory management */
	gmac_dev_mm.p_rx_buffer = gs_uc_rx_buffer;
	gmac_dev_mm.p_rx_dscr = gs_rx_desc;
	gmac_dev_mm.us_rx_size = GMAC_RX_BUFFERS;

	#if( ipconfigZERO_COPY_TX_DRIVER != 0 )
	{
		gmac_dev_mm.p_tx_buffer = NULL;
	}
	#else
	{
		gmac_dev_mm.p_tx_buffer = gs_uc_tx_buffer;
	}
	#endif
	gmac_dev_mm.p_tx_dscr = gs_tx_desc;
	gmac_dev_mm.us_tx_size = GMAC_TX_BUFFERS;

	gmac_init_mem(p_gmac, p_gmac_dev, &gmac_dev_mm
#if( GMAC_USES_TX_CALLBACK != 0 )
		, gs_tx_callback
#endif
		);

	gmac_set_address(p_gmac, 0, p_opt->uc_mac_addr);
}

/**
 * \brief Frames can be read from the GMAC in multiple sections.
 *
 * Returns > 0 if a complete frame is available
 * It also it cleans up incomplete older frames
 */

static uint32_t gmac_dev_poll(gmac_device_t* p_gmac_dev)
{
	uint32_t ulReturn = 0;
	int32_t ulIndex = p_gmac_dev->ul_rx_idx;
	gmac_rx_descriptor_t *pxHead = &p_gmac_dev->p_rx_dscr[ulIndex];

	/* Discard any incomplete frames */
	while ((pxHead->addr.val & GMAC_RXD_OWNERSHIP) &&
			(pxHead->status.val & GMAC_RXD_SOF) == 0) {
		pxHead->addr.val &= ~(GMAC_RXD_OWNERSHIP);
		circ_inc32 (&ulIndex, p_gmac_dev->ul_rx_list_size);
		pxHead = &p_gmac_dev->p_rx_dscr[ulIndex];
		p_gmac_dev->ul_rx_idx = ulIndex;
		#if( GMAC_STATS != 0 )
		{
			gmacStats.incompCount++;
		}
		#endif
	}

	while ((pxHead->addr.val & GMAC_RXD_OWNERSHIP) != 0) {
		if ((pxHead->status.val & GMAC_RXD_EOF) != 0) {
			/* Here a complete frame has been seen with SOF and EOF */
			ulReturn = pxHead->status.bm.len;
			break;
		}
		circ_inc32 (&ulIndex, p_gmac_dev->ul_rx_list_size);
		pxHead = &p_gmac_dev->p_rx_dscr[ulIndex];
		if ((pxHead->addr.val & GMAC_RXD_OWNERSHIP) == 0) {
			/* CPU is not the owner (yet) */
			break;
		}
		if ((pxHead->status.val & GMAC_RXD_SOF) != 0) {
			/* Strange, we found a new Start Of Frame
			 * discard previous segments */
			int32_t ulPrev = p_gmac_dev->ul_rx_idx;
			pxHead = &p_gmac_dev->p_rx_dscr[ulPrev];
			do {
				pxHead->addr.val &= ~(GMAC_RXD_OWNERSHIP);
				circ_inc32 (&ulPrev, p_gmac_dev->ul_rx_list_size);
				pxHead = &p_gmac_dev->p_rx_dscr[ulPrev];
				#if( GMAC_STATS != 0 )
				{
					gmacStats.truncCount++;
				}
				#endif
			} while (ulPrev != ulIndex);
			p_gmac_dev->ul_rx_idx = ulIndex;
		}
	}
	return ulReturn;
}

/**
 * \brief Frames can be read from the GMAC in multiple sections.
 * Read ul_frame_size bytes from the GMAC receive buffers to pcTo.
 * p_rcv_size is the size of the entire frame.  Generally gmac_read
 * will be repeatedly called until the sum of all the ul_frame_size equals
 * the value of p_rcv_size.
 *
 * \param p_gmac_dev Pointer to the GMAC device instance.
 * \param p_frame Address of the frame buffer.
 * \param ul_frame_size  Length of the frame.
 * \param p_rcv_size   Received frame size.
 *
 * \return GMAC_OK if receiving frame successfully, otherwise failed.
 */
uint32_t gmac_dev_read(gmac_device_t* p_gmac_dev, uint8_t* p_frame,
		uint32_t ul_frame_size, uint32_t* p_rcv_size)
{
	int32_t nextIdx;	/* A copy of the Rx-index 'ul_rx_idx' */
	int32_t bytesLeft = gmac_dev_poll (p_gmac_dev);
	gmac_rx_descriptor_t *pxHead;

	if (bytesLeft == 0 )
	{
		return GMAC_RX_NULL;
	}

	/* gmac_dev_poll has confirmed that there is a complete frame at
	 * the current position 'ul_rx_idx'
	 */
	nextIdx = p_gmac_dev->ul_rx_idx;

	/* Read +2 bytes because buffers are aligned at -2 bytes */
	bytesLeft = min( bytesLeft + 2, ( int32_t )ul_frame_size );

	/* The frame will be copied in 1 or 2 memcpy's */
	if( ( p_frame != NULL ) && ( bytesLeft != 0 ) )
	{
	const uint8_t *source;
	int32_t left;
	int32_t toCopy;

		source = p_gmac_dev->p_rx_buffer + nextIdx * GMAC_RX_UNITSIZE;
		left = bytesLeft;
		toCopy = ( p_gmac_dev->ul_rx_list_size - nextIdx ) * GMAC_RX_UNITSIZE;
		if(toCopy > left )
		{
			toCopy = left;
		}
		memcpy (p_frame, source, toCopy);
		left -= toCopy;

		if( left != 0ul )
		{
			memcpy (p_frame + toCopy, (void*)p_gmac_dev->p_rx_buffer, left);
		}
	}

	do
	{
		pxHead = &p_gmac_dev->p_rx_dscr[nextIdx];
		pxHead->addr.val &= ~(GMAC_RXD_OWNERSHIP);
		circ_inc32 (&nextIdx, p_gmac_dev->ul_rx_list_size);
	} while ((pxHead->status.val & GMAC_RXD_EOF) == 0);

	p_gmac_dev->ul_rx_idx = nextIdx;

	*p_rcv_size = bytesLeft;

	return GMAC_OK;
}


extern void vGMACGenerateChecksum( uint8_t *apBuffer );

/**
 * \brief Send ulLength bytes from pcFrom. This copies the buffer to one of the
 * GMAC Tx buffers, and then indicates to the GMAC that the buffer is ready.
 * If lEndOfFrame is true then the data being copied is the end of the frame
 * and the frame can be transmitted.
 *
 * \param p_gmac_dev Pointer to the GMAC device instance.
 * \param p_buffer       Pointer to the data buffer.
 * \param ul_size    Length of the frame.
 * \param func_tx_cb  Transmit callback function.
 *
 * \return Length sent.
 */
uint32_t gmac_dev_write(gmac_device_t* p_gmac_dev, void *p_buffer,
		uint32_t ul_size, gmac_dev_tx_cb_t func_tx_cb)
{

	volatile gmac_tx_descriptor_t *p_tx_td;
#if( GMAC_USES_TX_CALLBACK != 0 )
	volatile gmac_dev_tx_cb_t *p_func_tx_cb;
#endif

	Gmac *p_hw = p_gmac_dev->p_hw;

#if( GMAC_USES_TX_CALLBACK == 0 )
	( void )func_tx_cb;
#endif

	/* Check parameter */
	if (ul_size > GMAC_TX_UNITSIZE) {
		return GMAC_PARAM;
	}

	/* Pointers to the current transmit descriptor */
	p_tx_td = &p_gmac_dev->p_tx_dscr[p_gmac_dev->l_tx_head];

	/* If no free TxTd, buffer can't be sent, schedule the wakeup callback */
//	if (CIRC_SPACE(p_gmac_dev->l_tx_head, p_gmac_dev->l_tx_tail,
//					p_gmac_dev->ul_tx_list_size) == 0)
	{
		if ((p_tx_td->status.val & GMAC_TXD_USED) == 0)
			return GMAC_TX_BUSY;
	}
#if( GMAC_USES_TX_CALLBACK != 0 )
	/* Pointers to the current Tx callback */
	p_func_tx_cb = &p_gmac_dev->func_tx_cb_list[p_gmac_dev->l_tx_head];
#endif

	/* Set up/copy data to transmission buffer */
	if (p_buffer && ul_size) {
		/* Driver manages the ring buffer */
		/* Calculating the checksum here is faster than calculating it from the GMAC buffer
		 * because withing p_buffer, it is well aligned */
		#if( ipconfigZERO_COPY_TX_DRIVER != 0 )
		{
			/* Zero-copy... */
			p_tx_td->addr = ( uint32_t ) p_buffer;
		}
		#else
		{
			/* Or Memcopy... */
			memcpy((void *)p_tx_td->addr, p_buffer, ul_size);
		}
		#endif /* ipconfigZERO_COPY_TX_DRIVER */
		vGMACGenerateChecksum( ( uint8_t * ) p_tx_td->addr );
	}

#if( GMAC_USES_TX_CALLBACK != 0 )
	/* Tx callback */
	*p_func_tx_cb = func_tx_cb;
#endif

	/* Update transmit descriptor status */

	/* The buffer size defined is the length of ethernet frame,
	   so it's always the last buffer of the frame. */
	if( p_gmac_dev->l_tx_head == ( int32_t )( p_gmac_dev->ul_tx_list_size - 1 ) )
	{
		/* No need to 'and' with GMAC_TXD_LEN_MASK because ul_size has been checked */
		p_tx_td->status.val =
			ul_size | GMAC_TXD_LAST | GMAC_TXD_WRAP;
	} else {
		p_tx_td->status.val =
			ul_size | GMAC_TXD_LAST;
	}

	circ_inc32( &p_gmac_dev->l_tx_head, p_gmac_dev->ul_tx_list_size );

	/* Now start to transmit if it is still not done */
	gmac_start_transmission(p_hw);

	return GMAC_OK;
}

/**
 * \brief Get current load of transmit.
 *
 * \param p_gmac_dev Pointer to the GMAC device instance.
 *
 * \return Current load of transmit.
 */
#if( GMAC_USES_TX_CALLBACK != 0 )
/* Without defining GMAC_USES_TX_CALLBACK, l_tx_tail won't be updated */
uint32_t gmac_dev_get_tx_load(gmac_device_t* p_gmac_dev)
{
	uint16_t us_head = p_gmac_dev->l_tx_head;
	uint16_t us_tail = p_gmac_dev->l_tx_tail;
	return CIRC_CNT(us_head, us_tail, p_gmac_dev->ul_tx_list_size);
}
#endif

/**
 * \brief Register/Clear RX callback. Callback will be invoked after the next received
 * frame.
 *
 * When gmac_dev_read() returns GMAC_RX_NULL, the application task calls
 * gmac_dev_set_rx_callback() to register func_rx_cb() 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_dev_read() is called, it will be successful.
 *
 * This function is usually invoked from the RX callback itself with NULL
 * callback, to unregister. Once the callback has resumed the application task,
 * there is no need to invoke the callback again.
 *
 * \param p_gmac_dev Pointer to the GMAC device instance.
 * \param func_tx_cb  Receive callback function.
 */
void gmac_dev_set_rx_callback(gmac_device_t* p_gmac_dev,
		gmac_dev_rx_cb_t func_rx_cb)
{
	Gmac *p_hw = p_gmac_dev->p_hw;

	if (func_rx_cb == NULL) {
		gmac_disable_interrupt(p_hw, GMAC_IDR_RCOMP);
		p_gmac_dev->func_rx_cb = NULL;
	} else {
		p_gmac_dev->func_rx_cb = func_rx_cb;
		gmac_enable_interrupt(p_hw, GMAC_IER_RCOMP);
	}
}

/**
 *  \brief Register/Clear TX wakeup callback.
 *
 * When gmac_dev_write() returns GMAC_TX_BUSY (all transmit descriptor busy), the application
 * task calls gmac_dev_set_tx_wakeup_callback() to register func_wakeup() callback and
 * enters suspend state. The callback is in charge to resume the task once
 * several transmit descriptors have been released. The next time gmac_dev_write() 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 p_gmac_dev   Pointer to GMAC device instance.
 * \param func_wakeup    Pointer to wakeup callback function.
 * \param uc_threshold Number of free transmit descriptor before wakeup callback invoked.
 *
 * \return GMAC_OK, GMAC_PARAM on parameter error.
 */
#if( GMAC_USES_WAKEUP_CALLBACK )
uint8_t gmac_dev_set_tx_wakeup_callback(gmac_device_t* p_gmac_dev,
		gmac_dev_wakeup_cb_t func_wakeup_cb, uint8_t uc_threshold)
{
	if (func_wakeup_cb == NULL) {
		p_gmac_dev->func_wakeup_cb = NULL;
	} else {
		if (uc_threshold <= p_gmac_dev->ul_tx_list_size) {
			p_gmac_dev->func_wakeup_cb = func_wakeup_cb;
			p_gmac_dev->uc_wakeup_threshold = uc_threshold;
		} else {
			return GMAC_PARAM;
		}
	}

	return GMAC_OK;
}
#endif /* GMAC_USES_WAKEUP_CALLBACK */

/**
 * \brief Reset TX & RX queue & statistics.
 *
 * \param p_gmac_dev   Pointer to GMAC device instance.
 */
void gmac_dev_reset(gmac_device_t* p_gmac_dev)
{
	Gmac *p_hw = p_gmac_dev->p_hw;

	gmac_reset_rx_mem(p_gmac_dev);
	gmac_reset_tx_mem(p_gmac_dev);
	gmac_network_control(p_hw, GMAC_NCR_TXEN | GMAC_NCR_RXEN
			| GMAC_NCR_WESTAT | GMAC_NCR_CLRSTAT);
}

void gmac_dev_halt(Gmac* p_gmac);

void gmac_dev_halt(Gmac* p_gmac)
{
	gmac_network_control(p_gmac, GMAC_NCR_WESTAT | GMAC_NCR_CLRSTAT);
	gmac_disable_interrupt(p_gmac, ~0u);
}


/**
 * \brief GMAC Interrupt handler.
 *
 * \param p_gmac_dev   Pointer to GMAC device instance.
 */

#if( GMAC_STATS != 0 )
	extern int logPrintf( const char *pcFormat, ... );

	void gmac_show_irq_counts ()
	{
		int index;
		for (index = 0; index < ARRAY_SIZE(intPairs); index++) {
			if (gmacStats.intStatus[intPairs[index].index]) {
				logPrintf("%s : %6u\n", intPairs[index].name, gmacStats.intStatus[intPairs[index].index]);
			}
		}
	}
#endif

void gmac_handler(gmac_device_t* p_gmac_dev)
{
	Gmac *p_hw = p_gmac_dev->p_hw;

#if( GMAC_USES_TX_CALLBACK != 0 )
	gmac_tx_descriptor_t *p_tx_td;
	gmac_dev_tx_cb_t *p_tx_cb = NULL;
	uint32_t ul_tx_status_flag;
#endif
#if( GMAC_STATS != 0 )
	int index;
#endif

	/* volatile */ uint32_t ul_isr;
	/* volatile */ uint32_t ul_rsr;
	/* volatile */ uint32_t ul_tsr;

	ul_isr = gmac_get_interrupt_status(p_hw);
	ul_rsr = gmac_get_rx_status(p_hw);
	ul_tsr = gmac_get_tx_status(p_hw);

/*	Why clear bits that are ignored anyway ? */
/*	ul_isr &= ~(gmac_get_interrupt_mask(p_hw) | 0xF8030300); */
	#if( GMAC_STATS != 0 )
	{
		for (index = 0; index < ARRAY_SIZE(intPairs); index++) {
			if (ul_isr & intPairs[index].mask)
				gmacStats.intStatus[intPairs[index].index]++;
		}
	}
	#endif /* GMAC_STATS != 0 */

	/* RX packet */
	if ((ul_isr & GMAC_ISR_RCOMP) || (ul_rsr & (GMAC_RSR_REC|GMAC_RSR_RXOVR|GMAC_RSR_BNA))) {
		/* Clear status */
		gmac_clear_rx_status(p_hw, ul_rsr);

		if (ul_isr & GMAC_ISR_RCOMP)
			ul_rsr |= GMAC_RSR_REC;
		/* Invoke callbacks which can be useful to wake op a task */
		if (p_gmac_dev->func_rx_cb) {
			p_gmac_dev->func_rx_cb(ul_rsr);
		}
	}

	/* TX packet */
	if ((ul_isr & GMAC_ISR_TCOMP) || (ul_tsr & (GMAC_TSR_TXCOMP|GMAC_TSR_COL|GMAC_TSR_RLE|GMAC_TSR_UND))) {

#if( GMAC_USES_TX_CALLBACK != 0 )
		ul_tx_status_flag = GMAC_TSR_TXCOMP;
#endif
		/* A frame transmitted */

		/* Check RLE */
		if (ul_tsr & GMAC_TSR_RLE) {
			/* Status RLE & Number of discarded buffers */
#if( GMAC_USES_TX_CALLBACK != 0 )
			ul_tx_status_flag = GMAC_TSR_RLE | CIRC_CNT(p_gmac_dev->l_tx_head,
					p_gmac_dev->l_tx_tail, p_gmac_dev->ul_tx_list_size);
			p_tx_cb = &p_gmac_dev->func_tx_cb_list[p_gmac_dev->l_tx_tail];
#endif
			gmac_reset_tx_mem(p_gmac_dev);
			gmac_enable_transmit(p_hw, 1);
		}
		/* Clear status */
		gmac_clear_tx_status(p_hw, ul_tsr);

#if( GMAC_USES_TX_CALLBACK != 0 )
		if (!CIRC_EMPTY(p_gmac_dev->l_tx_head, p_gmac_dev->l_tx_tail)) {
			/* Check the buffers */
			do {
				p_tx_td = &p_gmac_dev->p_tx_dscr[p_gmac_dev->l_tx_tail];
				p_tx_cb = &p_gmac_dev->func_tx_cb_list[p_gmac_dev->l_tx_tail];
				/* Any error? Exit if buffer has not been sent yet */
				if ((p_tx_td->status.val & GMAC_TXD_USED) == 0) {
					break;
				}

				/* Notify upper layer that a packet has been sent */
				if (*p_tx_cb) {
					(*p_tx_cb) (ul_tx_status_flag, (void*)p_tx_td->addr);
					#if( ipconfigZERO_COPY_TX_DRIVER != 0 )
					{
						p_tx_td->addr = 0ul;
					}
					#endif /* ipconfigZERO_COPY_TX_DRIVER */
				}

				circ_inc32(&p_gmac_dev->l_tx_tail, p_gmac_dev->ul_tx_list_size);
			} while (CIRC_CNT(p_gmac_dev->l_tx_head, p_gmac_dev->l_tx_tail,
							p_gmac_dev->ul_tx_list_size));
		}

		if (ul_tsr & GMAC_TSR_RLE) {
			/* Notify upper layer RLE */
			if (*p_tx_cb) {
				(*p_tx_cb) (ul_tx_status_flag, NULL);
			}
		}
#endif /* GMAC_USES_TX_CALLBACK */

#if( GMAC_USES_WAKEUP_CALLBACK )
		/* If a wakeup has been scheduled, notify upper layer that it can
		   send other packets, and the sending will be successful. */
		if ((CIRC_SPACE(p_gmac_dev->l_tx_head, p_gmac_dev->l_tx_tail,
				p_gmac_dev->ul_tx_list_size) >= p_gmac_dev->uc_wakeup_threshold)
				&& p_gmac_dev->func_wakeup_cb) {
			p_gmac_dev->func_wakeup_cb();
		}
#endif
	}
}

//@}

/// @cond 0
/**INDENT-OFF**/
#ifdef __cplusplus
}
#endif
/**INDENT-ON**/
/// @endcond
