/*
 * ENC424J600.c
 *
 *  Created on: Nov 14, 2011
 *      Author: RomanM
 */

#include "hal_Enc424j600.h"
#include "Enc424j600.h"
#include "Enc424j600_Registers.h"
#include "Enc424j600_SPI_Instructions.h"

#define	TXSTART	0x0000
#define	RXSTART	0x3000
#define	RAMSIZE	0x6000

uint16_t	nextPacketPointer;

typedef struct {
	uint16_t ReceiveByteCount;
	uint8_t  PacketPreviouslyIgnored	: 1;
	uint8_t  Reserved1					: 1;
	uint8_t  CarrierEventPreviouslySeen	: 1;
	uint8_t  Reserved2					: 1;
	uint8_t  CRCError					: 1;
	uint8_t  LengthCheckError			: 1;
	uint8_t  LengthOutOfRange			: 1;
	uint8_t  ReceivedOk					: 1;
	uint8_t  ReceiveMulticastPacket		: 1;
	uint8_t  ReceiveBroadcastPacket		: 1;
	uint8_t  DribbleNibble				: 1;
	uint8_t  ReceiveControlFrame		: 1;
	uint8_t  ReceivePauseControlFrame	: 1;
	uint8_t  ReceiveUnknownOpcode		: 1;
	uint8_t  ReceiveVLANTypeDetected	: 1;
	uint8_t  RuntFilterMatch			: 1;
	uint8_t  NotMeFilterMatch			: 1;
	uint8_t  HashFilterMatch			: 1;
	uint8_t  MagicPacketFilterMatch		: 1;
	uint8_t  PatternMatchFilterMatch	: 1;
	uint8_t  UnicastFilterMatch			: 1;
	uint8_t  Reserved3					: 2;
	uint8_t  Zero						: 1;
	uint8_t  Zeros;
} receive_status_vector_t;

//***************************************************************************
// Sending - Receiving packets
//***************************************************************************

/*
 * Send packet
 */
void Enc424j600_SendPacket(uint8_t *toSend, uint8_t length) {
	Enc424j600_WriteReg(EGPWRPT, TXSTART);		// General purpose buffer write pointer to start SRAM
	Enc424j600_WriteN(WGPDATA, toSend, length);	// Write data to general purpose buffer
	Enc424j600_WriteReg(ETXLEN, length);		// Length of transmit buffer
	Enc424j600_BitSetReg(ECON1, TXRTS);			// Initiate transmission
	while (Enc424j600_ReadReg(ECON1) & TXRTS);	// Wait until transmission is completed
}

/*
 * Receive packet
 */
uint16_t Enc424j600_ReceivePacket(uint8_t *buffer) {
	uint16_t length = 0;
	receive_status_vector_t receiveStatusVector;

	if (!(Enc424j600_ReadReg(EIR) & PKTIF)) {
		return length;
	}

	// Set address to read
	Enc424j600_WriteReg(ERXRDPT, nextPacketPointer);

	// Read address of next packet
	Enc424j600_ReadN(RRXDATA, (uint8_t*)(&nextPacketPointer), sizeof(nextPacketPointer));

	// Read receive status vector
	Enc424j600_ReadN(RRXDATA, (uint8_t*)(&receiveStatusVector), sizeof(receiveStatusVector));
	length = receiveStatusVector.ReceiveByteCount;

	// Read packet
	Enc424j600_ReadN(RRXDATA, buffer, length);

	// Update end of RX buffer in SRAM
	if (nextPacketPointer == 0x3000) {
		Enc424j600_WriteReg(ERXTAIL, 0x5ffe);
	}
	else {
		Enc424j600_WriteReg(ERXTAIL, (nextPacketPointer - 2));
	}

	// Decrement packet counter
	Enc424j600_BitSetReg(ECON1, PKTDEC);

	return length;
}

//***************************************************************************
// Utility
//***************************************************************************

/*
 * Initalization of ENC424J600
 */
void Enc424j600_Init(void) {
	hal_Enc424j600_Init();
	Enc424j600_SystemReset();

	Enc424j600_WriteReg(ETXST, TXSTART);		// Transmit buffer start address

	Enc424j600_WriteReg(ERXST, RXSTART);		// Receive buffer start address (1/2 of RAM 0x5fff)
	nextPacketPointer = RXSTART;
	Enc424j600_WriteReg(ERXTAIL, RAMSIZE - 2);	// Tail pointer

	Enc424j600_WriteReg(EUDAST, RAMSIZE);
	Enc424j600_WriteReg(EUDAND, RAMSIZE + 1);

	Enc424j600_WriteReg(ERXFCON,			// Receive filters
						BCEN	|			// Broadcast Destination Collection Filter Enable bit
						UCEN	|			// Unicast Destination Collection Filter Enable bit
						RUNTEN	|			// Discard packets that are 63 bytes or smaller
						CRCEN);				// Packets with an invalid CRC will be discarded

	Enc424j600_WriteReg(MAMXFL, 0x05ee);	// Maximum packet size 1518
	Enc424j600_WritePhyReg(PHANA, 0x05e1);	// PHY initialization
	Enc424j600_BitSetReg(ECON1, RXEN);		// Enable packet reception
//	Enc424j600_BitSetReg(EIE, PKTIE);		// Enable RX packet pending interrupt
}

