/****************************************************************************/
/*	Copyright (c) 2009, Bendor Research Pty. Ltd. All rights reserved.		*/
/*	This software is released under the GNU General Public License, version	*/
/*	3, with the exemptions that 1) the header files installed by this 	 	*/
/*	package can be included in closed-source code and 2) the libraries		*/
/*	built by this package can be statically linked into a closed-source 	*/
/*	binary. See the file LICENSE for the details.							*/
/*	This software is provided "AS IS", without any warranty or guarantee.	*/
/*	By using this software you acknowledge that neither Bendor Research nor	*/
/*	any of its personnel or affiliates can be held liable for any damages,	*/
/*	including, but not limited to, loss of business, loss of money, loss of */
/*	life, loss of reputation, arising out of this software being faulty or	*/
/*	not performing as advertised, even if Bendor Research was aware of such	*/
/*	faults in the software. You use this software at your own risk, any and */
/*	all liabilities that arise out of your using this software are strictly	*/
/*	yours.																	*/
/****************************************************************************/

#include "lpctcl.h"

/****************************************************************************/
/*							Local definitions								*/
/****************************************************************************/

/*
*	Sector state flags
*/

#define	SECT_NOT_BLANK	1			// The FLASH sector is not blank
#define	SECT_NEED_READ	2			// The buffer contains VOID bytes
#define	SECT_USER_DATA	4			// The buffer contains DIRTY ot ERASED bytes

/****************************************************************************/
/*							Local prototypes								*/
/****************************************************************************/

static void		FlashBufferWriteByte( LPCTCL *lib, unsigned addr, int data );
static int		FlashSectorStatus( LPCTCL *lib, int sect, int force );
static int		FlashSectorUpdate( LPCTCL *lib, int sect, int force );
static int		FlashSectorErased( LPCTCL *lib, int sect );
static int		FlashBufferStatus( LPCTCL *lib, int sbeg, int send );
static int		FlashSectorAddress( const LPC_CHIP *chip, int sect );

/****************************************************************************/
/*							File I/O commands								*/
/****************************************************************************/

/*
*	Sets the hexfile format
*	~~~~~~~~~~~~~~~~~~~~~~~
*	It has one argument which must be "srec" or "ihex"
*/

CMDFUNC( FileType )
{
	if ( objc != 2 ) {

		Tcl_AppendResult( tcl, "The ",
						  Tcl_GetStringFromObj( objv[ 0 ], NULL ),
						  " command needs one argument", NULL );
		return( TCL_ERROR );
	}

	if ( ! strcmp( "srec", Tcl_GetStringFromObj( objv[ 1 ], NULL ) ) ) {

		lib->hex_type = "srec";
		return( TCL_OK );
	}

	if ( ! strcmp( "ihex", Tcl_GetStringFromObj( objv[ 1 ], NULL ) ) ) {

		lib->hex_type = "ihex";
		return( TCL_OK );
	}

	Tcl_AppendResult( tcl, "The file type must be \"srec\" or \"ihex\"", NULL );
	return( TCL_ERROR );
}

/*
*	Loads a file into the FLASH buffer
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*	There's one argument, which is the name of the file to load.
*/

CMDFUNC( FileLoad )
{
LPC_IO			file;
int				code;
unsigned int	i;
unsigned char	buff[ 256 ];
const LPC_CHIP	*chip;

	// We must be connected to a chip so that we know the chip size

	if ( ! ( chip = LPC_TargetChip( lib->lpc ) ) ) {

		Lpctcl_Liberror( tcl, LPC_ERROR_CHIP );
		return( TCL_ERROR );
	}

	if ( objc != 2 ) {

		Tcl_AppendResult( tcl, "The ",
						  Tcl_GetStringFromObj( objv[ 0 ], NULL ),
						  " command needs one argument", NULL );
		return( TCL_ERROR );
	}

	code = LPC_FileOpen( lib->lpc, &file,
						 Tcl_GetStringFromObj( objv[ 1 ], NULL ),
						 lib->hex_type, 0 );

	if ( code < 0 ) {

		Lpctcl_Liberror( tcl, code );
		return( TCL_ERROR );
	}

	for ( ;; ) {

		file.data = &buff;
		file.rlen = 256;

		code = LPC_FileRead( &file );
		if ( code < 0 ) {

			Lpctcl_Liberror( tcl, code );
			return( TCL_ERROR );
		}

		if ( code == 0 ) {

			LPC_FileClose( &file );
			return( TCL_OK );
		}

		for ( i = 0 ; i < file.rlen ; i++ ) {

			if ( file.addr >= chip->rom->rom_len * 1024 ) {

				Tcl_AppendResult( tcl, "Illegal address in hexfile", NULL );
				LPC_FileClose( &file );
				return( TCL_ERROR );
			}

			FlashBufferWriteByte( lib, file.addr++, buff[ i ] );
		}
	}
}

