/****************************************************************************/
/* Copyright 2015-2016 MBARI                                                */
/****************************************************************************/
/* Summary  : Serial I/O for OASIS5 on PIC32MX470F512L                      */
/* Filename : serial.c                                                      */
/* Author   : Robert Herlien (rah)                                          */
/* Project  : OASIS Mooring Replacement (OASIS5)                            */
/* Revision: 1.0                                                            */
/* Created  : 07/13/2015                                                    */
/*                                                                          */
/* 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:                                                    */
/* 13jul2015 rah - created                                                  */
/* 20jun2016 rah - Merged with Oasis3/4 source								*/
/****************************************************************************/

#include <xc.h>

#include <mbariTypes.h>					/* MBARI type definitions			*/
#include <time.h>						/* Std C time.h						*/
#include <oasis.h>						/* OASIS controller definitions		*/
#include <debug.h>						/* For DEBUG_SERIAL_CMD				*/

#include <serial.h>
#include <buffer.h>
#include <dig_io.h>
#include <sem.h>
#include <timer.h>
#include <modbus.h>						/* Modbus protocol library		*/
#include <istdio.h>
#include <FreeRTOS.h>					/* FreeRTOS definitions				*/

#define SER_POLL	(TICKS_PER_SEC/50)	/* Serial polling rate in ticks		*/


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

Extern Parm_t		xoff_tmout;					/* Timeout for xoff			*/
Extern Nat32		wakeOnConsoleChar;
Extern TaskHandle_t	oasisTask;
Extern ModBusIO		modSerData;


/****************************************/
/*			Global Data					*/
/****************************************/

Global Nat32		serPortsToWake;
Global Nat32		serPortsWoke;
Global Semaphore	ser_sem[PIC_UARTS];			/* Mutex sems for ser ports */


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

MLocal unsigned int iec1_RxBits[] = 
    {0, _IEC1_U1RXIE_MASK, _IEC1_U2RXIE_MASK, _IEC1_U3RXIE_MASK, 0};

MLocal unsigned int iec2_RxBits[] = 
    {_IEC2_U5RXIE_MASK, 0, 0, 0, _IEC2_U4RXIE_MASK};

MLocal unsigned int iec1_TxBits[] = 
    {0, _IEC1_U1TXIE_MASK, _IEC1_U2TXIE_MASK, 0, 0};

MLocal unsigned int iec2_TxBits[] = 
    {_IEC2_U5TXIE_MASK, 0, 0, _IEC2_U3TXIE_MASK, _IEC2_U4TXIE_MASK};

MLocal SerStruct	serStr[PIC_UARTS];

MLocal Byte			dummy;


/************************************************************************/
/* Function    : serInit                                                */
/* Purpose     : Initialize serial I/O Module                           */
/* Inputs      : None                                                   */
/* Outputs     : None                                                   */
/************************************************************************/
Errno serInit(void)
{
    int			i;
	SerStruct	*ssp;
	Errno		rtn = OK;

    /* UART Setup - Note that dig_io.c sets all the Tx bits to output 0 state */
    /* This will cause the port to go into break when the UART is disabled    */
    /* Map the PPS pins for the UARTs                                         */

    U1RXR = 0x01;                       /* UART1 RX = Ser 1             */
    RPG7R = 0x03;                       /* UART1 TX                     */
    U2RXR = 0x02;                       /* UART2 RX = Ser 2             */
    RPF5R = 0x01;                       /* UART2 TX                     */
    U3RXR = 0x0b;                       /* UART3 RX = Ser 3             */
    RPD14R = 0x01;                      /* UART3 TX                     */
    U4RXR = 0x02;                       /* UART4 RX = Ser 4             */
    RPB15R = 0x02;                      /* UART4 TX                     */
    U5RXR = 0x0b;                       /* UART5 RX = Ser 0 = Console   */
    RPF8R = 0x04;                       /* UART5 TX = Ser 0 = Console   */

    /* initialize serial data */
    for (i = 0, ssp = serStr; i < PIC_UARTS; i++, ssp++)
    {
		ssp->serType = INST_PORT;
        bufInit(&ssp->rxBuff,
				&ssp->rxBuffData[0], RX_BUFF_SIZE);
        bufInit(&ssp->txBuff,
				&ssp->txBuffData[0], TX_BUFF_SIZE);

		/* Create mutex sems	*/
		if (semCreateMutex(&ser_sem[i]) == NULL)
			rtn = ERROR;

		isnprintf(ser_sem[i].sem_name, SEM_NAMELEN, "SerSem%02u", i);

		serPortInit(i, STD_BAUD, NO_PTY | BIT8 | STOP1);
										        /* Sets up UxMODE and UxSTA */

        ssp->rxOverFlowCnt = 0;				/* Init overflow cnt		*/
		ssp->do_xon = FALSE;
		ssp->xoff_status = FALSE;
    }
    

/******************** Setup Serial Port 0 == UART 5 *********************/

    /* Configure uart5 receive and transmit interrupt */
    /* clear any pending interrupts just in case */
    IFS2CLR = (_IFS2_U5RXIF_MASK | _IFS2_U5TXIF_MASK);

    /* Set interrupt priority - Rx and Tx share priority    */
    IPC10bits.U5IP = 4;
    IPC10bits.U5IS = 0;

    /* enable receive interrupts, disable transmit interrupts */
    IEC2SET = _IEC2_U5RXIE_MASK;
    IEC2CLR = _IEC2_U5TXIE_MASK;


/******************** Setup Serial Port 1 == UART 1 *********************/

    /* Configure uart1 receive and transmit interrupt */
    /* clear any pending interrupts just in case */
    IFS1CLR = _IFS1_U1RXIF_MASK;
    IFS1CLR = _IFS1_U1TXIF_MASK;

    /* Set interrupt priority - Rx and Tx share priority    */
    IPC7bits.U1IP = 4;
    IPC7bits.U1IS = 3;

    /* enable receive interrupts, disable transmit interrupts */
    IEC1SET = _IEC1_U1RXIE_MASK;
    IEC1CLR = _IEC1_U1TXIE_MASK;


/******************** Setup Serial Port 2 == UART 2 *********************/

    /* Configure uart2 receive and transmit interrupt */
    /* clear any pending interrupts just in case */
    IFS1CLR = _IFS1_U2RXIF_MASK;
    IFS1CLR = _IFS1_U2TXIF_MASK;

    /* Set interrupt priority - Rx and Tx share priority    */
    IPC9bits.U2IP = 4;
    IPC9bits.U2IS = 3;

    /* enable receive interrupts, disable transmit interrupts */
    IEC1SET = _IEC1_U2RXIE_MASK;
    IEC1CLR = _IEC1_U2TXIE_MASK;
    
    
/******************** Setup Serial Port 3 == UART 3 *********************/

    /* Configure uart3 receive and transmit interrupt */
    /* clear any pending interrupts just in case */
    IFS1CLR = _IFS1_U3RXIF_MASK;
    IFS2CLR = _IFS2_U3TXIF_MASK;

    /* Set interrupt priority - Rx and Tx share priority    */
    IPC9bits.U3IP = 4;
    IPC9bits.U3IS = 3;

    /* enable receive interrupts, disable transmit interrupts */
    IEC1SET = _IEC1_U3RXIE_MASK;
    IEC2CLR = _IEC2_U3TXIE_MASK;


/******************** Setup Serial Port 4 == UART 4 *********************/

    /* Configure uart4 receive and transmit interrupt */
    /* clear any pending interrupts just in case */
    IFS2CLR = _IFS2_U4RXIF_MASK;
    IFS2CLR = _IFS2_U4TXIF_MASK;

    /* Set interrupt priority - Rx and Tx share priority    */
    IPC9bits.U4IP = 4;
    IPC9bits.U4IS = 3;

    /* enable receive interrupts, disable transmit interrupts */
    IEC2SET = _IEC2_U4RXIE_MASK;
    IEC2CLR = _IEC2_U4TXIE_MASK;

	return(rtn);
}


