/****************************************************************************/
/* Copyright 2002 MBARI														*/
/****************************************************************************/
/* Summary	: Serial comm support for OASIS mooring controller				*/
/* Filename : serial.c														*/
/* Author	: Robert Herlien (rah)											*/
/* Project	: OASIS Mooring Replacement (OASIS3)							*/
/* Revision: 0.1															*/
/* Created	: 10/17/2002													*/
/*																			*/
/* MBARI provides this documentation and code "as is", with no warranty,	*/
/* express or implied, of its quality or consistency. It is provided without*/
/* support and without obligation on the part of the Monterey Bay Aquarium	*/
/* Research Institute to assist in its use, correction, modification, or	*/
/* enhancement. This information should not be published or distributed to	*/
/* third parties without specific written permission from MBARI.			*/
/*																			*/
/****************************************************************************/
/* Modification History:													*/
/* 17oct2002 rah - created from OASIS io.c									*/
/* $Log: serial.c,v $
/* Revision 1.3	 2004/08/18 21:08:35  bobh
/* Diagnostic routines added, minor bug fixes, blinky added as parm
/*
/* Revision 1.2	 2003/09/03 17:48:16  bobh
/* Further mods for config file changes
/*
 */
/****************************************************************************/

#include <mbariTypes.h>					/* MBARI type definitions			*/
#include <mbariConst.h>					/* MBARI constants					*/
#include <oasis.h>						/* OASIS controller definitions		*/
#include <debug.h>
#include <serial.h>						/* OASIS serial I/O definitions		*/
#include <io.h>							/* OASIS I/O definitions			*/
#include <task.h>						/* OASIS serial I/O definitions		*/
#include <clock.h>						/* OASIS clock routines				*/
#include <utils.h>						/* OASIS Utility routines			*/

#include <cfxpico.h>					/* Persistor PicoDOS definitions	*/
#include <serInternal.h>				/* Internal PicoDOS definitions		*/
#include <stdlib.h>						/* For calloc, free					*/
#include <string.h>


/********************************/
/*		External Data			*/
/********************************/

Extern TimeOfDay		tod;			/* Current time in TimeOfDay format */
Extern Parm_t			xoff_tmout;		/* Timeout for xoff					*/


/********************************/
/*		Module Local Data		*/
/********************************/

MLocal bool		xoff_status[PHYS_SER_PORTS]; /* UART Transmitter status*/
MLocal bool		do_xon[PHYS_SER_PORTS];		 /* TRUE to do XON/XOFF	   */
MLocal TimeOfDay xoff_time[PHYS_SER_PORTS];	 /* To time out XOFF status	 */

/* Default TUChParams for serial TPU initialization		*/

MLocal TUChParams dfltTUParms =
	{8, 'N', true, 9600L, tpuAutoPriority, tpuAutoPriority,
	 RXBUFSIZE, TXBUFSIZE, 0};

/* Serial Channel Descriptors -- describes an OASIS Physical	*/
/* Serial Port in terms of the TPU channels it uses, keeps the	*/
/* TUPort references for use by low-level functions, and saves	*/
/* the settings.  The typedef is in serInternal.h				*/

MLocal SerChanDesc		serChans[PHYS_SER_PORTS] =
{ {0, 0, NULL, NULL, 0, 0, 0}, {2, 3, NULL, NULL, 0, 0, 0},
  {4, 5, NULL, NULL, 0, 0, 0}, {6, 7, NULL, NULL, 0, 0, 0},
  {8, 9, NULL, NULL, 0, 0, 0}, {10, 11, NULL, NULL, 0, 0, 0},
  {12, 13, NULL, NULL, 0, 0, 0}, {14, 15, NULL, NULL, 0, 0, 0}
};

/* The following "reverse" table is for the interrupt handlers		   */
/* to index with TPU channel number and get the serChan number (above) */
/* and the Tx and Rx TUChan pointers.								   */
/* THIS MUST BE KEPT CONSISTENT WITH serChans						   */
/* Note that each serChan will have 2 entries in this table -- one for */
/* its Tx channel and one for its Rx channel. Typedef in serInternal.h */

