/******************************************************************************
** Copyright 2015 MBARI - Monterey Bay Aquarium Research Institute
*******************************************************************************
*******************************************************************************
** Summary  : Routines to access CS5506 20 Bit AtoD and DataAcq Support IC's
** Filename : dura_adc.c
** Author   : Luke Coletti
** Project  :
** Version  : 1.0
** Compiler : MetroWerks Code Warrior v8.3
** Created  : 12/15/14
** Archived :
*******************************************************************************
** Modification History:
** 01/02/15 : CF2 Port, LJC
*******************************************************************************/

#include    <cfxpico.h>
#include    <stdio.h>
#include    <stdlib.h>
#include    <math.h>
#include	<io_regs.h>
#include	<pcal6416.h>
#include	<tca6424.h>
#include	<ds2482.h>
#include	<dura_adc.h>

//#define IKIB_DEBUG


int AtodPowerOn(void)
{
	char ebuf[ERRBUFSIZE];
	int ret;

	if( dura_atod_pwr == TRUE )
		return( TRUE );
	else{
		PCAL6416_WritePortBit(PCAL6416_OUTPUT_PORT0, DURA_PWR_ON, HIGH);                        // power up board, turn on ISO DC/DC
		DelaySecs( 2 );                                                                                    // wait 2secs for board to power up
		ret = AtodInit();                                                                                                               // initialize
		if( ret == TRUE ) {
			dura_atod_pwr = TRUE;
			TCA6424_WritePortBit(TCA6424_OUTPUT_PORT2, A_SUPPLY_SHDN, HIGH);                // disable standby bias supply
			return( TRUE );
		}
		else{
			sprintf(ebuf, "AtoD Power On Failure!");
			LogErrorMsg(ebuf, (int)ATOD_PWR_ON_FAILURE, sys_message_enable);
			RegIO_ClrBit( DURA_ADC_CLK );                                                                                   // take ADC_CLK low
			PCAL6416_WritePortBit(PCAL6416_OUTPUT_PORT0, DURA_PWR_ON, LOW);                 // power down board, turn off ISO DC/DC
			dura_atod_pwr = FALSE;
			return( ret );
		}
	}
}


int AtodPowerOff(void)
{

	if( dura_atod_pwr == FALSE ) {
		return( TRUE );
	}
	else{
		AtodDisable();
		TCA6424_WritePortDefaults();    // turn everything off
		RegIO_ClrBit( DURA_ADC_CLK );           // take ADC_CLK low
		PCAL6416_WritePortBit(PCAL6416_OUTPUT_PORT0, DURA_PWR_ON, LOW);                 // power down board, turn off ISO DC/DC
		dura_atod_pwr = FALSE;
		return( TRUE );
	}

}


int AtodInit(void)
{
#if DURA_SENSOR_TYPE == DURA_III
char ebuf[ERRBUFSIZE];
#endif
	int ret;

	ret = TCA6424_Init();
	
	// If the DuraFET adapter is NOT attached, TCA6424_Init() will always return I2C_ACK (1) with revB MSC hardware 
	// To fix, the MSC board needs a weak pull-up on the following: DURA_I2C_RXD and DURA_I2C_INT
	// To fix, the MSC board needs a weak pull-dn on the following: DURA_ADC_DAT
	
#if DURA_SENSOR_TYPE == DURA_III
    if( DS2482_detect() != TRUE ){
      sprintf(ebuf, "DS2482 Init Failure!");
      LogErrorMsg(ebuf, (int)DS2482_INIT_FAILURE, sys_message_enable);
      }
#endif

	return( ret );

}


int AtodReset(void)
{
	int ret;

	AtodPowerOff();
	DelayMilliSecs( 100 );     // wait 100ms
	ret = AtodPowerOn();
	ret &= AtodEnable();

	//printf("AtodReset() ret = %d\n", ret);
	
	return( ret );

}


