#include "archinc.h"
#include "cli.h"
#include "strutil.h"
#include "eventq.h"
#include "zlregs.h"
#include "zarlink.h"
#include "zl_event.h"

#ifdef CONFIG_INCLUDE_ZARLINK

#include <stdio.h>
#include <stdlib.h>

static unsigned char frameStatusData[8];

static void handleFrameRxEvent(void);

/*
 * 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.
 *
 */

int zarlinkEthFrameWrite(unsigned short frameLen, unsigned char * pFrameData, unsigned char port)
{
  int i;
  unsigned char tmpStatus;
  unsigned short padBytes;
  unsigned short totalBytes;
  unsigned short * p16;

  union {
    unsigned short u16;
    unsigned char u8[2];
    } u;

  if (pFrameData == NULL)
    return -1;

  if (frameLen < 64)  // always send at least 64 bytes
    frameLen = 64;

  // initialize frame status data (which is mostly zero)
  for(i=0;i<8;i++)
    frameStatusData[i] = 0x00;

  padBytes = 0;

  // Pad the frame length to ensure it ends on an 8-byte boundary
  if ((frameLen & 0x0007) != 0) {
    padBytes = (8 - (frameLen & 0x0007));
    }
  
  totalBytes = ((frameLen + padBytes) & 0x3fff); // 14 bits of length
  u.u16 = totalBytes;

  // 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)(0x01 << port);
  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;
      }
    }

  if ((tmpStatus & 0x10) == 0)
    return -1;

  p16 = (unsigned short *)(frameStatusData);
  // write frame status data to switch
  for(i=0;i<8/2;i++) {
    zarlinkFrameWrite(p16[i]);
    }

  // poll status reg bit 4
  for(i=0;i<1000;i++) {
    tmpStatus = zarlinkStatusRead();
    if ((tmpStatus & 0x10) != 0)
      break;
    }

  if ((tmpStatus & 0x10) == 0)
    return -2;

  p16 = (unsigned short *)pFrameData;

  while(totalBytes > 0) {
    // write totalBytes or 8 bytes, whichever is less, and check 
    for(i=0;((totalBytes > 0) && (i<16/2)); totalBytes -= 2, i++) {
      if (totalBytes > padBytes) {
        // get data from the packet buffer
        zarlinkFrameWrite(*p16++);
        }
      else {
        zarlinkFrameWrite(0x00);
        // write pad data (0x00)
        }
      } // for

    // poll status reg bit 4
    for(i=0;i<1000;i++) {
      tmpStatus = zarlinkStatusRead();
      if ((tmpStatus & 0x10) != 0)
        break;
      }

    if ((tmpStatus & 0x10) == 0) {
      return -3;
      }
    } // while

  // poll status reg bit 4
  for(i=0;i<1000;i++) {
    tmpStatus = zarlinkStatusRead();
    if ((tmpStatus & 0x10) != 0)
      break;
    }

  if ((tmpStatus & 0x10) == 0)
    return -4;

  // write 0x10 to command reg to signal end-of-frame and that's it!
  zarlinkCommandWrite(0x10);

  return 0;
}

/*
 * 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.
 */

static char recvFrameBuffer[1536];
static unsigned short lastRecvFrameLen;

