// ****************************************************************************
// INCLUDE FILES: Header files of modules referenced by this module
// ****************************************************************************
#include <Ad77x8Driver.h>
#include <Ad77x8DriverSetup.h>
#include <AssertISTP.h>


// ****************************************************************************
// PRIVATE TYPES: Private types used by this module only
// ****************************************************************************
typedef enum
{
	AD77X8_REG_COMMUNICATION_WRITE = 0,
	AD77X8_REG_STATUS_READ = 0,
	AD77X8_REG_MODE = 1,
	AD77X8_REG_ADC_CONTROL = 2,
	AD77X8_REG_FILTER = 3,
	AD77X8_REG_ADC_DATA = 4,
	AD77X8_REG_ADC_OFFSET = 5,
	AD77X8_REG_ADC_GAIN = 6,
	AD77X8_REG_IO_CTRL = 7,
	AD77X8_REG_UNDEFINED_1 = 8,
	AD77X8_REG_UNDEFINED_2 = 9,
	AD77X8_REG_UNDEFINED_3 = 10,
	AD77X8_REG_UNDEFINED_4 = 11,
	AD77X8_REG_TEST_1 = 12,
	AD77X8_REG_TEST_2 = 13,
	AD77X8_REG_UNDEFINED_5 = 14,
	AD77X8_REG_ID = 15,
	AD77X8_REG_NUMBER
}Ad77x8DriverRegisterId_t;

typedef enum
{
    AD77X8_MODE_POWER_DOWN = 0,
    AD77X8_MODE_IDLE = 1,
    AD77X8_MODE_SINGLE_CONVERSION = 2,
    AD77X8_MODE_CONTINUOUS_CONVERSION = 3
}Ad77x8Mode_t;


// ****************************************************************************
// PRIVATE MACROS: Private #define directives used by this module only
// ****************************************************************************
#define AD77X8_COMM_REG_WEN       ( uint32_t )( 1 << 7 )
#define AD77X8_COMM_REG_RW        ( uint32_t )( 1 << 6 )
#define AD77x8_COMM_REG_ADDR_MASK ( uint32_t )( 0xF )

#define AD77x8_STATUS_REG_RDY       (1 << 7)
#define AD77x8_STATUS_REG_CAL       (1 << 5)
#define AD77x8_STATUS_REG_ERR       (1 << 3)
#define AD77x8_STATUS_REG_LOCK      (1)

#define AD77X8_MODE_REG_CHOPN_POS  (7)
#define AD77X8_MODE_REG_NEGBUF_POS (6)
#define AD77X8_MODE_REG_REFSEL_POS (5)
#define AD77X8_MODE_REG_CHCON_POS  (4)
#define AD77X8_MODE_REG_OSCPD_POS  (3)
#define AD77X8_MODE_REG_MODE_POS   (0)
#define AD77X8_MODE_REG_MODE_MASK  (0x07)

#define AD77X8_ID_REG_CHIP7718 (0x40)
#define AD77X8_ID_REG_CHIP7708 (0x50)
#define AD77X8_ID_REG_CHIP_MASK (0xF0)

#define AD77X8_ADCCON_REG_CHANNEL_MASK (0xF)
#define AD77X8_ADCCON_REG_CHANNEL_POS  (4)
#define AD77X8_ADCCON_REG_UB_POS  (3)

#define AD77X8_ADCCON_REG_RANGE_MASK  (0x07)
#define AD77X8_ADCCON_REG_RANGE_POS   (0)

#define AD77X8_REF_VOLTAGE_MV          (2500)

// ****************************************************************************
// PRIVATE VARIABLES: Variables used by this module only (static)
// ****************************************************************************
static const uint8_t ad77x8RegisterSizeBits[AD77X8_REG_NUMBER] =
{
	8,	// COMMUNICATION AND STATUS REGISTER
	8,	// MODE
	8,  // ADC CONTROL
	8,  // FILTER
	24, // ADC DATA
	24, // ADC OFFSET
	24, // ADC GAIN
	8,  // IO CONTROL
	8,  // UNDEFINED
	8,  // UNDEFINED
	8,  // UNDEFINED
	8,  // UNDEFINED
	16, // TEST 1
	16, // TEST 2
	8,  // UNDEFINED
	8   // ID
};