/************************************************************************/
/* Function	   : getSerialSemTmout										*/
/* Purpose	   : Get access to a serial port							*/
/* Input	   : Physical serial port number							*/
/* Output	   : None													*/
/************************************************************************/
Errno getSerialSemTmout(Port_t serport, TickType_t tmout)
{
	if (isPhysSer(serport))					/* If phys ser port, take sem*/
		return(semTakeTmout(&ser_sem[serport], tmout));

	return(OK);

} /* getSerialSemTmout() */


/************************************************************************/
/* Function	   : releaseSerialSem										*/
/* Purpose	   : Release access to a serial port						*/
/* Input	   : Physical serial port number							*/
/* Output	   : None													*/
/************************************************************************/
void releaseSerialSem(Port_t serport)
{
	if (isPhysSer(serport))					/* If physical ser port		*/
		semGive(&ser_sem[serport]);			/* Give up semaphore		*/

} /* releaseSerialSem() */


/************************************************************************/
/* Function	   : getSerialStruct										*/
/* Purpose	   : Get ptr to SerStruct									*/
/* Input	   : Physical serial port number							*/
/* Output	   : Ptr to SerStruct, or NULL								*/
/************************************************************************/
SerStruct *getSerialStruct(Port_t serport)
{
	if (serport < PIC_UARTS)				/* If phys ser port, take sem*/
		return(&serStr[serport]);
	else
		return(NULL);
	
} /* getSerialStruct() */


/************************************************************************/
/* 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 Nat32		i;
	Reg SerStruct	*ssp;
	Reg time_t		tod = clkTime();

	for (i = 0, ssp = serStr; i < PIC_UARTS; i++, ssp++)
		if ((ssp->serType == INST_PORT) &&
			(ssp->xoff_status) &&
			((tod - ssp->xoff_time) > xoff_tmout))
	  {											/* Timed out on XOFF	*/
		  ssp->xoff_status = FALSE;		/* Turn off xoff status */

		  IEC1SET = iec1_TxBits[i];			    /* Turn on UART Tx ints */
		  IEC2SET = iec2_TxBits[i];
	  }

} /* serCheckXoffStatus() */


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

    if (port < PIC_UARTS)               /* Valid port?                  */
    {
        IEC1CLR = iec1_RxBits[port];    /* Turn off ints from the UART  */
        IEC2CLR = iec2_RxBits[port];    /* while manipulating the buffer*/

        stat = bufGet(&c, &serStr[port].rxBuff);

        IEC1SET = iec1_RxBits[port];    /* Turn UART ints back on       */
        IEC2SET = iec2_RxBits[port];    /* Turn UART ints back on       */

        return(stat ? (int)c : ERROR);
    }

    return(ERROR);
}


/************************************************************************/
/* Function	   : ser_putc												*/
/* Purpose	   : Write one character to serial port						*/
/* Inputs	   : Physical port number, character						*/
/* Output	   : MBool, TRUE if character sent							*/
/************************************************************************/
MBool ser_putc(Port_t port, Byte b)
{
    MBool			rtn;
	Reg SerStruct	*ssp;

    if (port < PIC_UARTS)               /* Valid port?                  */
    {
        IEC1CLR = iec1_TxBits[port];    /* Turn off ints from the UART  */
        IEC2CLR = iec2_TxBits[port];    /* while manipulating the buffer*/

		ssp = &serStr[port];
        rtn = bufPut(b, &ssp->txBuff);

		if (!(ssp->do_xon && ssp->xoff_status))
		{
			IEC1SET = iec1_TxBits[port]; /* Turn on UART Tx ints        */
			IEC2SET = iec2_TxBits[port];
		}

        return(rtn);
    }

    return(FALSE);
}


/************************************************************************/
/* Function    : serPutString                                           */
/* Purpose     : Queue a C string for transmission                      */
/* Inputs      : Ser port number, string to send                        */
/* Outputs     : Number of bytes queued                                 */
/************************************************************************/
int serPutString(Port_t port, char* buff)
{
    int         i;
    char        *p;

    if (port < PIC_UARTS)               /* Valid port?                  */
    {
        for (i = 0, p = buff; *p; )
            if (ser_putc(port, *p))
            {
                p++;
                i++;
            }
			else
				vTaskDelay(SER_POLL);

        return(i);
    }

    return(0);
}


/************************************************************************/
/* Function	   : serPortInit											*/
/* Purpose	   : Initialize one serial port								*/
/* Inputs	   : Physical serial port to init							*/
/*				 Initialization word									*/
/*				 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(Port_t port, Nat32 baud, Nat32 mode)
{
	int				data_par;
	Reg SerStruct	*ssp;

    if (port >= PIC_UARTS)               /* Valid port?                  */
		return(FALSE);

	ssp = &serStr[port];

	ssp->do_xon = (mode & FLOW) ? TRUE : FALSE;
	ssp->xoff_status = FALSE;

	if (mode & BIT7)					/* 7 bit mode not supported		*/
		return(FALSE);
	
	switch(mode & PTY_MASK)
	{
	  case ODD_PTY:
		  data_par = D8_ODD;
		  break;

	  case EVEN_PTY:
		  data_par = D8_EVEN;
		  break;
		
	  default:
		  data_par = D8_NONE;
		  break;
	}

    switch(port)
    {
		case SER_PORT_0:
			U5MODE = UxMODE;
			U5MODEbits.PDSEL = data_par;

			if (mode & STOP2)
				U5MODEbits.STSEL = 1;

			if (baud <= 19200L)
				U5BRG = StdBRG(baud);
			else
			{
				U5BRG = FastBRG(baud);
				U5MODEbits.BRGH = 1;
			}

			U5STA = UxSTA;
			break;

		case SER_PORT_1:
			U1MODE = UxMODE;
			U1MODEbits.PDSEL = data_par;

			if (mode & STOP2)
				U1MODEbits.STSEL = 1;

			if (baud <= 19200L)
				U1BRG = StdBRG(baud);
			else
			{
				U1BRG = FastBRG(baud);
				U1MODEbits.BRGH = 1;
			}

			U1STA = UxSTA;
			break;

        case SER_PORT_2:
            U2MODE = UxMODE;
            U2MODEbits.PDSEL = data_par;
                          
			if (mode & STOP2)
                U2MODEbits.STSEL = 1;

            if (baud <= 19200L)
                U2BRG = StdBRG(baud);
            else
            {
                U2BRG = FastBRG(baud);
                U2MODEbits.BRGH = 1;
            }

            U2STA = UxSTA;
            break;

        case SER_PORT_3:
            U3MODE = UxMODE;
            U3MODEbits.PDSEL = data_par;

			if (mode & STOP2)
                U3MODEbits.STSEL = 1;

            if (baud <= 19200L)
                U3BRG = StdBRG(baud);
            else
            {
                U3BRG = FastBRG(baud);
                U3MODEbits.BRGH = 1;
            }

            U3STA = UxSTA;
            break;

        case SER_PORT_4:
            U4MODE = UxMODE;
            U4MODEbits.PDSEL = data_par;

			if (mode & STOP2)
                U4MODEbits.STSEL = 1;

            if (baud <= 19200L)
                U4BRG = StdBRG(baud);
            else
            {
                U4BRG = FastBRG(baud);
                U4MODEbits.BRGH = 1;
            }

            U4STA = UxSTA;
            break;

	    default:
			return(FALSE);
    }

	ssp->baudRate = baud;
	return(TRUE);
}


