/* flaneth - flash and ethernet
   Copyright (C) 2007-2012 Stefan Schuermans <stefan@schuermans.info>
   Copyleft: GNU public license V2 - http://www.gnu.org/copyleft/gpl.html
   a BlinkenArea project - http://www.blinkenarea.org/ */

#include <avr/io.h>

#include "bus.h"
#include "config.h"
#include "debug.h"
#include "ethernet.h"
#include "macros.h"
#include "random.h"
#include "rtl8019.h"
#include "timing.h"

// maximum receive unit
#define RTL_MRU (320)

// reinitialization timeout
// - if no reception is detected for this time, the RTL8019 is reinitialized
#define RtlReinitTimeoutTicks (50)      // in 200ms steps

// IO pins of RTL8019
#define RTL_DDR_INT (DDRE)
#define RTL_PORT_INT (PORTE)
#define RTL_BIT_INT (5)
#define RTL_DDR_nAEN (DDRE)
#define RTL_PORT_nAEN (PORTE)
#define RTL_BIT_nAEN (4)
#define RTL_DDR_RST (DDRE)
#define RTL_PORT_RST (PORTE)
#define RTL_BIT_RST (3)
// special pin commands
#define RTL_AEN_ACT( ) (bit_clear( RTL_PORT_nAEN, RTL_BIT_nAEN ))
#define RTL_AEN_IDLE( ) (bit_set( RTL_PORT_nAEN, RTL_BIT_nAEN ))
#define RTL_RESET_ACT( ) (bit_set( RTL_PORT_RST, RTL_BIT_RST ))
#define RTL_RESET_IDLE( ) (bit_clear( RTL_PORT_RST, RTL_BIT_RST ))

// IO pins of RTL8019 EEPROM interface
#define RTL_DDR_EECS (DDRD)
#define RTL_PORT_EECS (PORTD)
#define RTL_PIN_EECS (PIND)
#define RTL_BIT_EECS (7)
#define RTL_DDR_EESK (DDRD)
#define RTL_PORT_EESK (PORTD)
#define RTL_PIN_EESK (PIND)
#define RTL_BIT_EESK (4)
#define RTL_DDR_EEDI (DDRD)
#define RTL_PORT_EEDI (PORTD)
#define RTL_PIN_EEDI (PIND)
#define RTL_BIT_EEDI (5)
#define RTL_DDR_EEDO (DDRD)
#define RTL_PORT_EEDO (PORTD)
#define RTL_BIT_EEDO (6)

// RTL8019 registers
#define RTL_REG_CR (0x00)
#define RTL_REG_PSTART (0x01)
#define RTL_REG_PAR0 (0x01)
#define RTL_REG_PSTOP (0x02)
#define RTL_REG_BNRY (0x03)
#define RTL_REG_TPSR (0x04)
#define RTL_REG_TBCR0 (0x05)
#define RTL_REG_TBCR1 (0x06)
#define RTL_REG_ISR (0x07)
#define RTL_REG_CURR (0x07)
#define RTL_REG_RSAR0 (0x08)
#define RTL_REG_CRDA0 (0x08)
#define RTL_REG_RSAR1 (0x09)
#define RTL_REG_CRDAl (0x09)
#define RTL_REG_RBCR0 (0x0A)
#define RTL_REG_ID0 (0x0A)
#define RTL_REG_ID1 (0x0B)
#define RTL_REG_RBCR1 (0x0B)
#define RTL_REG_RSR (0x0C)
#define RTL_REG_RCR (0x0C)
#define RTL_REG_TCR (0x0D)
#define RTL_REG_CNTR0 (0x0D)
#define RTL_REG_DCR (0x0E)
#define RTL_REG_CNTR1 (0x0E)
#define RTL_REG_IMR (0x0F)
#define RTL_REG_CNTR2 (0x0F)
#define RTL_REG_RDMAPORT (0x10)
#define RTL_REG_RSTPORT (0x18)