/*
*	Saves the FLASH buffer
*	~~~~~~~~~~~~~~~~~~~~~~
*	There are two arguments.
*	The first is the is the name of the file.
*	The second is an integer, indicating whether continuous runs of
*	256 bytes of FFs should be trimmed or not.
*/

CMDFUNC( FileSave )
{
const LPC_CHIP	*chip;
LPC_IO			file;
int				code, trim;
unsigned int	addr, cntr, test;

	// We must be connected to a chip so that we know the chip size

	if ( ! ( chip = LPC_TargetChip( lib->lpc ) ) ) {

		Lpctcl_Liberror( tcl, LPC_ERROR_CHIP );
		return( TCL_ERROR );
	}

	if ( objc != 3 ) {

		Tcl_AppendResult( tcl, "The ",
						  Tcl_GetStringFromObj( objv[ 0 ], NULL ),
						  " command needs two arguments", NULL );
		return( TCL_ERROR );
	}

	if ( Tcl_GetIntFromObj( tcl, objv[ 2 ], &trim ) != TCL_OK ) {

		Tcl_AppendResult( tcl, "The ",
						  Tcl_GetStringFromObj( objv[ 0 ], NULL ),
						  " command's second argument must be an integer",
						  NULL );
		return( TCL_ERROR );
	}

	file.addr = 0;
	file.rlen = 0;
	file.data = NULL;

	code = LPC_FileOpen( lib->lpc, &file,
						 Tcl_GetStringFromObj( objv[ 1 ], NULL ),
						 lib->hex_type, 1 );

	if ( code < 0 ) {

		Lpctcl_Liberror( tcl, code );
		return( TCL_ERROR );
	}

	// Load the FLASH content

	for ( addr = 0 ; addr < chip->rom->num_sec ; addr++ ) {

		FlashSectorStatus( lib, addr, 1 );
		FlashSectorUpdate( lib, addr, 1 );
	}

	// Now go through the buffer in 32 byte steps and write it out

	Lpctcl_Progress( lib, "Saving to file" );

	for ( addr = 0 ; addr < chip->rom->rom_len * 1024 ; addr += 32 ) {

		if ( trim ) {

			for ( test = 0xff, cntr = 0 ; cntr < 32 ; cntr++ )

				test &= lib->tgt_data[ addr + cntr ];

			if ( test == 0xff ) continue;
		}

		file.addr = addr;
		file.data = lib->tgt_data + addr;
		file.rlen = 32;
		file.line = 100;

		if ( ( code = LPC_FileWrite( &file ) ) < 0 ) {

			Lpctcl_Liberror( tcl, code );
			return( TCL_ERROR );
		}
	}

	file.addr = 0;
	file.rlen = 0;
	file.data = NULL;

	if ( ( code = LPC_FileClose( &file ) ) < 0 ) {

		Lpctcl_Liberror( tcl, code );
		return( TCL_ERROR );
	}

	Lpctcl_Progress( lib, "Done" );
	return( TCL_OK );
}

