/*
 * FreeModbus Libary: BARE Port
 * Copyright (C) 2006 Christian Walter <wolti@sil.at>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 1*/

/****************************************************************************/
/* Copyright 2016 MBARI and Christian Walter                                */
/****************************************************************************/
/* Summary  : Port of ModBus over SPI on PIC24F128GA406 for OASIS5          */
/* Filename : portspi.c                                                     */
/* Author   : Robert Herlien (rah)                                          */
/* Project  : OASIS5                                                        */
/* Revision : 1.0                                                           */
/* Created  : 3/7/2016                                                      */
/*                                                                          */
/* 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:                                                    */
/* 07mar2016 rah - created for OASIS5 Daughter Board                        */
/****************************************************************************/
/* This module implements a freeModBus port to ModBus over SPI bus for the  */
/* OASIS5 Daughter Board.  This port uses ModBus RTU, which is intended for */
/* a serial transport layer (RS-232 or RS-485).  Hence the terminology of   */
/* the functions and interface refers to serial ports.  Rest assured, though*/
/* that we're using SPI.  The Daughter Board is the SPI Slave.  Things like */
/* baud rate and stop bits have no impact on this port.                     */
/****************************************************************************/

#include "port.h"
#include "dig_io.h"
#include "sys_timer.h"


/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"

#define SPIMstr()        LATBbits.LATB3

static UCHAR ucCriticalNesting = 0x00;
static CHAR rxByte;
static BOOL txEnable;
static ULONG modRxTick;


/************************************************************************/
/* Function    : xMBPortSerialInit                                      */
/* Purpose     : Initialize this module                                 */
/* Inputs      : Serial port params that are unused                     */
/* Outputs     : TRUE                                                   */
/* Note        : Actually sets up SPI Bus 1                             */
/************************************************************************/
BOOL xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
    UCHAR       dummy;

    dummy = SPI1BUFH;                   /* Clear rcv registers          */
    dummy = SPI1BUFL;
    _SPI1IF = 0;                        /* Clear int flags              */
    _SPI1RXIF = 0;
    _SPI1TXIF = 0;

    SPI1CON1L = 0x0181;                 /* 8 bit slave, enh buf         */
    SPI1CON1H = 0x3010;
    SPI1STATL = 0x20;                   /* Clear error bits             */
    SPI1IMSKL = 0x08;                   /* Int on  Tx empty             */
    SPI1IMSKH = 0x8100;                 /* Int on Rx char		*/
    SPI1BRGL = 9;                       /* 500 KHz when we're master      */

    /* set the interrupt priority for receive and transmit              */
    /* NOTE - these priorities must be < 4, to make critical region code work!!!*/
    _SPI1RXIP = 3;
    _SPI1TXIP = 2;

    _SPI1RXIE = 0;                      /* make sure interrupts are disabled */
    _SPI1TXIE = 0;

    SPI1CON1Lbits.SPIEN = 1;            /* Turn on SPI module           */
    return(TRUE);
}


/************************************************************************/
/* Function    : vMBPortSerialEnable                                    */
/* Purpose     : Enable serial (SPI) Rx and/or Tx                       */
/* Inputs      : RxEnable, TxEnable                                     */
/* Outputs     : None                                                   */
/************************************************************************/
/* Use:  At init or after frame is sent, called with TRUE, FALSE        */
/*       When frame rcvd in prep to send, called with FALSE, TRUE       */
/*       When protocol stack is stopped, called with FALSE, FALSE       */
/************************************************************************/
void vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{
    ENTER_CRITICAL_SECTION();

    if (xRxEnable)
    {
        if (txEnable)                       /* If we were transmitting  */
            while (SPI1STATLbits.SRMT == 0) /* wait for Tx to finish  */
                Nop();

        while (SPI1STATLbits.SPIRBE == 0)   /* Empty Rx FIFO            */
           rxByte = SPI1BUFL;

        SPI1STATLbits.SPIROV = 0;           /* Clear overflow bit       */
        SPI1CON1Lbits.SPIEN = 0;            /* Turn off SPI module	*/
	SPI1CON1L = 0x0181;                 /* Make us SPI slave	*/
        _SPI1RXIF = 0;                      /* Clear interrupt flag     */
        _SPI1RXIE = 1;                      /* Enable Rx ints           */
    }
    else
        _SPI1RXIE = 0;                      /* Disable Rx ints          */

    txEnable = xTxEnable;
    if (xTxEnable)
    {
        SPI1CON1Lbits.SPIEN = 0;            /* Turn off SPI module	*/
        SPI1CON1L = 0x0121;                 /* We temp become Master    */
        _SPI1TXIF = 1;                      /* Kick start Tx int handler*/
        _SPI1TXIE = 1;                      /* Enable Tx ints           */
    }
    else
    {
        _SPI1TXIE = 0;                      /* Disable Tx ints          */
        spiTxPinOff();			    /* Turn off debug pin	*/
    }

    SPI1CON1Lbits.SPIEN = 1;                /* Turn SPI module back on  */
    EXIT_CRITICAL_SECTION();
}