MLocal ReverseChanDesc	tpuChans[16] =
{ {0, NULL, NULL}, {0, NULL, NULL}, {1, NULL, NULL}, {1, NULL, NULL},
  {2, NULL, NULL}, {2, NULL, NULL}, {3, NULL, NULL}, {3, NULL, NULL},
  {4, NULL, NULL}, {4, NULL, NULL}, {5, NULL, NULL}, {5, NULL, NULL},
  {6, NULL, NULL}, {6, NULL, NULL}, {7, NULL, NULL}, {7, NULL, NULL}
};


/************************************************************************/
/* Function	   : serCheckXoffStatus										*/
/* Purpose	   : Check if any port has timed out on XOFF				*/
/* Input	   : None													*/
/* Output	   : None													*/
/* Comment	   : Called twice/second from oasis task main loop			*/
/************************************************************************/
		Void
serCheckXoffStatus( Void )
{
  Reg Nat16		i;

  /* Do only the TPU Ports.	 The SCI serial console can't time out on XOFF*/
  for ( i = CPU_SER + 1; i < PHYS_SER_PORTS; i++ )
	if ((xoff_status[i]) && ((tod - xoff_time[i]) > xoff_tmout))
	{											/* Timed out on XOFF	*/
	  xoff_status[i] = FALSE;					/* Turn off xoff status */
	  TUEnableTx(serChans[i].txRef->txchp);		/* Get the Tx data flowing*/
	}

} /* serCheckXoffStatus() */


/************************************************************************/
/* Function	   : serPortClose											*/
/* Purpose	   : Close a (physical) serial port							*/
/* Inputs	   : Physical serial port to close							*/
/*				 Boolean to make TPU pins into outputs					*/
/* Outputs	   : None													*/
/************************************************************************/
		Void
serPortClose( Nat16 port, MBool makeOutput )
{
  SerChanDesc	*scd;

  if ((port != CPU_SER) && (port < PHYS_SER_PORTS))
  {
	scd = &serChans[port];
	if (scd->txRef != NULL)
	  TUClose(scd->txRef);

	if (scd->rxRef != NULL)
	  TUClose(scd->rxRef);

	scd->txRef = NULL;
	scd->rxRef = NULL;

	if (makeOutput)
	{
	  PIOClear(TPUPinFromChan(scd->txChan));
	  PIOClear(TPUPinFromChan(scd->rxChan));
	}
  }

  if (port < PHYS_SER_PORTS)
	xoff_status[port] = FALSE;

} /* serPortClose() */


/************************************************************************/
/* Function	   : serPortInit											*/
/* Purpose	   : Initialize one serial port								*/
/* Inputs	   : Physical serial port to init							*/
/*				 Initialization word									*/
/*				 Bit 8 = Big Tx Buf: Use 1Kbyte Tx Buffer				*/
/*				 Bits 6-7 = Newline mode - what to send on \n to output:*/
/*							  00 = \n, 01 = \r\n, 02 = \r, 03 = none	*/
/*				 Bit 5 = Echo mode: 1 to do local echo, 0 for none		*/
/*				 Bit 4 = Flow control: 1 for XON/XOFF, 0 for none		*/
/*				 Bit 3 = Stop bits: 0 for 1 stop bit, 1 for 2 stop bits */
/*				 Bit 2 = Character len: 0 for 7 bits, 1 for 8 bits		*/
/*				 Bits 0-1 = Parity: 00 = none, 01 = Even, 10 = odd, 11 rsvd*/
/* Outputs	   : MBool, TRUE if OK										*/
/************************************************************************/
		MBool