int AtodConversion(ulong *Counts, float *Volts)
{
	uchar Port0;
	int i, bipolar;
	ushort sav_mask;
	ulong cts;
	float vref, adval;
	RTCTimer TmOut;

	sav_mask = CPUReadInterruptMask();              //save current interrupt mask value
	CPUWriteInterruptMask(IEV_MASK_ALL);            //disable ALL interrupts

	if( dura_led_enable == TRUE ) {
		TCA6424_WritePortBit(TCA6424_OUTPUT_PORT2, STATUS_LED, LOW); //low true
	}
	TCA6424_ReadPort(TCA6424_INPUT_PORT0, &Port0);

	if( Port0 & ADC_POL ) //is the part currently in bipolar mode
		bipolar = TRUE;
	else
		bipolar = FALSE;

	TCA6424_WritePortBit(TCA6424_OUTPUT_PORT0, ADC_CONV, HIGH);
	TCA6424_WritePortBit(TCA6424_OUTPUT_PORT0, ADC_CONV, LOW);
	TCA6424_ArmINT();

	RTCCountdownTimerSetup(&TmOut, CS5506_CONV_TIMEOUT);    // set timeout to 75ms
	do {                                                                    //CS5506 conv time (Tcon) is approx 49.5mS with a 32kHz OSC
		if( RTCCountdownTimeout(&TmOut) == true       ) {
			CPUWriteInterruptMask(sav_mask);                                //update with previously saved mask
			return( TIMEOUT );
		}
	} while( TCA6424_MonINT() == FALSE );

	TCA6424_WritePortBit(TCA6424_OUTPUT_PORT0, ADC_CS, LOW);
	cts = 0L;
	for( i = 1; i <= 20; i++ ) {                                    // 20 bits of data
		cts += (ulong)(RegIO_RdWord() & DURA_ADC_DAT);  // read data bit, MSB first
		RegIO_SetBit( DURA_ADC_CLK );                                           // take ADC_CLK high
		RegIO_ClrBit( DURA_ADC_CLK );                                           // take ADC_CLK low
		if(i != 20)                                             // shift it along...
			cts <<= 1;
	}
	TCA6424_WritePortBit(TCA6424_OUTPUT_PORT0, ADC_CS, HIGH);

	*Counts = cts & 0xFFFFF;
	adval   = (float)*Counts;
	vref    = (float)CS5506_VREF;
	if( bipolar ) {
		if( adval == 0 )
			adval = 1;
		*Volts = ((adval - (float)0x80000) / (float)0x7FFFF) * vref; //BIPOLAR_ADC_COUNT_DIVISOR  = 2^19 - 1 = 0x7FFFF
	}
	else
		*Volts =  (adval / (float)0xFFFFF) * vref;         //UNIPOLAR_ADC_COUNT_DIVISOR = 2^20 - 1 = 0xFFFFF

	if( dura_led_enable == TRUE ) {
		TCA6424_WritePortBit(TCA6424_OUTPUT_PORT2, STATUS_LED, HIGH); //LED off now
	}

	CPUWriteInterruptMask(sav_mask);                                                                //update with previously saved mask

	return( TRUE );

}


int AtodCalibrate(void)
{
	uchar Port0;

	TCA6424_ReadPort(TCA6424_INPUT_PORT0, &Port0);

	if( (Port0 & ADC_SHDN) )                //is the part currently in Sleep Mode
		return( ERROR );

	TCA6424_WritePortBit(TCA6424_OUTPUT_PORT0, ADC_CAL, HIGH);

	TCA6424_WritePortBit(TCA6424_OUTPUT_PORT0, ADC_CONV, HIGH);
	TCA6424_WritePortBit(TCA6424_OUTPUT_PORT0, ADC_CONV, LOW);

	TCA6424_WritePortBit(TCA6424_OUTPUT_PORT0, ADC_CAL, LOW);

   	DelayMilliSecs( 150 );					//wait for cal cycle (Tcal) approx 100mS to complete
	
	TCA6424_ReadPort(TCA6424_INPUT_PORT0, &Port0);

	if( !(Port0 & ADC_DRDY) )               //is DRDY being asserted
		return( ERROR );
	else
		return( TRUE );

}


