/*
* Wiznet5500 library
* Contains functions to allow for networking
*
* Functions from this library can be used to operate up to 8 sockets
* Application based code should not be in this library
*/

// uses stdlib.c

// Wiznet W5500 Op Codes
#define WIZNET_WRITE_COMMON 0x04 //opcode to write to one of the common block of registers
#define WIZNET_READ_COMMON  0x00 //opcode to read one of the common block of registers
#define WIZNET_WRITE_SnR    0x0C // s<<5 (nnn 01 1 00) opcode to write to one of the socket n registers
#define WIZNET_READ_SnR     0x08 // s<<5 (nnn 01 0 00) opcode to read one of the socket n registers
#define WIZNET_WRITE_SnTX   0x14 // s<<5 (nnn 10 1 00) opcode to write to the socket n transmit buffer
#define WIZNET_READ_SnRX    0x18 // s<<5 (nnn 11 0 00) opcode to read from the socket n receive buffer

// Wiznet W5500 Register Addresses
#define WIZNET_MR     0x0000    // Mode
#define WIZNET_GAR    0x0001    // Gateway IP address
#define WIZNET_SUBR   0x0005    // Subnet mask address
#define WIZNET_SHAR   0x0009    // Source MAC address
#define WIZNET_SIPR   0x000F    // Source IP address
#define WIZNET_IR     0x0015    // Interrupt
#define WIZNET_IMR    0x0016    // Interrupt Mask
#define WIZNET_RTR    0x0019    // Timeout address
#define WIZNET_RCR    0x001B    // Retry count
#define WIZNET_UIPR   0x0028    // Unreachable IP address in UDP mode
#define WIZNET_UPORT  0x002C    // Unreachable Port address in UDP mode

//W5500 Socket Registers follow
#define WIZNET_SnMR        0x0000        // Mode
#define WIZNET_SnCR        0x0001        // Command
#define WIZNET_SnIR        0x0002        // Interrupt
#define WIZNET_SnSR        0x0003        // Status
#define WIZNET_SnPORT      0x0004        // Source Port (2 bytes)
#define WIZNET_SnDHAR      0x0006        // Destination Hardw Addr
#define WIZNET_SnDIPR      0x000C        // Destination IP Addr
#define WIZNET_SnDPORT     0x0010        // Destination Port
#define WIZNET_SnMSSR      0x0012        // Max Segment Size
#define WIZNET_SnPROTO     0x0014        // Protocol in IP RAW Mode
#define WIZNET_SnTOS       0x0015        // IP TOS
#define WIZNET_SnTTL       0x0016        // IP TTL
#define WIZNET_SnRX_BSZ    0x001E        // RX Buffer Size
#define WIZNET_SnTX_BSZ    0x001F        // TX Buffer Size
#define WIZNET_SnTX_FSR    0x0020        // TX Free Size
#define WIZNET_SnTX_RD     0x0022        // TX Read Pointer
#define WIZNET_SnTX_WR     0x0024        // TX Write Pointer
#define WIZNET_SnRX_RSR    0x0026        // RX RECEIVED SIZE REGISTER
#define WIZNET_SnRX_RD     0x0028        // RX Read Pointer
#define WIZNET_SnRX_WR     0x002A        // RX Write Pointer (supported?

//Socket n Mode Register (0x0000)
//WIZNET_SnMR
#define WIZNET_MR_CLOSE    0x00    // Unused socket
#define WIZNET_MR_TCP      0x01    // TCP
#define WIZNET_MR_UDP      0x02    // UDP
#define WIZNET_MR_IPRAW    0x03    // IP LAYER RAW SOCK
#define WIZNET_MR_MACRAW   0x04    // MAC LAYER RAW SOCK
#define WIZNET_MR_PPPOE    0x05    // PPPoE
#define WIZNET_MR_ND       0x20    // No Delayed Ack(TCP) flag
#define WIZNET_MR_MULTI    0x80    // support multicating