/************************************************************************/
/* Function    : serGetConfig                                           */
/* Purpose     : Return parity and stop bits for serial port            */
/* Inputs      : Ser port number, ptrs to parity and stop bits          */
/* Outputs     : None                                                   */
/************************************************************************/
void serGetConfig(int port, unsigned long *baud, int *data_par, int *stop)
{
    switch ( port )
    {
        case SER_PORT_0:
            *stop = U5MODEbits.STSEL + 1;
            *data_par = U5MODEbits.PDSEL;
            break;

        case SER_PORT_1:
            *stop = U1MODEbits.STSEL + 1;
            *data_par = U1MODEbits.PDSEL;
            break;

        case SER_PORT_2:
            *stop = U2MODEbits.STSEL + 1;
            *data_par = U2MODEbits.PDSEL;
            break;

        case SER_PORT_3:
            *stop = U3MODEbits.STSEL + 1;
            *data_par = U3MODEbits.PDSEL;
            break;

        case SER_PORT_4:
            *stop = U4MODEbits.STSEL + 1;
            *data_par = U4MODEbits.PDSEL;
            break;

        default:
            return;
    }

    *baud = serStr[port].baudRate;
}


/************************************************************************/
/* 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.	*/
/************************************************************************/
int ser_sndsts(Port_t port)
{
	int		avail;

	if (port >= PIC_UARTS)
		return(0);

	if ((avail = bufAvailable(serStr[port].txBuff)) > 0)
		return(avail);

    switch(port)
    {
	  case SER_PORT_0:
		  return(U5STAbits.TRMT ? 0 : 1);

	  case SER_PORT_1:
		  return(U1STAbits.TRMT ? 0 : 1);

	  case SER_PORT_2:
		  return(U2STAbits.TRMT ? 0 : 1);

	  case SER_PORT_3:
		  return(U3STAbits.TRMT ? 0 : 1);

	  case SER_PORT_4:
		  return(U4STAbits.TRMT ? 0 : 1);
	}

	return(0);

} /* ser_sndsts() */


/************************************************************************/
/* Function	   : ser_read												*/
/* Purpose	   : Read bytes from serial port with timeout				*/
/* Inputs	   : Phys serial port, buffer, num bytes to read, timeout	*/
/* Output	   : Number of bytes read									*/
/*				 Operates a byte at a time because PicoDOS TURxGetBlock */
/*				 doesn't work with a timeout of 0, and is inefficient	*/
/*				 with a timeout of 1 ms									*/
/************************************************************************/
int ser_read(Port_t port, char *buffer, Int32 nbytes, TickType_t tmout)
{
	Reg char	*p;						/* Buffer ptr			*/
	Int16		c;						/* Serial character		*/
	Int32		bytesRcvd;				/* Num bytes received	*/
	TickType_t	endTick;				/* When to stop			*/

	p = buffer;
	endTick = xTaskGetTickCount() + tmout;

	for (bytesRcvd = 0; bytesRcvd < nbytes; )
	{
		if ((c = ser_getc(port)) != ERROR)
		{
			*p++ = c;
			bytesRcvd++;
		}
		else if ((Int32)(xTaskGetTickCount() - endTick) < 0)
			vTaskDelay(SER_POLL);
		else
			return(bytesRcvd);
	}

	return(bytesRcvd);

} /* ser_read() */


/************************************************************************/
/* Function	   : ser_write												*/
/* Purpose	   : Write bytes to serial port with timeout				*/
/* Inputs	   : Phys serial port, buffer, num bytes to write, timeout	*/
/* Output	   : Number of bytes sent									*/
/************************************************************************/
int		ser_write(Port_t port, const char *buffer, Int32 nbytes, TickType_t tmout)
{
	const char	*bufp;					/* Buffer ptr			*/
	TickType_t	endTick;				/* When to stop			*/
	Int32		bytesSent;				/* Number bytes sent	*/

	bufp = buffer;
	endTick = xTaskGetTickCount() + tmout;

	for (bytesSent = 0; bytesSent < nbytes; )
	{
		if (ser_putc(port, *bufp))
		{
			bufp++;
			bytesSent++;
		}
		else if ((Int32)(xTaskGetTickCount() - endTick) < 0)
			vTaskDelay(SER_POLL);
		else
			return(bytesSent);
	}

	return(bytesSent);

} /* ser_write() */


/************************************************************************/
/* Function    : serRxCount                                             */
/* Purpose     : Return number of bytes available on serial port        */
/* Inputs      : Serial port number                                     */
/* Outputs     : None                                                   */
/************************************************************************/
int  serRxCount(Port_t port)
{
    return((port < PIC_UARTS) ? bufAvailable(serStr[port].rxBuff) : 0);
}

/************************************************************************/
/* Function    : serRxFlush                                             */
/* Purpose     : Flush serial input FIFO                                */
/* Inputs      : Serial port number                                     */
/* Outputs     : None                                                   */
/************************************************************************/
void serRxFlush(Port_t port)
{
    unsigned int reg;

    if (port < PIC_UARTS)
    {
        __builtin_disable_interrupts();

        bufClear(&serStr[port].rxBuff);

        switch ( port )
        {
		  case 0: while ( U5STAbits.URXDA ) reg = U5RXREG; break;
          case 1: while ( U1STAbits.URXDA ) reg = U1RXREG; break;
          case 2: while ( U2STAbits.URXDA ) reg = U2RXREG; break;
          case 3: while ( U3STAbits.URXDA ) reg = U3RXREG; break;
          case 4: while ( U4STAbits.URXDA ) reg = U4RXREG; break;
        }

        __builtin_enable_interrupts();
    }
}