int zarlinkEthFrameRead(unsigned short * pFrameLen, unsigned char * pFrameData)
{
  int i;
  int retVal = -1;
  unsigned char tmpStatus;
  unsigned int frameLen;
  unsigned short * p16;
  boolean lastOctet;

  if (pFrameData == NULL) {
    pFrameData = recvFrameBuffer;
    }

  p16 = (unsigned short *)frameStatusData;
  lastOctet = false;

  // read frame status data from switch
  for(i=0;i<8/2;i++) {
    p16[i] = zarlinkFrameRead();
    }

  for(i=0;i<1000;i++) {
    tmpStatus = zarlinkStatusRead();
    if ((tmpStatus & 0x08) != 0) {
      break;
      }
    }

  if ((tmpStatus & 0x08) == 0) {
    retVal = -1;
    }

  if ((tmpStatus & 0x20) == 0x20) {
    lastOctet = true;
    }

  frameLen = 0;
  lastRecvFrameLen = 0;
  p16 = (unsigned short *)pFrameData;

  for(;;) {
    // get 8 bytes of data
    for(i=0; i<8/2; i++) {
      *p16++ = zarlinkFrameRead();
      frameLen += 2;
      }

    if (lastOctet == false) {
      for(i=0;i<1000;i++) {
        tmpStatus = zarlinkStatusRead();
        if ((tmpStatus & 0x08) != 0) {
          break;
          }
        }

      if ((tmpStatus & 0x08) == 0) {
        retVal = -2;
        break;
        }

      tmpStatus = zarlinkStatusRead();  
      if ((tmpStatus & 0x20) == 0x20) {
        lastOctet = true;
        }
      }
    else {
      retVal = 0;
      break;
      }
    }

  // write 0x08 to command reg to signal end-of-receive and that's it!
  zarlinkCommandWrite(0x08);

  if (pFrameLen != NULL) {
    *pFrameLen = frameLen;
    }

  lastRecvFrameLen = frameLen;

  return retVal;
}

static int fsendHandler(int argc, char * argv[], int flags)
{
  int retVal = -1;
  int bufLen;
  char * pOutBuf;
  unsigned char sendPort;

  pOutBuf = clicharGetOutputBuffer(&bufLen);

  if (argc < 2) {
    sprintf(pOutBuf, "%s: sending eth frame on all ports!\n", argv[0]);
    sendPort = 0x1f;
    }
  else if (argc == 2) {
    sendPort = (unsigned char)strtol(argv[1], (char **)NULL, 16);
    sprintf(pOutBuf, "%s: sending eth frame on port [%d]\r\n", argv[0], sendPort);
    }
  else {
    return retVal;
    }

  clicharSendOutputBuffer(pOutBuf, bufLen);

  return zarlinkEthFrameWrite(0, NULL, sendPort);
}

static int frecvHandler(int argc, char * argv[], int flags)
{
  int retVal = -1;
  int bufLen;
  char * pOutBuf;
  unsigned short recvBytes;

  pOutBuf = clicharGetOutputBuffer(&bufLen);

  retVal = zarlinkEthFrameRead(&recvBytes, NULL);

  if (retVal == 0) {
    sprintf(pOutBuf, "%s: received [%u] bytes in packet buffer\r\n", argv[0], recvBytes);
    }
  else {
    sprintf(pOutBuf, "%s: packet receive error [%d]\r\n", argv[0], retVal);
    }

  clicharSendOutputBuffer(pOutBuf, bufLen);

  return retVal;
}

static int fshowHandler(int argc, char * argv[], int flags)
{
  int retVal = -1;

  if (lastRecvFrameLen != 0) {
    strutilHexdump(recvFrameBuffer, lastRecvFrameLen);
    retVal = 0;
    }

  return retVal;
}

static void handleFrameRxEvent(void)
{
  int bufLen;
  char * pOutBuf;
  unsigned short recvBytes;
  int retVal;

  pOutBuf = clicharGetOutputBuffer(&bufLen);
  retVal = zarlinkEthFrameRead(&recvBytes, NULL);
  sprintf(pOutBuf, "*** frame received, %d bytes\r\n", recvBytes);
  clicharSendOutputBuffer(pOutBuf, bufLen);

  // TODO: send frame to upper level when buffer pool is implemented
  // before turning on frame rx interrupts again

  zarlinkEventUnmaskIrq(INT_CPU_FRAME);
}

int zarlinkFrameInit(void)
{
  eventqRegisterHandler(SWITCH_FRAME_RX, handleFrameRxEvent);

  cliRegisterCommand("fsend", fsendHandler, "fsend [port] - sends a 128 byte ethernet frame on switch port");
  cliRegisterCommand("frecv", frecvHandler, "frecv - receives a packet into internal memory");
  cliRegisterCommand("fshow", fshowHandler, "fshow - shows a hex dump of the last received packet");  
}

#endif

