/*
* Copyright (C) 2006 Pascal Brisset, Antoine Drouin
*
* This file is part of paparazzi.
*
* paparazzi is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* paparazzi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with paparazzi; see the file COPYING. If not, write to
* the Free Software Foundation, 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*
*/

#include "mcu_periph/sys_time.h"
#include "subsystems/datalink/w5100.h"
#include "mcu_periph/spi.h"

#define TXBUF_BASE 0x4000
#define RXBUF_BASE 0x6000
#define SOCKETS 4

#define CMD_SOCKET 1
#define TELEM_SOCKET 0

#define SOCK_OPEN 0x01
#define SOCK_CLOSE 0x10
#define SOCK_SEND 0x20
#define SOCK_RECV 0x40
#define SNMR_UDP 0x02
#define SNMR_MULTI 0x80
#define SNIR_SEND_OK 0x10
#define SNIR_TIMEOUT 0x08
#define CH_BASE 0x0400
#define CH_SIZE 0x0100
#define SMASK 0x07FF // Tx buffer MASK
#define RMASK 0x07FF // Tx buffer MASK

#define REG_MR 0x0000
#define REG_RX_MEM 0x001A
#define REG_TX_MEM 0x001B

#define REG_GAR 0x0001
#define REG_SUBR 0x0005
#define REG_SHAR 0x0009
#define REG_SIPR 0x000F

#define SOCK_MR 0x0000
#define SOCK_CR 0x0001
#define SOCK_IR 0x0002
#define SOCK_PORT 0x0004
#define SOCK_DHAR 0x0006
#define SOCK_DIPR 0x000C
#define SOCK_DPORT 0x0010
#define SOCK_TX_WR 0x0024
#define SOCK_RSR 0x0026
#define SOCK_RXRD 0x0028

#ifndef W5100_SPI_DEV
#define W5100_SPI_DEV spi1
#endif

#ifndef W5100_SLAVE_IDX
#define W5100_SLAVE_IDX SPI_SLAVE1
#endif

struct w5100_periph chip0;
uint8_t ck_a, ck_b;
uint8_t rx_buf[W5100_RX_BUFFER_SIZE];

// the media access control (ethernet hardware) address for the shield.
static uint8_t mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };

//the IP address for the shield:
static uint8_t ip[] = { W5100_IP };
static uint8_t dest[] = { W5100_MULTICAST_IP };
static uint8_t subnet[] = { W5100_SUBNET };
static uint16_t dport = W5100_MULTICAST_PORT;

static const uint8_t RST = 7; // Reset BIT

uint16_t SBASE[SOCKETS]; // Tx buffer base address
uint16_t RBASE[SOCKETS]; // Rx buffer base address
static const uint16_t SSIZE = 2048; // Max Tx buffer size
static const uint16_t RSIZE = 2048; // Max Rx buffer size

struct w5100_transport w5100_tp;
struct spi_transaction w5100_spi;

static void w5100_close_socket( uint8_t _s );
static void configure_socket( uint8_t _s, uint8_t _flag, uint16_t _lport, uint16_t _dport, uint8_t *_dest );
static void w5100_read_data( uint8_t s, volatile uint8_t *src, volatile uint8_t *dst, uint16_t len );
static uint16_t w5100_read(uint16_t _addr, uint8_t *_buf, uint16_t _len);

static inline void w5100_set(uint16_t _reg, uint8_t _val)
{
  w5100_spi.output_buf[0] = 0xF0;
  w5100_spi.output_buf[1] = _reg >> 8;
  w5100_spi.output_buf[2] = _reg & 0xFF;
  w5100_spi.output_buf[3] = _val;

  spi_submit( &(W5100_SPI_DEV), &w5100_spi );

  // FIXME: no busy waiting! if really needed add a timeout!!!!
  while(w5100_spi.status != SPITransSuccess);
}

static inline uint8_t w5100_get(uint16_t _reg)
{
  w5100_spi.output_buf[0] = 0x0F;
  w5100_spi.output_buf[1] = _reg >> 8;
  w5100_spi.output_buf[2] = _reg & 0xFF;

  spi_submit( &(W5100_SPI_DEV), &w5100_spi );

  // FIXME: no busy waiting! if really needed add a timeout!!!!
  while(w5100_spi.status != SPITransSuccess);

  return w5100_spi.input_buf[3];
}

static inline void w5100_set_buffer(uint16_t _reg, volatile uint8_t *_buf, uint16_t _len)
{
  for (int i=0; i<_len; i++) {
    w5100_set( _reg, _buf[ i ] );
    _reg++;
  }
}

static inline void w5100_sock_set(uint8_t _sock, uint16_t _reg, uint8_t _val) {
  w5100_set( CH_BASE + _sock * CH_SIZE + _reg, _val );
}

