/***************************************************************************//**
 *   @file   ad7606.c
 *   @brief  Implementation of ad7606 Driver.
 *   @author Stefan Popa (stefan.popa@analog.com)
 *   @author Darius Berghe (darius.berghe@analog.com)
********************************************************************************
 * Copyright 2020(c) Analog Devices, Inc.
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *  - Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *  - Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *  - Neither the name of Analog Devices, Inc. nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *  - The use of this software may or may not infringe the patent rights
 *    of one or more patent holders.  This license does not release you
 *    from the requirement that you obtain separate licenses from these
 *    patent holders to use this software.
 *  - Use of the software either in source or binary form, must be run
 *    on or directly connected to an Analog Devices Inc. component.
 *
 * THIS SOFTWARE IS PROVIDED BY ANALOG DEVICES "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, NON-INFRINGEMENT,
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL ANALOG DEVICES BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, INTELLECTUAL PROPERTY RIGHTS, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*******************************************************************************/

/******************************************************************************/
/***************************** Include Files **********************************/
/******************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <errno.h>
#include "ad7606.h"
#include "error.h"
#include "util.h"
#include "crc.h"

struct ad7606_chip_info {
	uint8_t num_channels;
	uint8_t bits;
	uint8_t max_dout_lines;
	bool has_oversampling;
	bool has_registers;
	uint8_t device_id;
	const struct ad7606_range *hw_range_table;
	uint32_t hw_range_table_sz;
	const struct ad7606_range *sw_range_table;
	uint32_t sw_range_table_sz;
};

DECLARE_CRC8_TABLE(ad7606_crc8);
DECLARE_CRC16_TABLE(ad7606_crc16);

static const struct ad7606_range ad7606_range_table[] = {
	{-5000, 5000, false},	/* RANGE pin LOW */
	{-10000, 10000, false},	/* RANGE pin HIGH */
};

static const struct ad7606_range ad7609_range_table[] = {
	{-10000, 10000, true},	/* RANGE pin LOW */
	{-20000, 20000, true},	/* RANGE pin HIGH */
};

static const struct ad7606_range ad7606b_range_table[] = {
	{-2500, 2500, false},	/* 0000 */
	{-5000, 5000, false},	/* 0001 */
	{-10000, 10000, false},	/* 0010 */
	{-10000, 10000, false},	/* 0011 */
	{-10000, 10000, false},	/* 0100 */
	{-10000, 10000, false},	/* 0101 */
	{-10000, 10000, false},	/* 0110 */
	{-10000, 10000, false},	/* 0111 */
	{-10000, 10000, false},	/* 1000 */
	{-10000, 10000, false},	/* 1001 */
	{-10000, 10000, false},	/* 1010 */
	{-10000, 10000, false},	/* 1011 */
};

static const struct ad7606_range ad7606c_range_table[] = {
	{-2500, 2500, false},	/* 0000 */
	{-5000, 5000, false},	/* 0001 */
	{-6250, 6250, false},	/* 0010 */
	{-10000, 10000, false},	/* 0011 */
	{-12500, 12500, false},	/* 0100 */
	{0, 5000, false},	/* 0101 */
	{0, 10000, false},	/* 0110 */
	{0, 12500, false},	/* 0111 */
	{-5000, 5000, true},	/* 1000 */
	{-10000, 10000, true},	/* 1001 */
	{-12500, 12500, true},	/* 1010 */
	{-20000, 20000, true},	/* 1011 */
	{-20000, 20000, true},	/* 1100 */
	{-20000, 20000, true},	/* 1101 */
	{-20000, 20000, true},	/* 1110 */
	{-20000, 20000, true},	/* 1111 */
};