// RTL8019AS CR register bits
#define RTL_CR_STP (0)
#define RTL_CR_STA (1)
#define RTL_CR_TXP (2)
#define RTL_CR_RD0 (3)
#define RTL_CR_RD1 (4)
#define RTL_CR_RD2 (5)
#define RTL_CR_PS0 (6)
#define RTL_CR_PS1 (7)

// RTL8019 ISR register bits
#define RTL_ISR_PRX (0)
#define RTL_ISR_PTX (1)
#define RTL_ISR_RXE (2)
#define RTL_ISR_TXE (3)
#define RTL_ISR_OVW (4)
#define RTL_ISR_CNT (5)
#define RTL_ISR_RDC (6)
#define RTL_ISR_RST (7)

// RTL8019 (initial) register values
#define RTL_VAL_RCR (0x04)
#define RTL_VAL_TCR (0x00)
#define RTL_VAL_DCR (0x58)
#define RTL_VAL_IMR (0x00)
#define RTL_VAL_TXSTART (0x40)
#define RTL_VAL_RXSTART (0x46)
#define RTL_VAL_RXSTOP (0x60)

// write a RTL8019 register
extern inline void RtlWriteReg(unsigned char reg, unsigned char val)    // force 
                                                                        // inlining 
                                                                        // by 
                                                                        // using 
                                                                        // "extern"
{
  BUS_DATA = val;       // output value
  BUS_DATA_DDR = 0xFF;  // data port to output
  BUS_ADDR = reg;       // output address
  RTL_AEN_ACT();        // set address enable
  BUS_WRITE_ACT();      // activate write signal
  nop();
  nop();
  BUS_WRITE_IDLE();     // take back write signal
  RTL_AEN_IDLE();       // take back address enable
  BUS_DATA_DDR = 0x00;  // data back port to input
  BUS_DATA = 0x00;      // turn off pullups
}

// write buffer to RTL8019 register
extern inline void RtlWriteRegBuf(unsigned char reg, unsigned char *p_buf, unsigned short len)  // force 
                                                                                                // inlining 
                                                                                                // by 
                                                                                                // using 
                                                                                                // "extern"
{
  BUS_DATA = *p_buf;    // output first value to initialize port status
                        // before switching to output
  BUS_DATA_DDR = 0xFF;  // data port to output
  BUS_ADDR = reg;       // output address
  RTL_AEN_ACT();        // set address enable
  for (; len > 0; p_buf++, len--) {
    BUS_DATA = *p_buf;  // output value
    BUS_WRITE_ACT();    // activate write signal
    nop();
    nop();
    BUS_WRITE_IDLE();   // take back write signal
  }
  RTL_AEN_IDLE();       // take back address enable
  BUS_DATA_DDR = 0x00;  // data back port to input
  BUS_DATA = 0x00;      // turn off pullups
}

// write constant to RTL8019 register multiple times
extern inline void RtlWriteRegConst(unsigned char reg, unsigned char val, unsigned short cnt)   // force 
                                                                                                // inlining 
                                                                                                // by 
                                                                                                // using 
                                                                                                // "extern"
{
  BUS_DATA = val;       // output value
  BUS_DATA_DDR = 0xFF;  // data port to output
  BUS_ADDR = reg;       // output address
  RTL_AEN_ACT();        // set address enable
  for (; cnt > 0; cnt--) {
    BUS_WRITE_ACT();    // activate write signal
    nop();
    nop();
    BUS_WRITE_IDLE();   // take back write signal
  }
  RTL_AEN_IDLE();       // take back address enable
  BUS_DATA_DDR = 0x00;  // data back port to input
  BUS_DATA = 0x00;      // turn off pullups
}

// read a RTL8019 register
extern inline void RtlReadReg(unsigned char reg, unsigned char *p_var)  // force 
                                                                        // inlining 
                                                                        // by 
                                                                        // using 
                                                                        // "extern"
{
  BUS_ADDR = reg;       // output address
  RTL_AEN_ACT();        // set address enable
  BUS_READ_ACT();       // activate read signal
  nop();
  nop();
  *p_var = BUS_DATA_IN; // read data
  BUS_READ_IDLE();      // take back read signal
  RTL_AEN_IDLE();       // take back address enable
}

