/* ===================================================
   Knihovna slouzi pro komunikaci a nastaveni 
   ethernetoveho radice ENC28J60 vcetne odesilani
   a prijimani paketu.
   ---------------------------------------------------
   Zdrojovy kod byl prevzat a doplnen/editovan
   z prikladu pro vyvojovou desku s mikrokontrolerem
   STM32 V3.0 od tuxgraphics
   (http://tuxgraphics.org/electronics/200611/embedded-webserver.shtml)
   ================================================ */
#include "enc28j60.h"
#include "PIN_config.h"
#include <stdio.h>
#include "stm32f10x_gpio.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_spi.h"

static unsigned char Enc28j60Bank;
static unsigned int NextPacketPtr;
static uint16_t TxBufferLen[2]={0,0};	// Pomocna promenna kde se uchova delka naposledy odeslanych dat pro kazdy Tx buffer zvlast

/* -----------------------------------------------
   Konfigurace SPI1 pro potreby komunikace s
   radicem ENC28J60 
   -------------------------------------------- */
void enc28j60_SPI_Init(void)
{
	SPI_InitTypeDef  SPI_InitStructure;
	GPIO_InitTypeDef GPIO_InitStructure;
	
	/* Enable SPI1 and GPIOA clocks */
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
	
	/* Configure SPI1 pins: SCK, MISO and MOSI */
	GPIO_InitStructure.GPIO_Pin = ETH_CLK_Pin | ETH_SO_Pin | ETH_SI_Pin;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;						// alternativni funkce pinu
	GPIO_Init(ETH_SPI_Port, &GPIO_InitStructure);
	
	/* Configure SPI1 pins: NSS */
    GPIO_InitStructure.GPIO_Pin = ETH_CS_Pin;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;					// pin jako vystup
    GPIO_Init(ETH_SPI_Port, &GPIO_InitStructure);

	GPIO_SetBits(ETH_SPI_Port, ETH_CS_Pin);   							// Nastavi CS do "1" -> povoleni SPI rozhrani na radici ENC28J60

	/* SPI1 configuration */ 
	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
	SPI_InitStructure.SPI_Mode = SPI_Mode_Master;						// Rezim MASTER
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;					// 8 datovych bitu
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;							// CS ovladan programove
	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8;	// Delic prenosove rychlosti nastaven na 72/8 = 9MHz
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;					// Prvni se odesila MSB
	SPI_InitStructure.SPI_CRCPolynomial = 7;
	SPI_Init(SPI1, &SPI_InitStructure);
	
	/* Enable SPI1  */
	SPI_Cmd(SPI1, ENABLE);	
}

/* -----------------------------------------------
   Funkce pro zapis a cteni z/do SPI rozhrani
   -------------------------------------------- */
unsigned char SPI1_ReadWrite(unsigned char writedat)
{
	while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE) == RESET);	// Loop while DR register in not emplty		
	SPI_I2S_SendData(SPI1, writedat);								// Send byte through the SPI1 peripheral	
	while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);	// Wait to receive a byte	
	return SPI_I2S_ReceiveData(SPI1);								// Return the byte read from the SPI bus
}

/* -----------------------------------------------
   Cteni OP z radice
   -------------------------------------------- */
unsigned char enc28j60ReadOp(unsigned char op, unsigned char address)
{
	unsigned char dat = 0;
	
	ENC28J60_CSL();
	
	dat = op | (address & ADDR_MASK);
	SPI1_ReadWrite(dat);
	dat = SPI1_ReadWrite(0xFF);
	// do dummy read if needed (for mac and mii, see datasheet page 29)
	if(address & 0x80)
    {
		dat = SPI1_ReadWrite(0xFF);
    }
	// release CS
	ENC28J60_CSH();
	return dat;
}

/* -----------------------------------------------
   Zapis OP do radice
   -------------------------------------------- */