static const struct ad7606_chip_info ad7606_chip_info_tbl[] = {
	[ID_AD7605_4] = {
		.num_channels = 4,
		.bits = 16,
		.max_dout_lines = AD7606_2_DOUT,
		.has_oversampling = false,
		.hw_range_table = ad7606_range_table,
		.hw_range_table_sz = ARRAY_SIZE(ad7606_range_table),
	},
	[ID_AD7606_4] = {
		.num_channels = 4,
		.bits = 16,
		.max_dout_lines = AD7606_2_DOUT,
		.has_oversampling = true,
		.hw_range_table = ad7606_range_table,
		.hw_range_table_sz = ARRAY_SIZE(ad7606_range_table),
	},
	[ID_AD7606_6] = {
		.num_channels = 6,
		.bits = 16,
		.max_dout_lines = AD7606_2_DOUT,
		.has_oversampling = true,
		.hw_range_table = ad7606_range_table,
		.hw_range_table_sz = ARRAY_SIZE(ad7606_range_table),
	},
	[ID_AD7606_8] = {
		.num_channels = 8,
		.bits = 16,
		.max_dout_lines = AD7606_2_DOUT,
		.has_oversampling = true,
		.hw_range_table = ad7606_range_table,
		.hw_range_table_sz = ARRAY_SIZE(ad7606_range_table),
	},
	[ID_AD7606B] = {
		.num_channels = 8,
		.bits = 16,
		.max_dout_lines = AD7606_4_DOUT,
		.has_oversampling = true,
		.has_registers = true,
		.device_id = 0x1,
		.hw_range_table = ad7606_range_table,
		.hw_range_table_sz = ARRAY_SIZE(ad7606_range_table),
		.sw_range_table = ad7606b_range_table,
		.sw_range_table_sz = ARRAY_SIZE(ad7606b_range_table),
	},
	[ID_AD7606C_16] = {
		.num_channels = 8,
		.bits = 16,
		.max_dout_lines = AD7606_8_DOUT,
		.has_oversampling = true,
		.has_registers = true,
		.device_id = 0x3,
		.hw_range_table = ad7606_range_table,
		.hw_range_table_sz = ARRAY_SIZE(ad7606_range_table),
		.sw_range_table = ad7606c_range_table,
		.sw_range_table_sz = ARRAY_SIZE(ad7606c_range_table),
	},
	[ID_AD7606C_18] = {
		.num_channels = 8,
		.bits = 18,
		.max_dout_lines = AD7606_8_DOUT,
		.has_oversampling = true,
		.has_registers = true,
		.device_id = 0x3,
		.hw_range_table = ad7606_range_table,
		.hw_range_table_sz = ARRAY_SIZE(ad7606_range_table),
		.sw_range_table = ad7606c_range_table,
		.sw_range_table_sz = ARRAY_SIZE(ad7606c_range_table),
	},
	[ID_AD7608] = {
		.num_channels = 8,
		.bits = 18,
		.max_dout_lines = AD7606_2_DOUT,
		.has_oversampling = true,
		.hw_range_table = ad7606_range_table,
		.hw_range_table_sz = ARRAY_SIZE(ad7606_range_table),
	},
	[ID_AD7609] = {
		.num_channels = 8,
		.bits = 18,
		.max_dout_lines = AD7606_2_DOUT,
		.has_oversampling = true,
		.hw_range_table = ad7609_range_table,
		.hw_range_table_sz = ARRAY_SIZE(ad7609_range_table),
	},
};

static const uint16_t tconv_max[] = {
	1, 	/* AD7606_OSR_1 */
	3,	/* AD7606_OSR_2 */
	5,	/* AD7606_OSR_4 */
	10,	/* AD7606_OSR_8 */
	20,	/* AD7606_OSR_16 */
	41,	/* AD7606_OSR_32 */
	81,	/* AD7606_OSR_64 */
	162,	/* AD7606_OSR_128 */
	324	/* AD7606_OSR_256 */
};


/***************************************************************************//**
 * @brief Read a device register via SPI.
 *
 * This function performs CRC8 computation and checking if enabled in the device.
 *
 * @param dev        - The device structure.
 * @param reg_addr   - Register address in device memory.
 * @param reg_data   - Pointer to the location where to store the register value.
 *
 * @return ret - return code.
 *         Example: -EIO - SPI communication error.
 *                  -ENOTSUP - Device not in software mode.
 *                  -EBADMSG - CRC computation mismatch.
 *                  SUCCESS - No errors encountered.
*******************************************************************************/
int32_t ad7606_spi_reg_read(struct ad7606_dev *dev,
			    uint8_t reg_addr,
			    uint8_t *reg_data)
{
	uint8_t buf[3];
	uint8_t crc;
	uint32_t sz = 2;
	int32_t ret;

	if (!dev->sw_mode)
		return -ENOTSUP;

	buf[0] = AD7606_RD_FLAG_MSK(reg_addr);
	buf[1] = 0x00;
	if (dev->digital_diag_enable.int_crc_err_en) {
		crc = crc8(ad7606_crc8, buf, 2, 0);
		buf[2] = crc;
		sz += 1;
	}
	ret = spi_write_and_read(dev->spi_desc, buf, sz);
	if (ret < 0)
		return ret;

	dev->reg_mode = true;

	buf[0] = AD7606_RD_FLAG_MSK(reg_addr);
	buf[1] = 0x00;
	if (dev->digital_diag_enable.int_crc_err_en) {
		crc = crc8(ad7606_crc8, buf, 2, 0);
		buf[2] = crc;
	}
	ret = spi_write_and_read(dev->spi_desc, buf, sz);
	if (ret < 0)
		return ret;

	if (dev->digital_diag_enable.int_crc_err_en) {
		crc = crc8(ad7606_crc8, buf, 2, 0);
		if (crc != buf[2])
			return -EBADMSG;
	}

	if (reg_data)
		*reg_data = buf[1];

	return ret;
}

/***************************************************************************//**
 * @brief Write a device register via SPI.
 *
 * This function performs CRC8 computation and checking if enabled in the device.
 *
 * @param dev        - The device structure.
 * @param reg_addr   - Register address in device memory.
 * @param reg_data   - Value to write to register.
 *
 * @return ret - return code.
 *         Example: -EIO - SPI communication error.
 *                  -ENOTSUP - Device not in software mode.
 *                  -EBADMSG - CRC computation mismatch.
 *                  SUCCESS - No errors encountered.
*******************************************************************************/
int32_t ad7606_spi_reg_write(struct ad7606_dev *dev,
			     uint8_t reg_addr,
			     uint8_t reg_data)
{
	uint8_t buf[3];
	int32_t ret;
	uint8_t crc;
	uint32_t sz = 2;

	if (!dev->sw_mode)
		return -ENOTSUP;

	/* Dummy read to place the chip in register mode. */
	if (!dev->reg_mode) {
		ret = ad7606_spi_reg_read(dev, reg_addr, NULL);
		if (ret < 0)
			return ret;
	}

	buf[0] = AD7606_WR_FLAG_MSK(reg_addr);
	buf[1] = reg_data;
	if (dev->digital_diag_enable.int_crc_err_en) {
		crc = crc8(ad7606_crc8, buf, 2, 0);
		buf[2] = crc;
		sz += 1;
	}

	ret = spi_write_and_read(dev->spi_desc, buf, sz);
	if (ret < 0)
		return ret;

	return ret;
}

