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

/* Demo includes. */
#include "FEC.h"
#include "fecbd.h"
#include "mii.h"
#include "eth_phy.h"
#include "eth.h"

/* uIP includes. */
#include "uip.h"
#include "uip_arp.h"

/*-----------------------------------------------------------*/

/* FEC hardware specifics. */
#define MCF_FEC_MSCR_MII_SPEED(x)			(((x)&0x3F)<<0x1)
#define MCF_FEC_RDAR_R_DES_ACTIVE           ( 0x1000000 )
#define MCF_FEC_TDAR_X_DES_ACTIVE           ( 0x1000000 )

/* PHY hardware specifics. */
#define PHY_STATUS							( 16 )
#define PHY_DUPLEX_STATUS					( 4 )

/* Delay between polling the PHY to see if a link has been established. */
#define fecLINK_DELAY						( 500 / portTICK_PERIOD_MS )

/* Very short delay to use when waiting for the Tx to finish with a buffer if
we run out of Rx buffers. */
#define fecMINIMAL_DELAY					( 3 / portTICK_PERIOD_MS )

/* Don't block to wait for a buffer more than this many times. */
#define uipBUFFER_WAIT_ATTEMPTS	( 30 )

/* The Tx re-uses the Rx buffers and only has one descriptor. */
#define fecNUM_TX_DESCRIPTORS				( 1 )

/* The total number of buffers available, which should be greater than the
number of Rx descriptors. */
#define fecNUM_BUFFERS 						( configNUM_FEC_RX_DESCRIPTORS + 2 )

/*-----------------------------------------------------------*/

/*
 * Return an unused buffer to the pool of free buffers.
 */
static void prvReturnBuffer( unsigned char *pucBuffer );

/*
 * Find and return the next buffer that is not in use by anything else.
 */
static unsigned char *prvGetFreeBuffer( void );
/*-----------------------------------------------------------*/

/* The semaphore used to wake the uIP task when data arrives. */
SemaphoreHandle_t xFECSemaphore = NULL;

/* The buffer used by the uIP stack.  In this case the pointer is used to
point to one of the Rx buffers to avoid having to copy the Rx buffer into
the uIP buffer. */
unsigned char *uip_buf;

/* The DMA descriptors.  These are char arrays to allow us to align them
correctly. */
static unsigned char xFECTxDescriptors_unaligned[ ( fecNUM_TX_DESCRIPTORS * sizeof( FECBD ) ) + 16 ];
static unsigned char xFECRxDescriptors_unaligned[ ( configNUM_FEC_RX_DESCRIPTORS * sizeof( FECBD ) ) + 16 ];
static FECBD *pxFECTxDescriptor;
static FECBD *xFECRxDescriptors;

/* The DMA buffer.  This is a char arrays to allow it to be aligned correctly. */
static unsigned char ucFECRxBuffers[ ( fecNUM_BUFFERS * configFEC_BUFFER_SIZE ) + 16 ];

/* Index to the next descriptor to be inspected for received data. */
static unsigned long ulNextRxDescriptor = 0;

/* Contains the start address of each Rx buffer, after it has been correctly
aligned. */
static unsigned char *pucAlignedBufferStartAddresses[ fecNUM_BUFFERS ] = { 0 };

/* Each ucBufferInUse index corresponds to a position in the same index in the
pucAlignedBufferStartAddresses array.  If the index contains a 1 then the
buffer within pucAlignedBufferStartAddresses is in use, if it contains a 0 then
the buffer is free. */
static unsigned char ucBufferInUse[ fecNUM_BUFFERS ] = { 0 };

/*-----------------------------------------------------------*/

/********************************************************************/
/*
 * Write a value to a PHY's MII register.
 *
 * Parameters:
 *  ch          FEC channel
 *  phy_addr    Address of the PHY.
 *  reg_addr    Address of the register in the PHY.
 *  data        Data to be written to the PHY register.
 *
 * Return Values:
 *  0 on failure
 *  1 on success.
 *
 * Please refer to your PHY manual for registers and their meanings.
 * mii_write() polls for the FEC's MII interrupt event and clears it.
 * If after a suitable amount of time the event isn't triggered, a
 * value of 0 is returned.
 */
