/* BlueDataDistributor - data distribution module from ethernet to 32 serial ports
 * version 0.1.1 date 2006-10-07
 * Copyright (C) 2006 Stefan Schuermans <stefan@blinkenarea.org>
 * a BlinkenArea project - http://www.blinkenarea.org/
 */

#include <avr/io.h>
#include <avr/signal.h>

#include "config.h"
#include "ethernet.h"
#include "macros.h"
#include "rtl8019.h"
#include "timing.h"

// maximum receive unit
#define RTL_MRU 1024

// 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_ADDR_DDR DDRC
#define RTL_ADDR PORTC
#define RTL_DATA_DDR DDRA
#define RTL_DATA PORTA
#define RTL_DATA_IN PINA
#define RTL_ADDR_nRD 5
#define RTL_ADDR_nWR 6
#define RTL_ADDR_RST 7
// adress port masks (there are also RD, WR and RESET pins)
#define RTL_ADDR_AND_MASK 0x1F
#define RTL_ADDR_OR_MASK (1<<RTL_ADDR_nRD | 1<<RTL_ADDR_nWR)
#define RTL_ADDR_RESET (1<<RTL_ADDR_nRD | 1<<RTL_ADDR_nWR | 1<<RTL_ADDR_RST)
// special pins
#define RTL_READ_ACT( ) (bit_clear( RTL_ADDR, RTL_ADDR_nRD ))
#define RTL_READ_IDLE( ) (bit_set( RTL_ADDR, RTL_ADDR_nRD ))
#define RTL_WRITE_ACT( ) (bit_clear( RTL_ADDR, RTL_ADDR_nWR ))
#define RTL_WRITE_IDLE( ) (bit_set( RTL_ADDR, RTL_ADDR_nWR ))
#define RTL_RESET_ACT( ) (bit_set( RTL_ADDR, RTL_ADDR_RST ))
#define RTL_RESET_IDLE( ) (bit_clear( RTL_ADDR, RTL_ADDR_RST ))

// 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
#define RTL_WRITE( reg, val ) \
        { \
          RTL_DATA_DDR = 0xFF; /* data port to output */ \
          RTL_ADDR = ((reg) & RTL_ADDR_AND_MASK) | RTL_ADDR_OR_MASK; /* output address */ \
          RTL_DATA = (val); /* output value */ \
          RTL_WRITE_ACT( ); /* activate write signal */ \
          nop( ); \
          nop( ); \
          RTL_WRITE_IDLE( ); /* take back write signal */ \
        }

// read a RTL8019 register
#define RTL_READ( reg, var ) \
        { \
          RTL_DATA_DDR = 0x00; /* data port to input */ \
          RTL_ADDR = ((reg) & RTL_ADDR_AND_MASK) | RTL_ADDR_OR_MASK; /* output address */ \
          RTL_READ_ACT( ); /* activate read signal */ \
          nop( ); \
          nop( ); \
          var = RTL_DATA_IN; /* read data */ \
          RTL_READ_IDLE( ); /* take back read signal */ \
        }