/************************************************************************/
/* Function    : xMBPortSerialPutByte                                   */
/* Purpose     : Send a byte on the SPI bus                             */
/* Inputs      : Byte to send                                           */
/* Outputs     : TRUE                                                   */
/* Comment     : If called, it means that xmit int indicates Tx Ready   */
/************************************************************************/
BOOL xMBPortSerialPutByte( CHAR ucByte )
{
    SPI1BUFL = ucByte;                  /* Send the byte                */
    return(TRUE);
}


/************************************************************************/
/* Function    : xMBPortSerialGetByte                                   */
/* Purpose     : Get a byte from the SPI bus                            */
/* Inputs      : Ptr to byte                                            */
/* Outputs     : TRUE                                                   */
/* Comment     : If called, it means that rcv int indicates Rx Ready    */
/************************************************************************/
BOOL xMBPortSerialGetByte( CHAR * pucByte )
{
    *pucByte = rxByte;
    return(TRUE);
}


void EnterCriticalSection( void )
{
    /* disable ints < 4, which are all the Modbus stack interrupts */
    SRbits.IPL = 4;

    /* increment critical section nesting count */
    ++ucCriticalNesting;
}

void ExitCriticalSection( void )
{
    /* if you've nested all the way out  of critical sections, enable ints*/
    if( --ucCriticalNesting == 0 )
        SRbits.IPL = 0;
}


/************************************************************************/
/* Function    : modBusIsIdle											*/
/* Purpose     : Function to support putting CPU into Doze mode when idle*/
/* Inputs      : None													*/
/* Outputs     : TRUE if Modbus is idle, else FALSE						*/
/************************************************************************/
int	modBusIsIdle(void)
{
#ifdef NO_IDLE
	return(FALSE);
#else
	return(!txEnable &&
		   ((tmrGetTicks() - modRxTick) > MOD_IDLE_TICKS));
#endif
}


/************************************************************************/
/*                      ISRs                                            */
/************************************************************************/
void __attribute__ ((interrupt,no_auto_psv)) _SPI1TXInterrupt(void)
{
	NoDoze();
    _SPI1TXIF = 0;
    spiTxPinOn();

    while (txEnable && (SPI1STATLbits.SPITBF == 0))
        pxMBFrameCBTransmitterEmpty();

    spiTxPinOff();
}

void __attribute__ ((interrupt,no_auto_psv)) _SPI1RXInterrupt(void)
{
	NoDoze();
    _SPI1RXIF = 0;
    spiRxPinOn();
	modRxTick = tmrGetTicks();

    /* if you get an overrun error clear it and bail out */
    if ( SPI1STATLbits.SPIROV )
    {
        while (SPI1STATLbits.SPIRBE == 0)   /* Empty Rx FIFO            */
            rxByte = SPI1BUFL;

        SPI1STATLbits.SPIROV = 0;           /* Clear the overrun        */
    }
    else   /* read bytes out while data is available */
        while (SPI1STATLbits.SPIRBE == 0)
    	{
            rxByte = SPI1BUFL;

	    /* notify the RX FSM that a byte has been received */
            pxMBFrameCBByteReceived();
	}

    spiRxPinOff();
}