static int fec_mii_write( int phy_addr, int reg_addr, int data )
{
int timeout;
unsigned long eimr;

	/* Clear the MII interrupt bit */
	EIR = EIR_MII;

	/* Mask the MII interrupt */
	eimr = EIMR;
	EIMR &= ~EIMR_MII;

	/* Write to the MII Management Frame Register to kick-off the MII write */
	MMFR = ( unsigned long ) ( FEC_MMFR_ST_01 | FEC_MMFR_OP_WRITE | FEC_MMFR_PA(phy_addr) | FEC_MMFR_RA(reg_addr) | FEC_MMFR_TA_10 | FEC_MMFR_DATA( data ) );

	/* Poll for the MII interrupt (interrupt should be masked) */
	for (timeout = 0; timeout < FEC_MII_TIMEOUT; timeout++)
	{
		if( EIR & EIR_MII)
		{
			break;
		}
	}

	if( timeout == FEC_MII_TIMEOUT )
	{
		return 0;
	}

	/* Clear the MII interrupt bit */
	EIR = EIR_MII;

	/* Restore the EIMR */
	EIMR = eimr;

	return 1;
}

/********************************************************************/
/*
 * Read a value from a PHY's MII register.
 *
 * Parameters:
 *  ch          FEC channel
 *  phy_addr    Address of the PHY.
 *  reg_addr    Address of the register in the PHY.
 *  data        Pointer to storage for the Data to be read
 *              from the PHY register (passed by reference)
 *
 * Return Values:
 *  0 on failure
 *  1 on success.
 *
 * Please refer to your PHY manual for registers and their meanings.
 * mii_read() polls for the FEC's MII interrupt event and clears it.
 * If after a suitable amount of time the event isn't triggered, a
 * value of 0 is returned.
 */
static int fec_mii_read( int phy_addr, int reg_addr, unsigned short* data )
{
int timeout;
unsigned long eimr;

	/* Clear the MII interrupt bit */
	EIR = 0xffffffff;

	/* Mask the MII interrupt */
	eimr = EIMR;
	EIMR &= ~EIMR_MII;

	/* Write to the MII Management Frame Register to kick-off the MII read */
	MMFR = ( unsigned long ) ( FEC_MMFR_ST_01 | FEC_MMFR_OP_READ | FEC_MMFR_PA(phy_addr) | FEC_MMFR_RA(reg_addr) | FEC_MMFR_TA_10 );

	/* Poll for the MII interrupt (interrupt should be masked) */
	for (timeout = 0; timeout < FEC_MII_TIMEOUT; timeout++)
	{
		if (EIR)
		{
			break;
		}
	}

	if(timeout == FEC_MII_TIMEOUT)
	{
		return 0;
	}

	/* Clear the MII interrupt bit */
	EIR = EIR_MII;

	/* Restore the EIMR */
	EIMR = eimr;

	*data = (unsigned short)(MMFR & 0x0000FFFF);

	return 1;
}


/********************************************************************/
/*
 * Generate the hash table settings for the given address
 *
 * Parameters:
 *  addr    48-bit (6 byte) Address to generate the hash for
 *
 * Return Value:
 *  The 6 most significant bits of the 32-bit CRC result
 */
static unsigned char fec_hash_address( const unsigned char* addr )
{
unsigned long crc;
unsigned char byte;
int i, j;

	crc = 0xFFFFFFFF;
	for(i=0; i<6; ++i)
	{
		byte = addr[i];
		for(j=0; j<8; ++j)
		{
			if((byte & 0x01)^(crc & 0x01))
			{
				crc >>= 1;
				crc = crc ^ 0xEDB88320;
			}
			else
			{
				crc >>= 1;
			}

			byte >>= 1;
		}
	}

	return (unsigned char)(crc >> 26);
}

/********************************************************************/
/*
 * Set the Physical (Hardware) Address and the Individual Address
 * Hash in the selected FEC
 *
 * Parameters:
 *  ch  FEC channel
 *  pa  Physical (Hardware) Address for the selected FEC
 */
static void fec_set_address( const unsigned char *pa )
{
	unsigned char crc;

	/*
	* Set the Physical Address
	*/
	PALR = (unsigned long)((pa[0]<<24) | (pa[1]<<16) | (pa[2]<<8) | pa[3]);
	PAUR = (unsigned long)((pa[4]<<24) | (pa[5]<<16));

	/*
	* Calculate and set the hash for given Physical Address
	* in the  Individual Address Hash registers
	*/
	crc = fec_hash_address(pa);
	if(crc >= 32)
	{
		IAUR |= (unsigned long)(1 << (crc - 32));
	}
	else
	{
		IALR |= (unsigned long)(1 << crc);
	}
}
/*-----------------------------------------------------------*/

