/**********************************************************************
* Copyright(C) 2011, NXP Semiconductor
* All rights reserved.
* Heavily modified by Real Time Engineers ltd.
***********************************************************************
* Software that is described herein is for illustrative purposes only
* which provides customers with programming information regarding the
* products. This software is supplied "AS IS" without any warranties.
* NXP Semiconductors assumes no responsibility or liability for the
* use of the software, conveys no license or title under any patent,
* copyright, or mask work right to the product. NXP Semiconductors
* reserves the right to make changes in the software without
* notification. NXP Semiconductors also make no representation or
* warranty that such application will be suitable for the specified
* use without further testing or modification.
**********************************************************************/

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

/* FreeRTOS+UDP includes. */
#include "FreeRTOS_UDP_IP.h"
#include "FreeRTOS_IP_Private.h"
#include "NetworkBufferManagement.h"

/* Library includes. */
#include "lpc18xx_emac.h"
#include "lpc18xx_rgu.h"
#include "lpc18xx_scu.h"
#include "lpc18xx_gpio.h"


#define emacTIMEOUT_DELAY	( 2 )
#define emacNEGOTIATE_DELAY	( 10 / portTICK_RATE_MS )

#define emacEXPECTED_RX_STATUS_MASK	( RX_FIRST_SEGM | RX_LAST_SEGM )

/* Rx descriptors and data array. */
static volatile RX_Desc Rx_Desc[ configNUM_RX_ETHERNET_DMA_DESCRIPTORS ];
static unsigned int RxDescIndex = 0;

/** Rx Status data array - Must be 8-Byte aligned */
#if defined ( __CC_ARM   )
	static __align(8) RX_Stat Rx_Stat[ configNUM_RX_ETHERNET_DMA_DESCRIPTORS ];
#elif defined ( __ICCARM__ )
	#pragma data_alignment=8
	static RX_Stat Rx_Stat[ configNUM_RX_ETHERNET_DMA_DESCRIPTORS ];
#elif defined   (  __GNUC__  )
	static volatile __attribute__ ((aligned (8))) RX_Stat Rx_Stat[ configNUM_RX_ETHERNET_DMA_DESCRIPTORS ];
#endif

/* Tx descriptors and status array. */
static volatile TX_Desc Tx_Desc[ configNUM_TX_ETHERNET_DMA_DESCRIPTORS ];
static volatile TX_Stat Tx_Stat[ configNUM_TX_ETHERNET_DMA_DESCRIPTORS ];
static unsigned int TxDescIndex = 0;

/* Private Functions ---------------------------------------------------------- */
static void rx_descr_init( void );
static void tx_descr_init( void );
static int32_t write_PHY( uint32_t PhyReg, uint16_t Value );
static int32_t read_PHY( uint32_t PhyReg );
static void setEmacAddr( uint8_t abStationAddr[] );

/*********************************************************************//**
 * @brief		Initializes the EMAC peripheral according to the specified
 *               parameters in the EMAC_ConfigStruct.
 * @param[in]	EMAC_ConfigStruct Pointer to a EMAC_CFG_Type structure
 *                    that contains the configuration information for the
 *                    specified EMAC peripheral.
 * @return		None
 *
 * Note: This function will initialize EMAC module according to procedure below:
 *  - Remove the soft reset condition from the MAC
 *  - Configure the PHY via the MIIM interface of the MAC
 *  - Select RMII mode
 *  - Configure the transmit and receive DMA engines, including the descriptor arrays
 *  - Configure the host registers (MAC1,MAC2 etc.) in the MAC
 *  - Enable the receive and transmit data paths
 *  In default state after initializing, only Rx Done and Tx Done interrupt are enabled,
 *  all remain interrupts are disabled
 *  (Ref. from LPC17xx UM)
 **********************************************************************/
