/****************************************************************************/
/* Copyright 2002 MBARI														*/
/****************************************************************************/
/* Summary	: I/O Support for OASIS mooring controller						*/
/* Filename : io.c															*/
/* Author	: Robert Herlien (rah)											*/
/* Project	: OASIS Mooring Replacement (OASIS3)							*/
/* Revision: 0.1															*/
/* Created	: 10/14/2002													*/
/*																			*/
/* 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:													*/
/* 10oct2002 rah - created from OASIS io.c									*/
/* $Log: io.c,v $
/* Revision 1.1	 2011/10/25 22:39:22  bobh
/* Initial oasis4 revision
/*
/* Revision 1.12  2010/12/08 00:43:40  bobh
/* Fixes for low-power sleep
/*
/* Revision 1.11  2010/11/16 23:38:24  bobh
/* Fix 'reset' code that could cause file corruption
/*
/* Revision 1.10  2010/10/26 17:15:06  bobh
/* Moved ModBus errors to file
/*
/* Revision 1.9	 2010/10/25 16:42:50  bobh
/* Changes to support scripting
/*
/* Revision 1.8	 2010/05/18 21:09:40  bobh
/* Added insertLine, deleteLine, insertChars, deleteChars
/*
/* Revision 1.7	 2010/05/17 18:48:45  bobh
/* Added cfgsave, more, ren commands
/*
/* Revision 1.6	 2010/01/21 23:53:27  mrisi
/* Support for power shutdown
/*
/* Revision 1.5	 2009/11/19 19:05:58  bobh
/* Changes to support con to remote ports and fixes to file I/O due to testing
/*
/* Revision 1.4	 2009/06/18 22:38:48  bobh
/* Modifications for 2nd rev of controller board
/*
/* Revision 1.3	 2009/04/27 22:49:43  bobh
/* First working version of remote serial ports
/*
/* Revision 1.2	 2009/04/17 18:05:04  bobh
/* Changes leading up to CDR
/*
/* Revision 1.1	 2009/03/25 20:34:41  bobh
/* Initial checkin
/*
/* Revision 1.4	 2004/08/18 21:08:35  bobh
/* Diagnostic routines added, minor bug fixes, blinky added as parm
/*
/* Revision 1.3	 2003/09/03 17:48:16  bobh
/* Further mods for config file changes
/*
 */
/****************************************************************************/

#include <mbariTypes.h>					/* MBARI type definitions			*/
#include <mbariConst.h>					/* MBARI constants					*/
#include <oasis.h>						/* OASIS controller definitions		*/
#include <custom.h>						/* Custom def's for deployment		*/
#include <io.h>							/* OASIS I/O definitions			*/
#include <spi.h>						/* OASIS SPI I/O definitions		*/
#include <serial.h>						/* OASIS serial I/O definitions		*/
#include <list.h>						/* OASIS Linked List library		*/
#include <sem.h>						/* OASIS Semaphore library			*/
#include <task.h>						/* OASIS task dispatcher			*/
#include <clock.h>						/* OASIS Clock module				*/
#include <file.h>						/* OASIS File I/O Routines			*/
#include <syslog.h>						/* Respirometer system logger		*/
#include <debug.h>

#include <cfxbios.h>					/* Persistor BIOS definitions		*/
#include <cfxpico.h>					/* Persistor PicoDOS definitions	*/
#include <stdlib.h>						/* Standard C library				*/


/********************************/
/*		External Data			*/
/********************************/

Extern Nat32			pwr_perm;		/* Devices to leave powered on		*/
Extern MBool			running;
Extern MBool			wakeOnConsoleChar;
Extern Parm_t			pwrFailCount;


/********************************/
/*		Global Data				*/
/********************************/

// This semaphore may be misnamed.	Since we only use SPI in synchronous mode,
// individual SPI operations don't need to be protected by a mutex sem.
// What DOES need to be protected are sequences of operations that use common
// resources.  E.g., switching relay involves +12V on, set bit, delay, reset bit,
// +12V off.  You don't want two threads doing this, where the first thread
// turns off 12V power while the 2nd one is still using it.

Global Semaphore		spiSem;			/* Mutex semaphore for SPI ops	*/


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