/***************************************************************************//**
 * @brief Write a device register via SPI with masking.
 *
 * @param dev        - The device structure.
 * @param addr       - Register address in device memory.
 * @param mask       - Only bits set to 1 in mask will be modified.
 * @param val        - Value to write to register.
 *
 * @return ret - return code.
 *         Example: -EIO - SPI communication error.
 *                  -ENOTSUP - Device not in software mode.
 *                  -EBADMSG - CRC computation mismatch.
 *                  SUCCESS - No errors encountered.
*******************************************************************************/
int32_t ad7606_spi_write_mask(struct ad7606_dev *dev,
			      uint32_t addr,
			      uint32_t mask,
			      uint32_t val)
{
	uint8_t reg_data;
	int ret;

	ret = ad7606_spi_reg_read(dev, addr, &reg_data);
	if (ret < 0)
		return ret;

	reg_data &= ~mask;
	reg_data |= val;

	return ad7606_spi_reg_write(dev, addr, reg_data);
}

/* Internal function to copy the content of a buffer in 18-bit chunks to a 32-bit buffer by
 * extending the chunks to 32-bit size. */
static int32_t cpy18b32b(uint8_t *psrc, uint32_t srcsz, uint32_t *pdst)
{
	unsigned int i, j;

	if (srcsz % 9)
		return -EINVAL;

	for(i = 0; i < srcsz; i += 9) {
		j = 4 * (i / 9);
		pdst[j+0] = ((uint32_t)(psrc[i+0] & 0xff) << 10) | ((uint32_t)psrc[i+1] << 2)
			    | ((uint32_t)psrc[i+2] >> 6);
		pdst[j+1] = ((uint32_t)(psrc[i+2] & 0x3f) << 12) | ((uint32_t)psrc[i+3] << 4)
			    | ((uint32_t)psrc[i+4] >> 4);
		pdst[j+2] = ((uint32_t)(psrc[i+4] & 0x0f) << 14) | ((uint32_t)psrc[i+5] << 6)
			    | ((uint32_t)psrc[i+6] >> 2);
		pdst[j+3] = ((uint32_t)(psrc[i+6] & 0x03) << 16) | ((uint32_t)psrc[i+7] << 8)
			    | ((uint32_t)psrc[i+8] >> 0);
	}
	return SUCCESS;
}

/* Internal function to copy the content of a buffer in 26-bit chunks to a 32-bit buffer by
 * extending the chunks to 32-bit size. */
static int32_t cpy26b32b(uint8_t *psrc, uint32_t srcsz, uint32_t *pdst)
{
	unsigned int i, j;

	if (srcsz % 13)
		return -EINVAL;

	for(i = 0; i < srcsz; i += 13) {
		j = 4 * (i / 13);
		pdst[j+0] = ((uint32_t)(psrc[i+0] & 0xff) << 18) | ((uint32_t)psrc[i+1] << 10)
			    | ((uint32_t)psrc[i+2] << 2) | ((uint32_t)psrc[i+3] >> 6);
		pdst[j+1] = ((uint32_t)(psrc[i+3] & 0x3f) << 20) | ((uint32_t)psrc[i+4] << 12)
			    | ((uint32_t)psrc[i+5] << 4) | ((uint32_t)psrc[i+6] >> 4);
		pdst[j+2] = ((uint32_t)(psrc[i+6] & 0x0f) << 22) | ((uint32_t)psrc[i+7] << 14)
			    | ((uint32_t)psrc[i+8] << 6) | ((uint32_t)psrc[i+9] >> 2);
		pdst[j+3] = ((uint32_t)(psrc[i+9] & 0x03) << 24) | ((uint32_t)psrc[i+10] << 16)
			    | ((uint32_t)psrc[i+11] << 8) | ((uint32_t)psrc[i+12] >> 0);
	}
	return SUCCESS;
}

/***************************************************************************//**
 * @brief Toggle the CONVST pin to start a conversion.
 *
 * If needed, this function also puts the device in ADC reading mode by a write
 * at address zero.
 *
 * @param dev        - The device structure.
 *
 * @return ret - return code.
 *         Example: -EIO - SPI communication error.
 *                  -EIO - CONVST GPIO not available.
 *                  SUCCESS - No errors encountered.
*******************************************************************************/
int32_t ad7606_convst(struct ad7606_dev *dev)
{
	int32_t ret;

	if (dev->reg_mode) {
		/* Enter ADC reading mode by writing at address zero. */
		ret = ad7606_spi_reg_write(dev, 0, 0);
		if (ret < 0)
			return ret;

		dev->reg_mode = false;
	}

	ret = gpio_set_value(dev->gpio_convst, 0);
	if (ret < 0)
		return ret;

	/* wait LP_CNV time */
	udelay(1);

	return gpio_set_value(dev->gpio_convst, 1);
}