//Socket n Command Register (0x0001)
//WIZNET_SnCR
#define WIZNET_CR_OPEN          0x01   // Initialize or open socket
#define WIZNET_CR_LISTEN        0x02   // Wait connection request in tcp mode(Server mode)
#define WIZNET_CR_CONNECT       0x04   // Send connection request in tcp mode(Client mode)
#define WIZNET_CR_DISCON        0x08   // Send closing reqeuset in tcp mode
#define WIZNET_CR_CLOSE         0x10   // Close socket
#define WIZNET_CR_SEND          0x20   // Update Tx memory pointer and send data
#define WIZNET_CR_SEND_MAC      0x21   // Send data with MAC address, so without ARP process
#define WIZNET_CR_SEND_KEEP     0x22   // Send keep alive message
#define WIZNET_CR_RECV          0x40   // Update Rx memory buffer pointer and receive data

//Socket n Interrupt Register (0x0002)
//WIZNET_SnIR
// Bit 0: CON
// Bit 1: DISCON
// Bit 2: RECV
// Bit 3: TIMEOUT
// Bit 4: SEND_OK

//Socket n Status Register (0x0003)
//WIZNET_SnSR 
#define WIZNET_SOCK_CLOSED      0x00   // Closed
#define WIZNET_SOCK_INIT        0x13   // Init state
#define WIZNET_SOCK_LISTEN      0x14   // Listen state
#define WIZNET_SOCK_SYNSENT     0x15   // Connection state
#define WIZNET_SOCK_SYNRECV     0x16   // Connection state
#define WIZNET_SOCK_ESTABLISHED 0x17   // Success to connect
#define WIZNET_SOCK_FIN_WAIT    0x18   // Closing state
#define WIZNET_SOCK_CLOSING     0x1A   // Closing state
#define WIZNET_SOCK_TIME_WAIT   0x1B   // Closing state
#define WIZNET_SOCK_CLOSE_WAIT  0x1C   // Closing state
#define WIZNET_SOCK_LAST_ACK    0x1D   // Closing state
#define WIZNET_SOCK_UDP         0x22   // UDP socket
#define WIZNET_SOCK_IPRAW       0x32   // IP raw mode socket
#define WIZNET_SOCK_MACRAW      0x42   // MAC raw mode socket
#define WIZNET_SOCK_PPPOE       0x5F   // PPPOE socket

//Socket n Source Port Register (0x0004, 0x0005)
//WIZNET_SnPORT
// MSByte: 0x0004
// LSByte: 0x0005

#define WIZNET_MAX_RBUF 2048 // buffer for receiving data (max rx packet size!)
#define WIZNET_MAX_TBUF 2048 // buffer for sending data (max tx packet size!)


//-------------------
//BASIC READ AND WRITE FUNCTIONS
//-------------------

// Sets SPI3_CS low
void WizSpiBeginTransfer()
{
  asm(
      "; backup regs\n"
      "push r1\n"
      "push r2\n"

      "load32 0xC02732 r2                 ; r2 = 0xC02732\n"

      "load 0 r1                          ; r1 = 0 (enable)\n"
      "write 0 r2 r1                      ; write to SPI3_CS\n"

      "; restore regs\n"
      "pop r2\n"
      "pop r1\n"
      );
}

// Sets SPI3_CS high
void WizSpiEndTransfer()
{
  asm(
      "; backup regs\n"
      "push r1\n"
      "push r2\n"

      "load32 0xC02732 r2                 ; r2 = 0xC02732\n"

      "load 1 r1                          ; r1 = 1 (disable)\n"
      "write 0 r2 r1                      ; write to SPI3_CS\n"

      "; restore regs\n"
      "pop r2\n"
      "pop r1\n"
      );
}

// write dataByte and return read value
// write 0x00 for a read
// Writes byte over SPI3 to W5500
word WizSpiTransfer(word dataByte)
{
  word retval = 0;
  asm(
      //"ccache\n"
      "load32 0xC02731 r2                 ; r2 = 0xC02731\n"
      "write 0 r2 r4                      ; write r4 over SPI3\n"
      "read 0 r2 r2                       ; read return value\n"
      "write -4 r14 r2                    ; write to stack to return\n"
      );

  return retval;
}