static inline uint8_t w5100_sock_get(uint8_t _sock, uint16_t _reg) {
  return w5100_get( CH_BASE + _sock * CH_SIZE + _reg );
}

static inline uint16_t w5100_sock_get16( uint8_t _sock, uint16_t _reg) {
    uint16_t res = w5100_sock_get(_sock, _reg);
    uint16_t res2 = w5100_sock_get(_sock, _reg + 1);
    res = res << 8;
    res2 = res2 & 0xFF;
    res = res | res2;
    return res;
}

void w5100_init( void ) {

  // configure the SPI bus.
  w5100_spi.slave_idx = W5100_SLAVE_IDX;
  w5100_spi.output_length = 4;
  w5100_spi.input_length = 4;
  w5100_spi.select = SPISelectUnselect;
  w5100_spi.cpol = SPICpolIdleLow;
  w5100_spi.cpha = SPICphaEdge1;
  w5100_spi.dss = SPIDss8bit;
  w5100_spi.bitorder = SPIMSBFirst;
  w5100_spi.cdiv = SPIDiv64;

  chip0.status = W5100StatusUninit;
  chip0.curbuf = 0;
  w5100_spi.input_buf = &chip0.work_rx[0];
  w5100_spi.output_buf = &chip0.work_tx[0];

  // wait one second for proper initialization (chip getting powered up).
  sys_time_usleep(1000000);

  // @FIXME: should be no arch dependant code here...
  // set DRDY pin
  gpio_set_mode( GPIOB, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO1 );
  gpio_clear( GPIOB, GPIO1 );
  sys_time_usleep(200);
  gpio_set( GPIOB, GPIO1 );

  // allow some time for the chip to wake up.
  sys_time_usleep(20000);

  // write reset bit into mode register
  w5100_set( REG_MR, 1<<RST );

  // allow some time to wake up...
  sys_time_usleep(20000);

  // receive memory size
  w5100_set( REG_RX_MEM, 0x55 );

  // transmit memory size
  w5100_set( REG_TX_MEM, 0x55 );

  // Setting the required socket base addresses for reads and writes to/from sockets
  for (int i=0; i<SOCKETS; i++) {
    SBASE[i] = TXBUF_BASE + SSIZE * i;
    RBASE[i] = RXBUF_BASE + RSIZE * i;
  }

  uint8_t gateway[4];
  gateway[0] = ip[0];
  gateway[1] = ip[1];
  gateway[2] = ip[2];
  gateway[3] = 1;

  // configure gateway, subnet, mac and ip on "NIC".
  w5100_set_buffer( REG_GAR, gateway, 4 );
  w5100_set_buffer( REG_SUBR, subnet, 4 );
  w5100_set_buffer( REG_SHAR, mac, 6 );
  w5100_set_buffer( REG_SIPR, ip, 4 );

  // create a socket to send telemetry through.
  configure_socket( TELEM_SOCKET, SNMR_MULTI, 1, dport, dest );

  // make dest zero and configure socket to receive data
  dest[ 0 ] = 0x00;
  configure_socket( CMD_SOCKET, 0, dport, dport, dest );
}

void w5100_transmit( uint8_t data ) {

  uint16_t temp = (chip0.tx_insert_idx[ chip0.curbuf ] + 1) % W5100_TX_BUFFER_SIZE;

  if (temp == chip0.tx_extract_idx[ chip0.curbuf ]) {
    // no more room in this transaction.
    return;
  }

  // check if in process of sending data
  chip0.tx_buf[ chip0.curbuf ][ chip0.tx_insert_idx[ chip0.curbuf ] ] = data;
  chip0.tx_insert_idx[ chip0.curbuf ] = temp;
}

void w5100_send() {
  // Now send off spi transaction.
  uint16_t len = chip0.tx_insert_idx[ chip0.curbuf ];
  uint8_t curbuf = chip0.curbuf;

  // switch to other buffer to accept more chars.
  chip0.curbuf++;
  if ( chip0.curbuf >= W5100_BUFFER_NUM ) {
    chip0.curbuf = 0;
  }

  uint16_t ptr = w5100_sock_get16( TELEM_SOCKET, SOCK_TX_WR );
  uint16_t offset = ptr & SMASK;
  uint16_t dstAddr = offset + SBASE[ TELEM_SOCKET ];

  chip0.tx_insert_idx[ chip0.curbuf ] = 0;
  chip0.tx_extract_idx[ chip0.curbuf ] = 0;

  if (offset + len > SSIZE) {
    // Wrap around circular buffer
    uint16_t size = SSIZE - offset;
    w5100_set_buffer( dstAddr, &chip0.tx_buf[curbuf][0], size );
    w5100_set_buffer( SBASE[ TELEM_SOCKET ], &chip0.tx_buf[curbuf][0] + size, len - size);
  }
  else {
    w5100_set_buffer( dstAddr, &chip0.tx_buf[curbuf][0], len);
  }

  // Reset write pointer
  ptr += len;
  w5100_sock_set( TELEM_SOCKET, SOCK_TX_WR, ptr >> 8 );
  w5100_sock_set( TELEM_SOCKET, SOCK_TX_WR+1, ptr & 0xFF );

  // send
  w5100_sock_set( TELEM_SOCKET, SOCK_CR, SOCK_SEND );

  uint8_t complete = w5100_sock_get( TELEM_SOCKET, SOCK_CR);
  while ( complete != 0x00 ) {
    complete = w5100_sock_get( TELEM_SOCKET, SOCK_CR);
  }
}

