Host API Example for the ADMW1001

src/admw_1001.c

Committer:
Vkadaba
Date:
2019-06-05
Revision:
5:0728bde67bdb
Parent:
src/adi_sense_1000.c@ 4:2ca06eee5735
Child:
6:9d393a9677f4

File content as of revision 5:0728bde67bdb:

/*
Copyright 2018 (c) Analog Devices, Inc.

All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
  - Redistributions of source code must retain the above copyright
    notice, this list of conditions and the following disclaimer.
  - Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions and the following disclaimer in
    the documentation and/or other materials provided with the
    distribution.
  - Neither the name of Analog Devices, Inc. nor the names of its
    contributors may be used to endorse or promote products derived
    from this software without specific prior written permission.
  - The use of this software may or may not infringe the patent rights
    of one or more patent holders. This license does not release you
    from the requirement that you obtain separate licenses from these
    patent holders to use this software.
  - Use of the software either in source or binary form, must be run
    on or directly connected to an Analog Devices Inc. component.

THIS SOFTWARE IS PROVIDED BY ANALOG DEVICES "AS IS" AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, NON-INFRINGEMENT,
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL ANALOG DEVICES BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, INTELLECTUAL PROPERTY RIGHTS, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/******************************************************************************
Copyright 2017 (c) Analog Devices, Inc.

All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
  - Redistributions of source code must retain the above copyright
    notice, this list of conditions and the following disclaimer.
  - Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions and the following disclaimer in
    the documentation and/or other materials provided with the
    distribution.
  - Neither the name of Analog Devices, Inc. nor the names of its
    contributors may be used to endorse or promote products derived
    from this software without specific prior written permission.
  - The use of this software may or may not infringe the patent rights
    of one or more patent holders. This license does not release you
    from the requirement that you obtain separate licenses from these
    patent holders to use this software.
  - Use of the software either in source or binary form, must be run
    on or directly connected to an Analog Devices Inc. component.

THIS SOFTWARE IS PROVIDED BY ANALOG DEVICES "AS IS" AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, NON-INFRINGEMENT,
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL ANALOG DEVICES BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, INTELLECTUAL PROPERTY RIGHTS, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *****************************************************************************/

/*!
 ******************************************************************************
 * @file:
 * @brief: ADMW API implementation for ADSNS1000
 *-----------------------------------------------------------------------------
 */

#include <float.h>
#include <math.h>
#include <string.h>

#include "admw_platform.h"
#include "admw_api.h"
#include "admw1001/admw1001_api.h"

#include "admw1001/ADMW1001_REGISTERS_typedefs.h"
#include "admw1001/ADMW1001_REGISTERS.h"
#include "admw1001/admw1001_lut_data.h"
#include "admw1001/admw1001_host_comms.h"

#include "crc16.h"


uint32_t    getDataCnt = 0;

/*
 * The following macros are used to encapsulate the register access code
 * to improve readability in the functions further below in this file
 */
#define STRINGIFY(name) #name

/* Expand the full name of the reset value macro for the specified register */
#define REG_RESET_VAL(_name) REG_##_name##_RESET

