Example Program for EVAL-AD7606

Dependencies:   platform_drivers

app/iio_ad7606.c

Committer:
Kjansen45
Date:
2020-10-19
Revision:
5:42b1eeef29d9
Parent:
3:83b3133f544a
Child:
6:32de160dce43

File content as of revision 5:42b1eeef29d9:

/***************************************************************************//**
 *   @file    iio_ad7606.c
 *   @brief   Implementation of AD7606 IIO application interfaces
 *   @details This module acts as an interface for AD7606 IIO application
********************************************************************************
 * Copyright (c) 2020 Analog Devices, Inc.
 *
 * This software is proprietary to Analog Devices, Inc. and its licensors.
 * By using this software you agree to the terms of the associated
 * Analog Devices Software License Agreement.
*******************************************************************************/

/******************************************************************************/
/***************************** Include Files **********************************/
/******************************************************************************/

#include <inttypes.h>
#include <string.h>
#include <errno.h>
#include <math.h>

#include "app_config.h"

#include "tinyiiod.h"
#include "iio_ad7606.h"
#include "iio_ad7606_attr.h"
#include "iio_app.h"
#include "iio_transport.h"

#include "platform_support.h"
#include "spi_extra.h"
#include "gpio_extra.h"
#include "uart_extra.h"
#include "irq_extra.h"

#include "ad7606.h"
#include "ad7606_data_capture.h"
#include "ad7606_support.h"
#include "ad7606_user_config.h"

/******************************************************************************/
/************************ Macros/Constants ************************************/
/******************************************************************************/

/* ADC data to Voltage conversion scale factor for IIO client */
#define DEFAULT_SCALE		((DEFAULT_CHN_RANGE / ADC_MAX_COUNT_BIPOLAR) * 1000)

/* LSB Threshold to entry into open circuit detection as per datasheet */
#define MANUAL_OPEN_DETECT_ENTRY_TRHLD			350

/* Manual open circuit detect LSB threshold @50K Rpd as per datasheet */
#define MANUAL_OPEN_DETECT_THRESHOLD_RPD50K		20

/* Number of consecutive conversions (N) in manual open circuit detection */
#define MANUAL_OPEN_DETECT_CONV_CNTS			10

/* LSB Threshold b/w consecutive N conversions */
#define MANUAL_OPEN_DETECT_CONV_TRSHLD			10

/* Number of common mode conversions in manual open circuit detect */
#define MANUAL_OPEN_DETECT_CM_CNV_CNT			3

/* Max number of queue counts for auto mode open circuit detection */
#define AUTO_OPEN_DETECT_QUEUE_MAX_CNT			128
#define AUTO_OPEN_DETECT_QUEUE_EXTRA_CONV_CNT	15

/* Maximum ADC calibration gain value */
#define ADC_CALIBRATION_GAIN_MAX		64.0

#if defined(DEV_AD7606C_18)
#define	OFFSET_REG_RESOLUTION		4
#else
#define	OFFSET_REG_RESOLUTION		1
#endif

/* Default sampling frequency for AD7606 (in SPS) to define IIO client timeout period.
 * Note: The actual sampling frequncy is much higher (~30KSPS per channel), however the
 * data transmission back to IIO client is limited by the serial (UART) link, which
 * can provide max transmission rate of ~3-4KSPS. Hence sampling frequency is set to 1Khz
 * for safer side */
#define AD7606_DEFLT_SAMPLING_FREQEUNCY		(1000)

/******************************************************************************/
/*************************** Types Declarations *******************************/
/******************************************************************************/

/**
* IIO application instance descriptor.
*/
static struct iio_app_desc *iio_app_desc;

/**
 * Device name.
 */
static const char dev_name[] = ACTIVE_DEVICE_NAME;

/**
 * Pointer to the struct representing the AD7606 IIO device
 */
static struct ad7606_dev *p_ad7606_dev = NULL;


/* Device attributes with default values */

/* Power down mode values string representation (possible values specified in datasheet) */
static char *operating_mode_str[] = {
	"0  (Normal Mode)",
	"1  (Standby Mode)",
	"2  (Auto Standby Mode)",
	"3  (Shutdown Mode)"
};

/* Bandwidth values string */
static char *bandwidth_str[] = {
	"0  (Low)",
	"1  (High)"
};

/* Channel range values string representation (possible values specified in datasheet) */
static char *chn_range_str[] = {
#if defined(DEV_AD7606B)
	"0  (+/-2.5V SE)", "1  (+/-5.0V SE)", "2  (+/-10.0V SE)", "3  (+/-10.0V SE)",
	"4  (+/-10.0V SE)", "5  (+/-10.0V SE)", "6  (+/-10.0V SE)", "7  (+/-10.0V SE)",
	"8  (+/-10.0V SE)", "9  (+/-10.0V SE)", "10  (+/-10.0V SE)", "11  (+/-10.0V SE)",
#elif defined(DEV_AD7606C_18) || defined(DEV_AD7606C_16)
	"0  (+/-2.5V SE)", "1  (+/-5.0V SE)", "2  (+/-6.25V SE)", "3  (+/-10.0V SE)",
	"4  (+/-12.5V SE)", "5  (0 to 5V SE)", "6  (0 to 10V SE)", "7  (0 to 12.5V SE)",
	"8  (+/-5.0V Diff)", "9  (+/-10.0V Diff)", "10  (+/-12.5V Diff)", "11  (+/-20.0V Diff)"
#elif defined(DEV_AD7609)
	"0  (+/-10.0V SE)", "1  (+/-20.0V SE)"
#else
	"0  (+/-5.0V SE)", "1  (+/-10.0V SE)"
#endif
};

/* Oversampling values string representation (possible values specified in datasheet) */
static char *oversampling_val_str[] = {
	"0 (no oversampling)", "1 (oversampling by 2)", "2 (oversampling by 4)",
	"3 (oversampling by 8)", "4 (oversampling by 16)", "5 (oversampling by 32)",
	"6 (oversampling by 64)", "7 (oversampling by 128)", "8 (oversampling by 256)"
};


/* Channel range values string representation (possible values specified in datasheet) */
static float chn_range_val[] = {
#if defined(DEV_AD7606B)
	2.5, 5.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0
#elif defined(DEV_AD7606C_18) || defined(DEV_AD7606C_16)
	2.5, 5.0, 6.25, 10.0, 12.5, 5.0, 10.0, 12.5, 5.0, 10.0, 12.5, 20.0
#elif defined(DEV_AD7609)
	10.0, 20.0
#else
	5.0, 10.0
#endif
};

/* Range value per channel */
static float attr_chn_range[AD7606X_ADC_CHANNELS] = {
	DEFAULT_CHN_RANGE, DEFAULT_CHN_RANGE, DEFAULT_CHN_RANGE, DEFAULT_CHN_RANGE,
	DEFAULT_CHN_RANGE, DEFAULT_CHN_RANGE, DEFAULT_CHN_RANGE, DEFAULT_CHN_RANGE
};

/* Scale value per channel */
static float attr_scale_val[AD7606X_ADC_CHANNELS] = {
	DEFAULT_SCALE, DEFAULT_SCALE, DEFAULT_SCALE, DEFAULT_SCALE,
	DEFAULT_SCALE, DEFAULT_SCALE, DEFAULT_SCALE, DEFAULT_SCALE
};

/* Scale value per channel */
static polarity_e attr_polarity_val[AD7606X_ADC_CHANNELS] = {
	BIPOLAR, BIPOLAR, BIPOLAR, BIPOLAR,
	BIPOLAR, BIPOLAR, BIPOLAR, BIPOLAR
};

/* Channel range */
typedef enum {
	LOW,
	HIGH
} range_e;

/* Open detect auto mode QUEUE register count */
static uint8_t open_detect_queue_cnts[AD7606X_ADC_CHANNELS] = {
	0
};

/* ADC gain calibration Rfilter value (in Kohms) */
static uint8_t gain_calibration_reg_val[AD7606X_ADC_CHANNELS] = {
	0
};

/* Device register value */
static uint8_t device_reg_val;

/* GPIO LED descriptor and init structure */
static struct gpio_desc *gpio_led;

/* Flag to trigger new background conversion and capture when READBUFF command is issued */
static bool adc_background_data_capture_started = false;