// Write data to W5500
void wizWrite(word addr, word cb, char* buf, word len)
{
  WizSpiBeginTransfer();

  // Send address
  word addrMSB = (unsigned) addr >> 8;
  WizSpiTransfer(addrMSB); //msByte
  WizSpiTransfer(addr); //lsByte

  // Send control byte
  WizSpiTransfer(cb);

  // Send data
  word i;
  for (i = 0; i < len; i++)
  {
    WizSpiTransfer(buf[i]);
  }

  WizSpiEndTransfer();
}


// Write single byte to W5500
word wizWriteSingle(word addr, word cb, word data)
{
  WizSpiBeginTransfer();

  // Send address
  word addrMSB = (unsigned) addr >> 8;
  WizSpiTransfer(addrMSB); //msByte
  WizSpiTransfer(addr); //lsByte

  // Send control byte
  WizSpiTransfer(cb);

  // Send data
  WizSpiTransfer(data);

  WizSpiEndTransfer();

  return data;
}


// Write two bytes to W5500
void wizWriteDouble(word addr, word cb, word data)
{
  WizSpiBeginTransfer();

  // Send address
  word addrMSB = (unsigned) addr >> 8;
  WizSpiTransfer(addrMSB); //msByte
  WizSpiTransfer(addr); //lsByte

  // Send control byte
  WizSpiTransfer(cb);

  // Send data
  word dataMSB = (unsigned) data >> 8;
  WizSpiTransfer(dataMSB);
  WizSpiTransfer(data);

  WizSpiEndTransfer();
}


void wizRead(word addr, word cb, char* buf, word len)
{ 
  WizSpiBeginTransfer();

  // Send address
  word addrMSB = (unsigned) addr >> 8;
  WizSpiTransfer(addrMSB); //msByte
  WizSpiTransfer(addr); //lsByte

  // Send control byte
  WizSpiTransfer(cb);

  // Read data
  word i;
  for (i = 0; i < len; i++)
  {
    buf[i] = WizSpiTransfer(0);
  }

  WizSpiEndTransfer();
}


word wizReadSingle(word addr, word cb)
{ 
  WizSpiBeginTransfer();

  // Send address
  word addrMSB = (unsigned) addr >> 8;
  WizSpiTransfer(addrMSB); //msByte
  WizSpiTransfer(addr); //lsByte

  // Send control byte
  WizSpiTransfer(cb);

  // Read data
  word retval = WizSpiTransfer(0);

  WizSpiEndTransfer();

  // Return read value
  return retval;
}


word wizReadDouble(word addr, word cb)
{ 
  WizSpiBeginTransfer();

  // Send address
  word addrMSB = (unsigned) addr >> 8;
  WizSpiTransfer(addrMSB); //msByte
  WizSpiTransfer(addr); //lsByte

  // Send control byte
  WizSpiTransfer(cb);

  // Read data
  word retval = WizSpiTransfer(0) << 8;
  retval = retval + WizSpiTransfer(0);

  WizSpiEndTransfer();

  // Return read value
  return retval;
}


//-------------------
//W5500 SOCKET REGISTER FUNCTIONS
//-------------------

// Send a command cmd to socket s
void wizCmd(word s, word cmd)
{
  //wizWriteSingle(WIZNET_SnCR, WIZNET_WRITE_SnR, cmd);
  WizSpiBeginTransfer();
  WizSpiTransfer(0); //msByte
  WizSpiTransfer(WIZNET_SnCR); //lsByte
  WizSpiTransfer(WIZNET_WRITE_SnR + (s << 5));
  WizSpiTransfer(cmd);
  WizSpiEndTransfer();

  // wait untill done
  while ( wizReadSingle(WIZNET_SnCR, WIZNET_READ_SnR) );
}

