/*******************************************************************************
 * (c) Copyright 2015 Microsemi Corporation.  All rights reserved.
 *
 *
 *
 * SVN $Revision: $
 * SVN $Date: $
 */

#include <api_oam_sdk/isr/inc/cfm_isr.h>
#include <api_oam_sdk/link_trace/src/link_trace_types.h>
#include <api_oam_sdk/loopback/src/lb_support.h>
#include <api_oam_sdk/mep/src/mep_types.h>
#include <api_oam_sdk/metroam_hal/inc/mr_hal.h>
#include <api_oam_sdk/notifications/inc/egl_notifications.h>
#include <api_oam_sdk/policer_mef10p2/src/policer_mef10p2_types.h>
#include <api_oam_sdk/service_activation/src/service_activation_types.h>
#include <api_oam_sdk/service_interface/src/svc_iface_types.h>
#include <api_oam_sdk/system/inc/system.h>
#include <api_oam_sdk/table_access/inc/table_access.h>
#include <api_oam_sdk/table_access/inc/table_access_constants.h>
#include <api_oam_sdk/table_config/inc/table_config.h>
#include <api_oam_sdk/table_mem_alloc/inc/table_mem_alloc.h>
#include <syslog.h>

#include "egl_types.h"
#include "egl_assert.h"
#include "egl_log.h"

// VR Enable port 0,1 for STAG Packets
#define DEV_CFG_REG_TAG_PORT0_STAG_MASK           0x00800000
#define DEV_CFG_REG_TAG_PORT1_STAG_MASK           0x00200000

mrDeviceInfo_t		mrDeviceInfo;		//!< Global storage for Device specific params such as MAC addresses
UART_instance_t g_uart;

extern const char * g_mrhalinit_msg;

static uint16_t	log_handle		= 0;	// Handle used to log messages for this module
static bool		firstHwInit		= true;	// Used to determine if this is the first HW initialisation

static void resetHwTables(void)
{
	int	k;
	uint32_t			data;
	rMepSMEntry_t		rMepSMEntry;
	xConnSMEntry_t		xConnSMEntry;
	rMepErrSMEntry_t	rMepErrSMEntry;

	// Reset the contents of the VLan tables to default
	data = (PORT_ASSIGNED_EVC << VLAN_INFO_RAM_EVC_IDX_OFFSET) & VLAN_INFO_RAM_EVC_IDX_MASK;
	for (k=0; k < MR_MAX_VLANS; k++)
	{
		writeRawVlanPortTbl(k, data, wanPort);
		writeRawVlanPortTbl(k, data, lanPort);
	}

	rMepSMEntry.state 		= SM_IDLE;
	rMepSMEntry.goodCount	= 0;
	rMepSMEntry.enable 		= disable;
	rMepSMEntry.rdi			= CLR;
	rMepSMEntry.timer		= 0;

	xConnSMEntry.enable = disable;
	xConnSMEntry.state = IDLE;
	xConnSMEntry.timer = 0;

	rMepErrSMEntry.enable = disable;
	rMepErrSMEntry.state = IDLE;
	rMepErrSMEntry.timer = 0;

	for (k=0;k<MAX_NUM_MEPS;k++)
	{
		writeRMepSMRam(k, &rMepSMEntry);
		writeXConnSMRam(k, &xConnSMEntry);
		writeRMepErrSMRam(k, &rMepErrSMEntry);
	}
}