MLocal volatile Nat32	tick = 0L;		/* 10 ms ticker					*/
MLocal MBool			ioTickerRunning = FALSE;
MLocal DWord			iopwrbits = 0L; /* Current state of power relays	*/
MLocal Int16			ad_use_cnt = 0; /* Number of people using A/D	*/
MLocal WhatWokeUs		wakeupStatus;
MLocal vfptr			oldLevel4Handler, oldLevel5Handler, oldSpuriousHandler;
MLocal volatile Nat32	irq2Cnt = 0L;


/************************************************************************/
/* Function	   : ioGetTick												*/
/* Purpose	   : Return Value of system 10 ms ticker					*/
/* Inputs	   : None													*/
/* Outputs	   : Tick value												*/
/************************************************************************/
		Nat32
ioGetTick(Void)
{
  return(tick);

} /* ioGetTick() */


/************************************************************************/
/* Function	   : ioTick													*/
/* Purpose	   : Handler for PIT ticker									*/
/* Inputs	   : None													*/
/* Outputs	   : None													*/
/************************************************************************/
		MLocal Void
ioTick(Void)
{
  tick++;								/* Increment global ticker		*/

  //See if someone yanked power
  if ((pwrFailCount > 0) && (PinRead(IRQ2)) && running)
  {
	if (++irq2Cnt >= pwrFailCount)
	{
	  logDoFlush();
//		sysLog("Reset due to power fail");	/* Can't call this in interrupt context!!!*/
//		PZCacheFlush(DRIVE);			/* Already done in logDoFlush() */
	  BIOSResetToPicoDOS();
	}
  }
  else 
	irq2Cnt = 0;

} /* ioTick() */


/************************************************************************/
/* Function	   : ioPingWatchdog											*/
/* Purpose	   : Ping the External Watchdog Timer						*/
/* Inputs	   : None													*/
/* Outputs	   : None													*/
/************************************************************************/
		Void
ioPingWatchdog(Void)
{
  spiSetPIOBit(GPIO_CHIP, WDT_BIT, 1);
  task_delay(1);
  spiSetPIOBit(GPIO_CHIP, WDT_BIT, 0);

} /* ioPingWatchdog() */


/************************************************************************/
/* Function	   : ioSetHeartbeat											*/
/* Purpose	   : Set the Heartbeat LED									*/
/* Inputs	   : Boolean, ON or OFF										*/
/* Outputs	   : None													*/
/************************************************************************/
		Void
ioSetHeartbeat(MBool onoff)
{
	spiSetPIOBit(GPIO_CHIP, HEARTBEAT_BIT, onoff);

} /* ioSetHeartbeat() */


/************************************************************************/
/* Function	   : io_pwron												*/
/* Purpose	   : Turn on power to one power port						*/
/* Inputs	   : Power port number										*/
/* Outputs	   : None													*/
/************************************************************************/
		Void
io_pwron(Nat32 pwrnum)
{
	if (!isPhysPwr(pwrnum))
		return;

	sem_take(&spiSem);
	spiSetPIOBit(GPIO_CHIP, ENABLE_12V_BIT, 1);

	spiSetPIOBit(RELAY_CHIP, PWR_RELAY_SET(pwrnum), 1);
	task_delay(RELAY_DELAY);
	spiSetPIOBit(RELAY_CHIP, PWR_RELAY_SET(pwrnum), 0);

	spiSetPIOBit(GPIO_CHIP, ENABLE_12V_BIT, 0);
	iopwrbits |= (1L<<pwrnum);
	sem_give(&spiSem);

//	  dprintf("io_pwron %ld, iopwrbits = %lx, task %s\n",
//			pwrnum, iopwrbits, task_get()->td_name);

} /* io_pwron() */


/************************************************************************/
/* Function	   : io_pwroff												*/
/* Purpose	   : Turn off power to one power port						*/
/* Inputs	   : Power port number										*/
/* Outputs	   : None													*/
/************************************************************************/
		Void
io_pwroff(Nat32 pwrnum)
{
	if (!isPhysPwr(pwrnum))
		return;

	sem_take(&spiSem);
	spiSetPIOBit(GPIO_CHIP, ENABLE_12V_BIT, 1);

	spiSetPIOBit(RELAY_CHIP, PWR_RELAY_RESET(pwrnum), 1);
	task_delay(RELAY_DELAY);
	spiSetPIOBit(RELAY_CHIP, PWR_RELAY_RESET(pwrnum), 0);

	spiSetPIOBit(GPIO_CHIP, ENABLE_12V_BIT, 0);
	iopwrbits &= ~(1L<<pwrnum);
	sem_give(&spiSem);

//	  dprintf("io_pwroff %ld, iopwrbits = %lx, task %s\n",
//			pwrnum, iopwrbits, task_get()->td_name);

} /* io_pwroff() */