int AtodSleep(uchar State)
{
	uchar Port0;


	TCA6424_ReadPort(TCA6424_INPUT_PORT0, &Port0);

	if( State == CS5506_SLEEP ) {   //enter sleep mode
		if( !(Port0 & ADC_SHDN) ) { //is the part currently in Active Mode
			TCA6424_WritePortBit(TCA6424_OUTPUT_PORT0, ADC_SHDN, HIGH);
			return( TRUE );
		}
		else
			return( OK );
	}

	else if( State == CS5506_ACTIVE ) {         //enter active mode
		if( Port0 & ADC_SHDN ) {                //is the part currently in Sleep Mode
			TCA6424_WritePortBit(TCA6424_OUTPUT_PORT0, ADC_SHDN, LOW);
			DelaySecs( 1 ); 			       //1sec start-up delay, Tres + Tosu + Twup
			return( TRUE );
		}
		else
			return( OK );
	}

	else
		return( ERROR );

}


int AtodPolarity(uchar State)
{
	uchar Port0;

	TCA6424_ReadPort(TCA6424_INPUT_PORT0, &Port0);

	if( State == CS5506_UNIPOLAR ) { //set AD into unipolar mode
		if( Port0 & ADC_POL ) { //is the part currently in bipolar mode
			TCA6424_WritePortBit(TCA6424_OUTPUT_PORT0, ADC_POL, LOW);
			return( TRUE );
		}
		else
			return( OK );
	}

	else if( State == CS5506_BIPOLAR ) { //set AD into bipolar mode
		if( !(Port0 & ADC_POL) ) { //is the part currently in unipolar mode
			TCA6424_WritePortBit(TCA6424_OUTPUT_PORT0, ADC_POL, HIGH);
			return( TRUE );
		}
		else
			return( OK );
	}

	else
		return( ERROR );

}


int AtodChannel(uchar Chan)
{
	uchar Port0;

	if( (Chan < 0x00) || (Chan > 0x03) )
		return( ERROR );

	TCA6424_ReadPort(TCA6424_INPUT_PORT0, &Port0);

	if( (Port0 & 0x03) == Chan )
		return( OK );

	Port0 &= 0xFC; //clear lower two bits
	Port0 |= Chan; //set lower two bits with new chan val

	TCA6424_WritePort(TCA6424_OUTPUT_PORT0, Port0);

	return( TRUE );

}


int AtodIamp(uchar Gain, uchar Enable)
{
	uchar Port1, Port2;

	TCA6424_ReadPort(TCA6424_INPUT_PORT2, &Port2);

	if( Enable ) {                  //enable part
		if( !(Port2 & IAMP_SHDN) ) //is the part currently in shutdown mode
			TCA6424_WritePortBit(TCA6424_OUTPUT_PORT2, IAMP_SHDN, HIGH);
	}
	else{                           //disable part
		if( Port2 & IAMP_SHDN ) { //is the part currently in active mode
			TCA6424_WritePortBit(TCA6424_OUTPUT_PORT2, IAMP_SHDN, LOW);
			return( TRUE );
		}
		else
			return( OK );
	}

	TCA6424_ReadPort(TCA6424_INPUT_PORT1, &Port1);

	if( (Port1 & 0x78) == Gain ) //test bits [3...6]
		return( OK );

	Port1 &= 0x87; //clear bits [3...6]
	Port1 |= Gain; //set bits [3...6] with new gain val

	TCA6424_WritePort(TCA6424_OUTPUT_PORT1, Port1);
	TCA6424_WritePortBit(TCA6424_OUTPUT_PORT1, IAMP_LATCH, LOW);
	TCA6424_WritePortBit(TCA6424_OUTPUT_PORT1, IAMP_LATCH, HIGH);

	return( TRUE );


}


int AtodAnalogSw(uchar Chan, uchar Enable)
{
	uchar Port1;

	if( (Chan < 0x00) || (Chan > 0x03) )
		return( ERROR );

	TCA6424_ReadPort(TCA6424_INPUT_PORT1, &Port1);

	if( Enable ) {                  //enable part
		if( !(Port1 & ANSW_EN) ) //is the part currently disabled
			TCA6424_WritePortBit(TCA6424_OUTPUT_PORT1, ANSW_EN, HIGH);
	}
	else{                           //disable part
		if( Port1 & ANSW_EN ) { //is the part currently enabled
			TCA6424_WritePortBit(TCA6424_OUTPUT_PORT1, ANSW_EN, LOW);
			return( TRUE );
		}
		else
			return( OK );
	}

	TCA6424_ReadPort(TCA6424_INPUT_PORT1, &Port1);

	if( (Port1 & 0x03) == Chan )
		return( OK );

	Port1 &= 0xFC; //clear lower two bits
	Port1 |= Chan; //set lower two bits with new chan val

	TCA6424_WritePort(TCA6424_OUTPUT_PORT1, Port1);

	return( TRUE );

}