/*
 * System reset
 */
void Enc424j600_SystemReset(void) {
	do {
		do {
			Enc424j600_WriteReg(EUDAST, 0x1234);
		} while	(Enc424j600_ReadReg(EUDAST) != 0x1234);

		Enc424j600_BitClearReg(ESTAT, CLKRDY);
		while (!(Enc424j600_ReadReg(ESTAT) & CLKRDY));

		Enc424j600_BitSetReg(ECON2, ETHRST);	// System reset command
		hal_Enc424j600_Wait25us();
	} while (Enc424j600_ReadReg(EUDAST) != 0x0000);
	hal_Enc424j600_Wait256us();
}

/*
 * Read MAC address to array
 */
void Enc424j600_ReadMacAddress(uint8_t *macAddr) {
	((uint16_t*)(macAddr))[0] = Enc424j600_ReadReg(MAADR1);
	((uint16_t*)(macAddr))[1] = Enc424j600_ReadReg(MAADR2);
	((uint16_t*)(macAddr))[2] = Enc424j600_ReadReg(MAADR3);
}

/*
 * Return status of ethernet link with remote partner
 */
uint8_t Enc424j600_IsLinked(void) {
	if (Enc424j600_ReadReg(ESTAT) & PHYLNK) return 1;
	else return 0;
}

void Enc424j600_MACFlush(void) {
	uint16_t w;

	if (Enc424j600_ReadReg(EIR) & LINKIF) {
		Enc424j600_BitClearReg(EIR, LINKIF);
		// Update MAC duplex settings to match PHY duplex setting
		w = Enc424j600_ReadReg(MACON2);
		if (Enc424j600_ReadReg(ESTAT) & PHYDPX) {              // Switching to full duplex
			Enc424j600_WriteReg(MABBIPG, 0x15);
			w |= FULDPX;
		}
		else {              // Switching to half duplex
			Enc424j600_WriteReg(MABBIPG, 0x12);
			w &= ~FULDPX;
		}
		Enc424j600_WriteReg(MACON2, w);
	}
	if (Enc424j600_ReadReg(ESTAT) & PHYLNK) Enc424j600_BitSetReg(ECON1, TXRTS);
}

//***************************************************************************
// Read - Write registers
//***************************************************************************

/*
 * Read register
 */
uint16_t Enc424j600_ReadReg(uint8_t regAddr) {
	uint16_t returnValue;
	uint8_t bank = (regAddr & 0xe0) >> 5;

	if (bank != 4) {
		switch (bank) {
			case 0:
				Enc424j600_Cmd1B(B0SEL);
				break;
			case 1:
				Enc424j600_Cmd1B(B1SEL);
				break;
			case 2:
				Enc424j600_Cmd1B(B2SEL);
				break;
			case 3:
				Enc424j600_Cmd1B(B3SEL);
				break;
		}
		returnValue = Enc424j600_Cmd3B(RCR | (regAddr & 0x1f), 0xffff);
	}
	else {
		returnValue = (uint16_t)(Enc424j600_Cmd4B(RCRU, (uint32_t)regAddr) >> 8);
	}
	return returnValue;
}

/*
 * Write to register
 */
void Enc424j600_WriteReg(uint8_t regAddr, uint16_t data) {
	uint8_t bank = (regAddr & 0xe0) >> 5;

	if (bank != 4) {
		switch (bank) {
			case 0:
				Enc424j600_Cmd1B(B0SEL);
				break;
			case 1:
				Enc424j600_Cmd1B(B1SEL);
				break;
			case 2:
				Enc424j600_Cmd1B(B2SEL);
				break;
			case 3:
				Enc424j600_Cmd1B(B3SEL);
				break;
		}
		Enc424j600_Cmd3B(WCR | (regAddr & 0x1f), data);
	}
	else {
		Enc424j600_Cmd4B(WCRU, (uint32_t)regAddr | (data << 8));
	}
}

/*
 * Set bits in register
 */
