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

/****************************************************************************/
/*							Serial commands									*/
/****************************************************************************/

/*
*	Connect to the target
*	~~~~~~~~~~~~~~~~~~~~~
*	There are no arguments, but the signal control (manual or modem) must
*	have been called already, the port and baud also must have been specified.
*/

CMDFUNC( TargetConnect )
{
int		code;

	if ( objc != 1 ) {

		Tcl_AppendResult( tcl, "The ",
						  Tcl_GetStringFromObj( objv[ 0 ], NULL ),
						  " command doesn't take arguments", NULL );
		return( TCL_ERROR );
	}

	Lpctcl_Progress( lib, "Connecting" );

	if ( ( code = LPC_TargetConnect( lib->lpc ) ) < 0 ) {

		Lpctcl_Progress( lib, "Failed" );
		Lpctcl_Liberror( tcl, code );
		return( TCL_ERROR );
	}

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

/*
*	Establishes a call-back for power control purposes
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

CMDFUNC( TargetPower )
{
Tcl_Obj	*obj;

	if ( objc > 2 ) {

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

	if ( lib->tgt_pwrf ) {

		Tcl_DecrRefCount( lib->tgt_pwrf  );
		lib->tgt_pwrf = NULL;
	}

	if ( objc == 2 ) {

		obj = Tcl_NewStringObj( Tcl_GetStringFromObj( objv[ 1 ], NULL ), -1 );
		Tcl_IncrRefCount( obj );
		lib->tgt_pwrf = obj;
		LPC_TargetPower( lib->lpc, Lpctcl_Power, lib );
	}
	else {

		LPC_TargetPower( lib->lpc, NULL, NULL );
	}

	return( TCL_OK );
}

/*
*	Returns a number, which is the target chip's name (afterthe LPC bit)
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*	The target must be connected.
*/

CMDFUNC( TargetType )
{
const LPC_CHIP	*chip;

	(void) objc;
	(void) objv;

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

		Tcl_AppendResult( tcl, "Not connected to the target", NULL );
		return( TCL_ERROR );
	}

	Tcl_AppendResult( tcl, chip->name + 3, NULL );
	return( TCL_OK );
}

/*
*	Returns a number, which is the target chip's FLASH in KB
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*	The target must be connected.
*/

CMDFUNC( TargetFlash )
{
const LPC_CHIP	*chip;

	(void) objc;
	(void) objv;

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

		Tcl_AppendResult( tcl, "Not connected to the target", NULL );
		return( TCL_ERROR );
	}

	Tcl_SetObjResult( tcl, Tcl_NewIntObj( chip->rom->rom_len ) );
	return( TCL_OK );
}

/*
*	Sets the crystal frequency of the target
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*	The frequency must be specified in MHz
*/