/***************************************************************************//**
 * @brief Read conversion data.
 *
 * This function performs CRC16 computation and checking if enabled in the device.
 * If the status is enabled in device settings, each sample of data will contain
 * status information in the lowest 8 bits.
 *
 * The output buffer provided by the user should be as wide as to be able to
 * contain 1 sample from each channel since this function reads conversion data
 * across all channels.
 *
 * @param dev        - The device structure.
 * @param data       - Pointer to location of buffer where to store the data.
 *
 * @return ret - return code.
 *         Example: -EIO - SPI communication error.
 *                  -EBADMSG - CRC computation mismatch.
 *                  -ENOTSUP - Device bits per sample not supported.
 *                  SUCCESS - No errors encountered.
*******************************************************************************/
int32_t ad7606_spi_data_read(struct ad7606_dev *dev, uint32_t *data)
{
	uint32_t sz;
	int32_t ret, i;
	uint16_t crc, icrc;
	uint8_t bits = ad7606_chip_info_tbl[dev->device_id].bits;
	uint8_t sbits = dev->config.status_header ? 8 : 0;
	uint8_t nchannels = ad7606_chip_info_tbl[dev->device_id].num_channels;

	sz = nchannels * (bits + sbits);

	/* Number of bits to read, corresponds to SCLK cycles in transfer.
	 * This should always be a multiple of 8 to work with most SPI's.
	 * With this chip family this holds true because we either:
	 *  - multiply 8 channels * bits per sample
	 *  - multiply 4 channels * bits per sample (always multiple of 2)
	 * Therefore, due to design reasons, we don't check for the
	 * remainder of this division because it is zero by design.
	 */
	sz /= 8;

	if (dev->digital_diag_enable.int_crc_err_en) {
		sz += 2;
	}

	memset(dev->data, 0, sz);
	ret = spi_write_and_read(dev->spi_desc, dev->data, sz);
	if (ret < 0)
		return ret;

	if (dev->digital_diag_enable.int_crc_err_en) {
		sz -= 2;
		crc = crc16(ad7606_crc16, dev->data, sz, 0);
		icrc = ((uint16_t)dev->data[sz] << 8) |
		       dev->data[sz+1];
		if (icrc != crc)
			return -EBADMSG;
	}

	switch(bits) {
	case 18:
		if (dev->config.status_header)
			ret = cpy26b32b(dev->data, sz, data);
		else
			ret = cpy18b32b(dev->data, sz, data);
		if (ret < 0)
			return ret;
		break;
	case 16:
		for(i = 0; i < nchannels; i++) {
			if (dev->config.status_header) {
				data[i] = (uint32_t)dev->data[i*3] << 16;
				data[i] |= (uint32_t)dev->data[i*3+1] << 8;
				data[i] |= (uint32_t)dev->data[i*3+2];
			} else {
				data[i] = (uint32_t)dev->data[i*2] << 8;
				data[i] |= (uint32_t)dev->data[i*2+1];
			}
		}
		break;
	default:
		ret = -ENOTSUP;
		break;
	};

	return ret;
}

/***************************************************************************//**
 * @brief Blocking conversion start and data read.
 *
 * This function performs a conversion start and then proceeds to reading
 * the conversion data.
 *
 * @param dev        - The device structure.
 * @param data       - Pointer to location of buffer where to store the data.
 *
 * @return ret - return code.
 *         Example: -EIO - SPI communication error.
 *                  -ETIME - Timeout while waiting for the BUSY signal.
 *                  -EBADMSG - CRC computation mismatch.
 *                  SUCCESS - No errors encountered.
*******************************************************************************/
int32_t ad7606_read(struct ad7606_dev *dev, uint32_t * data)
{
	int32_t ret;
	uint8_t busy;
	uint32_t timeout = tconv_max[AD7606_OSR_256];

	ret = ad7606_convst(dev);
	if (ret < 0)
		return ret;

	if (dev->gpio_busy) {
		/* Wait for BUSY falling edge */
		while(timeout) {
			ret = gpio_get_value(dev->gpio_busy, &busy);
			if (ret < 0)
				return ret;

			if (busy == 0)
				break;

			udelay(1);
			timeout--;
		}

		if (timeout == 0)
			return -ETIME;
	} else {
		/* wait CONV time */
		udelay(tconv_max[dev->oversampling.os_ratio]);
	}

	return ad7606_spi_data_read(dev, data);
}

/* Internal function to reset device settings to default state after chip reset. */
static inline void ad7606_reset_settings(struct ad7606_dev *dev)
{
	int i;
	const struct ad7606_range *rt = dev->sw_mode ?
						ad7606_chip_info_tbl[dev->device_id].sw_range_table:
						ad7606_chip_info_tbl[dev->device_id].hw_range_table;

	for(i = 0; i < dev->num_channels; i++) {
		if (dev->sw_mode)
			dev->range_ch[i] = rt[3];
		else
			dev->range_ch[i] = rt[0];

		dev->offset_ch[i] = 0;
		dev->phase_ch[i] = 0;
		dev->gain_ch[i] = 0;
	}

	dev->oversampling.os_ratio = AD7606_OSR_1;
	dev->oversampling.os_pad = 0;
	dev->config.op_mode = AD7606_NORMAL;
	dev->config.dout_format = AD7606_2_DOUT;
	dev->config.ext_os_clock = false;
	dev->config.status_header = false;
	dev->digital_diag_enable.rom_crc_err_en = true;
	dev->digital_diag_enable.mm_crc_err_en = false;
	dev->digital_diag_enable.int_crc_err_en = false;
	dev->digital_diag_enable.spi_write_err_en = false;
	dev->digital_diag_enable.spi_read_err_en = false;
	dev->digital_diag_enable.busy_stuck_high_err_en = false;
	dev->digital_diag_enable.clk_fs_os_counter_en = false;
	dev->digital_diag_enable.interface_check_en = false;
	dev->reg_mode = false;
}