portBASE_TYPE EMAC_Init(EMAC_CFG_Type *EMAC_ConfigStruct)
{
int32_t id1, id2, regv, phy = 0;
int32_t phy_linkstatus_reg, phy_linkstatus_mask;
uint32_t x;
const uint32_t ulMaxAttempts = 250UL;
portBASE_TYPE xReturn = pdPASS;

	/* Enable Ethernet Pins (NGX LPC1830 Xplorer. */
	scu_pinmux(0x2 ,0 , (MD_EHS | MD_PLN | MD_EZI | MD_ZI), FUNC7);
	scu_pinmux(0x1 ,17 , (MD_EHS | MD_PLN | MD_EZI | MD_ZI), FUNC3);
	scu_pinmux(0x1 ,18 , (MD_EHS | MD_PLN | MD_EZI | MD_ZI), FUNC3);
	scu_pinmux(0x1 ,20 , (MD_EHS | MD_PLN | MD_EZI | MD_ZI), FUNC3);
	scu_pinmux(0x1 ,19 , (MD_EHS | MD_PLN | MD_EZI | MD_ZI), FUNC0);
	scu_pinmux(0x0 ,1 , (MD_EHS | MD_PLN | MD_EZI | MD_ZI), FUNC6);
	scu_pinmux(0x1 ,15 , (MD_EHS | MD_PLN | MD_EZI | MD_ZI), FUNC3);
	scu_pinmux(0x0 ,0 , (MD_EHS | MD_PLN | MD_EZI | MD_ZI), FUNC2);
	scu_pinmux(0x1 ,16 , (MD_EHS | MD_PLN | MD_EZI | MD_ZI), FUNC3);
	scu_pinmux(0xC ,9 , (MD_EHS | MD_PLN | MD_EZI | MD_ZI), FUNC3);
	scu_pinmux(0x1 ,16 , (MD_EHS | MD_PLN | MD_EZI | MD_ZI), FUNC7);

	/* Ethernet RESET Pins */
	scu_pinmux(0x1 ,0 , MD_PUP, FUNC0);
	GPIO_SetDir(0,(1<<4), 1);
	GPIO_SetValue(0,(1<<4));


	#if MII				  /*   Select MII interface       */				 // check MUXING for new Eagle...
	  scu_pinmux(0xC ,6 , (MD_PLN | MD_EZI | MD_ZI), FUNC3); 	// ENET_RXD2: PC_6 -> FUNC3
	  scu_pinmux(0xC ,7 , (MD_PLN | MD_EZI | MD_ZI), FUNC3); 	// ENET_RXD3: PC_7 -> FUNC3
	  scu_pinmux(0xC ,0 , (MD_PLN | MD_EZI | MD_ZI), FUNC3); 	// ENET_RXLK: PC_0 -> FUNC3
	  scu_pinmux(0xC ,2 , (MD_PLN | MD_EZI | MD_ZI), FUNC3); 	// ENET_TXD2: PC_2 -> FUNC3
	  scu_pinmux(0xC ,3 , (MD_PLN | MD_EZI | MD_ZI), FUNC3); 	// ENET_TXD3: PC_3 -> FUNC3
	  scu_pinmux(0xC ,5 , (MD_PLN | MD_EZI | MD_ZI), FUNC3); 	// ENET_TX_ER:  PC_5 -> FUNC3
	  scu_pinmux(0x0 ,1 , (MD_PLN | MD_EZI | MD_ZI), FUNC2); 	// ENET_COL:  P0_1 -> FUNC2
	#else				   /*   Select RMII interface     */
	  LPC_CREG->CREG6 |= RMII_SELECT;
	#endif


	RGU_SoftReset( RGU_SIG_ETHERNET );

	/* Wait for reset. */
	while( !( LPC_RGU->RESET_ACTIVE_STATUS0 & ( 1 << ETHERNET_RST ) ) )
	{
		vTaskDelay( emacTIMEOUT_DELAY );
	}

	/* Reset all GMAC Subsystem internal registers and logic. */
	LPC_ETHERNET->DMA_BUS_MODE |= DMA_SOFT_RESET;

	/* Wait for software reset completion. */
	while( LPC_ETHERNET->DMA_BUS_MODE & DMA_SOFT_RESET )
	{
		vTaskDelay( emacTIMEOUT_DELAY );
	}

	/* Put the PHY in reset mode */
	write_PHY( PHY_REG_BMCR, PHY_BMCR_RESET );

	/* Wait for hardware reset to end. */
	for( x = 0; x < ulMaxAttempts; x++ )
	{
		regv = read_PHY (PHY_REG_BMCR);
		if( !( regv & PHY_BMCR_RESET ) )
		{
			/* Reset complete */
			break;
		}
		else
		{
			vTaskDelay( emacTIMEOUT_DELAY );
		}
	}

	if( x == ulMaxAttempts )
	{
		xReturn = pdFAIL;
	}

	/* Check if this is a DP83848C PHY. */
	id1 = read_PHY( PHY_REG_IDR1 );
	id2 = read_PHY( PHY_REG_IDR2 );
	if( ( ( id1 << 16 ) | ( id2 & 0xFFF0 ) ) == DP83848C_ID )
	{
		phy = DP83848C_ID;
	}
	else if( ( ( id1 << 16 ) | id2 ) == LAN8720_ID )
	{
		phy = LAN8720_ID;
	}

	if( phy != 0 )
	{
		/* Use autonegotiation about the link speed. */
		write_PHY( PHY_REG_BMCR, PHY_AUTO_NEG );

		/* Wait to complete Auto_Negotiation. */
		for( x = 0; x < ulMaxAttempts; x++ )
		{
			regv = read_PHY( PHY_REG_BMSR );

			if( ( regv & PHY_AUTO_NEG_DONE ) != 0 )
			{
				/* Auto negotiation Complete. */
				break;
			}
			else
			{
				vTaskDelay( emacNEGOTIATE_DELAY );
			}
		}

		if( x == ulMaxAttempts )
		{
			xReturn = pdFAIL;
		}
	}
	else
	{
		xReturn = pdFAIL;
	}


	if( xReturn == pdPASS )
	{
		/* Default to DP83848C. */
		phy_linkstatus_reg = PHY_REG_STS;
		phy_linkstatus_mask = 0x0001;

		if( phy == LAN8720_ID )
		{
			phy_linkstatus_reg = PHY_REG_BMSR;
			phy_linkstatus_mask = 0x0004;
		}

		/* Check the link status. */
		for( x = 0; x < ulMaxAttempts; x++ )
		{
			regv = read_PHY( phy_linkstatus_reg );

			if( ( regv & phy_linkstatus_mask ) != 0 )
			{
				/* Link is on. */
				break;
			}
			else
			{
				vTaskDelay( emacNEGOTIATE_DELAY );
			}
		}

		if( x == ulMaxAttempts )
		{
			xReturn = pdFAIL;
		}

		regv = read_PHY( PHY_REG_SPCON );
		regv &= PHY_REG_HCDSPEED_MASK;

		/* Configure 100MBit/10MBit mode and Full/Half Duplex mode. */
		switch( regv )
		{
			case PHY_REG_HCDSPEED_10MB_FULLD:
				LPC_ETHERNET->MAC_CONFIG |= MAC_DUPMODE;
				break;

			case PHY_REG_HCDSPEED_100MB_HALFD:
				LPC_ETHERNET->MAC_CONFIG |= MAC_100MPS;
				break;

			case PHY_REG_HCDSPEED_100MB_FULLD:
				LPC_ETHERNET->MAC_CONFIG |= MAC_DUPMODE;
				LPC_ETHERNET->MAC_CONFIG |= MAC_100MPS;
				break;

			default:
				break;
		}

		/* Set the Ethernet MAC Address registers */
		setEmacAddr( EMAC_ConfigStruct->pbEMAC_Addr );

		/* Initialize Descriptor Lists    */
		rx_descr_init();
		tx_descr_init();

		/* Configure Filter
		LPC_ETHERNET->MAC_FRAME_FILTER is left at its default value.
		MAC_PROMISCUOUS and MAC_RECEIVEALL can be set if required. */

		/* Enable Receiver and Transmitter   */
		LPC_ETHERNET->MAC_CONFIG |= (MAC_TX_ENABLE | MAC_RX_ENABLE);

		/* Enable interrupts    */
		LPC_ETHERNET->DMA_INT_EN =  DMA_INT_NOR_SUM | DMA_INT_RECEIVE ;

		/* Start Transmission & Receive processes   */
		LPC_ETHERNET->DMA_OP_MODE |= (DMA_SS_TRANSMIT | DMA_SS_RECEIVE );
	}

	return xReturn;
}

