#include "archinc.h"
#include "cli.h"
#include "strutil.h"
#include "eventq.h"
#include "timer0.h"
#include "zarlink.h"
#include "zl_bus.h"
#include "zl_event.h"
#include "zlregs.h"

#include "uip/uip.h"
#include "uip/uip_arp.h"

#ifdef CONFIG_INCLUDE_ZARLINK

#include <stdio.h>
#include <stdlib.h>

static unsigned char frameStatusData[8];

boolean frameDataReady = false;

/*
 * Zarlink CPU Frame Send
 *
 * 1. Poll COMMAND&STATUS[4] until it is asserted.
 * 2. Write Transmit Frame Status (8 bytes) (described in Section 6.0, ?Processor Frame Format? on page 12) to
 *    CPU_ FRAME_REG.
 * 3. Poll COMMAND&STATUS[4] again (to verify that FIFO has space for incoming data) until it is asserted.
 * 4. Once COMMAND&STATUS[4] is asserted, one can write up to 16 bytes of data at a time. In 8-bit mode, write
 *    each byte in increasing order to CPU_ FRAME_REG [7:0]. (See CPU Frame General Structure for Memory
 *    Request section in Section 7.0, ?Control Command Format? on page 17 for further details on writing in increasing
 *    order). In 16-bit mode, write 2 bytes in increasing order; write even bytes in CPU_FRAME_REG [7:0] and
 *    odd bytes in CPU_FRAME_ REG [15:8]. After the 16 bytes are written to the FIFO, the frame engine transfers
 *    the data from the FIFO to the external frame data buffer.
 * 5. Repeat step 3 and 4 until the whole Ethernet frame is complete. Pad the frame with 0?s so the frame aligns to an
 *    8-byte boundary.
 * 6. Poll COMMAND&STATUS[4] until it is asserted.
 * 7. Write ?h0010 (16-bit mode) or ?h10 (8-bit mode) to COMMAND&STATUS to indicate the end of the frame.
 *
 */

