//-----------------------------------------------------------------------------
// Author        : Larry Ruckman  <ruckman@slac.stanford.edu>
// Created       : 01/28/2015
// Project       :  
//-----------------------------------------------------------------------------
// Description :
// Reloads the firmware via ETH
//-----------------------------------------------------------------------------
// This file is part of 'Amc Carrier Test Software'.
// It is subject to the license terms in the LICENSE.txt file found in the 
// top-level directory of this distribution and at: 
//    https://confluence.slac.stanford.edu/display/ppareg/LICENSE.html. 
// No part of 'Amc Carrier Test Software', including this file, 
// may be copied, modified, propagated, or distributed except according to 
// the terms contained in the LICENSE.txt file.
// Proprietary and confidential to SLAC.
//-----------------------------------------------------------------------------
// Modification history :
// 01/28/2015: created
//----------------------------------------------------------------------------
#define __STDC_FORMAT_MACROS
#include <inttypes.h>

#include <iomanip>
#include <fstream>
#include <iostream>

#include <getopt.h>

#include <PromApiUser.h>
#include <AxiMicronN25Q.h>
#include <McsFileReader.h>

#include <AxiVersion.h>

#include <cpsw_api_builder.h>
#include <cpsw_api_user.h>

#define VERSION_BASE_ADDR  0x00000000
#define BOOT_MEM_BASE_ADDR 0x02000000

/* defined by makefile rules */
#include <FirmwareLoader_git_version_string.h>

static Hub buildHier(const char *ip_addr, uint32_t prom_base, uint32_t vers_base)
{
NetIODev      commDev =      INetIODev::create( "udp",  ip_addr);
MMIODev       mmio    =       IMMIODev::create( "AxiTop", 0x80000000, LE );
AxiMicronN25Q prom    = IAxiMicronN25Q::create( "prom" );
MMIODev       vers    =   IAxiVersionB::create( "vers" );

	ProtoStackBuilder bldr = IProtoStackBuilder::create();
	bldr->setSRPVersion(IProtoStackBuilder::SRP_UDP_V2);
	bldr->setUdpPort(8192);

	commDev->addAtAddress( mmio, bldr );

	mmio->addAtAddress( vers, vers_base );
	mmio->addAtAddress( prom, prom_base );

	return commDev;
}

using namespace std;

static void usage(const char *name) {
      fprintf(stderr, "usage: %s [-h] [-Y YAML_file]  [-a ip_address] [-ewv] [-r]  [-P prom_base] [-V vers_base] [-FW] MCS_FILE_PATH | -H | -R | -I\n", name);
      fprintf(stderr, "  -h:           This message\n");
	  fprintf(stderr, "  -Y YAML_file: Load hierarchy defined in <YAML_file>; you can still tweak -a, -P, -V\n");
	  fprintf(stderr, "  -D YAML_path: Path to directory where YAML include files are stored\n");
	  fprintf(stderr, "\n");
      fprintf(stderr, "  -a ip_addr:   IP address of target\n");
      fprintf(stderr, "  -P prom_base: base-address of PROM programmer\n");
      fprintf(stderr, "  -V vers_base: base-address of AXI Version\n");
      fprintf(stderr, "  -F          : FSBL mode          (note: -W AND -F must be given to have any effect)\n");
      fprintf(stderr, "  -W          : Write-protect FSBL (note: -W AND -F must be given to have any effect)\n");
      fprintf(stderr, "  -e          : erase only\n");                          
      fprintf(stderr, "  -w          : write only\n");                          
      fprintf(stderr, "  -v          : verify only\n");
      fprintf(stderr, "                NOTE if any of 'e','w','v' are  given then only\n");
      fprintf(stderr, "                     the desired operation(s) are performed.\n");
      fprintf(stderr, "                     If 'w' is not among the desired steps then\n");
      fprintf(stderr, "                     the 'fpgaReload' step is omitted\n");
      fprintf(stderr, "  -r          : omit 'FpgaReload' after burning\n");
      fprintf(stderr, "\n");
      fprintf(stderr, "                MCS_FILE_PATH: File path to the .mcs file to be loaded\n");
      fprintf(stderr, "\n");
      fprintf(stderr, "  Utility Commands (no programming is performed and no file required):\n");
      fprintf(stderr, "  -H          : issue 'FpgaReloadHalt' command (only -- no programming performed)\n");
      fprintf(stderr, "  -R          : issue 'FpgaReload'     command (only -- no programming performed)\n");
      fprintf(stderr, "  -I          : just print 'FpgaVersion' and build string (no programming performed)\n");
      fprintf(stderr,"\n                GIT Version string (of programmer): '%s'\n", FIRMWARELOADER_GIT_VERSION_STRING);
}