/*********************************************************************//**
 **********************************************************************/
portBASE_TYPE EMAC_CheckTransmitIndex( void )
{
portBASE_TYPE xReturn;

	if( ( Tx_Desc[ TxDescIndex ].Status & OWN_BIT ) == 0 )
	{
		xReturn = pdPASS;
	}
	else
	{
		xReturn = pdFAIL;
	}

	return xReturn;
}

/*********************************************************************//**
 * @brief 		EMAC_SetNextPacketToSend
 * @param[in] 	pucBuffer
 * @return 		None
 ***********************************************************************/
void EMAC_SetNextPacketToSend( uint8_t * pucBuffer )
{
	/* The old packet is now finished with and can be freed. */
	vEthernetBufferRelease( ( void * ) Tx_Desc[ TxDescIndex ].Packet );

	/* Assign the new packet to the descriptor. */
	Tx_Desc[ TxDescIndex ].Packet = ( uint32_t ) pucBuffer;
}

void EMAC_StartTransmitNextBuffer( uint32_t ulLength )
{
	Tx_Desc[ TxDescIndex ].Ctrl = ulLength;
	Tx_Desc[ TxDescIndex ].Status |= OWN_BIT;

	/* Wake Up the DMA if it's in Suspended Mode. */
	LPC_ETHERNET->DMA_TRANS_POLL_DEMAND = 1;
	TxDescIndex++;

	if( TxDescIndex == configNUM_TX_ETHERNET_DMA_DESCRIPTORS )
	{
		TxDescIndex = 0;
	}
}