/***************************************************************************//**
 * @brief Reset the device by toggling the reset GPIO.
 *
 * @param dev        - The device structure.
 *
 * @return ret - return code.
 *         Example: -EIO - Reset GPIO not available.
 *                  SUCCESS - No errors encountered.
*******************************************************************************/
int32_t ad7606_reset(struct ad7606_dev *dev)
{
	int32_t ret;

	ret = gpio_set_value(dev->gpio_reset, 1);
	if (ret < 0)
		return ret;

	udelay(3);

	ret = gpio_set_value(dev->gpio_reset, 0);
	if (ret < 0)
		return ret;

	ad7606_reset_settings(dev);

	return ret;
}

/* Internal function that initializes GPIOs. */
static int32_t ad7606_request_gpios(struct ad7606_dev *dev,
				    struct ad7606_init_param *init_param)
{
	int32_t ret;

	ret = gpio_get_optional(&dev->gpio_reset, init_param->gpio_reset);
	if (ret < 0)
		return ret;

	if (dev->gpio_reset) {
		ret = gpio_direction_output(dev->gpio_reset, GPIO_LOW);
		if (ret < 0)
			return ret;
	}

	ret = gpio_get_optional(&dev->gpio_convst, init_param->gpio_convst);
	if (ret < 0)
		return ret;

	if (dev->gpio_convst) {
		ret = gpio_direction_output(dev->gpio_convst, GPIO_LOW);
		if (ret < 0)
			return ret;
	}

	ret = gpio_get_optional(&dev->gpio_busy, init_param->gpio_busy);
	if (ret < 0)
		return ret;

	if (dev->gpio_busy) {
		ret = gpio_direction_input(dev->gpio_busy);
		if (ret < 0)
			return ret;
	}

	ret = gpio_get_optional(&dev->gpio_stby_n, init_param->gpio_stby_n);
	if (ret < 0)
		return ret;

	if (dev->gpio_stby_n) {
		ret = gpio_direction_output(dev->gpio_stby_n, GPIO_HIGH);
		if (ret < 0)
			return ret;
	}

	ret = gpio_get_optional(&dev->gpio_range, init_param->gpio_range);
	if (ret < 0)
		return ret;

	if (dev->gpio_range) {
		ret = gpio_direction_output(dev->gpio_range, GPIO_LOW);
		if (ret < 0)
			return ret;
	}

	if (!ad7606_chip_info_tbl[dev->device_id].has_oversampling)
		return ret;

	ret = gpio_get_optional(&dev->gpio_os0, init_param->gpio_os0);
	if (ret < 0)
		return ret;

	if (dev->gpio_os0) {
		ret = gpio_direction_output(dev->gpio_os0, GPIO_LOW);
		if (ret < 0)
			return ret;
	}

	ret = gpio_get_optional(&dev->gpio_os1, init_param->gpio_os1);
	if (ret < 0)
		return ret;

	if (dev->gpio_os1) {
		ret = gpio_direction_output(dev->gpio_os1, GPIO_LOW);
		if (ret < 0)
			return ret;
	}

	ret = gpio_get_optional(&dev->gpio_os2, init_param->gpio_os2);
	if (ret < 0)
		return ret;

	if (dev->gpio_os2) {
		ret = gpio_direction_output(dev->gpio_os2, GPIO_LOW);
		if (ret < 0)
			return ret;
	}

	ret = gpio_get_optional(&dev->gpio_par_ser, init_param->gpio_par_ser);
	if (ret < 0)
		return ret;

	if (dev->gpio_par_ser) {
		/* Driver currently supports only serial interface, therefore,
		 * if available, pull the GPIO HIGH. */
		ret = gpio_direction_output(dev->gpio_par_ser, GPIO_HIGH);
		if (ret < 0)
			return ret;
	}

	return ret;
}

/***************************************************************************//**
 * @brief Set the oversampling ratio.
 *
 * In hardware mode, it silently sets AD7606_OSR_64 if higher oversampling
 * is provided.
 *
 * @param dev          - The device structure.
 * @param oversampling - Oversampling settings.
 *
 * @return ret - return code.
 *         Example: -EIO - SPI communication error.
 *                  -EBADMSG - CRC computation mismatch.
 *                  SUCCESS - No errors encountered.
*******************************************************************************/
int32_t ad7606_set_oversampling(struct ad7606_dev *dev,
				struct ad7606_oversampling oversampling)
{
	int32_t ret;
	uint8_t val;