void Enc424j600_BitSetReg(uint8_t regAddr, uint16_t bitMask) {
	uint16_t bank = (regAddr & 0xe0) >> 5;
	switch (bank) {
		case 0:
			Enc424j600_Cmd1B(B0SEL);
			break;
		case 1:
			Enc424j600_Cmd1B(B1SEL);
			break;
		case 2:
			Enc424j600_Cmd1B(B2SEL);
			break;
		case 3:
			Enc424j600_Cmd1B(B3SEL);
			break;
	}
	Enc424j600_Cmd3B(BFS | (regAddr & 0x1f), bitMask);
}

/*
 * Clear bits in register
 */
void Enc424j600_BitClearReg(uint8_t regAddr, uint16_t bitMask) {
	uint8_t bank = (regAddr & 0xe0) >> 5;
	switch (bank) {
		case 0:
			Enc424j600_Cmd1B(B0SEL);
			break;
		case 1:
			Enc424j600_Cmd1B(B1SEL);
			break;
		case 2:
			Enc424j600_Cmd1B(B2SEL);
			break;
		case 3:
			Enc424j600_Cmd1B(B3SEL);
			break;
	}
	Enc424j600_Cmd3B(BFC | (regAddr & 0x1f), bitMask);
}

/*
 * Read PHY register
 */
uint16_t Enc424j600_ReadPhyReg(uint8_t regAddr) {
	uint16_t returnValue = 0;

	Enc424j600_WriteReg(MIREGADR, regAddr | 0x0100);
	Enc424j600_BitSetReg(MICMD, MIIRD);
	while (Enc424j600_ReadReg(MISTAT) & BUSY);
	Enc424j600_BitClearReg(MICMD, MIIRD);
	returnValue = Enc424j600_ReadReg(MIRD);
	return returnValue;
}

/*
 * Write to PHY register
 */
void Enc424j600_WritePhyReg(int8_t regAddr, uint16_t data) {
	Enc424j600_WriteReg(MIREGADR, regAddr | 0x0100);
	Enc424j600_WriteReg(MIWR, data);
	while (Enc424j600_ReadReg(MISTAT) & BUSY);
}


//***************************************************************************
// Executing instructions
//***************************************************************************

// Single byte instruction
void Enc424j600_Cmd1B(uint8_t cmd) {
	hal_ENC424J600_NSS_Low();
	hal_Enc424j600_RW(cmd);
	hal_ENC424J600_NSS_High();
}

// Two-byte instruction
uint8_t Enc424j600_Cmd2B(uint8_t cmd, uint8_t data) {
	uint8_t returnValue = 0;

	hal_ENC424J600_NSS_Low();
	hal_Enc424j600_RW(cmd);
	returnValue = hal_Enc424j600_RW(data);;
	hal_ENC424J600_NSS_High();

	return returnValue;
}

// Three-byte instruction
uint16_t Enc424j600_Cmd3B(uint8_t cmd, unsigned short data) {
	uint16_t returnValue = 0;

	hal_ENC424J600_NSS_Low();
	hal_Enc424j600_RW(cmd);	// send command
	((uint8_t*)(&returnValue))[0] = hal_Enc424j600_RW(((uint8_t*)(&data))[0]);	// send (receive) low byte
	((uint8_t*)(&returnValue))[1] = hal_Enc424j600_RW(((uint8_t*)(&data))[1]);	// send (receive) high byte
	hal_ENC424J600_NSS_High();

	return returnValue;
}

// Four-byte instruction
uint32_t Enc424j600_Cmd4B(uint8_t cmd, uint32_t data) {
	uint32_t returnValue = 0;

	hal_ENC424J600_NSS_Low();
	hal_Enc424j600_RW(cmd);	// send command
	((uint8_t*)(&returnValue))[0] = hal_Enc424j600_RW(((uint8_t*)(&data))[0]);	// send (receive) low byte
	((uint8_t*)(&returnValue))[1] = hal_Enc424j600_RW(((uint8_t*)(&data))[1]);	// send (receive) middle byte
	((uint8_t*)(&returnValue))[2] = hal_Enc424j600_RW(((uint8_t*)(&data))[2]);	// send (receive) high byte
	hal_ENC424J600_NSS_High();

	return returnValue;
}

// Write N-byte
void Enc424j600_WriteN(uint8_t cmd, uint8_t *toSend, uint16_t length) {
	hal_ENC424J600_NSS_Low();
	hal_Enc424j600_RW(cmd);

	while (length--) {
		hal_Enc424j600_RW(*(toSend++));
	}
	hal_ENC424J600_NSS_High();
}

// Read N-Byte
void Enc424j600_ReadN(uint8_t cmd, uint8_t *readBuff, uint16_t length) {
	hal_ENC424J600_NSS_Low();
	hal_Enc424j600_RW(cmd);

	while (length--) {
		*(readBuff++) = hal_Enc424j600_RW(0xff);
	}
	hal_ENC424J600_NSS_High();
}
