ADISense1000 Version 2.1 code base

Fork of AdiSense1000_V21 by Sean Wilson

src/adi_sense_1000.c

Committer:
kevin1990
Date:
2017-10-20
Revision:
7:4dbae381f693

File content as of revision 7:4dbae381f693:

/*!
 ******************************************************************************
 * @file:  adi_sense_1000.c
 * @brief: ADI Sense API implementation for ADI Sense 1000
 *-----------------------------------------------------------------------------
 */

/******************************************************************************
Copyright (c) 2017 Emutex Ltd. / 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.
  - Modified versions of the software must be conspicuously marked as such.
  - This software is licensed solely and exclusively for use with processors
    manufactured by or for Analog Devices, Inc.
  - This software may not be combined or merged with other code in any manner
    that would cause the software to become subject to terms and conditions
    which differ from those listed here.
  - 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.

THIS SOFTWARE IS PROVIDED BY ANALOG DEVICES, INC. AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, NON-INFRINGEMENT,
TITLE, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
NO EVENT SHALL ANALOG DEVICES, INC. OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, PUNITIVE OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, DAMAGES ARISING OUT OF CLAIMS OF INTELLECTUAL
PROPERTY RIGHTS INFRINGEMENT; 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 <float.h>
#include <math.h>

#include "inc/adi_sense_api.h"
#include "inc/adi_sense_platform.h"

#include "adi_sense_1000/ADISENSE1000_REGISTERS_typedefs.h"
#include "adi_sense_1000/ADISENSE1000_REGISTERS.h"

#include "crc16.h"

#define REG_READ_DELAY_USEC (20)

#define ADI_SENSE_CHANNEL_IS_ADC(c) \
    ((c) >= ADI_SENSE_CHANNEL_ID_CJC_0 && (c) <= ADI_SENSE_CHANNEL_ID_CURRENT_0)

#define ADI_SENSE_CHANNEL_IS_ADC_CJC(c) \
    ((c) >= ADI_SENSE_CHANNEL_ID_CJC_0 && (c) <= ADI_SENSE_CHANNEL_ID_CJC_1)

#define ADI_SENSE_CHANNEL_IS_ADC_SENSOR(c) \
    ((c) >= ADI_SENSE_CHANNEL_ID_SENSOR_0 && (c) <= ADI_SENSE_CHANNEL_ID_SENSOR_3)

#define ADI_SENSE_CHANNEL_IS_ADC_VOLTAGE(c) \
    ((c) == ADI_SENSE_CHANNEL_ID_VOLTAGE_0)

#define ADI_SENSE_CHANNEL_IS_ADC_CURRENT(c) \
    ((c) == ADI_SENSE_CHANNEL_ID_CURRENT_0)

typedef struct
{
    unsigned nDeviceIndex;
    ADI_SENSE_SPI_HANDLE hSpi;
    ADI_SENSE_GPIO_HANDLE hGpio;
} ADI_SENSE_DEVICE_CONTEXT;

static ADI_SENSE_DEVICE_CONTEXT gDeviceCtx[ADI_SENSE_MAX_DEVICES];

/*
 * Open an ADI Sense device instance.
 */
ADI_SENSE_RESULT adi_sense_Open(
    unsigned                   const nDeviceIndex,
    ADI_SENSE_CONNECTION     * const pConnectionInfo,
    ADI_SENSE_DEVICE_HANDLE  * const phDevice)
{
    ADI_SENSE_DEVICE_CONTEXT *pCtx;
    ADI_SENSE_RESULT eRet;

    if (nDeviceIndex >= ADI_SENSE_MAX_DEVICES)
        return ADI_SENSE_INVALID_DEVICE_NUM;

    pCtx = &gDeviceCtx[nDeviceIndex];
    pCtx->nDeviceIndex = nDeviceIndex;

    eRet = adi_sense_LogOpen();
    if (eRet != ADI_SENSE_SUCCESS)
        return eRet;

    eRet = adi_sense_GpioOpen(&pConnectionInfo->gpio, &pCtx->hGpio);
    if (eRet != ADI_SENSE_SUCCESS)
        return eRet;

    eRet = adi_sense_SpiOpen(&pConnectionInfo->spi, &pCtx->hSpi);
    if (eRet != ADI_SENSE_SUCCESS)
        return eRet;

    *phDevice = pCtx;
    return ADI_SENSE_SUCCESS;
}

/*
 * Get the current state of the specified GPIO input signal.
 */
ADI_SENSE_RESULT adi_sense_GetGpioState(
    ADI_SENSE_DEVICE_HANDLE   const hDevice,
    ADI_SENSE_GPIO_PIN        const ePinId,
    bool_t                  * const pbAsserted)
{
    ADI_SENSE_DEVICE_CONTEXT *pCtx = hDevice;

    return adi_sense_GpioGet(pCtx->hGpio, ePinId, pbAsserted);
}

/*
 * Register an application-defined callback function for GPIO interrupts.
 */
ADI_SENSE_RESULT adi_sense_RegisterGpioCallback(
    ADI_SENSE_DEVICE_HANDLE          const hDevice,
    ADI_SENSE_GPIO_PIN               const ePinId,
    ADI_SENSE_GPIO_CALLBACK          const callbackFunction,
    void                           * const pCallbackParam)
{
    ADI_SENSE_DEVICE_CONTEXT *pCtx = hDevice;

    if (callbackFunction)
    {
        return adi_sense_GpioIrqEnable(pCtx->hGpio, ePinId, callbackFunction,
                                       pCallbackParam);
    }
    else
    {
        return adi_sense_GpioIrqDisable(pCtx->hGpio, ePinId);
    }
}

/*
 * Reset the specified ADI Sense device.
 */
ADI_SENSE_RESULT adi_sense_Reset(
    ADI_SENSE_DEVICE_HANDLE    const hDevice)
{
    ADI_SENSE_DEVICE_CONTEXT *pCtx = hDevice;
    ADI_SENSE_RESULT eRet;

    /* Pulse the Reset GPIO pin low for a minimum of 4 microseconds */
    eRet = adi_sense_GpioSet(pCtx->hGpio, ADI_SENSE_GPIO_PIN_RESET, false);
    if (eRet != ADI_SENSE_SUCCESS)
        return eRet;

    adi_sense_TimeDelayUsec(4);

    eRet = adi_sense_GpioSet(pCtx->hGpio, ADI_SENSE_GPIO_PIN_RESET, true);
    if (eRet != ADI_SENSE_SUCCESS)
        return eRet;

    return ADI_SENSE_SUCCESS;
}


/*!
 * @brief Get general status of ADISense module.
 *
 * @param[in]
 * @param[out] pStatus : Pointer to CORE Status struct.
 *
 * @return Status
 *         - #ADI_SENSE_SUCCESS Call completed successfully.
 *         - #ADI_SENSE_FAILURE If status register read fails.
 *
 * @details Read the general status register for the ADISense
 *          module. Indicates Error, Alert conditions, data ready
 *          and command running.
 *
 */
ADI_SENSE_RESULT adi_sense_GetStatus(
    ADI_SENSE_DEVICE_HANDLE    const hDevice,
    ADI_SENSE_STATUS         * const pStatus)
{
    ADI_ADISENSE_CORE_Status_t statusReg;
    ADI_SENSE_RESULT eRet;

    eRet = adi_sense_ReadRegister(hDevice, REG_ADISENSE_CORE_STATUS,
                                 &statusReg, sizeof(statusReg));
    if (eRet)
    {
        return eRet;
    }

    pStatus->deviceStatus = 0;

    if (statusReg.Cmd_Running)
        pStatus->deviceStatus |= ADI_SENSE_DEVICE_STATUS_BUSY;

    if (statusReg.Drdy)
        pStatus->deviceStatus |= ADI_SENSE_DEVICE_STATUS_DATAREADY;

    if (statusReg.Error)
        pStatus->deviceStatus |= ADI_SENSE_DEVICE_STATUS_ERROR;

    if (statusReg.Alert_Limit)
    {
        pStatus->deviceStatus |= ADI_SENSE_DEVICE_STATUS_ALERT;

        ADI_ADISENSE_CORE_Channel_Alert_Status_t channelAlertStatusReg;
        eRet = adi_sense_ReadRegister(hDevice, REG_ADISENSE_CORE_CHANNEL_ALERT_STATUS,
                                     &channelAlertStatusReg,
                                     sizeof(channelAlertStatusReg));
        if (eRet)
        {
            return eRet;
        }

        for (unsigned i = 0; i < ADI_SENSE_MAX_CHANNELS; i++)
        {
            pStatus->channelAlerts[i] = 0;

            if (channelAlertStatusReg.VALUE16 & (1 << i))
            {
                ADI_ADISENSE_CORE_Alert_Detail_Ch_t alertDetailReg;
                eRet = adi_sense_ReadRegister(hDevice,
                                             REG_ADISENSE_CORE_ALERT_DETAIL_CHn(i),
                                             &alertDetailReg,
                                             sizeof(alertDetailReg));
                if (eRet)
                {
                    return eRet;
                }

                if (alertDetailReg.Time_Out)
                    pStatus->channelAlerts[i] |= ADI_SENSE_CHANNEL_ALERT_TIMEOUT;
                if (alertDetailReg.Under_Range)
                    pStatus->channelAlerts[i] |= ADI_SENSE_CHANNEL_ALERT_UNDER_RANGE;
                if (alertDetailReg.Over_Range)
                    pStatus->channelAlerts[i] |= ADI_SENSE_CHANNEL_ALERT_OVER_RANGE;
                if (alertDetailReg.Low_Limit)
                    pStatus->channelAlerts[i] |= ADI_SENSE_CHANNEL_ALERT_LOW_LIMIT;
                if (alertDetailReg.High_Limit)
                    pStatus->channelAlerts[i] |= ADI_SENSE_CHANNEL_ALERT_HIGH_LIMIT;
                if (alertDetailReg.Sensor_Open)
                    pStatus->channelAlerts[i] |= ADI_SENSE_CHANNEL_ALERT_SENSOR_OPEN;
                if (alertDetailReg.Ref_Detect)
                    pStatus->channelAlerts[i] |= ADI_SENSE_CHANNEL_ALERT_REF_DETECT;
            }
        }
    }

    /* TODO - fill diagnosticsStatus field */

    return ADI_SENSE_SUCCESS;
}