/* Gain calibration status */
static bool gain_calibration_done = false;

/* Open circuit mode detection flags */
static bool open_circuit_detection_done = false;
static bool open_circuit_detection_error = false;
static bool open_circuit_detect_read_done = false;

/* Sampling frequency of device */
static uint16_t sampling_frequency = AD7606_DEFLT_SAMPLING_FREQEUNCY;

/******************************************************************************/
/************************ Functions Prototypes ********************************/
/******************************************************************************/

static float get_vltg_conv_scale_factor(float chn_range, polarity_e polarity);
static void save_local_attributes(void);

/******************************************************************************/
/************************ Functions Definitions *******************************/
/******************************************************************************/

/*!
 * @brief	Getter/Setter for the scale attribute value
 * @param	device- pointer to IIO device structure
 * @param	buf- pointer to buffer holding attribute value
 * @param	len- length of buffer string data
 * @param	channel- pointer to IIO channel structure
 * @return	Number of characters read/written
 */
ssize_t get_chn_scale(void *device,
		      char *buf,
		      size_t len,
		      const struct iio_ch_info *channel)
{
	return (ssize_t) sprintf(buf, "%f", attr_scale_val[channel->ch_num - 1]);
}

ssize_t set_chn_scale(void *device,
		      char *buf,
		      size_t len,
		      const struct iio_ch_info *channel)
{
	float scale;

	(void)sscanf(buf, "%f", &scale);

	if (scale > 0.0) {
		attr_scale_val[channel->ch_num - 1] = scale;
		return len;
	}

	return -EINVAL;
}


/*!
 * @brief	Getter/Setter for the sampling frequency attribute value
 * @param	device- pointer to IIO device structure
 * @param	buf- pointer to buffer holding attribute value
 * @param	len- length of buffer string data
 * @param	channel- pointer to IIO channel structure
 * @return	Number of characters read/written
 * @Note	This attribute is used to define the timeout period in IIO
 *			client during data capture.
 *			Timeout = (number of requested samples * (1/sampling frequency)) + 1sec
 *			e.g. if sampling frequency = 1KSPS and requested samples = 400
 *			Timeout = (400 * 0.001) + 1 = 1.4sec
 */
ssize_t get_sampling_frequency(void *device,
			       char *buf,
			       size_t len,
			       const struct iio_ch_info *channel)
{
	return (ssize_t) sprintf(buf, "%d", sampling_frequency);
}

ssize_t set_sampling_frequency(void *device,
			       char *buf,
			       size_t len,
			       const struct iio_ch_info *channel)
{
	/* NA- Can't set sampling frequency value */
	return len;
}


/*!
 * @brief	Getter/Setter for the raw attribute value
 * @param	device- pointer to IIO device structure
 * @param	buf- pointer to buffer holding attribute value
 * @param	len- length of buffer string data
 * @param	channel- pointer to IIO channel structure
 * @return	Number of characters read/written
 */
ssize_t get_chn_raw(void *device,
		    char *buf,
		    size_t len,
		    const struct iio_ch_info *channel)
{
	int32_t adc_data_raw;

	/* Capture the raw adc data */
	adc_data_raw = single_data_read(device, channel->ch_num - 1,
					attr_polarity_val[channel->ch_num - 1]);

	return (ssize_t) sprintf(buf, "%d", adc_data_raw);
}

ssize_t set_chn_raw(void *device,
		    char *buf,
		    size_t len,
		    const struct iio_ch_info *channel)
{
	/* NA- Can't set raw value */
	return len;
}


/*!
 * @brief	Getter/Setter for the operating mode attribute value
 * @param	device- pointer to IIO device structure
 * @param	buf- pointer to buffer holding attribute value
 * @param	len- length of buffer string data
 * @param	channel- pointer to IIO channel structure
 * @return	Number of characters read/written
 * @note	Available only for AD7606B and AD7606C
 */
ssize_t get_operating_mode(void *device,
			   char *buf,
			   size_t len,
			   const struct iio_ch_info *channel)
{
	uint8_t read_val;
	uint8_t operating_mode_value;

	if (ad7606_spi_reg_read(device, AD7606_REG_CONFIG, &read_val) == SUCCESS) {
		operating_mode_value = (read_val & AD7606_CONFIG_OPERATION_MODE_MSK);

		if (operating_mode_value < sizeof(operating_mode_str) / sizeof(
			    operating_mode_str[0])) {
			return (ssize_t)sprintf(buf, "%s", operating_mode_str[operating_mode_value]);
		}
	}

	return -EINVAL;
}

ssize_t set_operating_mode(void *device,
			   char *buf,
			   size_t len,
			   const struct iio_ch_info *channel)
{
	uint8_t operating_mode_value;

	(void)sscanf(buf, "%d", &operating_mode_value);

	if (operating_mode_value < sizeof(operating_mode_str) / sizeof(
		    operating_mode_str[0])) {
		if (ad7606_spi_write_mask(device,
					  AD7606_REG_CONFIG,
					  AD7606_CONFIG_OPERATION_MODE_MSK,
					  operating_mode_value) == SUCCESS) {
			return len;
		}
	}

	return -EINVAL;
}


/*!
 * @brief	Getter/Setter for the power down mode attribute value
 * @param	device- pointer to IIO device structure
 * @param	buf- pointer to buffer holding attribute value
 * @param	len- length of buffer string data
 * @param	channel- pointer to IIO channel structure
 * @return	Number of characters read/written
 * @note	Available for all devices except AD7606B and AD7606C
 */
ssize_t get_power_down_mode(void *device,
			    char *buf,
			    size_t len,
			    const struct iio_ch_info *channel)
{
	uint8_t gpio_stby_val;
	uint8_t gpio_range_val;

	if (gpio_get_value(((struct ad7606_dev *)device)->gpio_stby_n,
			   &gpio_stby_val) == SUCCESS) {
		if (gpio_get_value(((struct ad7606_dev *)device)->gpio_range,
				   &gpio_range_val) == SUCCESS) {

			if (gpio_stby_val) {
				return sprintf(buf, "%s", operating_mode_str[AD7606_NORMAL]);
			} else {
				if (gpio_range_val) {
					return sprintf(buf, "%s", operating_mode_str[AD7606_STANDBY]);
				} else {
					return sprintf(buf, "%s", operating_mode_str[AD7606_SHUTDOWN]);
				}
			}
		}
	}

	return -EINVAL;
}

ssize_t set_power_down_mode(void *device,
			    char *buf,
			    size_t len,
			    const struct iio_ch_info *channel)
{
	uint8_t power_down_mode_value;
	static enum ad7606_op_mode prev_power_down_mode = AD7606_NORMAL;
	struct ad7606_config dev_config;

	sscanf(buf, "%d", &power_down_mode_value);

	if (power_down_mode_value < (sizeof(operating_mode_str) / sizeof(
					     operating_mode_str[0]))) {

		dev_config.op_mode = power_down_mode_value;

		switch (power_down_mode_value) {
		case AD7606_NORMAL:
			if (ad7606_set_config(device, dev_config) == SUCCESS) {
				/* Reset the device if previous power down mode was either standby
				 * or shutdown */
				if (prev_power_down_mode != AD7606_NORMAL) {

					/* Power-up wait time */
					mdelay(1);

					/* Toggle reset pin */
					if (gpio_set_value(((struct ad7606_dev *)device)->gpio_reset,
							   GPIO_HIGH) == SUCCESS) {
						mdelay(1);

						if (gpio_set_value(((struct ad7606_dev *)device)->gpio_reset,
								   GPIO_LOW) == SUCCESS) {
							prev_power_down_mode = AD7606_NORMAL;
							return len;
						}
					}
				}
			}
			break;

		case AD7606_STANDBY:
			if (ad7606_set_config(device, dev_config) == SUCCESS) {
				prev_power_down_mode = AD7606_STANDBY;
				return len;
			}
			break;

		case AD7606_SHUTDOWN:
			if (ad7606_set_config(device, dev_config) == SUCCESS) {
				prev_power_down_mode = AD7606_SHUTDOWN;
				return len;
			}
			break;

		default:
			break;
		}
	}

	return -EINVAL;
}