static void prvInitialiseFECBuffers( void )
{
unsigned portBASE_TYPE ux;
unsigned char *pcBufPointer;

	/* Set the pointer to a correctly aligned address. */
	pcBufPointer = &( xFECTxDescriptors_unaligned[ 0 ] );
	while( ( ( unsigned long ) pcBufPointer & 0x0fUL ) != 0 )
	{
		pcBufPointer++;
	}

	pxFECTxDescriptor = ( FECBD * ) pcBufPointer;

	/* Likewise the pointer to the Rx descriptor. */
	pcBufPointer = &( xFECRxDescriptors_unaligned[ 0 ] );
	while( ( ( unsigned long ) pcBufPointer & 0x0fUL ) != 0 )
	{
		pcBufPointer++;
	}

	xFECRxDescriptors = ( FECBD * ) pcBufPointer;

	/* There is no Tx buffer as the Rx buffer is reused. */
	pxFECTxDescriptor->length = 0;
	pxFECTxDescriptor->status = 0;

	/* Align the Rx buffers. */
	pcBufPointer = &( ucFECRxBuffers[ 0 ] );
	while( ( ( unsigned long ) pcBufPointer & 0x0fUL ) != 0 )
	{
		pcBufPointer++;
	}

	/* Then fill in the Rx descriptors. */
	for( ux = 0; ux < configNUM_FEC_RX_DESCRIPTORS; ux++ )
	{
	    xFECRxDescriptors[ ux ].status = RX_BD_E;
	    xFECRxDescriptors[ ux ].length = configFEC_BUFFER_SIZE;
	    xFECRxDescriptors[ ux ].data = pcBufPointer;

		/* Note the start address of the buffer now that it is correctly
		aligned. */
		pucAlignedBufferStartAddresses[ ux ] = pcBufPointer;

		/* The buffer is in use by the descriptor. */
		ucBufferInUse[ ux ] = pdTRUE;

	    pcBufPointer += configFEC_BUFFER_SIZE;
	}

	/* Note the start address of the last buffer as one more buffer is
	allocated than there are Rx descriptors. */
	pucAlignedBufferStartAddresses[ ux ] = pcBufPointer;

	/* Set uip_buf to point to the last buffer. */
	uip_buf = pcBufPointer;
	ucBufferInUse[ ux ] = pdTRUE;

	/* Set the wrap bit in the last descriptors to form a ring. */
	xFECRxDescriptors[ configNUM_FEC_RX_DESCRIPTORS - 1 ].status |= RX_BD_W;

	/* We start with descriptor 0. */
	ulNextRxDescriptor = 0;
}
/*-----------------------------------------------------------*/

