/*
 * File:    fec.c
 * Purpose: Driver for the Fast Ethernet Controller (FEC)
 *
 * Notes:
 */

#include "net.h"
#include "fec.h"
#include "fecbd.h"
#include "exceptions.h"
#include "interrupts.h"
#include "MCF5475.h"
#include "MCD_dma.h"
#include "mcd_initiators.h"
#include "dma.h"
#include "bas_string.h"
#include "bas_printf.h"
#include "util.h"
#include "am79c874.h"
//#include "bcm5222.h"
#include <stdbool.h>

#if defined(MACHINE_FIREBEE)
#include "firebee.h"
#elif defined(MACHINE_M5484LITE)
#include "m5484l.h"
#else
#error Unknown machine!
#endif

//#define DBG_FEC
#ifdef DBG_FEC
#define dbg(format, arg...) do { xprintf("DEBUG: " format, ##arg); } while (0)
#else
#define dbg(format, arg...) do { ; } while (0)
#endif /* DBG_FEC */


FEC_EVENT_LOG fec_log[2];

/*
 * 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:
 *  1 on failure
 *  0 on success.
 *
 * Please refer to your PHY manual for registers and their meanings.
 * mii_write() polls for the FEC's MII interrupt event (which should
 * be masked from the interrupt handler) and clears it. If after a
 * suitable amount of time the event isn't triggered, a value of 0
 * is returned.
 */
int fec_mii_write(uint8_t ch, uint8_t phy_addr, uint8_t reg_addr, uint16_t data)
{
	int timeout;
	uint32_t eimr;

	/*
	 * Clear the MII interrupt bit
	 */
	MCF_FEC_EIR(ch) = MCF_FEC_EIR_MII;

	/*
	 * Write to the MII Management Frame Register to kick-off
	 * the MII write
	 */
	MCF_FEC_MMFR(ch) = 0
		| MCF_FEC_MMFR_ST_01
		| MCF_FEC_MMFR_OP_WRITE
		| MCF_FEC_MMFR_PA(phy_addr)
		| MCF_FEC_MMFR_RA(reg_addr)
		| MCF_FEC_MMFR_TA_10
		| MCF_FEC_MMFR_DATA(data);

	/*
	 * Mask the MII interrupt
	 */
	eimr = MCF_FEC_EIMR(ch);
	MCF_FEC_EIMR(ch) &= ~MCF_FEC_EIMR_MII;

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

	if(timeout == FEC_MII_TIMEOUT)
		return 0;

	/*
	 * Clear the MII interrupt bit
	 */
	MCF_FEC_EIR(ch) = MCF_FEC_EIR_MII;

	/*
	 * Restore the EIMR
	 */
	MCF_FEC_EIMR(ch) = 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:
 *  1 on failure
 *  0 on success.
 *
 * Please refer to your PHY manual for registers and their meanings.
 * mii_read() polls for the FEC's MII interrupt event (which should
 * be masked from the interrupt handler) and clears it. If after a
 * suitable amount of time the event isn't triggered, a value of 0
 * is returned.
 */
int fec_mii_read(uint8_t ch, uint8_t phy_addr, uint8_t reg_addr, uint16_t *data)
{
	int timeout;

	/*
	 * Clear the MII interrupt bit
	 */
	MCF_FEC_EIR(ch) = MCF_FEC_EIR_MII;

	/*
	 * Write to the MII Management Frame Register to kick-off
	 * the MII read
	 */
	MCF_FEC_MMFR(ch) = 0
		| MCF_FEC_MMFR_ST_01
		| MCF_FEC_MMFR_OP_READ
		| MCF_FEC_MMFR_PA(phy_addr)
		| MCF_FEC_MMFR_RA(reg_addr)
		| MCF_FEC_MMFR_TA_10;

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

	if(timeout == FEC_MII_TIMEOUT)
		return 0;

	/*
	 * Clear the MII interrupt bit
	 */
	MCF_FEC_EIR(ch) = MCF_FEC_EIR_MII;

	*data = (uint16_t)(MCF_FEC_MMFR(ch) & 0x0000FFFF);

	return 1;
}

/*
 * Initialize the MII interface controller
 *
 * Parameters:
 *  ch      FEC channel
 *  sys_clk System Clock Frequency (in MHz)
 */
void fec_mii_init(uint8_t ch, uint32_t sys_clk)
{
	/*
	 * Initialize the MII clock (EMDC) frequency
	 *
	 * Desired MII clock is 2.5MHz
	 * MII Speed Setting = System_Clock / (2.5MHz * 2)
	 * (plus 1 to make sure we round up)
	 */
	MCF_FEC_MSCR(ch) = MCF_FEC_MSCR_MII_SPEED((sys_clk / 5) + 1);
}

/* Initialize the MIB counters
 *
 * Parameters:
 *  ch      FEC channel
 */
void fec_mib_init(uint8_t ch)
{
	//To do
}

/*
 * Display the MIB counters
 *
 * Parameters:
 *  ch      FEC channel
 */
void fec_mib_dump(uint8_t ch)
{
	//To do
}

/*
 * Initialize the FEC log
 *
 * Parameters:
 *  ch      FEC channel
 */
void fec_log_init(uint8_t ch)
{
	memset(&fec_log[ch], 0, sizeof(FEC_EVENT_LOG));
}

/*
 * Display the FEC log
 *
 * Parameters:
 *  ch      FEC channel
 */
