/****************************************************************************/
/* Copyright 2009 - 2015 MBARI                                              */
/****************************************************************************/
/* Summary  : Instrument Port Functions for Oasis5 Expander Board           */
/* Filename : inst_port.c                                                   */
/* Author   : Mike Risi (mrisi), Robert Herlien (rah)                       */
/* Project  : OASIS Mooring Replacement (OASIS5)                            */
/* Revision: 1.0                                                            */
/* Created  : 2009 by Mike Risi, Adapted for Oasis5 10/29/2015 by Bob Herlien*/
/*                                                                          */
/* 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:                                                    */
/* 29oct2015 rah - adapted from Respirometer chamber controller             */
/****************************************************************************/

#include <xc.h>
#include <stdio.h>
#include <stdlib.h>

#include "inst_port.h"
#include "regs.h"
#include "buffer.h"
#include "serial.h"
#include "dig_io.h"
#include "sys_timer.h"


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

static ByteBuffer txFgBuff[SER_PORT_TOTAL];
static unsigned char txFgBuffData[SER_PORT_TOTAL][TX_BUFF_SIZE];

static unsigned char txWriteRequest[SER_PORT_TOTAL];
static unsigned char txWriteQueued[SER_PORT_TOTAL];
static unsigned int  rxAvailSnapshot[SER_PORT_TOTAL];


/************************************/
/*  Forward Function Declarations   */
/************************************/

static unsigned int getRxData(int port, unsigned int offset);
static void putTxData(int port, unsigned int offset, unsigned int reg);


/************************************************************************/
/* Function    : instPortInit                                           */
/* Purpose     : Initialize this module                                 */
/* Inputs      : None                                                   */
/* Outputs     : None                                                   */
/************************************************************************/
void instPortInit()
{
    int i;

    for (i = 0; i < SER_PORT_TOTAL; ++i)
    {
        bufInit(&txFgBuff[i], txFgBuffData[i], TX_BUFF_SIZE);

        /* clear all write requests */
        txWriteRequest[i] = 0;

        /* Clear rxAvail snapshots  */
        rxAvailSnapshot[i] = 0;
    }
}


/************************************************************************/
/* Function    : instPortTxTask                                         */
/* Purpose     : Send out buffered Tx Data                              */
/* Inputs      : None                                                   */
/* Outputs     : TRUE if Idle (no Tx chrs), FALSE if sending			*/
/************************************************************************/
int instPortTxTask()
{
    int i, idle;
    BYTE b;
    ByteBuffer* fg_buff;

    for (i = 0, idle = TRUE; i < SER_PORT_TOTAL; ++i)
    {
        /* if you are not in a break state put bytes in the TX FIFO */
        if ( !serGetBreak(i) )
        {
            fg_buff = &txFgBuff[i];
#ifndef NO_IDLE
			if (!bufIsEmptyP(fg_buff))
				idle = FALSE;
            /* Turn off EIA driver when empty?	*/
#endif
            /* get a byte from the foreground buffer and queue it */
            while (!serIsTxFull(i) && bufGet(&b, fg_buff))
                serPutByte(i, b);
        }
    }

	return(idle);
}


/************************************************************************/
/* Function    : instPortClearWriteReq                                  */
/* Purpose     : Clear the Tx write request registers                   */
/* Inputs      : None                                                   */
/* Outputs     : None                                                   */
/* Comments    : Called by main() at end of ModBus writes               */
/************************************************************************/
void instPortClearWriteReq()
{
    int i;
    
    for (i = 0; i < SER_PORT_TOTAL; i++)
        txWriteRequest[i] = 0;
}


/************************************************************************/
/* Function    : instSnapshotRxAvail                                    */
/* Purpose     : Take snapshot of RxAvail                               */
/* Inputs      : None                                                   */
/* Outputs     : None                                                   */
/* Comments    : Called by main() if reading any Rx registers, to sync  */
/************************************************************************/
void instSnapshotRxAvail()
{
    int         i;

    for (i = 0; i < SER_PORT_TOTAL; i++)
        rxAvailSnapshot[i] = (unsigned int)serRxAvailable(i);
}