void enc28j60WriteOp(unsigned char op, unsigned char address, unsigned char data)
{
	unsigned char dat = 0;
	  
	ENC28J60_CSL();
	// issue write command
	dat = op | (address & ADDR_MASK);
	SPI1_ReadWrite(dat);
	// write data
	dat = data;
	SPI1_ReadWrite(dat);
	ENC28J60_CSH();
}

/* -----------------------------------------------
   Cteni z bufferu radice
   -------------------------------------------- */
void enc28j60ReadBuffer(unsigned int len, unsigned char* data)
{
	ENC28J60_CSL();
	// issue read command
	SPI1_ReadWrite(ENC28J60_READ_BUF_MEM);
	while(len)
	{
        len--;
        // read data
        *data = (unsigned char)SPI1_ReadWrite(0);
        data++;
	}
	*data='\0';
	ENC28J60_CSH();
}

/* -----------------------------------------------
   Zapis do bufferu radice
   -------------------------------------------- */
void enc28j60WriteBuffer(unsigned int len, unsigned char* data)
{
	ENC28J60_CSL();
	// issue write command
	SPI1_ReadWrite(ENC28J60_WRITE_BUF_MEM);
	
	while(len)
	{
		len--;
		SPI1_ReadWrite(*data);
		data++;
	}
	ENC28J60_CSH();
}

/* -----------------------------------------------
   Nastaveni banky
   -------------------------------------------- */
void enc28j60SetBank(unsigned char address)
{
	// set the bank (if needed)
	if((address & BANK_MASK) != Enc28j60Bank)
	{
        // set the bank
        enc28j60WriteOp(ENC28J60_BIT_FIELD_CLR, ECON1, (ECON1_BSEL1|ECON1_BSEL0));
        enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, ECON1, (address & BANK_MASK)>>5);
        Enc28j60Bank = (address & BANK_MASK);
	}
}

/* -----------------------------------------------
   Cteni
   -------------------------------------------- */
unsigned char enc28j60Read(unsigned char address)
{
	// set the bank
	enc28j60SetBank(address);
	// do the read
	return enc28j60ReadOp(ENC28J60_READ_CTRL_REG, address);
}

/* -----------------------------------------------
   Zapis
   -------------------------------------------- */
void enc28j60Write(unsigned char address, unsigned char data)
{
	// set the bank
	enc28j60SetBank(address);
	// do the write
	enc28j60WriteOp(ENC28J60_WRITE_CTRL_REG, address, data);
}

/* -----------------------------------------------
   Zapis do PHY rozhrani
   -------------------------------------------- */
void enc28j60PhyWrite(unsigned char address, unsigned int data)
{
	// set the PHY register address
	enc28j60Write(MIREGADR, address);
	// write the PHY data
	enc28j60Write(MIWRL, data);
	enc28j60Write(MIWRH, data>>8);
	// wait until the PHY write completes
	while(enc28j60Read(MISTAT) & MISTAT_BUSY)
	{
	}
}

/* -----------------------------------------------
   Nastaveni vystupniho kmitoctu z pinu CLKOUT
   -------------------------------------------- */
void enc28j60clkout(unsigned char clk)
{
    //setup clkout: 2 is 12.5MHz:
	enc28j60Write(ECOCON, clk & 0x7);
}

/* -----------------------------------------------
   Inicializace obvodu
   - mezi jednotlivymi pakety v bufferu musi byt
     alespon 7 bytu volneho mista (pro interni 
	 ucely radice)
   -------------------------------------------- */