serPortInit( Nat16 port, Nat32 baud, Nat32 mode )
{
  Reg Int16		txChan, rxChan;
  Int16			parity;
  SerChanDesc	*scd;
  TUChParams	tucp;

  if (port >= PHYS_SER_PORTS)
	return(FALSE);

  scd = &serChans[port];
  memcpy(&tucp, &dfltTUParms, sizeof(TUChParams));
  do_xon[port] = (mode & FLOW) ? TRUE : FALSE;
  xoff_status[port] = FALSE;

  switch(mode & PTY_MASK)
  {
	case ODD_PTY:
	  parity = 'O';
	  break;

	case EVEN_PTY:
	  parity = 'E';
	  break;
		
	case NO_PTY:
	  parity = 'N';
	  break;
  }

  if (port == CPU_SER)
  {
	if (mode & STOP2)
	  parity = '2';
	SCIConfigure(baud, parity, true);
	SCITxHandshake(hshkOff, XON, XOFF);
	do_xon[CPU_SER] = FALSE;
	SCITxFlush();
	SCIRxFlush();
  }
  else
  {
	serPortClose(port, FALSE);
	if (mode & BIT7)
	  tucp.bits = 7;
	tucp.parity = parity;
	if (mode & BIG_TX_BUF)
	  tucp.txqsz = BIG_TXBUFSIZE;
	txChan = scd->txChan;
	rxChan = scd->rxChan;
	scd->txRef = TUOpen(-1, txChan, baud, &tucp);
	scd->rxRef = TUOpen(rxChan, -1, baud, &tucp);
	//	  TUTxFlush(scd->txRef);
	//	  TURxFlush(scd->rxRef);

	/* Set up reverse lookup table for Tx TPU and Rx TPU channels */
	/* This is for access by the Tx & Rx interrupt handlers		*/
	tpuChans[txChan].serChan = port;
	tpuChans[rxChan].serChan = port;
	tpuChans[txChan].txTUChan = scd->txRef->txchp;
	tpuChans[rxChan].txTUChan = scd->txRef->txchp;
	tpuChans[txChan].rxTUChan = scd->rxRef->rxchp;
	tpuChans[rxChan].rxTUChan = scd->rxRef->rxchp;

	/* Install the alternate TPU handlers if we're doing XON/XOFF */
	if (do_xon[port])
	{
	  scd->txRef->txchp->isr.handler = TUTxHandler;
	  scd->rxRef->rxchp->isr.handler = TURxHandler;
	}
  }

  scd->baudSave = baud;
  scd->paritySave = parity;
  scd->bitsSave = tucp.bits;
  return(TRUE);

} /* serPortInit() */


/************************************************************************/
/* Function	   : ser_putc												*/
/* Purpose	   : Write one character to serial port						*/
/* Inputs	   : Physical port number, character						*/
/* Output	   : MBool, TRUE if character sent							*/
/************************************************************************/
		MBool
ser_putc( Nat16 port, Byte byte )
{
  if (port == CPU_SER)
	return(SCITxPutByte((ushort)byte, false) ? TRUE : FALSE);
  else if (port < PHYS_SER_PORTS)
	return(TUTxPutByte(serChans[port].txRef, (ushort)byte, false) ? TRUE : FALSE);
  else
	return(FALSE);

} /* ser_putc() */


/************************************************************************/
/* Function	   : ser_getc												*/
/* Purpose	   : Read one character from serial port					*/
/* Inputs	   : Physical serial port number							*/
/* Output	   : Character, or ERROR if none							*/
/************************************************************************/
		Int16
ser_getc( Nat16 port )
{
  Int16 c;

  if (port == CPU_SER)
	return(((c = SCIRxGetByte(false)) & RxD_ERR_MASK) ? ERROR : (c & 0xff));
  else if (port < PHYS_SER_PORTS)
	return(((c = TURxGetByte(serChans[port].rxRef, false)) & RxD_ERR_MASK) ?
		   ERROR : (c & 0xff));
  else
	return(FALSE);

} /* ser_getc() */