/*
*	Burns the FLASH buffer into the target
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*	It has one parameter, a string.
*
*	keep		Keep all bytes in the target that have not been overwritten
*				by a previous load command or user setting command.
*
*	wipe		Erase the entire chip
*
*	norm		Erase all sectors that have been changed by a load command
*				but leave the rest intact. In the erased sectors the old
*				content is not retained.
*/

CMDFUNC( FileBurn )
{
const LPC_CHIP	*chip;
int				code, stat, keep, wipe;
unsigned		sect, addr, slen;
unsigned char	flag[ 256 ];
const char		*opts;
char			temp[ 80 ];

	if ( objc != 2 ) {

		Tcl_AppendResult( tcl, "The ",
						  Tcl_GetStringFromObj( objv[ 0 ], NULL ),
						  " command needs one argument", NULL );
		return( TCL_ERROR );
	}

	// We must be connected to a chip

	if ( ! ( chip = LPC_TargetChip( lib->lpc ) ) ) {

		Lpctcl_Liberror( tcl, LPC_ERROR_CHIP );
		return( TCL_ERROR );
	}

	// Process the argument flags

	wipe = 0;
	keep = 0;
	opts = Tcl_GetStringFromObj( objv[ 1 ], NULL );
	if ( ! strcmp( opts, "wipe" ) ) {

		wipe = 1;
	}
	else if ( ! strcmp( opts, "keep" ) ) {

		keep = 1;
	}
	else if ( ! strcmp( opts, "norm" ) ) {

		;
	}
	else {

		Tcl_AppendResult( tcl, "The ",
						  Tcl_GetStringFromObj( objv[ 0 ], NULL ),
						  " command has no ",
						  Tcl_GetStringFromObj( objv[ 1 ], NULL ),
						  " option", NULL );
		return( TCL_ERROR );
	}

	Lpctcl_Progress( lib, "" );

	// Clear the sector status flags

	for ( sect = 0 ; sect < chip->rom->num_sec ; sect++ )

		flag[ sect ] = 0;

	// Work out what to do with the sectors

	for ( sect = 0 ; sect < chip->rom->num_sec ; sect++ ) {

		if ( wipe ) {

			// We will wipe the sector, mark it erased. This will
			// save a blank check that would be overrode anyway due
			// to the wipe option.

			FlashSectorErased( lib, sect );
		}

		stat = FlashSectorStatus( lib, sect, 0 );

		if ( stat < 0 ) {

			Lpctcl_Liberror( tcl, stat );
			return( TCL_ERROR );
		}

		if ( ! ( stat & SECT_USER_DATA ) ) {

			// There's no user data in this sector, nothing to burn

			continue;
		}

		if ( keep && ( stat & SECT_NEED_READ ) ) {

			// We have to read this sector

			stat = FlashSectorUpdate( lib, sect, 0 );

			if ( stat < 0 ) {

				Lpctcl_Liberror( tcl, stat );
				return( TCL_ERROR );
			}
		}

		// Now if the sector has to be written mark it for erase
		// and then mark it for write as well

		if ( stat & SECT_USER_DATA ) {

			if ( stat & SECT_NOT_BLANK ) flag[ sect ] |= SECT_NOT_BLANK;
			flag[ sect ] |= SECT_USER_DATA;
		}
	}

	// If we were told to erase the whole chip, mark all sectors
	// to be erased

	for ( sect = 0 ; wipe && sect < chip->rom->num_sec ; sect++ )

		flag[ sect ] |= SECT_NOT_BLANK;

	// Now erase the sectors. If we can, we erase them in a block,
	// because that is faster. We use 'addr' and 'slen' as counters.

	for ( addr = 0 ;; ) {

		for ( ; addr < chip->rom->num_sec ; addr++ )

			if ( flag[ addr ] & SECT_NOT_BLANK )

				break;

		if ( addr == chip->rom->num_sec ) break;

		for ( slen = addr ; slen < chip->rom->num_sec ; slen++ )

			if ( ! ( flag[ slen ] & SECT_NOT_BLANK ) )

				break;

		slen--;

		sprintf( temp, "Erasing sectors %d to %d", addr, slen );
		Lpctcl_Progress( lib, temp );

		if ( ( code = LPC_TargetErase( lib->lpc, addr, slen ) ) < 0 ) {

			Lpctcl_Liberror( tcl, code );
			return( TCL_ERROR );
		}

		// Now mark them as erased so that our internal buffer is
		// now correct. That will also advance 'i'

		while ( addr <= slen ) FlashSectorErased( lib, addr++ );
	}

	// Now we go through the sectors and burn them

	for ( wipe = sect = 0 ; sect < chip->rom->num_sec ; sect++ )

		if ( flag[ sect ] & SECT_USER_DATA ) wipe++;

	for ( sect = 0 ; sect < chip->rom->num_sec ; sect++ ) {

		if ( flag[ sect ] & SECT_USER_DATA ) {

			// Burn the sector in 1K blocks.

			addr = FlashSectorAddress( chip, sect );
			slen = chip->rom->sec_siz[ sect ];

			for ( ; slen-- ; addr += 1024 ) {

				sprintf( temp, "Burning at %06X", addr );
				Lpctcl_Progress( lib, temp );

				code = LPC_TargetFlash( lib->lpc, addr,
									    lib->tgt_data + addr, 1024 );

				if ( code < 0 ) {

					Lpctcl_Liberror( tcl, code );
					return( TCL_ERROR );
				}
			}

			// Update the image - all bytes should be marked as CLEAN

			addr = FlashSectorAddress( chip, sect );
			slen = chip->rom->sec_siz[ sect ] * 1024;
			while ( slen-- ) lib->tgt_flag[ addr++ ] = STAT_CLEAN;
		}
	}

	Lpctcl_Progress( lib, "Done" );

	// All done

	return( TCL_OK );
}

