/****************************************************************************/
/* Copyright 2009 MBARI.                                                    */
/* MBARI Proprietary Information. All rights reserved.                      */
/****************************************************************************/
#include "serial.h"
#include "buffer.h"
#include "port.h"
#include "dig_in.h"
#include "sys_defs.h"

#include <xc.h>

#include <stdlib.h>
#include <stdio.h>

/****************************************************************************/
/*                            Serial Port Enables                           */
/****************************************************************************/

/* it might be better to move the following macros to sys_def.h
   and replace them with a check like,
 
#ifndef TRIS_UART1
#error "TRIS_UART1 note set, check sys_def.h"
#endif
 
*/

/* define tris and latch bits for uarts */
#if ENABLE_PORT_2
   #define TRIS_TX_UART1   _TRISF3
   #define LAT_TX_UART1    _LATF3
#endif

#if ENABLE_PORT_0
   #define TRIS_TX_UART2 _TRISD8
   #define LAT_TX_UART2 _LATD8
#endif

#if ENABLE_PORT_1
   #define TRIS_TX_UART3 _TRISD1
   #define LAT_TX_UART3 _LATD1 
#endif

/****************************************************************************/
/*                            Serial Buffer Data                            */
/****************************************************************************/
#define RX_BUFF_SIZE    32
static ByteBuffer rxBuff[INST_PORT_TOTAL];
static unsigned char rxBuffData[INST_PORT_TOTAL][RX_BUFF_SIZE];

static unsigned int rxErrorFlags[INST_PORT_TOTAL];
static unsigned int txBreakFlag[INST_PORT_TOTAL];

/* Error flag deines */
#define OVERFLOW_ERR    0x01

#define OVERRUN_ERR     0x02
#define FRAMING_ERR     0x04
#define PARITY_ERR      0x08
/* note: ERR_FLAG_MASK is for overrun, framing, and partiy 
only, it is used to mask off those bits in UxSTA register */
#define ERR_FLAG_MASK   0x0E

/* Baud rate calc defines */
#define BRGH_VAL    1

#if BRGH_VAL
#define BRG_DIV     4
#else
#define BRG_DIV     16
#endif

/* Local functions */
int isPortValid(int port);
unsigned long brgToBaud(unsigned int brg);
unsigned int baudToBrg(unsigned long baud);