/************************************************************************/
/* Function	   : ser_sndsts												*/
/* Purpose	   : Get number characters left to send on serial port		*/
/* Inputs	   : Physical serial port number							*/
/* Output	   : Number characters left to send							*/
/* Comments	   : If ring buffer is empty, checks UART to see if it's	*/
/*				 still sending a character.	 This is to ensure that the */
/*				 last byte is sent before this	function returns a 0.	*/
/************************************************************************/
		Int16
ser_sndsts( Nat16 port )
{
  if (port == CPU_SER)
	return(SCITxQueuedCount());
  else if (port < PHYS_SER_PORTS)
	return(TUTxQueuedCount(serChans[port].txRef));
  else
	return(0);

} /* ser_sndsts() */


/************************************************************************/
/* Function	   : ser_flush												*/
/* Purpose	   : Discard input on a serial line							*/
/* Inputs	   : Physical serial port number							*/
/* Output	   : None													*/
/************************************************************************/
		Void
ser_flush( Nat16 port )
{
  if (port == CPU_SER)
	SCIRxFlush();
  else if (port < PHYS_SER_PORTS)
	TURxFlush(serChans[port].rxRef);
	
} /* ser_flush() */


/************************************************************************/
/* Function	   : ser_break												*/
/* Purpose	   : Send Break signal to a serial port						*/
/* Inputs	   : Physical serial port number, length of break in ticks	*/
/* Output	   : None													*/
/************************************************************************/
		Void
ser_break( Nat16 port, Int16 breakTicks )
{
  Reg SerChanDesc		*scd;
  TUChParams			tucp;
  
  if (port == CPU_SER)
	SCITxBreak(breakTicks * (1000/TICKS_PER_SECOND));
  else if (port < PHYS_SER_PORTS)
  {
	scd = &serChans[port];
	if (scd->txRef != NULL)
	  TUClose(scd->txRef);
	PIOClear(TPUPinFromChan(scd->txChan));
	task_delay(breakTicks);
	memcpy(&tucp, &dfltTUParms, sizeof(TUChParams));
	tucp.parity = scd->paritySave;
	tucp.bits = scd->bitsSave;
	scd->txRef = TUOpen(-1, scd->txChan, scd->baudSave, &tucp);
  }
	
} /* ser_break() */


/************************************************************************/
/* Function	   : ser_init												*/
/* Purpose	   : Initialize serial I/O system							*/
/* Inputs	   : None													*/
/* Outputs	   : None													*/
/************************************************************************/
		Void
ser_init( Void )
{
  Reg Nat16		i;
  TimeOfDay		now = clk_read();

  TUInit(calloc, free);
  TUSetDefaultParams(&dfltTUParms);
  SCIRxSetBuffered(true);		// switch SCI to buffered receive mode
  SCITxSetBuffered(true);		// switch SCI to buffered transmit mode

  for ( i = 0; i < PHYS_SER_PORTS; i++ )
  {
	xoff_status[i] = FALSE;
	xoff_time[i] = now;
  }

} /* ser_init() */


/************************************************************************/
/* Function	   : TUEnableTx												*/
/* Purpose	   : Enable Tx on a TPU channel								*/
/* Inputs	   : TUChan ptr for channel to enable						*/
/* Outputs	   : None													*/
/************************************************************************/
		MLocal Void
TUEnableTx(TUChan *tchp)
{
  tchp->flags = true;					// Show enabled
  TPUEnableInterrupt(tchp->slot);		// Enable ints

} /* TUEnableTx() */


/************************************************************************/
/* Function	   : TUTxHandler											*/
/* Purpose	   : Tx Interrupt handler for TPUs							*/
/* Inputs	   : TPU channel in D0										*/
/* Outputs	   : None													*/
/************************************************************************/
		MLocal void