/************************************************************************/
/* Function    : serTxFlush                                             */
/* Purpose     : Flush serial output FIFO                               */
/* Inputs      : Serial port number                                     */
/* Outputs     : None                                                   */
/************************************************************************/
void serTxFlush(Port_t port)
{
    if (port < PIC_UARTS)
    {
        __builtin_disable_interrupts();

        bufClear(&serStr[port].txBuff);

        switch ( port )
        {
          case 0: U5STAbits.UTXEN = 0; Nop();
                  U5STAbits.UTXEN = 1; Nop();
                  break;
          case 1: U1STAbits.UTXEN = 0; Nop(); 
                  U1STAbits.UTXEN = 1; Nop(); 
                  break;
          case 2: U2STAbits.UTXEN = 0; Nop();
                  U2STAbits.UTXEN = 1; Nop();
                  break;
          case 3: U3STAbits.UTXEN = 0; Nop();
                  U3STAbits.UTXEN = 1; Nop();
                  break;
          case 4: U4STAbits.UTXEN = 0; Nop();
                  U4STAbits.UTXEN = 1; Nop();
                  break;
        }

        __builtin_enable_interrupts();
    }
}


/************************************************************************/
/* Function    : serWaitTxComplete                                      */
/* Purpose     : Wait for Tx to complete                                */
/* Inputs      : Serial port number                                     */
/* Outputs     : None                                                   */
/************************************************************************/
void serWaitTxComplete(int port)
{
	int		i;

    if (port < PIC_UARTS)
    {
		for (i = 0; i < 100; i++)
        {
            if (bufIsEmpty(serStr[port].txBuff))
                switch ( port )
                {
                  case 0: if (U5STAbits.TRMT) return; break;
                  case 1: if (U1STAbits.TRMT) return; break;
                  case 2: if (U2STAbits.TRMT) return; break;
                  case 3: if (U3STAbits.TRMT) return; break;
                  case 4: if (U4STAbits.TRMT) return; break;
                }

			vTaskDelay(SER_POLL);
        }
    }
}


/************************************************************************/
/* Function    : serAnyTxActive											*/
/* Purpose     : Wait for Tx to complete                                */
/* Inputs      : Serial port number                                     */
/* Outputs     : None                                                   */
/************************************************************************/
MBool serAnyTxActive(void)
{
	int		i;

	for (i = 0; i < PIC_UARTS; i++)
		if (bufAvailable(serStr[i].txBuff))
			return(TRUE);

	return( (U5STAbits.TRMT & U1STAbits.TRMT & U2STAbits.TRMT &
			 U3STAbits.TRMT & U4STAbits.TRMT) == 0 );

} /* serAnyTxActive() */


/************************************************************************/
/* Function	   : serGetRxOvflCnt										*/
/* Purpose	   : Get overflow count for a TPU serial port				*/
/* Inputs	   : Physical serial port number							*/
/* Output	   : None													*/
/************************************************************************/
Nat32	serGetRxOvflCnt(Port_t port)
{
	if (port < PIC_UARTS)
		return(serStr[port].rxOverFlowCnt);
	else
		return(0);
	
} /* serGetRxOvflCnt() */


/************************************************************************/
/* 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(Port_t port, TickType_t count)
{
	switch(port)
	{
	  case 0:
		  U5MODEbits.ON = 0;
		  vTaskDelay(count);
		  U5MODEbits.ON = 1;
		  break;

	  case 1:
		  U1MODEbits.ON = 0;
		  vTaskDelay(count);
		  U1MODEbits.ON = 1;
		  break;

	  case 2:
		  U2MODEbits.ON = 0;
		  vTaskDelay(count);
		  U2MODEbits.ON = 1;
		  break;

	  case 3:
		  U3MODEbits.ON = 0;
		  vTaskDelay(count);
		  U3MODEbits.ON = 1;
		  break;

	  case 4:
		  U4MODEbits.ON = 0;
		  vTaskDelay(count);
		  U4MODEbits.ON = 1;
		  break;
	}

} /* ser_break() */


/************************************************************************/
/* Function	   : setSerModbus											*/
/* Purpose	   : Set up serial port for ModBus transactions				*/
/* Inputs	   : Physical serial port number, boolean for start/stop	*/
/* Output	   : None													*/
/************************************************************************/
void setSerModbus(Port_t port, MBool start)
{
    switch(port)
    {
		case SER_PORT_1:
			U1STACLR = _U1STA_URXEN_MASK;		/* Disable Rx at first	*/
			dig1Snd485();						/* Enable driver Tx		*/
			IEC1CLR = (_IEC1_U1RXIE_MASK | _IEC1_U1TXIE_MASK);
			IFS1CLR = (_IFS1_U1RXIF_MASK | _IFS1_U1TXIF_MASK);
			if (start)
				IEC1SET = _IEC1_U1TXIE_MASK;
			break;

		case SER_PORT_2:
			U2STACLR = _U2STA_URXEN_MASK;		/* Disable Rx at first	*/
			dig2Snd485();						/* Enable driver Tx		*/
			IEC1CLR = (_IEC1_U2RXIE_MASK | _IEC1_U2TXIE_MASK);
			IFS1CLR = (_IFS1_U2RXIF_MASK | _IFS1_U2TXIF_MASK);
			if (start)
				IEC1SET = _IEC1_U2TXIE_MASK;
			break;

		case SER_PORT_3:
			U3STACLR = _U3STA_URXEN_MASK;		/* Disable Rx at first	*/
			dig3Snd485();						/* Enable driver Tx		*/
			IEC1CLR = _IEC1_U3RXIE_MASK;
			IEC2CLR = _IEC2_U3TXIE_MASK;
			IFS1CLR = _IFS1_U3RXIF_MASK;
			IFS2CLR = _IFS2_U3TXIF_MASK;
			if (start)
				IEC2SET = _IEC2_U3TXIE_MASK;
			break;

		case SER_PORT_4:
			U4STACLR = _U4STA_URXEN_MASK;		/* Disable Rx at first	*/
			dig4Snd485();						/* Enable driver Tx		*/
			IEC2CLR = (_IEC2_U4RXIE_MASK | _IEC2_U4TXIE_MASK);
			IFS2CLR = (_IFS2_U4RXIF_MASK | _IFS2_U4TXIF_MASK);
			if (start)
				IEC2SET = _IEC2_U4TXIE_MASK;
			break;
	}
}


/****************************************************************************/
/*			Routines to enable wakeup from sleep on Console Rx				*/
/****************************************************************************/

void setupConsoleWakeFromSleep(void)
{
	if (wakeOnConsoleChar & 1)
		U5MODESET = _U5MODE_WAKE_MASK;
	if (wakeOnConsoleChar & 2)
		U1MODESET = _U1MODE_WAKE_MASK;
	if (wakeOnConsoleChar & 4)
		U2MODESET = _U2MODE_WAKE_MASK;
	if (wakeOnConsoleChar & 8)
		U3MODESET = _U3MODE_WAKE_MASK;
	if (wakeOnConsoleChar & 0x10)
		U4MODESET = _U4MODE_WAKE_MASK;
}