/*
*	Invalidates the buffer
*	~~~~~~~~~~~~~~~~~~~~~~
*/

CMDFUNC( FileInvalid )
{
	(void) objc;
	(void) objv;
	(void) tcl;

	memset( lib->tgt_data, 0xff, sizeof( lib->tgt_data ) );
	memset( lib->tgt_flag, STAT_VOID, sizeof( lib->tgt_data ) );
	return( TCL_OK );
}

/*
*	Calculates the vector table checksum
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*	If none of the first 32 bytes of the buffer is not void, then the
*	LPC2xxx vector table checksum is calculated and written back into
*	the buffer. Returns 1 if the vector table was set and 0 if the
*	buffer was left unmodified.
*/

CMDFUNC( FileVect )
{
unsigned int	addr;
unsigned char	buff[ 32 ];

	(void) objc;
	(void) objv;

	// We must be connected to a chip

	if ( ! LPC_TargetChip( lib->lpc ) ) {

		Lpctcl_Liberror( tcl, LPC_ERROR_CHIP );
		return( TCL_ERROR );
	}

	// We have to calculate the vector table checksum.
	// However, we only do that if the vector table was
	// actually set by some user action

	for ( addr = 0 ; addr < 32 ; addr++ ) {

		if ( lib->tgt_flag[ addr ] == STAT_VOID ) {

			Tcl_AppendResult( tcl, "0", NULL );
			return( TCL_OK );
		}
	}

	// Get the first 8 words of the data into a temporary
	// buffer, do the calculation there and then write the
	// result back.

	memcpy( buff, lib->tgt_data, 8 * 4 );
	LPC_TargetVectab( lib->lpc, buff );

	for ( addr = 20 ; addr < 24 ; addr++ ) {

	   FlashBufferWriteByte( lib, addr, buff[ addr ] );
	}

	Tcl_AppendResult( tcl, "1", NULL );
	return( TCL_OK );
}

/****************************************************************************/
/*							Local functions									*/
/****************************************************************************/