static void hwReset(void)
{
	uint32_t	data;
	#ifndef EAGLE_SW_TEST
		uint16_t	tOutCount = 100;
	#endif

	DO_LOG_DEBUG(log_handle, "Initiating a HW reset of OAM Core\n");

#ifdef FREERTOS
	// Before trying to delete objects gracefully turn off OAM Tx
	cfmTxEnable(false);
#endif

	// We must check if this it the first time HW has been initialised
	if (firstHwInit)
	{
		mdInit();						// Initialise the Maintenance Domain module.
		firstHwInit = false;
	}
	mdDeleteMdList();

#ifdef FREERTOS
#if 0 // Removed - we no longer use software to do hardware reset at startup. POR is adequate.
	// do a reset of the HW to clear any old values
	mrHalRegWrite(SW_RESET_CNTR_REG_ADDR, 1);
#endif
#endif
	// wait for the HW reset to be cleared (i.e. SW_RESET_CNTR_REG_ADDR = 0)...
	mrHalRegRead(SW_RESET_CNTR_REG_ADDR, &data);

#	ifndef EAGLE_SW_TEST  // Don't wait for HW if there is no HW!
		while ((data) && ((tOutCount--) > 0))
		{
			mrHalRegRead(SW_RESET_CNTR_REG_ADDR, &data);
			osSleepMS(1);					// play nice with other OS Threads
		}
#	endif

#ifdef FREERTOS
	// Restore the Carrier and System MAC addresses as soon as possible after reset
	writePortMacRam(mrPort0,mrDeviceInfo.wanMAC);
	writePortMacRam(mrPort1,mrDeviceInfo.lanMAC);
	writePortMacRam(mrMgntPort,mrDeviceInfo.mgntMAC);

	// Re-enable the captured packet interrupts after reset
	mrHalRegWrite(INT_MASK_REG_ADDR, CAPT_PKT_INT_MASK | SYS_CAPT_PKT_INT_MASK);
#endif

	// Set the pmInit bit to get HW to initialise the PM Stats RAM
	mrHalRegRead(DEV_CFG_REG_ADDR,&data);
	data |= DEV_CFG_REG_PM_INIT_DONE_MASK;
	data |= DEV_CFG_REG_TAG_PORT0_STAG_MASK;
	data |= DEV_CFG_REG_TAG_PORT1_STAG_MASK;
	mrHalRegWrite(DEV_CFG_REG_ADDR,data);

	// If all previous object were successfully cleared from
	// memory then re-initialise the RAMs and scavenger
	initMemAlloc();	 // clear all the scavenger lists

	resetHwTables();	// If SW is reset but HW is not power cycled then some HW tables need to be cleared
	DO_LOG_DEBUG(log_handle, "HW reset complete");
}

/*
 * @brief Initialise the OAM HW
 *
 *  This function is used to initialise registers and RAM tables before beginning any
 *  configuration. Given the External RAM depth it will initialise all the base address
 *  registers and some global parameters. It will also clear the valid bit in all entries
 *  in the VlanPort Table.
 * **IMPORTANT** This function must be called before apiInit.
 */