void clearConsoleWakeFromSleep(void)
{
	U1MODECLR = _U1MODE_WAKE_MASK;
	U2MODECLR = _U2MODE_WAKE_MASK;
	U3MODECLR = _U3MODE_WAKE_MASK;
	U4MODECLR = _U4MODE_WAKE_MASK;
	U5MODECLR = _U5MODE_WAKE_MASK;
}

MBool consoleWakeupReceived(void)
{
	if (IFS2bits.U5RXIF && (wakeOnConsoleChar & 1))
		return(TRUE);

	if (IFS1bits.U1RXIF && (wakeOnConsoleChar & 2))
		return(TRUE);

	if (IFS1bits.U2RXIF && (wakeOnConsoleChar & 4))
		return(TRUE);

	if (IFS1bits.U3RXIF && (wakeOnConsoleChar & 8))
		return(TRUE);

	if (IFS2bits.U4RXIF && (wakeOnConsoleChar & 0x10))
		return(TRUE);

	return(FALSE);
}


/****************************************************************************/
/*			Routines to get/set xon status, for testing						*/
/****************************************************************************/

/* Bit 0 = enabled, Bit 1 = status */
int serGetXonStatus(Port_t port, time_t *xtime)
{
	Reg SerStruct	*ssp;

	if (port >= PIC_UARTS)
		return(0);

	ssp = &serStr[port];

	if (xtime)
		*xtime = ssp->xoff_time;
	
	return((ssp->do_xon ? 1 : 0) | (ssp->xoff_status ? 2 : 0));
}

void serSetXonStatus(Port_t port, MBool onoff)
{
	if (port < PIC_UARTS)
		serStr[port].do_xon = (onoff ? TRUE : FALSE);
}


/************************************************************************/
/* Function    : restartModSerTimer										*/
/* Purpose     : Reset Modbus timer count to keep it going              */
/* Inputs      : None                                                   */
/* Outputs     : None                                                   */
/************************************************************************/
inline void restartModSerTimer(void)
{
    TMR3 = 0x0000;                      /* clear timer count            */
    IFS0CLR = _IFS0_T3IF_MASK;          /* clear interrupt flag         */
}


/****************************************************************************/
/*                       Interrupt Service Routines                         */
/* Note: the standard buffer functions are not used here as calling external*/
/* functions from an interrupt service routine increases interrupt overhead */
/* However, buffer macros ARE used.											*/
/* Note also that these are the interrupt handlers for both the standard	*/
/* serial instrument ports, as well as serial Modbus ports.					*/
/****************************************************************************/

/************************************************************************/
/* Function    : _U5IntHandler                                          */
/* Purpose     : Interrupt handler for UART 5 (Ser Port 0, console)     */
/* Inputs      : None                                                   */
/* Outputs     : None                                                   */
/************************************************************************/
void __attribute__( (interrupt(IPL4SOFT), vector(_UART_5_VECTOR)) ) _U5IntHandler(void)
{
    Byte 			rcvByte;
	Reg SerStruct	*ssp;

	ssp = &serStr[0];
	if (ssp->serType == INST_PORT)
	{
		if (IFS2bits.U5RXIF)
		{
			/* grab all the bytes from the buffer */
			while ( U5STAbits.URXDA )
			{
				rcvByte = (Byte)U5RXREG;

				if (ssp->do_xon && (rcvByte == XON))
				{
					ssp->xoff_status = FALSE;
					IEC2SET = _IEC2_U5TXIE_MASK;
				}
				else if (ssp->do_xon && (rcvByte == XOFF))
				{
					ssp->xoff_status = TRUE;
					ssp->xoff_time = clkTime();	// Remember when
					IEC2CLR = _IEC2_U5TXIE_MASK;
				}

				/* if the buffer is full, mark overflow error   */
				else if (bufIsFull(ssp->rxBuff))
				{
					ssp->rxOverFlowCnt++;		/* inc overflow count and bail */
				}
				else   /* if there is room in the buffer put the byte in it */
				{
					*ssp->rxBuff.inptr++ = rcvByte;
					ssp->rxBuff.occupied++;

					if (ssp->rxBuff.inptr >= ssp->rxBuff.endptr)
						ssp->rxBuff.inptr = ssp->rxBuff.startptr;
				}
			}

			if ((serPortsToWake & 1) && oasisTask)
			{
				serPortsWoke |= 1;
				wakeupPinOn();
				vTaskNotifyGiveFromISR(oasisTask, NULL);
			}
		}
		
        /* clear the interrupt      */
        IFS2CLR = _IFS2_U5RXIF_MASK;

		if (IFS2bits.U5TXIF)
		{
			while (IEC2bits.U5TXIE && (U5STAbits.UTXBF == 0))
			{
				if (ssp->txBuff.occupied)
				{
					U5TXREG = *ssp->txBuff.outptr++;
					ssp->txBuff.occupied--;

					if (ssp->txBuff.outptr >= ssp->txBuff.endptr)
						ssp->txBuff.outptr = ssp->txBuff.startptr;
				}
				else
					IEC2CLR = _IEC2_U5TXIE_MASK;
			}

			/* clear the interrupt      */
			IFS2CLR = _IFS2_U5TXIF_MASK;
		}
	}
}