int AtodHighZBuffers(uchar Device, uchar Enable)
{
	uchar Port2;

	TCA6424_ReadPort(TCA6424_INPUT_PORT2, &Port2);

	if( Enable ) {                    //enable parts
		if( Device == VRS_BUFFER ) { //enable LTC2050
			if( !(Port2 & VRS_BUFF_SHDN) ) { //is the part currently in shutdown mode
				TCA6424_WritePortBit(TCA6424_OUTPUT_PORT2, VRS_BUFF_SHDN, HIGH);
				return( TRUE );
			}
			else
				return( OK );
		}
		else if( Device == TEMP_BUFFER ) { //enable LTC1152
			if( !(Port2 & TEMP_BUFF_SHDN) ) { //is the part currently in shutdown mode
				TCA6424_WritePortBit(TCA6424_OUTPUT_PORT2, TEMP_BUFF_SHDN, HIGH);
				return( TRUE );
			}
			else
				return( OK );
		}
		else if( Device == VK_BUFFER ) { //enable LTC2050
			if( !(Port2 & VK_BUFF_SHDN) ) { //is the part currently in shutdown mode
				TCA6424_WritePortBit(TCA6424_OUTPUT_PORT2, VK_BUFF_SHDN, HIGH);
				return( TRUE );
			}
			else
				return( OK );
		}
	}
	else{                             //disable parts
		if( Device == VRS_BUFFER ) { //enable LTC2050
			if( Port2 & VRS_BUFF_SHDN ) { //is the part currently in active mode
				TCA6424_WritePortBit(TCA6424_OUTPUT_PORT2, VRS_BUFF_SHDN, LOW);
				return( TRUE );
			}
			else
				return( OK );
		}
		else if( Device == TEMP_BUFFER ) { //enable LTC1152
			if( Port2 & TEMP_BUFF_SHDN ) { //is the part currently in active mode
				TCA6424_WritePortBit(TCA6424_OUTPUT_PORT2, TEMP_BUFF_SHDN, LOW);
				return( TRUE );
			}
			else
				return( OK );
		}
		else if( Device == VK_BUFFER ) { //enable LTC2050
			if( Port2 & VK_BUFF_SHDN ) { //is the part currently in active mode
				TCA6424_WritePortBit(TCA6424_OUTPUT_PORT2, VK_BUFF_SHDN, LOW);
				return( TRUE );
			}
			else
				return( OK );
		}
	}


	return( ERROR );


}


int AtodEnable(void)
{
	int ret;

	ret = AtodSleep(CS5506_ACTIVE);

	ret &= AtodHighZBuffers(VRS_BUFFER, ENABLE);
	ret &= AtodHighZBuffers(VK_BUFFER, ENABLE);
	
#if DURA_SENSOR_TYPE == DURA_III
    ret &= AtodHighZBuffers(TEMP_BUFFER, ENABLE);
#endif

	ret &= AtodAnalogSw(ADG1609_AIN4, ENABLE);

	ret &= AtodIamp(LTC6915_G1, ENABLE);

	ret &= AtodCalibrate();

	return( ret );

}


int AtodDisable(void)
{
	int ret;

	ret = AtodSleep(CS5506_SLEEP);

	ret &= AtodHighZBuffers(VRS_BUFFER, DISABLE);
	ret &= AtodHighZBuffers(VK_BUFFER, DISABLE);

#if DURA_SENSOR_TYPE == DURA_III
    ret &= AtodHighZBuffers(TEMP_BUFFER, DISABLE);
#endif

	ret &= AtodAnalogSw(ADG1609_AIN4, ENABLE);
	ret &= AtodAnalogSw(ADG1609_AIN4, DISABLE);

	ret &= AtodIamp(LTC6915_G1, ENABLE);
	ret &= AtodIamp(LTC6915_G1, DISABLE);

	return( ret );

}