ADI_SENSE_RESULT adi_sense_GetCommandRunningState(
    ADI_SENSE_DEVICE_HANDLE hDevice,
    bool_t *pbCommandRunning)
{
    ADI_SENSE_RESULT eRet;
    ADI_ADISENSE_CORE_Status_t statusReg;

    eRet = adi_sense_ReadRegister(hDevice, REG_ADISENSE_CORE_STATUS,
                                 &statusReg, sizeof(statusReg));
    if (eRet)
        return eRet;

    *pbCommandRunning = statusReg.Cmd_Running;

    return ADI_SENSE_SUCCESS;
}


static ADI_SENSE_RESULT executeCommand(
    ADI_SENSE_DEVICE_HANDLE const hDevice,
    ADI_ADISENSE_CORE_Command_Special_Command const command,
    bool_t const bWaitForCompletion)
{
    ADI_ADISENSE_CORE_Command_t commandReg = {
        .Special_Command = command
    };
    bool_t bCommandRunning;
    ADI_SENSE_RESULT eRet;

    /*
     * Don't allow another command to be issued if one is already running, but
     * make an exception for ADISENSE_CORE_COMMAND_NOP which can be used to
     * request a running command to be stopped (e.g. continuous measurement)
     */
    if (command != ADISENSE_CORE_COMMAND_NOP)
    {
        eRet = adi_sense_GetCommandRunningState(hDevice, &bCommandRunning);
        if (eRet)
            return eRet;

        if (bCommandRunning)
            return ADI_SENSE_IN_USE;
    }

    eRet = adi_sense_WriteRegister(hDevice, REG_ADISENSE_CORE_COMMAND,
                                  &commandReg, sizeof(commandReg));
    if (eRet)
        return eRet;

    if (bWaitForCompletion)
    {
        do {
            eRet = adi_sense_GetCommandRunningState(hDevice, &bCommandRunning);
            if (eRet)
                return eRet;
        } while (bCommandRunning);
    }

    return ADI_SENSE_SUCCESS;
}

ADI_SENSE_RESULT adi_sense_ApplyConfigUpdates(
    ADI_SENSE_DEVICE_HANDLE const hDevice)
{
    return executeCommand(hDevice, ADISENSE_CORE_COMMAND_LATCH_CONFIG, true);
}

/*!
 * @brief Start a measurement cycle.
 *
 * @param[out]
 *
 * @return Status
 *         - #ADI_SENSE_SUCCESS Call completed successfully.
 *         - #ADI_SENSE_FAILURE
 *
 * @details Sends the latch config command. Configuration for channels in
 *          conversion cycle should be completed before this function.
 *          Channel enabled bit should be set before this function.
 *          Starts a conversion and configures the format of the sample.
 *
 */
ADI_SENSE_RESULT adi_sense_StartMeasurement(
    ADI_SENSE_DEVICE_HANDLE    const hDevice)
{
    return executeCommand(hDevice, ADISENSE_CORE_COMMAND_CONVERT_WITH_RAW, false);
}

/*
 * Store the configuration settings to persistent memory on the device.
 * No other command must be running when this is called.
 * Do not power down the device while this command is running.
 */
ADI_SENSE_RESULT adi_sense_SaveConfig(
    ADI_SENSE_DEVICE_HANDLE    const hDevice)
{
    return executeCommand(hDevice, ADISENSE_CORE_COMMAND_SAVE_CONFIG, true);
}

/*
 * Restore the configuration settings from persistent memory on the device.
 * No other command must be running when this is called.
 */
ADI_SENSE_RESULT adi_sense_RestoreConfig(
    ADI_SENSE_DEVICE_HANDLE    const hDevice)
{
    return executeCommand(hDevice, ADISENSE_CORE_COMMAND_LOAD_CONFIG, true);
}

/*
 * Stop the measurement cycles on the device.
 * To be used only if a measurement command is currently running. 
 */
ADI_SENSE_RESULT adi_sense_StopMeasurement(
    ADI_SENSE_DEVICE_HANDLE    const hDevice)
{
    return executeCommand(hDevice, ADISENSE_CORE_COMMAND_NOP, true);
}

/*
 * Run built-in diagnostic checks on the device.
 * Diagnostics are executed according to the current applied settings.
 * No other command must be running when this is called.
 */
ADI_SENSE_RESULT adi_sense_RunDiagnostics(
    ADI_SENSE_DEVICE_HANDLE    const hDevice)
{
    return executeCommand(hDevice, ADISENSE_CORE_COMMAND_RUN_DIAGNOSTICS, true);
}

/*
 * Read a set of data samples from the device.
 * This may be called at any time.
 */
ADI_SENSE_RESULT adi_sense_GetData(
    ADI_SENSE_DEVICE_HANDLE    const hDevice,
    ADI_SENSE_DATA_SAMPLE    * const pSamples,
    uint32_t                   const nRequested,
    uint32_t                 * const pnReturned)
{
    ADI_SENSE_DEVICE_CONTEXT *pCtx = hDevice;
    uint16_t command = REG_ADISENSE_CORE_DATA_FIFO;
    uint8_t commandData[sizeof(command)] = {
        command >> 8,
        command & 0xFF
    };
    unsigned nValidSamples = 0;
    ADI_SENSE_RESULT eRet;

    eRet = adi_sense_SpiTransfer(pCtx->hSpi, commandData, NULL,
                                 sizeof(command), false);
    if (eRet)
        return eRet;

    adi_sense_TimeDelayUsec(REG_READ_DELAY_USEC);

    for (unsigned i = 0; i < nRequested; i++)
    {
        ADI_ADISENSE_CORE_Data_FIFO_t dataFifoReg;
        bool_t bHoldCs = true;

        /* Keep the CS signal asserted for all but the last sample */
        if ((i + 1) == nRequested)
            bHoldCs = false;

        eRet = adi_sense_SpiTransfer(pCtx->hSpi, NULL, &dataFifoReg,
                                     sizeof(dataFifoReg), bHoldCs);
        if (eRet)
            return eRet;

        if (! dataFifoReg.Ch_Valid)
        {
            ADI_SENSE_LOG_WARN("Read invalid data sample");
            continue;
        }

        ADI_SENSE_DATA_SAMPLE *pSample = &pSamples[nValidSamples];

        pSample->status = 0;
        if (dataFifoReg.Ch_Error)
            pSample->status |= ADI_SENSE_DEVICE_STATUS_ERROR;
        if (dataFifoReg.Ch_Alert)
            pSample->status |= ADI_SENSE_DEVICE_STATUS_ALERT;

        if (dataFifoReg.Ch_Raw)
            pSample->rawValue = dataFifoReg.Raw_Sample;
        else
            pSample->rawValue = 0;

        pSample->channelId = dataFifoReg.Channel_ID;
        pSample->processedValue = dataFifoReg.Sensor_Result;

        nValidSamples++;
    }
    *pnReturned = nValidSamples;

    return ADI_SENSE_SUCCESS;
}

/*
 * Close the given ADI Sense device.
 */
ADI_SENSE_RESULT adi_sense_Close(
    ADI_SENSE_DEVICE_HANDLE    const hDevice)
{
    ADI_SENSE_DEVICE_CONTEXT *pCtx = hDevice;

    adi_sense_GpioClose(pCtx->hGpio);
    adi_sense_SpiClose(pCtx->hSpi);

    return ADI_SENSE_SUCCESS;
}

ADI_SENSE_RESULT adi_sense_WriteRegister(
    ADI_SENSE_DEVICE_HANDLE hDevice,
    uint16_t nAddress,
    void *pData,
    unsigned nLength)
{
    ADI_SENSE_RESULT eRet;
    ADI_SENSE_DEVICE_CONTEXT *pCtx = hDevice;
    uint16_t command = 0x8000 | (nAddress & 0x7FFF);
    uint8_t commandData[sizeof(command)] = {
        command >> 8,
        command & 0xFF
    };

    eRet = adi_sense_SpiTransfer(pCtx->hSpi, commandData, NULL,
                                 sizeof(commandData), false);
    if (eRet)
    {
        return eRet;
    }

    return adi_sense_SpiTransfer(pCtx->hSpi, pData, NULL, nLength, false);
}

ADI_SENSE_RESULT adi_sense_ReadRegister(
    ADI_SENSE_DEVICE_HANDLE hDevice,
    uint16_t nAddress,
    void *pData,
    unsigned nLength)
{
    ADI_SENSE_RESULT eRet;
    ADI_SENSE_DEVICE_CONTEXT *pCtx = hDevice;
    uint16_t command = nAddress & 0x7FFF;
    uint8_t commandData[sizeof(command)] = {
        command >> 8,
        command & 0xFF
    };

    eRet = adi_sense_SpiTransfer(pCtx->hSpi, commandData, NULL,
                                 sizeof(command), false);
    if (eRet)
    {
        return eRet;
    }

    adi_sense_TimeDelayUsec(REG_READ_DELAY_USEC);

    eRet = adi_sense_SpiTransfer(pCtx->hSpi, NULL, pData, nLength, false);
    if (eRet)
    {
        return eRet;
    }

    return ADI_SENSE_SUCCESS;
}