/************************************************************************/
/* Function    : _U1IntHandler                                          */
/* Purpose     : Interrupt handler for UART 1 (Ser Port 1)              */
/* Inputs      : None                                                   */
/* Outputs     : None                                                   */
/************************************************************************/
void __attribute__( (interrupt(IPL4SOFT), vector(_UART_1_VECTOR)) ) _U1IntHandler(void)
{
    Byte 			rcvByte;
	Reg SerStruct	*ssp;

	serPinOn();
	ssp = &serStr[1];
	if (ssp->serType == INST_PORT)
	{
		if (IFS1bits.U1RXIF)
		{
			/* grab all the bytes from the buffer */
			while ( U1STAbits.URXDA )
			{
				rcvByte = (Byte)U1RXREG;

				if (ssp->do_xon && (rcvByte == XON))
				{
					ssp->xoff_status = FALSE;
					IEC1SET = _IEC1_U1TXIE_MASK;
				}
				else if (ssp->do_xon && (rcvByte == XOFF))
				{
					ssp->xoff_status = TRUE;
					ssp->xoff_time = clkTime();	// Remember when
					IEC1CLR = _IEC1_U1TXIE_MASK;
				}

				/* if the buffer is full, mark overflow error   */
				else if (bufIsFull(ssp->rxBuff))
				{
					ssp->rxOverFlowCnt++;		/* inc overflow count and bail */
				}
				else   /* if there is room in the buffer put the byte in it */
				{
					*ssp->rxBuff.inptr++ = rcvByte;
					ssp->rxBuff.occupied++;

					if (ssp->rxBuff.inptr >= ssp->rxBuff.endptr)
						ssp->rxBuff.inptr = ssp->rxBuff.startptr;
				}
			}

			if ((serPortsToWake & 2) && oasisTask)
			{
				serPortsWoke |= 2;
				wakeupPinOn();
				vTaskNotifyGiveFromISR(oasisTask, NULL);
			}
		}
		
        /* clear the interrupt      */
        IFS1CLR = _IFS1_U1RXIF_MASK;

		if (IFS1bits.U1TXIF)
		{
			while (IEC1bits.U1TXIE && (U1STAbits.UTXBF == 0))
			{
				if (ssp->txBuff.occupied)
				{
					U1TXREG = *ssp->txBuff.outptr++;
					ssp->txBuff.occupied--;

					if (ssp->txBuff.outptr >= ssp->txBuff.endptr)
						ssp->txBuff.outptr = ssp->txBuff.startptr;
				}
				else
					IEC1CLR = _IEC1_U1TXIE_MASK;
			}

			/* clear the interrupt      */
			IFS1CLR = _IFS1_U1TXIF_MASK;
		}
	}
	else	/* ModBus Interrupt Handler	*/
	{
		switch(modSerData.state)
		{
		  case MB_SEND:
			  while (U1STAbits.UTXBF == 0)
			  {
				  U1TXREG = modSerData.sndBuf[modSerData.sndPos++];
				  if (modSerData.sndPos >= modSerData.sndLen)
				  {
					  U1STAbits.UTXISEL = 0x01;		/* Interupt when snd complete*/
					  modSerData.state = MB_WAIT_SENT; /* Wait for Tx empty		*/
					  break;
				  }
			  }
			  break;

		  case MB_WAIT_SENT:
			  while (U1STAbits.TRMT == 0)       /* In case FIFO isn't empty*/
				  ;                             /* when we get this int   */

			  modSerData.state = MB_WAIT_RCV;
			  modSerData.rcvLen = 0;

			  IEC1CLR = _IEC1_U1TXIE_MASK;      /* Disable Tx ints		*/
			  dig1Rcv485();
			  U1STASET = _U1STA_URXEN_MASK;		/* Enable Rx			*/
			  U1STACLR = (_U1STA_OERR_MASK | _U1STA_UTXISEL_MASK);
						  /* Restore Tx int type, clear rcv ovfl		*/

			  while (U1STAbits.URXDA)
				  dummy = U1RXREG;              /* Clear rcv FIFO		*/

			  IEC1SET = _IEC1_U1RXIE_MASK;      /* Enable Rx ints		*/
			  break;
			  
		  case MB_WAIT_RCV:
			  if (U1STAbits.URXDA)
			  {
				  modSerData.rcvBuf[modSerData.rcvLen++] = U1RXREG;
				  restartModSerTimer();			/* Start Modbus timer      */
				  IEC0SET = _IEC0_T3IE_MASK;	/* Turn on timer interrupts*/
				  modSerData.state = MB_RCV;
			  }
			  break;
			   
		  case MB_RCV:
			  while (U1STAbits.URXDA)
			  {
				  modSerData.rcvBuf[modSerData.rcvLen++] = U1RXREG;
				  restartModSerTimer();
			  }
			  break;

		  default:
			  IEC1CLR = (_IEC1_U1RXIE_MASK | _IEC1_U1TXIE_MASK);
			  break;
		}

		IFS1CLR = (_IFS1_U1TXIF_MASK | _IFS1_U1RXIF_MASK);
	}

	serPinOff();
}

/************************************************************************/
/* Function    : _U2IntHandler                                          */
/* Purpose     : Interrupt handler for UART 2 (Ser Port 2)              */
/* Inputs      : None                                                   */
/* Outputs     : None                                                   */
/************************************************************************/
void __attribute__( (interrupt(IPL4SOFT), vector(_UART_2_VECTOR)) ) _U2IntHandler(void)
{
    Byte 			rcvByte;
	Reg SerStruct	*ssp;

	ssp = &serStr[2];
	if (ssp->serType == INST_PORT)
	{
		if (IFS1bits.U2RXIF)
		{
			/* grab all the bytes from the buffer */
			while ( U2STAbits.URXDA )
			{
				rcvByte = (Byte)U2RXREG;

				if (ssp->do_xon && (rcvByte == XON))
				{
					ssp->xoff_status = FALSE;
					IEC1SET = _IEC1_U2TXIE_MASK;
				}
				else if (ssp->do_xon && (rcvByte == XOFF))
				{
					ssp->xoff_status = TRUE;
					ssp->xoff_time = clkTime();	// Remember when
					IEC1CLR = _IEC1_U2TXIE_MASK;
				}

				/* if the buffer is full, mark overflow error   */
				else if (bufIsFull(ssp->rxBuff))
				{
					ssp->rxOverFlowCnt++;		/* inc overflow count and bail */
				}
				else   /* if there is room in the buffer put the byte in it */
				{
					*ssp->rxBuff.inptr++ = rcvByte;
					ssp->rxBuff.occupied++;

					if (ssp->rxBuff.inptr >= ssp->rxBuff.endptr)
						ssp->rxBuff.inptr = ssp->rxBuff.startptr;
				}
			}

			if ((serPortsToWake & 4) && oasisTask)
			{
				serPortsWoke |= 4;
				wakeupPinOn();
				vTaskNotifyGiveFromISR(oasisTask, NULL);
			}
		}
		
        /* clear the interrupt      */
        IFS1CLR = _IFS1_U2RXIF_MASK;

		if (IFS1bits.U2TXIF)
		{
			while (IEC1bits.U2TXIE && (U2STAbits.UTXBF == 0))
			{
				if (ssp->txBuff.occupied)
				{
					U2TXREG = *ssp->txBuff.outptr++;
					ssp->txBuff.occupied--;

					if (ssp->txBuff.outptr >= ssp->txBuff.endptr)
						ssp->txBuff.outptr = ssp->txBuff.startptr;
				}
				else
					IEC1CLR = _IEC1_U2TXIE_MASK;
			}

			/* clear the interrupt      */
			IFS1CLR = _IFS1_U2TXIF_MASK;
		}
	}
	else	/* ModBus Interrupt Handler	*/
	{
		switch(modSerData.state)
		{
		  case MB_SEND:
			  while (U2STAbits.UTXBF == 0)
			  {
				  U2TXREG = modSerData.sndBuf[modSerData.sndPos++];
				  if (modSerData.sndPos >= modSerData.sndLen)
				  {
					  U2STAbits.UTXISEL = 0x01;		/* Interupt when snd complete*/
					  modSerData.state = MB_WAIT_SENT; /* Wait for Tx empty		*/
					  break;
				  }
			  }
			  break;

		  case MB_WAIT_SENT:
			  while (U2STAbits.TRMT == 0)       /* In case FIFO isn't empty*/
				  ;                             /* when we get this int   */

			  modSerData.state = MB_WAIT_RCV;
			  modSerData.rcvLen = 0;

			  IEC1CLR = _IEC1_U2TXIE_MASK;      /* Disable Tx ints		*/
			  dig2Rcv485();
			  U2STASET = _U2STA_URXEN_MASK;		/* Enable Rx			*/
			  U2STACLR = (_U2STA_OERR_MASK | _U2STA_UTXISEL_MASK);
						  /* Restore Tx int type, clear rcv ovfl		*/

			  while (U2STAbits.URXDA)
				  dummy = U2RXREG;              /* Clear rcv FIFO		*/

			  IEC1SET = _IEC1_U2RXIE_MASK;      /* Enable Rx ints		*/
			  break;
			  
		  case MB_WAIT_RCV:
			  if (U2STAbits.URXDA)
			  {
				  modSerData.rcvBuf[modSerData.rcvLen++] = U2RXREG;
				  restartModSerTimer();			/* Start Modbus timer      */
				  IEC0SET = _IEC0_T3IE_MASK;	/* Turn on timer interrupts*/
				  modSerData.state = MB_RCV;
			  }
			  break;
			   
		  case MB_RCV:
			  while (U2STAbits.URXDA)
			  {
				  modSerData.rcvBuf[modSerData.rcvLen++] = U2RXREG;
				  restartModSerTimer();
			  }
			  break;

		  default:
			  IEC1CLR = (_IEC1_U2RXIE_MASK | _IEC1_U2TXIE_MASK);
			  break;
		}

		IFS1CLR = (_IFS1_U2TXIF_MASK | _IFS1_U2RXIF_MASK);
	}
}


