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

static	int		ControlCallback( LPC_CONTROL mode, void *user );

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

/*
*	Sets and opens the serial port
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*	The first argument is the name of the port.
*	The second argument is optional. If present it is the name of the
*	lockfile. If it is the empty string, then flock() is used. If does
*	not present, then no locking is used.
*/

CMDFUNC( SerialPort )
{
int		code;

	if ( objc > 1 ) {

		if ( objc > 2 )

			code = LPC_SerialOpen( lib->lpc,
								   Tcl_GetStringFromObj( objv[ 1 ], NULL ),
								   Tcl_GetStringFromObj( objv[ 2 ], NULL ) );
		else
			code = LPC_SerialOpen( lib->lpc,
								   Tcl_GetStringFromObj( objv[ 1 ], NULL ),
								   NULL );

		if ( code < 0 ) {

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

		return( TCL_OK );
	}
	else {

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

/*
*	Closes the serial port
*	~~~~~~~~~~~~~~~~~~~~~~
*	There's no option. This command is always successful.
*/

CMDFUNC( SerialClose )
{
	(void) tcl;
	(void) objc;
	(void) objv;
	LPC_SerialClose( lib->lpc );
	return( TCL_OK );
}

/*
*	Sets the baud rate for the port
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*	The first argument, if exists, is the initial (or current) baud rate, the
*	second, if exists, is the faster baud rate to use. The baud rates must
*	be one of 9600, 19200, 38400, 57600 or 115200.
*	Returns the current baud rate.
*/

CMDFUNC( SerialBaud )
{
int		code;

	if ( objc > 1 ) {

		if ( Tcl_GetIntFromObj( tcl, objv[ 1 ], &lib->ser_slow ) != TCL_OK )

			return( TCL_ERROR );

		if ( objc > 2 ) {

			if ( Tcl_GetIntFromObj( tcl, objv[ 2 ], &lib->ser_fast ) != TCL_OK )

				return( TCL_ERROR );
		}
		else
			lib->ser_fast = lib->ser_slow;

		if ( ( code = LPC_SerialBaud( lib->lpc, lib->ser_slow ) ) < 0 ||
			 ( code = LPC_TargetBaud( lib->lpc, lib->ser_slow,
			 						  lib->ser_fast ) ) < 0 ) {

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

	Tcl_SetObjResult( tcl, Tcl_NewIntObj( LPC_SerialBaud( lib->lpc, 0 ) ) );
	return( TCL_OK );
}

/*
*	Assigns the target control signals to the RTS and DTR signals
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*	It has two parameters. Both must be one of +rts, -rts, +dtr or -dtr.
*	The first argument controls the reset, the second the ISP.
*/

CMDFUNC( SerialModem )
{
int		res, isp, code;
char	*str;

	if ( objc != 3 ) {

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

	res = -1;
	isp = -1;

	str = Tcl_GetStringFromObj( objv[ 1 ], NULL );

	if ( ! strcmp( str, "+rts" ) ) res = LPC_MODE_pRTS;
	if ( ! strcmp( str, "-rts" ) ) res = LPC_MODE_nRTS;
	if ( ! strcmp( str, "+dtr" ) ) res = LPC_MODE_pDTR;
	if ( ! strcmp( str, "-dtr" ) ) res = LPC_MODE_nDTR;

	str = Tcl_GetStringFromObj( objv[ 2 ], NULL );

	if ( ! strcmp( str, "+rts" ) ) isp = LPC_MODE_pRTS;
	if ( ! strcmp( str, "-rts" ) ) isp = LPC_MODE_nRTS;
	if ( ! strcmp( str, "+dtr" ) ) isp = LPC_MODE_pDTR;
	if ( ! strcmp( str, "-dtr" ) ) isp = LPC_MODE_nDTR;

	if ( ( code = LPC_TargetControlModem( lib->lpc, res, isp ) ) < 0 ) {

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

	if ( lib->ser_user ) {

		lib->ser_user = NULL;
		free( lib->ser_user );
	}

	return( TCL_OK );
}

/*
*	Assigns the target control signals to a TCL procedure
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*	Designates the string of the argument as the TCL command to be called
*	when a control signal must change. The command will receive a single
*	integer argument, as per the LPC_CONTROL enum. This function will not
*	fail if the procedure doesn't exist (yet), only when signal control is
*	really needed will the precedure be looked up.
*/

CMDFUNC( SerialManual )
{
int		len;
char	*str;

	if ( objc != 2 ) {

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

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

	if ( lib->ser_user ) free( lib->ser_user );

	if ( ! ( lib->ser_user = malloc( len + 3 ) ) ) {

		Tcl_AppendResult( tcl, "Out of memory", NULL );
		return( TCL_ERROR );
	}

	strcpy( lib->ser_user, str );
	strcat( lib->ser_user, "  " );
	LPC_TargetControlManual( lib->lpc, ControlCallback, lib );
	return( TCL_OK );
}

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

/*
*	This is the callback when the signal control is manual
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

static	int		ControlCallback( LPC_CONTROL mode, void *user )
{
LPCTCL		*lib = user;

	switch ( mode ) {

		case LPC_RES_ASSERT:	mode = 0; break;
		case LPC_RES_NEGATE:	mode = 1; break;
		case LPC_ISP_ASSERT:	mode = 2; break;
		case LPC_ISP_NEGATE:	mode = 3; break;
	}

	lib->ser_user[ strlen( lib->ser_user ) - 1 ] = mode + '0';
	return( Tcl_Eval( lib->tcl, lib->ser_user ) == TCL_OK );
}

