ADISense1000 Version 2.1 code base

Fork of AdiSense1000_V21 by Sean Wilson

Revision:
7:4dbae381f693
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/adi_sense_1000.c	Fri Oct 20 15:58:01 2017 +0000
@@ -0,0 +1,1766 @@
+/*!
+ ******************************************************************************
+ * @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;
+}
+