void fec_log_dump(uint8_t ch)
{
	dbg("%s: \r\n   FEC%d Log\r\n", __FUNCTION__, ch);
	dbg("%s:   ---------------\r\n", __FUNCTION__);
	dbg("%s: Total: %4d\r\n", __FUNCTION__, fec_log[ch].total);
	dbg("%s: hberr: %4d\r\n", __FUNCTION__, fec_log[ch].hberr);
	dbg("%s: babr:  %4d\r\n", __FUNCTION__, fec_log[ch].babr);
	dbg("%s: babt:  %4d\r\n", __FUNCTION__, fec_log[ch].babt);
	dbg("%s: gra:   %4d\r\n", __FUNCTION__, fec_log[ch].gra);
	dbg("%s: txf:   %4d\r\n", __FUNCTION__, fec_log[ch].txf);
	dbg("%s: mii:   %4d\r\n", __FUNCTION__, fec_log[ch].mii);
	dbg("%s: lc:    %4d\r\n", __FUNCTION__, fec_log[ch].lc);
	dbg("%s: rl:    %4d\r\n", __FUNCTION__, fec_log[ch].rl);
	dbg("%s: xfun:  %4d\r\n", __FUNCTION__, fec_log[ch].xfun);
	dbg("%s: xferr: %4d\r\n", __FUNCTION__, fec_log[ch].xferr);
	dbg("%s: rferr: %4d\r\n", __FUNCTION__, fec_log[ch].rferr);
	dbg("%s: dtxf:  %4d\r\n", __FUNCTION__, fec_log[ch].dtxf);
	dbg("%s: drxf:  %4d\r\n", __FUNCTION__, fec_log[ch].drxf);
	dbg("%s: \r\nRFSW:\r\n", __FUNCTION__);
	dbg("%s: inv:   %4d\r\n", __FUNCTION__, fec_log[ch].rfsw_inv);
	dbg("%s: m:     %4d\r\n", __FUNCTION__, fec_log[ch].rfsw_m);
	dbg("%s: bc:    %4d\r\n", __FUNCTION__, fec_log[ch].rfsw_bc);
	dbg("%s: mc:    %4d\r\n", __FUNCTION__, fec_log[ch].rfsw_mc);
	dbg("%s: lg:    %4d\r\n", __FUNCTION__, fec_log[ch].rfsw_lg);
	dbg("%s: no:    %4d\r\n", __FUNCTION__, fec_log[ch].rfsw_no);
	dbg("%s: cr:    %4d\r\n", __FUNCTION__, fec_log[ch].rfsw_cr);
	dbg("%s: ov:    %4d\r\n", __FUNCTION__, fec_log[ch].rfsw_ov);
	dbg("%s: tr:    %4d\r\n", __FUNCTION__, fec_log[ch].rfsw_tr);
	dbg("%s: ---------------\r\n\r\n", __FUNCTION__);
}

/*
 * Display some of the registers for debugging
 *
 * Parameters:
 *  ch      FEC channel
 */
void fec_debug_dump(uint8_t ch)
{
	dbg("\r\n------------- FEC%d -------------\r\n",ch);
	dbg("EIR      %08x        \r\n", MCF_FEC_EIR(ch));
	dbg("EIMR     %08x        \r\n", MCF_FEC_EIMR(ch));
	dbg("ECR      %08x        \r\n", MCF_FEC_ECR(ch));
	dbg("RCR      %08x        \r\n", MCF_FEC_RCR(ch));
	dbg("R_HASH   %08x        \r\n", MCF_FEC_RHR_HASH(ch));
	dbg("TCR      %08x        \r\n", MCF_FEC_TCR(ch));
	dbg("FECTFWR  %08x        \r\n", MCF_FEC_FECTFWR(ch));
	dbg("FECRFSR  %08x        \r\n", MCF_FEC_FECRFSR(ch));
	dbg("FECRFCR  %08x        \r\n", MCF_FEC_FECRFCR(ch));
	dbg("FECRLRFP %08x        \r\n", MCF_FEC_FECRLRFP(ch));
	dbg("FECRLWFP %08x        \r\n", MCF_FEC_FECRLWFP(ch));
	dbg("FECRFAR  %08x        \r\n", MCF_FEC_FECRFAR(ch));
	dbg("FECRFRP  %08x        \r\n", MCF_FEC_FECRFRP(ch));
	dbg("FECRFWP  %08x        \r\n", MCF_FEC_FECRFWP(ch));
	dbg("FECTFSR  %08x        \r\n", MCF_FEC_FECTFSR(ch));
	dbg("FECTFCR  %08x        \r\n", MCF_FEC_FECTFCR(ch));
	dbg("FECTLRFP %08x        \r\n", MCF_FEC_FECTLRFP(ch));
	dbg("FECTLWFP %08x        \r\n", MCF_FEC_FECTLWFP(ch));
	dbg("FECTFAR  %08x        \r\n", MCF_FEC_FECTFAR(ch));
	dbg("FECTFRP  %08x        \r\n", MCF_FEC_FECTFRP(ch));
	dbg("FECTFWP  %08x        \r\n", MCF_FEC_FECTFWP(ch));
	dbg("FRST     %08x        \r\n", MCF_FEC_FECFRST(ch));
	dbg("--------------------------------\r\n\r\n");
}

/*
 * Set the duplex on the selected FEC controller
 *
 * Parameters:
 *  ch      FEC channel
 *  duplex  FEC_MII_FULL_DUPLEX or FEC_MII_HALF_DUPLEX
 */