/************************************************************************/
/* Function    : _U3IntHandler                                          */
/* Purpose     : Interrupt handler for UART 3 (Ser Port 3)              */
/* Inputs      : None                                                   */
/* Outputs     : None                                                   */
/************************************************************************/
void __attribute__( (interrupt(IPL4SOFT), vector(_UART_3_VECTOR)) ) _U3IntHandler(void)
{
    Byte 			rcvByte;
	Reg SerStruct	*ssp;

	ssp = &serStr[3];
	if (ssp->serType == INST_PORT)
	{
		if (IFS1bits.U3RXIF)
		{
			/* grab all the bytes from the buffer */
			while ( U3STAbits.URXDA )
			{
				rcvByte = (Byte)U3RXREG;

				if (ssp->do_xon && (rcvByte == XON))
				{
					ssp->xoff_status = FALSE;
					IEC2SET = _IEC2_U3TXIE_MASK;
				}
				else if (ssp->do_xon && (rcvByte == XOFF))
				{
					ssp->xoff_status = TRUE;
					ssp->xoff_time = clkTime();	// Remember when
					IEC2CLR = _IEC2_U3TXIE_MASK;
				}

				/* if the buffer is full, mark overflow error   */
				else if (bufIsFull(ssp->rxBuff))
				{
					ssp->rxOverFlowCnt++;		/* inc overflow count and bail */
				}
				else   /* if there is room in the buffer put the byte in it */
				{
					*ssp->rxBuff.inptr++ = rcvByte;
					ssp->rxBuff.occupied++;

					if (ssp->rxBuff.inptr >= ssp->rxBuff.endptr)
						ssp->rxBuff.inptr = ssp->rxBuff.startptr;
				}
			}

			/* clear the interrupt      */
			IFS1CLR = _IFS1_U3RXIF_MASK;

			if ((serPortsToWake & 8) && oasisTask)
			{
				serPortsWoke |= 8;
				wakeupPinOn();
				vTaskNotifyGiveFromISR(oasisTask, NULL);
			}
		}

		if (IFS2bits.U3TXIF)
		{
			while (IEC2bits.U3TXIE && (U3STAbits.UTXBF == 0))
			{
				if (ssp->txBuff.occupied)
				{
					U3TXREG = *ssp->txBuff.outptr++;
					ssp->txBuff.occupied--;

					if (ssp->txBuff.outptr >= ssp->txBuff.endptr)
						ssp->txBuff.outptr = ssp->txBuff.startptr;
				}
				else
					IEC2CLR = _IEC2_U3TXIE_MASK;
			}

			/* clear the interrupt      */
			IFS2CLR = _IFS2_U3TXIF_MASK;
		}
	}
	else	/* ModBus Interrupt Handler	*/
	{
		switch(modSerData.state)
		{
		  case MB_SEND:
			  while (U3STAbits.UTXBF == 0)
			  {
				  U3TXREG = modSerData.sndBuf[modSerData.sndPos++];
				  if (modSerData.sndPos >= modSerData.sndLen)
				  {
					  U3STAbits.UTXISEL = 0x01;		/* Interupt when snd complete*/
					  modSerData.state = MB_WAIT_SENT; /* Wait for Tx empty		*/
					  break;
				  }
			  }
			  break;

		  case MB_WAIT_SENT:
			  while (U3STAbits.TRMT == 0)       /* In case FIFO isn't empty*/
				  ;                             /* when we get this int   */

			  modSerData.state = MB_WAIT_RCV;
			  modSerData.rcvLen = 0;

			  IEC2CLR = _IEC2_U3TXIE_MASK;      /* Disable Tx ints		*/
			  U3STASET = _U3STA_URXEN_MASK;		/* Enable Rx			*/
			  U3STACLR = (_U3STA_OERR_MASK | _U3STA_UTXISEL_MASK);
						  /* Restore Tx int type, clear rcv ovfl		*/

			  while (U3STAbits.URXDA)
				  dummy = U3RXREG;              /* Clear rcv FIFO		*/

			  IEC1SET = _IEC1_U3RXIE_MASK;      /* Enable Rx ints		*/
			  break;
			  
		  case MB_WAIT_RCV:
			  if (U3STAbits.URXDA)
			  {
				  modSerData.rcvBuf[modSerData.rcvLen++] = U3RXREG;
				  restartModSerTimer();			/* Start Modbus timer      */
				  IEC0SET = _IEC0_T3IE_MASK;	/* Turn on timer interrupts*/
				  modSerData.state = MB_RCV;
			  }
			  break;
			   
		  case MB_RCV:
			  while (U3STAbits.URXDA)
			  {
				  modSerData.rcvBuf[modSerData.rcvLen++] = U3RXREG;
				  restartModSerTimer();
			  }
			  break;

		  default:
			  IEC1CLR = _IEC1_U3RXIE_MASK;
			  IEC2CLR = _IEC2_U3TXIE_MASK;
			  break;
		}

		IFS1CLR = _IFS1_U3RXIF_MASK;
		IFS2CLR = _IFS2_U3TXIF_MASK;
	}
}