void apiHwInit(void)
{
	pVidEntry_t		pVidEntry;
	int				k;
	uint32_t		data,rdData;

	log_init();	// Initialises the logging system if it's not already initialised

	// Get a log handle if we don't already have one
	if (log_handle == 0)
	{
		log_handle = log_register("INIT");
	}

	//mrHalInit();					// Initialise the HAL module for basic IO read and write to HW

#ifdef FREERTOS
	// before starting initialisation make sure to disable interrupts
	mrHalRegWrite(INT_MASK_REG_ADDR,0x0);
#endif

	// Initialise these modules - mainly to configure logging
	tableConfigInit();
	tableAccessInit();

	DO_LOG_DEBUG(log_handle, "Resetting HW and removing any existing objects...");
	hwReset();	     // Remove any pre-existing objects and do a SW reset of HW
	DO_LOG_DEBUG(log_handle, "reset and object removal...Completed OK.");

	// Temporary fix to set Base Address registers to correct values - no harm if left here
	rdData = 0;
	mrHalRegRead(DATABASE_BA_REG_ADDR, &rdData);
	mrHalRegWrite(DATABASE_BA_REG_ADDR, MR_BA_RMEP_DB);
	mrHalRegRead(DATABASE_BA_REG_ADDR, &rdData);
	mrHalRegWrite(PM_STATS_BA_REG_ADDR, MR_BA_PM_STATS);
	mrHalRegRead(PM_STATS_BA_REG_ADDR, &rdData);
	DO_LOG_DEBUG(log_handle, "Initialising the Port Vlan Info Table...");
	// Initialise the Port VID Table
	pVidEntry.vlan = 0;
	pVidEntry.de = 0;
	pVidEntry.prio = 0;
	for (k=0;k<MR_MAX_PORTS;k++)
	{
		writePVidTable(k,&pVidEntry);
	}
	DO_LOG_DEBUG(log_handle, "Initialising the Port Vlan Table...Completed OK.");

	// Enable Tx of CFM traffic
	cfmTxEnable(true);

	// Disable all loopback channels by default
	disableLB(LB_RESET_REG_ALL_CHNL_RESET_MASK,MR_BOTH_TX_RX);
#if 0	// this code is moved to the ethernet device driver - access to this register from user space causes corruption of the register content!
	// Set the Interrupt mask bits for all the CCM interrupts and CPU packet capture
	// IMPORTANT:
	// Enable the captured packet interrupt bits for Carrier (WAN) and System (LAN) Ports
	// Both should be enabled by default. Even if the port is disabled for mgnt the
	// interrupt enable bits should be set to cater for Link Trace or other CFM pkts.
	mrHalRegRead(INT_MASK_REG_ADDR, &data);

#ifdef FREERTOS
	// If in FREERTOS then make sure all necessary interrupt Mask bits are set
	mrHalRegWrite(INT_MASK_REG_ADDR, (data | 0x00FE | CAPT_PKT_INT_MASK | SYS_CAPT_PKT_INT_MASK));
#else
	// If not in FREERTOS then do not modify any interrupt Mask bits other than the OAM bits
	mrHalRegWrite(INT_MASK_REG_ADDR, (data | 0x00FE));
#endif
#endif

	// Ensure the CFM interrupt 'report disable' bit cleared. This allows CFM interrupt be asserted.
	mrHalRegRead(RPT_EN_REG_ADDR,&data);
	mrHalRegWrite(RPT_EN_REG_ADDR,data & ~HW_INT_DISABLE);

#ifndef EAGLE_SW_TEST
		// Wait until the PM RAM init is completed by HW...
		mrHalRegRead(DEV_CFG_REG_ADDR,&data);
		while (data & DEV_CFG_REG_PM_INIT_DONE_MASK)
			mrHalRegRead(DEV_CFG_REG_ADDR,&data);
#endif
	DO_LOG_DEBUG(log_handle, "Initialising...Completed OK.");
}

  // This must exist to keep the linker happy but is never called.
  int _gettimeofday( struct timeval *tv, void *tzvp )
  {

    uint64_t time = 0;  // get uptime in nanoseconds
    tv->tv_sec = time / 1000000000;  // convert to seconds
    tv->tv_usec = ( time % 1000000000 ) / 1000;  // get remaining microseconds
    return 0;  // return non-zero for error
  } // end _gettimeofday()

void apiInit(void)
{
    //VR Skip history stats - NVM, uCLinux dependency
	//initRecStore();					// Initialsie the task used to save and recall pmStats from non-volatile memory.

    apiHwInit();					// Initialise the API dependent Hardware

   // mrHalInit();					// Initialise the HAL module for basic IO read and write to HW

	createNotificationQueue();		// Initialise the Notifications Queue so notifications can be sent to upper layers

	mdInit();						// Initialise the Maintenance Domain module.

	maInit();						// Initialise the Maintenance Association module.

	mepInit();						// Initialise the MEP Module and associated modules (rMep, FNG, loopback, CC, PM and link trace)


	#if 1
	//VR Skip - uCLinux dependency
	initCfmInterruptHandler();		// Initialise the CFM Interrupt module. This starts a OS thread for processing CFM Interrupts

	//VR Skip for now leading to random stack overflows
	iFaceInit();					// Initialise the Service UNI Modules

	saInit();						// Initialise the Service Activation Module (Y1564 & RFC2544)

	policerInit();
#endif
}

/* @brief This function should be called during application shutdown to ensure a graceful exit of the API
 *
 */
void apiShutdown(void)
{
#ifndef FREERTOS
	cfm_isr_shutdown();		// Close the interrupt netLink socket to the kernel
	linkTraceShutdown();	// Close the CFM netlink socket to the kernel
#endif
}