/* Checks if a value is outside the bounds of the specified register field */
#define CHECK_REG_FIELD_VAL(_field, _val)                               \
    do {                                                                \
        uint32_t _mask  = BITM_##_field;                       \
        uint32_t _shift = BITP_##_field;                       \
        if ((((_val) << _shift) & ~(_mask)) != 0) {                     \
            ADMW_LOG_ERROR("Value 0x%08X invalid for register field %s", \
                                (uint32_t)(_val),                       \
                                STRINGIFY(ADMW_##_field));          \
            return ADMW_INVALID_PARAM;                             \
        }                                                               \
    } while(false)

/*
 * Encapsulates the write to a specified register
 * NOTE - this will cause the calling function to return on error
 */
#define WRITE_REG(_hdev, _val, _name, _type)                            \
    do {                                                                \
        ADMW_RESULT _res;                                          \
        _type _regval = _val;                                           \
        _res = admw1001_WriteRegister((_hdev),                    \
                                            REG_##_name,       \
                                            &_regval, sizeof(_regval)); \
        if (_res != ADMW_SUCCESS)                                  \
            return _res;                                                \
    } while(false)

/* Wrapper macro to write a value to a uint32_t register */
#define WRITE_REG_U32(_hdev, _val, _name)       \
    WRITE_REG(_hdev, _val, _name, uint32_t)
/* Wrapper macro to write a value to a uint16_t register */
#define WRITE_REG_U16(_hdev, _val, _name)       \
    WRITE_REG(_hdev, _val, _name, uint16_t)
/* Wrapper macro to write a value to a uint8_t register */
#define WRITE_REG_U8(_hdev, _val, _name)        \
    WRITE_REG(_hdev, _val, _name, uint8_t)
/* Wrapper macro to write a value to a float32_t register */
#define WRITE_REG_FLOAT(_hdev, _val, _name)     \
    WRITE_REG(_hdev, _val, _name, float32_t)

/*
 * Encapsulates the read from a specified register
 * NOTE - this will cause the calling function to return on error
 */
#define READ_REG(_hdev, _val, _name, _type)                             \
    do {                                                                \
        ADMW_RESULT _res;                                          \
        _type _regval;                                                  \
        _res = admw1001_ReadRegister((_hdev),                     \
                                           REG_##_name,        \
                                           &_regval, sizeof(_regval));  \
        if (_res != ADMW_SUCCESS)                                  \
            return _res;                                                \
        _val = _regval;                                                 \
    } while(false)

/* Wrapper macro to read a value from a uint32_t register */
#define READ_REG_U32(_hdev, _val, _name)        \
    READ_REG(_hdev, _val, _name, uint32_t)
/* Wrapper macro to read a value from a uint16_t register */
#define READ_REG_U16(_hdev, _val, _name)        \
    READ_REG(_hdev, _val, _name, uint16_t)
/* Wrapper macro to read a value from a uint8_t register */
#define READ_REG_U8(_hdev, _val, _name)         \
    READ_REG(_hdev, _val, _name, uint8_t)
/* Wrapper macro to read a value from a float32_t register */
#define READ_REG_FLOAT(_hdev, _val, _name)      \
    READ_REG(_hdev, _val, _name, float32_t)

/*
 * Wrapper macro to write an array of values to a uint8_t register
 * NOTE - this is intended only for writing to a keyhole data register
 */
#define WRITE_REG_U8_ARRAY(_hdev, _arr, _len, _name)                \
    do {                                                            \
        ADMW_RESULT _res;                                      \
        _res = admw1001_WriteRegister(_hdev,                  \
                                            REG_##_name,   \
                                            _arr, _len);            \
        if (_res != ADMW_SUCCESS)                              \
            return _res;                                            \
    } while(false)

/*
 * Wrapper macro to read an array of values from a uint8_t register
 * NOTE - this is intended only for reading from a keyhole data register
 */
#define READ_REG_U8_ARRAY(_hdev, _arr, _len, _name)                 \
    do {                                                            \
        ADMW_RESULT _res;                                      \
        _res = admw1001_ReadRegister((_hdev),                 \
                                           REG_##_name,    \
                                           _arr, _len);             \
        if (_res != ADMW_SUCCESS)                              \
            return _res;                                            \
    } while(false)

#define ADMW1001_CHANNEL_IS_ADC(c)                                \
    ((c) >= ADMW1001_CHANNEL_ID_CJC_0 && (c) <= ADMW1001_CHANNEL_ID_CURRENT_0)

#define ADMW1001_CHANNEL_IS_ADC_CJC(c)                            \
    ((c) >= ADMW1001_CHANNEL_ID_CJC_0 && (c) <= ADMW1001_CHANNEL_ID_CJC_1)

#define ADMW1001_CHANNEL_IS_ADC_SENSOR(c)                         \
    ((c) >= ADMW1001_CHANNEL_ID_SENSOR_0 && (c) <= ADMW1001_CHANNEL_ID_SENSOR_3)

#define ADMW1001_CHANNEL_IS_ADC_VOLTAGE(c)    \
    ((c) == ADMW1001_CHANNEL_ID_VOLTAGE_0)

#define ADMW1001_CHANNEL_IS_ADC_CURRENT(c)    \
    ((c) == ADMW1001_CHANNEL_ID_CURRENT_0)

#define ADMW1001_CHANNEL_IS_VIRTUAL(c)                            \
    ((c) == ADMW1001_CHANNEL_ID_SPI_1 || (c) == ADMW1001_CHANNEL_ID_SPI_2)

typedef struct
{
    unsigned nDeviceIndex;
    ADMW_SPI_HANDLE hSpi;
    ADMW_GPIO_HANDLE hGpio;
} ADMW_DEVICE_CONTEXT;

static ADMW_DEVICE_CONTEXT gDeviceCtx[ADMW_PLATFORM_MAX_DEVICES];

/*
 * Open an ADMW device instance.
 */
ADMW_RESULT admw_Open(
    unsigned                   const nDeviceIndex,
    ADMW_CONNECTION     * const pConnectionInfo,
    ADMW_DEVICE_HANDLE  * const phDevice)
{
    ADMW_DEVICE_CONTEXT *pCtx;
    ADMW_RESULT eRet;

    if (nDeviceIndex >= ADMW_PLATFORM_MAX_DEVICES)
        return ADMW_INVALID_DEVICE_NUM;

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

    eRet = admw_LogOpen(&pConnectionInfo->log);
    if (eRet != ADMW_SUCCESS)
        return eRet;

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

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

    *phDevice = pCtx;
    return ADMW_SUCCESS;
}

/*
 * Get the current state of the specified GPIO input signal.
 */
ADMW_RESULT admw_GetGpioState(
    ADMW_DEVICE_HANDLE   const hDevice,
    ADMW_GPIO_PIN        const ePinId,
    bool                  * const pbAsserted)
{
    ADMW_DEVICE_CONTEXT *pCtx = hDevice;

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

/*
 * Register an application-defined callback function for GPIO interrupts.
 */
ADMW_RESULT admw_RegisterGpioCallback(
    ADMW_DEVICE_HANDLE          const hDevice,
    ADMW_GPIO_PIN               const ePinId,
    ADMW_GPIO_CALLBACK          const callbackFunction,
    void                           * const pCallbackParam)
{
    ADMW_DEVICE_CONTEXT *pCtx = hDevice;

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

/*
 * Reset the specified ADMW device.
 */
ADMW_RESULT admw_Reset(
    ADMW_DEVICE_HANDLE    const hDevice)
{
    ADMW_DEVICE_CONTEXT *pCtx = hDevice;
    ADMW_RESULT eRet;

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

    admw_TimeDelayUsec(4);

    eRet = admw_GpioSet(pCtx->hGpio, ADMW_GPIO_PIN_RESET, true);
    if (eRet != ADMW_SUCCESS)
        return eRet;

    return ADMW_SUCCESS;
}


/*!
 * @brief Get general status of ADISense module.
 *
 * @param[in]
 * @param[out] pStatus : Pointer to CORE Status struct.
 *
 * @return Status
 *         - #ADMW_SUCCESS Call completed successfully.
 *         - #ADMW_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.
 *
 */
ADMW_RESULT admw_GetStatus(
    ADMW_DEVICE_HANDLE    const hDevice,
    ADMW_STATUS         * const pStatus)
{
    CORE_Status_t statusReg;
    READ_REG_U8(hDevice, statusReg.VALUE8, CORE_STATUS);

    CORE_Alert_Status_2_t alert2Reg;
    READ_REG_U16(hDevice, alert2Reg.VALUE16, CORE_ALERT_STATUS_2);

    memset(pStatus, 0, sizeof(*pStatus));

    if (!statusReg.Cmd_Running) /* Active-low, so invert it */
        pStatus->deviceStatus |= ADMW_DEVICE_STATUS_BUSY;
    if (statusReg.Drdy)
        pStatus->deviceStatus |= ADMW_DEVICE_STATUS_DATAREADY;
    if (statusReg.FIFO_Error)
        pStatus->deviceStatus |= ADMW_DEVICE_STATUS_FIFO_ERROR;
    if (alert2Reg.Ext_Flash_Error)
        pStatus->deviceStatus |= ADMW_DEVICE_STATUS_EXT_FLASH_ERROR;
    if (statusReg.Alert_Active)
    {
        pStatus->deviceStatus |= ADMW_DEVICE_STATUS_ALERT;

        CORE_Alert_Code_t alertCodeReg;
        READ_REG_U16(hDevice, alertCodeReg.VALUE16, CORE_ALERT_CODE);
        pStatus->alertCode = alertCodeReg.Alert_Code;

        CORE_Channel_Alert_Status_t channelAlertStatusReg;
        READ_REG_U16(hDevice, channelAlertStatusReg.VALUE16,
                     CORE_CHANNEL_ALERT_STATUS);

        for (unsigned i = 0; i < ADMW1001_MAX_CHANNELS; i++)
        {
            if (channelAlertStatusReg.VALUE16 & (1 << i))
            {
                CORE_Alert_Code_Ch_t channelAlertCodeReg;
                READ_REG_U16(hDevice, channelAlertCodeReg.VALUE16, CORE_ALERT_CODE_CHn(i));
                pStatus->channelAlertCodes[i] = channelAlertCodeReg.Alert_Code_Ch;

                CORE_Alert_Detail_Ch_t alertDetailReg;
                READ_REG_U16(hDevice, alertDetailReg.VALUE16,
                             CORE_ALERT_DETAIL_CHn(i));

                if (alertDetailReg.Time_Out)
                    pStatus->channelAlerts[i] |= ADMW_CHANNEL_ALERT_TIMEOUT;
                if (alertDetailReg.Under_Range)
                    pStatus->channelAlerts[i] |= ADMW_CHANNEL_ALERT_UNDER_RANGE;
                if (alertDetailReg.Over_Range)
                    pStatus->channelAlerts[i] |= ADMW_CHANNEL_ALERT_OVER_RANGE;
                if (alertDetailReg.Low_Limit)
                    pStatus->channelAlerts[i] |= ADMW_CHANNEL_ALERT_LOW_LIMIT;
                if (alertDetailReg.High_Limit)
                    pStatus->channelAlerts[i] |= ADMW_CHANNEL_ALERT_HIGH_LIMIT;
                if (alertDetailReg.Sensor_Open)
                    pStatus->channelAlerts[i] |= ADMW_CHANNEL_ALERT_SENSOR_OPEN;
                if (alertDetailReg.Ref_Detect)
                    pStatus->channelAlerts[i] |= ADMW_CHANNEL_ALERT_REF_DETECT;
                if (alertDetailReg.Config_Err)
                    pStatus->channelAlerts[i] |= ADMW_CHANNEL_ALERT_CONFIG_ERR;
                if (alertDetailReg.LUT_Error_Ch)
                    pStatus->channelAlerts[i] |= ADMW_CHANNEL_ALERT_LUT_ERR;
                if (alertDetailReg.Sensor_Not_Ready)
                    pStatus->channelAlerts[i] |= ADMW_CHANNEL_ALERT_SENSOR_NOT_READY;
                if (alertDetailReg.Comp_Not_Ready)
                    pStatus->channelAlerts[i] |= ADMW_CHANNEL_ALERT_COMP_NOT_READY;
                if (alertDetailReg.Correction_UnderRange)
                    pStatus->channelAlerts[i] |= ADMW_CHANNEL_ALERT_LUT_UNDER_RANGE;
                if (alertDetailReg.Correction_OverRange)
                    pStatus->channelAlerts[i] |= ADMW_CHANNEL_ALERT_LUT_OVER_RANGE;
            }
        }

        if (alert2Reg.Configuration_Error)
            pStatus->deviceStatus |= ADMW_DEVICE_STATUS_CONFIG_ERROR;
        if (alert2Reg.LUT_Error)
            pStatus->deviceStatus |= ADMW_DEVICE_STATUS_LUT_ERROR;
    }

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

        CORE_Error_Code_t errorCodeReg;
        READ_REG_U16(hDevice, errorCodeReg.VALUE16, CORE_ERROR_CODE);
        pStatus->errorCode = errorCodeReg.Error_Code;

        CORE_Diagnostics_Status_t diagStatusReg;
        READ_REG_U16(hDevice, diagStatusReg.VALUE16, CORE_DIAGNOSTICS_STATUS);

        if (diagStatusReg.Diag_Checksum_Error)
            pStatus->diagnosticsStatus |= ADMW_DIAGNOSTICS_STATUS_CHECKSUM_ERROR;
        if (diagStatusReg.Diag_Comms_Error)
            pStatus->diagnosticsStatus |= ADMW_DIAGNOSTICS_STATUS_COMMS_ERROR;
        if (diagStatusReg.Diag_Supply_Monitor_Error)
            pStatus->diagnosticsStatus |= ADMW_DIAGNOSTICS_STATUS_SUPPLY_MONITOR_ERROR;
        if (diagStatusReg.Diag_Supply_Cap_Error)
            pStatus->diagnosticsStatus |= ADMW_DIAGNOSTICS_STATUS_SUPPLY_CAP_ERROR;
        if (diagStatusReg.Diag_Conversion_Error)
            pStatus->diagnosticsStatus |= ADMW_DIAGNOSTICS_STATUS_CONVERSION_ERROR;
        if (diagStatusReg.Diag_Calibration_Error)
            pStatus->diagnosticsStatus |= ADMW_DIAGNOSTICS_STATUS_CALIBRATION_ERROR;
    }

    if (statusReg.Alert_Active || statusReg.Error)
    {
        CORE_Debug_Code_t debugCodeReg;
        READ_REG_U32(hDevice, debugCodeReg.VALUE32, CORE_DEBUG_CODE);
        pStatus->debugCode = debugCodeReg.Debug_Code;
    }

    return ADMW_SUCCESS;
}

ADMW_RESULT admw_GetCommandRunningState(
    ADMW_DEVICE_HANDLE hDevice,
    bool *pbCommandRunning)
{
    CORE_Status_t statusReg;

    READ_REG_U8(hDevice, statusReg.VALUE8, CORE_STATUS);

    /* We should never normally see 0xFF here if the module is operational */
    if (statusReg.VALUE8 == 0xFF)
        return ADMW_ERR_NOT_INITIALIZED;

    *pbCommandRunning = !statusReg.Cmd_Running; /* Active-low, so invert it */

    return ADMW_SUCCESS;
}

static ADMW_RESULT executeCommand(
    ADMW_DEVICE_HANDLE const hDevice,
    CORE_Command_Special_Command const command,
    bool const bWaitForCompletion)
{
    CORE_Command_t commandReg;
    bool bCommandRunning;
    ADMW_RESULT eRet;

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

        if (bCommandRunning)
            return ADMW_IN_USE;
    }

    commandReg.Special_Command = command;
    WRITE_REG_U8(hDevice, commandReg.VALUE8, CORE_COMMAND);

    if (bWaitForCompletion)
    {
        do {
            /* Allow a minimum 50usec delay for status update before checking */
            admw_TimeDelayUsec(50);

            eRet = admw_GetCommandRunningState(hDevice, &bCommandRunning);
            if (eRet)
                return eRet;
        } while (bCommandRunning);
    }

    return ADMW_SUCCESS;
}

ADMW_RESULT admw_Shutdown(
    ADMW_DEVICE_HANDLE const hDevice)
{
    return executeCommand(hDevice, CORE_COMMAND_POWER_DOWN, false);
}


ADMW_RESULT admw_ApplyConfigUpdates(
    ADMW_DEVICE_HANDLE const hDevice)
{
    return executeCommand(hDevice, CORE_COMMAND_LATCH_CONFIG, true);
}

/*!
 * @brief Start a measurement cycle.
 *
 * @param[out]
 *
 * @return Status
 *         - #ADMW_SUCCESS Call completed successfully.
 *         - #ADMW_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.
 *
 */
ADMW_RESULT admw_StartMeasurement(
    ADMW_DEVICE_HANDLE    const hDevice,
    ADMW_MEASUREMENT_MODE const eMeasurementMode)
{
    switch (eMeasurementMode)
    {
    case ADMW_MEASUREMENT_MODE_HEALTHCHECK:
        return executeCommand(hDevice, CORE_COMMAND_SYSTEM_CHECK, false);
    case ADMW_MEASUREMENT_MODE_NORMAL:
        return executeCommand(hDevice, CORE_COMMAND_CONVERT_WITH_RAW, false);
    case ADMW_MEASUREMENT_MODE_OMIT_RAW:
        return executeCommand(hDevice, CORE_COMMAND_CONVERT, false);
    case ADMW_MEASUREMENT_MODE_FFT:
        return executeCommand(hDevice, CORE_COMMAND_CONVERT_FFT, false);
    default:
        ADMW_LOG_ERROR("Invalid measurement mode %d specified",
                            eMeasurementMode);
        return ADMW_INVALID_PARAM;
    }
}

/*
 * Store the configuration settings to persistent memory on the device.
 * The settings can be saved to 4 different flash memory areas (slots).
 * No other command must be running when this is called.
 * Do not power down the device while this command is running.
 */
ADMW_RESULT admw_SaveConfig(
    ADMW_DEVICE_HANDLE    const hDevice,
    ADMW_USER_CONFIG_SLOT const eSlotId)
{
    switch (eSlotId)
    {
        case ADMW_FLASH_CONFIG_1:
            return executeCommand(hDevice, CORE_COMMAND_SAVE_CONFIG_1, true);
        case ADMW_FLASH_CONFIG_2:
            return executeCommand(hDevice, CORE_COMMAND_SAVE_CONFIG_2, true);
        case ADMW_FLASH_CONFIG_3:
            return executeCommand(hDevice, CORE_COMMAND_SAVE_CONFIG_3, true);
        case ADMW_FLASH_CONFIG_4:
            return executeCommand(hDevice, CORE_COMMAND_SAVE_CONFIG_4, true);
        default:
            ADMW_LOG_ERROR("Invalid user config target slot %d specified",
                                eSlotId);
            return ADMW_INVALID_PARAM;
    }
}

/*
 * Restore the configuration settings from persistent memory on the device.
 * No other command must be running when this is called.
 */
ADMW_RESULT admw_RestoreConfig(
    ADMW_DEVICE_HANDLE    const hDevice,
    ADMW_USER_CONFIG_SLOT const eSlotId)
{
    switch (eSlotId)
    {
        case ADMW_FLASH_CONFIG_1:
            return executeCommand(hDevice, CORE_COMMAND_LOAD_CONFIG_1, true);
        case ADMW_FLASH_CONFIG_2:
            return executeCommand(hDevice, CORE_COMMAND_LOAD_CONFIG_2, true);
        case ADMW_FLASH_CONFIG_3:
            return executeCommand(hDevice, CORE_COMMAND_LOAD_CONFIG_3, true);
        case ADMW_FLASH_CONFIG_4:
            return executeCommand(hDevice, CORE_COMMAND_LOAD_CONFIG_4, true);
        default:
            ADMW_LOG_ERROR("Invalid user config source slot %d specified",
                                eSlotId);
            return ADMW_INVALID_PARAM;
    }
}

/*
 * Erase the entire external flash memory.
 * No other command must be running when this is called.
 */
ADMW_RESULT admw_EraseExternalFlash(
    ADMW_DEVICE_HANDLE    const hDevice)
{
    return executeCommand(hDevice, CORE_COMMAND_ERASE_EXTERNAL_FLASH, true);
}

/*
 * Read the number of samples stored in external flash memory.
 * No other command must be running when this is called.
 */
ADMW_RESULT admw_GetExternalFlashSampleCount(
    ADMW_DEVICE_HANDLE    const hDevice,
    uint32_t * nSampleCount)
{
    CORE_Ext_Flash_Sample_Count_t nCount;

    READ_REG_U32(hDevice, nCount.VALUE32, CORE_EXT_FLASH_SAMPLE_COUNT);

    *nSampleCount = nCount.VALUE32;

    return ADMW_SUCCESS;
}

// DEBUG - TO BE DELETED
ADMW_RESULT admw_SetExternalFlashIndex(
    ADMW_DEVICE_HANDLE    const hDevice,
    uint32_t nStartIndex)
{
    WRITE_REG_U32(hDevice, nStartIndex, CORE_EXT_FLASH_INDEX);

    return ADMW_SUCCESS;
}

/*
 * Read a set of data samples stored in the device external flash memory.
 * This may be called at any time.
 */
ADMW_RESULT admw_GetExternalFlashData(
    ADMW_DEVICE_HANDLE    const hDevice,
    ADMW_DATA_SAMPLE    * const pSamples,
    uint32_t                   const nStartIndex,
    uint32_t                   const nRequested,
    uint32_t                 * const pnReturned)
{
    ADMW_DEVICE_CONTEXT *pCtx = hDevice;
    uint16_t command = ADMW1001_HOST_COMMS_READ_CMD |
        (REG_CORE_EXT_FLASH_DATA & ADMW1001_HOST_COMMS_ADR_MASK);
    uint8_t commandData[2] = {
        command >> 8,
        command & 0xFF
    };
    uint8_t commandResponse[2];
    unsigned nValidSamples = 0;
    ADMW_RESULT eRet = ADMW_SUCCESS;

    /* Setup initial sample */
    WRITE_REG_U32(hDevice, nStartIndex, CORE_EXT_FLASH_INDEX);

    /* Send flash read command */
    do {
        eRet = admw_SpiTransfer(pCtx->hSpi, commandData, commandResponse,
                                     sizeof(command), false);
        if (eRet)
        {
            ADMW_LOG_ERROR("Failed to send read command for external flash");
            return eRet;
        }
        
        admw_TimeDelayUsec(ADMW1001_HOST_COMMS_XFER_DELAY);
    } while ((commandResponse[0] != ADMW1001_HOST_COMMS_CMD_RESP_0) ||
             (commandResponse[1] != ADMW1001_HOST_COMMS_CMD_RESP_1));

    /* Read samples from external flash memory */
    for (unsigned i = 0; i < nRequested; i++)
    {
        ADMW1001_Sensor_Result_t sensorResult;
        bool bHoldCs = true;

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

        eRet = admw_SpiTransfer(pCtx->hSpi, NULL, (uint8_t *) (&sensorResult),
                                     8, bHoldCs);
        if (eRet)
        {
            ADMW_LOG_ERROR("Failed to read data from external flash");
            return eRet;
        }

        ADMW_DATA_SAMPLE *pSample = &pSamples[nValidSamples];

        pSample->status = (ADMW_DEVICE_STATUS_FLAGS)0;
        if (sensorResult.Ch_Error)
            pSample->status |= ADMW_DEVICE_STATUS_ERROR;
        if (sensorResult.Ch_Alert)
            pSample->status |= ADMW_DEVICE_STATUS_ALERT;

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

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

        nValidSamples++;
    }
    *pnReturned = nValidSamples;

    admw_TimeDelayUsec(ADMW1001_HOST_COMMS_XFER_DELAY);

    return eRet;
}


/*
 * Store the LUT data 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.
 */
ADMW_RESULT admw_SaveLutData(
    ADMW_DEVICE_HANDLE    const hDevice)
{
    return executeCommand(hDevice, CORE_COMMAND_SAVE_LUT, true);
}

/*
 * Restore the LUT data from persistent memory on the device.
 * No other command must be running when this is called.
 */
ADMW_RESULT admw_RestoreLutData(
    ADMW_DEVICE_HANDLE    const hDevice)
{
    return executeCommand(hDevice, CORE_COMMAND_LOAD_LUT, true);
}

/*
 * Stop the measurement cycles on the device.
 * To be used only if a measurement command is currently running.
 */
ADMW_RESULT admw_StopMeasurement(
    ADMW_DEVICE_HANDLE    const hDevice)
{
    return executeCommand(hDevice, 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.
 */
ADMW_RESULT admw_RunDiagnostics(
    ADMW_DEVICE_HANDLE    const hDevice)
{
    return executeCommand(hDevice, CORE_COMMAND_RUN_DIAGNOSTICS, true);
}

/*
 * Run self-calibration routines on the device.
 * Calibration is executed according to the current applied settings.
 * No other command must be running when this is called.
 */
ADMW_RESULT admw_RunCalibration(
    ADMW_DEVICE_HANDLE    const hDevice)
{
    return executeCommand(hDevice, CORE_COMMAND_SELF_CALIBRATION, true);
}

/*
 * Run digital calibration routines on the device.
 * Calibration is executed according to the current applied settings.
 * No other command must be running when this is called.
 */
ADMW_RESULT admw_RunDigitalCalibration(
    ADMW_DEVICE_HANDLE    const hDevice)
{
    return executeCommand(hDevice, CORE_COMMAND_CALIBRATE_DIGITAL, true);
}

/*
 * Read a set of data samples from the device.
 * This may be called at any time.
 */
ADMW_RESULT admw_GetData(
    ADMW_DEVICE_HANDLE    const hDevice,
    ADMW_MEASUREMENT_MODE const eMeasurementMode,
    ADMW_DATA_SAMPLE    * const pSamples,
    uint8_t                    const nBytesPerSample,
    uint32_t                   const nRequested,
    uint32_t                 * const pnReturned)
{
    ADMW1001_Sensor_Result_t sensorResult;
    ADMW_DEVICE_CONTEXT *pCtx = hDevice;
    uint16_t command = ADMW1001_HOST_COMMS_READ_CMD |
        (REG_CORE_DATA_FIFO & ADMW1001_HOST_COMMS_ADR_MASK);
    uint8_t commandData[2] = {
        command >> 8,
        command & 0xFF
    };
    uint8_t commandResponse[2];
    unsigned nValidSamples = 0;
    ADMW_RESULT eRet = ADMW_SUCCESS;

    do {
        eRet = admw_SpiTransfer(pCtx->hSpi, commandData, commandResponse,
                                     sizeof(command), false);
        if (eRet)
        {
            ADMW_LOG_ERROR("Failed to send read command for FIFO register");
            return eRet;
        }
        admw_TimeDelayUsec(ADMW1001_HOST_COMMS_XFER_DELAY);
    } while ((commandResponse[0] != ADMW1001_HOST_COMMS_CMD_RESP_0) ||
             (commandResponse[1] != ADMW1001_HOST_COMMS_CMD_RESP_1));

    for (unsigned i = 0; i < nRequested; i++)
    {
        bool bHoldCs = true;

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

        getDataCnt++;

        eRet = admw_SpiTransfer(pCtx->hSpi, NULL, &sensorResult,
                                     nBytesPerSample, bHoldCs);
        if (eRet)
        {
            ADMW_LOG_ERROR("Failed to read data from FIFO register");
            return eRet;
        }

        if (! sensorResult.Ch_Valid)
        {
            /*
             * Reading an invalid sample indicates that there are no
             * more samples available or we've lost sync with the device.
             * In the latter case, it might be recoverable, but return here
             * to let the application check the device status and decide itself.
             */
            eRet = ADMW_INCOMPLETE;
            break;
        }

        ADMW_DATA_SAMPLE *pSample = &pSamples[nValidSamples];

        pSample->status = (ADMW_DEVICE_STATUS_FLAGS)0;
        if (sensorResult.Ch_Error)
            pSample->status |= ADMW_DEVICE_STATUS_ERROR;
        if (sensorResult.Ch_Alert)
            pSample->status |= ADMW_DEVICE_STATUS_ALERT;

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

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

        nValidSamples++;

        admw_TimeDelayUsec(ADMW1001_HOST_COMMS_XFER_DELAY);
    }
    *pnReturned = nValidSamples;

    return eRet;
}

/*
 * Close the given ADMW device.
 */
ADMW_RESULT admw_Close(
    ADMW_DEVICE_HANDLE    const hDevice)
{
    ADMW_DEVICE_CONTEXT *pCtx = hDevice;

    admw_GpioClose(pCtx->hGpio);
    admw_SpiClose(pCtx->hSpi);
    admw_LogClose();

    return ADMW_SUCCESS;
}

ADMW_RESULT admw1001_WriteRegister(
    ADMW_DEVICE_HANDLE hDevice,
    uint16_t nAddress,
    void *pData,
    unsigned nLength)
{
    ADMW_RESULT eRet;
    ADMW_DEVICE_CONTEXT *pCtx = hDevice;
    uint16_t command = ADMW1001_HOST_COMMS_WRITE_CMD |
        (nAddress & ADMW1001_HOST_COMMS_ADR_MASK);
    uint8_t commandData[2] = {
        command >> 8,
        command & 0xFF
    };
    uint8_t commandResponse[2];

    do {
        eRet = admw_SpiTransfer(pCtx->hSpi, commandData, commandResponse,
                                     sizeof(command), false);
        if (eRet)
        {
            ADMW_LOG_ERROR("Failed to send write command for register %u",
                                nAddress);
            return eRet;
        }

        admw_TimeDelayUsec(ADMW1001_HOST_COMMS_XFER_DELAY);
    } while ((commandResponse[0] != ADMW1001_HOST_COMMS_CMD_RESP_0) ||
             (commandResponse[1] != ADMW1001_HOST_COMMS_CMD_RESP_1));

    eRet = admw_SpiTransfer(pCtx->hSpi, pData, NULL, nLength, false);
    if (eRet)
    {
        ADMW_LOG_ERROR("Failed to write data (%dB) to register %u",
                            nLength, nAddress);
        return eRet;
    }

    admw_TimeDelayUsec(ADMW1001_HOST_COMMS_XFER_DELAY);

    return ADMW_SUCCESS;
}

ADMW_RESULT admw1001_ReadRegister(
    ADMW_DEVICE_HANDLE hDevice,
    uint16_t nAddress,
    void *pData,
    unsigned nLength)
{
    ADMW_RESULT eRet;
    ADMW_DEVICE_CONTEXT *pCtx = hDevice;
    uint16_t command = ADMW1001_HOST_COMMS_READ_CMD |
        (nAddress & ADMW1001_HOST_COMMS_ADR_MASK);
    uint8_t commandData[2] = {
        command >> 8,
        command & 0xFF
    };
    uint8_t commandResponse[2];

    do {
        eRet = admw_SpiTransfer(pCtx->hSpi, commandData, commandResponse,
                                     sizeof(command), false);
        if (eRet)
        {
            ADMW_LOG_ERROR("Failed to send read command for register %u",
                                nAddress);
            return eRet;
        }

        admw_TimeDelayUsec(ADMW1001_HOST_COMMS_XFER_DELAY);
    } while ((commandResponse[0] != ADMW1001_HOST_COMMS_CMD_RESP_0) ||
             (commandResponse[1] != ADMW1001_HOST_COMMS_CMD_RESP_1));

    eRet = admw_SpiTransfer(pCtx->hSpi, NULL, pData, nLength, false);
    if (eRet)
    {
        ADMW_LOG_ERROR("Failed to read data (%uB) from register %u",
                            nLength, nAddress);
        return eRet;
    }

    admw_TimeDelayUsec(ADMW1001_HOST_COMMS_XFER_DELAY);

    return ADMW_SUCCESS;
}

ADMW_RESULT admw_GetDeviceReadyState(
    ADMW_DEVICE_HANDLE   const hDevice,
    bool                  * const bReady)
{
    ADMW_SPI_Chip_Type_t chipTypeReg;

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

    return ADMW_SUCCESS;
}

ADMW_RESULT admw1001_GetDataReadyModeInfo(
    ADMW_DEVICE_HANDLE         const hDevice,
    ADMW_MEASUREMENT_MODE      const eMeasurementMode,
    ADMW1001_OPERATING_MODE * const peOperatingMode,
    ADMW1001_DATAREADY_MODE * const peDataReadyMode,
    uint32_t                      * const pnSamplesPerDataready,
    uint32_t                      * const pnSamplesPerCycle,
    uint8_t                       * const pnBytesPerSample)
{
    unsigned nChannelsEnabled = 0;
    unsigned nSamplesPerCycle = 0;

    CORE_Mode_t modeReg;
    READ_REG_U8(hDevice, modeReg.VALUE8, CORE_MODE);

    if ((eMeasurementMode == ADMW_MEASUREMENT_MODE_HEALTHCHECK) ||
        (modeReg.Conversion_Mode == CORE_MODE_SINGLECYCLE))
        *peOperatingMode = ADMW1001_OPERATING_MODE_SINGLECYCLE;
    else if (modeReg.Conversion_Mode == CORE_MODE_MULTICYCLE)
        *peOperatingMode = ADMW1001_OPERATING_MODE_MULTICYCLE;
    else
        *peOperatingMode = ADMW1001_OPERATING_MODE_CONTINUOUS;


    /* FFT mode is quite different to the other modes:
     * - Each FFT result produces a batch of samples
     * - The size of the batch depends on selected FFT size and output config options
     * - DATAREADY will fire for each FFT result (once per channel)
     * - The size of the cycle depends on the number of channels enabled for FFT
     */
    if (eMeasurementMode == ADMW_MEASUREMENT_MODE_FFT)
    {
        CORE_FFT_Config_t fftConfigReg;

        unsigned nFftChannels;
        unsigned nSamplesPerChannel;

        READ_REG_U32(hDevice, fftConfigReg.VALUE32, CORE_FFT_CONFIG);

        nFftChannels = fftConfigReg.FFT_Num_Channels + 1;

        if (fftConfigReg.FFT_Output == CORE_FFT_CONFIG_FFT_OUTPUT_MAX16)
        {
            nSamplesPerChannel = 16;
            *pnBytesPerSample = 8;
        }
        else if (fftConfigReg.FFT_Output == CORE_FFT_CONFIG_FFT_OUTPUT_FULL)
        {
            nSamplesPerChannel = (256 << fftConfigReg.FFT_Num_Bins) >> 1;
            *pnBytesPerSample = 5;
        }
        else if (fftConfigReg.FFT_Output == CORE_FFT_CONFIG_FFT_OUTPUT_FULL_WITH_RAW)
        {
            nSamplesPerChannel = (256 << fftConfigReg.FFT_Num_Bins);
            *pnBytesPerSample = 8;
        }
        else
        {
            ADMW_LOG_ERROR("Invalid FFT output format option %d configured",
                                fftConfigReg.FFT_Output);
            return ADMW_INVALID_PARAM;
        }

        *pnSamplesPerDataready = nSamplesPerChannel;
        *pnSamplesPerCycle = nSamplesPerChannel * nFftChannels;

        *peDataReadyMode = ADMW1001_DATAREADY_PER_CYCLE;

        if (modeReg.FFT_Mode == CORE_MODE_FFT_MODE_CONTINUOUS)
        {
            *peOperatingMode = ADMW1001_OPERATING_MODE_CONTINUOUS;
        }
        else
        {
            *peOperatingMode = ADMW1001_OPERATING_MODE_SINGLECYCLE;
        }
    }
    else
    {
        if (eMeasurementMode == ADMW_MEASUREMENT_MODE_OMIT_RAW)
        {
            *pnBytesPerSample = 5;
        }
        else
        {
            *pnBytesPerSample = 8;
        }

        for (ADMW1001_CHANNEL_ID chId = ADMW1001_CHANNEL_ID_CJC_0;
             chId < ADMW1001_MAX_CHANNELS;
             chId++)
        {
            CORE_Sensor_Details_t sensorDetailsReg;
            CORE_Channel_Count_t channelCountReg;

            if (ADMW1001_CHANNEL_IS_VIRTUAL(chId))
                continue;

            READ_REG_U8(hDevice, channelCountReg.VALUE8, CORE_CHANNEL_COUNTn(chId));
            READ_REG_U32(hDevice, sensorDetailsReg.VALUE32, CORE_SENSOR_DETAILSn(chId));

            if (channelCountReg.Channel_Enable && !sensorDetailsReg.Do_Not_Publish)
            {
                CORE_Sensor_Type_t sensorTypeReg;
                unsigned nActualChannels = 1;

                READ_REG_U16(hDevice, sensorTypeReg.VALUE16, CORE_SENSOR_TYPEn(chId));

                if (chId == ADMW1001_CHANNEL_ID_SPI_0)
                {
                    /* Some sensors automatically generate samples on additional "virtual" channels
                     * so these channels must be counted as active when those sensors are selected
                     * and we use the count from the corresponding "physical" channel */
                    if ((sensorTypeReg.Sensor_Type >=
                         CORE_SENSOR_TYPE_SENSOR_SPI_ACCELEROMETER_A_DEF_L1) &&
                        (sensorTypeReg.Sensor_Type <=
                         CORE_SENSOR_TYPE_SENSOR_SPI_ACCELEROMETER_B_ADV_L2))
                        nActualChannels += 2;
                }

                nChannelsEnabled += nActualChannels;
                if (eMeasurementMode == ADMW_MEASUREMENT_MODE_HEALTHCHECK)
                    /* Assume a single sample per channel in test mode */
                    nSamplesPerCycle += nActualChannels;
                else
                    nSamplesPerCycle += nActualChannels *
                        (channelCountReg.Channel_Count + 1);
            }
        }

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

        *pnSamplesPerCycle = nSamplesPerCycle;

        if (modeReg.Drdy_Mode == CORE_MODE_DRDY_PER_CONVERSION)
        {
            *pnSamplesPerDataready = 1;
        }
        else if (modeReg.Drdy_Mode == CORE_MODE_DRDY_PER_CYCLE)
        {
            *pnSamplesPerDataready = nSamplesPerCycle;
        }
        else
        {
            /* Assume DRDY will be asserted after max. 1 cycle in test mode */
            if (eMeasurementMode == ADMW_MEASUREMENT_MODE_HEALTHCHECK)
            {
                *pnSamplesPerDataready = nSamplesPerCycle;
            }
            else
            {
                CORE_Fifo_Num_Cycles_t fifoNumCyclesReg;
                READ_REG_U8(hDevice, fifoNumCyclesReg.VALUE8, CORE_FIFO_NUM_CYCLES);

                *pnSamplesPerDataready =
                nSamplesPerCycle * fifoNumCyclesReg.Fifo_Num_Cycles;
            }
        }

        if (modeReg.Drdy_Mode == CORE_MODE_DRDY_PER_CONVERSION)
            *peDataReadyMode = ADMW1001_DATAREADY_PER_CONVERSION;
        else if (modeReg.Drdy_Mode == CORE_MODE_DRDY_PER_CYCLE)
            *peDataReadyMode = ADMW1001_DATAREADY_PER_CYCLE;
        else
        {
            /* Assume DRDY will be asserted after max. 1 cycle in test mode */
            if (eMeasurementMode == ADMW_MEASUREMENT_MODE_HEALTHCHECK)
                *peDataReadyMode = ADMW1001_DATAREADY_PER_CYCLE;
            else
                *peDataReadyMode = ADMW1001_DATAREADY_PER_MULTICYCLE_BURST;
        }
    }

    return ADMW_SUCCESS;
}

ADMW_RESULT admw_GetProductID(
    ADMW_DEVICE_HANDLE hDevice,
    ADMW_PRODUCT_ID *pProductId)
{
    ADMW_SPI_Product_ID_L_t productIdLoReg;
    ADMW_SPI_Product_ID_H_t productIdHiReg;

    READ_REG_U8(hDevice, productIdLoReg.VALUE8, SPI_PRODUCT_ID_L);
    READ_REG_U8(hDevice, productIdHiReg.VALUE8, SPI_PRODUCT_ID_H);

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

static ADMW_RESULT admw_SetPowerMode(
    ADMW_DEVICE_HANDLE hDevice,
    ADMW1001_POWER_MODE powerMode)
{
    CORE_Power_Config_t powerConfigReg;

    if (powerMode == ADMW1001_POWER_MODE_LOW)
    {
        powerConfigReg.Power_Mode_ADC = CORE_POWER_CONFIG_ADC_LOW_POWER;
    }
    else if (powerMode == ADMW1001_POWER_MODE_MID)
    {
        powerConfigReg.Power_Mode_ADC = CORE_POWER_CONFIG_ADC_MID_POWER;
    }
    else if (powerMode == ADMW1001_POWER_MODE_FULL)
    {
        powerConfigReg.Power_Mode_ADC = CORE_POWER_CONFIG_ADC_FULL_POWER;
    }
    else
    {
        ADMW_LOG_ERROR("Invalid power mode %d specified", powerMode);
        return ADMW_INVALID_PARAM;
    }

    WRITE_REG_U8(hDevice, powerConfigReg.VALUE8, CORE_POWER_CONFIG);

    return ADMW_SUCCESS;
}

ADMW_RESULT admw1001_SetPowerConfig(
    ADMW_DEVICE_HANDLE hDevice,
    ADMW1001_POWER_CONFIG *pPowerConfig)
{
    ADMW_RESULT eRet;

    eRet = admw_SetPowerMode(hDevice, pPowerConfig->powerMode);
    if (eRet != ADMW_SUCCESS)
    {
        ADMW_LOG_ERROR("Failed to set power mode");
        return eRet;
    }

    return ADMW_SUCCESS;
}

static ADMW_RESULT admw_SetMode(
    ADMW_DEVICE_HANDLE hDevice,
    ADMW1001_OPERATING_MODE eOperatingMode,
    ADMW1001_DATAREADY_MODE eDataReadyMode,
    ADMW1001_CALIBRATION_MODE eCalibrationMode,
    bool bEnableExtFlash)
{
    CORE_Mode_t modeReg;

    modeReg.VALUE8 = REG_RESET_VAL(CORE_MODE);

    if (eOperatingMode == ADMW1001_OPERATING_MODE_SINGLECYCLE)
    {
        modeReg.Conversion_Mode = CORE_MODE_SINGLECYCLE;
    }
    else if (eOperatingMode == ADMW1001_OPERATING_MODE_CONTINUOUS)
    {
        modeReg.Conversion_Mode = CORE_MODE_CONTINUOUS;
    }
    else if (eOperatingMode == ADMW1001_OPERATING_MODE_MULTICYCLE)
    {
        modeReg.Conversion_Mode = CORE_MODE_MULTICYCLE;
    }
    else
    {
        ADMW_LOG_ERROR("Invalid operating mode %d specified",
                            eOperatingMode);
        return ADMW_INVALID_PARAM;
    }

    if (eDataReadyMode == ADMW1001_DATAREADY_PER_CONVERSION)
    {
        modeReg.Drdy_Mode = CORE_MODE_DRDY_PER_CONVERSION;
    }
    else if (eDataReadyMode == ADMW1001_DATAREADY_PER_CYCLE)
    {
        modeReg.Drdy_Mode = CORE_MODE_DRDY_PER_CYCLE;
    }
    else if (eDataReadyMode == ADMW1001_DATAREADY_PER_MULTICYCLE_BURST)
    {
        if (eOperatingMode != ADMW1001_OPERATING_MODE_MULTICYCLE)
        {
            ADMW_LOG_ERROR(
                "Data-ready mode %d cannot be used with operating mode %d",
                eDataReadyMode, eOperatingMode);
            return ADMW_INVALID_PARAM;
        }
        else
        {
            modeReg.Drdy_Mode = CORE_MODE_DRDY_PER_FIFO_FILL;
        }
    }
    else
    {
        ADMW_LOG_ERROR("Invalid data-ready mode %d specified", eDataReadyMode);
        return ADMW_INVALID_PARAM;
    }

    if (eCalibrationMode == ADMW1001_NO_CALIBRATION)
    {
        modeReg.Calibration_Method = CORE_MODE_NO_CAL;
    }
    else if (eCalibrationMode == ADMW1001_DO_CALIBRATION)
    {
        modeReg.Calibration_Method = CORE_MODE_DO_CAL;
    }
    else
    {
        ADMW_LOG_ERROR("Invalid calibration mode %d specified",
                            eCalibrationMode);
        return ADMW_INVALID_PARAM;
    }

    modeReg.Ext_Flash_Store = (bEnableExtFlash ?
                                CORE_MODE_EXT_FLASH_USED :
                                CORE_MODE_EXT_FLASH_NOT_USED);

    WRITE_REG_U8(hDevice, modeReg.VALUE8, CORE_MODE);

    return ADMW_SUCCESS;
}

ADMW_RESULT admw_SetCycleControl(
    ADMW_DEVICE_HANDLE hDevice,
    uint32_t nCycleInterval,

#ifdef __V2_3_CFG_FMT__    
    ADMW1001_CYCLE_TYPE eCycleType,
    ADMW1001_FILTER_SETTLING eFilterSettling)
#else
    ADMW1001_CYCLE_TYPE eCycleType)
#endif
{
    CORE_Cycle_Control_t cycleControlReg;

    cycleControlReg.VALUE16 = REG_RESET_VAL(CORE_CYCLE_CONTROL);

    if (nCycleInterval < (1 << 12))
    {
        cycleControlReg.Cycle_Time_Units = CORE_CYCLE_CONTROL_MICROSECONDS;
    }
    else if (nCycleInterval < (1000 * (1 << 12)))
    {
        cycleControlReg.Cycle_Time_Units = CORE_CYCLE_CONTROL_MILLISECONDS;
        nCycleInterval /= 1000;
    }
    else
    {
        cycleControlReg.Cycle_Time_Units = CORE_CYCLE_CONTROL_SECONDS;
        nCycleInterval /= 1000000;
    }

    CHECK_REG_FIELD_VAL(CORE_CYCLE_CONTROL_CYCLE_TIME, nCycleInterval);
    cycleControlReg.Cycle_Time = nCycleInterval;

    if (eCycleType == ADMW1001_CYCLE_TYPE_SWITCH)
    {
        cycleControlReg.Cycle_Type = CORE_CYCLE_CONTROL_CYCLE_TYPE_SWITCH;
    }
    else if (eCycleType == ADMW1001_CYCLE_TYPE_FULL)
    {
        cycleControlReg.Cycle_Type = CORE_CYCLE_CONTROL_CYCLE_TYPE_FULL;
    }
    else
    {
        ADMW_LOG_ERROR("Invalid cycle type %d specified", eCycleType);
        return ADMW_INVALID_PARAM;
    }

#ifdef __V2_3_CFG_FMT__   
    if (eFilterSettling == ADMW1001_FILTER_SETTLING_ALWAYS)
    {
        cycleControlReg.Filter_Settling = CORE_CYCLE_CONTROL_FILTER_SETTLING_SETTLED;
    }
    else if (eFilterSettling == ADMW1001_FILTER_SETTLING_FAST)
    {
        cycleControlReg.Filter_Settling = CORE_CYCLE_CONTROL_FILTER_SETTLING_FAST;
    }
    else
    {
        ADMW_LOG_ERROR("Invalid filter settling option %d specified", eFilterSettling);
        return ADMW_INVALID_PARAM;
    }
#endif
    
    WRITE_REG_U16(hDevice, cycleControlReg.VALUE16, CORE_CYCLE_CONTROL);

    return ADMW_SUCCESS;
}

static ADMW_RESULT admw_SetMultiCycleConfig(
    ADMW_DEVICE_HANDLE hDevice,
    ADMW1001_MULTICYCLE_CONFIG *pMultiCycleConfig)
{
    CHECK_REG_FIELD_VAL(CORE_FIFO_NUM_CYCLES_FIFO_NUM_CYCLES,
                        pMultiCycleConfig->cyclesPerBurst);

    WRITE_REG_U8(hDevice, pMultiCycleConfig->cyclesPerBurst,
                 CORE_FIFO_NUM_CYCLES);

    WRITE_REG_U32(hDevice, pMultiCycleConfig->burstInterval,
                  CORE_MULTI_CYCLE_REPEAT_INTERVAL);

    return ADMW_SUCCESS;
}

static ADMW_RESULT admw_SetExternalReferenceValues(
    ADMW_DEVICE_HANDLE hDevice,
    float32_t externalRef1Value,
    float32_t externalRef2Value)
{
    WRITE_REG_FLOAT(hDevice, externalRef1Value, CORE_EXTERNAL_REFERENCE1);
    WRITE_REG_FLOAT(hDevice, externalRef2Value, CORE_EXTERNAL_REFERENCE2);

    return ADMW_SUCCESS;
}

ADMW_RESULT admw1001_SetMeasurementConfig(
    ADMW_DEVICE_HANDLE hDevice,
    ADMW1001_MEASUREMENT_CONFIG *pMeasConfig)
{
    ADMW_RESULT eRet;

    eRet = admw_SetMode(hDevice,
                             pMeasConfig->operatingMode,
                             pMeasConfig->dataReadyMode,
                             pMeasConfig->calibrationMode,
                             pMeasConfig->enableExternalFlash);
    if (eRet != ADMW_SUCCESS)
    {
        ADMW_LOG_ERROR("Failed to set operating mode");
        return eRet;
    }

    eRet = admw_SetCycleControl(hDevice,
                                     pMeasConfig->cycleInterval,
                                     pMeasConfig->cycleType);
    if (eRet != ADMW_SUCCESS)
    {
        ADMW_LOG_ERROR("Failed to set cycle control");
        return eRet;
    }

    if (pMeasConfig->operatingMode == ADMW1001_OPERATING_MODE_MULTICYCLE)
    {
        eRet = admw_SetMultiCycleConfig(hDevice,
                                            &pMeasConfig->multiCycleConfig);
        if (eRet != ADMW_SUCCESS)
        {
            ADMW_LOG_ERROR("Failed to set multi-cycle configuration");
            return eRet;
        }
    }

    eRet = admw_SetExternalReferenceValues(hDevice,
                                                pMeasConfig->externalRef1Value,
                                                pMeasConfig->externalRef2Value);
    if (eRet != ADMW_SUCCESS)
    {
        ADMW_LOG_ERROR("Failed to set external reference values");
        return eRet;
    }

    return ADMW_SUCCESS;
}

ADMW_RESULT admw1001_SetDiagnosticsConfig(
    ADMW_DEVICE_HANDLE hDevice,
    ADMW1001_DIAGNOSTICS_CONFIG *pDiagnosticsConfig)
{
    CORE_Diagnostics_Control_t diagnosticsControlReg;

    diagnosticsControlReg.VALUE16 = REG_RESET_VAL(CORE_DIAGNOSTICS_CONTROL);

    if (pDiagnosticsConfig->disableGlobalDiag)
        diagnosticsControlReg.Diag_Global_En = 0;
    else
        diagnosticsControlReg.Diag_Global_En = 1;

    if (pDiagnosticsConfig->disableMeasurementDiag)
        diagnosticsControlReg.Diag_Meas_En = 0;
    else
        diagnosticsControlReg.Diag_Meas_En = 1;

    switch (pDiagnosticsConfig->osdFrequency)
    {
    case ADMW1001_OPEN_SENSOR_DIAGNOSTICS_DISABLED:
        diagnosticsControlReg.Diag_OSD_Freq = CORE_DIAGNOSTICS_CONTROL_OCD_OFF;
        break;
    case ADMW1001_OPEN_SENSOR_DIAGNOSTICS_PER_CYCLE:
        diagnosticsControlReg.Diag_OSD_Freq = CORE_DIAGNOSTICS_CONTROL_OCD_PER_1_CYCLE;
        break;
    case ADMW1001_OPEN_SENSOR_DIAGNOSTICS_PER_100_CYCLES:
        diagnosticsControlReg.Diag_OSD_Freq = CORE_DIAGNOSTICS_CONTROL_OCD_PER_100_CYCLES;
        break;
    case ADMW1001_OPEN_SENSOR_DIAGNOSTICS_PER_1000_CYCLES:
        diagnosticsControlReg.Diag_OSD_Freq = CORE_DIAGNOSTICS_CONTROL_OCD_PER_1000_CYCLES;
        break;
    default:
        ADMW_LOG_ERROR("Invalid open-sensor diagnostic frequency %d specified",
                            pDiagnosticsConfig->osdFrequency);
        return ADMW_INVALID_PARAM;
    }

    WRITE_REG_U16(hDevice, diagnosticsControlReg.VALUE16, CORE_DIAGNOSTICS_CONTROL);

    return ADMW_SUCCESS;
}

ADMW_RESULT admw1001_SetFftConfig(
    ADMW_DEVICE_HANDLE hDevice,
    ADMW1001_FFT_CONFIG *pFftConfig,
    ADMW1001_CHANNEL_CONFIG *pChannels)
{
    CORE_FFT_Config_t fftConfigReg;
    CORE_Mode_t modeReg;
    uint32_t numFftChannels = 0;

    fftConfigReg.VALUE32 = REG_RESET_VAL(CORE_FFT_CONFIG);

    for (ADMW1001_CHANNEL_ID id = ADMW1001_CHANNEL_ID_CJC_0;
         id < ADMW1001_MAX_CHANNELS;
         id++)
    {
        if (pChannels[id].enableFFT)
        {
            if (numFftChannels >= 4) /* TODO - temporary limit */
            {
                ADMW_LOG_ERROR("Maximum limit of 4 FFT channels exceeded");
                return ADMW_INVALID_PARAM;
            }

            numFftChannels++;
        }
    }

    if (numFftChannels > 0)
    {
        fftConfigReg.FFT_Num_Channels = numFftChannels - 1;

        switch (pFftConfig->size)
        {
        case ADMW1001_FFT_SIZE_256:
            fftConfigReg.FFT_Num_Bins = CORE_FFT_CONFIG_FFT_BINS_256;
            break;
        case ADMW1001_FFT_SIZE_512:
            fftConfigReg.FFT_Num_Bins = CORE_FFT_CONFIG_FFT_BINS_512;
            break;
        case ADMW1001_FFT_SIZE_1024:
            fftConfigReg.FFT_Num_Bins = CORE_FFT_CONFIG_FFT_BINS_1024;
            break;
        case ADMW1001_FFT_SIZE_2048:
            fftConfigReg.FFT_Num_Bins = CORE_FFT_CONFIG_FFT_BINS_2048;
            break;
        default:
            ADMW_LOG_ERROR("Invalid FFT size option %d specified",
                                pFftConfig->size);
            return ADMW_INVALID_PARAM;
        }

        switch (pFftConfig->window)
        {
        case ADMW1001_FFT_WINDOW_NONE:
            fftConfigReg.FFT_Window = CORE_FFT_CONFIG_FFT_WINDOW_NONE;
            break;
        case ADMW1001_FFT_WINDOW_HANN:
            fftConfigReg.FFT_Window = CORE_FFT_CONFIG_FFT_WINDOW_HANN;
            break;
        case ADMW1001_FFT_WINDOW_BLACKMAN_HARRIS:
            fftConfigReg.FFT_Window = CORE_FFT_CONFIG_FFT_WINDOW_BLACKMANN_HARRIS;
            break;
        default:
            ADMW_LOG_ERROR("Invalid FFT window option %d specified",
                                pFftConfig->window);
            return ADMW_INVALID_PARAM;
        }

        switch (pFftConfig->output)
        {
        case ADMW1001_FFT_OUTPUT_FULL:
            fftConfigReg.FFT_Output = CORE_FFT_CONFIG_FFT_OUTPUT_FULL;
            break;
        case ADMW1001_FFT_OUTPUT_MAX16:
            fftConfigReg.FFT_Output = CORE_FFT_CONFIG_FFT_OUTPUT_MAX16;
            break;
        case ADMW1001_FFT_OUTPUT_FULL_WITH_RAW:
            fftConfigReg.FFT_Output = CORE_FFT_CONFIG_FFT_OUTPUT_FULL_WITH_RAW;
            break;
        default:
            ADMW_LOG_ERROR("Invalid FFT output format option %d specified",
                                pFftConfig->output);
            return ADMW_INVALID_PARAM;
        }
    }
    WRITE_REG_U32(hDevice, fftConfigReg.VALUE32, CORE_FFT_CONFIG);

    if (numFftChannels > 0)
    {
        READ_REG_U8(hDevice, modeReg.VALUE8, CORE_MODE);

        if (pFftConfig->mode == ADMW1001_FFT_MODE_SINGLE)
        {
            modeReg.FFT_Mode = CORE_MODE_FFT_MODE_SINGLE;
        }
        else if (pFftConfig->mode == ADMW1001_FFT_MODE_CONTINUOUS)
        {
            modeReg.FFT_Mode = CORE_MODE_FFT_MODE_CONTINUOUS;
        }
        else
        {
            ADMW_LOG_ERROR("Invalid FFT mode %d specified",
                                pFftConfig->mode);
            return ADMW_INVALID_PARAM;
        }

        WRITE_REG_U8(hDevice, modeReg.VALUE8, CORE_MODE);
    }

    return ADMW_SUCCESS;
}

ADMW_RESULT admw1001_SetChannelCount(
    ADMW_DEVICE_HANDLE hDevice,
    ADMW1001_CHANNEL_ID eChannelId,
    uint32_t nMeasurementsPerCycle)
{
    CORE_Channel_Count_t channelCountReg;

    channelCountReg.VALUE8 = REG_RESET_VAL(CORE_CHANNEL_COUNTn);

    if (nMeasurementsPerCycle > 0)
    {
        nMeasurementsPerCycle -= 1;

        CHECK_REG_FIELD_VAL(CORE_CHANNEL_COUNT_CHANNEL_COUNT,
                            nMeasurementsPerCycle);

        channelCountReg.Channel_Enable = 1;
        channelCountReg.Channel_Count = nMeasurementsPerCycle;
    }
    else
    {
        channelCountReg.Channel_Enable = 0;
    }

    WRITE_REG_U8(hDevice, channelCountReg.VALUE8, CORE_CHANNEL_COUNTn(eChannelId));

    return ADMW_SUCCESS;
}

ADMW_RESULT admw1001_SetChannelOptions(
    ADMW_DEVICE_HANDLE hDevice,
    ADMW1001_CHANNEL_ID eChannelId,
    ADMW1001_CHANNEL_PRIORITY ePriority,
    bool bEnableFft)
{
    CORE_Channel_Options_t channelOptionsReg;

    channelOptionsReg.VALUE8 = REG_RESET_VAL(CORE_CHANNEL_OPTIONSn);

    CHECK_REG_FIELD_VAL(CORE_CHANNEL_OPTIONS_CHANNEL_PRIORITY, ePriority);
    channelOptionsReg.Channel_Priority = ePriority;
    channelOptionsReg.FFT_Enable_Ch = bEnableFft ? 1 : 0;

    WRITE_REG_U8(hDevice, channelOptionsReg.VALUE8, CORE_CHANNEL_OPTIONSn(eChannelId));

    return ADMW_SUCCESS;
}

ADMW_RESULT admw1001_SetChannelSkipCount(
    ADMW_DEVICE_HANDLE hDevice,
    ADMW1001_CHANNEL_ID eChannelId,
    uint32_t nCycleSkipCount)
{
    CORE_Channel_Skip_t channelSkipReg;

    channelSkipReg.VALUE16 = REG_RESET_VAL(CORE_CHANNEL_SKIPn);

    CHECK_REG_FIELD_VAL(CORE_CHANNEL_SKIP_CHANNEL_SKIP, nCycleSkipCount);

    channelSkipReg.Channel_Skip = nCycleSkipCount;

    WRITE_REG_U16(hDevice, channelSkipReg.VALUE16, CORE_CHANNEL_SKIPn(eChannelId));

    return ADMW_SUCCESS;
}

static ADMW_RESULT admw_SetChannelAdcSensorType(
    ADMW_DEVICE_HANDLE hDevice,
    ADMW1001_CHANNEL_ID eChannelId,
    ADMW1001_ADC_SENSOR_TYPE sensorType)
{
    CORE_Sensor_Type_t sensorTypeReg;

    sensorTypeReg.VALUE16 = REG_RESET_VAL(CORE_SENSOR_TYPEn);

    /* Ensure that the sensor type is valid for this channel */
    switch(sensorType)
    {
    case ADMW1001_ADC_SENSOR_THERMOCOUPLE_J_DEF_L1:
    case ADMW1001_ADC_SENSOR_THERMOCOUPLE_K_DEF_L1:
    case ADMW1001_ADC_SENSOR_THERMOCOUPLE_T_DEF_L1:
    case ADMW1001_ADC_SENSOR_THERMOCOUPLE_1_DEF_L2:
    case ADMW1001_ADC_SENSOR_THERMOCOUPLE_2_DEF_L2:
    case ADMW1001_ADC_SENSOR_THERMOCOUPLE_3_DEF_L2:
    case ADMW1001_ADC_SENSOR_THERMOCOUPLE_4_DEF_L2:
    case ADMW1001_ADC_SENSOR_THERMOCOUPLE_J_ADV_L1:
    case ADMW1001_ADC_SENSOR_THERMOCOUPLE_K_ADV_L1:
    case ADMW1001_ADC_SENSOR_THERMOCOUPLE_T_ADV_L1:
    case ADMW1001_ADC_SENSOR_THERMOCOUPLE_1_ADV_L2:
    case ADMW1001_ADC_SENSOR_THERMOCOUPLE_2_ADV_L2:
    case ADMW1001_ADC_SENSOR_THERMOCOUPLE_3_ADV_L2:
    case ADMW1001_ADC_SENSOR_THERMOCOUPLE_4_ADV_L2:
    case ADMW1001_ADC_SENSOR_BRIDGE_4WIRE_1_DEF_L2:
    case ADMW1001_ADC_SENSOR_BRIDGE_4WIRE_2_DEF_L2:
    case ADMW1001_ADC_SENSOR_BRIDGE_4WIRE_3_DEF_L2:
    case ADMW1001_ADC_SENSOR_BRIDGE_4WIRE_4_DEF_L2:
    case ADMW1001_ADC_SENSOR_BRIDGE_4WIRE_1_ADV_L2:
    case ADMW1001_ADC_SENSOR_BRIDGE_4WIRE_2_ADV_L2:
    case ADMW1001_ADC_SENSOR_BRIDGE_4WIRE_3_ADV_L2:
    case ADMW1001_ADC_SENSOR_BRIDGE_4WIRE_4_ADV_L2:
    case ADMW1001_ADC_SENSOR_BRIDGE_6WIRE_1_DEF_L2:
    case ADMW1001_ADC_SENSOR_BRIDGE_6WIRE_2_DEF_L2:
    case ADMW1001_ADC_SENSOR_BRIDGE_6WIRE_3_DEF_L2:
    case ADMW1001_ADC_SENSOR_BRIDGE_6WIRE_4_DEF_L2:
    case ADMW1001_ADC_SENSOR_BRIDGE_6WIRE_1_ADV_L2:
    case ADMW1001_ADC_SENSOR_BRIDGE_6WIRE_2_ADV_L2:
    case ADMW1001_ADC_SENSOR_BRIDGE_6WIRE_3_ADV_L2:
    case ADMW1001_ADC_SENSOR_BRIDGE_6WIRE_4_ADV_L2:
        if (! ADMW1001_CHANNEL_IS_ADC_SENSOR(eChannelId))
        {
            ADMW_LOG_ERROR(
                "Invalid ADC sensor type %d specified for channel %d",
                sensorType, eChannelId);
            return ADMW_INVALID_PARAM;
        }
        break;
    case ADMW1001_ADC_SENSOR_RTD_2WIRE_PT100_DEF_L1:
    case ADMW1001_ADC_SENSOR_RTD_2WIRE_PT1000_DEF_L1:
    case ADMW1001_ADC_SENSOR_RTD_2WIRE_1_DEF_L2:
    case ADMW1001_ADC_SENSOR_RTD_2WIRE_2_DEF_L2:
    case ADMW1001_ADC_SENSOR_RTD_2WIRE_3_DEF_L2:
    case ADMW1001_ADC_SENSOR_RTD_2WIRE_4_DEF_L2:
    case ADMW1001_ADC_SENSOR_RTD_2WIRE_PT100_ADV_L1:
    case ADMW1001_ADC_SENSOR_RTD_2WIRE_PT1000_ADV_L1:
    case ADMW1001_ADC_SENSOR_RTD_2WIRE_1_ADV_L2:
    case ADMW1001_ADC_SENSOR_RTD_2WIRE_2_ADV_L2:
    case ADMW1001_ADC_SENSOR_RTD_2WIRE_3_ADV_L2:
    case ADMW1001_ADC_SENSOR_RTD_2WIRE_4_ADV_L2:
    case ADMW1001_ADC_SENSOR_RTD_3WIRE_PT100_DEF_L1:
    case ADMW1001_ADC_SENSOR_RTD_3WIRE_PT1000_DEF_L1:
    case ADMW1001_ADC_SENSOR_RTD_3WIRE_1_DEF_L2:
    case ADMW1001_ADC_SENSOR_RTD_3WIRE_2_DEF_L2:
    case ADMW1001_ADC_SENSOR_RTD_3WIRE_3_DEF_L2:
    case ADMW1001_ADC_SENSOR_RTD_3WIRE_4_DEF_L2:
    case ADMW1001_ADC_SENSOR_RTD_3WIRE_PT100_ADV_L1:
    case ADMW1001_ADC_SENSOR_RTD_3WIRE_PT1000_ADV_L1:
    case ADMW1001_ADC_SENSOR_RTD_3WIRE_1_ADV_L2:
    case ADMW1001_ADC_SENSOR_RTD_3WIRE_2_ADV_L2:
    case ADMW1001_ADC_SENSOR_RTD_3WIRE_3_ADV_L2:
    case ADMW1001_ADC_SENSOR_RTD_3WIRE_4_ADV_L2:
    case ADMW1001_ADC_SENSOR_RTD_4WIRE_PT100_DEF_L1:
    case ADMW1001_ADC_SENSOR_RTD_4WIRE_PT1000_DEF_L1:
    case ADMW1001_ADC_SENSOR_RTD_4WIRE_1_DEF_L2:
    case ADMW1001_ADC_SENSOR_RTD_4WIRE_2_DEF_L2:
    case ADMW1001_ADC_SENSOR_RTD_4WIRE_3_DEF_L2:
    case ADMW1001_ADC_SENSOR_RTD_4WIRE_4_DEF_L2:
    case ADMW1001_ADC_SENSOR_RTD_4WIRE_PT100_ADV_L1:
    case ADMW1001_ADC_SENSOR_RTD_4WIRE_PT1000_ADV_L1:
    case ADMW1001_ADC_SENSOR_RTD_4WIRE_1_ADV_L2:
    case ADMW1001_ADC_SENSOR_RTD_4WIRE_2_ADV_L2:
    case ADMW1001_ADC_SENSOR_RTD_4WIRE_3_ADV_L2:
    case ADMW1001_ADC_SENSOR_RTD_4WIRE_4_ADV_L2:
            if (!ADMW1001_CHANNEL_IS_ADC_CJC(eChannelId))
        {
            ADMW_LOG_ERROR(
                "Invalid ADC sensor type %d specified for channel %d",
                sensorType, eChannelId);
            return ADMW_INVALID_PARAM;
        }
        break;
    case ADMW1001_ADC_SENSOR_DIODE_2C_TYPEA_DEF_L1:
    case ADMW1001_ADC_SENSOR_DIODE_3C_TYPEA_DEF_L1:
    case ADMW1001_ADC_SENSOR_DIODE_2C_1_DEF_L2:
    case ADMW1001_ADC_SENSOR_DIODE_3C_1_DEF_L2:
    case ADMW1001_ADC_SENSOR_DIODE_2C_TYPEA_ADV_L1:
    case ADMW1001_ADC_SENSOR_DIODE_3C_TYPEA_ADV_L1:
    case ADMW1001_ADC_SENSOR_DIODE_2C_1_ADV_L2:
    case ADMW1001_ADC_SENSOR_DIODE_3C_1_ADV_L2:
    case ADMW1001_ADC_SENSOR_THERMISTOR_A_10K_DEF_L1:
    case ADMW1001_ADC_SENSOR_THERMISTOR_B_10K_DEF_L1:
    case ADMW1001_ADC_SENSOR_THERMISTOR_1_DEF_L2:
    case ADMW1001_ADC_SENSOR_THERMISTOR_2_DEF_L2:
    case ADMW1001_ADC_SENSOR_THERMISTOR_3_DEF_L2:
    case ADMW1001_ADC_SENSOR_THERMISTOR_4_DEF_L2:
    case ADMW1001_ADC_SENSOR_THERMISTOR_A_10K_ADV_L1:
    case ADMW1001_ADC_SENSOR_THERMISTOR_B_10K_ADV_L1:
    case ADMW1001_ADC_SENSOR_THERMISTOR_1_ADV_L2:
    case ADMW1001_ADC_SENSOR_THERMISTOR_2_ADV_L2:
    case ADMW1001_ADC_SENSOR_THERMISTOR_3_ADV_L2:
    case ADMW1001_ADC_SENSOR_THERMISTOR_4_ADV_L2:
        if (! (ADMW1001_CHANNEL_IS_ADC_SENSOR(eChannelId) ||
               ADMW1001_CHANNEL_IS_ADC_CJC(eChannelId)))
        {
            ADMW_LOG_ERROR(
                "Invalid ADC sensor type %d specified for channel %d",
                sensorType, eChannelId);
            return ADMW_INVALID_PARAM;
        }
        break;
    case ADMW1001_ADC_SENSOR_VOLTAGE:
    case ADMW1001_ADC_SENSOR_VOLTAGE_PRESSURE_A_DEF_L1:
    case ADMW1001_ADC_SENSOR_VOLTAGE_PRESSURE_B_DEF_L1:
    case ADMW1001_ADC_SENSOR_VOLTAGE_PRESSURE_1_DEF_L2:
    case ADMW1001_ADC_SENSOR_VOLTAGE_PRESSURE_2_DEF_L2:
    case ADMW1001_ADC_SENSOR_VOLTAGE_PRESSURE_A_ADV_L1:
    case ADMW1001_ADC_SENSOR_VOLTAGE_PRESSURE_B_ADV_L1:
    case ADMW1001_ADC_SENSOR_VOLTAGE_PRESSURE_1_ADV_L2:
    case ADMW1001_ADC_SENSOR_VOLTAGE_PRESSURE_2_ADV_L2:
        if (! ADMW1001_CHANNEL_IS_ADC_VOLTAGE(eChannelId))
        {
            ADMW_LOG_ERROR(
                "Invalid ADC sensor type %d specified for channel %d",
                sensorType, eChannelId);
            return ADMW_INVALID_PARAM;
        }
        break;
    case ADMW1001_ADC_SENSOR_CURRENT:
    case ADMW1001_ADC_SENSOR_CURRENT_PRESSURE_A_DEF_L1:
    case ADMW1001_ADC_SENSOR_CURRENT_PRESSURE_1_DEF_L2:
    case ADMW1001_ADC_SENSOR_CURRENT_PRESSURE_2_DEF_L2:
    case ADMW1001_ADC_SENSOR_CURRENT_PRESSURE_A_ADV_L1:
    case ADMW1001_ADC_SENSOR_CURRENT_PRESSURE_1_ADV_L2:
    case ADMW1001_ADC_SENSOR_CURRENT_PRESSURE_2_ADV_L2:
        if (! ADMW1001_CHANNEL_IS_ADC_CURRENT(eChannelId))
        {
            ADMW_LOG_ERROR(
                "Invalid ADC sensor type %d specified for channel %d",
                sensorType, eChannelId);
            return ADMW_INVALID_PARAM;
        }
        break;
    default:
        ADMW_LOG_ERROR("Invalid/unsupported ADC sensor type %d specified",
                            sensorType);
        return ADMW_INVALID_PARAM;
    }

    sensorTypeReg.Sensor_Type = sensorType;

    WRITE_REG_U16(hDevice, sensorTypeReg.VALUE16, CORE_SENSOR_TYPEn(eChannelId));

    return ADMW_SUCCESS;
}

static ADMW_RESULT admw_SetChannelAdcSensorDetails(
    ADMW_DEVICE_HANDLE hDevice,
    ADMW1001_CHANNEL_ID eChannelId,
    ADMW1001_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
 */
{
    ADMW1001_ADC_CHANNEL_CONFIG *pAdcChannelConfig = &pChannelConfig->adcChannelConfig;
    ADMW1001_ADC_REFERENCE_CONFIG *pRefConfig = &pAdcChannelConfig->reference;
    CORE_Sensor_Details_t sensorDetailsReg;

    sensorDetailsReg.VALUE32 = REG_RESET_VAL(CORE_SENSOR_DETAILSn);

    switch(pChannelConfig->measurementUnit)
    {
    case ADMW1001_MEASUREMENT_UNIT_FAHRENHEIT:
        sensorDetailsReg.Measurement_Units = CORE_SENSOR_DETAILS_UNITS_DEGF;
        break;
    case ADMW1001_MEASUREMENT_UNIT_CELSIUS:
        sensorDetailsReg.Measurement_Units = CORE_SENSOR_DETAILS_UNITS_DEGC;
        break;
    case ADMW1001_MEASUREMENT_UNIT_UNSPECIFIED:
        sensorDetailsReg.Measurement_Units = CORE_SENSOR_DETAILS_UNITS_UNSPECIFIED;
        break;
    default:
        ADMW_LOG_ERROR("Invalid measurement unit %d specified",
                            pChannelConfig->measurementUnit);
        return ADMW_INVALID_PARAM;
    }

    if (pChannelConfig->compensationChannel == ADMW1001_CHANNEL_ID_NONE)
    {
        sensorDetailsReg.Compensation_Disable = 1;
        sensorDetailsReg.Compensation_Channel = 0;
    }
    else
    {
        sensorDetailsReg.Compensation_Disable = 0;
        sensorDetailsReg.Compensation_Channel = pChannelConfig->compensationChannel;
    }

    switch(pRefConfig->type)
    {
    case ADMW1001_ADC_REFERENCE_RESISTOR_INTERNAL_1:
        sensorDetailsReg.Reference_Select = CORE_SENSOR_DETAILS_REF_RINT1;
        break;
    case ADMW1001_ADC_REFERENCE_RESISTOR_INTERNAL_2:
        sensorDetailsReg.Reference_Select = CORE_SENSOR_DETAILS_REF_RINT2;
        break;
    case ADMW1001_ADC_REFERENCE_VOLTAGE_INTERNAL:
        sensorDetailsReg.Reference_Select = CORE_SENSOR_DETAILS_REF_INT;
        break;
    case ADMW1001_ADC_REFERENCE_VOLTAGE_AVDD:
        sensorDetailsReg.Reference_Select = CORE_SENSOR_DETAILS_REF_AVDD;
        break;
    case ADMW1001_ADC_REFERENCE_RESISTOR_EXTERNAL_1:
        sensorDetailsReg.Reference_Select = CORE_SENSOR_DETAILS_REF_REXT1;
        break;
    case ADMW1001_ADC_REFERENCE_RESISTOR_EXTERNAL_2:
        sensorDetailsReg.Reference_Select = CORE_SENSOR_DETAILS_REF_REXT2;
        break;
    case ADMW1001_ADC_REFERENCE_VOLTAGE_EXTERNAL_1:
        sensorDetailsReg.Reference_Select = CORE_SENSOR_DETAILS_REF_VEXT1;
        break;
    case ADMW1001_ADC_REFERENCE_VOLTAGE_EXTERNAL_2:
        sensorDetailsReg.Reference_Select = CORE_SENSOR_DETAILS_REF_VEXT2;
        break;
    case ADMW1001_ADC_REFERENCE_BRIDGE_EXCITATION:
        sensorDetailsReg.Reference_Select = CORE_SENSOR_DETAILS_REF_EXC;
        break;
    default:
        ADMW_LOG_ERROR("Invalid ADC reference type %d specified",
                            pRefConfig->type);
        return ADMW_INVALID_PARAM;
    }

    switch(pAdcChannelConfig->gain)
    {
    case ADMW1001_ADC_GAIN_1X:
        sensorDetailsReg.PGA_Gain = CORE_SENSOR_DETAILS_PGA_GAIN_1;
        break;
    case ADMW1001_ADC_GAIN_2X:
        sensorDetailsReg.PGA_Gain = CORE_SENSOR_DETAILS_PGA_GAIN_2;
        break;
    case ADMW1001_ADC_GAIN_4X:
        sensorDetailsReg.PGA_Gain = CORE_SENSOR_DETAILS_PGA_GAIN_4;
        break;
    case ADMW1001_ADC_GAIN_8X:
        sensorDetailsReg.PGA_Gain = CORE_SENSOR_DETAILS_PGA_GAIN_8;
        break;
    case ADMW1001_ADC_GAIN_16X:
        sensorDetailsReg.PGA_Gain = CORE_SENSOR_DETAILS_PGA_GAIN_16;
        break;
    case ADMW1001_ADC_GAIN_32X:
        sensorDetailsReg.PGA_Gain = CORE_SENSOR_DETAILS_PGA_GAIN_32;
        break;
    case ADMW1001_ADC_GAIN_64X:
        sensorDetailsReg.PGA_Gain = CORE_SENSOR_DETAILS_PGA_GAIN_64;
        break;
    case ADMW1001_ADC_GAIN_128X:
        sensorDetailsReg.PGA_Gain = CORE_SENSOR_DETAILS_PGA_GAIN_128;
        break;
    default:
        ADMW_LOG_ERROR("Invalid ADC gain %d specified",
                            pAdcChannelConfig->gain);
        return ADMW_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;

    if (pChannelConfig->enableUnityLut)
        sensorDetailsReg.Unity_LUT_Select = 1;
    else
        sensorDetailsReg.Unity_LUT_Select = 0;

    WRITE_REG_U32(hDevice, sensorDetailsReg.VALUE32, CORE_SENSOR_DETAILSn(eChannelId));

    return ADMW_SUCCESS;
}

static ADMW_RESULT admw_SetChannelAdcFilter(
    ADMW_DEVICE_HANDLE hDevice,
    ADMW1001_CHANNEL_ID eChannelId,
    ADMW1001_ADC_FILTER_CONFIG *pFilterConfig)
{
    CORE_Filter_Select_t filterSelectReg;

    filterSelectReg.VALUE32 = REG_RESET_VAL(CORE_FILTER_SELECTn);

    if (pFilterConfig->type == ADMW1001_ADC_FILTER_SINC4)
    {
        filterSelectReg.ADC_Filter_Type = CORE_FILTER_SELECT_FILTER_SINC4;
        filterSelectReg.ADC_FS = pFilterConfig->fs;
    }
    else if (pFilterConfig->type == ADMW1001_ADC_FILTER_FIR_20SPS)
    {
        filterSelectReg.ADC_Filter_Type = CORE_FILTER_SELECT_FILTER_FIR_20SPS;
    }
    else if (pFilterConfig->type == ADMW1001_ADC_FILTER_FIR_25SPS)
    {
        filterSelectReg.ADC_Filter_Type = CORE_FILTER_SELECT_FILTER_FIR_25SPS;
    }
    else
    {
        ADMW_LOG_ERROR("Invalid ADC filter type %d specified",
                            pFilterConfig->type);
        return ADMW_INVALID_PARAM;
    }

    WRITE_REG_U32(hDevice, filterSelectReg.VALUE32, CORE_FILTER_SELECTn(eChannelId));

    return ADMW_SUCCESS;
}

static ADMW_RESULT admw_SetChannelAdcCurrentConfig(
    ADMW_DEVICE_HANDLE hDevice,
    ADMW1001_CHANNEL_ID eChannelId,
    ADMW1001_ADC_EXC_CURRENT_CONFIG *pCurrentConfig)
{
    CORE_Channel_Excitation_t channelExcitationReg;

    channelExcitationReg.VALUE8 = REG_RESET_VAL(CORE_CHANNEL_EXCITATIONn);

    if (pCurrentConfig->outputLevel == ADMW1001_ADC_EXC_CURRENT_NONE)
    {
        channelExcitationReg.IOUT_Excitation_Current = CORE_CHANNEL_EXCITATION_IEXC_OFF;
    }
    else
    {
        if (pCurrentConfig->outputLevel == ADMW1001_ADC_EXC_CURRENT_50uA)
            channelExcitationReg.IOUT_Excitation_Current = CORE_CHANNEL_EXCITATION_IEXC_50UA;
        else if (pCurrentConfig->outputLevel == ADMW1001_ADC_EXC_CURRENT_100uA)
            channelExcitationReg.IOUT_Excitation_Current = CORE_CHANNEL_EXCITATION_IEXC_100UA;
        else if (pCurrentConfig->outputLevel == ADMW1001_ADC_EXC_CURRENT_250uA)
            channelExcitationReg.IOUT_Excitation_Current = CORE_CHANNEL_EXCITATION_IEXC_250UA;
        else if (pCurrentConfig->outputLevel == ADMW1001_ADC_EXC_CURRENT_500uA)
            channelExcitationReg.IOUT_Excitation_Current = CORE_CHANNEL_EXCITATION_IEXC_500UA;
        else if (pCurrentConfig->outputLevel == ADMW1001_ADC_EXC_CURRENT_750uA)
            channelExcitationReg.IOUT_Excitation_Current = CORE_CHANNEL_EXCITATION_IEXC_750UA;
        else if (pCurrentConfig->outputLevel == ADMW1001_ADC_EXC_CURRENT_1000uA)
            channelExcitationReg.IOUT_Excitation_Current = CORE_CHANNEL_EXCITATION_IEXC_1000UA;
        else
        {
            ADMW_LOG_ERROR("Invalid ADC excitation current %d specified",
                                pCurrentConfig->outputLevel);
            return ADMW_INVALID_PARAM;
        }
    }

    if (pCurrentConfig->diodeRatio == ADMW1001_ADC_EXC_CURRENT_IOUT_DIODE_DEFAULT)
    {
        channelExcitationReg.IOUT_Diode_Ratio = 0;
    }
    else
    {
        channelExcitationReg.IOUT_Diode_Ratio = 1;
    }

    WRITE_REG_U8(hDevice, channelExcitationReg.VALUE8, CORE_CHANNEL_EXCITATIONn(eChannelId));

    return ADMW_SUCCESS;
}

ADMW_RESULT admw_SetAdcChannelConfig(
    ADMW_DEVICE_HANDLE hDevice,
    ADMW1001_CHANNEL_ID eChannelId,
    ADMW1001_CHANNEL_CONFIG *pChannelConfig)
{
    ADMW_RESULT eRet;
    ADMW1001_ADC_CHANNEL_CONFIG *pAdcChannelConfig =
        &pChannelConfig->adcChannelConfig;

    eRet = admw_SetChannelAdcSensorType(hDevice, eChannelId,
                                             pAdcChannelConfig->sensor);
    if (eRet != ADMW_SUCCESS)
    {
        ADMW_LOG_ERROR("Failed to set ADC sensor type for channel %d",
                            eChannelId);
        return eRet;
    }

    eRet = admw_SetChannelAdcSensorDetails(hDevice, eChannelId,
                                                pChannelConfig);
    if (eRet != ADMW_SUCCESS)
    {
        ADMW_LOG_ERROR("Failed to set ADC sensor details for channel %d",
                            eChannelId);
        return eRet;
    }

    eRet = admw_SetChannelAdcFilter(hDevice, eChannelId,
                                         &pAdcChannelConfig->filter);
    if (eRet != ADMW_SUCCESS)
    {
        ADMW_LOG_ERROR("Failed to set ADC filter for channel %d",
                            eChannelId);
        return eRet;
    }

    eRet = admw_SetChannelAdcCurrentConfig(hDevice, eChannelId,
                                                &pAdcChannelConfig->current);
    if (eRet != ADMW_SUCCESS)
    {
        ADMW_LOG_ERROR("Failed to set ADC current for channel %d",
                            eChannelId);
        return eRet;
    }

    return ADMW_SUCCESS;
}

static ADMW_RESULT admw_SetChannelDigitalSensorDetails(
    ADMW_DEVICE_HANDLE hDevice,
    ADMW1001_CHANNEL_ID eChannelId,
    ADMW1001_CHANNEL_CONFIG *pChannelConfig)
{
    CORE_Sensor_Details_t sensorDetailsReg;

    sensorDetailsReg.VALUE32 = REG_RESET_VAL(CORE_SENSOR_DETAILSn);

    if (pChannelConfig->compensationChannel == ADMW1001_CHANNEL_ID_NONE)
    {
        sensorDetailsReg.Compensation_Disable = 1;
        sensorDetailsReg.Compensation_Channel = 0;
    }
    else
    {
        ADMW_LOG_ERROR("Invalid compensation channel specified for digital sensor");
        return ADMW_INVALID_PARAM;
    }

    if (pChannelConfig->measurementUnit == ADMW1001_MEASUREMENT_UNIT_UNSPECIFIED)
    {
        sensorDetailsReg.Measurement_Units = CORE_SENSOR_DETAILS_UNITS_UNSPECIFIED;
    }
    else
    {
        ADMW_LOG_ERROR("Invalid measurement unit specified for digital channel");
        return ADMW_INVALID_PARAM;
    }

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

    if (pChannelConfig->enableUnityLut)
        sensorDetailsReg.Unity_LUT_Select = 1;
    else
        sensorDetailsReg.Unity_LUT_Select = 0;

    sensorDetailsReg.Vbias = 0;
    sensorDetailsReg.Reference_Buffer_Disable = 1;

    WRITE_REG_U32(hDevice, sensorDetailsReg.VALUE32, CORE_SENSOR_DETAILSn(eChannelId));

    return ADMW_SUCCESS;
}

static ADMW_RESULT admw_SetDigitalSensorCommands(
    ADMW_DEVICE_HANDLE hDevice,
    ADMW1001_CHANNEL_ID eChannelId,
    ADMW1001_DIGITAL_SENSOR_COMMAND *pConfigCommand,
    ADMW1001_DIGITAL_SENSOR_COMMAND *pDataRequestCommand)
{
    CORE_Digital_Sensor_Num_Cmds_t numCmdsReg;

    numCmdsReg.VALUE8 = REG_RESET_VAL(CORE_DIGITAL_SENSOR_NUM_CMDSn);

    CHECK_REG_FIELD_VAL(CORE_DIGITAL_SENSOR_NUM_CMDS_DIGITAL_SENSOR_NUM_CFG_CMDS,
                        pConfigCommand->commandLength);
    CHECK_REG_FIELD_VAL(CORE_DIGITAL_SENSOR_NUM_CMDS_DIGITAL_SENSOR_NUM_READ_CMDS,
                        pDataRequestCommand->commandLength);

    numCmdsReg.Digital_Sensor_Num_Cfg_Cmds = pConfigCommand->commandLength;
    numCmdsReg.Digital_Sensor_Num_Read_Cmds = pDataRequestCommand->commandLength;

    WRITE_REG_U8(hDevice, numCmdsReg.VALUE8,
                 CORE_DIGITAL_SENSOR_NUM_CMDSn(eChannelId));

    /*
     * NOTE - the fall-through cases in the switch statement below are
     * intentional, so temporarily disable related compiler warnings which may
     * be produced here by GCC
     */
#ifndef __CC_ARM
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
#endif

    switch (pConfigCommand->commandLength)
    {
    case 7:
        WRITE_REG_U8(hDevice, pConfigCommand->command[6],
                     CORE_DIGITAL_SENSOR_COMMAND7n(eChannelId));
    case 6:
        WRITE_REG_U8(hDevice, pConfigCommand->command[5],
                     CORE_DIGITAL_SENSOR_COMMAND6n(eChannelId));
    case 5:
        WRITE_REG_U8(hDevice, pConfigCommand->command[4],
                     CORE_DIGITAL_SENSOR_COMMAND5n(eChannelId));
    case 4:
        WRITE_REG_U8(hDevice, pConfigCommand->command[3],
                     CORE_DIGITAL_SENSOR_COMMAND4n(eChannelId));
    case 3:
        WRITE_REG_U8(hDevice, pConfigCommand->command[2],
                     CORE_DIGITAL_SENSOR_COMMAND3n(eChannelId));
    case 2:
        WRITE_REG_U8(hDevice, pConfigCommand->command[1],
                     CORE_DIGITAL_SENSOR_COMMAND2n(eChannelId));
    case 1:
        WRITE_REG_U8(hDevice, pConfigCommand->command[0],
                     CORE_DIGITAL_SENSOR_COMMAND1n(eChannelId));
    case 0:
    default:
        break;
    };

    switch (pDataRequestCommand->commandLength)
    {
    case 7:
        WRITE_REG_U8(hDevice, pDataRequestCommand->command[6],
                     CORE_DIGITAL_SENSOR_READ_CMD7n(eChannelId));
    case 6:
        WRITE_REG_U8(hDevice, pDataRequestCommand->command[5],
                     CORE_DIGITAL_SENSOR_READ_CMD6n(eChannelId));
    case 5:
        WRITE_REG_U8(hDevice, pDataRequestCommand->command[4],
                     CORE_DIGITAL_SENSOR_READ_CMD5n(eChannelId));
    case 4:
        WRITE_REG_U8(hDevice, pDataRequestCommand->command[3],
                     CORE_DIGITAL_SENSOR_READ_CMD4n(eChannelId));
    case 3:
        WRITE_REG_U8(hDevice, pDataRequestCommand->command[2],
                     CORE_DIGITAL_SENSOR_READ_CMD3n(eChannelId));
    case 2:
        WRITE_REG_U8(hDevice, pDataRequestCommand->command[1],
                     CORE_DIGITAL_SENSOR_READ_CMD2n(eChannelId));
    case 1:
        WRITE_REG_U8(hDevice, pDataRequestCommand->command[0],
                     CORE_DIGITAL_SENSOR_READ_CMD1n(eChannelId));
    case 0:
    default:
        break;
    };

    /* Re-enable the implicit-fallthrough warning */
#ifndef __CC_ARM
#pragma GCC diagnostic pop
#endif

    return ADMW_SUCCESS;
}

static ADMW_RESULT admw_SetDigitalSensorFormat(
    ADMW_DEVICE_HANDLE hDevice,
    ADMW1001_CHANNEL_ID eChannelId,
    ADMW1001_DIGITAL_SENSOR_DATA_FORMAT *pDataFormat)
{
    CORE_Digital_Sensor_Config_t sensorConfigReg;

    sensorConfigReg.VALUE16 = REG_RESET_VAL(CORE_DIGITAL_SENSOR_CONFIGn);

    if (pDataFormat->coding != ADMW1001_DIGITAL_SENSOR_DATA_CODING_NONE)
    {
        if (pDataFormat->frameLength == 0)
        {
            ADMW_LOG_ERROR("Invalid frame length specified for digital sensor data format");
            return ADMW_INVALID_PARAM;
        }
        if (pDataFormat->numDataBits == 0)
        {
            ADMW_LOG_ERROR("Invalid frame length specified for digital sensor data format");
            return ADMW_INVALID_PARAM;
        }

        CHECK_REG_FIELD_VAL(CORE_DIGITAL_SENSOR_CONFIG_DIGITAL_SENSOR_READ_BYTES,
                            pDataFormat->frameLength - 1);
        CHECK_REG_FIELD_VAL(CORE_DIGITAL_SENSOR_CONFIG_DIGITAL_SENSOR_DATA_BITS,
                            pDataFormat->numDataBits - 1);
        CHECK_REG_FIELD_VAL(CORE_DIGITAL_SENSOR_CONFIG_DIGITAL_SENSOR_BIT_OFFSET,
                            pDataFormat->bitOffset);

        sensorConfigReg.Digital_Sensor_Read_Bytes = pDataFormat->frameLength - 1;
        sensorConfigReg.Digital_Sensor_Data_Bits = pDataFormat->numDataBits - 1;
        sensorConfigReg.Digital_Sensor_Bit_Offset = pDataFormat->bitOffset;
        sensorConfigReg.Digital_Sensor_Left_Aligned = pDataFormat->leftJustified ? 1 : 0;
        sensorConfigReg.Digital_Sensor_Little_Endian = pDataFormat->littleEndian ? 1 : 0;

        switch (pDataFormat->coding)
        {
        case ADMW1001_DIGITAL_SENSOR_DATA_CODING_UNIPOLAR:
            sensorConfigReg.Digital_Sensor_Coding = CORE_DIGITAL_SENSOR_CONFIG_CODING_UNIPOLAR;
            break;
        case ADMW1001_DIGITAL_SENSOR_DATA_CODING_TWOS_COMPLEMENT:
            sensorConfigReg.Digital_Sensor_Coding = CORE_DIGITAL_SENSOR_CONFIG_CODING_TWOS_COMPL;
            break;
        case ADMW1001_DIGITAL_SENSOR_DATA_CODING_OFFSET_BINARY:
            sensorConfigReg.Digital_Sensor_Coding = CORE_DIGITAL_SENSOR_CONFIG_CODING_OFFSET_BINARY;
            break;
        default:
            ADMW_LOG_ERROR("Invalid coding specified for digital sensor data format");
            return ADMW_INVALID_PARAM;
        }
    }
    else
    {
        sensorConfigReg.Digital_Sensor_Coding = CORE_DIGITAL_SENSOR_CONFIG_CODING_NONE;
    }

    WRITE_REG_U16(hDevice, sensorConfigReg.VALUE16,
                  CORE_DIGITAL_SENSOR_CONFIGn(eChannelId));


    return ADMW_SUCCESS;
}

static ADMW_RESULT admw_SetDigitalCalibrationParam(
        ADMW_DEVICE_HANDLE hDevice,
        ADMW1001_CHANNEL_ID eChannelId,
        ADMW1001_DIGITAL_CALIBRATION_COMMAND *pCalibrationParam)
{
    CORE_Calibration_Parameter_t calibrationParamReg;

    calibrationParamReg.VALUE32 = REG_RESET_VAL(CORE_CALIBRATION_PARAMETERn);

    if (pCalibrationParam->enableCalibrationParam == false)
        calibrationParamReg.Calibration_Parameter_Enable = 0;
    else
        calibrationParamReg.Calibration_Parameter_Enable = 1;

    CHECK_REG_FIELD_VAL(CORE_CALIBRATION_PARAMETER_CALIBRATION_PARAMETER,
        pCalibrationParam->calibrationParam);

    calibrationParamReg.Calibration_Parameter = pCalibrationParam->calibrationParam;

    WRITE_REG_U32(hDevice, calibrationParamReg.VALUE32,
                  CORE_CALIBRATION_PARAMETERn(eChannelId));

    return ADMW_SUCCESS;
}

static ADMW_RESULT admw_SetChannelI2cSensorType(
    ADMW_DEVICE_HANDLE hDevice,
    ADMW1001_CHANNEL_ID eChannelId,
    ADMW1001_I2C_SENSOR_TYPE sensorType)
{
    CORE_Sensor_Type_t sensorTypeReg;

    sensorTypeReg.VALUE16 = REG_RESET_VAL(CORE_SENSOR_TYPEn);

    /* Ensure that the sensor type is valid for this channel */
    switch(sensorType)
    {
    case ADMW1001_I2C_SENSOR_HUMIDITY_A_DEF_L1:
    case ADMW1001_I2C_SENSOR_HUMIDITY_B_DEF_L1:
    case ADMW1001_I2C_SENSOR_HUMIDITY_A_DEF_L2:
    case ADMW1001_I2C_SENSOR_HUMIDITY_B_DEF_L2:
    case ADMW1001_I2C_SENSOR_HUMIDITY_A_ADV_L1:
    case ADMW1001_I2C_SENSOR_HUMIDITY_B_ADV_L1:
    case ADMW1001_I2C_SENSOR_HUMIDITY_A_ADV_L2:
    case ADMW1001_I2C_SENSOR_HUMIDITY_B_ADV_L2:
    case ADMW1001_I2C_SENSOR_AMBIENTLIGHT_A_DEF_L1:
    case ADMW1001_I2C_SENSOR_AMBIENTLIGHT_A_DEF_L2:
    case ADMW1001_I2C_SENSOR_AMBIENTLIGHT_A_ADV_L1:
    case ADMW1001_I2C_SENSOR_AMBIENTLIGHT_A_ADV_L2:
        sensorTypeReg.Sensor_Type = sensorType;
        break;
    default:
        ADMW_LOG_ERROR("Unsupported I2C sensor type %d specified", sensorType);
        return ADMW_INVALID_PARAM;
    }

    WRITE_REG_U16(hDevice, sensorTypeReg.VALUE16, CORE_SENSOR_TYPEn(eChannelId));

    return ADMW_SUCCESS;
}

static ADMW_RESULT admw_SetChannelI2cSensorAddress(
    ADMW_DEVICE_HANDLE hDevice,
    ADMW1001_CHANNEL_ID eChannelId,
    uint32_t deviceAddress)
{
    CHECK_REG_FIELD_VAL(CORE_DIGITAL_SENSOR_ADDRESS_DIGITAL_SENSOR_ADDRESS, deviceAddress);
    WRITE_REG_U8(hDevice, deviceAddress, CORE_DIGITAL_SENSOR_ADDRESSn(eChannelId));

    return ADMW_SUCCESS;
}

static ADMW_RESULT admw_SetDigitalChannelComms(
    ADMW_DEVICE_HANDLE hDevice,
    ADMW1001_CHANNEL_ID eChannelId,
    ADMW1001_DIGITAL_SENSOR_COMMS *pDigitalComms)
{
    CORE_Digital_Sensor_Comms_t digitalSensorComms;

    digitalSensorComms.VALUE16 = REG_RESET_VAL(CORE_DIGITAL_SENSOR_COMMSn);

    if(pDigitalComms->useCustomCommsConfig)
    {
        digitalSensorComms.Digital_Sensor_Comms_En = 1;

        if(pDigitalComms->i2cClockSpeed == ADMW1001_DIGITAL_SENSOR_COMMS_I2C_CLOCK_SPEED_100K)
        {
            digitalSensorComms.I2C_Clock = CORE_DIGITAL_SENSOR_COMMS_I2C_100K;
        }
        else if(pDigitalComms->i2cClockSpeed == ADMW1001_DIGITAL_SENSOR_COMMS_I2C_CLOCK_SPEED_400K)
        {
            digitalSensorComms.I2C_Clock = CORE_DIGITAL_SENSOR_COMMS_I2C_400K;
        }
        else
        {
            ADMW_LOG_ERROR("Invalid I2C clock speed %d specified",
                                pDigitalComms->i2cClockSpeed);
            return ADMW_INVALID_PARAM;
        }

        if(pDigitalComms->spiMode == ADMW1001_DIGITAL_SENSOR_COMMS_SPI_MODE_0)
        {
            digitalSensorComms.SPI_Mode = CORE_DIGITAL_SENSOR_COMMS_SPI_MODE_0;
        }
        else if(pDigitalComms->spiMode == ADMW1001_DIGITAL_SENSOR_COMMS_SPI_MODE_1)
        {
            digitalSensorComms.SPI_Mode = CORE_DIGITAL_SENSOR_COMMS_SPI_MODE_1;
        }
        else if(pDigitalComms->spiMode == ADMW1001_DIGITAL_SENSOR_COMMS_SPI_MODE_2)
        {
            digitalSensorComms.SPI_Mode = CORE_DIGITAL_SENSOR_COMMS_SPI_MODE_2;
        }
        else if(pDigitalComms->spiMode == ADMW1001_DIGITAL_SENSOR_COMMS_SPI_MODE_3)
        {
            digitalSensorComms.SPI_Mode = CORE_DIGITAL_SENSOR_COMMS_SPI_MODE_3;
        }
        else
        {
            ADMW_LOG_ERROR("Invalid SPI mode %d specified",
                                pDigitalComms->spiMode);
            return ADMW_INVALID_PARAM;
        }

        switch (pDigitalComms->spiClock)
        {
        case ADMW1001_DIGITAL_SENSOR_COMMS_SPI_CLOCK_13MHZ:
            digitalSensorComms.SPI_Clock = CORE_DIGITAL_SENSOR_COMMS_SPI_13MHZ;
            break;
        case ADMW1001_DIGITAL_SENSOR_COMMS_SPI_CLOCK_6_5MHZ:
            digitalSensorComms.SPI_Clock = CORE_DIGITAL_SENSOR_COMMS_SPI_6_5MHZ;
            break;
        case ADMW1001_DIGITAL_SENSOR_COMMS_SPI_CLOCK_3_25MHZ:
            digitalSensorComms.SPI_Clock = CORE_DIGITAL_SENSOR_COMMS_SPI_3_25MHZ;
            break;
        case ADMW1001_DIGITAL_SENSOR_COMMS_SPI_CLOCK_1_625MHZ:
            digitalSensorComms.SPI_Clock = CORE_DIGITAL_SENSOR_COMMS_SPI_1_625MHZ;
            break;
        case ADMW1001_DIGITAL_SENSOR_COMMS_SPI_CLOCK_812KHZ:
            digitalSensorComms.SPI_Clock = CORE_DIGITAL_SENSOR_COMMS_SPI_812KHZ;
            break;
        case ADMW1001_DIGITAL_SENSOR_COMMS_SPI_CLOCK_406KHZ:
            digitalSensorComms.SPI_Clock = CORE_DIGITAL_SENSOR_COMMS_SPI_406KHZ;
            break;
        case ADMW1001_DIGITAL_SENSOR_COMMS_SPI_CLOCK_203KHZ:
            digitalSensorComms.SPI_Clock = CORE_DIGITAL_SENSOR_COMMS_SPI_203KHZ;
            break;
        case ADMW1001_DIGITAL_SENSOR_COMMS_SPI_CLOCK_101KHZ:
            digitalSensorComms.SPI_Clock = CORE_DIGITAL_SENSOR_COMMS_SPI_101KHZ;
            break;
        case ADMW1001_DIGITAL_SENSOR_COMMS_SPI_CLOCK_50KHZ:
            digitalSensorComms.SPI_Clock = CORE_DIGITAL_SENSOR_COMMS_SPI_50KHZ;
            break;
        case ADMW1001_DIGITAL_SENSOR_COMMS_SPI_CLOCK_25KHZ:
            digitalSensorComms.SPI_Clock = CORE_DIGITAL_SENSOR_COMMS_SPI_25KHZ;
            break;
        case ADMW1001_DIGITAL_SENSOR_COMMS_SPI_CLOCK_12KHZ:
            digitalSensorComms.SPI_Clock = CORE_DIGITAL_SENSOR_COMMS_SPI_12KHZ;
            break;
        case ADMW1001_DIGITAL_SENSOR_COMMS_SPI_CLOCK_6KHZ:
            digitalSensorComms.SPI_Clock = CORE_DIGITAL_SENSOR_COMMS_SPI_6KHZ;
            break;
        case ADMW1001_DIGITAL_SENSOR_COMMS_SPI_CLOCK_3KHZ:
            digitalSensorComms.SPI_Clock = CORE_DIGITAL_SENSOR_COMMS_SPI_3KHZ;
            break;
        case ADMW1001_DIGITAL_SENSOR_COMMS_SPI_CLOCK_1_5KHZ:
            digitalSensorComms.SPI_Clock = CORE_DIGITAL_SENSOR_COMMS_SPI_1_5KHZ;
            break;
        case ADMW1001_DIGITAL_SENSOR_COMMS_SPI_CLOCK_793HZ:
            digitalSensorComms.SPI_Clock = CORE_DIGITAL_SENSOR_COMMS_SPI_793HZ;
            break;
        case ADMW1001_DIGITAL_SENSOR_COMMS_SPI_CLOCK_396HZ:
            digitalSensorComms.SPI_Clock = CORE_DIGITAL_SENSOR_COMMS_SPI_396HZ;
            break;
        default:
            ADMW_LOG_ERROR("Invalid SPI clock %d specified",
                                pDigitalComms->spiClock);
            return ADMW_INVALID_PARAM;
        }

        switch (pDigitalComms->uartLineConfig)
        {
        case ADMW1001_DIGITAL_SENSOR_COMMS_UART_LINE_CONFIG_8N1:
            digitalSensorComms.Uart_Mode = CORE_DIGITAL_SENSOR_COMMS_LINECONTROL_8N1;
            break;
        case ADMW1001_DIGITAL_SENSOR_COMMS_UART_LINE_CONFIG_8N2:
            digitalSensorComms.Uart_Mode = CORE_DIGITAL_SENSOR_COMMS_LINECONTROL_8N2;
            break;
        case ADMW1001_DIGITAL_SENSOR_COMMS_UART_LINE_CONFIG_8N3:
            digitalSensorComms.Uart_Mode = CORE_DIGITAL_SENSOR_COMMS_LINECONTROL_8N3;
            break;
        case ADMW1001_DIGITAL_SENSOR_COMMS_UART_LINE_CONFIG_8E1:
            digitalSensorComms.Uart_Mode = CORE_DIGITAL_SENSOR_COMMS_LINECONTROL_8E1;
            break;
        case ADMW1001_DIGITAL_SENSOR_COMMS_UART_LINE_CONFIG_8E2:
            digitalSensorComms.Uart_Mode = CORE_DIGITAL_SENSOR_COMMS_LINECONTROL_8E2;
            break;
        case ADMW1001_DIGITAL_SENSOR_COMMS_UART_LINE_CONFIG_8E3:
            digitalSensorComms.Uart_Mode = CORE_DIGITAL_SENSOR_COMMS_LINECONTROL_8E3;
            break;
        case ADMW1001_DIGITAL_SENSOR_COMMS_UART_LINE_CONFIG_8O1:
            digitalSensorComms.Uart_Mode = CORE_DIGITAL_SENSOR_COMMS_LINECONTROL_8O1;
            break;
        case ADMW1001_DIGITAL_SENSOR_COMMS_UART_LINE_CONFIG_8O2:
            digitalSensorComms.Uart_Mode = CORE_DIGITAL_SENSOR_COMMS_LINECONTROL_8O2;
            break;
        case ADMW1001_DIGITAL_SENSOR_COMMS_UART_LINE_CONFIG_8O3:
            digitalSensorComms.Uart_Mode = CORE_DIGITAL_SENSOR_COMMS_LINECONTROL_8O3;
            break;
        default:
            ADMW_LOG_ERROR("Invalid UART mode %d specified",
                                pDigitalComms->uartLineConfig);
            return ADMW_INVALID_PARAM;
        }

        switch (pDigitalComms->uartBaudRate)
        {
        case ADMW1001_DIGITAL_SENSOR_COMMS_UART_BAUD_RATE_115200:
            digitalSensorComms.Uart_Baud = CORE_DIGITAL_SENSOR_COMMS_UART_115200;
            break;
        case ADMW1001_DIGITAL_SENSOR_COMMS_UART_BAUD_RATE_57600:
            digitalSensorComms.Uart_Baud = CORE_DIGITAL_SENSOR_COMMS_UART_57600;
            break;
        case ADMW1001_DIGITAL_SENSOR_COMMS_UART_BAUD_RATE_38400:
            digitalSensorComms.Uart_Baud = CORE_DIGITAL_SENSOR_COMMS_UART_38400;
            break;
        case ADMW1001_DIGITAL_SENSOR_COMMS_UART_BAUD_RATE_19200:
            digitalSensorComms.Uart_Baud = CORE_DIGITAL_SENSOR_COMMS_UART_19200;
            break;
        case ADMW1001_DIGITAL_SENSOR_COMMS_UART_BAUD_RATE_9600:
            digitalSensorComms.Uart_Baud = CORE_DIGITAL_SENSOR_COMMS_UART_9600;
            break;
        case ADMW1001_DIGITAL_SENSOR_COMMS_UART_BAUD_RATE_4800:
            digitalSensorComms.Uart_Baud = CORE_DIGITAL_SENSOR_COMMS_UART_4800;
            break;
        case ADMW1001_DIGITAL_SENSOR_COMMS_UART_BAUD_RATE_2400:
            digitalSensorComms.Uart_Baud = CORE_DIGITAL_SENSOR_COMMS_UART_2400;
            break;
        case ADMW1001_DIGITAL_SENSOR_COMMS_UART_BAUD_RATE_1200:
            digitalSensorComms.Uart_Baud = CORE_DIGITAL_SENSOR_COMMS_UART_1200;
            break;
        default:
            ADMW_LOG_ERROR("Invalid UART baud rate %d specified",
                                pDigitalComms->uartBaudRate);
            return ADMW_INVALID_PARAM;
        }
    }
    else
    {
        digitalSensorComms.Digital_Sensor_Comms_En = 0;
    }

    WRITE_REG_U16(hDevice, digitalSensorComms.VALUE16, CORE_DIGITAL_SENSOR_COMMSn(eChannelId));

    return ADMW_SUCCESS;
}

ADMW_RESULT admw_SetI2cChannelConfig(
    ADMW_DEVICE_HANDLE hDevice,
    ADMW1001_CHANNEL_ID eChannelId,
    ADMW1001_CHANNEL_CONFIG *pChannelConfig)
{
    ADMW_RESULT eRet;
    ADMW1001_I2C_CHANNEL_CONFIG *pI2cChannelConfig =
        &pChannelConfig->i2cChannelConfig;

    eRet = admw_SetChannelI2cSensorType(hDevice, eChannelId,
                                            pI2cChannelConfig->sensor);
    if (eRet != ADMW_SUCCESS)
    {
        ADMW_LOG_ERROR("Failed to set I2C sensor type for channel %d",
                            eChannelId);
        return eRet;
    }

    eRet = admw_SetChannelI2cSensorAddress(hDevice, eChannelId,
                                               pI2cChannelConfig->deviceAddress);
    if (eRet != ADMW_SUCCESS)
    {
        ADMW_LOG_ERROR("Failed to set I2C sensor address for channel %d",
                            eChannelId);
        return eRet;
    }

    eRet = admw_SetChannelDigitalSensorDetails(hDevice, eChannelId,
                                                    pChannelConfig);
    if (eRet != ADMW_SUCCESS)
    {
        ADMW_LOG_ERROR("Failed to set I2C sensor details for channel %d",
                            eChannelId);
        return eRet;
    }

    eRet = admw_SetDigitalSensorCommands(hDevice, eChannelId,
                                              &pI2cChannelConfig->configurationCommand,
                                              &pI2cChannelConfig->dataRequestCommand);
    if (eRet != ADMW_SUCCESS)
    {
        ADMW_LOG_ERROR("Failed to set I2C sensor commands for channel %d",
                            eChannelId);
        return eRet;
    }

    eRet = admw_SetDigitalSensorFormat(hDevice, eChannelId,
                                            &pI2cChannelConfig->dataFormat);
    if (eRet != ADMW_SUCCESS)
    {
        ADMW_LOG_ERROR("Failed to set I2C sensor data format for channel %d",
                            eChannelId);
        return eRet;
    }

    eRet = admw_SetDigitalCalibrationParam(hDevice, eChannelId,
                                            &pI2cChannelConfig->digitalCalibrationParam);
    if (eRet != ADMW_SUCCESS)
    {
        ADMW_LOG_ERROR("Failed to set I2C digital calibration param for channel %d",
                            eChannelId);
        return eRet;
    }

    eRet = admw_SetDigitalChannelComms(hDevice, eChannelId,
                                            &pI2cChannelConfig->configureComms);
    if (eRet != ADMW_SUCCESS)
    {
        ADMW_LOG_ERROR("Failed to set I2C comms for channel %d",
                            eChannelId);
        return eRet;
    }

    return ADMW_SUCCESS;
}

static ADMW_RESULT admw_SetChannelSpiSensorType(
    ADMW_DEVICE_HANDLE hDevice,
    ADMW1001_CHANNEL_ID eChannelId,
    ADMW1001_SPI_SENSOR_TYPE sensorType)
{
    CORE_Sensor_Type_t sensorTypeReg;

    sensorTypeReg.VALUE16 = REG_RESET_VAL(CORE_SENSOR_TYPEn);

    /* Ensure that the sensor type is valid for this channel */
    switch(sensorType)
    {
    case ADMW1001_SPI_SENSOR_PRESSURE_A_DEF_L1:
    case ADMW1001_SPI_SENSOR_PRESSURE_A_DEF_L2:
    case ADMW1001_SPI_SENSOR_PRESSURE_A_ADV_L1:
    case ADMW1001_SPI_SENSOR_PRESSURE_A_ADV_L2:
    case ADMW1001_SPI_SENSOR_ACCELEROMETER_A_DEF_L1:
    case ADMW1001_SPI_SENSOR_ACCELEROMETER_B_DEF_L1:
    case ADMW1001_SPI_SENSOR_ACCELEROMETER_A_DEF_L2:
    case ADMW1001_SPI_SENSOR_ACCELEROMETER_B_DEF_L2:
    case ADMW1001_SPI_SENSOR_ACCELEROMETER_A_ADV_L1:
    case ADMW1001_SPI_SENSOR_ACCELEROMETER_B_ADV_L1:
    case ADMW1001_SPI_SENSOR_ACCELEROMETER_A_ADV_L2:
    case ADMW1001_SPI_SENSOR_ACCELEROMETER_B_ADV_L2:
        sensorTypeReg.Sensor_Type = sensorType;
        break;
    default:
        ADMW_LOG_ERROR("Unsupported SPI sensor type %d specified", sensorType);
        return ADMW_INVALID_PARAM;
    }

    WRITE_REG_U16(hDevice, sensorTypeReg.VALUE16, CORE_SENSOR_TYPEn(eChannelId));

    return ADMW_SUCCESS;
}

ADMW_RESULT admw_SetSpiChannelConfig(
    ADMW_DEVICE_HANDLE hDevice,
    ADMW1001_CHANNEL_ID eChannelId,
    ADMW1001_CHANNEL_CONFIG *pChannelConfig)
{
    ADMW_RESULT eRet;
    ADMW1001_SPI_CHANNEL_CONFIG *pSpiChannelConfig =
        &pChannelConfig->spiChannelConfig;

    eRet = admw_SetChannelSpiSensorType(hDevice, eChannelId,
                                             pSpiChannelConfig->sensor);
    if (eRet != ADMW_SUCCESS)
    {
        ADMW_LOG_ERROR("Failed to set SPI sensor type for channel %d",
                            eChannelId);
        return eRet;
    }

    eRet = admw_SetChannelDigitalSensorDetails(hDevice, eChannelId,
                                                    pChannelConfig);
    if (eRet != ADMW_SUCCESS)
    {
        ADMW_LOG_ERROR("Failed to set SPI sensor details for channel %d",
                            eChannelId);
        return eRet;
    }

    eRet = admw_SetDigitalSensorCommands(hDevice, eChannelId,
                                              &pSpiChannelConfig->configurationCommand,
                                              &pSpiChannelConfig->dataRequestCommand);
    if (eRet != ADMW_SUCCESS)
    {
        ADMW_LOG_ERROR("Failed to set SPI sensor commands for channel %d",
                            eChannelId);
        return eRet;
    }

    eRet = admw_SetDigitalSensorFormat(hDevice, eChannelId,
                                            &pSpiChannelConfig->dataFormat);
    if (eRet != ADMW_SUCCESS)
    {
        ADMW_LOG_ERROR("Failed to set SPI sensor data format for channel %d",
                            eChannelId);
        return eRet;
    }

    eRet = admw_SetDigitalCalibrationParam(hDevice, eChannelId,
                                            &pSpiChannelConfig->digitalCalibrationParam);
    if (eRet != ADMW_SUCCESS)
    {
        ADMW_LOG_ERROR("Failed to set SPI digital calibration param for channel %d",
                            eChannelId);
        return eRet;
    }

    eRet = admw_SetDigitalChannelComms(hDevice, eChannelId,
                                            &pSpiChannelConfig->configureComms);
    if (eRet != ADMW_SUCCESS)
    {
        ADMW_LOG_ERROR("Failed to set SPI comms for channel %d",
                            eChannelId);
        return eRet;
    }

    return ADMW_SUCCESS;
}

static ADMW_RESULT admw_SetChannelUartSensorType(
    ADMW_DEVICE_HANDLE hDevice,
    ADMW1001_CHANNEL_ID eChannelId,
    ADMW1001_UART_SENSOR_TYPE sensorType)
{
    CORE_Sensor_Type_t sensorTypeReg;

    sensorTypeReg.VALUE16 = REG_RESET_VAL(CORE_SENSOR_TYPEn);

    /* Ensure that the sensor type is valid for this channel */
    switch(sensorType)
    {
    case ADMW1001_UART_SENSOR_UART_CO2_A_DEF_L1:
    case ADMW1001_UART_SENSOR_UART_CO2_B_DEF_L1:
    case ADMW1001_UART_SENSOR_UART_CO2_A_DEF_L2:
    case ADMW1001_UART_SENSOR_UART_CO2_B_DEF_L2:
    case ADMW1001_UART_SENSOR_UART_CO2_A_ADV_L1:
    case ADMW1001_UART_SENSOR_UART_CO2_B_ADV_L1:
    case ADMW1001_UART_SENSOR_UART_CO2_A_ADV_L2:
    case ADMW1001_UART_SENSOR_UART_CO2_B_ADV_L2:
        sensorTypeReg.Sensor_Type = sensorType;
        break;
    default:
        ADMW_LOG_ERROR("Unsupported UART sensor type %d specified", sensorType);
        return ADMW_INVALID_PARAM;
    }

    WRITE_REG_U16(hDevice, sensorTypeReg.VALUE16, CORE_SENSOR_TYPEn(eChannelId));

    return ADMW_SUCCESS;
}

ADMW_RESULT admw_SetUartChannelConfig(
    ADMW_DEVICE_HANDLE hDevice,
    ADMW1001_CHANNEL_ID eChannelId,
    ADMW1001_CHANNEL_CONFIG *pChannelConfig)
{
    ADMW_RESULT eRet;
    ADMW1001_UART_CHANNEL_CONFIG *pUartChannelConfig =
        &pChannelConfig->uartChannelConfig;

    eRet = admw_SetChannelUartSensorType(hDevice, eChannelId,
                                             pUartChannelConfig->sensor);
    if (eRet != ADMW_SUCCESS)
    {
        ADMW_LOG_ERROR("Failed to set UART sensor type for channel %d",
                            eChannelId);
        return eRet;
    }

    eRet = admw_SetChannelDigitalSensorDetails(hDevice, eChannelId,
                                                    pChannelConfig);
    if (eRet != ADMW_SUCCESS)
    {
        ADMW_LOG_ERROR("Failed to set UART sensor details for channel %d",
                            eChannelId);
        return eRet;
    }

    eRet = admw_SetDigitalCalibrationParam(hDevice, eChannelId,
                                            &pUartChannelConfig->digitalCalibrationParam);
    if (eRet != ADMW_SUCCESS)
    {
        ADMW_LOG_ERROR("Failed to set UART digital calibration param for channel %d",
                            eChannelId);
        return eRet;
    }

    eRet = admw_SetDigitalChannelComms(hDevice, eChannelId,
                                            &pUartChannelConfig->configureComms);
    if (eRet != ADMW_SUCCESS)
    {
        ADMW_LOG_ERROR("Failed to set UART comms for channel %d",
                            eChannelId);
        return eRet;
    }


    return ADMW_SUCCESS;
}

ADMW_RESULT admw1001_SetChannelThresholdLimits(
    ADMW_DEVICE_HANDLE hDevice,
    ADMW1001_CHANNEL_ID eChannelId,
    float32_t fHighThresholdLimit,
    float32_t fLowThresholdLimit)
{
    /*
     * If the low/high limits are *both* set to 0 in memory, or NaNs, assume
     * that they are unset, or not required, and use infinity defaults instead
     */
    if (fHighThresholdLimit == 0.0f && fLowThresholdLimit == 0.0f)
    {
        fHighThresholdLimit = INFINITY;
        fLowThresholdLimit = -INFINITY;
    }
    else
    {
        if (isnan(fHighThresholdLimit))
            fHighThresholdLimit = INFINITY;
        if (isnan(fLowThresholdLimit))
            fLowThresholdLimit = -INFINITY;
    }

    WRITE_REG_FLOAT(hDevice, fHighThresholdLimit,
                    CORE_HIGH_THRESHOLD_LIMITn(eChannelId));
    WRITE_REG_FLOAT(hDevice, fLowThresholdLimit,
                    CORE_LOW_THRESHOLD_LIMITn(eChannelId));

    return ADMW_SUCCESS;
}

ADMW_RESULT admw1001_SetOffsetGain(
    ADMW_DEVICE_HANDLE hDevice,
    ADMW1001_CHANNEL_ID eChannelId,
    float32_t fOffsetAdjustment,
    float32_t fGainAdjustment)
{
    /* Replace with default values if NaNs are specified (or 0.0 for gain) */
    if (isnan(fGainAdjustment) || (fGainAdjustment == 0.0f))
        fGainAdjustment = 1.0f;
    if (isnan(fOffsetAdjustment))
        fOffsetAdjustment = 0.0f;

    WRITE_REG_FLOAT(hDevice, fGainAdjustment, CORE_SENSOR_GAINn(eChannelId));
    WRITE_REG_FLOAT(hDevice, fOffsetAdjustment, CORE_SENSOR_OFFSETn(eChannelId));

    return ADMW_SUCCESS;
}

ADMW_RESULT admw1001_SetSensorParameter(
    ADMW_DEVICE_HANDLE hDevice,
    ADMW1001_CHANNEL_ID eChannelId,
    float32_t fSensorParam)
{
    if (fSensorParam == 0.0f)
        fSensorParam = NAN;

    WRITE_REG_FLOAT(hDevice, fSensorParam, CORE_SENSOR_PARAMETERn(eChannelId));

    return ADMW_SUCCESS;
}

ADMW_RESULT admw1001_SetChannelSettlingTime(
    ADMW_DEVICE_HANDLE hDevice,
    ADMW1001_CHANNEL_ID eChannelId,
    uint32_t nSettlingTime)
{
    CORE_Settling_Time_t settlingTimeReg;

    if (nSettlingTime < (1 << 12))
    {
        settlingTimeReg.Settling_Time_Units = CORE_SETTLING_TIME_MICROSECONDS;
    }
    else if (nSettlingTime < (1000 * (1 << 12)))
    {
        settlingTimeReg.Settling_Time_Units = CORE_SETTLING_TIME_MILLISECONDS;
        nSettlingTime /= 1000;
    }
    else
    {
        settlingTimeReg.Settling_Time_Units = CORE_SETTLING_TIME_SECONDS;
        nSettlingTime /= 1000000;
    }

    CHECK_REG_FIELD_VAL(CORE_SETTLING_TIME_SETTLING_TIME, nSettlingTime);
    settlingTimeReg.Settling_Time = nSettlingTime;

    WRITE_REG_U16(hDevice, settlingTimeReg.VALUE16, CORE_SETTLING_TIMEn(eChannelId));

    return ADMW_SUCCESS;
}

ADMW_RESULT admw1001_SetChannelConfig(
    ADMW_DEVICE_HANDLE hDevice,
    ADMW1001_CHANNEL_ID eChannelId,
    ADMW1001_CHANNEL_CONFIG *pChannelConfig)
{
    ADMW_RESULT eRet;

    if (! ADMW1001_CHANNEL_IS_VIRTUAL(eChannelId))
    {
        eRet = admw1001_SetChannelCount(hDevice, eChannelId,
                                              pChannelConfig->enableChannel ?
                                              pChannelConfig->measurementsPerCycle : 0);
        if (eRet != ADMW_SUCCESS)
        {
            ADMW_LOG_ERROR("Failed to set measurement count for channel %d",
                                eChannelId);
            return eRet;
        }

        eRet = admw1001_SetChannelOptions(hDevice, eChannelId,
                                                pChannelConfig->priority,
                                                pChannelConfig->enableFFT);
        if (eRet != ADMW_SUCCESS)
        {
            ADMW_LOG_ERROR("Failed to set priority for channel %d",
                                eChannelId);
            return eRet;
        }

        /* If the channel is not enabled, we can skip the following steps */
        if (pChannelConfig->enableChannel || pChannelConfig->enableFFT)
        {
            eRet = admw1001_SetChannelSkipCount(hDevice, eChannelId,
                                                      pChannelConfig->cycleSkipCount);
            if (eRet != ADMW_SUCCESS)
            {
                ADMW_LOG_ERROR("Failed to set cycle skip count for channel %d",
                                    eChannelId);
                return eRet;
            }

            switch (eChannelId)
            {
            case ADMW1001_CHANNEL_ID_CJC_0:
            case ADMW1001_CHANNEL_ID_CJC_1:
            case ADMW1001_CHANNEL_ID_SENSOR_0:
            case ADMW1001_CHANNEL_ID_SENSOR_1:
            case ADMW1001_CHANNEL_ID_SENSOR_2:
            case ADMW1001_CHANNEL_ID_SENSOR_3:
            case ADMW1001_CHANNEL_ID_VOLTAGE_0:
            case ADMW1001_CHANNEL_ID_CURRENT_0:
                eRet = admw_SetAdcChannelConfig(hDevice, eChannelId, pChannelConfig);
                break;
            case ADMW1001_CHANNEL_ID_I2C_0:
            case ADMW1001_CHANNEL_ID_I2C_1:
                eRet = admw_SetI2cChannelConfig(hDevice, eChannelId, pChannelConfig);
                break;
            case ADMW1001_CHANNEL_ID_SPI_0:
                eRet = admw_SetSpiChannelConfig(hDevice, eChannelId, pChannelConfig);
                break;
            case ADMW1001_CHANNEL_ID_UART:
                eRet = admw_SetUartChannelConfig(hDevice, eChannelId, pChannelConfig);
                break;
            default:
                ADMW_LOG_ERROR("Invalid channel ID %d specified", eChannelId);
                return ADMW_INVALID_PARAM;
            }

            eRet = admw1001_SetChannelSettlingTime(hDevice, eChannelId,
                                                         pChannelConfig->extraSettlingTime);
            if (eRet != ADMW_SUCCESS)
            {
                ADMW_LOG_ERROR("Failed to set settling time for channel %d",
                                    eChannelId);
                return eRet;
            }
        }
    }

    if (pChannelConfig->enableChannel || pChannelConfig->enableFFT)
    {
        /* Threshold limits can be configured individually for virtual channels */
        eRet = admw1001_SetChannelThresholdLimits(hDevice, eChannelId,
                                                        pChannelConfig->highThreshold,
                                                        pChannelConfig->lowThreshold);
        if (eRet != ADMW_SUCCESS)
        {
            ADMW_LOG_ERROR("Failed to set threshold limits for channel %d",
                                eChannelId);
            return eRet;
        }

        /* Offset and gain can be configured individually for virtual channels */
        eRet = admw1001_SetOffsetGain(hDevice, eChannelId,
                                            pChannelConfig->offsetAdjustment,
                                            pChannelConfig->gainAdjustment);
        if (eRet != ADMW_SUCCESS)
        {
            ADMW_LOG_ERROR("Failed to set offset/gain for channel %d",
                                eChannelId);
            return eRet;
        }

        /* Set sensor specific parameter */
        eRet = admw1001_SetSensorParameter(hDevice, eChannelId,
                                                 pChannelConfig->sensorParameter);
        if (eRet != ADMW_SUCCESS)
        {
            ADMW_LOG_ERROR("Failed to set sensor parameter for channel %d",
                                eChannelId);
            return eRet;
        }
    }

    return ADMW_SUCCESS;
}

ADMW_RESULT admw_SetConfig(
    ADMW_DEVICE_HANDLE    const hDevice,
    ADMW_CONFIG         * const pConfig)
{
    ADMW1001_CONFIG *pDeviceConfig;
    ADMW_PRODUCT_ID productId;
    ADMW_RESULT eRet;

    if (pConfig->productId != ADMW_PRODUCT_ID_ADMW1001)
    {
        ADMW_LOG_ERROR("Configuration Product ID (0x%X) is not supported (0x%0X)",
                            pConfig->productId, ADMW_PRODUCT_ID_ADMW1001);
        return ADMW_INVALID_PARAM;
    }

    /* Check that the actual Product ID is a match? */
    eRet = admw_GetProductID(hDevice, &productId);
    if (eRet)
    {
        ADMW_LOG_ERROR("Failed to read device Product ID register");
        return eRet;
    }
    if (pConfig->productId != productId)
    {
        ADMW_LOG_ERROR("Configuration Product ID (0x%X) does not match device (0x%0X)",
                            pConfig->productId, productId);
        return ADMW_INVALID_PARAM;
    }

    pDeviceConfig = &pConfig->admw1001;

    eRet = admw1001_SetPowerConfig(hDevice, &pDeviceConfig->power);
    if (eRet)
    {
        ADMW_LOG_ERROR("Failed to set power configuration");
        return eRet;
    }

    eRet = admw1001_SetMeasurementConfig(hDevice, &pDeviceConfig->measurement);
    if (eRet)
    {
        ADMW_LOG_ERROR("Failed to set measurement configuration");
        return eRet;
    }

    eRet = admw1001_SetDiagnosticsConfig(hDevice, &pDeviceConfig->diagnostics);
    if (eRet)
    {
        ADMW_LOG_ERROR("Failed to set diagnostics configuration");
        return eRet;
    }

    for (ADMW1001_CHANNEL_ID id = ADMW1001_CHANNEL_ID_CJC_0;
         id < ADMW1001_MAX_CHANNELS;
         id++)
    {
        eRet = admw1001_SetChannelConfig(hDevice, id,
                                               &pDeviceConfig->channels[id]);
        if (eRet)
        {
            ADMW_LOG_ERROR("Failed to set channel %d configuration", id);
            return eRet;
        }
    }

    eRet = admw1001_SetFftConfig(hDevice, &pDeviceConfig->fft,
                                       pDeviceConfig->channels);
    if (eRet)
    {
        ADMW_LOG_ERROR("Failed to set FFT configuration");
        return eRet;
    }

    return ADMW_SUCCESS;
}

ADMW_RESULT admw1001_SetLutData(
    ADMW_DEVICE_HANDLE    const hDevice,
    ADMW1001_LUT       * const pLutData)
{
    ADMW1001_LUT_HEADER *pLutHeader = &pLutData->header;
    ADMW1001_LUT_TABLE *pLutTable = pLutData->tables;
    unsigned actualLength = 0;

    if (pLutData->header.signature != ADMW_LUT_SIGNATURE)
    {
        ADMW_LOG_ERROR("LUT signature incorrect (expected 0x%X, actual 0x%X)",
                            ADMW_LUT_SIGNATURE, pLutHeader->signature);
        return ADMW_INVALID_SIGNATURE;
    }

    for (unsigned i = 0; i < pLutHeader->numTables; i++)
    {
        ADMW1001_LUT_DESCRIPTOR *pDesc = &pLutTable->descriptor;
        ADMW1001_LUT_TABLE_DATA *pData = &pLutTable->data;
        unsigned short calculatedCrc;

        switch (pDesc->geometry)
        {
        case ADMW1001_LUT_GEOMETRY_COEFFS:
                switch (pDesc->equation)
                {
                case ADMW1001_LUT_EQUATION_POLYN:
                case ADMW1001_LUT_EQUATION_POLYNEXP:
                case ADMW1001_LUT_EQUATION_QUADRATIC:
                case ADMW1001_LUT_EQUATION_STEINHART:
                case ADMW1001_LUT_EQUATION_LOGARITHMIC:
                case ADMW1001_LUT_EQUATION_BIVARIATE_POLYN:
                break;
                default:
                    ADMW_LOG_ERROR("Invalid equation %u specified for LUT table %u",
                                        pDesc->equation, i);
                    return ADMW_INVALID_PARAM;
                }
            break;
        case ADMW1001_LUT_GEOMETRY_NES_1D:
        case ADMW1001_LUT_GEOMETRY_NES_2D:
        case ADMW1001_LUT_GEOMETRY_ES_1D:
        case ADMW1001_LUT_GEOMETRY_ES_2D:
                if (pDesc->equation != ADMW1001_LUT_EQUATION_LUT) {
                    ADMW_LOG_ERROR("Invalid equation %u specified for LUT table %u",
                                        pDesc->equation, i);
                    return ADMW_INVALID_PARAM;
                }
            break;
        default:
            ADMW_LOG_ERROR("Invalid geometry %u specified for LUT table %u",
                                pDesc->geometry, i);
            return ADMW_INVALID_PARAM;
        }

        switch (pDesc->dataType)
        {
        case ADMW1001_LUT_DATA_TYPE_FLOAT32:
        case ADMW1001_LUT_DATA_TYPE_FLOAT64:
            break;
        default:
            ADMW_LOG_ERROR("Invalid vector format %u specified for LUT table %u",
                                pDesc->dataType, i);
            return ADMW_INVALID_PARAM;
        }

        calculatedCrc = admw_crc16_ccitt(pData, pDesc->length);
        if (calculatedCrc != pDesc->crc16)
        {
            ADMW_LOG_ERROR("CRC validation failed on LUT table %u (expected 0x%04X, actual 0x%04X)",
                                i, pDesc->crc16, calculatedCrc);
            return ADMW_CRC_ERROR;
        }

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

        /* Move to the next look-up table */
        pLutTable = (ADMW1001_LUT_TABLE *)((uint8_t *)pLutTable + sizeof(*pDesc) + pDesc->length);
    }

    if (actualLength != pLutHeader->totalLength)
    {
        ADMW_LOG_ERROR("LUT table length mismatch (expected %u, actual %u)",
                            pLutHeader->totalLength, actualLength);
        return ADMW_WRONG_SIZE;
    }

    if (sizeof(*pLutHeader) + pLutHeader->totalLength > ADMW_LUT_MAX_SIZE)
    {
        ADMW_LOG_ERROR("Maximum LUT table length (%u bytes) exceeded",
                            ADMW_LUT_MAX_SIZE);
        return ADMW_WRONG_SIZE;
    }

    /* Write the LUT data to the device */
    unsigned lutSize = sizeof(*pLutHeader) + pLutHeader->totalLength;
    WRITE_REG_U16(hDevice, 0, CORE_LUT_OFFSET);
    WRITE_REG_U8_ARRAY(hDevice, (uint8_t *)pLutData, lutSize, CORE_LUT_DATA);

    return ADMW_SUCCESS;
}

ADMW_RESULT admw1001_SetLutDataRaw(
    ADMW_DEVICE_HANDLE    const hDevice,
    ADMW1001_LUT_RAW   * const pLutData)
{
    return admw1001_SetLutData(hDevice,
                                     (ADMW1001_LUT *)pLutData);
}

static ADMW_RESULT getLutTableSize(
    ADMW1001_LUT_DESCRIPTOR * const pDesc,
    ADMW1001_LUT_TABLE_DATA * const pData,
    unsigned *pLength)
{
    switch (pDesc->geometry)
    {
    case ADMW1001_LUT_GEOMETRY_COEFFS:
        if (pDesc->equation == ADMW1001_LUT_EQUATION_BIVARIATE_POLYN)
            *pLength = ADMW1001_LUT_2D_POLYN_COEFF_LIST_SIZE(pData->coeffList2d);
        else
            *pLength = ADMW1001_LUT_COEFF_LIST_SIZE(pData->coeffList);
        break;
    case ADMW1001_LUT_GEOMETRY_NES_1D:
        *pLength = ADMW1001_LUT_1D_NES_SIZE(pData->lut1dNes);
        break;
    case ADMW1001_LUT_GEOMETRY_NES_2D:
        *pLength = ADMW1001_LUT_2D_NES_SIZE(pData->lut2dNes);
        break;
    case ADMW1001_LUT_GEOMETRY_ES_1D:
        *pLength = ADMW1001_LUT_1D_ES_SIZE(pData->lut1dEs);
        break;
    case ADMW1001_LUT_GEOMETRY_ES_2D:
        *pLength = ADMW1001_LUT_2D_ES_SIZE(pData->lut2dEs);
        break;
    default:
        ADMW_LOG_ERROR("Invalid LUT table geometry %d specified\r\n",
                            pDesc->geometry);
        return ADMW_INVALID_PARAM;
    }

    return ADMW_SUCCESS;
}

ADMW_RESULT admw1001_AssembleLutData(
    ADMW1001_LUT                  * pLutBuffer,
    unsigned                              nLutBufferSize,
    unsigned                        const nNumTables,
    ADMW1001_LUT_DESCRIPTOR * const ppDesc[],
    ADMW1001_LUT_TABLE_DATA * const ppData[])
{
    ADMW1001_LUT_HEADER *pHdr = &pLutBuffer->header;
    uint8_t *pLutTableData = (uint8_t *)pLutBuffer + sizeof(*pHdr);

    if (sizeof(*pHdr) > nLutBufferSize)
    {
        ADMW_LOG_ERROR("Insufficient LUT buffer size provided");
        return ADMW_INVALID_PARAM;
    }

    /* First initialise the top-level header */
    pHdr->signature = ADMW_LUT_SIGNATURE;
    pHdr->version.major = 1;
    pHdr->version.minor = 0;
    pHdr->numTables = 0;
    pHdr->totalLength = 0;

    /*
     * Walk through the list of table pointers provided, appending the table
     * descriptor+data from each one to the provided LUT buffer
     */
    for (unsigned i = 0; i < nNumTables; i++)
    {
        ADMW1001_LUT_DESCRIPTOR * const pDesc = ppDesc[i];
        ADMW1001_LUT_TABLE_DATA * const pData = ppData[i];
        ADMW_RESULT res;
        unsigned dataLength = 0;

        /* Calculate the length of the table data */
        res = getLutTableSize(pDesc, pData, &dataLength);
        if (res != ADMW_SUCCESS)
            return res;

        /* Fill in the table descriptor length and CRC fields */
        pDesc->length = dataLength;
        pDesc->crc16 = admw_crc16_ccitt(pData, dataLength);

        if ((sizeof(*pHdr) + pHdr->totalLength + sizeof(*pDesc) + dataLength) > nLutBufferSize)
        {
            ADMW_LOG_ERROR("Insufficient LUT buffer size provided");
            return ADMW_INVALID_PARAM;
        }

        /* Append the table to the LUT buffer (desc + data) */
        memcpy(pLutTableData + pHdr->totalLength, pDesc, sizeof(*pDesc));
        pHdr->totalLength += sizeof(*pDesc);
        memcpy(pLutTableData + pHdr->totalLength, pData, dataLength);
        pHdr->totalLength += dataLength;

        pHdr->numTables++;
    }

    return ADMW_SUCCESS;
}