/************************************************************************/
/* Function	   : io_power												*/
/* Purpose	   : Set all (7) power control relays to a given state		*/
/* Inputs	   : Bit vector of instruments to turn on					*/
/* Outputs	   : None													*/
/* Comment	   : Parm declared as 32 bits per oasis3, but just 7 bits used*/
/************************************************************************/
		Void
io_power( DWord pwrbits )
{
  sem_take(&spiSem);
  spiSetPIOBit(GPIO_CHIP, ENABLE_12V_BIT, 1);

  if (pwrbits & PWR_BIT_MASK)
  {
	spiSetPIOByte(RELAY_CHIP, PWR_SET_OFFSET, (pwrbits & PWR_BIT_MASK));
	task_delay(RELAY_DELAY);
	spiSetPIOByte(RELAY_CHIP, PWR_SET_OFFSET, 0);
  }

  if (~pwrbits & PWR_BIT_MASK)
  {
	spiSetPIOByte(RELAY_CHIP, PWR_RESET_OFFSET, (~pwrbits & PWR_BIT_MASK));
	task_delay(RELAY_DELAY);
	spiSetPIOByte(RELAY_CHIP, PWR_RESET_OFFSET, 0);
  }

  spiSetPIOBit(GPIO_CHIP, ENABLE_12V_BIT, 0);
  iopwrbits = (pwrbits & PWR_BIT_MASK);
  sem_give(&spiSem);

} /* io_power() */


/************************************************************************/
/* Function	   : ioGetPwrVec											*/
/* Purpose	   : Return state of power vector							*/
/* Inputs	   : None													*/
/* Outputs	   : Bit vector of power ports turned on					*/
/************************************************************************/
		DWord
ioGetPwrVec(Void)
{
	return(iopwrbits);

} /* ioGetPwrVec() */


/************************************************************************/
/* Function	   : io_serRelay											*/
/* Purpose	   : Turn on/off serial relays to select/deselect serial port*/
/* Inputs	   : Serial port number, Boolean to select/deselect			*/
/* Outputs	   : None													*/
/************************************************************************/
		Void
io_serRelay( Nat16 port, MBool onoff)
{
	Reg Nat16	bitnum;

	if ((port <= 0) || (port >= PHYS_SER_PORTS))
		return;

	sem_take(&spiSem);
	spiSetPIOBit(GPIO_CHIP, ENABLE_12V_BIT, 1);

	bitnum =  onoff ? SER_RELAY_SET(port) : SER_RELAY_RESET(port);

	spiSetPIOBit(RELAY_CHIP, bitnum, 1);
	task_delay(RELAY_DELAY);
	spiSetPIOBit(RELAY_CHIP, bitnum, 0);

	spiSetPIOBit(GPIO_CHIP, ENABLE_12V_BIT, 0);
	sem_give(&spiSem);

} /* io_serRelay() */


/************************************************************************/
/* Function	   : io_serEnable											*/
/* Purpose	   : Enable/Disable EIA Drivers for serial port				*/
/* Inputs	   : PHYSICAL serial port number, CommsMode					*/
/* Outputs	   : None													*/
/************************************************************************/
		Void
io_serEnable(Nat16 physPort, CommsMode mode)
{
	if (physPort == CPU_SER)
		EIAForceOff(mode==COMMS_OFF);
	else if (physPort < PHYS_SER_PORTS)
	{
		// We used to protect this region with spiSem, but it's not necessary
		// since the following calls are synchronous and uninterruptible
		spiSetPIOBit(GPIO_CHIP, NOT_SHUTDN(physPort), (mode==COMMS_OFF) ? 0 : 1);
		spiSetPIOBit(GPIO_CHIP, DE_485(physPort), (mode==SEND485) ? 1 : 0);
	}

} /* io_serEnable() */


/************************************************************************/
/* Function	   : io_atod												*/
/* Purpose	   : A/D Conversion Routine									*/
/* Inputs	   : A/D Port number										*/
/* Outputs	   : 12 bit A/D value, or ERROR								*/
/* Comment	   : We used to protect this entire function with spiSem.	*/
/*				 But it's not necessary, as the operation is			*/
/*				 synchronous and uninterruptible						*/
/************************************************************************/
		Nat16