/*
 
 Here's the official uIP byte gymnastics from the uip.h header file...

 void
 devicedriver_send(void)
 {
    hwsend(&uip_buf[0], UIP_LLH_LEN); 
    if(uip_len <= UIP_LLH_LEN + UIP_TCPIP_HLEN) {
      hwsend(&uip_buf[UIP_LLH_LEN], uip_len - UIP_LLH_LEN);    
    } else {
      hwsend(&uip_buf[UIP_LLH_LEN], UIP_TCPIP_HLEN);
      hwsend(uip_appdata, uip_len - UIP_TCPIP_HLEN - UIP_LLH_LEN);
    }
 }

*/
void zarlinkEthFrameWrite(void)
{
  int i;
  unsigned char tmpStatus;
  unsigned short padBytes;
  unsigned short frameLen;
  unsigned short linkLevelLen;
  unsigned short headerLen;
  unsigned short appLen;
  unsigned short fifoBytes;
  unsigned short crcBytes;
  boolean alignAppData;

  unsigned char * pLink8;
  unsigned char * pHeader8;
  volatile unsigned char * pApp8;

  union {
    unsigned short u16;
    unsigned char u8[2];
    } u;

  /*
   * perform uip packet length jiggery pokery suckery!
   */
  alignAppData = false;
  crcBytes = 4;

  if (uip_len <= (UIP_TCPIP_HLEN + UIP_LLH_LEN)) {
    headerLen = uip_len - UIP_LLH_LEN;
    pHeader8 = &uip_buf[UIP_LLH_LEN];
    appLen = 0;
    pApp8 = NULL;
    }
  else {
    headerLen = UIP_TCPIP_HLEN;
    pHeader8 = &uip_buf[UIP_LLH_LEN];
    appLen = uip_len - UIP_TCPIP_HLEN - UIP_LLH_LEN;
    pApp8 = uip_appdata;
    }

  // have to add UIP_LLH_LEN back in because we're supposed to send it first
  frameLen = UIP_LLH_LEN + headerLen + appLen;

  // if appbytes is not 16-bit aligned, make it so
  if ((appLen & 0x0001) != 0) {
    frameLen++; // bump the frame length to allow for app byte pad
    alignAppData = true;
    }

  // initialize frame status data (which is mostly zero)
  for(i=0;i<8;i++)
    frameStatusData[i] = 0x00;

  // Pad the frame length to ensure it ends on an 8-byte boundary
  if ((frameLen + crcBytes) < 64) {
    padBytes = 64 - (frameLen + crcBytes);
    }
  else if (((frameLen + crcBytes) & 0x0007) != 0) {
    padBytes = (8 - ((frameLen + crcBytes) & 0x0007));
    }
  else {
    padBytes = 0;
    }

  // 14 bits of frame length
  u.u16 = ((frameLen + padBytes + crcBytes) & 0x3fff);

  // frame length in bits [13:0] and '1' in bit [14]
  frameStatusData[0] = u.u8[0];
  frameStatusData[1] = u.u8[1] | 0x40; // frame status (bit 14) must be 1

  // destination port in bits [51:48], device ID (0) in [54:52], '0' in [55]

#if 1
  u.u16 = (unsigned short)0;
  u.u16 &= 0x03ff; // 10 bit port mask

  frameStatusData[6] = u.u8[0];
  frameStatusData[7] = (u.u8[1] | 0x04);
#else
  frameStatusData[6] = (port & 0x0f);
#endif

  // bit[63] - substitute frame source when it departs the device (=1 for now)
  // bit[62:59] - 4 bits of priority (set zero for now)
  // bit[58] - 1 to use port map (set zero)
  // bit[57:48] - port map
  // bit[57:55] - always zero
  // bit[54:52] - device ID (=0 in our case)
  // bit[51:48] - destination port

  // poll status reg bit 4
  for(i=0;i<1000;i++) {
    tmpStatus = zarlinkStatusRead();
    if ((tmpStatus & 0x10) != 0) {
      break;
      }
    timer0Delay(1);
    }

  if ((tmpStatus & 0x10) == 0)
    return;

  // write frame status data to switch
  for(i=0;i<8;i++) {
    zarlinkFrameWrite8(frameStatusData[i]);
    }

  // poll status reg bit 4
  for(i=0;i<1000;i++) {
    tmpStatus = zarlinkStatusRead();
    if ((tmpStatus & 0x10) != 0) {
      break;
      }
    timer0Delay(1);
    }

  if ((tmpStatus & 0x10) == 0)
    return;

  /* 
   * first write link level header bytes
   */
  linkLevelLen = UIP_LLH_LEN;
  pLink8 = (unsigned char *)&uip_buf[0];

  fifoBytes = 16;
  while(linkLevelLen > 0) {
    while ((linkLevelLen > 0) && (fifoBytes > 0)) {
        zarlinkFrameWrite8(*pLink8++);
        fifoBytes --;
        linkLevelLen --;
        } // for

    // poll status reg bit 4
    if (fifoBytes == 0) {
      for(i=0;i<1000;i++) {
        tmpStatus = zarlinkStatusRead();
        if ((tmpStatus & 0x10) != 0) {
          fifoBytes = 16;
          break;
          }
        timer0Delay(1);
        }
      }
    } // while

  /* 
   * write remaining header bytes
   */
  fifoBytes = 16;
  while(headerLen > 0) {
    while ((headerLen > 0) && (fifoBytes > 0)) {
        zarlinkFrameWrite8(*pHeader8++);
        fifoBytes --;
        headerLen --;
        } // for

    // poll status reg bit 4
    if (fifoBytes == 0) {
      for(i=0;i<1000;i++) {
        tmpStatus = zarlinkStatusRead();
        if ((tmpStatus & 0x10) != 0) {
          fifoBytes = 16;
          break;
          }
        timer0Delay(1);
        }
      }
    } // while

  /*
   * write app bytes if frameLen is > headerLen
   */

  while(appLen > 0) {
    while ((appLen > 0) && (fifoBytes > 0)) {
      zarlinkFrameWrite8(*pApp8++);
      if ((appLen == 1) && (alignAppData == true)) {
        zarlinkFrameWrite8(0x00); // write app data first, then pad byte
        }
      fifoBytes --;
      appLen --;
      } // while

    // poll status reg bit 4
    if (fifoBytes == 0) {
      for(i=0;i<1000;i++) {
        tmpStatus = zarlinkStatusRead();
        if ((tmpStatus & 0x10) != 0) {
          fifoBytes = 16;
          break;
          }
        timer0Delay(1);
        }
      }
    } // while

  /*
   * write 4 bytes for ethernet frame CRC32 (per Zarlink driver)
   */
  while(crcBytes > 0) {
    while ((crcBytes > 0) && (fifoBytes > 0)) {
      zarlinkFrameWrite8(0); // filled in by hardware
      fifoBytes --;
      crcBytes --;
      } // while

    // poll status reg bit 4
    if (fifoBytes == 0) {
      for(i=0;i<1000;i++) {
        tmpStatus = zarlinkStatusRead();
        if ((tmpStatus & 0x10) != 0) {
          fifoBytes = 16;
          break;
          }
        timer0Delay(1);
        }
      }
    } // while

  
  /*
   * write pad bytes 
   */
  while(padBytes > 0) {
    while ((padBytes > 0) && (fifoBytes > 0)) {
        zarlinkFrameWrite8((unsigned char)0);
        fifoBytes --;
        padBytes --;
        } // for

    // poll status reg bit 4
    if (fifoBytes == 0) {
      for(i=0;i<1000;i++) {
        tmpStatus = zarlinkStatusRead();
        if ((tmpStatus & 0x10) != 0) {
          fifoBytes = 16;
          break;
          }
        timer0Delay(1);
        }
      }
    } // while

  // poll status reg bit 4
  for(i=0;i<1000;i++) {
    tmpStatus = zarlinkStatusRead();
    if ((tmpStatus & 0x10) != 0) {
      break;
      }
    timer0Delay(1);
    }

  // write 0x10 to command reg to signal end-of-frame and that's it!
  zarlinkCommandWrite(0x10);
  }