TUTxHandler(ushort slot:__d0)
{
  ReverseChanDesc		*rchanp;
  TUChan				*tchp;

  rchanp = tpuChans + slot;
  tchp= rchanp->txTUChan;

  /* We know, if this int handler is running, that we're doing XON/XOFF */
  if (!xoff_status[rchanp->serChan] && (tchp->free != tchp->size))
  {
	TPUClearInterrupt(slot);
	TPRAM[tchp->slot][uartRxTxDataReg] = *(tchp->buffer + tchp->tail++);
	if (tchp->tail == tchp->size)
	  tchp->tail = 0;
	tchp->free += 1;			
  }
  else
  {
	TPUDisableInterrupt(slot);
	tchp->flags = false;
  }

} /* TUTxHandler()*/


/************************************************************************/
/* Function	   : TURxHandler											*/
/* Purpose	   : Rx Interrupt handler for TPUs							*/
/* Inputs	   : TPU channel in D0										*/
/* Outputs	   : None													*/
/************************************************************************/
		MLocal void
TURxHandler(ushort slot:__d0)
{
  ReverseChanDesc		*rchanp;
  TUChan				*tchp;
  uchar					rcvByte;

  rchanp = tpuChans + slot;
  tchp= rchanp->rxTUChan;
  rcvByte = TPRAM[tchp->slot][uartRxTxDataReg];

  /* Since this int handler is running, we know that we're doing XON/XOFF */
  if (rcvByte == XON)
  {
	xoff_status[rchanp->serChan] = FALSE;		// Re-enable xmit
	TUEnableTx(rchanp->txTUChan);				// Restart Tx data
  }
  else if (rcvByte == XOFF)
  {
	xoff_status[rchanp->serChan] = TRUE;		// Disable xmit
	xoff_time[rchanp->serChan] = tod;			// Remember when
  }
  else if (tchp->free == 0)						// no room
	tchp->flags++;								// bump overrun count
  else
  {
	*(tchp->buffer + tchp->head++) = rcvByte;
	if (tchp->head == tchp->size)
	  tchp->head = 0;
	tchp->free -= 1;
  }

  TPUClearInterrupt(slot);

} /* TURxHandler()*/


#ifdef DEBUG_SERIAL_CMD

/************************************************************************/
/* Function	   : serDump												*/
/* Purpose	   : Show status of serial channel							*/
/* Inputs	   : Parm Mask, Channel number								*/
/* Outputs	   : OK														*/
/************************************************************************/
		Int16
serDump(Nat16 pmask, Parm_t serchan)
{
  Int16 chan = (Int16)serchan;
  TUChan		txch, rxch;

  if ((pmask == 0) || (chan <= CPU_SER) || (chan >= PHYS_SER_PORTS))
	xprintf("Bad channel number\n");
  else
  {
	memcpy(&txch, serChans[chan].txRef->txchp, sizeof(TUChan));
	memcpy(&rxch, serChans[chan].rxRef->rxchp, sizeof(TUChan));
	xprintf("Status of Serial Channel %d\n\n", chan);
	xprintf("Using TPU %d for Tx and TPU %d for Rx\n", 
			serChans[chan].txChan, serChans[chan].rxChan);
	if (do_xon[chan])
	  xprintf("Xon/Xoff ENABLED, current status %s\n",
			  xoff_status[chan] ? "XOFF" : "XON");
	else
	  xprintf("Xon/Xoff DISABLED\n");
	xprintf("Tx TUChan at %#lx, Rx TUChan at %#lx\n",
			serChans[chan].txRef->txchp, serChans[chan].rxRef->rxchp);
	xprintf("Tx buf: %#lx  size %d	free %d	 head %d  tail %d\n",
			txch.buffer, txch.size, txch.free, txch.head, txch.tail);
	xprintf("Rx buf: %#lx  size %d	free %d	 head %d  tail %d\n",
			rxch.buffer, rxch.size, rxch.free, rxch.head, rxch.tail);
	xprintf("Tx ISR at %#lx	 Rx ISR at %#lx\n", 
			txch.isr.handler, rxch.isr.handler);
  }

  return(OK);

} /* serDump() */

#endif