static const float ad7718RangeLkupMv[] = 
{
	(1.024 * 20),   // AD77X8_RANGE_20_MV,
	(1.024 * 40),   // AD77X8_RANGE_40_MV,
	(1.024 * 80),   // AD77X8_RANGE_80_MV,
	(1.024 * 160),  // AD77X8_RANGE_160_MV,
	(1.024 * 320),  // AD77X8_RANGE_320_MV,
	(1.024 * 640),  // AD77X8_RANGE_640_MV,
	(1.024 * 1280), // AD77X8_RANGE_1280_MV,
	(1.024 * 2560)  // AD77X8_RANGE_2560_MV,
};

// ****************************************************************************
// PRIVATE FUNCTIONS PROTOTYPES: Functions used by this module only (static)
// ****************************************************************************
static uint32_t Ad77x8DriverReadRegister( Ad77x8DriverRegisterId_t registerId );
static void Ad77x8DriverWriteRegister( Ad77x8DriverRegisterId_t registerId, uint32_t value);
static void Ad77x8DriverWriteCommRegister(uint8_t value);
static uint8_t Ad77x8DriverCalculateFilterValue(uint8_t chopDisabled, uint16_t updateRateHz);
static void Ad77x8DriverSetMode( Ad77x8Mode_t mode);
static void Ad77x8DriverConfigureChannel( Ad77x8Channel_t channel, Ad77x8ChnlOpt_t const * channelOptions );

// ****************************************************************************
// PUBLIC VARIABLES: Public variables shared with other modules
// ****************************************************************************


// ****************************************************************************
// PRIVATE FUNCTIONS: Private functions used by this module only
// ****************************************************************************
static uint32_t Ad77x8DriverReadRegister( Ad77x8DriverRegisterId_t registerId )
{
	uint32_t retVal = 0;
	uint8_t commRegisterVal = 0;
	uint32_t regSizeBits;

	// Each operation is initiated by writing to the communication register
	
	// Write to the communication register to indicate read operation

	// Prepare the value for the communication register
	commRegisterVal = ( AD77X8_COMM_REG_RW | (registerId & AD77x8_COMM_REG_ADDR_MASK) ); 

	// Write the communication register
	Ad77x8DriverWriteCommRegister( commRegisterVal );
	
	// Registers has different size, get the size for the desired register
	regSizeBits = ad77x8RegisterSizeBits[registerId];
	
	// Read the register value in 8 bit chunks
	// Activate the chip select
	_Ad77x8DriverSpiChipSelectAssert();

	while( regSizeBits > 0 )
	{
		regSizeBits = regSizeBits - 8;
		
        // Write dummy byte - just in order to provide the clock
        _Ad77x8DriverSpiWriteByte(0);
		retVal |= ( (uint32_t)(_Ad77x8DriverSpiReadByte()) << regSizeBits);
	}

	_Ad77x8DriverSpiChipSelectDeAssert();
	
	return retVal;
}

static void Ad77x8DriverWriteRegister( Ad77x8DriverRegisterId_t registerId, uint32_t value)
{
	uint8_t byteToWrite;
	uint8_t commRegisterVal = 0;
	uint32_t regSizeBits;

	// Each operation is initiated by writing to the communication register

	// Write to the communication register to indicate write operation

	// Prepare the value for the communication register
	commRegisterVal = (registerId & AD77x8_COMM_REG_ADDR_MASK);

	// Write the communication register
	Ad77x8DriverWriteCommRegister( commRegisterVal );

	// Registers has different size, get the size for the desired register
	regSizeBits = ad77x8RegisterSizeBits[registerId];

	// Write the register value in 8 bit chunks
	// Activate the chip select
	_Ad77x8DriverSpiChipSelectAssert();

	while( regSizeBits > 0 )
	{
		regSizeBits = regSizeBits - 8;
		byteToWrite = (uint8_t)(value >> regSizeBits); 
	
	    _Ad77x8DriverSpiWriteByte(byteToWrite);
	}

	_Ad77x8DriverSpiChipSelectDeAssert();
}

static void Ad77x8DriverWriteCommRegister(uint8_t value)
{
	// Activate the chip select
	_Ad77x8DriverSpiChipSelectAssert();
	
	// Write the comm register value
	_Ad77x8DriverSpiWriteByte(value);

	// De-activate chip select
	_Ad77x8DriverSpiChipSelectDeAssert();
}