/* init registers, init software buffers, install IRQ */
void serInit()
{
    int i;

    /* initialize serial data */
    for (i = 0; i < INST_PORT_TOTAL; ++i)
    {
        /* init buffers */
        rxBuff[i].data = rxBuffData[i];
        rxBuff[i].head = 0;
        rxBuff[i].tail = 0;
        rxBuff[i].ptr_mask = (RX_BUFF_SIZE - 1);
        
        /* init flags */
        rxErrorFlags[i] = 0;
        txBreakFlag[i] = FALSE;
    }

    /***************************** Setup UART 1 *****************************/
#if ENABLE_PORT_2

    /* set baud to 19200 */
    U1BRG = baudToBrg(19200);
    
    /* No parity, one stop bit, no autobaud, polled */
    U1MODEbits.UARTEN = 1;
    U1MODEbits.ABAUD = 0;
    U1MODEbits.BRGH = BRGH_VAL;
    U1MODEbits.PDSEL = 0;
    U1MODEbits.STSEL = 0;
    
    /* Set TX Interrupt Mode (for 485) - interrupt when tsr is clear.*/
    if (IO_Uart0Mode_GetValue()) {
        U1STAbits.UTXISEL0 = 1;
        U1STAbits.UTXISEL1 = 0;
    }  
    
    U1STAbits.UTXEN = 1;

    /* Once the UART is enabled set the TRIS bit for output and 
    set the output latch to the break state. This will cause the 
    port to go into break when the UART is disabled. */
    Nop();
    TRIS_TX_UART1 = 0;
    Nop();
    LAT_TX_UART1 = 0;
    Nop();

    /* Configure uart1 receive and transmit interrupt */

    /* clear any pending interrupts just in case */
    _U1RXIF = 0;
    _U1TXIF = 0;

    /* set the interrupt priority for receive and transmit */
    _U1RXIP = 6;
    _U1TXIP = 2;

    /* enable receive interrupts, enable transmit interrupts */
    _U1RXIE = 1;
    if (IO_Uart0Mode_GetValue())
        _U1TXIE = 1;
    else 
        _U1TXIE = 0;
#endif

    /***************************** Setup UART 2 *****************************/
#if ENABLE_PORT_0
    /* set baud to 19200 */
    U2BRG = baudToBrg(19200);
    
    /* No parity, one stop bit, autobaud, polled */
    U2MODEbits.UARTEN = 1;
    U2MODEbits.ABAUD = 0;
    U2MODEbits.BRGH = BRGH_VAL;
    U2MODEbits.PDSEL = 0;
    U2MODEbits.STSEL = 0;

    U2STAbits.UTXEN = 1;

    /* Once the UART is enabled set the TRIS bit for output and 
    set the output latch to the break state. This will cause the 
    port to go into break when the UART is disabled. */
    Nop();
    TRIS_TX_UART2 = 0;
    Nop();
    LAT_TX_UART2 = 0;
    Nop();

    /* Configure uart2 receive and transmit interrupt */

    /* clear any pending interrupts just in case */
    _U2RXIF = 0;
    _U2TXIF = 0;

    /* set the interrupt priority for receive and transmit */
    _U2RXIP = 6;
    _U2TXIP = 2;

    /* enable receive interrupts, disable transmit interrupts */
    _U2RXIE = 1;
    _U2TXIE = 0;
#endif
    
    /***************************** Setup UART 3 *****************************/
#if ENABLE_PORT_1
    /* set baud to 19200 */
    U3BRG = baudToBrg(19200);
    
    /* No parity, one stop bit, no autobaud, polled */
    U3MODEbits.UARTEN = 1;
    U3MODEbits.ABAUD = 0;
    U3MODEbits.BRGH = BRGH_VAL;
    U3MODEbits.PDSEL = 0;
    U3MODEbits.STSEL = 0;

    U3STAbits.UTXEN = 1;

    /* Once the UART is enabled set the TRIS bit for output and 
    set the output latch to the break state. This will cause the 
    port to go into break when the UART is disabled. */
    Nop();
    TRIS_TX_UART3 = 0;
    Nop();
    LAT_TX_UART3 = 0;
    Nop();

    /* Configure uart3 receive and transmit interrupt */

    /* clear any pending interrupts just in case */
    _U3RXIF = 0;
    _U3TXIF = 0;

    /* set the interrupt priority for receive and transmit */
    _U3RXIP = 6;
    _U3TXIP = 2;

    /* enable receive interrupts, disable transmit interrupts */
    _U3RXIE = 1;
    _U3TXIE = 0;
#endif
}

void serSetEnable(int val)
{
    return;
}

int serGetEnable()
{
    return TRUE;
}

int serSetConfig(int port, unsigned long baud, int data_par, int stop)
{   
    unsigned int brg_val;

    brg_val = baudToBrg(baud);

    switch ( port )
    {
#if ENABLE_PORT_2
        case INST_PORT_2: U1BRG = brg_val;
                          U1MODEbits.PDSEL = data_par;

                          if ( stop == S_2 )
                              U1MODEbits.STSEL = 1;
                          else
                              U1MODEbits.STSEL = 0;

                          break;
#endif

#if ENABLE_PORT_0
        case INST_PORT_0: U2BRG = brg_val;
                          U2MODEbits.PDSEL = data_par;
                          
                          if ( stop == S_2 )
                              U2MODEbits.STSEL = 1;
                          else
                              U2MODEbits.STSEL = 0;

                          break;
#endif

#if ENABLE_PORT_1
        case INST_PORT_1: U3BRG = brg_val;
                          U3MODEbits.PDSEL = data_par;

                          if ( stop == S_2 )
                              U3MODEbits.STSEL = 1;
                          else
                              U3MODEbits.STSEL = 0;

                          break;
#endif

        default: return -1;
    }


    return 0;
}