CMDFUNC( TargetXtal )
{
double	xtal;

	if ( objc > 2 ) {

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

	if ( objc > 1 ) {

		if ( Tcl_GetDoubleFromObj( tcl, objv[ 1 ], &xtal ) != TCL_OK ) {

			Tcl_AppendResult( tcl, "The ",
							  Tcl_GetStringFromObj( objv[ 0 ], NULL ),
							  " command expects a float-point argument", NULL );
			return( TCL_ERROR );
		}

		LPC_TargetCrystal( lib->lpc, (int)( xtal * 1e6 + 0.5 ) );
	}

	xtal = LPC_TargetCrystal( lib->lpc, 0 );
	Tcl_SetObjResult( tcl, Tcl_NewDoubleObj( xtal / 1e6 ) );
	return( TCL_OK );
}

/*
*	Sets up a progress callback
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~
*	It has two parameters, the first one is the TCL procedure to call
*	the second is an arbitrary parameter. The procedure will be called
*	with the second argument plus an integer, which is the progress,
*	in percent (an int between 0 and 100)
*/

CMDFUNC( TargetProgress )
{
	Tcl_Obj	*obj;

	if ( objc != 1 && objc != 3 ) {

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

	if ( lib->tgt_proc ) {

		Tcl_DecrRefCount( lib->tgt_proc );
		Tcl_DecrRefCount( lib->tgt_user );
		lib->tgt_user = NULL;
		lib->tgt_proc = NULL;
	}

	if ( objc == 1 ) return( TCL_OK );

	obj = Tcl_NewStringObj( Tcl_GetStringFromObj( objv[ 1 ], NULL ), -1 );

	Tcl_IncrRefCount( obj );
	Tcl_IncrRefCount( objv[ 2 ] );
	lib->tgt_proc = obj;
	lib->tgt_user = objv[ 2 ];
	return( TCL_OK );
}

/*
*	Resets the target and lets it go (not entering ISP)
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

CMDFUNC( TargetReset )
{
	(void) objc;
	(void) objv;

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

		Tcl_AppendResult( tcl, "Not connected to the target", NULL );
		return( TCL_ERROR );
	}

	LPC_TargetReset( lib->lpc, 1 );
	LPC_TargetIsp( lib->lpc, 0 );
	usleep( 100 );
	LPC_TargetReset( lib->lpc, 0 );
	return( TCL_OK );
}

/*
*	Send data to a target that's running application code
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

CMDFUNC( TargetSend )
{
int		len;
char	*str;
int		code;

	if ( objc != 2 ) {

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

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

		Tcl_AppendResult( tcl, "Not connected to the target", NULL );
		return( TCL_ERROR );
	}

	str = Tcl_GetStringFromObj( objv[ 1 ], &len );

	if ( ( code = LPC_SerialWrite( lib->lpc, str, len ) ) < 0 ) {

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

	return( TCL_OK );
}

/*
*	Receive data fromo a target that's running application code
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

CMDFUNC( TargetRecv )
{
char	str[ 1025 ];
int		code;

	(void) objc;
	(void) objv;

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

		Tcl_AppendResult( tcl, "Not connected to the target", NULL );
		return( TCL_ERROR );
	}

	if ( ( code = LPC_SerialRead( lib->lpc, str, 1024, 0 ) ) < 0 ) {

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

	Tcl_SetObjResult( tcl, Tcl_NewStringObj( str, code ) );
	return( TCL_OK );
}

/*
*	Progress reporting function
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

void	Lpctcl_Progress( LPCTCL *lib, const char *val )
{
Tcl_CmdInfo	info;
Tcl_Obj		*args[ 4 ];

	if ( ! lib->tgt_proc ) return;

	if ( Tcl_GetCommandInfo( lib->tcl,
							 Tcl_GetStringFromObj( lib->tgt_proc, NULL ),
							 &info ) == 0 ) {

		// Can't find the nominated callback - we can't do anything.

		return;
	}

	// We use the object API and leave it to TCL to do internal
	// conversions if need be. So, we have to create an object out
	// of the log string. Everything else is already an object.

	args[ 0 ] = lib->tgt_proc;
	args[ 1 ] = lib->tgt_user;
	args[ 2 ] = Tcl_NewStringObj( val, -1 );
	args[ 3 ] = NULL;

	Tcl_IncrRefCount( args[ 2 ] );
	(*info.objProc)( info.objClientData, lib->tcl, 3, args );
	Tcl_DecrRefCount( args[ 2 ] );
}

/*
*	Power control callback function
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

void	Lpctcl_Power( int state, void *param )
{
LPCTCL		*lib = param;
Tcl_CmdInfo	info;
Tcl_Obj		*args[ 3 ];

	if ( ! lib->tgt_pwrf ) return;

	if ( Tcl_GetCommandInfo( lib->tcl,
							 Tcl_GetStringFromObj( lib->tgt_pwrf, NULL ),
							 &info ) == 0 ) {

		// Can't find the nominated callback - we can't do anything.

		return;
	}

	args[ 0 ] = lib->tgt_pwrf;
	args[ 1 ] = Tcl_NewIntObj( state );
	args[ 2 ] = NULL;

	Tcl_IncrRefCount( args[ 1 ] );
	(*info.objProc)( info.objClientData, lib->tcl, 2, args );
	Tcl_DecrRefCount( args[ 1 ] );
}