//////////////////////////////////////////////////////////////////////////////////////////////
// Note: 
// Status register bits can be read from or written to using READ STATUS REGISTER or
// WRITE STATUS REGISTER commands, respectively. When the status register enable/
// disable bit (bit 7) is set to 1 and W# is driven LOW, the status register non-volatile bits
// become read-only and the WRITE STATUS REGISTER operation will not execute. The
// only way to exit this hardware-protected mode is to drive W# HIGH.
//////////////////////////////////////////////////////////////////////////////////////////////
// BIT7       = Use WP pin          = '1'    = enabled
// BIT5       = Top/bottom          = '1'    = bottom
// BIT[6,4:2] = BP[3:0]             = "1010" = Sectors 511:0 protected
// BIT1       = Write enable latch  = '0'    = Don't Care (Volatile)
// BIT0       = Write in progress   = '0'    = Don't Care (Volatile)
#define STATUS_REG_CONFIG     0xE8
//////////////////////////////////////////////////////////////////////////////////////////////
//

#define STEP_ERASE (1<<0)
#define STEP_WRITE (1<<1)
#define STEP_VERIF (1<<2)

static bool
setPromStatusReg(EEProm prom, uint8_t val)
{
uint8_t rbval;
	prom->setPromStatusReg( val );
	if ( (rbval = prom->getPromStatusReg()) != val ) {
		fprintf(stderr,"Error writing EEPROM Status Register -- value won't stick\n");
		fprintf(stderr,"  -> wrote 0%02" PRIx8 ", read back 0%02" PRIx8 "\n", val, rbval);
		return false;
	}
	return true;	
}

#define CMD_HALT (1<<0)
#define CMD_RELD (1<<1)
#define CMD_VERS (1<<2)

class Fixup : public IYamlFixup {
public:
	static const unsigned IP = 1<<0;
	static const unsigned PB = 1<<1;
	static const unsigned VB = 1<<2;
private:
	unsigned    mask_;
	const char *ipAddr_;
	uint32_t    promBase_;
    uint32_t    versBase_;

public:
	Fixup(unsigned mask, const char *ipAddr, uint32_t promBase, uint32_t versBase)
	: mask_(mask),
	  ipAddr_(ipAddr),
	  promBase_(promBase),
	  versBase_(versBase)
	{
	}

	virtual void operator()(YAML::Node &root, YAML::Node &top)
	{
		if ( (mask_ & IP) )
			root["ipAddr"] = ipAddr_;
		if ( (mask_ & PB) )
			root["children"]["AxiTop"]["children"]["prom"]["at"]["offset"] = promBase_;
		if ( (mask_ & VB) )
			root["children"]["AxiTop"]["children"]["vers"]["at"]["offset"] = versBase_;
	}
};