ADI_SENSE_RESULT adi_sense_GetDeviceReadyState(
    ADI_SENSE_DEVICE_HANDLE   const hDevice,
    bool_t                  * const bReady)
{
    ADI_ADISENSE_SPI_Chip_Type_t chipTypeReg;
    ADI_SENSE_RESULT eRet;

    eRet = adi_sense_ReadRegister(hDevice, REG_ADISENSE_SPI_CHIP_TYPE, &chipTypeReg,
                                  sizeof(chipTypeReg));
    if (eRet)
    {
        ADI_SENSE_LOG_ERROR("Failed to read chip-type register");
        return eRet;
    }

    /* If we read this register successfully, assume the device is ready */
    *bReady = (chipTypeReg.VALUE8 == REG_ADISENSE_SPI_CHIP_TYPE_RESET);

    return ADI_SENSE_SUCCESS;
}

ADI_SENSE_RESULT adi_sense_GetDataPublishingInfo(
    ADI_SENSE_DEVICE_HANDLE       const hDevice,
    ADI_SENSE_OPERATING_MODE    * const peOperatingMode,
    ADI_SENSE_DATA_PUBLISH_MODE * const peDataPublishMode,
    uint32_t                    * const pnSamplesPerDataready,
    uint32_t                    * const pnSamplesPerCycle)
{
    ADI_ADISENSE_CORE_Channel_Count_t channelCountReg;
    ADI_ADISENSE_CORE_Mode_t modeReg;
    ADI_SENSE_RESULT eRet;

    unsigned nChannelsEnabled = 0;
    unsigned nSamplesPerCycle = 0;
    for (ADI_SENSE_CHANNEL_ID chId = 0; chId < ADI_SENSE_MAX_CHANNELS; chId++)
    {
        eRet = adi_sense_ReadRegister(hDevice, REG_ADISENSE_CORE_CHANNEL_COUNTn(chId),
                                      &channelCountReg,
                                      sizeof(channelCountReg));
        if (eRet)
            return eRet;

        if (channelCountReg.Channel_Enable)
        {
            nChannelsEnabled++;
            nSamplesPerCycle += (channelCountReg.Channel_Count + 1);
        }
    }

    if (nChannelsEnabled == 0)
    {
        *pnSamplesPerDataready = 0;
        *pnSamplesPerCycle = 0;
        return ADI_SENSE_SUCCESS;
    }

    eRet = adi_sense_ReadRegister(hDevice, REG_ADISENSE_CORE_MODE,
                                  &modeReg, sizeof(modeReg));
    if (eRet)
        return eRet;

    *pnSamplesPerCycle = nSamplesPerCycle;
    if (modeReg.Drdy_Mode == ADISENSE_CORE_MODE_DRDY_PER_CONVERSION)
    {
        *pnSamplesPerDataready = 1;
    }
    else if (modeReg.Drdy_Mode == ADISENSE_CORE_MODE_DRDY_PER_CYCLE)
    {
        *pnSamplesPerDataready = nSamplesPerCycle;
    }
    else
    {
        ADI_ADISENSE_CORE_Fifo_Num_Cycles_t fifoNumCyclesReg;

        eRet = adi_sense_ReadRegister(hDevice, REG_ADISENSE_CORE_FIFO_NUM_CYCLES,
                                      &fifoNumCyclesReg,
                                      sizeof(fifoNumCyclesReg));
        if (eRet)
            return eRet;

        *pnSamplesPerDataready =
            nSamplesPerCycle * fifoNumCyclesReg.Fifo_Num_Cycles;
    }

    if (modeReg.Conversion_Mode == ADISENSE_CORE_MODE_SINGLECYCLE)
        *peOperatingMode = ADI_SENSE_OPERATING_MODE_SINGLECYCLE;
    else if (modeReg.Conversion_Mode == ADISENSE_CORE_MODE_MULTICYCLE)
        *peOperatingMode = ADI_SENSE_OPERATING_MODE_MULTICYCLE;
    else
        *peOperatingMode = ADI_SENSE_OPERATING_MODE_CONTINUOUS;

    if (modeReg.Drdy_Mode == ADISENSE_CORE_MODE_DRDY_PER_CONVERSION)
        *peDataPublishMode = ADI_SENSE_DATA_PUBLISH_PER_CONVERSION;
    else if (modeReg.Drdy_Mode == ADISENSE_CORE_MODE_DRDY_PER_CYCLE)
        *peDataPublishMode = ADI_SENSE_DATA_PUBLISH_PER_CYCLE;
    else
        *peDataPublishMode = ADI_SENSE_DATA_PUBLISH_PER_MULTICYCLE_BURST;

    return ADI_SENSE_SUCCESS;
}

ADI_SENSE_RESULT adi_sense_GetProductID(
    ADI_SENSE_DEVICE_HANDLE hDevice,
    ADI_SENSE_PRODUCT_ID *pProductId)
{
    ADI_ADISENSE_SPI_Product_ID_L_t productIdLoReg;
    ADI_ADISENSE_SPI_Product_ID_H_t productIdHiReg;
    ADI_SENSE_RESULT eRet;

    eRet = adi_sense_ReadRegister(hDevice, REG_ADISENSE_SPI_PRODUCT_ID_L,
                                 &productIdLoReg, sizeof(productIdLoReg));
    if (eRet)
        return eRet;

    eRet = adi_sense_ReadRegister(hDevice, REG_ADISENSE_SPI_PRODUCT_ID_H,
                                 &productIdHiReg, sizeof(productIdHiReg));
    if (eRet)
        return eRet;

    *pProductId = (productIdHiReg.VALUE8 << 8) | productIdLoReg.VALUE8;
    return ADI_SENSE_SUCCESS;
}

static ADI_SENSE_RESULT adi_sense_SetPowerMode(
    ADI_SENSE_DEVICE_HANDLE hDevice,
    ADI_SENSE_POWER_MODE powerMode)
{
    ADI_ADISENSE_CORE_Power_Config_t powerConfigReg;

    if (powerMode == ADI_SENSE_POWER_MODE_LOW)
    {
        powerConfigReg.Power_Mode_ADC = ADISENSE_CORE_POWER_CONFIG_ADC_LOW_POWER;
        /* TODO - we need an enum in the register map for the MCU power modes */
        powerConfigReg.Power_Mode_MCU = 0x0;
    }
    else if (powerMode == ADI_SENSE_POWER_MODE_MID)
    {
        powerConfigReg.Power_Mode_ADC = ADISENSE_CORE_POWER_CONFIG_ADC_MID_POWER;
        powerConfigReg.Power_Mode_MCU = 0x1;
    }
    else if (powerMode == ADI_SENSE_POWER_MODE_FULL)
    {
        powerConfigReg.Power_Mode_ADC = ADISENSE_CORE_POWER_CONFIG_ADC_FULL_POWER;
        powerConfigReg.Power_Mode_MCU = 0x2;
    }
    else
    {
        return ADI_SENSE_INVALID_PARAM;
    }

    return adi_sense_WriteRegister(hDevice, REG_ADISENSE_CORE_POWER_CONFIG,
                                  &powerConfigReg, sizeof(powerConfigReg));
}

static ADI_SENSE_RESULT adi_sense_SetVddVoltage(
    ADI_SENSE_DEVICE_HANDLE hDevice,
    float32_t vddVoltage)
{
    ADI_ADISENSE_CORE_AVDD_Voltage_t avddVoltageReg = {
        .Avdd_Voltage = vddVoltage
    };

    return adi_sense_WriteRegister(hDevice, REG_ADISENSE_CORE_AVDD_VOLTAGE,
                                  &avddVoltageReg, sizeof(avddVoltageReg));
}

ADI_SENSE_RESULT adi_sense_SetPowerConfig(
    ADI_SENSE_DEVICE_HANDLE hDevice,
    ADI_SENSE_POWER_CONFIG *pPowerConfig)
{
    ADI_SENSE_RESULT eRet;

    eRet = adi_sense_SetPowerMode(hDevice, pPowerConfig->powerMode);
    if (eRet)
    {
        return eRet;
    }

    return adi_sense_SetVddVoltage(hDevice, pPowerConfig->supplyVoltage);
}

static ADI_SENSE_RESULT adi_sense_SetMode(
    ADI_SENSE_DEVICE_HANDLE hDevice,
    ADI_SENSE_OPERATING_MODE eOperatingMode,
    ADI_SENSE_DATA_PUBLISH_MODE ePublishMode)
{
    ADI_ADISENSE_CORE_Mode_t modeReg = { .VALUE8 = 0 };

    if (eOperatingMode == ADI_SENSE_OPERATING_MODE_SINGLECYCLE)
    {
        modeReg.Conversion_Mode = ADISENSE_CORE_MODE_SINGLECYCLE;
    }
    else if (eOperatingMode == ADI_SENSE_OPERATING_MODE_CONTINUOUS)
    {
        modeReg.Conversion_Mode = ADISENSE_CORE_MODE_CONTINUOUS;
    }
    else if (eOperatingMode == ADI_SENSE_OPERATING_MODE_MULTICYCLE)
    {
        modeReg.Conversion_Mode = ADISENSE_CORE_MODE_MULTICYCLE;
    }
    else
    {
        return ADI_SENSE_INVALID_PARAM;
    }

    if (ePublishMode == ADI_SENSE_DATA_PUBLISH_PER_CONVERSION)
    {
        modeReg.Drdy_Mode = ADISENSE_CORE_MODE_DRDY_PER_CONVERSION;
    }
    else if (ePublishMode == ADI_SENSE_DATA_PUBLISH_PER_CYCLE)
    {
        modeReg.Drdy_Mode = ADISENSE_CORE_MODE_DRDY_PER_CYCLE;
    }
    else if (ePublishMode == ADI_SENSE_DATA_PUBLISH_PER_MULTICYCLE_BURST)
    {
        if (eOperatingMode != ADI_SENSE_OPERATING_MODE_MULTICYCLE)
        {
            return ADI_SENSE_INVALID_PARAM;
        }
        else
        {
            modeReg.Drdy_Mode = ADISENSE_CORE_MODE_DRDY_PER_FIFO_FILL;
        }
    }
    else
    {
        return ADI_SENSE_INVALID_PARAM;
    }

    return adi_sense_WriteRegister(hDevice, REG_ADISENSE_CORE_MODE,
                                  &modeReg, sizeof(modeReg));
}