void vInitFEC( void )
{
unsigned short usData;
struct uip_eth_addr xAddr;
const unsigned char ucMACAddress[6] =
{
	configMAC_0, configMAC_1,configMAC_2,configMAC_3,configMAC_4,configMAC_5
};

	prvInitialiseFECBuffers();

	/* Create the semaphore used to wake the uIP task when data arrives. */
	vSemaphoreCreateBinary( xFECSemaphore );

	/* Set the MAC address within the stack. */
	for( usData = 0; usData < 6; usData++ )
	{
		xAddr.addr[ usData ] = ucMACAddress[ usData ];
	}
	uip_setethaddr( xAddr );

	/* Set the Reset bit and clear the Enable bit */
	ECR_RESET = 1;

	/* Enable the clock. */
	SCGC4 |= SCGC4_FEC_MASK;

	/* Wait at least 8 clock cycles */
	for( usData = 0; usData < 10; usData++ )
	{
		asm( "NOP" );
	}

	/* Set MII speed to 2.5MHz. */
	MSCR = MCF_FEC_MSCR_MII_SPEED( ( ( configCPU_CLOCK_HZ / 1000000 ) / 5 ) + 1 );

	/*
	 * Make sure the external interface signals are enabled
	 */
    PTCPF2_C0 = 1;
    PTCPF2_C1 = 1;
    PTCPF2_C2 = 1;
    PTAPF1 = 0x55;
    PTAPF2 = 0x55;
    PTBPF1 = 0x55;
    PTBPF2 = 0x55;

    /* Set all pins to full drive with no filter. */
    PTADS = 0x06;
    PTAIFE = 0x06;
    PTBDS = 0xf4;
    PTBIFE = 0xf4;
    PTCDS = 0;
    PTCIFE = 0;


	/* Can we talk to the PHY? */
	do
	{
		vTaskDelay( fecLINK_DELAY );
		usData = 0xffff;
		fec_mii_read( configPHY_ADDRESS, PHY_PHYIDR1, &usData );

	} while( usData == 0xffff );

	/* Start auto negotiate. */
	fec_mii_write( configPHY_ADDRESS, PHY_BMCR, ( PHY_BMCR_AN_RESTART | PHY_BMCR_AN_ENABLE ) );

	/* Wait for auto negotiate to complete. */
	do
	{
		vTaskDelay( fecLINK_DELAY );
		fec_mii_read( configPHY_ADDRESS, PHY_BMSR, &usData );

	} while( !( usData & PHY_BMSR_AN_COMPLETE ) );

	/* When we get here we have a link - find out what has been negotiated. */
	usData = 0;
	fec_mii_read( configPHY_ADDRESS, PHY_STATUS, &usData );

	/* Setup half or full duplex. */
	if( usData & PHY_DUPLEX_STATUS )
	{
		RCR &= (unsigned long)~RCR_DRT;
		TCR |= TCR_FDEN;
	}
	else
	{
		RCR |= RCR_DRT;
		TCR &= (unsigned long)~TCR_FDEN;
	}

	/* Clear the Individual and Group Address Hash registers */
	IALR = 0;
	IAUR = 0;
	GALR = 0;
	GAUR = 0;

	/* Set the Physical Address for the selected FEC */
	fec_set_address( ucMACAddress );

	/* Set Rx Buffer Size */
	EMRBR = (unsigned short) configFEC_BUFFER_SIZE;

	/* Point to the start of the circular Rx buffer descriptor queue */
	ERDSR = ( volatile unsigned long ) &( xFECRxDescriptors[ 0 ] );

	/* Point to the start of the circular Tx buffer descriptor queue */
	ETSDR = ( volatile unsigned long ) pxFECTxDescriptor;

	/* Clear all FEC interrupt events */
	EIR = ( unsigned long ) -1;

	/* Various mode/status setup. */
	RCR = 0;
	RCR_MAX_FL = configFEC_BUFFER_SIZE;
	RCR_MII_MODE = 1;

	#if( configUSE_PROMISCUOUS_MODE == 1 )
	{
		RCR |= RCR_PROM;
	}
	#endif

	/* Enable interrupts. */
	EIMR = EIR_TXF_MASK | EIMR_RXF_MASK | EIMR_RXB_MASK | EIMR_UN_MASK | EIMR_RL_MASK | EIMR_LC_MASK | EIMR_BABT_MASK | EIMR_BABR_MASK | EIMR_HBERR_MASK;

	/* Enable the MAC itself. */
    ECR = ECR_ETHER_EN_MASK;

    /* Indicate that there have been empty receive buffers produced */
    RDAR = MCF_FEC_RDAR_R_DES_ACTIVE;
}
/*-----------------------------------------------------------*/

unsigned long ulFECRx( void )
{
unsigned long ulLen = 0UL;

	/* Is a buffer ready? */
	if( ( xFECRxDescriptors[ ulNextRxDescriptor ].status & RX_BD_E ) == 0 )
	{
		/* uip_buf is about to be set to a new buffer, so return the buffer it
		is already pointing to. */
		prvReturnBuffer( uip_buf );

		/* Obtain the size of the packet and put it into the "len" variable. */
		ulLen = xFECRxDescriptors[ ulNextRxDescriptor ].length;
		uip_buf = xFECRxDescriptors[ ulNextRxDescriptor ].data;

		/* The buffer that this descriptor was using is now in use by the
		TCP/IP stack, so allocate it a new buffer. */
		xFECRxDescriptors[ ulNextRxDescriptor ].data = prvGetFreeBuffer();

		/* Doing this here could cause corruption! */
		xFECRxDescriptors[ ulNextRxDescriptor ].status |= RX_BD_E;

		portENTER_CRITICAL();
		{
			ulNextRxDescriptor++;
			if( ulNextRxDescriptor >= configNUM_FEC_RX_DESCRIPTORS )
			{
				ulNextRxDescriptor = 0;
			}
		}
		portEXIT_CRITICAL();

		/* Tell the DMA a new buffer is available. */
		RDAR = MCF_FEC_RDAR_R_DES_ACTIVE;
	}

    return ulLen;
}
/*-----------------------------------------------------------*/