// Write 8 bits to a sockets control register
void wizSetSockReg8(word s, word addr, word val)
{
  //wizWriteSingle(addr, WIZNET_WRITE_SnR, val);
  WizSpiBeginTransfer();
  WizSpiTransfer(0); //msByte
  WizSpiTransfer(addr); //lsByte
  WizSpiTransfer(WIZNET_WRITE_SnR + (s << 5));
  WizSpiTransfer(val);
  WizSpiEndTransfer();
}

// Read 8 bits from a sockets control register
word wizGetSockReg8(word s, word addr){
  //return wizReadSingle(addr, WIZNET_READ_SnR);
  WizSpiBeginTransfer();

  // Send address
  word addrMSB = (unsigned) addr >> 8;
  WizSpiTransfer(addrMSB); //msByte
  WizSpiTransfer(addr); //lsByte

  word cb = WIZNET_READ_SnR + (s << 5);
  
  // Send control byte
  WizSpiTransfer(cb);

  // Read data
  word retval = WizSpiTransfer(0);

  WizSpiEndTransfer();

  // Return read value
  return retval;
}

// Write 16 bits to a sockets control register
void wizSetSockReg16(word s, word addr, word val)
{
  //wizWriteDouble(addr, WIZNET_WRITE_SnR + (s << 5), val);
  WizSpiBeginTransfer();
  WizSpiTransfer(0); //msByte
  WizSpiTransfer(addr); //lsByte
  WizSpiTransfer(WIZNET_WRITE_SnR + (s << 5));
  word valMSB = (unsigned) val >> 8;
  WizSpiTransfer(valMSB);
  WizSpiTransfer(val);
  WizSpiEndTransfer();
}

// Read 16 bits from a sockets control register
word wizGetSockReg16(word s, word addr)
{
  //return wizReadDouble(addr, WIZNET_READ_SnR + (s << 5));
  WizSpiBeginTransfer();

  // Send address
  word addrMSB = (unsigned) addr >> 8;
  WizSpiTransfer(addrMSB); //msByte
  WizSpiTransfer(addr); //lsByte

  word cb = WIZNET_READ_SnR + (s << 5);

  // Send control byte
  WizSpiTransfer(cb);

  // Read data
  word retval = WizSpiTransfer(0) << 8;
  retval = retval + WizSpiTransfer(0);

  WizSpiEndTransfer();

  // Return read value
  return retval;
}


//-------------------
//W5500 SETUP FUNCTIONS
//-------------------

// Initialize W5500 chip
void wiz_Init(char* ip_addr, char* gateway_addr, char* mac_addr, char* sub_mask)
{
  WizSpiEndTransfer();
  delay(10);

  wizWrite(WIZNET_SIPR,  WIZNET_WRITE_COMMON,  ip_addr,       4);
  wizWrite(WIZNET_GAR,   WIZNET_WRITE_COMMON,  gateway_addr,  4);
  wizWrite(WIZNET_SHAR,  WIZNET_WRITE_COMMON,  mac_addr,      6);
  wizWrite(WIZNET_SUBR,  WIZNET_WRITE_COMMON,  sub_mask,      4);
}


// Initialize socket s for TCP
void wizInitSocketTCP(word s, word port)
{
  wizCmd(s, WIZNET_CR_CLOSE);
  wizSetSockReg8      (s, WIZNET_SnIR, 0xFF);    //reset interrupt register
  wizSetSockReg8      (s, WIZNET_SnMR, WIZNET_MR_TCP);  //set mode register to tcp
  wizSetSockReg16     (s, WIZNET_SnPORT, port);  //set tcp port
  wizCmd(s, WIZNET_CR_OPEN);
  wizCmd(s, WIZNET_CR_LISTEN);
  delay(10); //wait a bit to make sure the socket is in the correct state (technically not necessary)
}