// read buffer from RTL8019 register
extern inline void RtlReadRegBuf(unsigned char reg, unsigned char *p_buf, unsigned short len)   // force 
                                                                                                // inlining 
                                                                                                // by 
                                                                                                // using 
                                                                                                // "extern"
{
  BUS_ADDR = reg;       // output address
  RTL_AEN_ACT();        // set address enable
  for (; len > 0; p_buf++, len--) {
    BUS_READ_ACT();     // activate read signal
    nop();
    nop();
    *p_buf = BUS_DATA_IN;       // read data
    BUS_READ_IDLE();    // take back read signal
  }
  RTL_AEN_IDLE();       // take back address enable
}

// read a RTL8019 register multiple times and throw away data
extern inline void RtlReadRegMulti(unsigned char reg, unsigned short cnt)       // force 
                                                                                // inlining 
                                                                                // by 
                                                                                // using 
                                                                                // "extern"
{
  BUS_ADDR = reg;       // output address
  RTL_AEN_ACT();        // set address enable
  for (; cnt > 0; cnt--) {
    BUS_READ_ACT();     // activate read signal
    nop();
    nop();
    BUS_READ_IDLE();    // take back read signal
  }
  RTL_AEN_IDLE();       // take back address enable
}

// reinitialization timeout counter
unsigned char RtlReinitTimeout = 0;

// emulate eeprom containing RTL8019AS configuration
static void RtlEmulateEeprom(void)
{
  static const unsigned char timeout = 150;

  unsigned int addr, data, entropy;
  unsigned char t, i;

  while (1) {
    entropy = 0;

    // wait for CS HIGH
    for (t = 0; bit_is_clear(RTL_PIN_EECS, RTL_BIT_EECS); t++)
      if (t >= timeout)
        return;
    entropy += t;

    // ignore one clock
    // wait for clock HIGH
    for (t = 0; bit_is_clear(RTL_PIN_EESK, RTL_BIT_EESK); t++)
      if (t >= timeout)
        return;
    entropy += t;
    // wait for clock LOW
    for (t = 0; bit_is_set(RTL_PIN_EESK, RTL_BIT_EESK); t++)
      if (t >= timeout)
        return;
    entropy += t;

    // shift in address
    addr = 0;
    for (i = 0; i < 9; i++) {
      // wait for clock HIGH
      for (t = 0; bit_is_clear(RTL_PIN_EESK, RTL_BIT_EESK); t++)
        if (t >= timeout)
          return;
      entropy += t;
      // read next bit
      addr <<= 1;
      if (bit_is_set(RTL_PIN_EEDI, RTL_BIT_EEDI))
        addr |= 0x01;
      // wait for clock LOW
      for (t = 0; bit_is_set(RTL_PIN_EESK, RTL_BIT_EESK); t++)
        if (t >= timeout)
          return;
      entropy += t;
    }

    // get data
    switch (addr) {
      // CONFIG 3 = FUDUP,LEDS0; CONFIG 4 = -
    case 0x181:
      data = 0x0050;
      break;
      // output zero by default (as if no EEPROM is connected)
    default:
      data = 0x0000;
      break;
    }

    // shift out data
    for (i = 0; i < 16; i++) {
      // wait for clock HIGH
      for (t = 0; bit_is_clear(RTL_PIN_EESK, RTL_BIT_EESK); t++)
        if (t >= timeout)
          return;
      entropy += t;
      // write next bit
      if (data & 0x8000)
        bit_set(RTL_PORT_EEDO, RTL_BIT_EEDO);
      else
        bit_clear(RTL_PORT_EEDO, RTL_BIT_EEDO);
      data <<= 1;
      // wait for clock LOW
      for (t = 0; bit_is_set(RTL_PIN_EESK, RTL_BIT_EESK); t++)
        if (t >= timeout)
          return;
      entropy += t;
    }

    // wait for CS LOW
    for (t = 0; bit_is_set(RTL_PIN_EECS, RTL_BIT_EECS); t++)
      if (t >= timeout)
        return;
    entropy += t;

    // pass on entropy to randomness generator
    RandomProvideEntropy((unsigned char)entropy);

  }     // while( 1 )
}