	if (dev->sw_mode) {
		val = field_prep(AD7606_OS_RATIO_MSK, oversampling.os_ratio);
		val |= field_prep(AD7606_OS_PAD_MSK, oversampling.os_pad);
		ret = ad7606_spi_reg_write(dev, AD7606_REG_OVERSAMPLING, val);
		if (ret < 0)
			return ret;
	} else {
		/* In hardware mode, OSR 128 and 256 are not avaialable */
		if (oversampling.os_ratio > AD7606_OSR_64)
			oversampling.os_ratio = AD7606_OSR_64;

		ret = gpio_set_value(dev->gpio_os0, ((oversampling.os_ratio & 0x01) >> 0));
		if (ret < 0)
			return ret;

		ret = gpio_set_value(dev->gpio_os1, ((oversampling.os_ratio & 0x02) >> 1));
		if (ret < 0)
			return ret;

		ret = gpio_set_value(dev->gpio_os2, ((oversampling.os_ratio & 0x04) >> 2));
		if (ret < 0)
			return ret;
	}

	dev->oversampling = oversampling;

	return SUCCESS;
}

/* Internal function to find the index of a given operation range in the
 * operation range table specific to a device. */
static int8_t ad7606_find_range(struct ad7606_dev *dev,
				struct ad7606_range range)
{
	uint8_t i;
	int8_t v = -1;
	const struct ad7606_range *rt = dev->sw_mode ?
						ad7606_chip_info_tbl[dev->device_id].sw_range_table:
						ad7606_chip_info_tbl[dev->device_id].hw_range_table;

	uint32_t rtsz = dev->sw_mode ?
			ad7606_chip_info_tbl[dev->device_id].sw_range_table_sz:
			ad7606_chip_info_tbl[dev->device_id].hw_range_table_sz;

	for (i = 0; i < rtsz; i++) {
		if (range.min != rt[i].min)
			continue;
		if (range.max != rt[i].max)
			continue;
		if (range.differential != rt[i].differential)
			continue;
		v = i;
		break;
	}

	return v;
}

/***************************************************************************//**
 * @brief Set the channel operation range.
 *
 * @param dev        - The device structure.
 * @param ch         - Channel number (0-7).
 * @param range      - Operation range.
 *
 * @return ret - return code.
 *         Example: -EIO - SPI communication error.
 *                  -EINVAL - Invalid input.
 *                  -EBADMSG - CRC computation mismatch.
 *                  SUCCESS - No errors encountered.
*******************************************************************************/
int32_t ad7606_set_ch_range(struct ad7606_dev *dev, uint8_t ch,
			    struct ad7606_range range)
{
	int value;
	int32_t ret;

	if (range.min > range.max)
		return -EINVAL;

	if (ch >= dev->num_channels)
		return -EINVAL;

	value = ad7606_find_range(dev, range);
	if (value < 0)
		return -EINVAL;

	if (dev->sw_mode)
		ret = ad7606_spi_write_mask(dev, AD7606_REG_RANGE_CH_ADDR(ch),
					    AD7606_RANGE_CH_MSK(ch),
					    AD7606_RANGE_CH_MODE(ch, value));
	else
		ret = gpio_set_value(dev->gpio_range, value);

	if (ret)
		return ret;

	dev->range_ch[ch] = range;

	return ret;
}

/***************************************************************************//**
 * @brief Set the channel offset.
 *
 * The offset parameter is a signed 8-bit integer ranging from -128 to 127 to
 * make it intuitive and user-friendly.
 *
 * This offset gets converted to the register representation where 0x80 is
 * calibration offset 0, 0x0 is calibration offset -128 and 0xFF is calibration
 * offset 127, etc.
 *
 * @param dev        - The device structure.
 * @param ch         - Channel number (0-7).
 * @param offset     - Offset calibration amount (-128...127).
 *
 * @return ret - return code.
 *         Example: -EIO - SPI communication error.
 *                  -EINVAL - Invalid input.
 *                  -ENOTSUP - Device not in software mode.
 *                  -EBADMSG - CRC computation mismatch.
 *                  SUCCESS - No errors encountered.
*******************************************************************************/
int32_t ad7606_set_ch_offset(struct ad7606_dev *dev, uint8_t ch,
			     int8_t offset)
{
	int ret;
	uint8_t value = (uint8_t)(offset - 0x80);

	if (ch >= dev->num_channels)
		return -EINVAL;

	if (!dev->sw_mode)
		return -ENOTSUP;

	ret = ad7606_spi_reg_write(dev, AD7606_REG_OFFSET_CH(ch), value);
	if (ret < 0)
		return ret;

	dev->offset_ch[ch] = offset;

	return ret;
}

/***************************************************************************//**
 * @brief Set the channel phase.
 *
 * @param dev        - The device structure.
 * @param ch         - Channel number (0-7).
 * @param phase      - Phase calibration amount.
 *
 * @return ret - return code.
 *         Example: -EIO - SPI communication error.
 *                  -EINVAL - Invalid input.
 *                  -ENOTSUP - Device not in software mode.
 *                  -EBADMSG - CRC computation mismatch.
 *                  SUCCESS - No errors encountered.
*******************************************************************************/
int32_t ad7606_set_ch_phase(struct ad7606_dev *dev, uint8_t ch,
			    uint8_t phase)
{
	int ret;

	if (ch >= dev->num_channels)
		return -EINVAL;

	if (!dev->sw_mode)
		return -ENOTSUP;

	ret = ad7606_spi_reg_write(dev, AD7606_REG_PHASE_CH(ch), phase);
	if (ret < 0)
		return ret;

	dev->phase_ch[ch] = phase;

	return ret;
}