/*!
 * @brief	Getter/Setter for the range attribute value
 * @param	device- pointer to IIO device structure
 * @param	buf- pointer to buffer holding attribute value
 * @param	len- length of buffer string data
 * @param	channel- pointer to IIO channel structure
 * @return	Number of characters read/written
 * @note	Available for all devices except AD7606B and AD7606C
 */
ssize_t get_range(void *device,
		  char *buf,
		  size_t len,
		  const struct iio_ch_info *channel)
{
	uint8_t gpio_range_val;
	struct ad7606_dev *dev = device;

	if (gpio_get_value(dev->gpio_range, &gpio_range_val) == SUCCESS) {
		if (gpio_range_val) {
			return sprintf(buf, "%s", chn_range_str[HIGH]);
		} else {
			return sprintf(buf, "%s", chn_range_str[LOW]);
		}
	}

	return -EINVAL;
}

ssize_t set_range(void *device,
		  char *buf,
		  size_t len,
		  const struct iio_ch_info *channel)
{
	uint8_t range_value;
	struct ad7606_dev *dev = device;

	(void)sscanf(buf, "%d", &range_value);

	if (range_value < (sizeof(chn_range_str) / sizeof(chn_range_str[0]))) {
		if (range_value == LOW) {
			if (gpio_set_value(dev->gpio_range, GPIO_LOW) == SUCCESS) {
				return len;
			}
		} else {
			if (gpio_set_value(dev->gpio_range, GPIO_HIGH) == SUCCESS) {
				return len;
			}
		}
	}

	return -EINVAL;
}


/*!
 * @brief	Getter/Setter for the oversampling attribute value
 * @param	device- pointer to IIO device structure
 * @param	buf- pointer to buffer holding attribute value
 * @param	len- length of buffer string data
 * @param	channel- pointer to IIO channel structure
 * @return	Number of characters read/written
 * @note	Available for all devices except AD7606B and AD7606C
 */
ssize_t get_oversampling(void *device,
			 char *buf,
			 size_t len,
			 const struct iio_ch_info *channel)
{
	uint8_t oversampling_value;
	uint8_t read_val;
	uint8_t gpio_osr0_val;
	uint8_t gpio_osr1_val;
	uint8_t gpio_osr2_val;
	struct ad7606_dev *dev = device;

#if defined(DEV_AD7606B) || defined(DEV_AD7606C_18) || defined(DEV_AD7606C_16)
	if (ad7606_spi_reg_read(device,
				AD7606_REG_OVERSAMPLING,
				&read_val) == SUCCESS) {
		oversampling_value = (read_val & AD7606_OVERSAMPLING_MSK);

		if (oversampling_value < sizeof(oversampling_val_str) / sizeof(
			    oversampling_val_str[0])) {
			return (ssize_t)sprintf(buf, "%s", oversampling_val_str[oversampling_value]);
		}
	}
#else
	if (gpio_get_value(dev->gpio_os0, &gpio_osr0_val) == SUCCESS) {
		if (gpio_get_value(dev->gpio_os1, &gpio_osr1_val) == SUCCESS) {
			if (gpio_get_value(dev->gpio_os2, &gpio_osr2_val) == SUCCESS) {
				oversampling_value = (gpio_osr2_val << 2) | (gpio_osr1_val << 1) |
						     gpio_osr0_val;

				if (oversampling_value < (sizeof(oversampling_val_str) / sizeof(
								  oversampling_val_str[0]))) {
					return sprintf(buf, "%s", oversampling_val_str[oversampling_value]);
				}
			}
		}
	}
#endif

	return -EINVAL;
}

ssize_t set_oversampling(void *device,
			 char *buf,
			 size_t len,
			 const struct iio_ch_info *channel)
{
	uint8_t oversampling_value;
	struct ad7606_oversampling oversampling_cfg;

	(void)sscanf(buf, "%d", &oversampling_value);

	if (oversampling_value < (sizeof(oversampling_val_str) / sizeof(
					  oversampling_val_str[0]))) {

		oversampling_cfg.os_pad = 0;
		oversampling_cfg.os_ratio = oversampling_value;

		ad7606_set_oversampling(device, oversampling_cfg);

		return len;
	}

	return -EINVAL;
}


/*!
 * @brief	Getter/Setter for the bandwidth attribute value
 * @param	device- pointer to IIO device structure
 * @param	buf- pointer to buffer holding attribute value
 * @param	len- length of buffer string data
 * @param	channel- pointer to IIO channel structure
 * @return	Number of characters read/written
 * @note	Available for only AD7606C
 */
ssize_t get_bandwidth(void *device,
		      char *buf,
		      size_t len,
		      const struct iio_ch_info *channel)
{
	uint8_t bw_value;
	uint8_t read_val;

	if (ad7606_spi_reg_read(device,
				AD7606_REG_BANDWIDTH,
				&read_val) == SUCCESS) {
		bw_value = (read_val >> (channel->ch_num - 1)) & 0x1;

		if (bw_value < sizeof(bandwidth_str) / sizeof(
			    bandwidth_str[0])) {
			return (ssize_t)sprintf(buf, "%s", bandwidth_str[bw_value]);
		}
	}

	return -EINVAL;
}

ssize_t set_bandwidth(void *device,
		      char *buf,
		      size_t len,
		      const struct iio_ch_info *channel)
{
	uint8_t bw_value;
	uint8_t read_val;

	(void)sscanf(buf, "%d", &bw_value);

	if (bw_value < sizeof(bandwidth_str) / sizeof(
		    bandwidth_str[0])) {
		if (ad7606_spi_reg_read(device,
					AD7606_REG_BANDWIDTH,
					&read_val) == SUCCESS) {
			if (bw_value) {
				read_val |= (1 << (channel->ch_num - 1));
			} else {
				read_val &= (~(1 << (channel->ch_num - 1)));
			}

			if (ad7606_spi_reg_write(device,
						 AD7606_REG_BANDWIDTH,
						 read_val) == SUCCESS) {
				return len;
			}
		}
	}

	return -EINVAL;
}


/*!
 * @brief	Getter/Setter for the channel range attribute value
 * @param	device- pointer to IIO device structure
 * @param	buf- pointer to buffer holding attribute value
 * @param	len- length of buffer string data
 * @param	channel- pointer to IIO channel structure
 * @return	Number of characters read/written
 * @note	Available only for AD7606B and AD7606C
 */
ssize_t get_chn_range(void *device,
		      char *buf,
		      size_t len,
		      const struct iio_ch_info *channel)
{
	uint8_t read_val;
	uint8_t chn_range;

	if (ad7606_spi_reg_read(device, AD7606_REG_RANGE_CH_ADDR(channel->ch_num-1),
				&read_val) == SUCCESS) {
		if (((channel->ch_num - 1) % 2) != 0) {
			read_val >>= CHANNEL_RANGE_MSK_OFFSET;
			chn_range = read_val;
		} else {
			chn_range = (read_val & AD7606_RANGE_CH_MSK(channel->ch_num - 1));
		}

		if (chn_range < sizeof(chn_range_str) / sizeof(chn_range_str[0])) {
			attr_chn_range[channel->ch_num - 1] = chn_range_val[chn_range];
			attr_polarity_val[channel->ch_num - 1] = ad7606_get_input_polarity(chn_range);

			return (ssize_t)sprintf(buf, "%s", chn_range_str[chn_range]);
		}
	}

	return -EINVAL;
}

ssize_t set_chn_range(void *device,
		      char *buf,
		      size_t len,
		      const struct iio_ch_info *channel)
{
	uint8_t chn_range;

	(void)sscanf(buf, "%d", &chn_range);

	if (chn_range < sizeof(chn_range_val) / sizeof(chn_range_val[0])) {

		/* Get the polarity of channel */
		attr_polarity_val[channel->ch_num - 1] = ad7606_get_input_polarity(chn_range);

		attr_chn_range[channel->ch_num - 1] = chn_range_val[chn_range];
		attr_scale_val[channel->ch_num - 1] = get_vltg_conv_scale_factor(
				chn_range_val[chn_range],
				attr_polarity_val[channel->ch_num - 1]);

		if (((channel->ch_num - 1) % 2) != 0) {
			chn_range <<= CHANNEL_RANGE_MSK_OFFSET;
		}

		if (ad7606_spi_write_mask(device,
					  AD7606_REG_RANGE_CH_ADDR(channel->ch_num-1),
					  AD7606_RANGE_CH_MSK(channel->ch_num-1),
					  chn_range) == SUCCESS) {
			return len;
		}
	}

	return -EINVAL;
}


