/*
 * AD7794.c - routines to work with ADC AD7794 by SPI
 *
 * Copyright 2013 Edward V. Emelianoff <eddy@sao.ru>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301, USA.
 */

#include "main.h"
#include "spi.h"
#include "cdcacm.h"


volatile uint8_t data_error = 0;
uint8_t SPI_buffer[4]; // IO buffer: contains data to transmit & received data

#define check_err()  if(data_error){DBG("SPI error!\n"); data_error = 0; return 0;}
#define ERR_SPI() do{data_error = 1; return NULL;}while(0)

void SPI_buffer_clear(){
	uint8_t i;
	for(i = 0; i < 4; i++)
		SPI_buffer[i] = 0;
}

/**
 * Send len bytes from SPI buffer to register reg
 * @param reg - register to write + key READ_FROM_REG in case of read request
 * @param len - length of data to transmit/receive (without reg!): max 3!!!
 * @return received data
 */
uint8_t *send_SPI_data(uint8_t reg, uint8_t len){
	data_error = 0;
	if(len > 3) return NULL; // error: too big data array
	SPI_buffer[0] = reg;
	len++;
	if(!write_SPI(SPI_buffer, len))
		ERR_SPI();
	if(!read_SPI(SPI_buffer, len))
		ERR_SPI();
	return &SPI_buffer[1];
}

#undef ERR_SPI
#define ERR_SPI() do{data_error = 1; return 0;}while(0)
/**
 * Write data to 8bit register over SPI
 * @param reg  - register to write
 * @param data - data to write
 * @return readed data
 */
uint8_t sendByte(uint8_t reg, uint8_t data){
	SPI_buffer_clear();
	SPI_buffer[1] = data;
	if(!send_SPI_data(reg, 1))
		ERR_SPI();
	return SPI_buffer[1]; // return readed data
}
/**
 * Write data to 16bit register over SPI
 * @param reg  - register to write
 * @param data - data to write
 * @return readed data
 */
uint16_t sendWord(uint8_t reg, uint16_t data){
	uint16_t ret;
	SPI_buffer_clear();
	SPI_buffer[1] = (data >> 8 )& 0xff; // HSB first
	SPI_buffer[2] = data & 0xff;
	if(!send_SPI_data(reg, 2))
		ERR_SPI();
	ret = ((uint16_t)SPI_buffer[1]) << 8 | SPI_buffer[2];
	return ret; // return readed data
}
/**
 * reads 24 bit ADC data register
 * @return value of ADC || 1 (0 indicates error)
 */
uint32_t read_ADC_data(){
	uint32_t ret;
	SPI_buffer_clear();
	if(!send_SPI_data(DATA_REGISTER | READ_FROM_REG, 3))
		return 0;
	ret = SPI_buffer[1] << 16 | SPI_buffer[2] << 8 | SPI_buffer[3];
	if(!ret) ret = 1; // there could be a very little case that we get 0
	return ret;
}

/**
 * Checks ending of ADC and/or errors
 * @return 1 in case of data ready, set data_error to 1 in case of error
 */
uint8_t check_data_ready(){
	uint8_t ret = 0;
	//DBG("check_data_ready\n");
	uint8_t x = sendByte(STAT_REGISTER | READ_FROM_REG, 0);
	if(data_error){
		DBG("some data error\n");
		ret = 1; // data not ready, but error flag is set
	}else if((x & 0x18) != 0x08 || x & DATA_ERROR || x & STATUS_NOREF){ // test for AD7794 status register
		#ifdef EBUG
		if((x & 0x18) != 0x08) DBG("There's no AD7794 signature found\n");
		if(x & DATA_ERROR) DBG("DATA_ERROR\n");
		if(x & STATUS_NOREF) DBG("STATUS_NOREF\n");
		#endif
		if(x) data_error = x;
		else data_error = 1; // 0 -> no signature
		ret = 1;
	}else if(!(x & DATA_NOTRDY)) ret = 1;
	return ret;
}

#define read_AD7794_conf()		sendWord(CONF_REGISTER | READ_FROM_REG, 0)

static uint8_t ADC_gain = 1;
/**
 * Changes ADC gain coefficient
 * @param gain - log2(gain)
 * gain	voltage range (Vref+-)
 * 0	2.5 V
 * 1	1.25 V
 * 2	625 mV
 * 3	312.5 mV
 * 4	156.2 mV
 * 5	78.125 mV
 * 6	39.06 mV
 * 7	19.53 mV
 * @return 0 in case of wrong gain value
 */