// reinitialization timeout counter
unsigned char RtlReinitTimeout = 0;

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

  // setup ports
  RTL_ADDR = RTL_ADDR_RESET; // default address value with reset set
  RTL_ADDR_DDR = 0xFF; // address port to output
  RTL_DATA_DDR = 0x00; // data port to input

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

  // clear software reset
  RTL_READ( RTL_REG_RSTPORT, i );
  RTL_WRITE( 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
  RTL_WRITE( RTL_REG_CR, 1<<RTL_CR_STP | 1<<RTL_CR_RD2 );
  for( i = 0; i < 100; i++ )
    nop( );

  // output RTL8019 ID
  RTL_READ( RTL_REG_ID0, i );
  RTL_READ( RTL_REG_ID1, j );

  // set up RTL8019
  RTL_WRITE( RTL_REG_DCR, RTL_VAL_DCR );
  RTL_WRITE( RTL_REG_RBCR0, 0x00 );
  RTL_WRITE( RTL_REG_RBCR1, 0x00 );
  RTL_WRITE( RTL_REG_RCR, 0x04 );
  RTL_WRITE( RTL_REG_TPSR, RTL_VAL_RXSTART );
  RTL_WRITE( RTL_REG_TCR, 0x02 );
  RTL_WRITE( RTL_REG_PSTART, RTL_VAL_RXSTART );
  RTL_WRITE( RTL_REG_BNRY, RTL_VAL_RXSTART );
  RTL_WRITE( RTL_REG_PSTOP, RTL_VAL_RXSTOP );
  RTL_WRITE( 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
  RTL_WRITE( RTL_REG_CURR, RTL_VAL_RXSTART );
  for( i = 0; i < 6; i++ )
    RTL_WRITE( RTL_REG_PAR0 + i, ConfigMac[i] );

  // go on with intializing
  RTL_WRITE( RTL_REG_CR, 1<<RTL_CR_STP | 1<<RTL_CR_RD2 );
  RTL_WRITE( RTL_REG_DCR, RTL_VAL_DCR );
  RTL_WRITE( RTL_REG_CR, 1<<RTL_CR_STA | 1<<RTL_CR_RD2 );
  RTL_WRITE( 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 );
  RTL_WRITE( RTL_REG_IMR, RTL_VAL_IMR );
  RTL_WRITE( RTL_REG_TCR, RTL_VAL_TCR );

  // start RTL8019
  RTL_WRITE( 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( 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
  RTL_WRITE( RTL_REG_CR, 1<<RTL_CR_STA | 1<<RTL_CR_RD2 );
  RTL_READ( 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
  RTL_READ( RTL_REG_BNRY, bnry );
  RTL_WRITE( RTL_REG_CR, 1<<RTL_CR_STA | 1<<RTL_CR_RD2 | 1<<RTL_CR_PS0 );
  RTL_READ( RTL_REG_CURR, curr );
  RTL_WRITE( 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
  RTL_WRITE( 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, ...)
  RTL_WRITE( 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( char * pData, unsigned short Length ) // (extern)
{
  unsigned short i;
  unsigned char val;

  // initialize RTL8019 to transmit
  RTL_WRITE( RTL_REG_CR, 1<<RTL_CR_STA | 1<<RTL_CR_RD2 );
  RTL_WRITE( RTL_REG_TPSR, RTL_VAL_TXSTART );
  RTL_WRITE( RTL_REG_RSAR0, 0x00 );
  RTL_WRITE( RTL_REG_RSAR1, RTL_VAL_TXSTART );
  RTL_WRITE( 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
  {
    RTL_WRITE( RTL_REG_RBCR0, 0x3C );
    RTL_WRITE( RTL_REG_RBCR1, 0x00 );
  }
  else
  {
    RTL_WRITE( RTL_REG_RBCR0, (unsigned char)Length );
    RTL_WRITE( RTL_REG_RBCR1, (unsigned char)(Length >> 8) );
  }
  RTL_WRITE( RTL_REG_CR, 1<<RTL_CR_STA | 1<<RTL_CR_RD1 );

  // write data to RTL8019
  for( i = 0; i < Length; i++ )
    RTL_WRITE( RTL_REG_RDMAPORT, pData[i] );
  for( ; i < 0x3C; i++ ) // padding
    RTL_WRITE( RTL_REG_RDMAPORT, 0x00 );

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

  // start transmission
  if( Length < 0x3C ) // minimal length is 60 bytes
  {
    RTL_WRITE( RTL_REG_TBCR0, 0x3C );
    RTL_WRITE( RTL_REG_TBCR1, 0x00 );
  }
  else
  {
    RTL_WRITE( RTL_REG_TBCR0, (unsigned char)Length );
    RTL_WRITE( RTL_REG_TBCR1, (unsigned char)(Length >> 8) );
  }
  RTL_WRITE( 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( char * pData, unsigned short * pLength ) // (extern)
{
  unsigned char tmp1, tmp2;
  unsigned short read_len, len, cnt, i;

  // get size of received packet
  RTL_WRITE( RTL_REG_CR, 1<<RTL_CR_STA | 1<<RTL_CR_RD0 | 1<<RTL_CR_RD1 );
  RTL_READ( RTL_REG_RDMAPORT, tmp1 );
  RTL_READ( RTL_REG_RDMAPORT, tmp1 );
  RTL_READ( RTL_REG_RDMAPORT, tmp1 );
  RTL_READ( 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;

  // read as much data as possible into buffer
  cnt = min( len, *pLength );
  for( i = 0; i < cnt; i++ )
    RTL_READ( RTL_REG_RDMAPORT, pData[i] );
  *pLength = cnt;

  // get rest of data (rest of real data that did not fit into buffer, CRC)
  for( ; i < read_len; i++ )
    RTL_READ( RTL_REG_RDMAPORT, tmp1 );
}

