Pratyush Mallick
/
testing
this is testing
Diff: app/noos_mbed/drivers/adc/ad7606/ad7606.c
- Revision:
- 0:3afcd581558d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/noos_mbed/drivers/adc/ad7606/ad7606.c Thu Jan 14 18:54:16 2021 +0530 @@ -0,0 +1,1251 @@ +/***************************************************************************//** + * @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, ®_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, ®); + 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; +}