int serGetConfig(int port, unsigned long* baud, int* data_par, int* stop)
{
    *baud = 0;

    switch ( port )
    {
#if ENABLE_PORT_2
        case INST_PORT_2: *baud = brgToBaud(U1BRG);
                          *stop = U1MODEbits.STSEL + 1;
                          *data_par = U1MODEbits.PDSEL;
                          break;
#endif

#if ENABLE_PORT_0
        case INST_PORT_0: *baud = brgToBaud(U2BRG);
                          *stop = U2MODEbits.STSEL + 1;
                          *data_par = U2MODEbits.PDSEL;
                          break;
#endif

#if ENABLE_PORT_1
        case INST_PORT_1: *baud = brgToBaud(U3BRG);
                          *stop = U3MODEbits.STSEL + 1;
                          *data_par = U3MODEbits.PDSEL;
                          break;
#endif

        default: return -1;
    }

    return 0;
}

int serGetByte(int port, unsigned char* b)
{
    int stat;

    /* if the port is valid, try to get the byte */
    if ( isPortValid(port) )
    {
        /* try to get a byte from the buffer */
        EnterCriticalSection();
        stat = bufGet(b, &rxBuff[port]);
        ExitCriticalSection();

        return stat;
    }
    
    *b = 0; 
    
    return FALSE;
}

int serPutByte(int port, unsigned char b)
{
    int is_full;
    
    /* see if there is room in the FIFO */
    switch ( port )
    {
#if ENABLE_PORT_2
        case INST_PORT_2: is_full = U1STAbits.UTXBF; break;
#endif

#if ENABLE_PORT_0
        case INST_PORT_0: is_full = U2STAbits.UTXBF; break;
#endif

#if ENABLE_PORT_1
        case INST_PORT_1: is_full = U3STAbits.UTXBF; break;
#endif
        default: is_full = TRUE;
    }
    
    /* if there is room put in the next byte */
    if ( !is_full )
    {
        switch (port)
        {
#if ENABLE_PORT_2
            case INST_PORT_2: 
                //for 485 mode. 
                if (IO_Uart0Mode_GetValue() == 1) {
                    Nop();
                    _LATF4 = 1;
                    Nop();
                }
                U1TXREG = b; 
                return TRUE;
#endif

#if ENABLE_PORT_0
            case INST_PORT_0: U2TXREG = b; return TRUE;
#endif

#if ENABLE_PORT_1
            case INST_PORT_1: U3TXREG = b; return TRUE;
#endif
            default: break;
        }
    }
    
    return FALSE;
}

int serPutString(int port, char* buff)
{
    int i = 0;
    
    while ( buff[i] != '\0' )
    {
        if ( serPutByte(port, (unsigned char)buff[i]) )
            ++i;
    }

    return 0;
}

void serRxFlush(int port)
{
    unsigned reg;

    /* if the port is valid, flush the buffer */
    if ( isPortValid(port) )
    {
        EnterCriticalSection();

        /* clear out the software FIFO */
        bufClear(&rxBuff[port]);

        /* clear out the harware FIFO */
        switch ( port )
        {
#if ENABLE_PORT_2
            case INST_PORT_2: while ( U1STAbits.URXDA ) reg = U1RXREG; break;
#endif

#if ENABLE_PORT_0
            case INST_PORT_0: while ( U2STAbits.URXDA ) reg = U2RXREG; break;
#endif

#if ENABLE_PORT_1
            case INST_PORT_1: while ( U3STAbits.URXDA ) reg = U3RXREG; break;
#endif
            default: break;
        }

        ExitCriticalSection();
    }

    return;
}

void serTxFlush(int port)
{
    switch ( port )
    {
#if ENABLE_PORT_2
        case INST_PORT_2: U1STAbits.UTXEN = 0; Nop(); 
                          U1STAbits.UTXEN = 1; Nop(); 
                          break;
#endif

#if ENABLE_PORT_0
        case INST_PORT_0: U2STAbits.UTXEN = 0; Nop(); 
                          U2STAbits.UTXEN = 1; Nop(); 
                          break;
#endif

#if ENABLE_PORT_1
        case INST_PORT_1: U3STAbits.UTXEN = 0; Nop(); 
                          U3STAbits.UTXEN = 1; Nop(); 
                          break;
#endif
        default: return;
    }
}

