/****************************************************************************/
/* Copyright 2016 MBARI                                                     */
/****************************************************************************/
/* Summary  : ModBus over SPI Routines for OASIS5 on PIC32MX470F512L        */
/* Filename : spi.c                                                         */
/* Author   : Robert Herlien (rah)                                          */
/* Project  : OASIS Mooring Replacement (OASIS5)                            */
/* Revision: 1.0                                                            */
/* Created  : 07/23/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:                                                    */
/* 23jul2015 rah - created                                                  */
/****************************************************************************/
/* SPI I/O via ModBus to the Daughter Boards is protected by a MUTEX, 		*/
/* modSpiSem, in modbusio.c.  Because I/O to the 2 DBs shares the SPI bus,	*/
/* we can talk to only one DB at a time.  Because of this mutual exclusion,	*/
/* we can share an I/O buffer, modSpiData, and PIC timer 2 for detecting the*/
/* end of ModBus responses.													*/
/****************************************************************************/

#include <mbariTypes.h>					/* MBARI type definitions		*/
#include <oasis.h>						/* OASIS controller definitions */
#include <dig_io.h>						/* OASIS digital I/O definitions*/
#include <timer.h>						/* OASIS clock routines			*/
#include <sem.h>						/* OASIS Semaphore library		*/
#include <serial.h>						/* Serial port declarations		*/
#include <modbus.h>						/* Modbus protocol library		*/
#include <modspi.h>						/* Modbus over SPI				*/
#include <remote.h>						/* Remote I/O library			*/

#include <xc.h>
#include <FreeRTOS.h>


/********************************/
/*		External Data			*/
/********************************/

/********************************/
/*		Global Data				*/
/********************************/

Global Errno		modSpiErrno;


/********************************/
/*		Module Local Data		*/
/********************************/

MLocal ModBusStruct		modBusSpi[NUM_DBS];

/* Single I/O buffer because ModBus SPI I/O is protected by modSpiSem	*/
MLocal volatile ModBusIO modSpiData;

MLocal Byte				dummy;

/* Forward Declarations			*/
MLocal void modSpiIdle();
inline void stopModTimer(void);


/************************************************************************/
/* Function    : modInitDB												*/
/* Purpose     : Initialize one O5 Daughter Board						*/
/* Inputs      : SPI port (0 or 1), non-zero if DB is present			*/
/* Outputs     : None                                                   */
/************************************************************************/
MLocal void modInitDB(Port_t port, int present)
{
	Nat32		i, virtPort;
	RemID		*rp;
	char		name[16];

	if ((rp = assignRemoteID(MB_SPI, port, 1)) != NULL)
		rp->remType = RemO5DB;

	virtPort = port ? UARTS_PER_DB : 0;

	if (present)
		for (i = 0; i < UARTS_PER_DB; i++, virtPort++)
		{
			isprintf(name, "ser%02u", virtPort);
			if (assignVirtSerial(name, virtPort, MB_SPI, port, 1, i, DFLT_MULT, DFLT_SURGE)
				!= OK)
				return;

			isprintf(name, "pwr%02u", virtPort);
			if (assignVirtPwr(name, virtPort, MB_SPI, port, 1, i, DFLT_MULT, DFLT_SURGE)
				!= OK)
				return;
		}
}


/************************************************************************/
/* Function    : modSpiInit                                             */
/* Purpose     : Setup SPI buses                                        */
/* Inputs      : None                                                   */
/* Outputs     : None                                                   */
/************************************************************************/
void modSpiInit()
{
	int		i;

    /* Map the pins             */
    SDI1R = 0x03;                       /* MISO1 = RPD11, pin 71        */
    RPD9R = 0x08;                       /* MOSI1 = RPD9, pin 69         */

    /* Setup SPI1 (local) Control Registers */
    SPI1CON = 0x10129;                  /* 8 bit, master, edge 1, enh buf*/
    SPI1BRG = 23;                       /* 500 KHz                      */

    IPC7bits.SPI1IP = 5;                /* Set SPI Interrupt priority	*/
    IPC7bits.SPI1IS = 0;

    /* Initialize the ModBus Timeout timer      */
    T2CON = 0x0000; 	                /* Prescale divide by 1         */
    PR2 = (7*PBCLK/4000) - 1;           /* 1.75 ms                      */
    TMR2 = 0x0000;                      /* clear timer count            */
    T2CONSET = _T2CON_TON_MASK;         /* Turn on the timer            */

    IPC2bits.T2IP = 3;                  /* Lower priority than SPI      */
    IPC2bits.T2IS = 1;					/* Higher sub-prio than serial timer*/

    modSpiIdle();                       /* Go to SPI idle state         */


	/* Initialize Data Structures	*/
	memset(modBusSpi, 0, sizeof(modBusSpi));
	memset((void *)&modSpiData, 0, sizeof(modSpiData));

	for (i = 0; i < NUM_DBS; i++)
	{
		modBusSpi[i].busType = MB_SPI;
		modBusSpi[i].port = i;
		modBusSpi[i].baud = 500000;
		modBusSpi[i].ident = IDENT;
		modBusSpi[i].ioBuf = &modSpiData;
	}

	modInitDB(0, digDB0Present());
	modInitDB(1, digDB1Present());
}