ADI_SENSE_RESULT adi_sense_SetCycleInterval(
    ADI_SENSE_DEVICE_HANDLE hDevice,
    uint32_t nCycleInterval)
{
    ADI_ADISENSE_CORE_Cycle_Control_t cycleControlReg;

    if (nCycleInterval < (1 << 12))
    {
        cycleControlReg.Cycle_Time = nCycleInterval;
        cycleControlReg.Cycle_Time_Units = ADISENSE_CORE_CYCLE_CONTROL_MICROSECONDS;
    }
    else if (nCycleInterval < (1000 * (1 << 12)))
    {
        cycleControlReg.Cycle_Time = nCycleInterval / 1000;
        cycleControlReg.Cycle_Time_Units = ADISENSE_CORE_CYCLE_CONTROL_MILLISECONDS;
    }
    else
    {
        cycleControlReg.Cycle_Time = nCycleInterval / 1000000;
        cycleControlReg.Cycle_Time_Units = ADISENSE_CORE_CYCLE_CONTROL_SECONDS;
    }

    return adi_sense_WriteRegister(hDevice, REG_ADISENSE_CORE_CYCLE_CONTROL,
                                  &cycleControlReg, sizeof(cycleControlReg));
}

static ADI_SENSE_RESULT adi_sense_SetMultiCycleConfig(
    ADI_SENSE_DEVICE_HANDLE hDevice,
    ADI_SENSE_MULTICYCLE_CONFIG *pMultiCycleConfig)
{
    ADI_ADISENSE_CORE_Fifo_Num_Cycles_t fifoNumCyclesReg = {
        .VALUE8 = REG_ADISENSE_CORE_FIFO_NUM_CYCLES_RESET
    };
    ADI_ADISENSE_CORE_Multi_Cycle_Repeat_Interval_t multiCycleIntervalReg = {
        .VALUE32 = REG_ADISENSE_CORE_MULTI_CYCLE_REPEAT_INTERVAL_RESET
    };
    ADI_SENSE_RESULT eRet;

    fifoNumCyclesReg.Fifo_Num_Cycles = pMultiCycleConfig->cyclesPerBurst;
    eRet = adi_sense_WriteRegister(hDevice, REG_ADISENSE_CORE_FIFO_NUM_CYCLES,
                                   &fifoNumCyclesReg,
                                   sizeof(fifoNumCyclesReg));
    if (eRet)
    {
        return eRet;
    }

    multiCycleIntervalReg.Multi_Cycle_Repeat_Interval = pMultiCycleConfig->burstInterval;
    return adi_sense_WriteRegister(hDevice, REG_ADISENSE_CORE_MULTI_CYCLE_REPEAT_INTERVAL,
                                   &multiCycleIntervalReg,
                                   sizeof(multiCycleIntervalReg));
}

static ADI_SENSE_RESULT adi_sense_SetExternalReferenceValues(
    ADI_SENSE_DEVICE_HANDLE hDevice,
    float32_t externalRef1Value,
    float32_t externalRef2Value)
{
    ADI_SENSE_RESULT eRet;
    ADI_ADISENSE_CORE_External_Reference1_t externalReference1Reg = {
        .Ext_Refin1_Value = externalRef1Value
    };
    ADI_ADISENSE_CORE_External_Reference2_t externalReference2Reg = {
        .Ext_Refin2_Value = externalRef2Value
    };

    eRet = adi_sense_WriteRegister(hDevice, REG_ADISENSE_CORE_EXTERNAL_REFERENCE1,
                                   &externalReference1Reg,
                                   sizeof(externalReference1Reg));
    if (eRet)
    {
        return eRet;
    }

    return adi_sense_WriteRegister(hDevice, REG_ADISENSE_CORE_EXTERNAL_REFERENCE2,
                                   &externalReference2Reg,
                                   sizeof(externalReference2Reg));
}

ADI_SENSE_RESULT adi_sense_SetMeasurementConfig(
    ADI_SENSE_DEVICE_HANDLE hDevice,
    ADI_SENSE_MEASUREMENT_CONFIG *pMeasConfig)
{
    ADI_SENSE_RESULT eRet;

    eRet = adi_sense_SetMode(hDevice,
                            pMeasConfig->operatingMode,
                            pMeasConfig->dataPublishMode);
    if (eRet)
    {
        return eRet;
    }

    if (pMeasConfig->operatingMode != ADI_SENSE_OPERATING_MODE_SINGLECYCLE)
    {
        eRet = adi_sense_SetCycleInterval(hDevice, pMeasConfig->cycleInterval);
        if (eRet)
        {
            return eRet;
        }
    }

    if (pMeasConfig->operatingMode == ADI_SENSE_OPERATING_MODE_MULTICYCLE)
    {
        eRet = adi_sense_SetMultiCycleConfig(hDevice,
                                            &pMeasConfig->multiCycleConfig);
        if (eRet)
        {
            return eRet;
        }
    }

    return adi_sense_SetExternalReferenceValues(hDevice,
                                                pMeasConfig->externalRef1Value,
                                                pMeasConfig->externalRef2Value);
}

ADI_SENSE_RESULT adi_sense_SetDiagnosticsConfig(
    ADI_SENSE_DEVICE_HANDLE hDevice,
    ADI_SENSE_DIAGNOSTICS_CONFIG *pDiagnosticsConfig)
{
    ADI_ADISENSE_CORE_Diagnostics_Control_t diagnosticsControlReg = {
        .VALUE16 = REG_ADISENSE_CORE_DIAGNOSTICS_CONTROL_RESET
    };

    if (pDiagnosticsConfig->enableGlobalDiag)
        diagnosticsControlReg.Diag_Global_En = 1;

    if (pDiagnosticsConfig->enableMeasurementDiag)
    {
        diagnosticsControlReg.Diag_Meas_En = 1;

        switch (pDiagnosticsConfig->openCircuitDetectionFreq)
        {
        case ADI_SENSE_OCD_DISABLED:
            diagnosticsControlReg.Diag_OCD_Freq = ADISENSE_CORE_DIAGNOSTICS_CONTROL_OCD_OFF;
            break;
        case ADI_SENSE_OCD_PER_CYCLE:
            diagnosticsControlReg.Diag_OCD_Freq = ADISENSE_CORE_DIAGNOSTICS_CONTROL_OCD_PER_1_CYCLE;
            break;
        case ADI_SENSE_OCD_PER_100_CYCLES:
            diagnosticsControlReg.Diag_OCD_Freq = ADISENSE_CORE_DIAGNOSTICS_CONTROL_OCD_PER_100_CYCLES;
            break;
        case ADI_SENSE_OCD_PER_1000_CYCLES:
            diagnosticsControlReg.Diag_OCD_Freq = ADISENSE_CORE_DIAGNOSTICS_CONTROL_OCD_PER_1000_CYCLES;
            break;
        default:
            return ADI_SENSE_INVALID_PARAM;
        }
    }

    /* TODO - what should be set in the REG_ADISENSE_CORE_DIAGNOSTICS_EXTRA register? */

    return adi_sense_WriteRegister(hDevice, REG_ADISENSE_CORE_DIAGNOSTICS_CONTROL,
                                  &diagnosticsControlReg,
                                  sizeof(diagnosticsControlReg));
}

ADI_SENSE_RESULT adi_sense_SetChannelCount(
    ADI_SENSE_DEVICE_HANDLE hDevice,
    ADI_SENSE_CHANNEL_ID eChannelId,
    uint32_t nMeasurementsPerCycle)
{
    ADI_ADISENSE_CORE_Channel_Count_t channelCountReg = {
        .VALUE8 = REG_ADISENSE_CORE_CHANNEL_COUNTn_RESET
    };

    if (nMeasurementsPerCycle > 0)
    {
        channelCountReg.Channel_Enable = 1;
        channelCountReg.Channel_Count = nMeasurementsPerCycle - 1;
    }
    else
    {
        channelCountReg.Channel_Enable = 0;
    }

    return adi_sense_WriteRegister(hDevice, REG_ADISENSE_CORE_CHANNEL_COUNTn(eChannelId),
                                  &channelCountReg, sizeof(channelCountReg));
}