// initialize
void RtlInit(void)      // (extern)
{
  unsigned char i;
  unsigned short j;

  // setup ports
  bit_clear(RTL_PORT_INT, RTL_BIT_INT); // pull-up of interrupt pin off
  bit_clear(RTL_DDR_INT, RTL_BIT_INT);  // interrupt pin to input
  bit_set(RTL_PORT_nAEN, RTL_BIT_nAEN); // address enable pin to HIGH
  bit_set(RTL_DDR_nAEN, RTL_BIT_nAEN);  // address enable pin to output
  bit_set(RTL_PORT_RST, RTL_BIT_RST);   // reset pin to HIGH
  bit_set(RTL_DDR_RST, RTL_BIT_RST);    // reset pin to output

  // configuration of EEPROM emulation
  bit_clear(RTL_PORT_EECS, RTL_BIT_EECS);       // EECS := input, no pull-up
  bit_clear(RTL_DDR_EECS, RTL_BIT_EECS);
  bit_clear(RTL_PORT_EESK, RTL_BIT_EESK);       // EESK := input, no pull-up
  bit_clear(RTL_DDR_EESK, RTL_BIT_EESK);
  bit_clear(RTL_PORT_EEDI, RTL_BIT_EEDI);       // EEDI := input, no pull-up
  bit_clear(RTL_DDR_EEDI, RTL_BIT_EEDI);
  bit_clear(RTL_PORT_EEDO, RTL_BIT_EEDO);       // EEDO := output, LOW
  bit_set(RTL_DDR_EEDO, RTL_BIT_EEDO);

  // take back reset
  for (i = 0; i < 10; i++)
    nop();
  RTL_RESET_IDLE();
  RtlEmulateEeprom();
  for (j = 0; j < 5000; j++)
    nop();

  // clear software reset
  RtlReadReg(RTL_REG_RSTPORT, &i);
  RtlWriteReg(RTL_REG_RSTPORT, 0xFF);
  for (i = 0; i < 100; i++)
    nop();

  // do the same as in reinitialization
  RtlReinit();
}

// re-initialize RT8019 (i.e. if MAC changed)
void RtlReinit(void)    // (extern)
{
  unsigned char i, j;

  // stop RTL8019
  RtlWriteReg(RTL_REG_CR, 1 << RTL_CR_STP | 1 << RTL_CR_RD2);
  for (i = 0; i < 100; i++)
    nop();

  // output RTL8019 ID
  RtlReadReg(RTL_REG_ID0, &i);
  RtlReadReg(RTL_REG_ID1, &j);
  debug_rtl8019_printf("RTL8019AS (re)init ID=0x%02X,0x%02X", i, j);

  // set up RTL8019
  RtlWriteReg(RTL_REG_DCR, RTL_VAL_DCR);
  RtlWriteReg(RTL_REG_RBCR0, 0x00);
  RtlWriteReg(RTL_REG_RBCR1, 0x00);
  RtlWriteReg(RTL_REG_RCR, 0x04);
  RtlWriteReg(RTL_REG_TPSR, RTL_VAL_RXSTART);
  RtlWriteReg(RTL_REG_TCR, 0x02);
  RtlWriteReg(RTL_REG_PSTART, RTL_VAL_RXSTART);
  RtlWriteReg(RTL_REG_BNRY, RTL_VAL_RXSTART);
  RtlWriteReg(RTL_REG_PSTOP, RTL_VAL_RXSTOP);
  RtlWriteReg(RTL_REG_CR,
              1 << RTL_CR_STP | 1 << RTL_CR_RD2 | 1 << RTL_CR_PS0);
  for (i = 0; i < 100; i++)
    nop();

  // write MAC to chip
  RtlWriteReg(RTL_REG_CURR, RTL_VAL_RXSTART);
  for (i = 0; i < 6; i++)
    RtlWriteReg(RTL_REG_PAR0 + i, ConfigMac[i]);

  // go on with intializing
  RtlWriteReg(RTL_REG_CR, 1 << RTL_CR_STP | 1 << RTL_CR_RD2);
  RtlWriteReg(RTL_REG_DCR, RTL_VAL_DCR);
  RtlWriteReg(RTL_REG_CR, 1 << RTL_CR_STA | 1 << RTL_CR_RD2);
  RtlWriteReg(RTL_REG_ISR, 1 << RTL_ISR_PRX | 1 << RTL_ISR_PTX |
              1 << RTL_ISR_RXE | 1 << RTL_ISR_TXE |
              1 << RTL_ISR_OVW | 1 << RTL_ISR_CNT |
              1 << RTL_ISR_RDC | 1 << RTL_ISR_RST);
  RtlWriteReg(RTL_REG_IMR, RTL_VAL_IMR);
  RtlWriteReg(RTL_REG_TCR, RTL_VAL_TCR);

  // start RTL8019
  RtlWriteReg(RTL_REG_CR, 1 << RTL_CR_STA | 1 << RTL_CR_RD2);

  // clear reinitialization timeout
  RtlReinitTimeout = 0;
}