/*
 * Zarlink CPU Frame Read
 *
 * 1. Wait for a hardware interrupt. Read interrupt register (direct register 5). When bit 0 is set, it indicates an Ethernet
 *    frame waiting is asserted. Interrupt is de-asserted when first byte of frame is read out. The interrupt can be
 *    dynamically masked when the CPU is busy and does not want to be interrupted. The register INT_MASK0 programs
 *    the mask.
 * 2. Read 8 bytes of Receive Frame Status/ Ethernet frame (see Section 6.0, ?Processor Frame Format? on page
 *    12). In 8-bit mode, each byte is read out in increasing order from CPU_FRAME_REG [7:0]. In 16-bit mode,
 *    every 2 bytes are read out in increasing order; even bytes are read out from CPU_FRAME_REG[7:0] and odd
 *    bytes from CPU_FRAME_REG [15:8].
 * 3. Poll COMMAND&STATUS[3] until asserted.
 * 4. Read COMMAND&STATUS[5]; if asserted, the last 8 bytes needs to be read out (step 5), otherwise, repeat
 *    steps 2 and 3.
 * 5. Read last 8 byte of Ethernet frame.
 * 6. Write ?h0008 (16-bit mode) or ?h08 (8-bit mode) to COMMAND&STATUS to indicate the end of the frame.
 */

/* either returns a valid frame descriptor or NULL */

unsigned short zarlinkEthFrameRead(void)
{
  int i;
  int retVal = 0;
  unsigned char tmpStatus;
  unsigned short frameLen;
  unsigned short bytesLeft;
  unsigned char * p8;
  boolean lastOctet;
  union {
    unsigned char u8[2];
    unsigned short u16;
    } u;

  lastOctet = false;

  // read frame status data from switch
  for(i=0;i<8;i++) {
    frameStatusData[i] = zarlinkFrameRead8();
    }

  for(i=0;i<1000;i++) {
    tmpStatus = zarlinkStatusRead();
    if ((tmpStatus & 0x08) != 0) {
      break;
      }
    // timer0Delay(1);
    }

  if ((tmpStatus & 0x08) == 0x00)
    return 0;

  u.u8[0] = frameStatusData[0];
  u.u8[1] = frameStatusData[1];

  bytesLeft = (unsigned int)(u.u16 & 0x3fff);

  tmpStatus = zarlinkStatusRead();
  if ((tmpStatus & 0x20) == 0x20) {
    lastOctet = true;
    }

  frameLen = 0;
  p8 = &uip_buf[0];

  while(bytesLeft > 0) {
    // get 8 bytes of data
    for(i=0; i<8; i++) {
      *p8++ = zarlinkFrameRead8();
      frameLen ++;
      bytesLeft --;
      }

    if (lastOctet == false) {
      for(i=0;i<1000;i++) {
        tmpStatus = zarlinkStatusRead();
        if ((tmpStatus & 0x08) != 0) {
          break;
          }
        // timer0Delay(1);
        }

      tmpStatus = zarlinkStatusRead();  
      if ((tmpStatus & 0x20) == 0x20) {
        lastOctet = true;
        }
      }
    else {
      break;
      }
    }

  // write 0x08 to command reg to signal end-of-receive and that's it!
  zarlinkCommandWrite(0x08);

  return frameLen;
}

#endif