static ADI_SENSE_RESULT adi_sense_SetChannelAdcSensorType(
    ADI_SENSE_DEVICE_HANDLE hDevice,
    ADI_SENSE_CHANNEL_ID eChannelId,
    ADI_SENSE_ADC_SENSOR_TYPE sensorType)
{
    ADI_ADISENSE_CORE_Sensor_Type_t sensorTypeReg = {
        .VALUE16 = REG_ADISENSE_CORE_SENSOR_TYPEn_RESET
    };

    /* Ensure that the sensor type is valid for this channel */
    switch(sensorType)
    {
    case ADI_SENSE_ADC_SENSOR_THERMOCOUPLE_J:
    case ADI_SENSE_ADC_SENSOR_THERMOCOUPLE_K:
    case ADI_SENSE_ADC_SENSOR_THERMOCOUPLE_T:
    case ADI_SENSE_ADC_SENSOR_THERMOCOUPLE_CUSTOM:
    case ADI_SENSE_ADC_SENSOR_RTD_3WIRE_PT100:
    case ADI_SENSE_ADC_SENSOR_RTD_3WIRE_PT1000:
    case ADI_SENSE_ADC_SENSOR_RTD_3WIRE_CUSTOM:
    case ADI_SENSE_ADC_SENSOR_RTD_4WIRE_PT100:
    case ADI_SENSE_ADC_SENSOR_RTD_4WIRE_PT1000:
    case ADI_SENSE_ADC_SENSOR_RTD_4WIRE_CUSTOM:
    case ADI_SENSE_ADC_SENSOR_BRIDGE_4WIRE_TRANSDUCER:
    case ADI_SENSE_ADC_SENSOR_BRIDGE_4WIRE_CUSTOM:
    case ADI_SENSE_ADC_SENSOR_BRIDGE_6WIRE_TRANSDUCER:
    case ADI_SENSE_ADC_SENSOR_BRIDGE_6WIRE_CUSTOM:
    case ADI_SENSE_ADC_SENSOR_THERMISTOR_10K_NTC:
    case ADI_SENSE_ADC_SENSOR_THERMISTOR_CUSTOM:
        if (! ADI_SENSE_CHANNEL_IS_ADC_SENSOR(eChannelId))
            return ADI_SENSE_INVALID_PARAM;
        break;
    case ADI_SENSE_ADC_SENSOR_RTD_2WIRE_PT100:
    case ADI_SENSE_ADC_SENSOR_RTD_2WIRE_PT1000:
    case ADI_SENSE_ADC_SENSOR_RTD_2WIRE_CUSTOM:
        if (! ADI_SENSE_CHANNEL_IS_ADC_SENSOR(eChannelId) &&
            ! ADI_SENSE_CHANNEL_IS_ADC_CJC(eChannelId))
            return ADI_SENSE_INVALID_PARAM;
        break;
    case ADI_SENSE_ADC_SENSOR_VOLTAGE_PRESSURE_1:
    case ADI_SENSE_ADC_SENSOR_VOLTAGE_PRESSURE_2:
    case ADI_SENSE_ADC_SENSOR_VOLTAGE_PRESSURE_CUSTOM:
        if (! ADI_SENSE_CHANNEL_IS_ADC_VOLTAGE(eChannelId))
            return ADI_SENSE_INVALID_PARAM;
        break;
    case ADI_SENSE_ADC_SENSOR_CURRENT_PRESSURE_1:
    case ADI_SENSE_ADC_SENSOR_CURRENT_PRESSURE_CUSTOM:
        if (! ADI_SENSE_CHANNEL_IS_ADC_CURRENT(eChannelId))
            return ADI_SENSE_INVALID_PARAM;
        break;
    default:
        return ADI_SENSE_INVALID_PARAM;
    }

    switch(sensorType)
    {
    case ADI_SENSE_ADC_SENSOR_THERMOCOUPLE_T:
        sensorTypeReg.Sensor_Type = ADISENSE_CORE_SENSOR_TYPE_SENSOR_THERMOCOUPLE_T_DEF_L1;
        break;
    case ADI_SENSE_ADC_SENSOR_THERMOCOUPLE_J:
        sensorTypeReg.Sensor_Type = ADISENSE_CORE_SENSOR_TYPE_SENSOR_THERMOCOUPLE_J_DEF_L1;
        break;
    case ADI_SENSE_ADC_SENSOR_THERMOCOUPLE_K:
        sensorTypeReg.Sensor_Type = ADISENSE_CORE_SENSOR_TYPE_SENSOR_THERMOCOUPLE_K_DEF_L1;
        break;
    case ADI_SENSE_ADC_SENSOR_RTD_2WIRE_PT100:
        sensorTypeReg.Sensor_Type = ADISENSE_CORE_SENSOR_TYPE_SENSOR_RTD_2W_PT100_DEF_L1;
        break;
    case ADI_SENSE_ADC_SENSOR_RTD_2WIRE_PT1000:
        sensorTypeReg.Sensor_Type = ADISENSE_CORE_SENSOR_TYPE_SENSOR_RTD_2W_PT1000_DEF_L1;
        break;
    case ADI_SENSE_ADC_SENSOR_RTD_3WIRE_PT100:
        sensorTypeReg.Sensor_Type = ADISENSE_CORE_SENSOR_TYPE_SENSOR_RTD_3W_PT100_DEF_L1;
        break;
    case ADI_SENSE_ADC_SENSOR_RTD_3WIRE_PT1000:
        sensorTypeReg.Sensor_Type = ADISENSE_CORE_SENSOR_TYPE_SENSOR_RTD_3W_PT1000_DEF_L1;
        break;
    case ADI_SENSE_ADC_SENSOR_RTD_4WIRE_PT100:
        sensorTypeReg.Sensor_Type = ADISENSE_CORE_SENSOR_TYPE_SENSOR_RTD_4W_PT100_DEF_L1;
        break;
    case ADI_SENSE_ADC_SENSOR_RTD_4WIRE_PT1000:
        sensorTypeReg.Sensor_Type = ADISENSE_CORE_SENSOR_TYPE_SENSOR_RTD_4W_PT1000_DEF_L1;
        break;
    case ADI_SENSE_ADC_SENSOR_BRIDGE_4WIRE_TRANSDUCER:
        sensorTypeReg.Sensor_Type = ADISENSE_CORE_SENSOR_TYPE_SENSOR_BRIDGE_4W_1_DEF_L2;
        break;
    case ADI_SENSE_ADC_SENSOR_BRIDGE_6WIRE_TRANSDUCER:
        sensorTypeReg.Sensor_Type = ADISENSE_CORE_SENSOR_TYPE_SENSOR_BRIDGE_6W_1_DEF_L2;
        break;
    case ADI_SENSE_ADC_SENSOR_THERMISTOR_10K_NTC:
        sensorTypeReg.Sensor_Type = ADISENSE_CORE_SENSOR_TYPE_SENSOR_THERMISTOR_A_10K_DEF_L1;
        break;
    case ADI_SENSE_ADC_SENSOR_VOLTAGE_PRESSURE_1:
        sensorTypeReg.Sensor_Type = ADISENSE_CORE_SENSOR_TYPE_SENSOR_VOLTAGE_PRESSURE_HONEYWELL_TRUSTABILITY;
        break;
    case ADI_SENSE_ADC_SENSOR_VOLTAGE_PRESSURE_2:
        sensorTypeReg.Sensor_Type = ADISENSE_CORE_SENSOR_TYPE_SENSOR_VOLTAGE_PRESSURE_AMPHENOL_NPA300X;
        break;
    case ADI_SENSE_ADC_SENSOR_CURRENT_PRESSURE_1:
        sensorTypeReg.Sensor_Type = ADISENSE_CORE_SENSOR_TYPE_SENSOR_CURRENT_PRESSURE_HONEYWELL_PX2;
        break;
    default:
        /* TODO - add support for custom sensor types? */
        return ADI_SENSE_INVALID_PARAM;
    }

    return adi_sense_WriteRegister(hDevice, REG_ADISENSE_CORE_SENSOR_TYPEn(eChannelId),
                                  &sensorTypeReg, sizeof(sensorTypeReg));
}

static ADI_SENSE_RESULT adi_sense_SetChannelAdcSensorDetails(
    ADI_SENSE_DEVICE_HANDLE hDevice,
    ADI_SENSE_CHANNEL_ID eChannelId,
    ADI_SENSE_CHANNEL_CONFIG *pChannelConfig)