// tick procedure - call every 200ms
void RtlTick200(void)   // (extern)
{
  // increment reinitialization timeout counter
  RtlReinitTimeout++;

  // reinitialization timeout
  if (RtlReinitTimeout >= RtlReinitTimeoutTicks)
    // reinitialize RTL8019 (resets reinitialization timeout)
    RtlReinit();
}

// fetch and process a received packet
static inline void RtlRecv(void)
{
  unsigned short PacketLen;
  unsigned char Packet[RTL_MRU];

  // fetch packet from RTL8019
  PacketLen = count(Packet);
  RtlReadFrame((unsigned char *)Packet, &PacketLen);

  // pass packet on to ethernet
  EthernetRecv(Packet, PacketLen);
}

// task function to do the work - call from main loop
void RtlTask(void)      // (extern)
{
  unsigned char isr, bnry, curr;

  // read interrupt register
  RtlWriteReg(RTL_REG_CR, 1 << RTL_CR_STA | 1 << RTL_CR_RD2);
  RtlReadReg(RTL_REG_ISR, &isr);
  // a packet was received
  if (isr & 1 << RTL_ISR_PRX)
    // use current timestamp for generating some entropy
    TimingEntropy();

  // check if receive ring buffer is not empty
  RtlReadReg(RTL_REG_BNRY, &bnry);
  RtlWriteReg(RTL_REG_CR,
              1 << RTL_CR_STA | 1 << RTL_CR_RD2 | 1 << RTL_CR_PS0);
  RtlReadReg(RTL_REG_CURR, &curr);
  RtlWriteReg(RTL_REG_CR, 1 << RTL_CR_STA | 1 << RTL_CR_RD2);
  if (bnry != curr)
    // fetch and process a single received packet
    RtlRecv();

  // reset interrupt bits of RTL8019
  RtlWriteReg(RTL_REG_ISR, 1 << RTL_ISR_PRX | 1 << RTL_ISR_PTX |
              1 << RTL_ISR_RXE | 1 << RTL_ISR_TXE |
              1 << RTL_ISR_OVW | 1 << RTL_ISR_CNT |
              1 << RTL_ISR_RDC | 1 << RTL_ISR_RST);

  // put RTL8019 in default state (default page selected, ...)
  RtlWriteReg(RTL_REG_CR, 1 << RTL_CR_STA | 1 << RTL_CR_RD2);

  // clear reinitialization timeout
  RtlReinitTimeout = 0;
}