/***************************************************************************//**
 * @brief Set the channel gain.
 *
 * @param dev        - The device structure.
 * @param ch         - Channel number (0-7).
 * @param gain       - Gain calibration amount.
 *
 * @return ret - return code.
 *         Example: -EIO - SPI communication error.
 *                  -EINVAL - Invalid input.
 *                  -ENOTSUP - Device not in software mode.
 *                  -EBADMSG - CRC computation mismatch.
 *                  SUCCESS - No errors encountered.
*******************************************************************************/
int32_t ad7606_set_ch_gain(struct ad7606_dev *dev, uint8_t ch,
			   uint8_t gain)
{
	int ret;

	if (ch >= dev->num_channels)
		return -EINVAL;

	if (!dev->sw_mode)
		return -ENOTSUP;

	gain = field_get(AD7606_GAIN_MSK, gain);
	ret = ad7606_spi_reg_write(dev, AD7606_REG_GAIN_CH(ch), gain);
	if (ret < 0)
		return ret;

	dev->gain_ch[ch] = gain;

	return ret;
}

/***************************************************************************//**
 * @brief Set the device config register.
 *
 * Configuration structure affects the CONFIG register of the device.
 *
 * @param dev        - The device structure.
 * @param config     - Configuration structure.
 *
 * @return ret - return code.
 *         Example: -EIO - SPI communication error.
 *                  -EIO - GPIO not available.
 *                  -EINVAL - Invalid input.
 *                  -EBADMSG - CRC computation mismatch.
 *                  SUCCESS - No errors encountered.
*******************************************************************************/
int32_t ad7606_set_config(struct ad7606_dev *dev,
			  struct ad7606_config config)
{
	int32_t ret;
	uint8_t val = 0;
	uint8_t range_pin, stby_n_pin;

	if (dev->sw_mode) {

		val |= field_prep(AD7606_CONFIG_OPERATION_MODE_MSK, config.op_mode);
		/* This driver currently supports only normal SPI with 1 DOUT line.
		 * TODO: remove this check when implementing multi-line DOUT. */
		if ((uint8_t)config.dout_format > AD7606_1_DOUT)
			return -EINVAL;
		if ((uint8_t)config.dout_format > (uint8_t)dev->max_dout_lines)
			return -EINVAL;
		val |= field_prep(AD7606_CONFIG_DOUT_FORMAT_MSK, config.dout_format);
		val |= field_prep(AD7606_CONFIG_EXT_OS_CLOCK_MSK, config.ext_os_clock);
		val |= field_prep(AD7606_CONFIG_STATUS_HEADER_MSK, config.status_header);

		ret = ad7606_spi_reg_write(dev, AD7606_REG_CONFIG, val);
		if (ret)
			return ret;
	} else {
		switch(config.op_mode) {
		case AD7606_NORMAL:
			range_pin = GPIO_LOW;
			stby_n_pin = GPIO_HIGH;
			break;
		case AD7606_STANDBY:
			range_pin = GPIO_LOW;
			stby_n_pin = GPIO_LOW;
			break;
		case AD7606_SHUTDOWN:
			range_pin = GPIO_HIGH;
			stby_n_pin = GPIO_LOW;
			break;
		default:
			return -EINVAL;
		};

		ret = gpio_set_value(dev->gpio_stby_n, stby_n_pin);
		if (ret)
			return ret;

		ret = gpio_set_value(dev->gpio_range, range_pin);
		if (ret)
			return ret;
	}

	dev->config = config;

	return ret;
}

/***************************************************************************//**
 * @brief Set the device digital diagnostics configuration.
 *
 * Digital diagnostics structure affects the DIGITAL_DIAG register of the device.
 *
 * @param dev        - The device structure.
 * @param diag       - Configuration structure.
 *
 * @return ret - return code.
 *         Example: -EIO - SPI communication error.
 *                  -ENOTSUP - Device not in software mode.
 *                  -EBADMSG - CRC computation mismatch.
 *                  SUCCESS - No errors encountered.
*******************************************************************************/
int32_t ad7606_set_digital_diag(struct ad7606_dev *dev,
				struct ad7606_digital_diag diag)
{
	int32_t ret;
	uint8_t val = 0;

	if (!dev->sw_mode)
		return -ENOTSUP;

	val |= field_prep(AD7606_ROM_CRC_ERR_EN_MSK, diag.rom_crc_err_en);
	val |= field_prep(AD7606_MM_CRC_ERR_EN_MSK, diag.mm_crc_err_en);
	val |= field_prep(AD7606_INT_CRC_ERR_EN_MSK, diag.int_crc_err_en);
	val |= field_prep(AD7606_SPI_WRITE_ERR_EN_MSK, diag.spi_write_err_en);
	val |= field_prep(AD7606_SPI_READ_ERR_EN_MSK, diag.spi_read_err_en);
	val |= field_prep(AD7606_BUSY_STUCK_HIGH_ERR_EN_MSK,
			  diag.busy_stuck_high_err_en);
	val |= field_prep(AD7606_CLK_FS_OS_COUNTER_EN_MSK, diag.clk_fs_os_counter_en);
	val |= field_prep(AD7606_INTERFACE_CHECK_EN_MSK, diag.interface_check_en);

	ret = ad7606_spi_reg_write(dev, AD7606_REG_DIGITAL_DIAG_ENABLE, val);
	if (ret < 0)
		return ret;