/*!
 * @brief	Getter/Setter for the channel offset attribute value
 * @param	device- pointer to IIO device structure
 * @param	buf- pointer to buffer holding attribute value
 * @param	len- length of buffer string data
 * @param	channel- pointer to IIO channel structure
 * @return	Number of characters read/written
 * @note	Available only for AD7606B and AD7606C
 */
ssize_t get_chn_offset(void *device,
		       char *buf,
		       size_t len,
		       const struct iio_ch_info *channel)
{
	uint8_t chn_offset_value;

	if (ad7606_spi_reg_read(device, AD7606_REG_OFFSET_CH(channel->ch_num-1),
				&chn_offset_value) == SUCCESS) {
		return (ssize_t)sprintf(buf, "%d", chn_offset_value);
	}

	return -EINVAL;
}

ssize_t set_chn_offset(void *device,
		       char *buf,
		       size_t len,
		       const struct iio_ch_info *channel)
{
	uint8_t chn_offset_value = 0;

	(void)sscanf(buf, "%d", &chn_offset_value);

	if (ad7606_set_ch_offset(device, channel->ch_num - 1,
				 chn_offset_value) == SUCCESS) {
		return len;
	}

	return -EINVAL;
}


/*!
 * @brief	Getter/Setter for the channel pahse offset attribute value
 * @param	device- pointer to IIO device structure
 * @param	buf- pointer to buffer holding attribute value
 * @param	len- length of buffer string data
 * @param	channel- pointer to IIO channel structure
 * @return	Number of characters read/written
 * @note	Available only for AD7606B and AD7606C
 */
ssize_t get_chn_phase_offset(void *device,
			     char *buf,
			     size_t len,
			     const struct iio_ch_info *channel)
{
	uint8_t chn_phase_offset_value;

	if (ad7606_spi_reg_read(device,
				AD7606_REG_PHASE_CH(channel->ch_num - 1),
				&chn_phase_offset_value) == SUCCESS) {
		return (ssize_t)sprintf(buf, "%d", chn_phase_offset_value);
	}

	return -EINVAL;
}

ssize_t set_chn_phase_offset(void *device,
			     char *buf,
			     size_t len,
			     const struct iio_ch_info *channel)
{
	uint8_t chn_phase_offset_value = 0;

	(void)sscanf(buf, "%d", &chn_phase_offset_value);

	if (ad7606_set_ch_phase(device, channel->ch_num - 1,
				chn_phase_offset_value) == SUCCESS) {
		return len;
	}

	return -EINVAL;
}


/*!
 * @brief	Getter/Setter for the channel temperature attribute value
 * @param	device- pointer to IIO device structure
 * @param	buf- pointer to buffer holding attribute value
 * @param	len- length of buffer string data
 * @param	channel- pointer to IIO channel structure
 * @return	Number of characters read/written
 * @note	Available only for AD7606B and AD7606C
 */
ssize_t get_chn_temperature(void *device,
			    char *buf,
			    size_t len,
			    const struct iio_ch_info *channel)
{
	int32_t adc_chn_data = 0;
	float temperature;
	float voltage;

	/* Configure the channel multiplexer to select temperature read */
	if (ad7606_spi_write_mask(device,
				  AD7606_REG_DIAGNOSTIC_MUX_CH(channel->ch_num - 1),
				  AD7606_DIAGN_MUX_CH_MSK(channel->ch_num - 1),
				  AD7606_DIAGN_MUX_CH_VAL((channel->ch_num - 1),
						  TEMPERATURE_MUX)) == SUCCESS) {

		/* Allow to settle Mux channel */
		udelay(100);

		/* Sample the channel and read conversion result */
		adc_chn_data = single_data_read(device, channel->ch_num - 1,
						attr_polarity_val[channel->ch_num - 1]);

		/* Convert ADC data into equivalent voltage */
		voltage = convert_adc_raw_to_voltage(adc_chn_data,
						     attr_scale_val[channel->ch_num - 1]);

		/* Obtain the temperature using equation specified in device datasheet */
		temperature = ((voltage - 0.69068) / 0.019328) + 25.0;

		/* Change channel mux back to analog input */
		(void)ad7606_spi_write_mask(device,
					    AD7606_REG_DIAGNOSTIC_MUX_CH(channel->ch_num - 1),
					    AD7606_DIAGN_MUX_CH_MSK(channel->ch_num - 1),
					    AD7606_DIAGN_MUX_CH_VAL((channel->ch_num - 1),
							    ANALOG_INPUT_MUX));

		return (ssize_t)sprintf(buf, "%f", temperature);
	}

	return -EINVAL;
}

ssize_t set_chn_temperature(void *device,
			    char *buf,
			    size_t len,
			    const struct iio_ch_info *channel)
{
	// NA- Can't set temperature
	return -EINVAL;
}


/*!
 * @brief	Getter/Setter for the channel Vref attribute value
 * @param	device- pointer to IIO device structure
 * @param	buf- pointer to buffer holding attribute value
 * @param	len- length of buffer string data
 * @param	channel- pointer to IIO channel structure
 * @return	Number of characters read/written
 * @note	Available only for AD7606B and AD7606C
 */
ssize_t get_chn_vref(void *device,
		     char *buf,
		     size_t len,
		     const struct iio_ch_info *channel)
{
	float vref_voltge;
	int32_t adc_chn_data;

	/* Configure the channel multiplexer to select Vref read */
	if (ad7606_spi_write_mask(device,
				  AD7606_REG_DIAGNOSTIC_MUX_CH(channel->ch_num - 1),
				  AD7606_DIAGN_MUX_CH_MSK(channel->ch_num - 1),
				  AD7606_DIAGN_MUX_CH_VAL((channel->ch_num - 1),
						  VREF_MUX)) == SUCCESS) {

		/* Allow to settle Mux channel */
		udelay(100);

		/* Sample the channel and read conversion result */
		adc_chn_data = single_data_read(device, channel->ch_num - 1,
						attr_polarity_val[channel->ch_num - 1]);

		/* Convert ADC data into equivalent voltage */
		vref_voltge = convert_adc_raw_to_voltage(adc_chn_data,
				attr_scale_val[channel->ch_num - 1]);

		/* Divide by 4 since Vref Mux has 4x multiplier on it */
		vref_voltge /= VREF_MUX_MULTIPLIER;

		/* Change channel mux back to analog input */
		(void)ad7606_spi_write_mask(device,
					    AD7606_REG_DIAGNOSTIC_MUX_CH(channel->ch_num - 1),
					    AD7606_DIAGN_MUX_CH_MSK(channel->ch_num - 1),
					    AD7606_DIAGN_MUX_CH_VAL((channel->ch_num - 1),
							    ANALOG_INPUT_MUX));

		return (ssize_t)sprintf(buf, "%f", vref_voltge);
	}

	return -EINVAL;
}

ssize_t set_chn_vref(void *device,
		     char *buf,
		     size_t len,
		     const struct iio_ch_info *channel)
{
	// NA- Can't set Vref
	return - EINVAL;
}


/*!
 * @brief	Getter/Setter for the channel Vdrive attribute value
 * @param	device- pointer to IIO device structure
 * @param	buf- pointer to buffer holding attribute value
 * @param	len- length of buffer string data
 * @param	channel- pointer to IIO channel structure
 * @return	Number of characters read/written
 * @note	Available only for AD7606B and AD7606C
 */