/*********************************************************************//**
 * @brief		Get size of current Received data in received buffer (due to
 * 				RxConsumeIndex)
 * @param[in]	None
 * @return		Size of received data
 **********************************************************************/
uint32_t EMAC_GetReceiveDataSize(void)
{
unsigned short RxLen = 0;

	RxLen = ( Rx_Desc[ RxDescIndex ].Status >> 16 ) & 0x03FFF;
	return RxLen;
}

/*********************************************************************//**
 * @brief		Increase the RxConsumeIndex (after reading the Receive buffer
 * 				to release the Receive buffer) and wrap-around the index if
 * 				it reaches the maximum Receive Number
 * @param[in]	None
 * @return		None
 **********************************************************************/
void EMAC_UpdateRxConsumeIndex( void )
{
	Rx_Desc[ RxDescIndex ].Status = OWN_BIT;
	RxDescIndex++;

	if( RxDescIndex == configNUM_RX_ETHERNET_DMA_DESCRIPTORS )
	{
		RxDescIndex = 0;
	}
}

/*********************************************************************//**
 * @brief		Check whether if the current RxConsumeIndex is not equal to the
 * 				current RxProduceIndex.
 * @param[in]	None
 * @return		TRUE if they're not equal, otherwise return FALSE
 *
 * Note: In case the RxConsumeIndex is not equal to the RxProduceIndex,
 * it means there're available data has been received. They should be read
 * out and released the Receive Data Buffer by updating the RxConsumeIndex value.
 **********************************************************************/
portBASE_TYPE EMAC_CheckReceiveIndex(void)
{
portBASE_TYPE xReturn;

	if( ( Rx_Desc[ RxDescIndex ].Status & OWN_BIT ) == 0 )
	{
		xReturn = pdPASS;
	}
	else
	{
		xReturn = pdFAIL;
	}

	return xReturn;
}