void enc28j60Init(unsigned char* macaddr)
{  
	// initialize I/O
	enc28j60_SPI_Init();
	ENC28J60_CSH();	      
	// perform system reset
	enc28j60WriteOp(ENC28J60_SOFT_RESET, 0, ENC28J60_SOFT_RESET);
	// check CLKRDY bit to see if reset is complete
        // The CLKRDY does not work. See Rev. B4 Silicon Errata point. Just wait.
	//while(!(enc28j60Read(ESTAT) & ESTAT_CLKRDY));
	// do bank 0 stuff
	// initialize receive buffer
	// 16-bit transfers, must write low byte first
	// set receive buffer start address
	NextPacketPtr = RXSTART_INIT;
    // Rx start
	enc28j60Write(ERXSTL, RXSTART_INIT&0xFF);	 //
	enc28j60Write(ERXSTH, RXSTART_INIT>>8);
	// set receive pointer address
	enc28j60Write(ERXRDPTL, RXSTART_INIT&0xFF);
	enc28j60Write(ERXRDPTH, RXSTART_INIT>>8);
	// RX end
	enc28j60Write(ERXNDL, RXSTOP_INIT&0xFF);
	enc28j60Write(ERXNDH, RXSTOP_INIT>>8);
	// TX 1 start	  1500
	enc28j60Write(ETXSTL, TXSTART1_INIT&0xFF);
	enc28j60Write(ETXSTH, TXSTART1_INIT>>8);	
	// TX 2 end
	enc28j60Write(ETXNDL, TXSTOP1_INIT&0xFF);
	enc28j60Write(ETXNDH, TXSTOP1_INIT>>8);
	// do bank 1 stuff, packet filter:
        // For broadcast packets we allow only ARP packtets
        // All other packets should be unicast only for our mac (MAADR)
        //
        // The pattern to match on is therefore
        // Type     ETH.DST
        // ARP      BROADCAST
        // 06 08 -- ff ff ff ff ff ff -> ip checksum for theses bytes=f7f9
        // in binary these poitions are:11 0000 0011 1111
        // This is hex 303F->EPMM0=0x3f,EPMM1=0x30

	enc28j60Write(ERXFCON, ERXFCON_UCEN|ERXFCON_CRCEN|ERXFCON_PMEN);
	enc28j60Write(EPMM0, 0x3f);
	enc28j60Write(EPMM1, 0x30);
	enc28j60Write(EPMCSL, 0xf9);
	enc28j60Write(EPMCSH, 0xf7);

	// do bank 2 stuff
	// enable MAC receive
	enc28j60Write(MACON1, MACON1_MARXEN|MACON1_TXPAUS|MACON1_RXPAUS);
	// bring MAC out of reset
	enc28j60Write(MACON2, 0x00);
	// enable automatic padding to 60bytes and CRC operations
	enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, MACON3, MACON3_PADCFG0|MACON3_TXCRCEN|MACON3_FRMLNEN|MACON3_FULDPX);
	// set inter-frame gap (non-back-to-back)
	enc28j60Write(MAIPGL, 0x12);
	enc28j60Write(MAIPGH, 0x0C);
	// set inter-frame gap (back-to-back)
	enc28j60Write(MABBIPG, 0x15);
	// Set the maximum packet size which the controller will accept
    // Do not send packets longer than MAX_FRAMELEN:
	enc28j60Write(MAMXFLL, MAX_FRAMELEN&0xFF);	
	enc28j60Write(MAMXFLH, MAX_FRAMELEN>>8);
	// do bank 3 stuff
	// write MAC address
	// NOTE: MAC address in ENC28J60 is byte-backward
	enc28j60Write(MAADR5, macaddr[0]);	
	enc28j60Write(MAADR4, macaddr[1]);
	enc28j60Write(MAADR3, macaddr[2]);
	enc28j60Write(MAADR2, macaddr[3]);
	enc28j60Write(MAADR1, macaddr[4]);
	enc28j60Write(MAADR0, macaddr[5]);

	enc28j60PhyWrite(PHCON1, PHCON1_PDPXMD);

	//PHLCON LEDA=links status, LEDB=receive/transmit
	enc28j60PhyWrite(PHLCON,0x0476);

	// no loopback of transmitted frames
	enc28j60PhyWrite(PHCON2, PHCON2_HDLDIS);
	// switch to bank 0
	
	enc28j60SetBank(ECON1);
	// enable interrutps
	enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, EIE, EIE_INTIE|EIE_PKTIE);
	// enable packet reception
	enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_RXEN);
}