uint16_t w5100_rx_size( uint8_t _s ) {
  uint16_t val=0,val1=0;

  do {
    val1 = w5100_sock_get16( _s, SOCK_RSR );
    if (val1 != 0)
      val = w5100_sock_get16( _s, SOCK_RSR );
  }
  while (val != val1);
  return val;
}

static void w5100_close_socket( uint8_t _s ) {
  // send command to close socket
  w5100_sock_set( _s, SOCK_CR, SOCK_CLOSE );
  // clear interrupt registers
  w5100_sock_set( _s, SOCK_IR, 0xFF );
}

static void configure_socket( uint8_t _s, uint8_t _flag, uint16_t _lport, uint16_t _dport, uint8_t *_dest ) {
  // Configure socket that receives data on 1234
  w5100_close_socket( _s );
  // configure type of socket
  w5100_sock_set( _s, SOCK_MR, SNMR_UDP | _flag );
  // configure MSB+LSB of local port number
  w5100_sock_set( _s, SOCK_PORT, _lport >> 8 );
  w5100_sock_set( _s, SOCK_PORT+1, _lport & 0xFF );
  if ( _dest[0] != 0x00 ) {
    // configure destination to send stuff to.
    w5100_set_buffer( CH_BASE + _s * CH_SIZE + SOCK_DIPR, _dest, 4 );
  }

  // this mac corresponds to a mac for multicasting....
  uint8_t mac_multi[] = { 0x01, 0x00, 0x5E, 0x01, 0x01, 0x0B };
  w5100_set_buffer( CH_BASE + _s * CH_SIZE + SOCK_DHAR, mac_multi, 6 );

  // configure destination port
  w5100_sock_set( _s, SOCK_DPORT, _dport >> 8 );
  w5100_sock_set( _s, SOCK_DPORT+1, _dport & 0xFF );

  // send command to open the socket
  w5100_sock_set( _s, SOCK_CR, SOCK_OPEN );
}

bool_t w5100_ch_available() {
  if ( w5100_rx_size( CMD_SOCKET ) > 0 ) {
    return TRUE;
  }
  return FALSE;
}

uint16_t w5100_receive( uint8_t *buf, uint16_t len ) {
  uint8_t head[8];
  uint16_t data_len=0;
  uint16_t ptr=0;

  // Get socket read pointer
  ptr = w5100_sock_get16( CMD_SOCKET, SOCK_RXRD );
  w5100_read_data( CMD_SOCKET, (uint8_t *)ptr, head, 0x08);
  ptr += 8;
  data_len = head[6];
  data_len = (data_len << 8) + head[7];

  // read data from buffer.
  w5100_read_data( CMD_SOCKET, (uint8_t *)ptr, buf, data_len); // data copy.
  ptr += data_len;

  // update read pointer
  w5100_sock_set( CMD_SOCKET, SOCK_RXRD, ptr >> 8);
  w5100_sock_set( CMD_SOCKET, SOCK_RXRD+1, ptr & 0xFF );

  // finalize read.
  w5100_sock_set( CMD_SOCKET, SOCK_CR, SOCK_RECV);
  return data_len;
}

static void w5100_read_data( uint8_t s, volatile uint8_t *src, volatile uint8_t *dst, uint16_t len ) {
  uint16_t size;
  uint16_t src_mask;
  uint16_t src_ptr;

  src_mask = (uint16_t)src & RMASK;
  src_ptr = RBASE[CMD_SOCKET] + src_mask;

  if( (src_mask + len) > RSIZE ) {
    size = RSIZE - src_mask;
    w5100_read(src_ptr, (uint8_t *)dst, size);
    dst += size;
    w5100_read(RBASE[CMD_SOCKET], (uint8_t *) dst, len - size);
  } else {
    w5100_read(src_ptr, (uint8_t *) dst, len);
  }
}

static uint16_t w5100_read(uint16_t _addr, uint8_t *_buf, uint16_t _len)
{
  for (int i=0; i<_len; i++) {
    _buf[i] = w5100_get( _addr );
    _addr++;
  }
  return _len;
}