void EMAC_NextPacketToRead( xNetworkBufferDescriptor_t *pxNetworkBuffer )
{
uint8_t *pucTemp;

	/* Swap the buffer in the network buffer with the buffer used by the DMA.
	This allows the data to be passed out without having to perform any copies. */
	pucTemp = ( uint8_t * ) Rx_Desc[ RxDescIndex ].Packet;
	Rx_Desc[ RxDescIndex ].Packet = ( uint32_t ) pxNetworkBuffer->pucEthernetBuffer;
	pxNetworkBuffer->pucEthernetBuffer = pucTemp;

	/* Only supports frames coming in single buffers.  If this frame is split
	across multiple buffers then reject it (and if the frame is needed increase
	the ipconfigNETWORK_MTU setting). */
	if( ( Rx_Desc[ RxDescIndex ].Status & emacEXPECTED_RX_STATUS_MASK ) != emacEXPECTED_RX_STATUS_MASK )
	{
		pxNetworkBuffer->xDataLength = 0;
	}
	else
	{
		pxNetworkBuffer->xDataLength = ( size_t ) EMAC_GetReceiveDataSize() - ( ipETHERNET_CRC_BYTES - 1U );;
	}
}

/*********************************************************************//**
 * @brief 		Initializes RX Descriptor
 * @param[in] 	None
 * @return 		None
 ***********************************************************************/
static void rx_descr_init( void )
{
uint32_t x;
size_t xBufferSize = ipTOTAL_ETHERNET_FRAME_SIZE;

	for( x = 0; x < configNUM_RX_ETHERNET_DMA_DESCRIPTORS; x++ )
	{
		/* Obtain the buffer first, as the size of the buffer might be changed
		within the pucEthernetBufferGet() call. */
		Rx_Desc[ x ].Packet  = ( uint32_t ) pucEthernetBufferGet( &xBufferSize );
		Rx_Desc[ x ].Status = OWN_BIT;
		Rx_Desc[ x ].Ctrl  = xBufferSize;
		Rx_Desc[ x ].NextDescripter = ( uint32_t ) &Rx_Desc[ x + 1 ];
		
		configASSERT( ( ( ( uint32_t ) Rx_Desc[x].Packet ) & 0x07 ) == 0 );
	}

	/* Last Descriptor */
	Rx_Desc[ configNUM_RX_ETHERNET_DMA_DESCRIPTORS - 1 ].Ctrl |= RX_END_RING;

	RxDescIndex = 0;

	/* Set Starting address of RX Descriptor list */
	LPC_ETHERNET->DMA_REC_DES_ADDR = ( uint32_t ) Rx_Desc;
}

/*********************************************************************//**
 * @brief 		Initializes TX Descriptor
 * @param[in] 	None
 * @return 		None
 ***********************************************************************/
static void tx_descr_init( void )
{
/* Initialize Transmit Descriptor and Status array. */
uint32_t x;

	for( x = 0; x < configNUM_TX_ETHERNET_DMA_DESCRIPTORS; x++ )
	{
		Tx_Desc[ x ].Status = TX_LAST_SEGM | TX_FIRST_SEGM;
		Tx_Desc[ x ].Ctrl  = 0;
		Tx_Desc[ x ].NextDescripter = ( uint32_t ) &Tx_Desc[ x + 1 ];

		/* Packet is assigned when a Tx is initiated. */
		Tx_Desc[ x ].Packet   = ( uint32_t )NULL;
	}

	/* Last Descriptor? */
	Tx_Desc[ configNUM_TX_ETHERNET_DMA_DESCRIPTORS-1 ].Status |= TX_END_RING;

	/* Set Starting address of TX Descriptor list */
	LPC_ETHERNET->DMA_TRANS_DES_ADDR = ( uint32_t ) Tx_Desc;
}


/*********************************************************************//**
 * @brief 		Write value to PHY device
 * @param[in] 	PhyReg: PHY Register address
 * @param[in] 	Value:  Value to write
 * @return 		0 - if success
 * 				1 - if fail
 ***********************************************************************/
