/****************************************************************************/
/* Copyright 2015-2018 MBARI                                                */
/****************************************************************************/
/* Summary  : Serial I/O for BEDS2 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								*/
/* 20feb2018 rah - Modified for BEDS2										*/
/****************************************************************************/

#include <xc.h>

#include <mbariTypes.h>					/* MBARI type definitions			*/
#include <time.h>						/* Std C time.h						*/
#include <beds.h>						/* BEDS2 controller definitions		*/

#include <serial.h>
#include <buffer.h>
#include <dig_io.h>
#include <clock.h>
#include <debug.h>						/* For debug pin definitions		*/

#define SER_POLL	(TICKS_PER_SEC/50)	/* Serial polling rate in ticks		*/


/********************************/
/*		External Data			*/
/********************************/

Extern Int32	modemPort, modemBaud, debugBaud, usrTmout;
Extern MBool	localEcho;


/****************************************/
/*		Module Local Data				*/
/****************************************/

MLocal SerPort	serPortInUse = 1;

MLocal unsigned int iec1_RxBits[] = 
    {0, _IEC1_U1RXIE_MASK, _IEC1_U2RXIE_MASK, _IEC1_U3RXIE_MASK, 0};

MLocal unsigned int iec1_TxBits[] = 
    {0, _IEC1_U1TXIE_MASK, _IEC1_U2TXIE_MASK, 0, 0};

MLocal SerStruct	serStr[PIC_UARTS];