io_atod( Nat16 channel )
{
//		The Max146 returns its 12-bit precision result in 15 bits, with the 
//		LSB 3 bits as zeroes.  We just shift it back.
	return(spiAtoD(channel) >> 3);

} /* io_atod() */


/************************************************************************/
/* Function	   : io_AtoDPwrOn											*/
/* Purpose	   : Turn on power to A/D chip and aux circuits				*/
/* Inputs	   : auxPwrOn = TRUE to turn on +5 to aux circuits			*/
/*				 A/D Port number to pre-select for 1st conversion		*/
/* Outputs	   : None													*/
/* Comment	   : We used to protect this entire function with spiSem.	*/
/*				 But it's not necessary, as all the operations are		*/
/*				 synchronous and uninterruptible (except final delay)	*/
/************************************************************************/
		Void
io_AtoDPwrOn(Nat16 channel)
{
	ad_use_cnt++;
	spiSetPIOBit(GPIO_CHIP, ANALOG_ENABLE_BIT, 1);
	spiAtoDPower(ON, channel);
	task_delay(1);

} /* ioAtoDPwrOn() */


/************************************************************************/
/* Function	   : io_AtoDPwrOff											*/
/* Purpose	   : Turn off power to A/D chip if we're last one using it	*/
/* Inputs	   : None													*/
/* Outputs	   : None													*/
/* Comment	   : We used to protect this entire function with spiSem.	*/
/*				 But it's not necessary, as all the operations are		*/
/*				 synchronous and uninterruptible						*/
/************************************************************************/
		Void
io_AtoDPwrOff(Void)
{
	if (--ad_use_cnt <= 0)
	{
		ad_use_cnt = 0;
		spiAtoDPower(OFF, 0);
		spiSetPIOBit(GPIO_CHIP, ANALOG_ENABLE_BIT, 0);
	}

} /* ioAtoDPwrOff() */


/************************************************************************/
/* Function	   : irq5ISR												*/
/* Purpose	   : Interrupt Handler for IRQ4 and IRQ5					*/
/* Inputs	   : None													*/
/* Outputs	   : None													*/
/* This single interrrupt service routine handles the IRQ4 and IRQ5		*/
/* interrupts and the very likely spurious interrupts that my be		*/
/* generated by the repeated asynchronous and non-acknowledged pulses	*/
/* of RS-232 input.														*/
/* The handler simply reverts IRQ{4,5} back to input (to prevent further*/
/* level sensative interrupts) and returns. It's assumed the routine	*/
/* that set this up is just looking for the side effect of breaking the */
/* CPU out of LPSTOP mode.												*/
/* Note that this very simple handler is defined as a normal C function */
/* rather than an IEV_C_PROTO/IEV_C_FUNCT. We can do this because we know*/
/* (and can verify by checking the disassembly) that is generates only	*/
/*	direct addressing instructions and will not modify any registers.	*/
/************************************************************************/
MLocal void Irq5ISR(void)
{
  PinIO(IRQ4RXD);				// 31 // /IRQ4 (tied to Rx)
  PinIO(IRQ5);
  wakeupStatus = WakePinOrConsoleChar;
  RTE();
}


/************************************************************************/
/* Function	   : io_init												*/
/* Purpose	   : Initialize I/O system									*/
/* Inputs	   : None													*/
/* Outputs	   : None													*/
/************************************************************************/
		Void
io_init( Void )
{
  uchar			mirrorpins[] = 
	  {1, 15, 16, 17, 18, 19, 21, 22, 23, 24, 25, 26, 
	   27, 28, 29, 30, 31, 32, 33, 34, 35, 37, 0 };

  TMGSetSpeed(CPU_CLOCK);				/* Set system clock speed		*/
  RTCInit(RTC_INT_LEVEL);
  PIOMirrorList(mirrorpins);			/* Set unused CPU pins as outputs*/
  PinIO(IRQ4RXD);
  PinIO(IRQ5);
  PinIO(IRQ2);
  PinRead(IRQ2);

  tick = 0L;							/* Initialize ticker			*/
  PITSet100usPeriod(10000/TICKS_PER_SECOND);
										/* Set our tick interval		*/
  PITAddChore(ioTick, TICK_INT_LEVEL);	/* Set tick interrupt handler	*/
  ioTickerRunning = TRUE;
  sem_init(&spiSem, MUTEX, 1);
  sem_name(&spiSem, "spiSem");

  spiInit();

  oldLevel4Handler = IEVInsertAsmFunct(Irq5ISR, level4InterruptAutovector);
  oldLevel5Handler = IEVInsertAsmFunct(Irq5ISR, level5InterruptAutovector);
  oldSpuriousHandler = IEVInsertAsmFunct(Irq5ISR, spuriousInterrupt);

} /* io_init() */