void vFECTx( void )
{
	/* When we get here the Tx descriptor should show as having completed. */
	while( pxFECTxDescriptor->status & TX_BD_R )
	{
		vTaskDelay( fecMINIMAL_DELAY );
	}

	portENTER_CRITICAL();
	{
		/* To maintain the zero copy implementation, point the Tx descriptor
		to the data from the Rx buffer. */
		pxFECTxDescriptor->data = uip_buf;

		/* Setup the buffer descriptor for transmission */
		pxFECTxDescriptor->length = uip_len;

		/* NB this assumes only one Tx descriptor! */
		pxFECTxDescriptor->status = ( TX_BD_R | TX_BD_L | TX_BD_TC | TX_BD_W );
	}
	portEXIT_CRITICAL();

	/* Continue the Tx DMA task (in case it was waiting for a new TxBD) */
	TDAR = MCF_FEC_TDAR_X_DES_ACTIVE;

	/* uip_buf is being used by the Tx descriptor.  Allocate a new buffer to
	uip_buf. */
	uip_buf = prvGetFreeBuffer();
}
/*-----------------------------------------------------------*/

static void prvReturnBuffer( unsigned char *pucBuffer )
{
unsigned long ul;

	/* Mark a buffer as free for use. */
	for( ul = 0; ul < fecNUM_BUFFERS; ul++ )
	{
		if( pucAlignedBufferStartAddresses[ ul ] == pucBuffer )
		{
			ucBufferInUse[ ul ] = pdFALSE;
			break;
		}
	}
}
/*-----------------------------------------------------------*/

static unsigned char *prvGetFreeBuffer( void )
{
portBASE_TYPE x;
unsigned char *pucReturn = NULL;
unsigned long ulAttempts = 0;

	while( pucReturn == NULL )
	{
		/* Look through the buffers to find one that is not in use by
		anything else. */
		for( x = 0; x < fecNUM_BUFFERS; x++ )
		{
			if( ucBufferInUse[ x ] == pdFALSE )
			{
				ucBufferInUse[ x ] = pdTRUE;
				pucReturn = pucAlignedBufferStartAddresses[ x ];
				break;
			}
		}

		/* Was a buffer found? */
		if( pucReturn == NULL )
		{
			ulAttempts++;

			if( ulAttempts >= uipBUFFER_WAIT_ATTEMPTS )
			{
				break;
			}

			/* Wait then look again. */
			vTaskDelay( fecMINIMAL_DELAY );
		}
	}

	return pucReturn;
}
/*-----------------------------------------------------------*/

void interrupt 86 vFECISRHandler( void )
{
unsigned long ulEvent;
portBASE_TYPE xHighPriorityTaskWoken = pdFALSE;

	/* Determine the cause of the interrupt. */
	ulEvent = EIR & EIMR;
	EIR = ulEvent;

	if( ulEvent & EIR_RXF_MASK )
	{
		/* A packet has been received.  Wake the handler task in case it is
		blocked. */
		xSemaphoreGiveFromISR( xFECSemaphore, &xHighPriorityTaskWoken );
	}

	if( ulEvent & EIR_TXF_MASK )
	{
		/* The Tx has completed.  Mark the buffer it was using as free again. */
		prvReturnBuffer( pxFECTxDescriptor->data );
		pxFECTxDescriptor->data = NULL;
	}

	if (ulEvent & ( EIR_UN_MASK | EIR_RL_MASK | EIR_LC_MASK | EIR_EBERR_MASK | EIR_BABT_MASK | EIR_BABR_MASK | EIR_HBERR_MASK ) )
	{
		/* Sledge hammer error handling. */
		prvInitialiseFECBuffers();
		RDAR = MCF_FEC_RDAR_R_DES_ACTIVE;
	}

	portEND_SWITCHING_ISR( xHighPriorityTaskWoken );
}