/************************************************************************/
/* Function    : serInit                                                */
/* Purpose     : Initialize serial I/O Module                           */
/* Inputs      : None                                                   */
/* Outputs     : None                                                   */
/************************************************************************/
Errno serInit(void)
{
    int			i;
	SerStruct	*ssp;

    /* 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                     */

    /* initialize serial data */
    for (i = 0, ssp = serStr; i < PIC_UARTS; i++, ssp++)
    {
        bufInit(&ssp->rxBuff,
				&ssp->rxBuffData[0], RX_BUFF_SIZE);
        bufInit(&ssp->txBuff,
				&ssp->txBuffData[0], TX_BUFF_SIZE);

        ssp->rxOverFlowCnt = 0;				/* Init overflow cnt		*/
    }
    

	setSerialBaud(modemPort, modemBaud);	/* Sets up UxMODE and UxSTA */
	setSerialBaud(((modemPort == 1) ? 2 : 1), debugBaud);


/******************** 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;
    
	return(OK);
}


/************************************************************************/
/* Function	   : getSerialStruct										*/
/* Purpose	   : Get ptr to SerStruct									*/
/* Input	   : Physical serial port number							*/
/* Output	   : Ptr to SerStruct, or NULL								*/
/************************************************************************/
SerStruct *getSerialStruct(SerPort serport)
{
	if (serport && (serport <= PIC_UARTS))	
		return(&serStr[serport-1]);
	else
		return(NULL);
	
} /* getSerialStruct() */


/************************************************************************/
/* Function	   : setConsole												*/
/* Purpose	   : Set the user I/F console								*/
/* Inputs	   : SerPort enumeration, boolean if on modem				*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
Errno setConsole(SerPort port)
{
	if ((port==0) || (port > PIC_UARTS))	
		return(ERROR);

	serPortInUse = port;
	return(OK);
}


/************************************************************************/
/* Function	   : getConsole												*/
/* Purpose	   : Get the user I/F console								*/
/* Inputs	   : None													*/
/* Outputs	   : Serial port number										*/
/************************************************************************/
SerPort getConsole(void)
{
	return(serPortInUse);
}


/************************************************************************/
/* Function	   : setSerialBaud											*/
/* Purpose	   : Set Baud rate on one serial port						*/
/* Inputs	   : Serial port to init, baud rate							*/
/* Outputs	   : OK or ERROR											*/
/************************************************************************/
Errno setSerialBaud(SerPort port, Nat32 baud)
{
    switch(port)
    {
	    case SER_PORT_1:
			U1MODE = UxMODE;
			if (baud <= 19200L)
				U1BRG = StdBRG(baud);
			else
			{
				U1BRG = FastBRG(baud);
				U1MODEbits.BRGH = 1;
			}

			U1STA = UxSTA;
			serStr[0].baudRate = baud;
			break;

        case SER_PORT_2:
            U2MODE = UxMODE;

            if (baud <= 19200L)
                U2BRG = StdBRG(baud);
            else
            {
                U2BRG = FastBRG(baud);
                U2MODEbits.BRGH = 1;
            }

            U2STA = UxSTA;
			serStr[1].baudRate = baud;
            break;

	    default:
			return(ERROR);
    }

	return(OK);
}


/************************************************************************/
/* Function	   : ser_putc												*/
/* Purpose	   : Write one character to serial port						*/
/* Inputs	   : Physical port number, character						*/
/* Output	   : MBool, TRUE if character sent							*/
/************************************************************************/
MBool ser_putc(SerPort port, Byte b)
{
    MBool			rtn;

	if ((port==0) || (port > PIC_UARTS))	
		return(FALSE);

	IEC1CLR = iec1_TxBits[port];    	/* Turn off ints from the UART  */
	rtn = bufPut(b, &serStr[port-1].txBuff);
	IEC1SET = iec1_TxBits[port];		/* Turn on UART Tx ints        */
	return(rtn);
}


/************************************************************************/
/* Function	   : ser_getc												*/
/* Purpose	   : Read one character from serial port					*/
/* Inputs	   : Physical serial port number							*/
/* Output	   : Character, or ERROR if none							*/
/************************************************************************/
int ser_getc(SerPort port)
{
    int     stat;
	Byte	c;

	if ((port==0) || (port > PIC_UARTS))	
		return(ERROR);

	IEC1CLR = iec1_RxBits[port];		/* Turn off ints from the UART  */
	stat = bufGet(&c, &serStr[port-1].rxBuff);
	IEC1SET = iec1_RxBits[port];		/* Turn UART ints back on       */
	return(stat ? (int)c : ERROR);
}


/************************************************************************/
/* Function	   : ser_getc_tmout											*/
/* Purpose	   : Read one character from serial port with timeout		*/
/* Inputs	   : Physical serial port number, timeout in ticks			*/
/* Output	   : Character, or ERROR if none							*/
/************************************************************************/
int ser_getc_tmout(SerPort port, Nat32 ticks)
{
	Nat32	stTick;
    int     stat;
	Byte	c;

	if ((port==0) || (port > PIC_UARTS))	
		return(ERROR);

	if (ticks == 0)
		return(ser_getc(port));
	
	for (stTick = clkGetTick(); (clkGetTick() - stTick) < ticks; )
	{
		IEC1CLR = iec1_RxBits[port];	/* Turn off ints from the UART  */
		stat = bufGet(&c, &serStr[port-1].rxBuff);
		IEC1SET = iec1_RxBits[port];	/* Turn UART ints back on       */
		if (stat)
			return((int)c);
	}

	return(ERROR);
}


/************************************************************************/
/* Function	   : _mon_putc												*/
/* Purpose	   : Write one character to serial port						*/
/* Inputs	   : Character												*/
/* Output	   : None													*/
/* Comment     : Used by stdio for printf(), putc(), etc				*/
/************************************************************************/
void _mon_putc(char c)
{
	while(!ser_putc(serPortInUse, c))
		clkDelayMs(SER_POLL);					/* Wait for space avail */

} /* _mon_putc() */    


/************************************************************************/
/* Function	   : _mon_getc												*/
/* Purpose	   : Read one character from serial port					*/
/* Inputs	   : canblock???  defined by std library					*/
/* Output	   : Character												*/
/* Comment     : Used by stdio for scanf(), getc(), etc					*/
/************************************************************************/
int _mon_getc(int canblock)
{
    int		c;

	while ((c = ser_getc(serPortInUse)) == ERROR)
		clkDelayMs(SER_POLL);					/* Wait for char		*/

	return(c);
}


/************************************************************************/
/* 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==0) || (port > PIC_UARTS))	
		return(0);

	if ((avail = bufAvailable(serStr[port-1].txBuff)) > 0)
		return(avail);

    switch(port)
    {
	  case SER_PORT_1:
		  return(U1STAbits.TRMT ? 0 : 1);

	  case SER_PORT_2:
		  return(U2STAbits.TRMT ? 0 : 1);
	}

	return(0);

} /* ser_sndsts() */


/************************************************************************/
/* Function    : serRxCount                                             */
/* Purpose     : Return number of bytes available on serial port        */
/* Inputs      : Serial port number                                     */
/* Outputs     : Bytes available										*/
/************************************************************************/
int  serRxCount(SerPort port)
{
    return((port && (port <= PIC_UARTS)) ? bufAvailable(serStr[port-1].rxBuff) : 0);
}


/************************************************************************/
/* Function    : kbhit													*/
/* Purpose     : Return number of bytes available on serial port in use	*/
/* Inputs      : None													*/
/* Outputs     : Non-zero if bytes available, zero if none				*/
/************************************************************************/
int kbhit(void)
{
	return(bufAvailable(serStr[serPortInUse-1].rxBuff));
}


/************************************************************************/
/* Function    : serRxFlush                                             */
/* Purpose     : Flush serial input FIFO                                */
/* Inputs      : Serial port number                                     */
/* Outputs     : None                                                   */
/************************************************************************/
void serRxFlush(SerPort port)
{
    unsigned int reg;

    if (port && (port <= PIC_UARTS))
    {
        __builtin_disable_interrupts();

        bufClear(&serStr[port-1].rxBuff);

        switch ( port )
        {
          case 1: while ( U1STAbits.URXDA ) reg = U1RXREG; break;
          case 2: while ( U2STAbits.URXDA ) reg = U2RXREG; break;
        }

        __builtin_enable_interrupts();
    }
}


/************************************************************************/
/* Function    : serTxFlush                                             */
/* Purpose     : Flush serial output FIFO                               */
/* Inputs      : Serial port number                                     */
/* Outputs     : None                                                   */
/************************************************************************/
void serTxFlush(SerPort port)
{
    if (port && (port <= PIC_UARTS))
    {
        __builtin_disable_interrupts();

        bufClear(&serStr[port-1].txBuff);

        switch ( port )
        {
          case 1: U1STAbits.UTXEN = 0; Nop(); 
                  U1STAbits.UTXEN = 1; Nop(); 
                  break;
          case 2: U2STAbits.UTXEN = 0; Nop();
                  U2STAbits.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 && (port <= PIC_UARTS))
    {
		for (i = 0; i < 100; i++)
        {
            if (bufIsEmpty(serStr[port-1].txBuff))
                switch ( port )
                {
                  case 1: if (U1STAbits.TRMT) return; break;
                  case 2: if (U2STAbits.TRMT) return; break;
                }

			clkDelayMs(SER_POLL);
        }
    }
}


/************************************************************************/
/* 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 && (port < PIC_UARTS))
		return(serStr[port-1].rxOverFlowCnt);
	else
		return(0);
	
} /* serGetRxOvflCnt() */


/****************************************************************************/
/*                       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.											*/
/****************************************************************************/

/************************************************************************/
/* 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[0];

	if (IFS1bits.U1RXIF)
	{
		/* grab all the bytes from the buffer */
		while ( U1STAbits.URXDA )
		{
			rcvByte = (Byte)U1RXREG;

			/* if the buffer is full, mark overflow error   */
			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;
			}
		}
		clkSetWakeupVec(1);
	}
		
	/* 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;
	}

	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;

	serPinOn();
	ssp = &serStr[1];

	if (IFS1bits.U2RXIF)
	{
		/* grab all the bytes from the buffer */
		while ( U2STAbits.URXDA )
		{
			rcvByte = (Byte)U2RXREG;

			/* if the buffer is full, mark overflow error   */
			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;
			}
		}
		clkSetWakeupVec(2);
	}
		
	/* 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;
	}

	serPinOff();
}