// Initialize socket s for TCP client
void wizInitSocketTCPClient(word s, word port)
{
  wizCmd(s, WIZNET_CR_CLOSE);
  wizSetSockReg8      (s, WIZNET_SnIR, 0xFF);    //reset interrupt register
  wizSetSockReg8      (s, WIZNET_SnMR, WIZNET_MR_TCP);  //set mode register to tcp
  wizSetSockReg16     (s, WIZNET_SnPORT, port);  //set tcp port
  wizCmd(s, WIZNET_CR_OPEN);
  delay(10); //wait a bit to make sure the socket is in the correct state (technically not necessary)
}


//-------------------
//W5500 READING AND WRITING FUNCTIONS
//-------------------


// from memory, so no need for unsigned comparisons (because we have less than 2GB RAM)
word wizWriteDataFromMemory(word s, char* buf, word buflen)
{
  // Make sure there is something to send
  if (buflen <= 0)
  {
    return 0;
  }

  word bytesSent = 0;

  // loop until all bytes are sent
  while (bytesSent != buflen)
  {

    if (wizGetSockReg8(s, WIZNET_SnSR) == WIZNET_SOCK_CLOSED)
    {
      //uprintln("connection closed");
      return 0;
    }

    word partToSend = buflen - bytesSent;
    if (partToSend > WIZNET_MAX_TBUF)
      partToSend = WIZNET_MAX_TBUF;

    // Make sure there is room in the transmit buffer for what we want to send
    word txfree = wizGetSockReg16(s, WIZNET_SnTX_FSR); // Size of the available buffer area

    word timeout = 0;
    while (txfree < partToSend) 
    {
      timeout++; // Increase timeout counter
      delay(1); // Wait a bit
      txfree = wizGetSockReg16(s, WIZNET_SnTX_FSR); // Size of the available buffer area
      
      // After a second
      if (timeout > 1000) 
      {
        wizCmd(s, WIZNET_CR_DISCON); // Disconnect the connection
        //uprintln("timeout");
        return 0;
      }
    }

     // Space is available so we will send the buffer
    word txwr = wizGetSockReg16(s, WIZNET_SnTX_WR);  // Read the Tx Write Pointer

    // Write the outgoing data to the transmit buffer
    wizWrite(txwr, WIZNET_WRITE_SnTX + (s << 5), buf + bytesSent, partToSend);

    // update the buffer pointer
    word newSize = txwr + partToSend;
    wizSetSockReg16(s, WIZNET_SnTX_WR, newSize);

    // Now Send the SEND command which tells the wiznet the pointer is updated
    wizCmd(s, WIZNET_CR_SEND);

    // Update the amount of bytes sent
    bytesSent += partToSend;
  }


  return 1;
}



// Read received data
word wizReadRecvData(word s, char* buf, word buflen)
{
  if (buflen == 0)
  {
    return 1;
  }
  
  if (buflen > WIZNET_MAX_RBUF) // If the request size > WIZNET_MAX_RBUF, truncate it to prevent overflow
  {
    //uprintln("W: Received too large TCP data");
    buflen = WIZNET_MAX_RBUF; // - 1; // -1 Because room for 0 terminator
  }
   
  // Get the address where the wiznet is holding the data
  word rxrd = wizGetSockReg16(s, WIZNET_SnRX_RD); 

  // Read the data into the buffer
  wizRead(rxrd, WIZNET_READ_SnRX + (s << 5), buf, buflen);

  // Remove read data from rxbuffer to make space for new data
  word nsize = rxrd + buflen;
    wizSetSockReg16(s, WIZNET_SnRX_RD, nsize);  //replace read data pointer
    //tell the wiznet we have retrieved the data
    wizCmd(s, WIZNET_CR_RECV);

  // Terminate buffer for printing in case the data was a string
  *(buf + buflen) = 0;

  return 1;
}


// Remove the received data
void wizFlush(word s, word rsize)
{
  if (rsize > 0)
  {
    word rxrd = wizGetSockReg16(s, WIZNET_SnRX_RD);         //retrieve read data pointer
    word nsize = rxrd + rsize;
    wizSetSockReg16(s, WIZNET_SnRX_RD, nsize);  //replace read data pointer
    //tell the wiznet we have retrieved the data
    wizCmd(s, WIZNET_CR_RECV);
  }
}