ssize_t get_chn_vdrive(void *device,
		       char *buf,
		       size_t len,
		       const struct iio_ch_info *channel)
{
	float vdrive_voltge;
	int32_t adc_chn_data;

	/* Configure the channel multiplexer to select Vdrive read */
	if (ad7606_spi_write_mask(device,
				  AD7606_REG_DIAGNOSTIC_MUX_CH(channel->ch_num - 1),
				  AD7606_DIAGN_MUX_CH_MSK(channel->ch_num - 1),
				  AD7606_DIAGN_MUX_CH_VAL((channel->ch_num - 1),
						  VDRIVE_MUX)) == SUCCESS) {

		/* Allow to settle Mux channel */
		udelay(100);

		/* Sample the channel and read conversion result */
		adc_chn_data = single_data_read(device, channel->ch_num - 1,
						attr_polarity_val[channel->ch_num - 1]);

		/* Convert ADC data into equivalent voltage */
		vdrive_voltge = convert_adc_raw_to_voltage(adc_chn_data,
				attr_scale_val[channel->ch_num - 1]);

		/* Change channel mux back to analog input */
		(void)ad7606_spi_write_mask(device,
					    AD7606_REG_DIAGNOSTIC_MUX_CH(channel->ch_num - 1),
					    AD7606_DIAGN_MUX_CH_MSK(channel->ch_num - 1),
					    AD7606_DIAGN_MUX_CH_VAL((channel->ch_num - 1),
							    ANALOG_INPUT_MUX));

		return (ssize_t)sprintf(buf, "%f", vdrive_voltge);
	}

	return -EINVAL;
}

ssize_t set_chn_vdrive(void *device,
		       char *buf,
		       size_t len,
		       const struct iio_ch_info *channel)
{
	// NA- Can't set Vdrive
	return - EINVAL;
}


/*!
 * @brief	Getter/Setter for the channel ALDO attribute value
 * @param	device- pointer to IIO device structure
 * @param	buf- pointer to buffer holding attribute value
 * @param	len- length of buffer string data
 * @param	channel- pointer to IIO channel structure
 * @return	Number of characters read/written
 * @note	Available only for AD7606B and AD7606C
 */
ssize_t get_chn_aldo(void *device,
		     char *buf,
		     size_t len,
		     const struct iio_ch_info *channel)
{
	float aldo_voltge;
	int32_t adc_chn_data;

	/* Configure the channel multiplexer to select ALDO read */
	if (ad7606_spi_write_mask(device,
				  AD7606_REG_DIAGNOSTIC_MUX_CH(channel->ch_num - 1),
				  AD7606_DIAGN_MUX_CH_MSK(channel->ch_num - 1),
				  AD7606_DIAGN_MUX_CH_VAL((channel->ch_num - 1),
						  ALDO_MUX)) == SUCCESS) {

		/* Allow to settle Mux channel */
		udelay(100);

		/* Sample the channel and read conversion result */
		adc_chn_data = single_data_read(device, channel->ch_num - 1,
						attr_polarity_val[channel->ch_num - 1]);

		/* Convert ADC data into equivalent voltage */
		aldo_voltge = convert_adc_raw_to_voltage(adc_chn_data,
				attr_scale_val[channel->ch_num - 1]);

		/* Divide by 4 since ALDO Mux has 4x multiplier on it */
		aldo_voltge /= VREF_MUX_MULTIPLIER;

		/* Change channel mux back to analog input */
		(void)ad7606_spi_write_mask(device,
					    AD7606_REG_DIAGNOSTIC_MUX_CH(channel->ch_num - 1),
					    AD7606_DIAGN_MUX_CH_MSK(channel->ch_num - 1),
					    AD7606_DIAGN_MUX_CH_VAL((channel->ch_num - 1),
							    ANALOG_INPUT_MUX));

		return (ssize_t)sprintf(buf, "%f", aldo_voltge);
	}

	return -EINVAL;
}

ssize_t set_chn_aldo(void *device,
		     char *buf,
		     size_t len,
		     const struct iio_ch_info *channel)
{
	// NA- Can't set ALDO
	return - EINVAL;
}


/*!
 * @brief	Getter/Setter for the channel DLDO attribute value
 * @param	device- pointer to IIO device structure
 * @param	buf- pointer to buffer holding attribute value
 * @param	len- length of buffer string data
 * @param	channel- pointer to IIO channel structure
 * @return	Number of characters read/written
 * @note	Available only for AD7606B and AD7606C
 */
ssize_t get_chn_dldo(void *device,
		     char *buf,
		     size_t len,
		     const struct iio_ch_info *channel)
{
	float dldo_voltge;
	int32_t adc_chn_data;

	/* Configure the channel multiplexer to select DLDO read */
	if (ad7606_spi_write_mask(device,
				  AD7606_REG_DIAGNOSTIC_MUX_CH(channel->ch_num - 1),
				  AD7606_DIAGN_MUX_CH_MSK(channel->ch_num - 1),
				  AD7606_DIAGN_MUX_CH_VAL((channel->ch_num - 1),
						  DLDO_MUX)) == SUCCESS) {

		/* Allow to settle Mux channel */
		udelay(100);

		/* Sample the channel and read conversion result */
		adc_chn_data = single_data_read(device,
						channel->ch_num - 1,
						attr_polarity_val[channel->ch_num - 1]);

		/* Convert ADC data into equivalent voltage */
		dldo_voltge = convert_adc_raw_to_voltage(adc_chn_data,
				attr_scale_val[channel->ch_num - 1]);

		/* Divide by 4 since ALDO Mux has 4x multiplier on it */
		dldo_voltge /= VREF_MUX_MULTIPLIER;

		/* Change channel mux back to analog input */
		(void)ad7606_spi_write_mask(device,
					    AD7606_REG_DIAGNOSTIC_MUX_CH(channel->ch_num - 1),
					    AD7606_DIAGN_MUX_CH_MSK(channel->ch_num - 1),
					    AD7606_DIAGN_MUX_CH_VAL((channel->ch_num - 1),
							    ANALOG_INPUT_MUX));

		return (ssize_t)sprintf(buf, "%f", dldo_voltge);
	}

	return -EINVAL;
}

ssize_t set_chn_dldo(void *device,
		     char *buf,
		     size_t len,
		     const struct iio_ch_info *channel)
{
	// NA- Can't set DLDO
	return - EINVAL;
}


/*!
 * @brief	Getter/Setter for the channel open circuit detect manual attribute value
 * @param	device- pointer to IIO device structure
 * @param	buf- pointer to buffer holding attribute value
 * @param	len- length of buffer string data
 * @param	channel- pointer to IIO channel structure
 * @return	Number of characters read/written
 */
ssize_t get_chn_open_circuit_detect_manual(void *device,
		char *buf,
		size_t len,
		const struct iio_ch_info *channel)
{
	int32_t prev_adc_code, curr_adc_code;
	bool open_detect_flag = false;
	bool open_detect_done = false;
	uint8_t cnt;

	/* Enter into manual open circuit detection mode */
	do {
		if (ad7606_spi_reg_write(device, AD7606_REG_OPEN_DETECT_QUEUE, 1) == SUCCESS) {
			/* Read the ADC on selected chnnel (first reading post open circuit detection start) */
			prev_adc_code = single_data_read(device,
							 channel->ch_num - 1,
							 attr_polarity_val[channel->ch_num - 1]);

			/* Perform N conversions and monitor the code delta */
			for (cnt = 0; cnt < MANUAL_OPEN_DETECT_CONV_CNTS; cnt++) {
				/* Check if code is within 350LSB (nearest ZS code) */
				if (prev_adc_code >= 0 && prev_adc_code < MANUAL_OPEN_DETECT_ENTRY_TRHLD) {
					/* Perform next conversion and read the result */
					curr_adc_code = single_data_read(device,
									 channel->ch_num - 1,
									 attr_polarity_val[channel->ch_num - 1]);

					/* Check if delta b/w current and previus reading is within 10 LSB code */
					if (abs(curr_adc_code - prev_adc_code) > MANUAL_OPEN_DETECT_CONV_TRSHLD) {
						open_detect_done = true;
						break;
					}

					/* Get the previous code */
					prev_adc_code = curr_adc_code;
				} else {
					open_detect_done = true;
					break;
				}
			}

			/* Break if open circuit detection aborted (in case above conditions not met) */
			if (open_detect_done)
				break;

			/* Set common mode high (enabling open circuit detect on selected channel) */
			if (ad7606_spi_reg_write(device,
						 AD7606_REG_OPEN_DETECT_ENABLE,
						 (1 << ((channel->ch_num) - 1))) == SUCCESS) {

				/* Perform next conversions (~2-3) and read the result (with common mode set high) */
				for (cnt = 0; cnt < MANUAL_OPEN_DETECT_CM_CNV_CNT; cnt++) {
					udelay(100);
					curr_adc_code = single_data_read(device,
									 channel->ch_num - 1,
									 attr_polarity_val[channel->ch_num - 1]);
				}

				/* Check if delta b/w common mode high code and previous N conversion code is > threshold */
				if ((curr_adc_code - prev_adc_code) < MANUAL_OPEN_DETECT_THRESHOLD_RPD50K) {
					open_detect_done = true;
					break;
				}
			} else {
				return -EINVAL;
			}

			/* Break if open circuit detection aborted (in case above conditions not met) */
			if (open_detect_done)
				break;

			/* Set common mode low (disabling open circuit detect on channel) */
			if (ad7606_spi_reg_write(device,
						 AD7606_REG_OPEN_DETECT_ENABLE,
						 0) == SUCCESS) {
				/* Perform next conversion and read the result (with common mode set low) */
				curr_adc_code = single_data_read(device,
								 channel->ch_num - 1,
								 attr_polarity_val[channel->ch_num - 1]);

				/* Check if delta b/w common mode low code and previous N conversion code is < threshold */
				if (abs(curr_adc_code - prev_adc_code) < MANUAL_OPEN_DETECT_THRESHOLD_RPD50K) {
					open_detect_flag = true;
					open_detect_done = true;
				}
			} else {
				return -EINVAL;
			}
		} else {
			return -EINVAL;
		}
	} while (0);

	/* Disable open detect mode */
	(void)ad7606_spi_reg_write(device, AD7606_REG_OPEN_DETECT_QUEUE, 0);

	if (open_detect_done) {
		if (open_detect_flag) {
			strcpy(buf, "Open Circuit Detected");
		} else {
			strcpy(buf, "Open Circuit Not Detected");
		}

		return len;
	}

	return -EINVAL;
}