int main (int argc, char **argv) {
	string          ipAddr = "192.168.2.10";   
	string          pathToFile;
	uint32_t        prom_base = BOOT_MEM_BASE_ADDR;
	uint32_t        vers_base = VERSION_BASE_ADDR;

	int             opt;
	int             rval      = 1;
	uint32_t        *i_p;
	int             wp        = 0;
    unsigned        steps     = 0;
	int             cmd       = 0;
	int             norld     = 1;
	const char *    yamlFile  = 0;
	unsigned        mask      = 0;
	const char *    yamlPath  = 0;

	while ( (opt = getopt(argc, argv, "hewva:P:V:FWHrRIY:D:")) > 0 ) {

		i_p = 0;

		switch ( opt ) {

			case 'h':  rval = 0;
			default:
					   usage( argv[0] );
					   return rval;

			case 'e':  steps |= STEP_ERASE;
					   break;

			case 'w':  steps |= STEP_WRITE;
					   break;

			case 'v':  steps |= STEP_VERIF;
					   break;

			case 'a':  ipAddr = optarg;
					   mask |= Fixup::IP;
					   break;

			case 'P':  i_p    = &prom_base;
					   mask |= Fixup::PB;
					   break;

			case 'V':  i_p    = &vers_base;
					   mask |= Fixup::VB;
					   break;

			case 'F':  wp++;
					   break;

			case 'W':  wp++;
					   break;

			case 'H':  cmd |= CMD_HALT;
					   break;

			case 'R':  cmd |= CMD_RELD;
					   norld = 0;
					   break;

			case 'I':  cmd |= CMD_VERS;
					   break;

			case 'r':  norld = 1;
					   break;

			case 'Y':  yamlFile = optarg;
					   break;

			case 'D':  yamlPath = optarg;
					   break;

		}

		if ( i_p && 1 != sscanf(optarg, "%" SCNi32, i_p) ) {
			fprintf(stderr,"Unable to scan arg to option '-%c'\n", opt);
			return 1;
		}
	}

	// Check for .mcs file path
	if ( argc > optind ){ 
		pathToFile = argv[optind];
	}else if ( ! cmd ) { 
		usage( argv[0] );
		return(1);
	}

	if ( ! steps ) {
		steps = STEP_ERASE | STEP_WRITE | STEP_VERIF;
	}

	if ( cmd )
		steps = 0;

	if ( steps && !(steps & STEP_WRITE) )
		norld = 1;

	try {
		Hub root;

		if ( ! yamlFile  ) {
			// LEGACY -- should use YAML now
			root = buildHier( ipAddr.c_str(), prom_base, vers_base );
		} else {
			Fixup fixup(mask, ipAddr.c_str(), prom_base, vers_base);
			root = IPath::loadYamlFile( yamlFile, "FirmwareLoader", yamlPath, &fixup )->origin();
		}

		Path    prefix         = root->findByName("AxiTop");
		Path    vprefix        = prefix->findByName("vers");

		EEProm  prom           =  IEEProm::create( prefix->findByName( "prom" ) );
		ScalVal fpgaReload     = IScalVal::create( vprefix->findByName( "FpgaReload" ) );
		ScalVal fpgaReloadHalt = IScalVal::create( vprefix->findByName( "FpgaReloadHalt" ) );
		ScalVal_RO fpgaVersion = IScalVal_RO::create( vprefix->findByName( "FpgaVersion" ) );
		ScalVal_RO buildStamp  = IScalVal_RO::create( vprefix->findByName( "BuildStamp" ) );

		if ( cmd ) {
			if ( ( cmd & CMD_VERS ) ) {
				unsigned sz;
				uint32_t vers;
				uint8_t buf[(sz = buildStamp->getNelms()) + 1];
				buildStamp->getVal( buf, sz );
				fpgaVersion->getVal( &vers );
				buf[sz] = 0;
				printf("Build Stamp: %s\n", buf);
				printf("FpgaVersion: 0x%08" PRIx32 "\n", vers);
				
			}
			if ( pathToFile.length() == 0 ) {
				if ( ( cmd & CMD_HALT ) )
					fpgaReloadHalt->setVal( 1 );
				if ( ( cmd & CMD_RELD ) )
					fpgaReload->setVal( 1 );
				sleep( 1 );
				return 0;
			}
		}

		try {
			// Set the path to the FPGA's .mcs file
			prom->setFileReader( McsFileReader::create( pathToFile ) ); 
		} catch ( IFileReader::InvalidPathError ) {
			// setFileReader already prints a message
			return(1);
		}

		// Setup for 32-bit address mode
		prom->setAddr32BitMode(true);

		if ( wp > 1 && (steps & (STEP_ERASE | STEP_WRITE)) ) {
			// Reset the FPGA's non-volatile Status Register
			if (!setPromStatusReg(prom, 0))
				return(1);
		}


		if ( (steps & STEP_ERASE) ) {
			// Erase the FPGA's PROM
			prom->eraseProm();  
		}

		if ( (steps & STEP_WRITE) ) {

			// Write the .mcs file to the FPGA's PROM
			if(!prom->writeProm()){
				cout << "Error in AxiMicronN25Q->writeBootProm() function" << endl;
				if ( wp > 1 ) {
					setPromStatusReg(prom, STATUS_REG_CONFIG);
				}
				return(1);      
			}


			if ( wp > 1 ) {
				// Set the FPGA's non-volatile Status Register
				if (!setPromStatusReg(prom, STATUS_REG_CONFIG))
					return(1);
			}

		}

		if ( ( steps & STEP_VERIF ) ) {
			// Compare the .mcs file with the FPGA's PROM
			if(!prom->verifyProm()) {
				cout << "Error in AxiMicronN25Q->verifyBootProm() function" << endl;
				return(1);      
			}
		}

		if ( ! norld ) {
			// Reboot the FPGA
			fpgaReload->setVal( 1 );
			// Display Reminder
			prom->rebootReminder(false); 
		}

	} catch ( CPSWError& e ) {

		fprintf(stderr,"CPSW Error: %s\n", e.getInfo().c_str());
		throw;

	}

	sleep(1);

	return(0);
}