	dev->digital_diag_enable = diag;

	return ret;
}

/***************************************************************************//**
 * @brief Initialize the ad7606 device structure.
 *
 * Performs memory allocation of the device structure.
 *
 * @param device     - Pointer to location of device structure to write.
 * @param init_param - Pointer to configuration of the driver.
 *
 * @return ret - return code.
 *         Example: -ENOMEM - Memory allocation error.
 *                  -EIO - SPI communication error.
 *                  -EIO - GPIO initialization error.
 *                  -ENODEV - Unexpected device id.
 *                  -EBADMSG - CRC computation mismatch.
 *                  SUCCESS - No errors encountered.
*******************************************************************************/
int32_t ad7606_init(struct ad7606_dev **device,
		    struct ad7606_init_param *init_param)
{
	struct ad7606_dev *dev;
	uint8_t reg, id;
	int32_t i, ret;

	crc8_populate_msb(ad7606_crc8, 0x7);
	crc16_populate_msb(ad7606_crc16, 0x755b);

	dev = (struct ad7606_dev *)calloc(1, sizeof(*dev));
	if (!dev)
		return -ENOMEM;

	dev->device_id = init_param->device_id;
	dev->num_channels = ad7606_chip_info_tbl[dev->device_id].num_channels;
	dev->max_dout_lines = ad7606_chip_info_tbl[dev->device_id].max_dout_lines;
	if (ad7606_chip_info_tbl[dev->device_id].has_registers)
		dev->sw_mode = init_param->sw_mode;

	ret = ad7606_request_gpios(dev, init_param);
	if (ret < 0)
		goto error;

	if (init_param->sw_mode) {
		ret = gpio_set_value(dev->gpio_os0, GPIO_HIGH);
		if (ret < 0)
			goto error;

		ret = gpio_set_value(dev->gpio_os1, GPIO_HIGH);
		if (ret < 0)
			goto error;

		ret = gpio_set_value(dev->gpio_os2, GPIO_HIGH);
		if (ret < 0)
			goto error;
	}

	ret = ad7606_reset(dev);
	if (ret < 0)
		goto error;

	/* wait DEVICE_SETUP time */
	udelay(253);

	ret = spi_init(&dev->spi_desc, &init_param->spi_init);
	if (ret < 0)
		goto error;

	if (dev->sw_mode) {
		ret = ad7606_spi_reg_read(dev, AD7606_REG_ID, &reg);
		if (ret < 0)
			goto error;

		id = ad7606_chip_info_tbl[dev->device_id].device_id;
		if (field_get(AD7606_ID_DEVICE_ID_MSK, reg) != id) {
			printf("ad7606: device id mismatch, expected 0x%.2x, got 0x%.2x\n",
			       id,
			       (int)field_get(AD7606_ID_DEVICE_ID_MSK, reg));
			ret = -ENODEV;
			goto error;
		}

		ret = ad7606_set_digital_diag(dev, init_param->digital_diag_enable);
		if (ret < 0)
			goto error;

		ret = ad7606_set_config(dev, init_param->config);
		if (ret < 0)
			goto error;

		for (i = 0; i < dev->num_channels; i++) {
			ret = ad7606_set_ch_range(dev, i, init_param->range_ch[i]);
			if (ret < 0)
				goto error;
		}

		for(i = 0; i < dev->num_channels; i++) {
			ret = ad7606_set_ch_offset(dev, i, init_param->offset_ch[i]);
			if (ret < 0)
				goto error;
		}

		for(i = 0; i < dev->num_channels; i++) {
			ret = ad7606_set_ch_phase(dev, i, init_param->phase_ch[i]);
			if (ret < 0)
				goto error;
		}

		for(i = 0; i < dev->num_channels; i++) {
			ret = ad7606_set_ch_gain(dev, i, init_param->gain_ch[i]);
			if (ret < 0)
				goto error;
		}
	} else {
		ret = ad7606_set_ch_range(dev, 0, init_param->range_ch[0]);
		if (ret < 0)
			goto error;
	}

	ret = gpio_set_value(dev->gpio_convst, 1);
	if (ret < 0)
		goto error;

	if (ad7606_chip_info_tbl[dev->device_id].has_oversampling)
		ad7606_set_oversampling(dev, init_param->oversampling);

	*device = dev;

	printf("ad7606 successfully initialized\n");

	return ret;
error:
	printf("ad7606 initialization failed\n");
	ad7606_remove(dev);
	return ret;
}

/***************************************************************************//**
 * @brief Free any resource used by the driver.
 *
 * @param dev        - The device structure.
 *
 * @return ret - return code.
 *         Example: -EIO - SPI communication error.
 *                  SUCCESS - No errors encountered.
*******************************************************************************/
int32_t ad7606_remove(struct ad7606_dev *dev)
{
	int32_t ret;

	gpio_remove(dev->gpio_reset);
	gpio_remove(dev->gpio_convst);
	gpio_remove(dev->gpio_busy);
	gpio_remove(dev->gpio_stby_n);
	gpio_remove(dev->gpio_range);
	gpio_remove(dev->gpio_os0);
	gpio_remove(dev->gpio_os1);
	gpio_remove(dev->gpio_os2);
	gpio_remove(dev->gpio_par_ser);

	ret = spi_remove(dev->spi_desc);

	free(dev);

	return ret;
}