ssize_t set_chn_open_circuit_detect_manual(void *device,
		char *buf,
		size_t len,
		const struct iio_ch_info *channel)
{
	// NA- Can't set open circuit detect
	return - EINVAL;
}


/*!
 * @brief	Getter/Setter for the channel open circuit detect auto attribute value
 * @param	device- pointer to IIO device structure
 * @param	buf- pointer to buffer holding attribute value
 * @param	len- length of buffer string data
 * @param	channel- pointer to IIO channel structure
 * @return	Number of characters read/written
 */
ssize_t get_chn_open_circuit_detect_auto(void *device,
		char *buf,
		size_t len,
		const struct iio_ch_info *channel)
{
	if (open_circuit_detect_read_done) {
		open_circuit_detect_read_done = false;

		if (open_circuit_detection_error) {
			strcpy(buf, "Error!!");
		}

		if (open_circuit_detection_done) {
			strcpy(buf, "Open Circuit Detected");
		} else {
			strcpy(buf, "Open Circuit Not Detected");
		}

		return len;
	}

	return (ssize_t)sprintf(buf, "OPEN_DETECT_QUEUE: %d",
				open_detect_queue_cnts[channel->ch_num - 1]);
}

ssize_t set_chn_open_circuit_detect_auto(void *device,
		char *buf,
		size_t len,
		const struct iio_ch_info *channel)
{
	uint8_t data;
	uint8_t open_detect_flag = false;
	int32_t rw_status = FAILURE;
	uint16_t conv_cnts;

	(void)sscanf(buf, "%d", &data);
	open_circuit_detection_error = false;

	if ((data > 1 && data <= AUTO_OPEN_DETECT_QUEUE_MAX_CNT) && (buf[0] >= '0'
			&& buf[0] <= '9')) {
		open_detect_queue_cnts[channel->ch_num - 1] = data;

		/* Enter into open circuit auto open detect mode */
		if (ad7606_spi_reg_write(device,
					 AD7606_REG_OPEN_DETECT_QUEUE,
					 open_detect_queue_cnts[channel->ch_num - 1]) == SUCCESS) {
			/* Enable open circuit detection on selected channel */
			if (ad7606_spi_reg_write(device,
						 AD7606_REG_OPEN_DETECT_ENABLE,
						 (1 << ((channel->ch_num) - 1))) == SUCCESS) {
				/* Monitor the open detect flag for max N+15 (open detect queue count) conversions.
				 * Note: In ideal scenario, the open detect flash should be monitored continuously while
				 * background N conversions are in progress */
				for (conv_cnts = 0;
				     conv_cnts < (open_detect_queue_cnts[channel->ch_num - 1] +
						  AUTO_OPEN_DETECT_QUEUE_EXTRA_CONV_CNT);
				     conv_cnts++) {
					if (ad7606_convst(device) == SUCCESS) {
						udelay(100);

						/* Monitor the open detect flag */
						if (ad7606_spi_reg_read(device,
									AD7606_REG_OPEN_DETECTED,
									&open_detect_flag) == SUCCESS) {
							open_detect_flag >>= (channel->ch_num - 1);
							open_detect_flag &= 0x1;

							rw_status = SUCCESS;
							if (open_detect_flag) {
								break;
							}
						} else {
							rw_status = FAILURE;
							break;
						}
					} else {
						rw_status = FAILURE;
						break;
					}
				}
			}
		}

		/* Disable open detect mode and clear open detect flag */
		(void)ad7606_spi_reg_write(device, AD7606_REG_OPEN_DETECT_QUEUE, 0);
		(void)ad7606_spi_reg_write(device, AD7606_REG_OPEN_DETECTED, 0xFF);

		open_detect_queue_cnts[channel->ch_num - 1] = 0;

		if (rw_status == SUCCESS) {
			if (open_detect_flag) {
				open_circuit_detection_done = true;
			} else {
				open_circuit_detection_done = false;
			}

			open_circuit_detect_read_done = true;
			return len;
		}
	}

	open_circuit_detection_error = true;
	return -EINVAL;
}


/*!
 * @brief	Getter/Setter for the adc offset calibration
 * @param	device- pointer to IIO device structure
 * @param	buf- pointer to buffer holding attribute value
 * @param	len- length of buffer string data
 * @param	channel- pointer to IIO channel structure
 * @return	Number of characters read/written
 */
ssize_t get_chn_calibrate_adc_offset(void *device,
				     char *buf,
				     size_t len,
				     const struct iio_ch_info *channel)
{
	float lsb_voltage;
	float adc_voltage;
	polarity_e polarity = attr_polarity_val[channel->ch_num - 1];
	int32_t adc_data;
	int8_t chn_offset;

	/* Perform the system offset calibration */

	if (polarity == UNIPOLAR) {
		lsb_voltage = attr_chn_range[channel->ch_num - 1] / ADC_MAX_COUNT_UNIPOLAR;
	} else {
		lsb_voltage = attr_chn_range[channel->ch_num - 1] / ADC_MAX_COUNT_BIPOLAR;
	}

	/* Sample and read the ADC channel */
	adc_data = single_data_read(device, channel->ch_num - 1,
				    polarity);

	/* Get an equivalent ADC voltage */
	adc_voltage = convert_adc_raw_to_voltage(adc_data,
			attr_scale_val[channel->ch_num - 1]);

	/* Calculate the channel offset and write it to offset register */
	chn_offset = -(adc_voltage / lsb_voltage / OFFSET_REG_RESOLUTION);

	if (ad7606_set_ch_offset(device, channel->ch_num - 1,
				 chn_offset) == SUCCESS) {
		return sprintf(buf, "%s", "ADC Calibration Done");
	}

	return -EINVAL;
}

ssize_t set_chn_calibrate_adc_offset(void *device,
				     char *buf,
				     size_t len,
				     const struct iio_ch_info *channel)
{
	// NA- Can't set open circuit detect
	return - EINVAL;
}


/*!
 * @brief	Getter/Setter for the adc gain calibration
 * @param	device- pointer to IIO device structure
 * @param	buf- pointer to buffer holding attribute value
 * @param	len- length of buffer string data
 * @param	channel- pointer to IIO channel structure
 * @return	Number of characters read/written
 */