/*
*	Writes a single byte into the FLASH buffer
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

static void		FlashBufferWriteByte( LPCTCL *lib, unsigned addr, int data )
{
	switch ( lib->tgt_flag[ addr ] ) {

		case STAT_VOID:
		case STAT_DIRTY:

			lib->tgt_flag[ addr ] = STAT_DIRTY;
			break;

		case STAT_CLEAN:

			if ( lib->tgt_data[ addr ] == data ) break;

			if ( lib->tgt_data[ addr ] == 0xff )

				lib->tgt_flag[ addr ] = STAT_ERASED;
			else
				lib->tgt_flag[ addr ] = STAT_DIRTY;

			break;

		case STAT_ERASED:

			if ( data != 0xff ) break;
			lib->tgt_flag[ addr ] = STAT_CLEAN;
			break;
	}

	lib->tgt_data[ addr ] = data;
}

/*
*	Work out the sector status
*	~~~~~~~~~~~~~~~~~~~~~~~~~~
*	Tests the given sector. It either returns a negative error if talking
*	to the target failed or it returns a (positive) int, with the following
*	bitmap:
*
*	SECT_NOT_BLANK	The FLASH contains non-0xFF data somewhere
*	SECT_NEED_READ	There are unknown non-0xFF bytes in the FLASH
*	SECT_USER_DATA	The sector has user data (to be written)
*
*	If the 'force' parameter is !0, then the FLASH blankness will be
*	tested regardless of anything else.
*/

static int		FlashSectorStatus( LPCTCL *lib, int sect, int force )
{
int				stat, addr;
int				sbeg, send;
const LPC_CHIP	*chip;

	if ( ! ( chip = LPC_TargetChip( lib->lpc ) ) ) return( LPC_ERROR_CHIP );

	sbeg = FlashSectorAddress( chip, sect );
	send = sbeg + chip->rom->sec_siz[ sect ] * 1024;

	// Run through the sector to see if we have to test it for blankness

	for ( stat = force, addr = sbeg ; ! stat && addr < send ; addr++ ) {

		switch ( lib->tgt_flag[ addr ] ) {

			case STAT_DIRTY:

				stat = 1;
				break;
		}
	}

	if ( stat ) {

		// First, test the FLASH sector for blankness

		if ( ( stat = LPC_TargetBlank( lib->lpc, sect, sect ) ) < 0 )

			return( stat );

		if ( stat ) {

			// The sector is blank, so we modify our map.

			for ( addr = sbeg ; addr < send ; addr++ ) {

				switch ( lib->tgt_flag[ addr ] ) {

					case STAT_DIRTY:
					case STAT_ERASED:

						if ( lib->tgt_data[ addr ] != 0xff ) {

							lib->tgt_flag[ addr ] = STAT_ERASED;
							break;
						}

						// FALLTHROUGH!!!

					case STAT_VOID:
					case STAT_CLEAN:

						// All these bytes are known to be 0xFF in the FLASH.

						lib->tgt_flag[ addr ] = STAT_CLEAN;
						lib->tgt_data[ addr ] = 0xff;
						break;
				}
			}
		}
	}

	return( FlashBufferStatus( lib, sbeg, send ) );
}

/*
*	Updates the image of a sector in the buffer
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*	If the force parameter is 0, then only the currently void values will be
*	fetched from the FLASH. If it is !0, then any walue that is not clean will
*	be fetched.
*/