int AtodSelectValue(ushort C)
{

	switch( C ) {

	case DURAFET_VRS_EXT:
		AtodChannel(CS5506_AIN2);
		AtodPolarity(CS5506_BIPOLAR);
		break;

	case DURAFET_VRS_INT:
		AtodChannel(CS5506_AIN3);
		AtodPolarity(CS5506_BIPOLAR);
		break;

	case DURAFET_VK:
		AtodChannel(CS5506_AIN4);
		AtodPolarity(CS5506_BIPOLAR);
		break;

	case DURAFET_IK_4096:
	case DURAFET_IK_512:
	case DURAFET_IK_32:
	case DURAFET_IK_4:
		AtodChannel(CS5506_AIN1);
		AtodPolarity(CS5506_BIPOLAR);
		AtodAnalogSw(ADG1609_AIN1, ENABLE);
		if( C == DURAFET_IK_4096 )
			AtodIamp(LTC6915_G4096, ENABLE);
		if( C == DURAFET_IK_512 )
			AtodIamp(LTC6915_G512, ENABLE);
		if( C == DURAFET_IK_32 )
			AtodIamp(LTC6915_G32, ENABLE);
		if( C == DURAFET_IK_4 )
			AtodIamp(LTC6915_G4, ENABLE);
		break;

	case DURAFET_IB_4096:
	case DURAFET_IB_512:
	case DURAFET_IB_32:
	case DURAFET_IB_4:
		AtodChannel(CS5506_AIN1);
		AtodPolarity(CS5506_BIPOLAR);
		AtodAnalogSw(ADG1609_AIN2, ENABLE);
		if( C == DURAFET_IB_4096 )
			AtodIamp(LTC6915_G4096, ENABLE);
		if( C == DURAFET_IB_512 )
			AtodIamp(LTC6915_G512, ENABLE);
		if( C == DURAFET_IB_32 )
			AtodIamp(LTC6915_G32, ENABLE);
		if( C == DURAFET_IB_4 )
			AtodIamp(LTC6915_G4, ENABLE);
		break;

	case DURAFET_TEMP:
		AtodChannel(CS5506_AIN1);
		AtodPolarity(CS5506_UNIPOLAR);
		AtodIamp(LTC6915_G1, ENABLE);
		AtodAnalogSw(ADG1609_AIN3, ENABLE);
		break;

	case DURAFET_BATT:
		AtodChannel(CS5506_AIN1);
		AtodPolarity(CS5506_UNIPOLAR);
		AtodIamp(LTC6915_G1, ENABLE);
		AtodAnalogSw(ADG1609_AIN4, ENABLE);
		TCA6424_WritePortBit(TCA6424_OUTPUT_PORT2, VBAT_MONITOR, HIGH);
		break;

	default:
		return( ERROR );

	}

	return( OK );

}


int AtodProcessValue(ushort C, float *V)
{
	float R;

	switch( C ) {

	case DURAFET_VRS_EXT:
	case DURAFET_VRS_INT:
	case DURAFET_VK:
		break;

	case DURAFET_IK_4096:
	case DURAFET_IK_512:
	case DURAFET_IK_32:
	case DURAFET_IK_4:
		if( fabs(*V) > IK_V_MAX ) {
			if( *V >= 0.0 )
				*V = IK_ERANGE_POS;
			else
				*V = IK_ERANGE_NEG;
		}
		else{
			if( C == DURAFET_IK_4096 )
				*V = *V / 15000.0 / 4096.0;
			if( C == DURAFET_IK_512 )
				*V = *V / 15000.0 / 512.0;
			if( C == DURAFET_IK_32 )
				*V = *V / 15000.0 / 32.0;
			if( C == DURAFET_IK_4 )
				*V = *V / 15000.0 / 4.0;
		}
		break;

	case DURAFET_IB_4096:
	case DURAFET_IB_512:
	case DURAFET_IB_32:
	case DURAFET_IB_4:
		if( fabs(*V) > IB_V_MAX ) {
			if( *V >= 0.0 )
				*V = IB_ERANGE_POS;
			else
				*V = IB_ERANGE_NEG;
		}
		else{
			if( C == DURAFET_IB_4096 )
				*V = *V / 15000.0 / 4096.0;
			if( C == DURAFET_IB_512 )
				*V = *V / 15000.0 / 512.0;
			if( C == DURAFET_IB_32 )
				*V = *V / 15000.0 / 32.0;
			if( C == DURAFET_IB_4 )
				*V = *V / 15000.0 / 4.0;
		}
		break;

	case DURAFET_TEMP:
		R  = *V * 20000.0 / (-*V + (float)CS5506_VREF);
		*V = (float)rtot( (double)R, SHHCoefs );
		break;

	case DURAFET_BATT:
		TCA6424_WritePortBit(TCA6424_OUTPUT_PORT2, VBAT_MONITOR, LOW);
		*V = *V * 2.0;
		break;

	default:
		*V=-4.0;
		return( ERROR );

	}

	return( OK );

}


