#include "config.h"
#include "adcext.h"

/* The ADC (AD7846) is tricky, as new conversions start
   automatically when the previous data read finishes.
   To allow more control over start/read, we stretch the
   read indefinitely by delaying the final SCK edge.
   This means we can't have the hardware do SPI for us. */

/* External serial clock, 2-wire I/O -- tie /EXT low,
   tie ADC SDI for rate selection, tie /CS low, use BUSY for 
   interrupt notification if desired */

/* Since the ADC is 5v, ADC SDO must be on a digital-only pin.
   All exposed pins on B D and E are analog, so use SPI pins:
   ADC SDO = PIC SDI1 = RF7
   ADC SCK = PIC SCK1 = RF6
*/
#define TRIS_SDO TRISFbits.TRISF7
#define R_SDO PORTFbits.RF7
#define TRIS_SCK TRISFbits.TRISF6
#define LAT_SCK LATFbits.LATF6

/* Short delays */
#define wait_200ns() do { \
	nop(); nop(); nop(); nop(); nop(); nop(); nop();  nop(); \
	} while(0)
#define wait_25ns() nop()

/* Initialize ADC */
void adcext_init(void)
{
	int32_t i;

	TRIS_SDO = 1;
	LAT_SCK = 0;
	TRIS_SCK = 0;
	
	/* Startup delay CS down to SCK high t4 = 5000ns */
	for (i = 0; i < 5000 / 25; i++)
		wait_25ns();

	/* We need to monitor BUSY, I think.  With the 2-wire
	   interface, we never know if we start in the middle of 
       a data output phase. For now, consider reading broken..
	   just return early.  XXXX */
	return;

	/* Trigger a dummy read so we're prepared for the
	   next conversion */
	(void) adcext_read();
}

/* Start a conversion if it hasn't already been started.
   Wait for conversion to finish.
   Read the result and return the raw 32-bit value. */
uint32_t adcext_read(void)
{
	uint32_t val;
	int i;

	/* Start conversion by completing previous read */
	LAT_SCK = 0;

	/* Wait tKQMAX for SCK down to SDO valid */
	wait_200ns();

	/* Wait for conversion to finish */
	while (R_SDO == 1)
		continue;

	/* Read it out */
	val = 0;
	for (i = 0; i < 32; i++) {
		/* SCK low tLESCK = 25ns */
		wait_25ns();
		LAT_SCK = 1;

		/* SCK high tHESCK = 25ns, but
		   we also have SCK down to SDO valid tKQMAX = 200ns?
	   	   Probably misspecified but wait tKQMAX anyway. */
		wait_200ns();

		val <<= 1;
		if (R_SDO) 
			val |= 1;
		/* Leave SCK high on final bit to delay new conversion */
		if (i < 31)
			LAT_SCK = 0;
	}

	/* Done */
	return val;
}

/* Convert a raw 32-bit value into a signed 32-bit result.
   The return value is full int32 range but only the high
   24 should be significant: low 3 will always be 0,
   and the next 5 will be sub-resolution (see datasheet). */
int32_t adcext_convert(uint32_t raw)
{
	int sigmsb = (raw >> 28) & 3;

	/* If SIG & MSB, it is a positive overflow */
	if (sigmsb == 3)
		return (int32_t)0x7FFFFFFFL;
	/* If !SIG & !MSB, it is a negative overflow */
	if (sigmsb == 0)
		return (int32_t)0x80000000L;
	/* Shift over EOC,DMY,SIG and return */
	return ((int32_t)(raw << 3));
}

/* Start a new conversion.  If a conversion was already started
   but the result was not read, this does nothing. */
void adcext_start_conversion(void)
{
	/* If we had a previous conversion ready to read,
	   read it out so we can start a new conversion instead */
	if (adcext_is_conversion_ready())
		(void) adcext_read();

	/* Start conversion by completing previous read */
	LAT_SCK = 0;

	/* Wait tKQMAX for SCK down to SDO valid in case we
	   call adcext_is_conversion_ready right away. */
	wait_200ns();
}

/* Return 1 if a conversion is finished and ready to be read, 0 otherwise */
int adcext_is_conversion_ready(void)
{
	if (LAT_SCK == 0 && R_SDO == 0)
		return 1;
	return 0;
}