/* -----------------------------------------------
   Precteni revize cipu
   -------------------------------------------- */
unsigned char enc28j60getrev(void)
{
	return(enc28j60Read(EREVID));
}
/* -----------------------------------------------
   Nastaveni ukazatelu na zacatek a konec
   TX bufferu a odeslani paketu do site
   -------------------------------------------- */
void enc28j60PacketSend(unsigned int len, unsigned char* packet, uint8_t BufferNumber)
{
	
	if(BufferNumber==0)
	{
		// TX 1 start	  1500
		enc28j60Write(ETXSTL, TXSTART1_INIT&0xFF);
		enc28j60Write(ETXSTH, TXSTART1_INIT>>8);	
		// TX 2 end
		enc28j60Write(ETXNDL, TXSTOP1_INIT&0xFF);
		enc28j60Write(ETXNDH, TXSTOP1_INIT>>8);
		// Set the write pointer to start of transmit buffer area -> Nastaveni zacatku bufferu pro odeslani
		enc28j60Write(EWRPTL, TXSTART1_INIT&0xFF);
		enc28j60Write(EWRPTH, TXSTART1_INIT>>8);
		TxBufferLen[0] = len;
	}
	else
	{
		// TX 1 start	  1500
		enc28j60Write(ETXSTL, TXSTART2_INIT&0xFF);
		enc28j60Write(ETXSTH, TXSTART2_INIT>>8);	
		// TX 2 end
		enc28j60Write(ETXNDL, TXSTOP2_INIT&0xFF);
		enc28j60Write(ETXNDH, TXSTOP2_INIT>>8);
		// Set the write pointer to start of transmit buffer area -> Nastaveni zacatku bufferu pro odeslani
		enc28j60Write(EWRPTL, TXSTART2_INIT&0xFF);
		enc28j60Write(EWRPTH, TXSTART2_INIT>>8);
		TxBufferLen[1] = len;
	}

	// Set the TXND pointer to correspond to the packet size given -> Nastaveni konce bufferu pro odeslani
	if(BufferNumber==0)
	{
		enc28j60Write(ETXNDL, (TXSTART1_INIT+len)&0xFF);
		enc28j60Write(ETXNDH, (TXSTART1_INIT+len)>>8);
	}
	else
	{
		enc28j60Write(ETXNDL, (TXSTART2_INIT+len)&0xFF);
		enc28j60Write(ETXNDH, (TXSTART2_INIT+len)>>8);
	}

	// write per-packet control byte (0x00 means use macon3 settings)
	enc28j60WriteOp(ENC28J60_WRITE_BUF_MEM, 0, 0x00);

	// copy the packet into the transmit buffer
	enc28j60WriteBuffer(len, packet);

	// send the contents of the transmit buffer onto the network
	enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_TXRTS);

    // Reset the transmit logic problem. See Rev. B4 Silicon Errata point 12.
	if( (enc28j60Read(EIR) & EIR_TXERIF) )
	{
        enc28j60WriteOp(ENC28J60_BIT_FIELD_CLR, ECON1, ECON1_TXRTS);
    }
}

/* --------------------------------------------------
 Odeslani paketu umisteneho v bufferu radice jeste
 jednou
 ------------------------------------------------- */