/************************************************************************/
/* Function	   : getSpiModStruct										*/
/* Purpose	   : Get pointer to SPI ModBusStruct array					*/
/* Inputs	   : None													*/
/* Outputs	   : Ptr to first element of ModBusStruct array				*/
/************************************************************************/
ModBusStruct *getSpiModStruct(void)
{
	return(modBusSpi);
}


/************************************************************************/
/* Function	   : modBusSpiOpen											*/
/* Purpose	   : Open a SPI ModBus bus									*/
/* Inputs	   : Bus number												*/
/* Outputs	   : ModBusHandle or NULL if failed							*/
/************************************************************************/
ModBusHandle modBusSpiOpen(Port_t port)
{
	ModBusStruct *mbp;

	if (port >= NUM_DBS)
	{
		modSpiErrno = ILLEGAL_PORT;
		return(NULL);
	}

	mbp = &modBusSpi[port];

	if (mbp->ident != IDENT)
	{
		modSpiErrno = ILLEGAL_PORT;
		return(NULL);
	}

	getModbusPort(mbp);

	if (mbp->openCount <= 0)
		mbp->openCount = 0;

	mbp->openCount++;

	releaseModbusPort(mbp);

	return(mbp);

} /* modBusSpiOpen() */


/************************************************************************/
/* Function    : modSpiIdle                                             */
/* Purpose     : Go to Idle SPI state                                   */
/* Inputs      : None                                                   */
/* Outputs     : None                                                   */
/************************************************************************/
MLocal void modSpiIdle()
{
    digSpiCSAllDis();					/* Deselect SPI CS pins         */
    modSpiData.state = MB_IDLE;			/* Show Idle state              */
    mbSentPinOff();
    stopModTimer();						/* Turn off Modbus timer        */

    SPI1STATCLR = _SPI1STAT_SPIROV_MASK;/* Clear rcv ovfl               */
    while (SPI1STATbits.SPIRBE == 0)
        dummy = SPI1BUF;                /* Clear rcvd queue             */

    IEC1CLR = _IEC1_SPI1RXIE_MASK;      /* Turn off ints                */
    IEC1CLR = _IEC1_SPI1TXIE_MASK;
    IFS1CLR = _IFS1_SPI1RXIF_MASK;
    IFS1CLR = _IFS1_SPI1TXIF_MASK;

    SPI1CONCLR = _SPI1CON_ON_MASK;		/* Turn off SPI                 */
    SPI1CONSET = _SPI1CON_MSTEN_MASK;	/* Make us into SPI master      */
    SPI1CONbits.STXISEL = 0x02;			/* Interupt when FIFO half full */
    SPI1CONSET = _SPI1CON_ON_MASK;		/* Turn on SPI                  */
}


/************************************************************************/
/* Function    : modSpiIO                                               */
/* Purpose     : Do ModBus transaction                                  */
/* Inputs      : ModBusHandle, length of msg to send                    */
/* Outputs     : OK or Error Number                                     */
/* Comment     : Uses static modSpiData because it's protected by MUTEX	*/
/************************************************************************/
Errno modSpiIO(ModBusHandle mh, Nat16 len)
{
	modSpiErrno = OK;
	modSpiData.ioTask = xTaskGetCurrentTaskHandle();

    if (mh->port)
        digSpiCS1En();
    else
        digSpiCS0En();

    spiTransactPinOn();
    SPI1CONCLR = _SPI1CON_ON_MASK;      /* Turn off SPI                 */
    SPI1CONSET = _SPI1CON_MSTEN_MASK;   /* Make us into SPI master      */
    SPI1CONbits.STXISEL = 0x02;         /* Interupt when FIFO half full */

	modSpiData.port = mh->port;
    modSpiData.state = MB_SEND;			/* Go to sending state          */
    modSpiData.sndLen = len;			/* Save msg length              */
    modSpiData.sndPos = 0;				/* Init send position           */
    modSpiData.rcvLen = 0;				/* Init rcv length				*/

    IEC1CLR = _IEC1_SPI1RXIE_MASK;      /* Disable SPI Rx ints          */
    SPI1CONSET = _SPI1CON_ON_MASK;      /* Turn on SPI                  */

    /* The following code pre-stuffs the FIFO, saving us some time in   */
    /* interrupt context. It's not necessary, but it reduces int latency*/
    if (modSpiData.sndLen > 7)
        while (spi1XmitAvail() && (modSpiData.sndPos < 7))
            SPI1BUF = modSpiData.sndBuf[modSpiData.sndPos++];
    
    IEC1SET = _IEC1_SPI1TXIE_MASK;      /* Enable SPI Tx ints           */
    IFS1SET = _IFS1_SPI1TXIF_MASK;      /* Kick start Tx Int handler    */

	if (xTaskNotifyWait(~0, ~0, NULL, RCV_TMOUT) != pdTRUE)
		modSpiErrno = COMM_TIME_OUT;	/* If timed out, set error code */
		
    modSpiIdle();                       /* Go to idle state             */
    spiTransactPinOff();

    return(modSpiErrno);
}