void fec_duplex(uint8_t ch, uint8_t duplex)
{
	switch (duplex)
	{
		case FEC_MII_HALF_DUPLEX:
			MCF_FEC_RCR(ch) |= MCF_FEC_RCR_DRT;
			MCF_FEC_TCR(ch) &= (uint32_t) ~MCF_FEC_TCR_FDEN;
			break;
		case FEC_MII_FULL_DUPLEX:
		default:
			MCF_FEC_RCR(ch) &= (uint32_t) ~MCF_FEC_RCR_DRT;
			MCF_FEC_TCR(ch) |= MCF_FEC_TCR_FDEN;
			break;
	}
}

/*
 * 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
 */
uint8_t fec_hash_address(const uint8_t *addr)
{
	uint32_t crc;
	uint8_t 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 (uint8_t)(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
 */
void fec_set_address(uint8_t ch, const uint8_t *pa)
{
	uint8_t crc;

	/*
	 * Set the Physical Address
	 */
	MCF_FEC_PALR(ch) = (uint32_t) ((pa[0] << 24) | (pa[1] << 16) | (pa[2] << 8) | pa[3]);
	MCF_FEC_PAHR(ch) = (uint32_t) ((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)
		MCF_FEC_IAUR(ch) |= (uint32_t) (1 << (crc - 32));
	else
		MCF_FEC_IALR(ch) |= (uint32_t) (1 << crc);
}

/*
 * Reset the selected FEC controller
 *
 * Parameters:
 *  ch      FEC channel
 */
void fec_reset(uint8_t ch)
{
	int i;

	/* Clear any events in the FIFO status registers */
	MCF_FEC_FECRFSR(ch) = (0
			| MCF_FEC_FECRFSR_OF
			| MCF_FEC_FECRFSR_UF
			| MCF_FEC_FECRFSR_RXW
			| MCF_FEC_FECRFSR_FAE
			| MCF_FEC_FECRFSR_IP);
	MCF_FEC_FECTFSR(ch) = (0
			| MCF_FEC_FECTFSR_OF
			| MCF_FEC_FECTFSR_UF
			| MCF_FEC_FECTFSR_TXW
			| MCF_FEC_FECTFSR_FAE
			| MCF_FEC_FECTFSR_IP);

	/* Reset the FIFOs */
	MCF_FEC_FECFRST(ch) |= MCF_FEC_FECFRST_SW_RST;
	MCF_FEC_FECFRST(ch) &= ~MCF_FEC_FECFRST_SW_RST;

	/* Set the Reset bit and clear the Enable bit */
	MCF_FEC_ECR(ch) = MCF_FEC_ECR_RESET;

	/* Wait at least 8 clock cycles */
	for (i = 0; i < 10; ++i)
		NOP();
}

/*
 * Initialize the selected FEC
 *
 * Parameters:
 *  ch      FEC channel
 *  mode    External interface mode (MII, 7-wire, or internal loopback)
 *  pa      Physical (Hardware) Address for the selected FEC
 */
void fec_init(uint8_t ch, uint8_t mode, const uint8_t *pa)
{
	/*
	 * Enable all the external interface signals
	 */
	if (mode == FEC_MODE_7WIRE)
	{
		if (ch == 1)
			MCF_PAD_PAR_FECI2CIRQ |= MCF_PAD_PAR_FECI2CIRQ_PAR_E17;
		else
			MCF_PAD_PAR_FECI2CIRQ |= MCF_PAD_PAR_FECI2CIRQ_PAR_E07;
	}
	else if (mode == FEC_MODE_MII)
	{
		if (ch == 1)
			MCF_PAD_PAR_FECI2CIRQ |= 0
				| MCF_PAD_PAR_FECI2CIRQ_PAR_E1MDC_E1MDC
				| MCF_PAD_PAR_FECI2CIRQ_PAR_E1MDIO_E1MDIO
				| MCF_PAD_PAR_FECI2CIRQ_PAR_E1MII
				| MCF_PAD_PAR_FECI2CIRQ_PAR_E17;
		else
			MCF_PAD_PAR_FECI2CIRQ |= 0
				| MCF_PAD_PAR_FECI2CIRQ_PAR_E0MDC
				| MCF_PAD_PAR_FECI2CIRQ_PAR_E0MDIO
				| MCF_PAD_PAR_FECI2CIRQ_PAR_E0MII
				| MCF_PAD_PAR_FECI2CIRQ_PAR_E07;
	}

	/*
	 * Clear the Individual and Group Address Hash registers
	 */
	MCF_FEC_IALR(ch) = 0;
	MCF_FEC_IAUR(ch) = 0;
	MCF_FEC_GALR(ch) = 0;
	MCF_FEC_GAUR(ch) = 0;

	/*
	 * Set the Physical Address for the selected FEC
	 */
	fec_set_address(ch, pa);

	/*
	 * Mask all FEC interrupts
	 */
	MCF_FEC_EIMR(ch) = MCF_FEC_EIMR_MASK_ALL;

	/*
	 * Clear all FEC interrupt events
	 */
	MCF_FEC_EIR(ch) = MCF_FEC_EIR_CLEAR_ALL;

	/*
	 * Initialize the Receive Control Register
	 */
	MCF_FEC_RCR(ch) = 0
		| MCF_FEC_RCR_MAX_FL(ETH_MAX_FRM)
//#ifdef FEC_PROMISCUOUS
		| MCF_FEC_RCR_PROM
//#endif
		| MCF_FEC_RCR_FCE;

	if (mode == FEC_MODE_MII)
		MCF_FEC_RCR(ch) |= MCF_FEC_RCR_MII_MODE;

	else if (mode == FEC_MODE_LOOPBACK)
		MCF_FEC_RCR(ch) |= MCF_FEC_RCR_LOOP;

	/*
	 * Initialize the Transmit Control Register
	 */
	MCF_FEC_TCR(ch) = MCF_FEC_TCR_FDEN;

	/*
	 * Set Rx FIFO alarm and granularity
	 */
	MCF_FEC_FECRFCR(ch) = 0
		| MCF_FEC_FECRFCR_FRMEN
		| MCF_FEC_FECRFCR_RXW_MSK
		| MCF_FEC_FECRFCR_GR(7);
	MCF_FEC_FECRFAR(ch) = MCF_FEC_FECRFAR_ALARM(768);

	/*
	 * Set Tx FIFO watermark, alarm and granularity
	 */
	MCF_FEC_FECTFCR(ch) = 0
		| MCF_FEC_FECTFCR_FRMEN
		| MCF_FEC_FECTFCR_TXW_MASK
		| MCF_FEC_FECTFCR_GR(7);
	MCF_FEC_FECTFAR(ch) = MCF_FEC_FECTFAR_ALARM(256);
	MCF_FEC_FECTFWR(ch) = MCF_FEC_FECTFWR_X_WMRK_256;

	/*
	 * Enable the transmitter to append the CRC
	 */
	MCF_FEC_FECCTCWR(ch) = 0
		| MCF_FEC_FECCTCWR_TFCW
		| MCF_FEC_FECCTCWR_CRC;
}

/*
 * Start the FEC Rx DMA task
 *
 * Parameters:
 *  ch      FEC channel
 *  rxbd    First Rx buffer descriptor in the chain
 */
void fec_rx_start(uint8_t ch, int8_t *rxbd)
{
	uint32_t initiator;
	int channel;
	int res;

	/*
	 * Make the initiator assignment
	 */
	res = dma_set_initiator(DMA_FEC_RX(ch));
	dbg("%s: dma_set_initiator(DMA_FEC_RX(%d)): %d\r\n", __FUNCTION__, ch, res);

	/*
	 * Grab the initiator number
	 */
	initiator = dma_get_initiator(DMA_FEC_RX(ch));
	dbg("%s: dma_get_initiator(DMA_FEC_RX(%d)) = %d\r\n", __FUNCTION__, ch, initiator);

	/*
	 * Determine the DMA channel running the task for the
	 * selected FEC
	 */
	channel = dma_set_channel(DMA_FEC_RX(ch),
			(ch == 0) ? fec0_rx_frame : fec1_rx_frame);
	dbg("%s: DMA channel for FEC%1d: %d\r\n", __FUNCTION__, ch, channel);

	/*
	 * Start the Rx DMA task
	 */
	MCD_startDma(channel,
			(int8_t *) rxbd,
			0,
			(int8_t *) MCF_FEC_FECRFDR(ch),
			0,
			RX_BUF_SZ,
			0,
			initiator,
			FECRX_DMA_PRI(ch),
			0 
			| MCD_FECRX_DMA   
			| MCD_INTERRUPT
			| MCD_TT_FLAGS_CW 
			| MCD_TT_FLAGS_RL
			| MCD_TT_FLAGS_SP
			,
			0 
			| MCD_NO_CSUM
			| MCD_NO_BYTE_SWAP
			);
	dbg("%s: Rx DMA task for FEC%1d started\r\n", __FUNCTION__, ch);
}

/*
 * Continue the Rx DMA task
 *
 * This routine is called after the DMA task has halted after
 * encountering an Rx buffer descriptor that wasn't marked as
 * ready. There is no harm in calling the DMA continue routine
 * if the DMA is not halted.
 *
 * Parameters:
 *  ch      FEC channel
 */
void fec_rx_continue(uint8_t ch)
{
	int channel;

	/*
	 * Determine the DMA channel running the task for the
	 * selected FEC
	 */
	channel = dma_get_channel(DMA_FEC_RX(ch));

	dbg("%s: RX DMA channel for FEC%1d is %d\r\n", __FUNCTION__, ch, channel);

	/*
	 * Continue/restart the DMA task
	 */
	MCD_continDma(channel);
	dbg("%s: RX dma on channel %d continued\r\n", __FUNCTION__, channel);
}

/*
 * Stop all frame receptions on the selected FEC
 *
 * Parameters:
 *  ch  FEC channel
 */
void fec_rx_stop (uint8_t ch)
{
	uint32_t mask;
	int channel;

	/* Save off the EIMR value */
	mask = MCF_FEC_EIMR(ch);

	/* Mask all interrupts */
	MCF_FEC_EIMR(ch) = 0;

	/*
	 * Determine the DMA channel running the task for the
	 * selected FEC
	 */
	channel = dma_get_channel(DMA_FEC_RX(ch));

	/* Kill the FEC Rx DMA task */
	MCD_killDma(channel);

	/* 
	 * Free up the FEC requestor from the software maintained 
	 * initiator list 
	 */
	dma_free_initiator(DMA_FEC_RX(ch));

	/* Free up the DMA channel */
	dma_free_channel(DMA_FEC_RX(ch));

	/* Restore the interrupt mask register value */
	MCF_FEC_EIMR(ch) = mask;
}

/*
 * Receive Frame interrupt handler - this handler is called by the 
 * DMA interrupt handler indicating that a packet was successfully
 * transferred out of the Rx FIFO.
 *
 * Parameters:
 *  nif     Pointer to Network Interface structure
 *  ch      FEC channel
 */
void fec_rx_frame(uint8_t ch, NIF *nif)
{
	ETH_HDR *eth_hdr;
	FECBD *pRxBD;
	NBUF *cur_nbuf, *new_nbuf;
	int keep;

	dbg("%s: started\r\n", __FUNCTION__);

	while ((pRxBD = fecbd_rx_alloc(ch)) != NULL)
	{
		fec_log[ch].drxf++;
		keep = true;

		/*
		 * Check the Receive Frame Status Word for errors
		 *  - The L bit should always be set
		 *  - No undefined bits should be set
		 *  - The upper 5 bits of the length should be cleared
		 */
		if (!(pRxBD->status & RX_BD_L) || (pRxBD->status & 0x0608) 
				|| (pRxBD->length & 0xF800))
		{
			keep = false;
			fec_log[ch].rfsw_inv++;
		}
		else if (pRxBD->status & RX_BD_ERROR)
		{
			keep = false;
			if (pRxBD->status & RX_BD_NO)
				fec_log[ch].rfsw_no++;
			if (pRxBD->status & RX_BD_CR)
				fec_log[ch].rfsw_cr++;
			if (pRxBD->status & RX_BD_OV)
				fec_log[ch].rfsw_ov++;
			if (pRxBD->status & RX_BD_TR)
				fec_log[ch].rfsw_tr++;
		}
		else
		{
			if (pRxBD->status & RX_BD_LG)
				fec_log[ch].rfsw_lg++;
			if (pRxBD->status & RX_BD_M)
				fec_log[ch].rfsw_m++;
			if (pRxBD->status & RX_BD_BC)
				fec_log[ch].rfsw_bc++;
			if (pRxBD->status & RX_BD_MC)
				fec_log[ch].rfsw_mc++;
		}

		if (keep)
		{
			/* 
			 * Pull the network buffer off the Rx ring queue 
			 */
			cur_nbuf = nbuf_remove(NBUF_RX_RING);

			/*
			 * Copy the buffer descriptor information to the network buffer
			 */
			cur_nbuf->length = (pRxBD->length - (ETH_HDR_LEN + ETH_CRC_LEN));
			cur_nbuf->offset = ETH_HDR_LEN;

			/*
			 * Get a new buffer pointer for this buffer descriptor
			 */
			new_nbuf = nbuf_alloc();
			if (new_nbuf == NULL)
			{
				dbg("%s: nbuf_alloc() failed\n", __FUNCTION__);

				/*
				 * Can't allocate a new network buffer, so we
				 * have to trash the received data and reuse the buffer
				 * hoping that some buffers will free up in the system
				 * and this frame will be re-transmitted by the host
				 */
				pRxBD->length = RX_BUF_SZ;
				pRxBD->status &= (RX_BD_W | RX_BD_INTERRUPT);
				pRxBD->status |= RX_BD_E;
				nbuf_add(NBUF_RX_RING, cur_nbuf);
				fec_rx_continue(ch);

				continue;
			}

			/*
			 * Add the new network buffer to the Rx ring queue
			 */
			nbuf_add(NBUF_RX_RING, new_nbuf);

			/*
			 * Re-initialize the buffer descriptor - pointing it
			 * to the new data buffer.  The previous data buffer
			 * will be passed up the stack
			 */
			pRxBD->data = new_nbuf->data;
			pRxBD->length = RX_BUF_SZ;
			pRxBD->status &= (RX_BD_W | RX_BD_INTERRUPT);
			pRxBD->status |= RX_BD_E;


			/*
			 * Let the DMA know that there is a new Rx BD (in case the 
			 * ring was full and the DMA was waiting for an empty one)
			 */
			fec_rx_continue(ch);

			/*
			 * Get pointer to the frame data inside the network buffer
			 */
			eth_hdr = (ETH_HDR *) cur_nbuf->data;

			/*
			 * Pass the received packet up the network stack if the 
			 * protocol is supported in our network interface (NIF)
			 */
			if (nif_protocol_exist(nif, eth_hdr->type))
			{
				hexdump((uint8_t *) eth_hdr, ETH_MAX_FRM);
				nif_protocol_handler(nif, eth_hdr->type, cur_nbuf);
			}
			else
			{
				nbuf_free(cur_nbuf);
				dbg("%s: got unsupported packet %d, trashed it\r\n", __FUNCTION__, eth_hdr->type);
			}
		}
		else 
		{
			/* 
			 * This frame isn't a keeper
			 * Reset the status and length, but don't need to get another
			 * buffer since we are trashing the data in the current one
			 */
			pRxBD->length = RX_BUF_SZ;
			pRxBD->status &= (RX_BD_W | RX_BD_INTERRUPT);
			pRxBD->status |= RX_BD_E;

			/*
			 * Move the current buffer from the beginning to the end of the 
			 * Rx ring queue
			 */
			cur_nbuf = nbuf_remove(NBUF_RX_RING);
			nbuf_add(NBUF_RX_RING, cur_nbuf);

			/*
			 * Let the DMA know that there are new Rx BDs (in case
			 * it is waiting for an empty one)
			 */
			fec_rx_continue(ch);
		}
	}
}

void fec0_rx_frame(void)
{
	extern NIF nif1;

	fec_rx_frame(0, &nif1);
}

void fec1_rx_frame(void)
{
	extern NIF nif1;

	fec_rx_frame(1, &nif1);
}

/*
 * Start the FEC Tx DMA task
 *
 * Parameters:
 *  ch      FEC channel
 *  txbd    First Tx buffer descriptor in the chain
 */
void fec_tx_start(uint8_t ch, int8_t *txbd)
{
	uint32_t initiator;
	int channel;
	int result;
	void fec0_tx_frame(void);
	void fec1_tx_frame(void);
	int res;

	/*
	 * Make the initiator assignment
	 */
	res = dma_set_initiator(DMA_FEC_TX(ch));
	dbg("%s: dma_set_initiator(%d) = %d\r\n", __FUNCTION__, ch, res);

	/*
	 * Grab the initiator number
	 */
	initiator = dma_get_initiator(DMA_FEC_TX(ch));
	dbg("%s: dma_get_initiator(%d) = %d\r\n", __FUNCTION__, ch, initiator);


	/*
	 * Determine the DMA channel running the task for the
	 * selected FEC
	 */
	channel = dma_set_channel(DMA_FEC_TX(ch),
			(ch == 0) ? fec0_tx_frame : fec1_tx_frame);
	dbg("%s: dma_set_channel(%d, ...) = %d\r\n", __FUNCTION__, ch, channel);

	/*
	 * Start the Tx DMA task
	 */
	MCD_startDma(channel,
			(int8_t *) txbd,
			0,
			(int8_t*) MCF_FEC_FECTFDR(ch),
			0,
			ETH_MTU,
			0,
			initiator,
			FECTX_DMA_PRI(ch),
			0 
			| MCD_FECTX_DMA   
			| MCD_INTERRUPT
			| MCD_TT_FLAGS_CW 
			| MCD_TT_FLAGS_RL
			| MCD_TT_FLAGS_SP
			,
			0 
			| MCD_NO_CSUM
			| MCD_NO_BYTE_SWAP
			);
	dbg("%s: DMA tx task started\r\n", __FUNCTION__);
}

/*
 * Continue the Tx DMA task
 *
 * This routine is called after the DMA task has halted after
 * encountering an Tx buffer descriptor that wasn't marked as
 * ready.  There is no harm in calling the continue DMA routine
 * if the DMA was not paused.
 *
 * Parameters:
 *  ch      FEC channel
 */
void fec_tx_continue(uint8_t ch)
{
	int channel;

	/*
	 * Determine the DMA channel running the task for the
	 * selected FEC
	 */
	channel = dma_get_channel(DMA_FEC_TX(ch));
	dbg("%s: dma_get_channel(DMA_FEC_TX(%d)) = %d\r\n",
			__FUNCTION__, ch, channel);

	/*
	 * Continue/restart the DMA task
	 */
	MCD_continDma(channel);
	dbg("%s: DMA TX task continue\r\n", __FUNCTION__);
}

/*
 * Stop all transmissions on the selected FEC and kill the DMA task
 *
 * Parameters:
 *  ch  FEC channel
 */
void fec_tx_stop(uint8_t ch)
{
	uint32_t mask;
	int channel;


	/* Save off the EIMR value */
	mask = MCF_FEC_EIMR(ch);

	/* Mask all interrupts */
	MCF_FEC_EIMR(ch) = 0;

	/* If the Ethernet is still enabled... */
	if (MCF_FEC_ECR(ch) & MCF_FEC_ECR_ETHER_EN)
	{
		/* Issue the Graceful Transmit Stop */
		MCF_FEC_TCR(ch) |= MCF_FEC_TCR_GTS;

		/* Wait for the Graceful Stop Complete interrupt */
		while (!(MCF_FEC_EIR(ch) & MCF_FEC_EIR_GRA))
		{
			if (!(MCF_FEC_ECR(ch) & MCF_FEC_ECR_ETHER_EN))
				break;
		}

		/* Clear the Graceful Stop Complete interrupt */
		MCF_FEC_EIR(ch) = MCF_FEC_EIR_GRA;
	}

	/*
	 * Determine the DMA channel running the task for the
	 * selected FEC
	 */
	channel = dma_get_channel(DMA_FEC_TX(ch));

	/* Kill the FEC Tx DMA task */
	MCD_killDma(channel);

	/* 
	 * Free up the FEC requestor from the software maintained 
	 * initiator list 
	 */
	dma_free_initiator(DMA_FEC_TX(ch));

	/* Free up the DMA channel */
	dma_free_channel(DMA_FEC_TX(ch));

	/* Restore the interrupt mask register value */
	MCF_FEC_EIMR(ch) = mask;
}

/*
 * Trasmit Frame interrupt handler - this handler is called by the 
 * DMA interrupt handler indicating that a packet was successfully
 * transferred to the Tx FIFO.
 *
 * Parameters:
 *  ch      FEC channel
 */
void fec_tx_frame(uint8_t ch)
{
	FECBD *pTxBD;
	NBUF *pNbuf;
	bool is_empty = true;

	dbg("%s:\r\n", __FUNCTION__);
	while ((pTxBD = fecbd_tx_free(ch)) != NULL)
	{
		fec_log[ch].dtxf++;

		/*
		 * Grab the network buffer associated with this buffer descriptor
		 */
		pNbuf = nbuf_remove(NBUF_TX_RING);

		/*
		 * Free up the network buffer that was just transmitted
		 */
		nbuf_free(pNbuf);
		dbg("%s: free buffer %p from TX ring\r\n", __FUNCTION__, pNbuf);

		/*
		 * Re-initialize the Tx BD
		 */
		pTxBD->data = NULL;
		pTxBD->length = 0;
		is_empty = false;

	}    
	if (is_empty)
		dbg("%s: transmit queue was empty!\r\n", __FUNCTION__);
}

void fec0_tx_frame(void)
{
	fec_tx_frame(0);
}

void fec1_tx_frame(void)
{
	fec_tx_frame(1);
}

/********************************************************************/
/*
 * Send a packet out the selected FEC
 *
 * Parameters:
 *  ch      FEC channel
 *  nif     Pointer to Network Interface (NIF) structure
 *  dst     Destination MAC Address
 *  src     Source MAC Address
 *  type    Ethernet Frame Type
 *  length  Number of bytes to be transmitted (doesn't include type, 
 *          src, or dest byte count)
 *  pkt     Pointer packet network buffer
 *
 * Return Value:
 *  1       success
 *  0       otherwise
 */
int fec_send(uint8_t ch, NIF *nif, uint8_t *dst, uint8_t *src, uint16_t type, NBUF *nbuf)
{
	FECBD *pTxBD;

	/* Check the length */
	if ((nbuf->length + ETH_HDR_LEN) > ETH_MTU)
	{
		dbg("%s: nbuf->length (%d) + ETH_HDR_LEN (%d) exceeds ETH_MTU (%d)\r\n", 
				__FUNCTION__, nbuf->length, ETH_HDR_LEN, ETH_MTU);
		return 0;
	}

	/* 
	 * Copy the destination address, source address, and Ethernet 
	 * type into the packet 
	 */
	memcpy(&nbuf->data[0],  dst,   6);
	memcpy(&nbuf->data[6],  src,   6);
	memcpy(&nbuf->data[12], &type, 2);

	/*
	 * Grab the next available Tx Buffer Descriptor
	 */
	while ((pTxBD = fecbd_tx_alloc(ch)) == NULL) {};

	/*
	 * Put the network buffer into the Tx waiting queue
	 */
	nbuf_add(NBUF_TX_RING, nbuf);

	/* 
	 * Setup the buffer descriptor for transmission
	 */
	pTxBD->data = nbuf->data;
	pTxBD->length = nbuf->length + ETH_HDR_LEN;
	pTxBD->status |= (TX_BD_R | TX_BD_L);

	/*
	 * Continue the Tx DMA task (in case it was waiting for a new
	 * TxBD to be ready
	 */
	fec_tx_continue(ch);

	return 1;
}

int fec0_send(NIF *nif, uint8_t *dst, uint8_t *src, uint16_t type, NBUF *nbuf)
{
	return fec_send(0, nif, dst, src, type, nbuf);
}

int fec1_send(NIF *nif, uint8_t *dst, uint8_t *src, uint16_t type, NBUF *nbuf)
{
	return fec_send(1, nif, dst, src, type, nbuf);
}

/********************************************************************/
/*
 * Enable interrupts on the selected FEC
 *
 * Parameters:
 *  ch      FEC channel
 *  pri     Interrupt Priority
 *  lvl     Interrupt Level
 */
void fec_irq_enable(uint8_t ch, uint8_t lvl, uint8_t pri)
{
	/*
	 * Setup the appropriate ICR
	 */
	MCF_INTC_ICR((ch == 0) ? 39 : 38) = (uint8_t)(0
			| MCF_INTC_ICR_IP(pri)
			| MCF_INTC_ICR_IL(lvl));

	/*
	 * Clear any pending FEC interrupt events
	 */
	MCF_FEC_EIR(ch) = MCF_FEC_EIR_CLEAR_ALL;

	/*
	 * Unmask all FEC interrupts
	 */
	MCF_FEC_EIMR(ch) = MCF_FEC_EIMR_UNMASK_ALL;

	/*
	 * Unmask the FEC interrupt in the interrupt controller
	 */
	if (ch == 0)
		MCF_INTC_IMRH &= ~MCF_INTC_IMRH_INT_MASK39;
	else
		MCF_INTC_IMRH &= ~MCF_INTC_IMRH_INT_MASK38;
}

/********************************************************************/
/*
 * Disable interrupts on the selected FEC
 *
 * Parameters:
 *  ch      FEC channel
 */
void fec_irq_disable(uint8_t ch)
{

	/*
	 * Mask all FEC interrupts
	 */
	MCF_FEC_EIMR(ch) = MCF_FEC_EIMR_MASK_ALL;

	/*
	 * Mask the FEC interrupt in the interrupt controller
	 */
	if (ch == 0)
		MCF_INTC_IMRH |= MCF_INTC_IMRH_INT_MASK39;
	else
		MCF_INTC_IMRH |= MCF_INTC_IMRH_INT_MASK38;
}

/********************************************************************/
/*
 * FEC interrupt handler
 * All interrupts are multiplexed into a single vector for each
 * FEC module. The lower level interrupt handler passes in the
 * channel to this handler. Note that the receive interrupt is
 * generated by the Multi-channel DMA FEC Rx task.
 *
 * Parameters:
 * ch       FEC channel
 */
static void fec_irq_handler(uint8_t ch)
{
	uint32_t event, eir;

	/*
	 * Determine which interrupt(s) asserted by AND'ing the
	 * pending interrupts with those that aren't masked.
	 */
	eir = MCF_FEC_EIR(ch);
	event = eir & MCF_FEC_EIMR(ch);

	if (event != eir)
		dbg("%s: pending but not enabled: 0x%08x\r\n", __FUNCTION__, (event ^ eir));

	/*
	 * Clear the event(s) in the EIR immediately
	 */
	MCF_FEC_EIR(ch) = event;

	if (event & MCF_FEC_EIR_RFERR)
	{
		fec_log[ch].total++;
		fec_log[ch].rferr++;
		dbg("%s: RFERR\r\n", __FUNCTION__);
		dbg("%s: FECRFSR%d = 0x%08x\r\n", __FUNCTION__, ch, MCF_FEC_FECRFSR(ch));
		//fec_eth_stop(ch);
	}

	if (event & MCF_FEC_EIR_XFERR)
	{
		fec_log[ch].total++;
		fec_log[ch].xferr++;
		dbg("%s: XFERR\r\n", __FUNCTION__);
	}

	if (event & MCF_FEC_EIR_XFUN)
	{
		fec_log[ch].total++;
		fec_log[ch].xfun++;
		dbg("%s: XFUN\r\n", __FUNCTION__);
		//fec_eth_stop(ch);
	}

	if (event & MCF_FEC_EIR_RL)
	{
		fec_log[ch].total++;
		fec_log[ch].rl++;
		dbg("%s: RL\r\n", __FUNCTION__);
	}

	if (event & MCF_FEC_EIR_LC)
	{
		fec_log[ch].total++;
		fec_log[ch].lc++;
		dbg("%s: LC\r\n", __FUNCTION__);
	}

	if (event & MCF_FEC_EIR_MII)
	{
		fec_log[ch].mii++;
		dbg("%s: MII\r\n", __FUNCTION__);
	}

	if (event & MCF_FEC_EIR_TXF)
	{
		fec_log[ch].txf++;
		dbg("%s: TXF\r\n", __FUNCTION__);
		fec_log_dump(0);
	}

	if (event & MCF_FEC_EIR_GRA)
	{
		fec_log[ch].gra++;
		dbg("%s: GRA\r\n", __FUNCTION__);
	}

	if (event & MCF_FEC_EIR_BABT)
	{
		fec_log[ch].total++;
		fec_log[ch].babt++;
		dbg("%s: BABT\r\n", __FUNCTION__);
	}

	if (event & MCF_FEC_EIR_BABR)
	{
		fec_log[ch].total++;
		fec_log[ch].babr++;
		dbg("%s: BABR\r\n", __FUNCTION__);
	}

	if (event & MCF_FEC_EIR_HBERR)
	{
		fec_log[ch].total++;
		fec_log[ch].hberr++;
		dbg("%s: HBERR\r\n", __FUNCTION__);
	}
}

/*
 * handler for FEC interrupts
 * arg2 is a pointer to the nif in this case
 */
int fec0_interrupt_handler(void* arg1, void* arg2)
{
	(void) arg1;
	(void) arg2;

	fec_irq_handler(0);

	return 1;
}

int fec1_interrupt_handler(void* arg1, void* arg2)
{
	(void) arg1;
	(void) arg2;

	fec_irq_handler(1);

	return 1;
}

/********************************************************************/
/*
 * Configure the selected Ethernet port and enable all operations
 *
 * Parameters:
 *  ch      FEC channel
 *  trcvr   Transceiver mode (MII, 7-Wire or internal loopback)
 *  speed   Maximum operating speed (MII only)
 *  duplex  Full or Half-duplex (MII only)
 *  mac     Physical (MAC) Address
 */
void fec_eth_setup(uint8_t ch, uint8_t trcvr, uint8_t speed, uint8_t duplex, const uint8_t *mac)
{
	/*
	 * Disable FEC interrupts
	 */
	fec_irq_disable(ch);

	/*
	 * Initialize the event log
	 */
	fec_log_init(ch);

	/*
	 * Initialize the network buffers and fec buffer descriptors
	 */
	nbuf_init();
	fecbd_init(ch);

	/*
	 * Initialize the FEC
	 */
	fec_reset(ch);
	fec_init(ch, trcvr, mac);

	if (trcvr == FEC_MODE_MII)
	{
		/*
		 * Initialize the MII interface
		 */
#if defined(MACHINE_FIREBEE)
		if (am79c874_init(0, 0, speed, duplex))
			dbg("%s: PHY init completed\r\n", __FUNCTION__);
		else
			dbg("%s: PHY init failed\r\n", __FUNCTION__);
#elif defined(MACHINE_M548X)
		bcm_5222_init(0, 0, speed, duplex);
#else
		fec_mii_init(ch, SYSCLK / 1000);
#endif /* MACHINE_FIREBEE */
	}

	/*
	 * Initialize and enable FEC interrupts
	 */
	fec_irq_enable(ch, FEC_INTC_LVL(ch), FEC_INTC_PRI(ch));

	/*
	 * Enable the multi-channel DMA tasks
	 */
	fec_rx_start(ch, (int8_t*) fecbd_get_start(ch, Rx));
	fec_tx_start(ch, (int8_t*) fecbd_get_start(ch, Tx));

	/*
	 * Enable the FEC channel
	 */
	MCF_FEC_ECR(ch) |= MCF_FEC_ECR_ETHER_EN;
}

/********************************************************************/
/*
 * Reset the selected Ethernet port
 *
 * Parameters:
 *  ch      FEC channel
 */
void fec_eth_reset(uint8_t ch)
{
	// To do
}

/********************************************************************/
/*
 * Stop the selected Ethernet port
 *
 * Parameters:
 *  ch      FEC channel
 */
void fec_eth_stop(uint8_t ch)
{
	int level;

	/*
	 * Disable interrupts
	 */
	level = set_ipl(7);

	dbg("%s: fec %d stopped\r\n", __FUNCTION__, ch);
	/*
	 * Gracefully disable the receiver and transmitter
	 */
	fec_tx_stop(ch);
	fec_rx_stop(ch);

	/*
	 * Disable FEC interrupts
	 */
	fec_irq_disable(ch);

	/*
	 * Disable the FEC channel
	 */
	MCF_FEC_ECR(ch) &= ~MCF_FEC_ECR_ETHER_EN;

#ifdef DBG_FEC
	nbuf_debug_dump();
	fec_log_dump(ch);
#endif

	/* 
	 * Flush the network buffers
	 */
	nbuf_flush();

	/* 
	 * Restore interrupt level
	 */
	set_ipl(level);
}