// write an ethernet frame to the RTL8019
void RtlWriteFrame(unsigned char *pData, unsigned short Length) // (extern)
{
  unsigned char val;

  debug_rtl8019_printf("send len=%u", Length);

  // initialize RTL8019 to transmit
  RtlWriteReg(RTL_REG_CR, 1 << RTL_CR_STA | 1 << RTL_CR_RD2);
  RtlWriteReg(RTL_REG_TPSR, RTL_VAL_TXSTART);
  RtlWriteReg(RTL_REG_RSAR0, 0x00);
  RtlWriteReg(RTL_REG_RSAR1, RTL_VAL_TXSTART);
  RtlWriteReg(RTL_REG_ISR, 1 << RTL_ISR_PRX | 1 << RTL_ISR_PTX |
              1 << RTL_ISR_RXE | 1 << RTL_ISR_TXE |
              1 << RTL_ISR_OVW | 1 << RTL_ISR_CNT |
              1 << RTL_ISR_RDC | 1 << RTL_ISR_RST);
  if (Length < 0x3C)    // minimal length is 60 bytes
  {
    RtlWriteReg(RTL_REG_RBCR0, 0x3C);
    RtlWriteReg(RTL_REG_RBCR1, 0x00);
  } else {
    RtlWriteReg(RTL_REG_RBCR0, (unsigned char)Length);
    RtlWriteReg(RTL_REG_RBCR1, (unsigned char)(Length >> 8));
  }
  RtlWriteReg(RTL_REG_CR, 1 << RTL_CR_STA | 1 << RTL_CR_RD1);

  // write data to RTL8019
  RtlWriteRegBuf(RTL_REG_RDMAPORT, pData, Length);
  if (Length < 0x3C)    // padding
    RtlWriteRegConst(RTL_REG_RDMAPORT, 0x00, 0x3C - Length);

  // wait for RTL8019
  do {
    RtlReadReg(RTL_REG_ISR, &val);
  }
  while ((val & 1 << RTL_ISR_RDC) == 0x00);

  // start transmission
  if (Length < 0x3C)    // minimal length is 60 bytes
  {
    RtlWriteReg(RTL_REG_TBCR0, 0x3C);
    RtlWriteReg(RTL_REG_TBCR1, 0x00);
  } else {
    RtlWriteReg(RTL_REG_TBCR0, (unsigned char)Length);
    RtlWriteReg(RTL_REG_TBCR1, (unsigned char)(Length >> 8));
  }
  RtlWriteReg(RTL_REG_CR, 1 << RTL_CR_TXP | 1 << RTL_CR_RD2);
}

// read an ethernet frame from the RTL8019
// *pLength must be initialized to the buffer size
void RtlReadFrame(unsigned char *pData, unsigned short *pLength)        // (extern)
{
  unsigned char tmp1, tmp2;
  unsigned short read_len, len, cnt;

  // get size of received packet
  RtlWriteReg(RTL_REG_CR,
              1 << RTL_CR_STA | 1 << RTL_CR_RD0 | 1 << RTL_CR_RD1);
  RtlReadReg(RTL_REG_RDMAPORT, &tmp1);
  RtlReadReg(RTL_REG_RDMAPORT, &tmp1);
  RtlReadReg(RTL_REG_RDMAPORT, &tmp1);
  RtlReadReg(RTL_REG_RDMAPORT, &tmp2);
  read_len = ((unsigned short)tmp2 << 8 | (unsigned short)tmp1);

  // subtract length of CRC (4 bytes)
  len = read_len < 4 ? 0 : read_len - 4;

  debug_rtl8019_printf("recv len=%u", len);

  // read as much data as possible into buffer
  cnt = min(len, *pLength);
  RtlReadRegBuf(RTL_REG_RDMAPORT, pData, cnt);
  *pLength = cnt;

  // get rest of data (rest of real data that did not fit into buffer, CRC)
  if (read_len > cnt)
    RtlReadRegMulti(RTL_REG_RDMAPORT, read_len - cnt);
}