static int32_t write_PHY (uint32_t PhyReg, uint16_t Value)
{
uint32_t x;
const uint32_t ulMaxAttempts = 250UL;
int32_t lReturn = pdPASS;

	/* Write a data 'Value' to PHY register 'PhyReg'. */
	x = 0;
	while( LPC_ETHERNET->MAC_MII_ADDR & GMII_BUSY )
	{
		x++;

		if( x >= ulMaxAttempts )
		{
			/* Time out. */
			lReturn = pdFAIL;
			break;
		}
		else
		{
			/* GMII is busy. */
			vTaskDelay( emacTIMEOUT_DELAY );
		}
	}

	if( lReturn == pdPASS )
	{
		LPC_ETHERNET->MAC_MII_ADDR = ( DP83848C_DEF_ADR << 11 ) | ( PhyReg << 6 ) | GMII_WRITE;
		LPC_ETHERNET->MAC_MII_DATA = Value;

		/* Start PHY Write Cycle. */
		LPC_ETHERNET->MAC_MII_ADDR |= GMII_BUSY;

		/* Wait untl operation completed. */
		for( x = 0; x < ulMaxAttempts; x++ )
		{
			if( ( LPC_ETHERNET->MAC_MII_ADDR & GMII_BUSY ) == 0 )
			{
				break;
			}
			else
			{
				vTaskDelay( emacTIMEOUT_DELAY );
			}
		}

		if( x == ulMaxAttempts )
		{
			/* Timeout. */
			lReturn = pdFAIL;
		}
	}

	return lReturn;
}

/*********************************************************************//**
 * @brief 		Read value from PHY device
 * @param[in] 	PhyReg: PHY Register address
 * @return 		0 - if success
 * 				1 - if fail
 ***********************************************************************/
static int32_t read_PHY( uint32_t PhyReg )
{
int32_t lValue = 0;
uint32_t x;
const uint32_t ulMaxAttempts = 250UL;

	/* Write a data 'Value' to PHY register 'PhyReg'. */
	x = 0;
	while( LPC_ETHERNET->MAC_MII_ADDR & GMII_BUSY )
	{
		x++;

		if( x >= ulMaxAttempts )
		{
			/* Time out. */
			break;
		}
		else
		{
			/* GMII is busy. */
			vTaskDelay( emacTIMEOUT_DELAY );
		}
	}

	if( x < ulMaxAttempts )
	{
		/* Read a PHY register 'PhyReg'. */
		LPC_ETHERNET->MAC_MII_ADDR = ( DP83848C_DEF_ADR << 11 ) | ( PhyReg << 6 ) | GMII_READ;

		/* Start PHY Read Cycle. */
		LPC_ETHERNET->MAC_MII_ADDR |= GMII_BUSY;

		/* Wait until operation completed */
		for( x = 0; x < ulMaxAttempts; x++ )
		{
			if( ( LPC_ETHERNET->MAC_MII_ADDR & GMII_BUSY ) == 0 )
			{
				break;
			}
			else
			{
				vTaskDelay( emacTIMEOUT_DELAY );
			}
		}

		configASSERT( x != ulMaxAttempts );
		lValue = LPC_ETHERNET->MAC_MII_DATA;
	}

	return lValue;
}

/*********************************************************************//**
 * @brief		Set Station MAC address for EMAC module
 * @param[in]	abStationAddr Pointer to Station address that contains 6-bytes
 * 				of MAC address (should be in order from MAC Address 1 to MAC Address 6)
 * @return		None
 **********************************************************************/
static void setEmacAddr( uint8_t abStationAddr[] )
{
	/* Set the Ethernet MAC Address registers */
	LPC_ETHERNET->MAC_ADDR0_HIGH = (( uint32_t ) abStationAddr[ 5 ] << 8 ) | ( uint32_t )abStationAddr[ 4 ];
	LPC_ETHERNET->MAC_ADDR0_LOW =	(( uint32_t )abStationAddr[ 3 ] << 24) | (( uint32_t )abStationAddr[ 2 ] << 16) | (( uint32_t )abStationAddr[ 1 ] << 8 ) | ( uint32_t )abStationAddr[ 0 ];
}