/*
 * TODO - it would be nice if the general- vs. ADC-specific sensor details could be split into separate registers
 * General details:
 * - Measurement_Units
 * - Compensation_Channel
 * - CJC_Publish (if "CJC" was removed from the name)
 * ADC-specific details:
 * - PGA_Gain
 * - Reference_Select
 * - Reference_Buffer_Disable
 * - Vbias
 */
{
    ADI_SENSE_ADC_CHANNEL_CONFIG *pAdcChannelConfig = &pChannelConfig->adcChannelConfig;
    ADI_SENSE_ADC_REFERENCE_CONFIG *pRefConfig = &pAdcChannelConfig->reference;
    ADI_ADISENSE_CORE_Sensor_Details_t sensorDetailsReg = {
        .VALUE32 = REG_ADISENSE_CORE_SENSOR_DETAILSn_RESET
    };

/* TODO - measurementType will most likely be replaced with a unit-conversion option */
#if 0
    switch(pChannelConfig->eMeasurementType)
    {
    case ADI_SENSE_MEASUREMENT_TEMPERATURE_CELCIUS:
        sensorDetailsReg.Measurement_Units = CORE_SENSOR_DETAILS_UNITS_DEGC;
        break;
    case ADI_SENSE_MEASUREMENT_TEMPERATURE_FAHRENHEIT:
        sensorDetailsReg.Measurement_Units = CORE_SENSOR_DETAILS_UNITS_DEGF;
        break;
        /* TODO - register map needs to define measurement units for the following options:  */
    case ADI_SENSE_MEASUREMENT_HUMIDITY_RH:
    case ADI_SENSE_MEASUREMENT_PRESSURE_BAR:
    case ADI_SENSE_MEASUREMENT_PRESSURE_PSI:
    case ADI_SENSE_MEASUREMENT_ACCELERATION_G:
    default:
        return ADI_SENSE_INVALID_PARAM;
    }
#endif

    sensorDetailsReg.Compensation_Channel = pChannelConfig->compensationChannel;

    switch(pRefConfig->type)
    {
    case ADI_SENSE_ADC_REFERENCE_RESISTOR_INTERNAL_1:
        sensorDetailsReg.Reference_Select = ADISENSE_CORE_SENSOR_DETAILS_REF_RINT1;
        break;
    case ADI_SENSE_ADC_REFERENCE_RESISTOR_INTERNAL_2:
        sensorDetailsReg.Reference_Select = ADISENSE_CORE_SENSOR_DETAILS_REF_RINT2;
        break;
    case ADI_SENSE_ADC_REFERENCE_VOLTAGE_INTERNAL:
        sensorDetailsReg.Reference_Select = ADISENSE_CORE_SENSOR_DETAILS_REF_INT;
        break;
    case ADI_SENSE_ADC_REFERENCE_VOLTAGE_AVDD:
        sensorDetailsReg.Reference_Select = ADISENSE_CORE_SENSOR_DETAILS_REF_AVDD;
        break;
    case ADI_SENSE_ADC_REFERENCE_RESISTOR_EXTERNAL_1:
        sensorDetailsReg.Reference_Select = ADISENSE_CORE_SENSOR_DETAILS_REF_REXT1;
        break;
    case ADI_SENSE_ADC_REFERENCE_RESISTOR_EXTERNAL_2:
        sensorDetailsReg.Reference_Select = ADISENSE_CORE_SENSOR_DETAILS_REF_REXT2;
        break;
    case ADI_SENSE_ADC_REFERENCE_VOLTAGE_EXTERNAL_1:
        sensorDetailsReg.Reference_Select = ADISENSE_CORE_SENSOR_DETAILS_REF_VEXT1;
        break;
    case ADI_SENSE_ADC_REFERENCE_VOLTAGE_EXTERNAL_2:
        sensorDetailsReg.Reference_Select = ADISENSE_CORE_SENSOR_DETAILS_REF_VEXT2;
        break;
    default:
        return ADI_SENSE_INVALID_PARAM;
    }

    switch(pAdcChannelConfig->gain)
    {
    case ADI_SENSE_ADC_GAIN_1X:
        /* TODO - add enum for gain options in register map headers */ 
        sensorDetailsReg.PGA_Gain = 0;
        break;
    case ADI_SENSE_ADC_GAIN_2X:
        sensorDetailsReg.PGA_Gain = 1;
        break;
    case ADI_SENSE_ADC_GAIN_4X:
        sensorDetailsReg.PGA_Gain = 2;
        break;
    case ADI_SENSE_ADC_GAIN_8X:
        sensorDetailsReg.PGA_Gain = 3;
        break;
    case ADI_SENSE_ADC_GAIN_16X:
        sensorDetailsReg.PGA_Gain = 4;
        break;
    case ADI_SENSE_ADC_GAIN_32X:
        sensorDetailsReg.PGA_Gain = 5;
        break;
    case ADI_SENSE_ADC_GAIN_64X:
        sensorDetailsReg.PGA_Gain = 6;
        break;
    case ADI_SENSE_ADC_GAIN_128X:
        sensorDetailsReg.PGA_Gain = 7;
        break;
    default:
        return ADI_SENSE_INVALID_PARAM;
    }

    if (pAdcChannelConfig->enableVbias)
        sensorDetailsReg.Vbias = 1;
    else
        sensorDetailsReg.Vbias = 0;

    if (pAdcChannelConfig->reference.disableBuffer)
        sensorDetailsReg.Reference_Buffer_Disable = 1;
    else
        sensorDetailsReg.Reference_Buffer_Disable = 0;

    if (pChannelConfig->disablePublishing)
        sensorDetailsReg.Do_Not_Publish = 1;
    else
        sensorDetailsReg.Do_Not_Publish = 0;

    return adi_sense_WriteRegister(hDevice, REG_ADISENSE_CORE_SENSOR_DETAILSn(eChannelId),
                                   &sensorDetailsReg, sizeof(sensorDetailsReg));
}

static ADI_SENSE_RESULT adi_sense_SetChannelAdcFilter(
    ADI_SENSE_DEVICE_HANDLE hDevice,
    ADI_SENSE_CHANNEL_ID eChannelId,
    ADI_SENSE_ADC_FILTER_CONFIG *pFilterConfig)
{
    ADI_ADISENSE_CORE_Filter_Select_t filterSelectReg = {
        .VALUE32 = REG_ADISENSE_CORE_FILTER_SELECTn_RESET
    };

    if (pFilterConfig->type == ADI_SENSE_ADC_FILTER_SINC4)
    {
        filterSelectReg.ADC_Filter_Type = ADISENSE_CORE_FILTER_SELECT_FILTER_SINC4;
        filterSelectReg.ADC_FS = pFilterConfig->fs;
    }
    else if (pFilterConfig->type == ADI_SENSE_ADC_FILTER_FIR_20SPS)
    {
        filterSelectReg.ADC_Filter_Type = ADISENSE_CORE_FILTER_SELECT_FILTER_FIR_20SPS;
    }
    else if (pFilterConfig->type == ADI_SENSE_ADC_FILTER_FIR_25SPS)
    {
        filterSelectReg.ADC_Filter_Type = ADISENSE_CORE_FILTER_SELECT_FILTER_FIR_25SPS;
    }
    else
    {
        return ADI_SENSE_INVALID_PARAM;
    }

    return adi_sense_WriteRegister(hDevice, REG_ADISENSE_CORE_FILTER_SELECTn(eChannelId),
                                   &filterSelectReg, sizeof(filterSelectReg));
}

static ADI_SENSE_RESULT adi_sense_SetChannelAdcCurrentConfig(
    ADI_SENSE_DEVICE_HANDLE hDevice,
    ADI_SENSE_CHANNEL_ID eChannelId,
    ADI_SENSE_ADC_CURRENT_CONFIG *pCurrentConfig)
{
    ADI_ADISENSE_CORE_Channel_Excitation_t channelExcitationReg = {
        .VALUE8 = REG_ADISENSE_CORE_CHANNEL_EXCITATIONn_RESET
    };

    if (pCurrentConfig->outputLevel == ADI_SENSE_ADC_CURRENT_LEVEL_NONE)
    {
        /* TODO - how should the IOUT0/1 Disable options be represented on the API? */
        channelExcitationReg.IOUT0_Disable = 1;
        channelExcitationReg.IOUT1_Disable = 1;

        channelExcitationReg.IOUT_Excitation_Current = ADISENSE_CORE_CHANNEL_EXCITATION_IEXC_OFF;
    }
    else
    {
        channelExcitationReg.IOUT0_Disable = 0;
        channelExcitationReg.IOUT1_Disable = 0;

        if (pCurrentConfig->outputLevel == ADI_SENSE_ADC_CURRENT_LEVEL_50uA)
            channelExcitationReg.IOUT_Excitation_Current = ADISENSE_CORE_CHANNEL_EXCITATION_IEXC_50UA;
        else if (pCurrentConfig->outputLevel == ADI_SENSE_ADC_CURRENT_LEVEL_100uA)
            channelExcitationReg.IOUT_Excitation_Current = ADISENSE_CORE_CHANNEL_EXCITATION_IEXC_100UA;
        else if (pCurrentConfig->outputLevel == ADI_SENSE_ADC_CURRENT_LEVEL_250uA)
            channelExcitationReg.IOUT_Excitation_Current = ADISENSE_CORE_CHANNEL_EXCITATION_IEXC_250UA;
        else if (pCurrentConfig->outputLevel == ADI_SENSE_ADC_CURRENT_LEVEL_500uA)
            channelExcitationReg.IOUT_Excitation_Current = ADISENSE_CORE_CHANNEL_EXCITATION_IEXC_500UA;
        else if (pCurrentConfig->outputLevel == ADI_SENSE_ADC_CURRENT_LEVEL_750uA)
            channelExcitationReg.IOUT_Excitation_Current = ADISENSE_CORE_CHANNEL_EXCITATION_IEXC_750UA;
        else if (pCurrentConfig->outputLevel == ADI_SENSE_ADC_CURRENT_LEVEL_1000uA)
            channelExcitationReg.IOUT_Excitation_Current = ADISENSE_CORE_CHANNEL_EXCITATION_IEXC_1000UA;
        else
            return ADI_SENSE_INVALID_PARAM;

        if (pCurrentConfig->sourceOption == ADI_SENSE_ADC_CURRENT_SOURCE_DEFAULT)
        {
            channelExcitationReg.IOUT_Dont_Swap_3Wire = 1;
            channelExcitationReg.IOUT_Static_Swap_3Wire = 0;
        }
        else if (pCurrentConfig->sourceOption == ADI_SENSE_ADC_CURRENT_SOURCE_SWAP_STATIC)
        {
            channelExcitationReg.IOUT_Dont_Swap_3Wire = 1;
            channelExcitationReg.IOUT_Static_Swap_3Wire = 1;
        }
        else if (pCurrentConfig->sourceOption == ADI_SENSE_ADC_CURRENT_SOURCE_SWAP_DYNAMIC)
        {
            channelExcitationReg.IOUT_Dont_Swap_3Wire = 0;
            channelExcitationReg.IOUT_Static_Swap_3Wire = 0;
        }
        else
        {
            return ADI_SENSE_INVALID_PARAM;
        }
    }

    return adi_sense_WriteRegister(hDevice,
                                   REG_ADISENSE_CORE_CHANNEL_EXCITATIONn(eChannelId),
                                   &channelExcitationReg,
                                   sizeof(channelExcitationReg));
}

ADI_SENSE_RESULT adi_sense_SetAdcChannelConfig(
    ADI_SENSE_DEVICE_HANDLE hDevice,
    ADI_SENSE_CHANNEL_ID eChannelId,
    ADI_SENSE_CHANNEL_CONFIG *pChannelConfig)
{
    ADI_SENSE_RESULT eRet;
    ADI_SENSE_ADC_CHANNEL_CONFIG *pAdcChannelConfig =
        &pChannelConfig->adcChannelConfig;

    eRet = adi_sense_SetChannelAdcSensorType(hDevice, eChannelId,
                                             pAdcChannelConfig->sensor);
    if (eRet)
    {
        return eRet;
    }

    eRet = adi_sense_SetChannelAdcSensorDetails(hDevice, eChannelId,
                                                pChannelConfig);
    if (eRet)
    {
        return eRet;
    }

    eRet = adi_sense_SetChannelAdcFilter(hDevice, eChannelId,
                                         &pAdcChannelConfig->filter);
    if (eRet)
    {
        return eRet;
    }

    eRet = adi_sense_SetChannelAdcCurrentConfig(hDevice, eChannelId,
                                                &pAdcChannelConfig->current);
    if (eRet)
    {
        return eRet;
    }

    return ADI_SENSE_SUCCESS;
}