/************************************************************************/
/* Function    : _U4IntHandler                                          */
/* Purpose     : Interrupt handler for UART 4 (Ser Port 4)              */
/* Inputs      : None                                                   */
/* Outputs     : None                                                   */
/************************************************************************/
void __attribute__( (interrupt(IPL4SOFT), vector(_UART_4_VECTOR)) ) _U4IntHandler(void)
{
    Byte 			rcvByte;
	Reg SerStruct	*ssp;

	ssp = &serStr[4];
	if (ssp->serType == INST_PORT)
	{
		if (IFS2bits.U4RXIF)
		{
			/* grab all the bytes from the buffer */
			while ( U4STAbits.URXDA )
			{
				rcvByte = (Byte)U4RXREG;

				if (ssp->do_xon && (rcvByte == XON))
				{
					ssp->xoff_status = FALSE;
					IEC2SET = _IEC2_U4TXIE_MASK;
				}
				else if (ssp->do_xon && (rcvByte == XOFF))
				{
					ssp->xoff_status = TRUE;
					ssp->xoff_time = clkTime();	// Remember when
					IEC2CLR = _IEC2_U4TXIE_MASK;
				}

				/* if the buffer is full, mark overflow error   */
				else if (bufIsFull(ssp->rxBuff))
				{
					ssp->rxOverFlowCnt++;		/* inc overflow count and bail */
				}
				else   /* if there is room in the buffer put the byte in it */
				{
					*ssp->rxBuff.inptr++ = rcvByte;
					ssp->rxBuff.occupied++;

					if (ssp->rxBuff.inptr >= ssp->rxBuff.endptr)
						ssp->rxBuff.inptr = ssp->rxBuff.startptr;
				}
			}

			if ((serPortsToWake & 0x10) && oasisTask)
			{
				serPortsWoke |= 0x10;
				wakeupPinOn();
				vTaskNotifyGiveFromISR(oasisTask, NULL);
			}
		}
		
        /* clear the interrupt      */
        IFS2CLR = _IFS2_U4RXIF_MASK;

		if (IFS2bits.U4TXIF)
		{
			while (IEC2bits.U4TXIE && (U4STAbits.UTXBF == 0))
			{
				if (ssp->txBuff.occupied)
				{
					U4TXREG = *ssp->txBuff.outptr++;
					ssp->txBuff.occupied--;

					if (ssp->txBuff.outptr >= ssp->txBuff.endptr)
						ssp->txBuff.outptr = ssp->txBuff.startptr;
				}
				else
					IEC2CLR = _IEC2_U4TXIE_MASK;
			}

			/* clear the interrupt      */
			IFS2CLR = _IFS2_U4TXIF_MASK;
		}
	}
	else	/* ModBus Interrupt Handler	*/
	{
		switch(modSerData.state)
		{
		  case MB_SEND:
			  while (U4STAbits.UTXBF == 0)
			  {
				  U4TXREG = modSerData.sndBuf[modSerData.sndPos++];
				  if (modSerData.sndPos >= modSerData.sndLen)
				  {
					  U4STAbits.UTXISEL = 0x01;		/* Interupt when snd complete*/
					  modSerData.state = MB_WAIT_SENT; /* Wait for Tx empty		*/
					  break;
				  }
			  }
			  break;

		  case MB_WAIT_SENT:
			  while (U4STAbits.TRMT == 0)       /* In case FIFO isn't empty*/
				  ;                             /* when we get this int   */

			  modSerData.state = MB_WAIT_RCV;
			  modSerData.rcvLen = 0;

			  IEC2CLR = _IEC2_U4TXIE_MASK;      /* Disable Tx ints		*/
			  dig4Rcv485();
			  U4STASET = _U4STA_URXEN_MASK;		/* Enable Rx			*/
			  U4STACLR = (_U4STA_OERR_MASK | _U4STA_UTXISEL_MASK);
						  /* Restore Tx int type, clear rcv ovfl		*/

			  while (U4STAbits.URXDA)
				  dummy = U4RXREG;              /* Clear rcv FIFO		*/

			  IEC2SET = _IEC2_U4RXIE_MASK;      /* Enable Rx ints		*/
			  break;
			  
		  case MB_WAIT_RCV:
			  if (U4STAbits.URXDA)
			  {
				  modSerData.rcvBuf[modSerData.rcvLen++] = U4RXREG;
				  restartModSerTimer();			/* Start Modbus timer      */
				  IEC0SET = _IEC0_T3IE_MASK;	/* Turn on timer interrupts*/
				  modSerData.state = MB_RCV;
			  }
			  break;
			   
		  case MB_RCV:
			  while (U4STAbits.URXDA)
			  {
				  modSerData.rcvBuf[modSerData.rcvLen++] = U4RXREG;
				  restartModSerTimer();
			  }
			  break;

		  default:
			  IEC2CLR = (_IEC2_U4RXIE_MASK | _IEC2_U4TXIE_MASK);
			  break;
		}

		IFS2CLR = (_IFS2_U4TXIF_MASK | _IFS2_U4RXIF_MASK);
	}
}

#ifdef DEBUG_SERIAL_CMD

/************************************************************************/
/* Function	   : serDump												*/
/* Purpose	   : Show status of serial channel							*/
/* Inputs	   : Parm Mask, Channel number								*/
/* Outputs	   : OK														*/
/************************************************************************/
int serDump(Nat32 pmask, Parm_t serchan)
{
	int				chan, nchans, i;
	MBool			haveModBus = FALSE;
	Reg SerStruct	*ssp;

	if (pmask == 0)
	{
		serchan = 0;
		nchans = PIC_UARTS;
	}
	else
		nchans = 1;

	if (serchan + nchans > PIC_UARTS)
		xprintf("Bad channel number\n");
	else
	{
		for (chan = serchan; chan < serchan+nchans; chan++)
		{
			ssp = &serStr[chan];
			xprintf("Serial Channel %u:  ", chan);

			if (ssp->serType == INST_PORT)
			{
				xprintf("Xon/Xoff ", chan);
				if (ssp->do_xon)
					xprintf("ENABLED, current status %s\n", 
							ssp->xoff_status ? "XOFF" : "XON");
				else
					xprintf("DISABLED\n");

				xprintf("Tx buf: 0x%x  size %3u  occupied %3u  inptr 0x%x  outptr 0x%x\n",
						ssp->txBuff.startptr, ssp->txBuff.size,
						bufAvailable(ssp->txBuff), ssp->txBuff.inptr,
						ssp->txBuff.outptr);

				xprintf("Rx buf: 0x%x  size %3u  occupied %3u  inptr 0x%x  outptr 0x%x\n",
						ssp->rxBuff.startptr, ssp->rxBuff.size,
						bufAvailable(ssp->rxBuff), ssp->rxBuff.inptr,
						ssp->rxBuff.outptr);
			}
			else
			{
				xprintf("ModBus\n");
				haveModBus = TRUE;
			}
		}
	}

	if (haveModBus)
	{
		xprintf("Last ModBus send/rcv: %u/%u bytes\n",
				modSerData.sndLen, modSerData.rcvLen);
		for (i = 0; i < modSerData.sndLen; i++)
			xprintf("%02x ", modSerData.sndBuf[i]);
		newline();
		for (i = 0; i < modSerData.rcvLen; i++)
			xprintf("%02x ", modSerData.rcvBuf[i]);
		newline();
	}

	return(OK);

} /* serDump() */

#endif