void serSetBreak(int port, int state)
{
    if ( state )
    {
        switch ( port )
        {
#if ENABLE_PORT_2
            case INST_PORT_2:   /* disable UART transmit */
                                U1STAbits.UTXEN = 0;
                                /* disable UART module */
                                U1MODEbits.UARTEN = 0;
                                /* set break flag */
                                txBreakFlag[INST_PORT_0] = TRUE;
                                break;
#endif

#if ENABLE_PORT_0
            case INST_PORT_0:   /* disable UART transmit */
                                U2STAbits.UTXEN = 0;
                                /* disable UART module */
                                U2MODEbits.UARTEN = 0;
                                /* set break flag */
                                txBreakFlag[INST_PORT_0] = TRUE;
                                break;
#endif

#if ENABLE_PORT_1
            case INST_PORT_1:   /* disable UART transmit */
                                U3STAbits.UTXEN = 0;
                                /* disable UART module */
                                U3MODEbits.UARTEN = 0;
                                /* set break flag */
                                txBreakFlag[INST_PORT_1] = TRUE;
                                break;
#endif
        }
    }
    else
    {
        switch ( port )
        {
#if ENABLE_PORT_2
            case INST_PORT_2:   /* enable UART module */
                                U1MODEbits.UARTEN = 1;
                                /* enable UART transmit */
                                U1STAbits.UTXEN = 1;
                                /* clear break flag */
                                txBreakFlag[INST_PORT_0] = FALSE;
                                break;
#endif

#if ENABLE_PORT_0
            case INST_PORT_0:   /* enable UART module */
                                U2MODEbits.UARTEN = 1;
                                /* enable UART transmit */
                                U2STAbits.UTXEN = 1;
                                /* clear break flag */
                                txBreakFlag[INST_PORT_0] = FALSE;
                                break;
#endif

#if ENABLE_PORT_1
            case INST_PORT_1:   /* enable UART module */
                                U3MODEbits.UARTEN = 1;
                                /* enable UART transmit */
                                U3STAbits.UTXEN = 1;
                                /* clear break flag */
                                txBreakFlag[INST_PORT_1] = FALSE;
                                break;
#endif
        }
    }

    return;
}

int serGetBreak(int port)
{
    if ( isPortValid(port) )
        return txBreakFlag[port]; 

    return FALSE;
}

int serIsRxFull(int port)
{
    int stat;
    
    /* if the port is valid, flush the buffer */
    if ( isPortValid(port) )
    {
        /* get the buffer status */
        EnterCriticalSection();
        stat = bufIsFull(&rxBuff[port]);
        ExitCriticalSection();

        return stat;
    }

    return FALSE;        
}

int serIsTxFull(int port)
{
    int is_full;

    /* return the hardware FIFO status */
    switch ( port )
    {
#if ENABLE_PORT_2
        case INST_PORT_2: is_full = U1STAbits.UTXBF; break;
#endif

#if ENABLE_PORT_0
        case INST_PORT_0: is_full = U2STAbits.UTXBF; break;
#endif

#if ENABLE_PORT_1
        case INST_PORT_1: is_full = U3STAbits.UTXBF; break;
#endif
        default: is_full = TRUE;
    }

    return is_full;
}

int serGetErrFlag(int port)
{
    if ( isPortValid(port) )
        if ( rxErrorFlags[port] > 0 ) return TRUE;

    return FALSE;
}


void serClrXERR(int port)
{
    if ( isPortValid(port) )
        rxErrorFlags[port] &= (~OVERFLOW_ERR);

    return;
}

int serGetXERR(int port)
{
    if ( isPortValid(port) )
    {
        if ( rxErrorFlags[port] & OVERFLOW_ERR )
            return TRUE;
        else
            return FALSE;
    }

    return FALSE;
}