ssize_t get_chn_calibrate_adc_gain(void *device,
				   char *buf,
				   size_t len,
				   const struct iio_ch_info *channel)
{
	uint8_t read_val;

	if (gain_calibration_done) {
		/* Get calibration status for previous gain value write event */
		gain_calibration_done = false;
		return sprintf(buf, "Calibration Done (Rfilter=%d K)",
			       gain_calibration_reg_val[channel->ch_num - 1]);
	}

	/* Return gain value when normal read event is triggered */
	if (ad7606_spi_reg_read(device,
				AD7606_REG_GAIN_CH(channel->ch_num - 1),
				&read_val) == SUCCESS) {
		gain_calibration_reg_val[channel->ch_num - 1] = (read_val & AD7606_GAIN_MSK);
		return sprintf(buf, "Rfilter= %d K",
			       gain_calibration_reg_val[channel->ch_num - 1]);
	}

	return -EINVAL;
}

ssize_t set_chn_calibrate_adc_gain(void *device,
				   char *buf,
				   size_t len,
				   const struct iio_ch_info *channel)
{
	float data;

	if (buf[0] >= '0' && buf[0] <= '9') {
		(void)sscanf(buf, "%f", &data);

		if (data >= 0 && data < ADC_CALIBRATION_GAIN_MAX) {
			/* Get the nearest value of unsigned integer */
			gain_calibration_reg_val[channel->ch_num - 1] = (uint8_t)(round(data));

			/* Perform the gain calibration by writing gain value into gain register */
			if (ad7606_set_ch_gain(device,
					       channel->ch_num - 1,
					       gain_calibration_reg_val[channel->ch_num - 1]) == SUCCESS) {
				gain_calibration_done = true;
				return len;
			}
		}
	}

	return -EINVAL;
}


/*!
 * @brief	Getter/Setter for the direct register access attribute value
 * @param	device- pointer to IIO device structure
 * @param	buf- pointer to buffer holding attribute value
 * @param	len- length of buffer string data
 * @param	channel- pointer to IIO channel structure
 * @return	Number of characters read/written
 */
ssize_t get_direct_reg_access(void *device,
			      char *buf,
			      size_t len,
			      const struct iio_ch_info *channel)
{
	return (ssize_t)sprintf(buf, "%d", device_reg_val);
}

ssize_t set_direct_reg_access(void *device,
			      char *buf,
			      size_t len,
			      const struct iio_ch_info *channel)
{
	uint8_t reg_address;
	char *token;
	uint8_t offset = strlen("0x");
	char str[10] = "";
	uint8_t i=0;
	uint8_t reg_data;

	if (buf[1] == 'x') {
		/* Write a data to device */

		/* Extract the register address from received string */
		strcpy(str, buf);
		token = strtok(str, " ");
		(void)sscanf(token + offset, "%x", &reg_address);

		/* Extract the register data from received string */
		i = (strlen(str) + 1) + offset;
		(void)sscanf(str + i, "%x", &reg_data);

		if (reg_address <= NUM_OF_REGISTERS) {
			if ((ad7606_spi_reg_write(device, reg_address, reg_data) == SUCCESS)) {
				save_local_attributes();
				return len;
			}
		}
	} else {
		/* Read the data from device */
		(void)sscanf(buf, "%d", &reg_address);
		if ((ad7606_spi_reg_read(device, reg_address, &device_reg_val) == SUCCESS)) {
			return len;
		}
	}

	return -EINVAL;
}


/**
 * @brief Get xml corresponding to an AD7606 device.
 * @param xml - Xml containing description of a device.
 * @param iio_dev - Structure describing a device, channels and attributes.
 * @return SUCCESS in case of success or negative value otherwise.
 */
static ssize_t iio_ad7606_get_xml(char **xml, struct iio_device *iio_dev)
{
	*xml = (char*)calloc(1, strlen(ad7606_phy_xml) + 1);
	if (!(*xml)) {
		return -ENOMEM;
	}

	memcpy(*xml, ad7606_phy_xml, strlen(ad7606_phy_xml));

	return SUCCESS;
}


/**
 * @brief	Read buffer data corresponding to AD7606 IIO device
 * @param	dev_instance[in] - IIO device instance
 * @param	pbuf[out] - Pointer to output data buffer
 * @return	SUCCESS in case of success or negative value otherwise
 */
static ssize_t iio_ad7606_read_data(void *dev_instance,
				    char *pbuf,
				    size_t offset,
				    size_t bytes_count,
				    uint32_t ch_mask)
{
	if (adc_background_data_capture_started == false) {
		start_background_data_capture(ch_mask, bytes_count);
		adc_background_data_capture_started = true;
	}

	/* Read the buffered data */
	return (ssize_t)buffered_data_read(pbuf, bytes_count, offset, ch_mask);
}


/**
 * @brief	Read requested samples count
 * @param	dev_instance[in] - IIO device instance
 * @param	pbuf[out] - Pointer to output data buffer
 * @return	SUCCESS in case of success or negative value otherwise
 */
static ssize_t iio_ad7606_get_samples_count(void *dev_instance,
		size_t bytes_count,
		uint32_t ch_mask)
{
	store_requested_samples_count(bytes_count);
	return SUCCESS;
}


/**
 * @brief	Create structure describing a device, channels and attributes
 * @param	device_name[in] - Device name
 * @return	iio_device or NULL, in case of failure
 */
static struct iio_device *iio_ad7606_create_device(const char *device_name)
{
	struct iio_device *iio_ad7606_device;

	iio_ad7606_device = calloc(1, sizeof(struct iio_device));
	if (!iio_ad7606_device) {
		return NULL;
	}

	iio_ad7606_device->name = device_name;
	iio_ad7606_device->num_ch = sizeof(iio_ad7606_channels) / sizeof(
					    iio_ad7606_channels[0]);
	iio_ad7606_device->channels = iio_ad7606_channels;
	iio_ad7606_device->attributes = global_attributes;

	return iio_ad7606_device;
}


/**
 * @brief	Delete IIO device.
 * @param	iio_device - Structure describing a device, channels and attributes
 * @return	SUCCESS in case of success or negative value otherwise
 */
static ssize_t iio_ad7606_delete_device(struct iio_device *iio_device)
{
	if (!iio_device) {
		return FAILURE;
	}

	free(iio_device);

	return SUCCESS;
}


/**
 * @brief	Init for reading/writing and parameterization of a
 * 			ad7606 IIO device
 * @param 	desc[in,out] - IIO device descriptor
 * @param	init[in] - Configuration structure
 * @return	SUCCESS in case of success, FAILURE otherwise
 */
int32_t iio_ad7606_init(struct iio_ad7606_desc **desc,
			struct iio_ad7606_init_param *init)
{
	int32_t status;
	struct iio_interface *iio_interface;

	iio_interface = (struct iio_interface *)calloc(1, sizeof(struct iio_interface));
	if (!iio_interface) {
		return -ENOMEM;
	}

	*iio_interface = (struct iio_interface) {
		.name = dev_name,
		.dev_instance = init->ad7606_phy,
		.iio = iio_ad7606_create_device(dev_name),
		.get_xml = iio_ad7606_get_xml,
		.transfer_dev_to_mem = iio_ad7606_get_samples_count,
		.transfer_mem_to_dev = NULL,
		.read_data = iio_ad7606_read_data,
		.write_data = NULL,
	};

	status = iio_register(iio_interface);
	if (status < 0) {
		free(iio_interface);
		return FAILURE;
	}

	*desc = calloc(1, sizeof(struct iio_ad7606_desc));
	if (!(*desc)) {
		iio_unregister(iio_interface);
		free(iio_interface);
		return FAILURE;
	}

	(*desc)->iio_interface = iio_interface;

	return SUCCESS;
}


/**
 * @brief Release resources allocated for IIO device
 * @param desc[in] - IIO device descriptor
 * @return SUCCESS in case of success, FAILURE otherwise
 */
int32_t iio_ad7606_remove(struct iio_ad7606_desc *desc)
{
	int32_t status;

	if (!desc) {
		return FAILURE;
	}

	status = iio_unregister(desc->iio_interface);
	if (status < 0) {
		return FAILURE;
	}

	status = iio_ad7606_delete_device(desc->iio_interface->iio);
	if (status < 0) {
		return FAILURE;
	}

	free(desc->iio_interface);
	free(desc);

	return SUCCESS;
}