int AtodReadValue(ushort C, ushort D, ushort N, float *V, float *S)
{
	char ebuf[ERRBUFSIZE];
	uchar Port0, Port1, Port2;
	int i, retries;
	ulong counts;
	float *AD;
#ifdef IKIB_DEBUG
	float min, max;
#endif


	AD = calloc((size_t)N, sizeof(float));
	if( AD == NULL ) {
		sprintf(ebuf, "CAN'T allocate AtoD Array memory, N = %hu");
		LogErrorMsg(ebuf, (int)MALLOC_FAILURE, sys_message_enable);
		*V=*S=-1.0;
		return( ERROR );
	}

	retries = 0;

Retry:
	AtodSelectValue( C );
	DelayMilliSecs( D );	//wait D ms

	for( i=0; i<N; i++ ) { //collect data at 10Hz rate

		Sleep(0, 1); // init for 10Hz loop

		if( AtodConversion( &counts, &AD[i] ) == TIMEOUT ) {
			TCA6424_ReadPorts(&Port0, &Port1, &Port2);
			sprintf(ebuf, "AtoD Read Timeout, %hu, 0x%02X, 0x%02X, 0x%02X", C, Port0, Port1, Port2);
			LogErrorMsg(ebuf, (int)ATOD_READ_TIMEOUT, sys_message_enable);
			if( AtodReset() == TRUE ) {
				++retries;
				if( retries <= ATOD_RESET_RETRIES )
					goto Retry;
				else{
					sprintf(ebuf, "AtoD Read Failure");
					LogErrorMsg(ebuf, (int)ATOD_READ_FAILURE, sys_message_enable);
					free(AD);
					*V=*S=-2.0;
					return( ERROR );
				}
			}
			else{
				sprintf(ebuf, "AtoD Reset Failure");
				LogErrorMsg(ebuf, (int)ATOD_RESET_FAILURE, sys_message_enable);
				free(AD);
				*V=*S=-3.0;
				return( ERROR );
			}
		}

		Sleep(TEN_HZ_LOOP, 0); // sleep for whatever is left of the 100ms loop rate

	}

	if( N > 1 ) {
		FloatVectorMeanStdDev(N, AD, V, S);
#ifdef IKIB_DEBUG
		if( C == DURAFET_IB_4096 ) {
			FloatVectorMinMax(N, AD, &min, &max);
			for( i=0; i<N; i++ )
				printf("Ib = AD [%d] = %e\n", i, AD[i]);
			printf("\nIb = %e min %e max %e mean %e sd\n", min, max, *V, *S);
		}

		if( C == DURAFET_IK_4096 ) {
			FloatVectorMinMax(N, AD, &min, &max);
			for( i=0; i<N; i++ )
				printf("Ik = AD [%d] = %e\n", i, AD[i]);
			printf("\nIk = %e min %e max %e mean %e sd\n", min, max, *V, *S);
		}
#endif
	}
	else{
		*V  = AD[0];
		*S  = 0.0;
	}

	free(AD);

	return ( AtodProcessValue( C, V ) );

}