static uint8_t Ad77x8DriverCalculateFilterValue(uint8_t chopDisabled, uint16_t updateRateHz)
{
	uint8_t filterRegisterValue = 0;
	
	if(chopDisabled)
	{
		filterRegisterValue = 32768 / (updateRateHz * 8);
	}
	else
	{
		filterRegisterValue = 32768 / (updateRateHz * 8 * 3);
	}

	return filterRegisterValue;
}

static void Ad77x8DriverSetMode( Ad77x8Mode_t mode)
{
    uint8_t modeRegister = 0;

    // Read the entire mode register
    modeRegister = (uint8_t)Ad77x8DriverReadRegister(AD77X8_REG_MODE);
    // Clean the mode bits
    modeRegister = modeRegister & (~AD77X8_MODE_REG_MODE_MASK);
    // Set the desired mode
    modeRegister |= (mode & AD77X8_MODE_REG_MODE_MASK);
    // Write the modified mode register back
    Ad77x8DriverWriteRegister( AD77X8_REG_MODE, (uint32_t)modeRegister );
}

static void Ad77x8DriverConfigureChannel( Ad77x8Channel_t channel, Ad77x8ChnlOpt_t const * channelOptions )
{
    uint8_t controlRegister = 0;
    
    // Prepare the channel configuration in form of the value of the entire ADC control register
    controlRegister = ( channel & AD77X8_ADCCON_REG_CHANNEL_MASK ) << AD77X8_ADCCON_REG_CHANNEL_POS;
    controlRegister |= ( channelOptions->polarity ) << AD77X8_ADCCON_REG_UB_POS;
    controlRegister |= ( ( channelOptions->range ) & AD77X8_ADCCON_REG_RANGE_MASK) << AD77X8_ADCCON_REG_RANGE_POS;

    // Write ADC control register to the chip
    Ad77x8DriverWriteRegister( AD77X8_REG_ADC_CONTROL, controlRegister );
}

// ****************************************************************************
// PUBLIC FUNCTIONS: Public functions shared with other modules
// ****************************************************************************
uint8_t Ad77x8DriverInit( Ad77x8Opt_t const * options)
{
	uint8_t modeRegValue = 0;
	uint8_t idRegisterValue = 0;
    uint8_t success = 1;
	// Initialize SPI into the mode needed for AD77x8 part
	Ad77x8DriverInitSPIOnly();

    // Reset the SPI interface of the AD77x8
    _Ad77x8DriverSpiChipSelectAssert();
    _Ad77x8DriverSpiWriteByte(0xff);
    _Ad77x8DriverSpiWriteByte(0xff);
    _Ad77x8DriverSpiWriteByte(0xff);
    _Ad77x8DriverSpiWriteByte(0xff);
    _Ad77x8DriverSpiChipSelectDeAssert();

    
	idRegisterValue = (uint8_t)Ad77x8DriverReadRegister( AD77X8_REG_ID );
	idRegisterValue = (idRegisterValue & AD77X8_ID_REG_CHIP_MASK);

	if( AD77X8_ID_REG_CHIP7718 == idRegisterValue )
	{
        // ID is correct, let's initialized as required

        // MODE
        modeRegValue = (options->ChopingDisabled) << AD77X8_MODE_REG_CHOPN_POS;
        modeRegValue |= (options->CommonInputBuffered) << AD77X8_MODE_REG_NEGBUF_POS;
        modeRegValue |= (options->ReferenceSelection) << AD77X8_MODE_REG_REFSEL_POS;
        modeRegValue |= (options->ChannelConfiguration) << AD77X8_MODE_REG_CHCON_POS;
        modeRegValue |= (options->OscilatorPowerDownEnabled) << AD77X8_MODE_REG_OSCPD_POS;

        // Mode bits left at power-down mode
        
        // Perform own write to the mode register
        Ad77x8DriverWriteRegister(AD77X8_REG_MODE, modeRegValue);
	}
    else
    {
        // Chip ID is not OK, do not communicate anymore and inform the caller
        success = 0;
    }

    return success;
    
}