void serClrOERR(int port)
{
    if ( isPortValid(port) )
    {
        rxErrorFlags[port] &= (~OVERRUN_ERR);

        /* also clear the OERR bit in the UxSTA register */
        switch ( port )
        {
#if ENABLE_PORT_2
            case INST_PORT_2: U1STAbits.OERR = 0; break;
#endif

#if ENABLE_PORT_0
            case INST_PORT_0: U2STAbits.OERR = 0; break;
#endif

#if ENABLE_PORT_1
            case INST_PORT_1: U3STAbits.OERR = 0; break;
#endif
            default: return;
        }
    }

    return;
}

int serGetOERR(int port)
{
    /* note: you have to check the the OERR bit directly when you 
    have an overrun condition.  When you have an overrun condition 
    interrupts stop occurring and the bit will never get set in 
    the rxErrorFlags[] variable. */

    switch ( port )
    {
#if ENABLE_PORT_2
        case INST_PORT_2: if (U1STAbits.OERR) return TRUE;
#endif

#if ENABLE_PORT_0
        case INST_PORT_0: if (U2STAbits.OERR) return TRUE;
#endif

#if ENABLE_PORT_1
        case INST_PORT_1: if (U3STAbits.OERR) return TRUE;
#endif
    }

    return FALSE;
}

void serClrFERR(int port)
{
    if ( isPortValid(port) )
        rxErrorFlags[port] &= (~FRAMING_ERR);

    return;
}

int serGetFERR(int port)
{
    if ( isPortValid(port) )
    {
        if ( rxErrorFlags[port] & FRAMING_ERR )
            return TRUE;
        else
            return FALSE;
    }

    return FALSE;
}

void serClrPERR(int port)
{
    if ( isPortValid(port) )
        rxErrorFlags[port] &= (~PARITY_ERR);

    return;
}


int serGetPERR(int port)
{
    if ( isPortValid(port) )
    {
        if ( rxErrorFlags[port] & PARITY_ERR )
            return TRUE;
        else
            return FALSE;
    }

    return FALSE;
}

/****************************************************************************/
/*                       Interrupt Service Routines                         */
/****************************************************************************/

/* Received data var */
static unsigned long txCount = 0;
static unsigned int rxOverFlow = 0;

/***************************** UART 1 IRQ routines ***************************/
#if ENABLE_PORT_2
/* This is UART1 transmit ISR */
void __attribute__ ((interrupt,no_auto_psv)) _U1TXInterrupt(void)
{
    //++txCount;
    Nop();
    
    _LATF4 = 0; // always turn off the RS485 DE 
    Nop();
    _U1TXIF = 0;
}

/* This is UART1 receive ISR */
void __attribute__ ((interrupt,no_auto_psv)) _U1RXInterrupt(void)
{
    /* note: the standard buffer functions are not used here 
    as calling an external function from and interruppt service 
    routine increases interrupt overhead */
    
    /* test for whether rs485 is enabled, forcing us into half duplex. */
    /* grab all the bytes from the buffer */
    while ( U1STAbits.URXDA )
    {
        /* if the buffer is not full, put in the byte */
        //TODO this is a bad use of the TRMT, at least in the case of RS232, good for half duplex rs485
        if ( (((rxBuff[2].head + 1) & rxBuff[2].ptr_mask) == rxBuff[2].tail ) | (U1STAbits.TRMT == 0) )
        {
            /* you need to get the byte out buffer or you will hang! */
            rxOverFlow = U1RXREG;
            
            /* set overflow flag and bail */
            rxErrorFlags[INST_PORT_2] |= OVERFLOW_ERR;
            break;
        }
        else
        {
            /* check error flags */
            rxErrorFlags[INST_PORT_2] |= (U1STA & ERR_FLAG_MASK);
            
            /* if there is room in the buffer put the byte in it */
            rxBuff[2].data[rxBuff[2].head] = (unsigned char)U1RXREG;
            rxBuff[2].head = (rxBuff[2].head + 1) & rxBuff[2].ptr_mask;
        }
    }
    
    /* clear the interrupt and return */
    _U1RXIF = 0;
    return;
}
#endif