/************************************************************************/
/* Function	   : io_pwrup												*/
/* Purpose	   : Prepare I/O system after powerup						*/
/* Inputs	   : None													*/
/* Outputs	   : None													*/
/************************************************************************/
		Void
io_pwrup( Void )
{
  tick = 0L;									/* Initialize ticker	*/
  ad_use_cnt = 0;
  PITSet100usPeriod(10000/TICKS_PER_SECOND);	/* Set tick interval	*/
  if (!ioTickerRunning)
	PITAddChore(ioTick, TICK_INT_LEVEL);		/* Set tick int handler */
  ioTickerRunning = TRUE;
  spiShutdownMode(TRUE);						/* Turn on SPI chips	*/

  EIAForceOff(false);							// turn on the RS232 driver
  EIAEnableRx(true);							// enable RS232 receiver
  CFEnable(true);								// turn on the CompactFlash card
  ciflush();									// discard any garbage characters
  clk_read();									/* Re-init clock		*/

// Note - set serial enables (/SHTDN)
//	io_setWakeupByte(0);
//	io_power(pwr_perm);							/* Set quiescent pwr bits*/

} /* io_pwrup() */


/************************************************************************/
/* Function	   : doLPStop												*/
/* Purpose	   : Execute LPStop for one period, until interrupt wakes us*/
/* Inputs	   : None													*/
/* Outputs	   : Wakeup enum											*/
/************************************************************************/
		MLocal WhatWokeUs
doLPStop(void)
{
  wakeupStatus = WakeNone;

  if (PinRead(IRQ5)==0)
	  return(WakePin);

  PinBus(IRQ5);

  if (wakeOnConsoleChar)
  {
	  if (PinRead(IRQ4RXD)==0)
		  return(WakeConsoleChar);
	  PinBus(IRQ4RXD);
  }

  LPStopCSE(FullStop);

  PinIO(IRQ4RXD);
  PinIO(IRQ5);

  if (wakeupStatus == WakeNone)
	return(WakeTmout);
  else if (PinRead(IRQ5) == 0)
	  return(WakePin);

  return(WakeConsoleChar);

} /* doLPStop() */


/************************************************************************/
/* Function	   : io_pwrdown												*/
/* Purpose	   : Prepare I/O system for powerdown						*/
/* Inputs	   : Number of seconds to power down						*/
/* Outputs	   : Wakeup enum											*/
/************************************************************************/
		WhatWokeUs
io_pwrdown( Nat32 secs )
{
  Nat16			i;
  Nat32			secsToStop;
  Int32			timeToStop;
  WhatWokeUs	rtn;

//	io_power(pwr_perm);							/* Set quiescent pwr bits*/

  for (i = CPU_SER+1; i < PHYS_SER_PORTS; i++)
	  io_serEnable(i, COMMS_OFF);

  spiSetPIOByte(GPIO_CHIP, ENABLE_12V_BIT, 0);	/* Turn off 12V, analog, GF*/

  spiPwrdown();									/* Turn off SPI chips	*/

  if (ioTickerRunning)
	PITRemoveChore(ioTick);						/* Turn off ticker		*/
  ioTickerRunning = FALSE;
  PITSet51msPeriod(SLEEP_51MS_TICKS);			/* Set PIT for 51 ms	*/
  srand((unsigned int)tick);					/* Seed random nmbr gen */

  SCITxWaitCompletion();						// let any pending UART data finish
  EIAForceOff(true);							// turn off the RS232 driver
  CFEnable(false);								// turn off the CompactFlash card

  secsToStop = min(secs, MAX_SLEEP_TIME);		/* Must wake every 1/2 hr*/
												/* to keep HW watchdog alive*/
  for (timeToStop = 10000L*secsToStop/512; timeToStop > SLEEP_51MS_TICKS;
	   timeToStop -= SLEEP_51MS_TICKS)
	if ((rtn = doLPStop()) != WakeTmout)
	  break;

  return(rtn);

} /* io_pwrdown() */