void Ad77x8DriverInitSPIOnly(void)
{
    // SPI mode 3 is needed
	// Clock inactive level HIGH
	// Shift clock edge FALLING
	// Capture clock edge RISING
	_Ad77x8DriverSpiInitMode3(3000000);
}

uint8_t Ad77x8DriverSetUpdateRate( uint16_t updateRateHz)
{
	uint8_t modeRegisterValue;
	uint8_t chopDisabled;
	uint8_t filterRegisterValue = 0;
	uint8_t rateValid = 0;
	
	modeRegisterValue = (uint8_t)Ad77x8DriverReadRegister( AD77X8_REG_MODE );
	chopDisabled = (modeRegisterValue >> AD77X8_MODE_REG_CHOPN_POS) & 0x1;
	
	// Calculate the value of the filter register based on chop mode and requested
	// update rate
	filterRegisterValue = Ad77x8DriverCalculateFilterValue( chopDisabled, updateRateHz );

	if(chopDisabled)
	{
		if( filterRegisterValue >= 3 )
		{
			rateValid = 1;
		}
	}
	else
	{
		if( filterRegisterValue >= 13 )
		{
			rateValid = 1;
		}
	}

	if( !rateValid )
	{
		// Requested rate is not valid, set the default rate to 100 Hz
		filterRegisterValue = Ad77x8DriverCalculateFilterValue( chopDisabled, 100 );
	}

	Ad77x8DriverWriteRegister( AD77X8_REG_FILTER, (uint32_t)filterRegisterValue  );

	return rateValid; 
}

void Ad77x8DriverStartContinousConversion( Ad77x8Channel_t channel, Ad77x8ChnlOpt_t const * channelOptions )
{
    // Activate continuous conversion
    Ad77x8DriverSetMode(AD77X8_MODE_CONTINUOUS_CONVERSION);
    // Set the active channel used for continuous conversion 
    Ad77x8DriverConfigureChannel(channel, channelOptions);
}

void Ad77x8DriverChangeContConversionChannel( Ad77x8Channel_t channel, Ad77x8ChnlOpt_t const * channelOptions )
{
    // Set the active channel used for continuous conversion
    Ad77x8DriverConfigureChannel(channel, channelOptions);
}

void Ad77x8DriverStartSingleConversion( Ad77x8Channel_t channel, Ad77x8ChnlOpt_t const * channelOptions )
{
    // Set the active channel used for single conversion
    Ad77x8DriverConfigureChannel(channel, channelOptions);
    // Start single conversion
    Ad77x8DriverSetMode(AD77X8_MODE_SINGLE_CONVERSION);
}

bool Ad77x8ReadRawConversionResult( uint32_t * rawResult, uint8_t * statusReg )
{
    bool dataReady;
    
    *statusReg = (uint8_t)Ad77x8DriverReadRegister( AD77X8_REG_STATUS_READ );

    if( (*statusReg) & AD77x8_STATUS_REG_RDY )
    {
        // conversion is finished, read the result
        *rawResult = Ad77x8DriverReadRegister( AD77X8_REG_ADC_DATA );
        dataReady = true;
    }
    else
    {
        *rawResult = 0;
        dataReady = false;
    }

    return dataReady;
}

bool Ad77x8ReadConversionResult( float * resultmV, uint8_t * statusReg )
{
    uint32_t adcData;
    uint32_t adcControl;
    uint8_t range;
    uint8_t polarity;
    bool dataReady;
    
    dataReady = Ad77x8ReadRawConversionResult( &adcData, statusReg );

    if( dataReady )
    {
        // Read the channel settings (uni/bi-polar, range)
        adcControl = Ad77x8DriverReadRegister( AD77X8_REG_ADC_CONTROL );
        range = (adcControl & AD77X8_ADCCON_REG_RANGE_MASK);
        polarity = (adcControl & AD77X8_ADCCON_REG_UB_POS);

        if( polarity )
        {
            // Unipolar
            *resultmV = ( ((float)adcData) / (1 < 24) ) * ad7718RangeLkupMv[range]; 
        }
        else
        {
            //Bipolar
            *resultmV = ( ( ( (float)adcData ) / (1 < 23) ) - 1 ) * ad7718RangeLkupMv[range];
        }
    }
    else
    {
        resultmV = 0;
    }

    return dataReady;
}