static ADI_SENSE_RESULT adi_sense_SetChannelI2cSensorType(
    ADI_SENSE_DEVICE_HANDLE hDevice,
    ADI_SENSE_CHANNEL_ID eChannelId,
    ADI_SENSE_I2C_SENSOR_TYPE sensorType)
{
    ADI_ADISENSE_CORE_Sensor_Type_t sensorTypeReg = {
        .VALUE16 = REG_ADISENSE_CORE_SENSOR_TYPEn_RESET
    };

    /* Ensure that the sensor type is valid for this channel */
    switch(sensorType)
    {
    case ADI_SENSE_I2C_SENSOR_HUMIDITY_HONEYWELL_HIH:
        sensorTypeReg.Sensor_Type = ADISENSE_CORE_SENSOR_TYPE_SENSOR_I2C_HUMIDITY_HONEYWELL_HUMIDICON;
        break;
    case ADI_SENSE_I2C_SENSOR_HUMIDITY_SENSIRION_SHT35:
        sensorTypeReg.Sensor_Type = ADISENSE_CORE_SENSOR_TYPE_SENSOR_I2C_HUMIDITY_SENSIRION_SHT3X;
        break;
    default:
        /* TODO - add support for custom I2C sensors */
        return ADI_SENSE_INVALID_PARAM;
    }

    return adi_sense_WriteRegister(hDevice, REG_ADISENSE_CORE_SENSOR_TYPEn(eChannelId),
                                  &sensorTypeReg, sizeof(sensorTypeReg));
}

static ADI_SENSE_RESULT adi_sense_SetChannelI2cSensorAddress(
    ADI_SENSE_DEVICE_HANDLE hDevice,
    ADI_SENSE_CHANNEL_ID eChannelId,
    uint32_t deviceAddress)
{
    ADI_ADISENSE_CORE_Digital_Sensor_Address_t sensorAddressReg = {
        .VALUE8 = REG_ADISENSE_CORE_DIGITAL_SENSOR_ADDRESSn_RESET
    };

    sensorAddressReg.Digital_Sensor_Address = deviceAddress;

    return adi_sense_WriteRegister(hDevice,
                                  REG_ADISENSE_CORE_DIGITAL_SENSOR_ADDRESSn(eChannelId),
                                  &sensorAddressReg, sizeof(sensorAddressReg));
}

ADI_SENSE_RESULT adi_sense_SetI2cChannelConfig(
    ADI_SENSE_DEVICE_HANDLE hDevice,
    ADI_SENSE_CHANNEL_ID eChannelId,
    ADI_SENSE_I2C_CHANNEL_CONFIG *pI2cChannelConfig)
{
    ADI_SENSE_RESULT eRet;

    eRet = adi_sense_SetChannelI2cSensorType(hDevice, eChannelId,
                                            pI2cChannelConfig->sensor);
    if (eRet)
    {
        return eRet;
    }

    eRet = adi_sense_SetChannelI2cSensorAddress(hDevice, eChannelId,
                                               pI2cChannelConfig->deviceAddress);
    if (eRet)
    {
        return eRet;
    }

    return ADI_SENSE_SUCCESS;
}

static ADI_SENSE_RESULT adi_sense_SetChannelSpiSensorType(
    ADI_SENSE_DEVICE_HANDLE hDevice,
    ADI_SENSE_CHANNEL_ID eChannelId,
    ADI_SENSE_SPI_SENSOR_TYPE sensorType)
{
    ADI_ADISENSE_CORE_Sensor_Type_t sensorTypeReg = {
        .VALUE16 = REG_ADISENSE_CORE_SENSOR_TYPEn_RESET
    };

    /* Ensure that the sensor type is valid for this channel */
    switch(sensorType)
    {
    case ADI_SENSE_SPI_SENSOR_PRESSURE_HONEYWELL_HSCDRN1:
        sensorTypeReg.Sensor_Type = ADISENSE_CORE_SENSOR_TYPE_SENSOR_SPI_PRESSURE_HONEYWELL_TRUSTABILITY;
        break;
    case ADI_SENSE_SPI_SENSOR_PRESSURE_ADI_ADXL362:
        sensorTypeReg.Sensor_Type = ADISENSE_CORE_SENSOR_TYPE_SENSOR_SPI_ACCELEROMETER_1;
        break;
    default:
        /* TODO - add support for custom SPI sensors */
        return ADI_SENSE_INVALID_PARAM;
    }

    return adi_sense_WriteRegister(hDevice, REG_ADISENSE_CORE_SENSOR_TYPEn(eChannelId),
                                  &sensorTypeReg, sizeof(sensorTypeReg));
}

ADI_SENSE_RESULT adi_sense_SetSpiChannelConfig(
    ADI_SENSE_DEVICE_HANDLE hDevice,
    ADI_SENSE_CHANNEL_ID eChannelId,
    ADI_SENSE_SPI_CHANNEL_CONFIG *pSpiChannelConfig)
{
    ADI_SENSE_RESULT eRet;

    eRet = adi_sense_SetChannelSpiSensorType(hDevice, eChannelId,
                                             pSpiChannelConfig->sensor);
    if (eRet)
    {
        return eRet;
    }

    return ADI_SENSE_SUCCESS;
}

ADI_SENSE_RESULT adi_sense_SetChannelThresholdLimits(
    ADI_SENSE_DEVICE_HANDLE hDevice,
    ADI_SENSE_CHANNEL_ID eChannelId,
    float32_t highThresholdLimit,
    float32_t lowThresholdLimit)
{
    ADI_ADISENSE_CORE_High_Threshold_Limit_t highLimitReg = {
        .High_Threshold = highThresholdLimit
    };
    ADI_ADISENSE_CORE_Low_Threshold_Limit_t lowLimitReg = {
        .Low_Threshold = lowThresholdLimit
    };
    ADI_SENSE_RESULT eRet;

    eRet = adi_sense_WriteRegister(hDevice,
                                   REG_ADISENSE_CORE_HIGH_THRESHOLD_LIMITn(eChannelId),
                                   &highLimitReg, sizeof(highLimitReg));
    if (eRet)
    {
        return eRet;
    }

    return adi_sense_WriteRegister(hDevice,
                                   REG_ADISENSE_CORE_LOW_THRESHOLD_LIMITn(eChannelId),
                                   &lowLimitReg, sizeof(lowLimitReg));;
}

ADI_SENSE_RESULT adi_sense_SetChannelSettlingTime(
    ADI_SENSE_DEVICE_HANDLE hDevice,
    ADI_SENSE_CHANNEL_ID eChannelId,
    uint32_t settlingTime)
{
    ADI_ADISENSE_CORE_Settling_Time_t settlingTimeReg = {
        .VALUE16 = REG_ADISENSE_CORE_SETTLING_TIMEn_RESET
    };

    settlingTimeReg.Settling_Time = settlingTime;

    return adi_sense_WriteRegister(hDevice, REG_ADISENSE_CORE_SETTLING_TIMEn(eChannelId),
                                  &settlingTimeReg, sizeof(settlingTimeReg));
}

ADI_SENSE_RESULT adi_sense_SetChannelConfig(
    ADI_SENSE_DEVICE_HANDLE hDevice,
    ADI_SENSE_CHANNEL_ID eChannelId,
    ADI_SENSE_CHANNEL_CONFIG *pChannelConfig)
{
    ADI_SENSE_RESULT eRet;

    /* If the channel is not enabled, disable it and return */
    if (! pChannelConfig->enableChannel)
        return adi_sense_SetChannelCount(hDevice, eChannelId, 0);

    eRet = adi_sense_SetChannelCount(hDevice, eChannelId,
                                     pChannelConfig->measurementsPerCycle);
    if (eRet)
        return eRet;

    switch (eChannelId)
    {
    case ADI_SENSE_CHANNEL_ID_CJC_0:
    case ADI_SENSE_CHANNEL_ID_CJC_1:
    case ADI_SENSE_CHANNEL_ID_SENSOR_0:
    case ADI_SENSE_CHANNEL_ID_SENSOR_1:
    case ADI_SENSE_CHANNEL_ID_SENSOR_2:
    case ADI_SENSE_CHANNEL_ID_SENSOR_3:
    case ADI_SENSE_CHANNEL_ID_VOLTAGE_0:
    case ADI_SENSE_CHANNEL_ID_CURRENT_0:
        eRet = adi_sense_SetAdcChannelConfig(hDevice, eChannelId, pChannelConfig);
        break;
    case ADI_SENSE_CHANNEL_ID_I2C_0:
    case ADI_SENSE_CHANNEL_ID_I2C_1:
        eRet = adi_sense_SetI2cChannelConfig(hDevice, eChannelId,
                                             &pChannelConfig->i2cChannelConfig);
        break;
    case ADI_SENSE_CHANNEL_ID_SPI_0:
        eRet = adi_sense_SetSpiChannelConfig(hDevice, eChannelId,
                                             &pChannelConfig->spiChannelConfig);
        break;
    default:
        return ADI_SENSE_INVALID_PARAM;
    }

    eRet = adi_sense_SetChannelThresholdLimits(hDevice, eChannelId,
                                              pChannelConfig->measurementMaxValue,
                                              pChannelConfig->measurementMinValue);
    if (eRet)
    {
        return eRet;
    }

    eRet = adi_sense_SetChannelSettlingTime(hDevice, eChannelId,
                                           pChannelConfig->extraSettlingTime);
    if (eRet)
    {
        return eRet;
    }

    return ADI_SENSE_SUCCESS;
}