void enc28j60SendPacketAgain(uint8_t BufferID)
{
	// Set the write pointer to start of transmit buffer area -> Nastaveni zacatku bufferu pro odeslani
	if(BufferID==0)
	{
		// TX 1 start	  1500
		enc28j60Write(ETXSTL, TXSTART1_INIT&0xFF);
		enc28j60Write(ETXSTH, TXSTART1_INIT>>8);	
		// TX 2 end
		enc28j60Write(ETXNDL, TXSTOP1_INIT&0xFF);
		enc28j60Write(ETXNDH, TXSTOP1_INIT>>8);

		enc28j60Write(EWRPTL, TXSTART1_INIT&0xFF);
		enc28j60Write(EWRPTH, TXSTART1_INIT>>8);
	}
	else
	{
		// TX 1 start	  1500
		enc28j60Write(ETXSTL, TXSTART2_INIT&0xFF);
		enc28j60Write(ETXSTH, TXSTART2_INIT>>8);	
		// TX 2 end
		enc28j60Write(ETXNDL, TXSTOP2_INIT&0xFF);
		enc28j60Write(ETXNDH, TXSTOP2_INIT>>8);

		enc28j60Write(EWRPTL, TXSTART2_INIT&0xFF);
		enc28j60Write(EWRPTH, TXSTART2_INIT>>8);
	}

	// Set the TXND pointer to correspond to the packet size given -> Nastaveni konce bufferu pro odeslani
	if(BufferID==0)
	{
		enc28j60Write(ETXNDL, (TXSTART1_INIT+TxBufferLen[0])&0xFF);
		enc28j60Write(ETXNDH, (TXSTART1_INIT+TxBufferLen[0])>>8);
	}
	else
	{
		enc28j60Write(ETXNDL, (TXSTART2_INIT+TxBufferLen[1])&0xFF);
		enc28j60Write(ETXNDH, (TXSTART2_INIT+TxBufferLen[1])>>8);
	}

	// write per-packet control byte (0x00 means use macon3 settings)
	enc28j60WriteOp(ENC28J60_WRITE_BUF_MEM, 0, 0x00);

	// send the contents of the transmit buffer onto the network
	enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_TXRTS);

    // Reset the transmit logic problem. See Rev. B4 Silicon Errata point 12.
	if( (enc28j60Read(EIR) & EIR_TXERIF) )
	{
        enc28j60WriteOp(ENC28J60_BIT_FIELD_CLR, ECON1, ECON1_TXRTS);
    }	
}

/* -----------------------------------------------
   Precteni paketu z sitoveho prijimaciho bufferu
   (pokud je dostupny)
   -------------------------------------------- */
	// The packet will by headed by an ethernet header.
	//      maxlen  The maximum acceptable length of a retrieved packet.
	//      packet  Pointer where packet data should be stored.
	// Returns: Packet length in bytes if a packet was retrieved, zero otherwise.
unsigned int enc28j60PacketReceive(unsigned int maxlen, unsigned char* packet)
{
	unsigned int rxstat;
	unsigned int len;

	// check if a packet has been received and buffered
	//if( !(enc28j60Read(EIR) & EIR_PKTIF) ){
        // The above does not work. See Rev. B4 Silicon Errata point 6.
	if( enc28j60Read(EPKTCNT) ==0 )
	{
		return(0);
    }

	// Set the read pointer to the start of the received packet
	enc28j60Write(ERDPTL, (NextPacketPtr));
	enc28j60Write(ERDPTH, (NextPacketPtr)>>8);

	// read the next packet pointer
	NextPacketPtr  = enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0);
	NextPacketPtr |= enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0)<<8;

	// read the packet length (see datasheet page 43)
	len  = enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0);
	len |= enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0)<<8;

    len-=4; //remove the CRC count
	// read the receive status (see datasheet page 43)
	rxstat  = enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0);
	rxstat |= enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0)<<8;
	// limit retrieve length
    if (len>maxlen-1)
	{
        len=maxlen-1;
    }

    // check CRC and symbol errors (see datasheet page 44, table 7-3):
    // The ERXFCON.CRCEN is set by default. Normally we should not
    // need to check this.
    if ((rxstat & 0x80)==0)
	{
	    // invalid
	    len=0;
	}
	else
	{
        // copy the packet from the receive buffer
    	enc28j60ReadBuffer(len, packet);
    }
	// Move the RX read pointer to the start of the next received packet
	// This frees the memory we just read out
	enc28j60Write(ERXRDPTL, (NextPacketPtr));
	enc28j60Write(ERXRDPTH, (NextPacketPtr)>>8);

	// decrement the packet counter indicate we are done with this packet
	enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, ECON2, ECON2_PKTDEC);
	return(len);
}

/* Konec souboru */