/*!
 * @brief	Get scale factor for adc data to voltage conversion for IIO client
 * @param	chn_range[in] - Current channel voltage range
 * @param	chn_range_bits[in] - Channel range register bits
 * @return	scale
 * @details	This function samples and capture the new data when previous data
 * 			is transmitted to IIO client
 */
static float get_vltg_conv_scale_factor(float chn_range, polarity_e polarity)
{
	float scale;

	/* Get the scale factor for voltage conversion from range */
	if (polarity == UNIPOLAR) {
		scale = (chn_range / ADC_MAX_COUNT_UNIPOLAR) * 1000;
	} else {
		scale = (chn_range / ADC_MAX_COUNT_BIPOLAR) * 1000;
	}

	return scale;
}


/**
 * @brief 	Save local variables
 * @return	none
 * @details	This function saves the local parameters with updated device values
 */
static void save_local_attributes(void)
{
	char buf[50];
	struct iio_ch_info channel;

	for (uint8_t chn = 1; chn <= AD7606X_ADC_CHANNELS; chn++) {
		channel.ch_num = chn;

		/* Get channel range */
		(void)get_chn_range(p_ad7606_dev, buf, 0, &channel);

		/* Get scale */
		attr_scale_val[channel.ch_num - 1] = get_vltg_conv_scale_factor(
				attr_chn_range[channel.ch_num - 1],
				attr_polarity_val[channel.ch_num - 1]);
	}
}


/**
 * @brief 	Initialize the IRQ contoller
 * @param	uart_desc[in] - UART descriptor for UART Rx IRQ event init
 * @return	none
 * @details	This function initialize the interrupt controller
 */
static int32_t iio_interrupt_handler_init(mbed_uart_desc *uart_desc)
{
	/* Pin to generate external interrupt */
	PinName ext_int_pin = BUSY_PIN;

	/* External interrupt descriptor */
	struct irq_ctrl_desc *external_int_desc;

	/* Define external interrupt platform specific parameters structure */
	mbed_irq_init_param mbed_ext_int_init_param = {
		.int_mode = EXT_IRQ_FALL,
		.int_obj_type = &ext_int_pin
	};

	/* External interrupt init parameters */
	struct irq_init_param external_int_init_param = {
		.irq_ctrl_id = EXTERNAL_INT_ID,
		.extra = &mbed_ext_int_init_param
	};

	/* External interrupt callback descriptor */
	struct callback_desc external_int_callback_desc = {
		&do_conversion_callback,
		NULL,
		NULL
	};

	/* UART Rx interrupt descriptor */
	struct irq_ctrl_desc *uart_rx_int_desc;

	/* Define external interrupt platform specific parameters structure */
	mbed_irq_init_param mbed_uart_rx_int_init_param = {
		.int_mode = 0,
		.int_obj_type = uart_desc->uart_port
	};

	/* UART Rx interrupt init parameters */
	struct irq_init_param uart_rx_int_init_param = {
		.irq_ctrl_id = UART_RX_INT_ID,
		.extra = &mbed_uart_rx_int_init_param
	};

	/* UART Rx interrupt callback descriptor */
	struct callback_desc uart_rx_int_callback_desc = {
		&iio_uart_rx_callback,
		NULL,
		NULL
	};

	/* Init interrupt controller for external interrupt */
	if (irq_ctrl_init(&external_int_desc, &external_int_init_param) == FAILURE) {
		return FAILURE;
	}

	/* Init interrupt controller for UART Rx interrupt */
	if (irq_ctrl_init(&uart_rx_int_desc, &uart_rx_int_init_param) == FAILURE) {
		return FAILURE;
	}

	/* Register a callback function for external interrupt */
	if (irq_register_callback(external_int_desc, EXTERNAL_INT_ID,
				  &external_int_callback_desc) == FAILURE) {
		return FAILURE;
	}

	/* Register a callback function for UART Rx interrupt */
	if (irq_register_callback(uart_rx_int_desc, UART_RX_INT_ID,
				  &uart_rx_int_callback_desc) == FAILURE) {
		return FAILURE;
	}

	return SUCCESS;
}


/**
 * @brief	Initialize the IIO interface for AD7606 IIO device
 * @return	none
 * @return	SUCCESS in case of success, FAILURE otherwise
 */
int32_t ad7606_iio_initialize(void)
{
	int32_t init_status;

	/**
	* AD7606 IIO instance descriptor
	*/
	struct iio_ad7606_desc *piio_ad7606_desc;

	/**
	 * AD7606 IIO init parameters
	 */
	struct iio_ad7606_init_param iio_ad7606_init_param;

	/**
	 * IIO application init parameters
	 */
	struct iio_app_init_param iio_app_init_par;

	/**
	 * UART serial interface read/write callback
	 */
	struct iio_server_ops uart_iio_server_ops;

	/*
	 * UART Mbed init extra parameters structure
	 */
	mbed_uart_init_param uart_extra_init_param;

	/*
	 * UART init parameters structure
	 */
	struct uart_init_param uart_init_param;

	/**
	 * IIO application UART descriptor
	 */
	struct uart_desc *uart_desc = NULL;

	/*
	 * GPIO LED Init parameters structure
	 */
	struct gpio_init_param gpio_init_led;

	iio_ad7606_init_param = (struct iio_ad7606_init_param) {
		.ad7606_phy = NULL,
	};

	uart_extra_init_param = (mbed_uart_init_param) {
		.uart_tx_pin = UART_TX,
		.uart_rx_pin = UART_RX
	};

	uart_init_param = (struct uart_init_param ) {
		.device_id = NULL,
		.baud_rate = IIO_UART_BAUD_RATE,
		.extra = &uart_extra_init_param
	};

	uart_iio_server_ops = (struct iio_server_ops) {
		.read = iio_uart_read,
		.write = iio_uart_write,
	};

	gpio_init_led = (struct gpio_init_param) {
		.number = LED_GREEN,
		.extra = NULL
	};

	iio_app_init_par = (struct iio_app_init_param) {
		.iio_server_ops = &uart_iio_server_ops,
	};

	/* Initialize AD7606 device and peripheral interface */
	init_status = ad7606_init(&p_ad7606_dev, &ad7606_init_str);
	if (init_status != SUCCESS) {
		return init_status;
	}

	/* Initialize the LED GPIO descriptor */
	init_status = gpio_get_optional(&gpio_led, &gpio_init_led);
	if (init_status != SUCCESS) {
		return init_status;
	} else {
		init_status = gpio_direction_output(gpio_led, GPIO_HIGH);
		if (init_status != SUCCESS) {
			return init_status;
		}
	}

	/* Get the AD7606 IIO device instance */
	iio_ad7606_init_param.ad7606_phy = p_ad7606_dev;

	/* Initialize the UART interface for IIO application */
	init_status = iio_uart_init(&uart_desc, &uart_init_param);
	if (init_status != SUCCESS) {
		return init_status;
	}

	/* Initialize the IIO application interface */
	init_status = iio_app_init(&iio_app_desc, &iio_app_init_par);
	if (init_status != SUCCESS) {
		return init_status;
	}

	/* Register and initialize the AD7606 device into IIO interface */
	init_status = iio_ad7606_init(&piio_ad7606_desc, &iio_ad7606_init_param);
	if (init_status != SUCCESS) {
		return init_status;
	}

	/* Init the data capture for AD7606 IIO app */
	init_status = iio_data_capture_init(p_ad7606_dev);
	if (init_status != SUCCESS) {
		return init_status;
	}

	/* Init the interrupt event handler for AD7606 IIO app */
	init_status = iio_interrupt_handler_init(uart_desc->extra);
	if (init_status != SUCCESS) {
		return init_status;
	}

	return init_status;
}


/**
 * @brief 	Run the AD7606 IIO event handler
 * @return	none
 * @details	This function monitors the new IIO client event
 */
void ad7606_iio_event_handler(void)
{
	/* Handle new IIO command */
	if (is_new_iio_command_detected()) {
		/* Stop the background data capture if it is running before and any
		 * iio command except READBUF is received */
		if (adc_background_data_capture_started) {
			if (!check_iio_cmd("READBUF", 7)) {
				stop_background_data_capture();
				adc_background_data_capture_started = false;
			}
		}

		/* Run the IIO interface when new command is detected */
		(void)iio_app(iio_app_desc);
	}
}