#include "archinc.h"
#include "eventq.h"
#include "timer0.h"
#include "pll.h"
#include "board.h"
#include "uart0.h"
#include "spi.h"
#include "spibpirq.h"
#include "i2cmaster.h"
#include "i2cswitch.h"
#include "i2cport.h"
#include "cli.h"
#include "clichar.h"
#include "sfp.h"
#include "sfpdiag.h"
#include "zarlink.h"
#include "zl_event.h"
#include "zl_util.h"
#include "zl_rmon.h"
#include "zl_trunk.h"
#include "zl_mii.h"
#include "zl_cpumac.h"
#include "zl_lhb.h"
#include "icmp.h"
#include "ip.h"
#include "watchdog.h"
#include "ee24lc256.h"
#include "nvstore.h"
#include "pm.h"
#include "portmgr.h"
#include "bdinfo.h"
#include "metadata.h"
#include "version.h"

#include "uip/uip.h"
#include "uip/uip_arp.h"
#include "uip/wakeapp.h"

#include <stdio.h>

static char buffer[128];

int main(void)
{
  int zarlinkStatus;
  volatile int wdogCount, arptimer;
  unsigned int current, start;
  volatile int i, j;

  // first, disable interrupts
  IRQ_DISABLE();

  // initialize the event (work) queue
  eventqInit();

  // initialize the hardware
  boardInit();

  // initialize the ARM Vectored Interrupt Controller
  armvicInit();

  // initialize Memory subsystem first (cacheability, flash timing)
  MAMCR  = (unsigned long)0x00; // disable MAM, so we can change MAMTIM
  MAMTIM = (unsigned long)0x03; // flash access = 3 CPU clock cycles
  MAMCR  = (unsigned long)0x01; // partially enable MAM operation (prefetch on)

  // before all else, initialize PLL and VPB peripheral divider
  pllInit();

  // run VPB bus at 1/2 processor clock
  pllSetPeriphDivider(2);

  // Initialize periodic timer
#ifdef CONFIG_INCLUDE_LPC210X_TIMER0
  timer0Init();
#endif

  //
  // Initialize on-chip peripherals
  //

  // console UART
  uart0Init(38400);

  sprintf(buffer, "\r\nMBARI Medusa v %s (%s %s) %s %s\r\n", 
    appVersion, toolchain, toolVersion, buildDate, buildTime);
  uart0Puts(buffer);

  // initialize the CLI
  cliInit();
  clicharInit();

  // non-critical initialization
  armvicInit2();
  uart0Init2();
  boardInit2();

  /* Initialize SPI controller */

#if defined(CONFIG_INCLUDE_LPC210X_SPI_SLAVE)
  spiInit(false);
#endif

  // initialize SPI backplane IRQ driver
  spibpirqInit();

  /* Initialize I2C */
 
#ifdef CONFIG_INCLUDE_LPC210X_I2C
  i2cmasterInit(0x10);
#endif

  // lastly enable interrupts
  IRQ_ENABLE();

#ifdef CONFIG_INCLUDE_I2C_PORT
  i2cportInit();

  // configure port 0 as all inputs
  i2cportDirConfig(0, ((unsigned short)0xffff));

  // initialize port 1 to all zeroes
  i2cportAssign(1, ((unsigned short)0x000f));

  // configure port 1 as all outputs
  i2cportDirConfig(1, ((unsigned short)0x0000));

#endif /* CONFIG_INCLUDE_I2C_PORT */

#ifdef CONFIG_INCLUDE_I2C_SWITCH
  if (i2cswitchInit(0xe0) == 0) {
    //i2cswitchDisable();
    }
#endif /* CONFIG_INCLUDE_I2C_SWITCH */

#ifdef CONFIG_INCLUDE_EEPROM_24LC256
  if (ee24lc256Init((unsigned char)8) == 0) {
    nvstoreInit();
    }
  else {
    sprintf(buffer, "ee24lc256 not found\r\n");
    uart0Puts(buffer);
    }
#endif

#ifdef CONFIG_INCLUDE_ZARLINK
  // initialize the Port Manager
  portmgrInit();
#endif

#ifdef CONFIG_INCLUDE_I2C_SFP
  sfpInit();
#ifdef CONFIG_INCLUDE_I2C_SFPDIAG
  sfpdiagInit();
#endif
#endif

  eventqDispatch();

#ifdef CONFIG_INCLUDE_ZARLINK

  zarlinkStatus = zarlinkInit();

  if (zarlinkStatus == 0) {
    // low level stuff
    zarlinkMiiInit();
    zarlinkRmonInit();
    zarlinkUtilInit();
    zarlinkMirrorInit();

    // Initialize event handlers
    zarlinkEventInit(true);
    zarlinkRxFrameInit();
    zarlinkCtrl1Init();
    zarlinkCtrl2Init();
    zarlinkResetInit();
    zarlinkLhbInit();
    zarlinkCpuMacInit();

    // enable link change and stat counter overflow interrupts on all ports
    zarlinkEventMaskPorts(0x44);

    // unmask events to handle
    zarlinkEventUnmaskIrq(INT_ALL);

    eventqDispatch();
    }
  else {
    // turn on the red light
    boardLockBusyLed(true);

    // minimal switch debug functionality
    zarlinkUtilMinInit();
    }

  zarlinkInitDone();
  eventqDispatch();

  if (zarlinkStatus == 0) {
    // enable all copper ports
    portmgrPortEnableCopper();
    eventqDispatch();

    // enable fiber ports that have been configured as 'up'
    portmgrPortEnableFiberConfig();
    eventqDispatch();

    // power down PHYs on SFP sites with no modules in them
    portmgrPhyPowerSave();

#ifdef CONFIG_INCLUDE_TRUNKING
    // initialize trunking 
    zarlinkTrunkInit();
#endif

    eventqDispatch();
    }

  // initialize the stack
  uip_init();
  uip_arp_init();

  // set a default mac address for the CPU port
  zarlinkCpuMacAddrSetUip();
  ipInit();

  eventqDispatch();

  wakeapp_init();
  httpd_init();

#endif /* CONFIG_INCLUDE_ZARLINK */

#ifdef CONFIG_INCLUDE_NVSTORE
  // Initialize 'board info' commands
  bdinfoInit();
  eventqDispatch();
#endif /* CONFIG_INCLUDE_NVSTORE */


#ifdef CONFIG_INCLUDE_LPC210X_WATCHDOG
  watchdogInit();
#endif

  metadataInit();

  arptimer = 0;
  start = timer0GetTicks();

  while(1) {

    // Give the event dispatcher a thread of execution
    eventqDispatch();

#ifdef CONFIG_INCLUDE_ZARLINK
      /* The poll function returned 0, so no packet was
	 received. Instead we check if there is time that we do the
	 periodic processing. */

      current = timer0GetTicks();
      
       /* first condition addresses counter wrap-around */
       if((start < current) && ((current - start) >= 5)) {
	start = current;
	for(i = 0; i < UIP_CONNS; i++) {
	  uip_periodic(i);
	  /* If the above function invocation resulted in data that
	     should be sent out on the network, the global variable
	     uip_len is set to a value > 0. */
	  if(uip_len > 0) {
	    uip_arp_out();
	    zarlinkEthFrameWrite();
	  }
	}

#if UIP_UDP
	for(i = 0; i < UIP_UDP_CONNS; i++) {
	  uip_udp_periodic(i);
	  /* If the above function invocation resulted in data that
	     should be sent out on the network, the global variable
	     uip_len is set to a value > 0. */
	  if(uip_len > 0) {
	    uip_arp_out();
	    zarlinkEthFrameWrite();
	  }
	}
#endif /* UIP_UDP */
      
	/* Call the ARP timer function every 10 seconds. */
	if(++arptimer == 20) {	
	  uip_arp_timer();
	  arptimer = 0;
	}
      } // timer
#endif

  } // while(1)

} // main