/************************************************************************/
/* Function    : restartModTimer                                        */
/* Purpose     : Reset Modbus timer count to keep it going              */
/* Inputs      : None                                                   */
/* Outputs     : None                                                   */
/************************************************************************/
inline void restartModTimer(void)
{
    TMR2 = 0x0000;                      /* clear timer count            */
    IFS0CLR = _IFS0_T2IF_MASK;          /* clear interrupt flag         */
}


/************************************************************************/
/* Function    : stopModTimer                                           */
/* Purpose     : Turn off Modbus Timer                                  */
/* Inputs      : None                                                   */
/* Outputs     : None                                                   */
/************************************************************************/
inline void stopModTimer(void)
{
    TMR2 = 0x0000;                      /* clear timer count            */
    IEC0CLR = _IEC0_T2IE_MASK;          /* Turn off interrupts          */
    IFS0CLR = _IFS0_T2IF_MASK;          /* clear interrupt flag         */
}


/************************************************************************/
/* Function    : _Spi1IntHandler                                        */
/* Purpose     : Interrupt Handler for SPI1 (ModBus I/O)                */
/* Inputs      : None                                                   */
/* Outputs     : None                                                   */
/* Comment     : Uses static modSpiData because it's protected by MUTEX	*/
/************************************************************************/
void __attribute__( (interrupt(IPL5SOFT), vector(_SPI_1_VECTOR)) ) _Spi1IntHandler(void)
{
    spiIntPinOn();

    switch(modSpiData.state)
    {
	  case MB_SEND:
          while (spi1XmitAvail())
          {
              SPI1BUF = modSpiData.sndBuf[modSpiData.sndPos++];
              if (modSpiData.sndPos >= modSpiData.sndLen)
              {
                  SPI1CONbits.STXISEL = 0x00;	/* Interupt when snd complete*/
                  modSpiData.state = MB_WAIT_SENT; /* Wait for Tx empty		*/
                  break;
              }
          }
          break;

      case MB_WAIT_SENT:
          mbSentPinOn();
          while (SPI1STATbits.SRMT == 0)        /* In case FIFO isn't empty*/
              ;                                 /* when we get this int   */

		  digSpiCSAllDis();						/* Turn off SPI CS        */
          SPI1CONCLR = _SPI1CON_ON_MASK;        /* Turn off SPI           */
          SPI1CONCLR = _SPI1CON_MSTEN_MASK;     /* Make us into SPI slave */
          modSpiData.state = MB_WAIT_RCV;
          modSpiData.rcvLen = 0;

          IEC1CLR = _IEC1_SPI1TXIE_MASK;        /* Disable SPI Tx ints    */
          SPI1CONSET = _SPI1CON_ON_MASK;        /* Turn on SPI            */

          SPI1STATCLR = _SPI1STAT_SPIROV_MASK;  /* Clear rcv ovfl         */
          while (SPI1STATbits.SPIRBE == 0)
              dummy = SPI1BUF;                  /* Clear rcvd queue       */

          IEC1SET = _IEC1_SPI1RXIE_MASK;		/* Enable SPI Rx ints     */
          mbSentPinOff();
          break;

      case MB_WAIT_RCV:
          if (spi1RcvAvail())
          {
              modSpiData.rcvBuf[modSpiData.rcvLen++] = SPI1BUF;
              restartModTimer();				/* Start Modbus timer      */
              IEC0SET = _IEC0_T2IE_MASK;		/* Turn on timer interrupts*/
              modSpiData.state = MB_RCV;
          }
          break;

      case MB_RCV:
          while (spi1RcvAvail())
          {
              modSpiData.rcvBuf[modSpiData.rcvLen++] = SPI1BUF;
              restartModTimer();
          }
          break;
    }

    IFS1CLR = (_IFS1_SPI1TXIF_MASK | _IFS1_SPI1RXIF_MASK);  /* Clear int flags */
    spiIntPinOff();
}


/************************************************************************/
/* Function    : _T2IntHandler                                          */
/* Purpose     : Interrupt Handler for T2, Modbus timer                 */
/* Inputs      : None                                                   */
/* Outputs     : None                                                   */
/* Comment     : Called from assembly language wrapper in port_asm.S    */
/************************************************************************/
void _T2IntHandler(void)
{
    spiIntPinOn();
    modSpiIdle();							/* Set Idle state			*/
	vTaskNotifyGiveFromISR(modSpiData.ioTask, NULL);
	portYIELD();
    spiIntPinOff();
}