static int		FlashSectorUpdate( LPCTCL *lib, int sect, int force )
{
int				stat, addr;
int				sbeg, send;
int				cntr;
unsigned char	buff[ 256 ];
const LPC_CHIP	*chip;

	if ( ! ( chip = LPC_TargetChip( lib->lpc ) ) ) return( LPC_ERROR_CHIP );

	sbeg = FlashSectorAddress( chip, sect );
	send = sbeg + chip->rom->sec_siz[ sect ] * 1024;

	// We update the sector in 256 byte blocks

	for ( addr = sbeg ; addr < send ; addr += 256 ) {

		if ( force ) {

			// Map user modified values to either clean or void

			for ( stat = 0, cntr = 0 ; cntr < 256 ; cntr++ ) {

				if ( lib->tgt_flag[ addr + cntr ] == STAT_ERASED ) {

					lib->tgt_flag[ addr + cntr ] = STAT_CLEAN;
					lib->tgt_data[ addr + cntr ] = 0xff;
				}

				if ( lib->tgt_flag[ addr + cntr ] == STAT_DIRTY )

					lib->tgt_flag[ addr + cntr ] = STAT_VOID;
			}
		}

		for ( stat = 0, cntr = 0 ; cntr < 256 ; cntr++ ) {

			if ( lib->tgt_flag[ addr + cntr ] == STAT_VOID ) {

				stat = 1;
				break;
			}
		}

		if ( stat ) {

			sprintf( (char *) buff, "Reading %08X", addr );
			Lpctcl_Progress( lib, (char *) buff );
			stat = LPC_TargetMemRead( lib->lpc, addr, buff, 256 );

			if ( stat < 0 ) return( stat );

			for ( cntr = 0 ; cntr < 256 ; cntr++ ) {

				switch ( lib->tgt_flag[ addr + cntr ] ) {

					case STAT_VOID:

						lib->tgt_flag[ addr + cntr ] = STAT_CLEAN;
						lib->tgt_data[ addr + cntr ] = buff[ cntr ];
						break;

					case STAT_DIRTY:

						if ( lib->tgt_data[ addr + cntr ] == buff[ cntr ] )

							lib->tgt_flag[ addr + cntr ] = STAT_CLEAN;

						break;
				}
			}
		}
	}

	// Now work out the modified sector status

	return( FlashBufferStatus( lib, sbeg, send ) );
}

/*
*	After erasing it updates the sector's state flags
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

static int		FlashSectorErased( LPCTCL *lib, int sect )
{
int				sbeg, send, addr;
const LPC_CHIP	*chip;

	if ( ! ( chip = LPC_TargetChip( lib->lpc ) ) ) return( LPC_ERROR_CHIP );

	sbeg = FlashSectorAddress( chip, sect );
	send = sbeg + chip->rom->sec_siz[ sect ] * 1024;

	for ( addr = sbeg ; addr < send ; addr++ ) {

		switch ( lib->tgt_flag[ addr ] ) {

			case STAT_VOID:
			case STAT_CLEAN:

				lib->tgt_flag[ addr ] = STAT_CLEAN;
				lib->tgt_data[ addr ] = 0xff;
				break;

			case STAT_ERASED:
			case STAT_DIRTY:

				if ( lib->tgt_data[ addr ] == 0xff )

					lib->tgt_flag[ addr ] = STAT_CLEAN;
				else
					lib->tgt_flag[ addr ] = STAT_ERASED;

				break;
		}
	}

	// Now work out the modified sector status

	return( FlashBufferStatus( lib, sbeg, send ) );
}

/*
*	Work out the status of a region
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

static int		FlashBufferStatus( LPCTCL *lib, int sbeg, int send )
{
int		stat, addr;

	for ( stat = 0, addr = sbeg ; addr < send ; addr++ ) {

		switch ( lib->tgt_flag[ addr ] ) {

			case STAT_VOID:

				// The sector contains an unknown FLASH byte.
				// We need to read it to retain it.

				stat |= SECT_NEED_READ;
				break;

			case STAT_CLEAN:

				// We know the byte in the FLASH. If it is not 0xFF, then
				// the sector is not blank.

				if ( lib->tgt_data[ addr ] != 0xff ) stat |= SECT_NOT_BLANK;
				break;

			case STAT_DIRTY:

				// It has a byte that we have to overwrite but we don't
				// know if the FLASH is empty at that location or not.
				// We have to assume that it is not blank.

				stat |= SECT_NOT_BLANK | SECT_USER_DATA;
				break;

			case STAT_ERASED:

				// We have data to write to the FLASH

				stat |= SECT_USER_DATA;
				break;
		}
	}

	return( stat );
}

/*
*	Calculates the start address of a sector
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*	It does it in a pretty inefficient way...
*/

static int		FlashSectorAddress( const LPC_CHIP *chip, int sect )
{
int		addr, cntr;

	for ( addr = cntr = 0 ; cntr < sect ; cntr++ )

		addr += chip->rom->sec_siz[ cntr ] * 1024;

	return( addr );
}