uint8_t change_AD7794_gain(uint8_t gain){
	if(gain > 7) return 0;
	ADC_gain = gain;
	uint16_t Conf = read_AD7794_conf();
	check_err();
	Conf &= ~GAIN_MASK; // clear gain bits
	Conf |=  ADC_gain << 8; // update gain
	sendWord(CONF_REGISTER, Conf);
	check_err();
	return 1;
}

uint8_t AD7794_set_channel(uint8_t channel){
	uint16_t Conf = read_AD7794_conf();
	check_err();
	Conf &= ~CHANNEL_MASK; // clear channel number
	Conf |= (CHANNEL_MASK & channel); // set new channel
	sendWord(CONF_REGISTER, Conf);
	check_err();
	return 1;
}

/**
 * make internal full-scale calibration for given channel
 * !! first run setup_AD7794() and after - this function !!
 * @param channel - channel to activate
 * @return 0 on error
 */
uint8_t AD7794_calibration(uint8_t channel){
	if(ADC_gain == 7){
		DBG("gain = 7, can't calibrate!\n");
		return 0;
	}
	if(!AD7794_set_channel(channel)) return 0;
	sendWord(MODE_REGISTER,
				INT_FS_CAL | U16(0x0f)); // make a full-scale calibration on lowest speed
	check_err();
	return 1;
}

/**
 * Setup ADC: write keys to registers
 * @param config - keys to be written to configuration register
 * @param io     - keys to be written to io register
 * @return 0 on error
 */
uint8_t setup_AD7794(uint16_t config, uint8_t io){
	sendWord(MODE_REGISTER, POWERDOWN_MODE); // put ADC into powerdown mode
	sendWord(CONF_REGISTER,
				ADC_gain << 8 // gain
				| config);
	check_err();
	sendByte(IO_REGISTER, io);
	check_err();
	return 1;
}

// we modify this macro to zero steps counter in case of error
#define check_errR()  if(data_error){DBG("SPI error!\n"); data_error = 0; N = 0; return 0;}
/**
 * Start ADC transform & read data value
 * !! first run setup_AD7794(), AD7794_calibration() and after - this function !!
 * @param channel - channel number to transform
 * @return:
 * 		AD7794_NOTRDY when measurement in process
 * 		ADC data value if ready
 * 		0 in case of error
 */
uint32_t read_AD7794(uint8_t channel){
	static uint8_t N = 0; // operation number
	uint8_t dr;
	switch (N){
		case 0: // start: set channel
//P("A0 ", uart1_send);
			if(!AD7794_set_channel(channel)){
				return 0; // 0 in return means error
			}
		break;
		case 1: // put ADC to a single conversion mode
//P("A1 ", uart1_send);
			sendWord(MODE_REGISTER, SINGLE_MODE |
				U16(0x0f)); // the lowest speed;
			check_errR();
		break;
		case 2: // wait for data reading & check errors
//P("A2", uart1_send);
			dr = check_data_ready();
			check_errR();
			if(!dr) return AD7794_NOTRDY;
		break;
		default: // last step -> return readed data
//P("\n", uart1_send);
			N = 0;
			return read_ADC_data();
	}
	N++;
	return AD7794_NOTRDY;
}

/**
 * This function tries to reset ADC's SPI controller & check returned status
 * If
 * @param
 * @return !!! ADC_NO_ERROR in case of succsess!!!
 * 			Other error code in case of error (ADC_ERR_NO_DEVICE is critical error!)
 */
uint8_t reset_AD7794(){
	uint8_t i, ret = ADC_NO_ERROR;
	for(i = 0; i < 3; i++){
		sendByte(0xFF, 0xFF); // >= 32 bits of 1
		if(data_error) break;
	}
	if(data_error){
		DBG("SPI error: can't send reset sequence!\n");
		data_error = 0;
		return ADC_ERR_NO_DEVICE;
	}
	Delay(1); // give some time to "reboot"
	check_data_ready();
	if(data_error){
		DBG("SPI error: device can't do reset!\n");
		if((data_error & 0x18) != 0x08) // no signature
			ret = ADC_ERR_NO_DEVICE;
		else
			ret = ADC_ERR_OTHER;
		data_error = 0;
	}
	return ret;
}