/***************************** UART 2 IRQ routines ***************************/
#if ENABLE_PORT_0
/* This is UART2 transmit ISR */
void __attribute__ ((interrupt,no_auto_psv)) _U2TXInterrupt(void)
{
    ++txCount;
    _U2TXIF = 0;

}

/* This is UART2 receive ISR */
void __attribute__ ((interrupt,no_auto_psv)) _U2RXInterrupt(void)
{
    /* note: the standard buffer functions are not used here 
    as calling an external function from and interrupt service 
    routine increases interrupt overhead */
    
    /* grab all the bytes from the buffer */
    while ( U2STAbits.URXDA )
    {
        /* if the buffer is not full, put in the byte */
        if ( ((rxBuff[0].head + 1) & rxBuff[0].ptr_mask) == rxBuff[0].tail )
        {
            /* you need to get the byte out buffer or you will hang! */
            rxOverFlow = U2RXREG;

            /* set overflow flag and bail */
            rxErrorFlags[INST_PORT_0] |= OVERFLOW_ERR;
            break;
        }
        else
        {
            /* check error flags */
            rxErrorFlags[INST_PORT_0] |= (U2STA & ERR_FLAG_MASK);

            /* if there is room in the buffer put the byte in it */
            rxBuff[0].data[rxBuff[0].head] = (unsigned char)U2RXREG;
            rxBuff[0].head = (rxBuff[0].head + 1) & rxBuff[0].ptr_mask;
        }
    }

    /* clear the interrupt and return */
    _U2RXIF = 0;
    return;
}
#endif

/***************************** UART 3 IRQ routines ***************************/
#if ENABLE_PORT_1
/* This is UART3 transmit ISR */
void __attribute__ ((interrupt,no_auto_psv)) _U3TXInterrupt(void)
{
    ++txCount;
    _U3TXIF = 0;
}

/* This is UART3 receive ISR */
void __attribute__ ((interrupt,no_auto_psv)) _U3RXInterrupt(void)
{
    /* note: the standard buffer functions are not used here 
    as calling an external function from and interrupt service 
    routine increases interrupt overhead */
    
    /* grab all the bytes from the buffer */
    while ( U3STAbits.URXDA )
    {
        /* if the buffer is not full, put in the byte */
        if ( ((rxBuff[1].head + 1) & rxBuff[1].ptr_mask) == rxBuff[1].tail )
        {
            /* you need to get the byte out buffer or you will hang! */
            rxOverFlow = U3RXREG;

            /* set overflow flag and bail */
            rxErrorFlags[INST_PORT_1] |= OVERFLOW_ERR;
            break;
        }
        else
        {
            /* check error flags */
            rxErrorFlags[INST_PORT_1] |= (U3STA & ERR_FLAG_MASK);

            /* if there is room in the buffer put the byte in it */
            rxBuff[1].data[rxBuff[1].head] = (unsigned char)U3RXREG;
            rxBuff[1].head = (rxBuff[1].head + 1) & rxBuff[1].ptr_mask;
        }
    }

    /* clear the interrupt and return */
    _U3RXIF = 0;
    return;
}
#endif

/* local functions */
int isPortValid(int port)
{
#if ENABLE_PORT_2
    if ( port == INST_PORT_2 ) return TRUE;
#endif

#if ENABLE_PORT_0
    if ( port == INST_PORT_0 ) return TRUE;
#endif

#if ENABLE_PORT_1
    if ( port == INST_PORT_1 ) return TRUE;
#endif

    return FALSE;
}

unsigned long brgToBaud(unsigned int brg)
{
    return (FCY / (BRG_DIV * ((unsigned long)brg + 1)));
}

unsigned int baudToBrg(unsigned long baud)
{
    if ( baud == 0 )
        return 0xFFFF;
    else if ( baud > (FCY / BRG_DIV) )
        return 0;
    else
        return (((FCY / baud) / BRG_DIV) - 1);
}