/************************************************************************/
/* Function    : instPortReadReg                                        */
/* Purpose     : Read one Modbus reg for instrument port                */
/* Inputs      : Register number                                        */
/* Outputs     : Register Value                                         */
/************************************************************************/
unsigned int instPortReadReg(unsigned int address)
{
    int port;
    unsigned int offset;
    unsigned int status;
    int stop, data_par;

    if ( address == SERIAL_RX_STAT )
    {
        for (port = status = 0; port < SER_PORT_TOTAL; port++)
            if (serRxAvailable(port))
                status |= (1 << port);

        return(status);
    }

    if ( address == SERIAL_TX_STAT )
    {
        for (port = status = 0; port < SER_PORT_TOTAL; port++)
        {
            if (bufAvailable(txFgBuff[port]))
                status |= (TX_DATA_BIT << (2*port));

            if (serGetErrFlag(port))
                status |= (ERR_STAT_BIT << (2*port));
        }

        return(status);
    }

    /* calculate the offset and port */
    if ((address >= SER_HOLDING_START) && (address <= SER_HOLDING_END))
    {
        port = (address/0x100) - 1;
        offset = address - ((port+1) * 0x100);
    }
    else
        return(0);

    if ((offset >= 0x08) && (offset <= 0x7f))
        return(getRxData(port, offset));
    
    switch ( offset )
    {
        /* read RX config register */
        case 0x00: return((unsigned int)(serGetBaud(port)/4));

        case 0x01: serGetConfig(port, &data_par, &stop); 
                   status = (data_par & DATA_PAR_BIT) << 1;
                   if (stop == S_2)
                       status |= STOP_BIT;
                   if ( digGetSerEnable(port) )
                       status |= ENABLE_BIT;
                   return(status);
        
        case 0x02: return(0);
        case 0x03: return(0);
        case 0x04: status = 0;
                   if ( serGetPERR(port) )
                       status |= PERR_BIT;
                   if ( serGetFERR(port) )
                       status |= FERR_BIT;
                   if ( serGetOERR(port) )
                       status |= OERR_BIT;
                   if ( serGetXERR(port) )
                       status |= XERR_BIT;
                   return(status);

        case 0x05: return(0);
        case 0x06: return(0);
        case 0x07: return(rxAvailSnapshot[port]);

        /* read TX status register */
        case 0x80: return(0);
        case 0x81: return(0);
        case 0x82: return(bufAvailable(txFgBuff[port]));
        case 0x83: status = bufAvailable(txFgBuff[port]);
                   if (status > 0xff)
                       status = 0xff;
                   return(((status & 0xff) << 8) | (txWriteQueued[port] & 0xff));

        /* read TX config register */
        case 0x84: return(0);   /* FLUSH always reads as 0 */
        case 0x85: return(serGetBreak(port) ? BREAK_TX_BIT : 0);
        case 0x86: return(0);
        case 0x87: /* note: txWriteRequest is always cleared 
                      after write operation, so return zero  */
                   return(0);
    }

    return(0);
}


/************************************************************************/
/* Function    : instPortWriteReg                                       */
/* Purpose     : Write one Modbus reg for instrument port               */
/* Inputs      : Register number, value                                 */
/* Outputs     : None                                                   */
/************************************************************************/
void instPortWriteReg(unsigned int address, unsigned int reg)
{
    int port, data_par, stop;
    unsigned int offset;

    /* calculate the offset and port */
    if ((address >= SER_HOLDING_START) && (address <= SER_HOLDING_END))
    {
        port = (address/0x100) - 1;
        offset = address - ((port+1) * 0x100);
    }
    else
        return;
    
    switch ( offset )
    {
        /* write RX config register */
        case 0x00:
            serSetBaud(port, 4L * (unsigned long)reg);
            return;

        case 0x01:
            data_par = (DATA_PAR_BIT & reg) >> 1;
            stop = (reg & STOP_BIT) ? S_2 : S_1;

            serSetConfig(port, data_par, stop);

            if ( reg & ENABLE_BIT )
                digSerEnable(port);
            else
                digSerDisable(port);
            return;

        case 0x02:
            if (reg & FLUSH_RX_BIT)
                serRxFlush(port);
            if (reg & PERR_BIT)
                serClrPERR(port);
            if (reg & FERR_BIT)
                serClrFERR(port);
            if (reg & OERR_BIT)
                serClrOERR(port);
            if (reg & XERR_BIT)
                serClrXERR(port);
            break;

        case 0x03: return;

        /* you can't write RX status registers, just return */
        case 0x04: return;
        case 0x05: return;
        case 0x06: return;
        case 0x07: return;

        /* you can't write TX status registers, just return */
        case 0x80: return;
        case 0x81: return;
        case 0x82: return;
        case 0x83: return;

        /* write TX config register */
        case 0x84:
            if (reg & FLUSH_TX_BIT)
                serTxFlush(port);
            return;

        case 0x85:
            serSetBreak(port, reg & BREAK_TX_BIT);
            return;

        case 0x86: return;
        case 0x87:
            txWriteRequest[port] = (reg & 0xff);
            txWriteQueued[port] = 0;
            return;

        default:
            putTxData(port, offset, reg);
            return;
    }
}


/************************************************************************/
/* Function    : getRxData                                              */
/* Purpose     : Read Rx Data for one instrument port                   */
/* Inputs      : Serial port, Register offset                           */
/* Outputs     : Register Value (two serial chars)                      */
/************************************************************************/
static unsigned int getRxData(int port, unsigned int offset)
{
    unsigned char b;
    unsigned int reg, rxByteNum;

    /* if you're past the last RX data reg return zero */
    if ( offset > 0x7F )
        return 0;

    rxByteNum = 2 * (offset - 0x08);

    if ((rxAvailSnapshot[port] > rxByteNum) && serGetByte(port, &b))
    {
        reg = b;
        reg <<= 8;
    }
    else
        return 0;
    
    if ((rxAvailSnapshot[port] > rxByteNum+1) && serGetByte(port, &b))
    {
        reg += (0x00FF & b);
        return reg;
    }
    else
        return reg;
}


/************************************************************************/
/* Function    : putTxData                                              */
/* Purpose     : Write Tx Data for one instrument port                  */
/* Inputs      : Serial port, register offset, reg data (2 serial chars)*/
/* Outputs     : None                                                   */
/************************************************************************/
static void putTxData(int port, unsigned int offset, unsigned int reg)
{
    unsigned char hi_byte;
    unsigned char lo_byte;

    /* if you're not into the TX registers, bail */
    if ( offset < 0x88 )
        return;

    hi_byte = (unsigned char)(reg >> 8);
    lo_byte = (unsigned char)(0x00FF & reg);
    
    if ( txWriteRequest[port] > txWriteQueued[port] )
        if ( bufPut(hi_byte, &txFgBuff[port]) )
            ++txWriteQueued[port];

    if ( txWriteRequest[port] > txWriteQueued[port] )
        if ( bufPut(lo_byte, &txFgBuff[port]) )
            ++txWriteQueued[port];
}