ADI_SENSE_RESULT adi_sense_SetDeviceConfig(
    ADI_SENSE_DEVICE_HANDLE    const hDevice,
    ADI_SENSE_CONFIG         * const pConfig)
{
    ADI_SENSE_DEVICE_CONTEXT *pCtx = hDevice;
    ADI_SENSE_DEVICE_CONFIG *pDeviceConfig;
    ADI_SENSE_PRODUCT_ID productId;
    ADI_SENSE_RESULT eRet;
    
    if (pCtx->nDeviceIndex >= pConfig->numDevices)
    {
        return ADI_SENSE_INVALID_DEVICE_NUM;
    }
    pDeviceConfig = &pConfig->devices[pCtx->nDeviceIndex];

    /* Check that the Product ID is a match? */
    eRet = adi_sense_GetProductID(hDevice, &productId);
    if (eRet)
    {
        return eRet;
    }
    if (pDeviceConfig->productId != productId)
    {
        return ADI_SENSE_INVALID_PARAM;
    }

    eRet = adi_sense_SetPowerConfig(hDevice, &pDeviceConfig->power);
    if (eRet)
    {
        return eRet;
    }

    eRet = adi_sense_SetMeasurementConfig(hDevice, &pDeviceConfig->measurement);
    if (eRet)
    {
        return eRet;
    }

    eRet = adi_sense_SetDiagnosticsConfig(hDevice, &pDeviceConfig->diagnostics);
    if (eRet)
    {
        return eRet;
    }

    for (ADI_SENSE_CHANNEL_ID id = 0; id < ADI_SENSE_MAX_CHANNELS; id++)
    {
        eRet = adi_sense_SetChannelConfig(hDevice, id,
                                          &pDeviceConfig->channels[id]);
        if (eRet)
        {
            return eRet;
        }
    }

    return ADI_SENSE_SUCCESS;
}

ADI_SENSE_RESULT adi_sense_SetDspData(
    ADI_SENSE_DEVICE_HANDLE    const hDevice,
    ADI_SENSE_DSP_LUT_RAW    * const pDspData)
{
    ADI_SENSE_DSP_LUT *pLut = (ADI_SENSE_DSP_LUT *)pDspData;
    ADI_SENSE_DSP_LUT_TABLE *pLutTable = pLut->tables;
    unsigned actualLength = 0;

    if (pLut->header.signature != ADI_SENSE_DSP_LUT_SIGNATURE)
    {
        ADI_SENSE_LOG_ERROR("DSP LUT signature incorrect (expected 0x%X, actual 0x%X)",
                            ADI_SENSE_DSP_LUT_SIGNATURE, pLut->header.signature);
        return ADI_SENSE_INVALID_SIGNATURE;
    }

    for (unsigned i = 0; i < pLut->header.numTables; i++)
    {
        ADI_SENSE_DSP_LUT_DESCRIPTOR *pDesc = &pLutTable->descriptor;
        unsigned short calculatedCrc;

        switch (pDesc->geometry)
        {
        case ADI_SENSE_DSP_LUT_GEOMETRY_COEFFICIENT_LIST:
        case ADI_SENSE_DSP_LUT_GEOMETRY_EQUALLY_SPACED_1D:
        case ADI_SENSE_DSP_LUT_GEOMETRY_EQUALLY_SPACED_2D:
            break;
        default:
            ADI_SENSE_LOG_ERROR("Invalid geometry %u specified for DSP LUT table %u",
                                pDesc->geometry, i);
            return ADI_SENSE_INVALID_PARAM;
        }

        switch (pDesc->equation)
        {
        case ADI_SENSE_DSP_LUT_EQUATION_NONE:
        case ADI_SENSE_DSP_LUT_EQUATION_POLYNOMIAL:
        case ADI_SENSE_DSP_LUT_EQUATION_POLYNOMIAL_EXPONENTIAL:
        case ADI_SENSE_DSP_LUT_EQUATION_QUADRATIC:
        case ADI_SENSE_DSP_LUT_EQUATION_EXPONENTIAL:
        case ADI_SENSE_DSP_LUT_EQUATION_LOGARITHMIC:
            break;
        default:
            ADI_SENSE_LOG_ERROR("Invalid equation %u specified for DSP LUT table %u",
                                pDesc->equation, i);
            return ADI_SENSE_INVALID_PARAM;
        }

        switch (pDesc->function)
        {
        case ADI_SENSE_DSP_LUT_FUNCTION_TCJ_MV2C:
        case ADI_SENSE_DSP_LUT_FUNCTION_TCJ_C2MV:
        case ADI_SENSE_DSP_LUT_FUNCTION_TCK_MV2C:
        case ADI_SENSE_DSP_LUT_FUNCTION_TCK_C2MV:
        case ADI_SENSE_DSP_LUT_FUNCTION_TCT_MV2C:
        case ADI_SENSE_DSP_LUT_FUNCTION_TCT_C2MV:
        case ADI_SENSE_DSP_LUT_FUNCTION_RTD_OHM2C:
        case ADI_SENSE_DSP_LUT_FUNCTION_VOUT_V2BAR:
        case ADI_SENSE_DSP_LUT_FUNCTION_VOUT_V2PSI:
        case ADI_SENSE_DSP_LUT_FUNCTION_IOUT_MA2PSI:
            break;
        default:
            ADI_SENSE_LOG_ERROR("Invalid function %u specified for DSP LUT table %u",
                                pDesc->function, i);
            return ADI_SENSE_INVALID_PARAM;
        }

        switch (pDesc->vectorFormat)
        {
        case ADI_SENSE_DSP_LUT_VECTOR_FORMAT_FLOAT32:
        case ADI_SENSE_DSP_LUT_VECTOR_FORMAT_FLOAT64:
            break;
        default:
            ADI_SENSE_LOG_ERROR("Invalid vector format %u specified for DSP LUT table %u",
                                pDesc->vectorFormat, i);
            return ADI_SENSE_INVALID_PARAM;
        }

        if (((pDesc->rangeMin != NAN) && (pDesc->rangeMax != NAN)) &&
            (pDesc->rangeMax <= pDesc->rangeMin))
        {
            ADI_SENSE_LOG_ERROR("Invalid range specified for DSP LUT table %u", i);
            return ADI_SENSE_INVALID_PARAM;
        }

        calculatedCrc = crc16_ccitt(pLutTable->data, pDesc->dataLength);
        if (calculatedCrc != pDesc->crc16)
        {
            ADI_SENSE_LOG_ERROR("CRC validation failed on DSP LUT table %u (expected 0x%04X, actual 0x%04X)",
                                i, pDesc->crc16, calculatedCrc);
            return ADI_SENSE_CRC_ERROR;
        }

        actualLength += sizeof(*pDesc) + pDesc->dataLength;

        /* Move to the next look-up table */
        pLutTable = (ADI_SENSE_DSP_LUT_TABLE *)(pLutTable->data + pDesc->dataLength);
    }

    if (actualLength != pLut->header.totalLength)
    {
        ADI_SENSE_LOG_ERROR("DSP LUT table length mismatch (expected %u, actual %u)",
                            pLut->header.totalLength, actualLength);
        return ADI_SENSE_WRONG_SIZE;
    }

    /* TODO - add a check to ensure that the total length doesn't exceed the maximum internal LUT storage size (TBD) */

    /* TODO - write the LUT data to the device */

    return ADI_SENSE_SUCCESS;
}

#define CAL_TABLE_ROWS 56
#define CAL_TABLE_COLS 3
#define CAL_TABLE_SIZE (sizeof(float) * CAL_TABLE_ROWS * CAL_TABLE_COLS)

/*!
 * @brief Read the contents of the ADISense internal calibration table
 *
 * Calibration coefficients/gains/offsets are stored internally in a table
 * of 56x3 32-bit floating point values
 *
 * @param[in]  uint8_t* : Pointer to destination buffer for the calibration data
 * @param[in]  maxLen   : The buffer capacity in bytes (minimum 672 bytes)
 * @param[out] dataLen  : The number of bytes written to the buffer
 * @param[out] nRows    : The number of rows in the table (56)
 * @param[out] nRows    : The number of columns in the table (3)
 *
 * @return Status
 *         - #ADI_SENSE_SUCCESS Call completed successfully.
 *         - #ADI_SENSE_FAILURE
 *         - #ADI_SENSE_INVALID_OPERATION Invalid register identifier.
 */
ADI_SENSE_RESULT adi_sense_ReadCalTable(
    ADI_SENSE_DEVICE_HANDLE hDevice,
    float *buffer,
    unsigned maxLen,
    unsigned *dataLen,
    unsigned *nRows,
    unsigned *nColumns)
{
    ADI_SENSE_RESULT eRet;
    ADI_ADISENSE_CORE_CAL_Offset_t calOffsetReg;

    *dataLen = 0;
    *nRows = CAL_TABLE_ROWS;
    *nColumns = CAL_TABLE_COLS;
    /* Read back in 256-byte chunks due to limitation in underlying layer */
    for (unsigned offset = 0; offset < CAL_TABLE_SIZE; offset += 256)
    {
        unsigned readSize = 256;

        if (readSize > maxLen)
            readSize = maxLen;

        if (readSize > (CAL_TABLE_SIZE - offset))
            readSize = CAL_TABLE_SIZE - offset;

        calOffsetReg.CAL_Offset = offset;
        eRet = adi_sense_WriteRegister(hDevice, REG_ADISENSE_CORE_CAL_OFFSET,
                                       &calOffsetReg, sizeof(calOffsetReg));
        if (eRet)
        {
            return eRet;
        }

        eRet = adi_sense_ReadRegister(hDevice, REG_ADISENSE_CORE_CAL_DATA,
                                      (uint8_t *)buffer + offset, readSize);
        if (eRet)
        {
            return eRet;
        }
        *dataLen += readSize;
    }

    return ADI_SENSE_SUCCESS;
}