/*****************************************************************************
* Copyright (c) 2007 Digi International Inc., All Rights Reserved
* 
* This software contains proprietary and confidential information of Digi
* International Inc.  By accepting transfer of this copy, Recipient agrees
* to retain this software in confidence, to prevent disclosure to others,
* and to make no use of this software other than that for which it was
* delivered.  This is an unpublished copyrighted work of Digi International
* Inc.  Except as permitted by federal law, 17 USC 117, copying is strictly
* prohibited.
* 
* Restricted Rights Legend
*
* Use, duplication, or disclosure by the Government is subject to
* restrictions set forth in sub-paragraph (c)(1)(ii) of The Rights in
* Technical Data and Computer Software clause at DFARS 252.227-7031 or
* subparagraphs (c)(1) and (2) of the Commercial Computer Software -
* Restricted Rights at 48 CFR 52.227-19, as applicable.
*
* Digi International Inc. 11001 Bren Road East, Minnetonka, MN 55343
*
*****************************************************************************/

#include "Npttypes.h"
#include "a2d.h"
#include "a2d_private.h"
#include "nastatus.h"
#include "tx_api.h"
#include <string.h>

typedef struct NAA2dNotifyList_t
{
    struct NAA2dNotifyList_t *next;
    struct NAA2dNotifyList_t *prev;
    NAA2dNotificationFn_t fn;
} NAA2dNotifyList_t;


static NAA2dNotifyList_t notifyHead;

static unsigned channelCount = 0;

static NAA2dChannelTable_t head;

TX_SEMAPHORE naA2dSemaphore;


/*ad*
 * Use the Analog to Digital Converter Driver to manage analog to digital converters.
 * The NS9215 has a built in analog to digital converter which is fully supported
 * by this API.  The API also allows you to add support for other devices.
 *
 * The NS9215 has a single Analog to Digital converter channel with the following
 * characteristics:
 * <list type="unordered">
 *  <item>Up to 8 inputs are supported.  The channel samples each selected input
 *        in a round robin fashion, storing the sampled values in registers.</item>
 *  <item>The channel continuously samples the inputs.</item>
 *  <item>The channel has 12-bit resolution.</item>
 *  <item>The channel scan rate can be set between 20 KHz and 1 MHz.  The rate is 
 *        in sampled inputs per second.</item>
 *  <item>The channel can use DMA to write the sampled values into memory buffers.</item>
 * </list>
 *
 * The API supports the following modes of operation:
 * <list type="unordered">
 *  <item>API users can read the last sampled value of any input at any time.</item>
 *  <item>The API can use the channel's DMA support to continuously sample inputs
 *        and write their values into buffers.</item>
 *  <item>API users can install an ISR callback which will be invoked everytime an 
 *        input is sampled.  The callback will be passed the value of the input just
 *        sampled.</item>
 * </list>
 *
 * Follow this procedure to use the A/D converter in manual mode.
 * <list type="ordered">
 *  <item>Use @link naA2dOpen to get ownership of the device.</item>
 *  <item>Use @link naA2dConfigure to configure the device.  The A/D converter
 *        will start running when this function completes.</item>
 *  <item>Use @link naA2dReadInput to read sampled values for input pins.</item>
 *  <item>Use @link naA2dClose to shut down the converter and release ownership of it
 *        when you are finished.</item>
 * </list>
 * 
 * Follow this procecure to use the A/D converter in automatic mode.
 * <list type="ordered">
 *  <item>Edit the bsp_serial.h file for your platform and make sure that
 *        @link BSP_SERIAL_PORT_4 is set to BSP_SERIAL_NO_DRIVER.  This is necessary
 *        because the A/D converter and serial port D both use the same DMA channel.
 *        Only one of them can be enabled at a time.  The 
 *        @link naA2dAutoConfigure function will call panic if serial port D is
 *        enabled.</item>
 *  <item>Use @link naA2dOpen to get ownership of the device.</item>
 *  <item>Use @link naA2dConfigure to configure the device.  The A/D converter
 *        will start running when this function completes.</item>
 *  <item>Use @link naA2dAutoConfigure to configure automatic mode.  The A/D converter 
 *        will call panic in this function if serial port D is also enabled.</item>
 *  <item>Allocate 1 or more buffers to receive the sampled values in.</item>
 *  <item>Use @link naA2dAutoLoadBuffer to place buffers into the converter's DMA
 *        buffer queue.</item>
 *  <item>Use @link naA2dAutoStart to have H/W start placing the sampled values into
 *        your buffers.  You can use naA2dAutoLoadBuffer to add additional buffers onto
 *        the queue (or to recycle old ones) while auto mode is running.</item>
 *  <item>Use @link naA2dAutoStop to stop auto mode.</item>
 *  <item>Use @link naA2dClose to shut down the converter and release ownership of it
 *        when you are finished.</item>
 * </list>
 *
 * @category BSP:Device_Drivers:a2d
 * @since 7.3
 * @overview Analog to Digital Converter Driver
 * @library bsp
 * @platform connectcore9p9215_a
 * @include "a2d.h"
 * @external
*ad*/



/*ad*
 * Use this function to add a notification function into the list of functions 
 * that are called when A/D channels change state.
 *
 * The A/D API allows software to dynamically add and remove A/D channels.  Use this
 * function to register a callback which the API will call whenever a new channel
 * is added, or an existing one is removed.
 *
 * @since 7.3
 * @category BSP:Device_Drivers:a2d
 *
 * @param fn                pointer to notification function to add to list
 *
 * @return NA_SUCCESS                   notification function has been added to the list
 * @return NA_A2D_BAD_ARGUMENT          fn was NULL
 * @return NA_A2D_OUT_OF_RESOURCES      out of memory
 * @return NA_A2D_DUPLICATE_CALLBACK    function is already in callback list
*ad*/
NaStatus naA2dRegisterNotificationFn(NAA2dNotificationFn_t fn) 
{
    NAA2dNotifyList_t *listEntry = NULL;
    NaStatus result = NA_SUCCESS;
    
    A2D_SEMAPHORE_GET;

    if (fn == NULL)
    {
        result = NA_A2D_BAD_ARGUMENT;
        goto naA2dRegisterNotificationFnEnd;
    }
    
    listEntry = &notifyHead;
    do
    {
        if (listEntry->next->fn == fn)
        {
            result = NA_A2D_DUPLICATE_CALLBACK;
            listEntry = NULL;
            goto naA2dRegisterNotificationFnEnd;
        }
        else
        {
            listEntry = listEntry->next;
        }
    } while (listEntry != &notifyHead);
    
    listEntry = malloc(sizeof(NAA2dNotifyList_t));
    if (listEntry == NULL)
    {
        result = NA_A2D_OUT_OF_RESOURCES;
        goto naA2dRegisterNotificationFnEnd;
    }

    listEntry->next = notifyHead.next;
    listEntry->prev = &notifyHead;
    listEntry->fn = fn;
    notifyHead.next->prev = listEntry;
    notifyHead.next = listEntry;

    result = NA_SUCCESS;
    
naA2dRegisterNotificationFnEnd:
    if (result != NA_SUCCESS)
    {
        if (listEntry != NULL)
        {
            free(listEntry);
        }
    }
    
    A2D_SEMAPHORE_RELEASE;
    
    return result;
}




/*ad*
 * Use this function to remove a notification function from the list of
 * notification functions.
 *
 * @since 7.3
 * @category BSP:Device_Drivers:a2d
 *
 * @param fn                pointer to notification function to remove from list
 *
 * @return NA_SUCCESS                   notification function has been removed from the list
 * @return NA_A2D_BAD_ARGUMENT          fn was NULL
 * @return NA_A2D_NO_SUCH_CALLBACK      the notification function was not in the list
*ad*/
NaStatus naA2dDeregisterNotificationFn(NAA2dNotificationFn_t fn) 
{
    NAA2dNotifyList_t *listEntry = NULL;
    NaStatus result = NA_A2D_NO_SUCH_CALLBACK;
    
    A2D_SEMAPHORE_GET;

    if (fn == NULL)
    {
        result = NA_A2D_BAD_ARGUMENT;
        goto naA2dDeregisterNotificationFnEnd;
    }
    
    listEntry = &notifyHead;
    
    do
    {
        if (listEntry->next->fn == fn)
        {
            NAA2dNotifyList_t *deadEntry = listEntry->next;
            
            deadEntry->next->prev = listEntry;
            listEntry->next = deadEntry->next;
            free(deadEntry);
            result = NA_SUCCESS;
            break;
        }
        else
        {
            listEntry = listEntry->next;
        }
    } while (listEntry != &notifyHead);

naA2dDeregisterNotificationFnEnd:
    A2D_SEMAPHORE_RELEASE;
    
    return result;
}




/*ad*
 * Use this function to determine the number of A/D channels and their identifiers.
 *
 * This function loads *numberOfChannels with the number of channels that have been
 * registered so far, and the array pointed to by id with their identifiers.  You can
 * set id to NULL if you do not want to get the identifiers, or set numberOfChannels to
 * NULL if you do not want the count.
 *
 * @since 7.3
 * @category BSP:Device_Drivers:a2d
 *
 * @param numberOfChannels      pointer to word to load with the number of registered
 *                              channels, or NULL if the caller does not want this 
 *                              information.
 * @param id                    pointer to array of NAA2dIdentifier_t which will be 
 *                              loaded with the identifiers of the registered channels, or
 *                              NULL if the caller does not want this information.
 * @param maxIds                set to the size of the id array.
 *
 * @return NA_SUCCESS       Function always succeeds
*ad*/
NaStatus naA2dGetChannelCount(unsigned int * numberOfChannels, NAA2dIdentifier_t * id, unsigned int maxIds)
{
    unsigned limit = maxIds;
    
    A2D_SEMAPHORE_GET;
    
    if (limit > channelCount)
    {
        limit = channelCount;
    }
    
    if (numberOfChannels != NULL)
    {
        *numberOfChannels = channelCount;
    }
    
    if (id != NULL)
    {
        unsigned idIndex;
        NAA2dChannelTable_t *channel = head.next;
        
        for (idIndex = 0; idIndex < limit; idIndex++)
        {
            strcpy(id[idIndex], channel->identifier);
            channel = channel->next;
        }
    }
    
    A2D_SEMAPHORE_RELEASE;
    
    return NA_SUCCESS;
}


/*
 * Internal function find a channel entry in the linked list of
 * channel entries.
 *
 * You must acquire the semaphore before calling this function.
 */
static NAA2dChannelTable_t * naA2dFindChannel(NAA2dIdentifier_t id)
{
    NAA2dChannelTable_t *result = NULL;
    NAA2dChannelTable_t *channel = head.next;
    
    if (id != NULL)
    {
        while (channel != &head)
        {
            if (strcmp(channel->identifier, id) == 0)
            {
                result = channel;
                break;
            }
            channel = channel->next;
        }
    }
    
    return result;
}



/*ad*
 * Use this function to retrieve information about a channel.
 *
 * You can pass any argument, except for id, as NULL if you are not interested in
 * the information which would be passed through it.
 *
 * @since 7.3
 * @external    
 * @category BSP:Device_Drivers:a2d
 *
 * @param id            must be set to the identifier of the channel to examine
 * @param maxInputs     pointer to word to load with the maximum number of inputs the
 *                      channel can sample
 * @param resolution    pointer to word to load with the resolution of the channel in
 *                      bits
 * @param minClockRate  pointer to word to load with the minimum clock rate for the 
 *                      channel
 * @param maxClockRate  pointer to word to load with the maximum clock rate for the 
 *                      channel
 *
 * @return NA_SUCCESS                   information about the channel has been retrieved
 * @return NA_A2D_BAD_ARGUMENT          id was NULL
 * @return NA_A2D_UNKNOWN_IDENTIFIER    channel does not exist
*ad*/
NaStatus naA2dGetChannelInfo(NAA2dIdentifier_t id, unsigned * maxInputs, unsigned * resolution,  WORD64 * minClockRate, WORD64 * maxClockRate)
{
    NAA2dChannelTable_t *channel;
    NaStatus result = NA_SUCCESS;
    
    A2D_SEMAPHORE_GET;
    
    if (id == NULL)
    {
        result = NA_A2D_BAD_ARGUMENT;
        goto naA2dGetChannelInfoEnd;
    }
    
    channel = naA2dFindChannel(id);
    
    if (channel == NULL)
    {
        result = NA_A2D_UNKNOWN_IDENTIFIER;
        goto naA2dGetChannelInfoEnd;
    }
                
    if (maxInputs != NULL)
    {
        *maxInputs = channel->maxInputs;
    }
    
    if (resolution != NULL)
    {
        *resolution = channel->resolution;
    }
    
    if (minClockRate != NULL)
    {
        *minClockRate = channel->minClockRate;
    }
    
    if (maxClockRate != NULL)
    {
        *maxClockRate = channel->maxClockRate;
    }
    
naA2dGetChannelInfoEnd:
    A2D_SEMAPHORE_RELEASE;
    
    return result;
}





/*
 * Use this function to call notification functions when
 * a channel goes up or down.
 *
 * You must have the semaphore before calling this function.
 */
static void naA2dNotify(NAA2dChannelTable_t * channel, unsigned flags)
{
    NAA2dNotifyList_t *listEntry = notifyHead.next;
    
    while (listEntry != &notifyHead)
    {
        listEntry->fn(channel->identifier, channel->maxInputs, channel->resolution, 
                        channel->minClockRate, channel->maxClockRate, flags);
        listEntry = listEntry->next;
    }   
}



/*ad*
 * Use this function to register a new A/D channel.  
 *
 * You normally do NOT need to use this function.  The NET+OS BSP automatically
 * registers the NS9215 A/D driver when it runs on a NS9215 based platform.
 * The code to do this is automatically built into the BSP library if the
 * platform uses the NS9215.
 *
 * Use this function to register additional A/D drivers that are not already
 * supported by NET+OS.  You may want to do this if you are implementing 
 * an extra A/D converter in your hardware.  Registering your driver with the
 * NET+OS API allows you to use the same API for both NET+OS A/D drivers and
 * yours.
 *
 * You must pass the configuration settings for your A/D device in the 
 * naA2dRegistrationSettings_t structure passed to this function, as well as
 * pointers to the low level driver functions.
 *
 * If the channel is always present, then call this function during start up
 * to register it.  If the hardware is not always available, then you can call
 * this function to register the driver when the hardware becomes available, and
 * @link naA2dDeregisterChannel
 * to deregister the driver when the hardware becomes unavailable.
 * 
 * You must set registrationSettings->structVersion to 
 * NA_A2D_REGISTRATION_SETTINGS_VERSION_1.
 *
 * @since 7.3
 * @category BSP:Device_Drivers:a2d
 *
 * @param registrationSettings  use this structure to pass configuration settings and
 *                              the low level A2D driver functions to this function
 *
 * @return NA_SUCCESS                   channel has been registered
 * @return NA_A2D_OUT_OF_RESOURCES      out of memory
 * @return NA_A2D_DUPLICATE_ID          another channel has already been registered with the same identifier
 * @return NA_A2D_BAD_ARGUMENT          registrationSettings is NULL, or contains an invalid field
 * @return NA_A2D_INCORRECT_VERSION     registrationSettings->structVersion is not set correctly
*ad*/
NaStatus naA2dRegisterChannel(const naA2dRegistrationSettings_t * registrationSettings)
{
    NAA2dChannelTable_t *channel = NULL;
    NaStatus result = NA_SUCCESS;
    naA2dRegistrationSettings_t * rg = (naA2dRegistrationSettings_t *) registrationSettings;
    
    A2D_SEMAPHORE_GET;

    if (rg == NULL)
    {
        result = NA_A2D_BAD_ARGUMENT;
        goto naA2dRegisterChannelEnd;
    }
    
    if (rg->structVersion != NA_A2D_REGISTRATION_SETTINGS_CURRENT_VERSION)
    {
        result = NA_A2D_INCORRECT_VERSION;
        goto naA2dRegisterChannelEnd;
    }
            
    if (rg->identifier == NULL)
    {
        result = NA_A2D_BAD_ARGUMENT;
        goto naA2dRegisterChannelEnd;
    }
    
    if (naA2dFindChannel(rg->identifier) != NULL)
    {
        result = NA_A2D_DUPLICATE_ID;
        goto naA2dRegisterChannelEnd;
    }
    
    channel = malloc(sizeof(NAA2dChannelTable_t));
    if (channel == NULL)
    {
        result = NA_A2D_OUT_OF_RESOURCES;
        goto naA2dRegisterChannelEnd;
    }
    
    if ((rg->maxInputs == 0) || (rg->resolution == 0) || (rg->resolution > 32) || (rg->minClockRate.x > rg->maxClockRate.x))
    {
        result = NA_A2D_BAD_ARGUMENT;
        goto naA2dRegisterChannelEnd;
    }
    
    if ((rg->init == NULL) || (rg->open == NULL) || (rg->close == NULL) || (rg->configure == NULL)
        || (rg->autoConfigure == NULL)
        || (rg->readInput == NULL) || (rg->autoLoadBuffer == NULL) || (rg->autoStart == NULL)
        || (rg->autoStop == NULL) || (rg->autoFlushBuffers == NULL) || (rg->isrInstall == NULL)
        || (rg->interruptEnable == NULL) || (rg->isrUninstall == NULL))
    {
        result = NA_A2D_BAD_ARGUMENT;
        goto naA2dRegisterChannelEnd;
    }
    
    result = rg->init(rg->context);
    if (result != NA_SUCCESS)
    {
        goto naA2dRegisterChannelEnd;
    }
    
    strcpy(channel->identifier, rg->identifier);
    channel->maxInputs = rg->maxInputs;
    channel->resolution = rg->resolution;
    channel->minClockRate = rg->minClockRate;    
    channel->maxClockRate = rg->maxClockRate;  
    channel->context = rg->context;         
    channel->init = rg->init;
    channel->open = rg->open;
    channel->close = rg->close;
    channel->configure = rg->configure;     
    channel->readInput = rg->readInput;
    channel->autoLoadBuffer = rg->autoLoadBuffer;
    channel->autoConfigure = rg->autoConfigure;
    channel->autoStart = rg->autoStart;
    channel->autoStop = rg->autoStop;
    channel->autoFlushBuffers = rg->autoFlushBuffers;
    channel->isrInstall = rg->isrInstall;
    channel->isrUninstall = rg->isrUninstall;
    channel->interruptEnable = rg->interruptEnable;
    channel->state = A2D_CHANNEL_STATE_CLOSED;
    channel->handleSignature = A2D_INVALID_SIGNATURE;
    channel->next = head.next;
    channel->prev = &head;
    head.next->prev = channel;
    head.next = channel;
    channelCount++;
    
    naA2dNotify(channel, NA_A2D_CHANNEL_UP);
    
    result = NA_SUCCESS;

naA2dRegisterChannelEnd:
    if (result != NA_SUCCESS)
    {
        if (channel != NULL)
        {
            free(channel);
        }
    }
    
    A2D_SEMAPHORE_RELEASE;
    
    return result;
}



/*ad*
 * Deregister a channel that was registered with 
 * @link naA2dRegisterChannel.
 *
 * @since 7.3
 * @category BSP:Device_Drivers:a2d
 *
 * @param id        identifier of channel to deregister
 *
 * @return NA_SUCCESS                   channel has been deregistered
 * @return NA_A2D_UNKNOWN_IDENTIFIER    no channel with that identifier
*ad*/
NaStatus naA2dDeregisterChannel(NAA2dIdentifier_t id)
{
    NAA2dChannelTable_t *channel = NULL;
    NaStatus result = NA_SUCCESS;
    
    A2D_SEMAPHORE_GET;

    channel = naA2dFindChannel(id);
    if (channel == NULL)
    {
        result = NA_A2D_UNKNOWN_IDENTIFIER;
        goto naA2dDeregisterChannelEnd;
    }
    
    naA2dNotify(channel, NA_A2D_CHANNEL_DOWN);

    channel->autoStop(channel->context);
    channel->autoFlushBuffers(channel->context);
    channel->interruptEnable(channel->context, FALSE);
    channel->isrUninstall(channel->context);
    channel->close(channel->context);
    
    channel->prev->next = channel->next;
    channel->next->prev = channel->prev;
    free(channel);
    channelCount--;
    result = NA_SUCCESS;
    
naA2dDeregisterChannelEnd:
    A2D_SEMAPHORE_RELEASE;
    
    return result;
}




/*ad*
 * Use this routine to acquire ownership of an A/D channel.  A channel must be
 * opened with this function before it can be used.  Once it is opened, other
 * calls to this function will fail until the channel is closed by calling 
 * @link naA2dClose.
 *
 * @since 7.3
 * @external    
 * @category BSP:Device_Drivers:a2d
 *
 * @param id            identifier of channel to open
 * @param handle        pointer to buffer to load with handle for this channel
 *
 * @return NA_SUCCESS                   the caller now has ownership of the channel
 * @return NA_A2D_UNKNOWN_IDENTIFIER    the id passed does not match any known A/D channels
 * @return NA_A2D_NOT_AVAILABLE         channel has already been opened 
 * @return NA_A2D_BAD_ARGUMENT          handle is NULL
*ad*/
NaStatus naA2dOpen(NAA2dIdentifier_t id, NAA2dHandle_t * handle)
{
    NaStatus result = NA_SUCCESS;
    NAA2dChannelTable_t *channel = NULL;
    
    A2D_SEMAPHORE_GET;
    
    if ((id == NULL) || (handle == NULL))
    {
        result = NA_A2D_BAD_ARGUMENT;
        goto naA2dOpenEnd;
    }
    
    channel = naA2dFindChannel(id);
    
    if (channel == NULL)
    {
        result = NA_A2D_UNKNOWN_IDENTIFIER;
        goto naA2dOpenEnd;
    }
    
    if (channel->state != A2D_CHANNEL_STATE_CLOSED)
    {
        result = NA_A2D_NOT_AVAILABLE;
        goto naA2dOpenEnd;
    }
    
    result = channel->open(channel->context);
    if (result == NA_SUCCESS)
    {
        channel->state = A2D_CHANNEL_STATE_OPEN;
        channel->handleSignature = A2D_VALID_SIGNATURE;
        *handle = (NAA2dHandle_t) channel;
    }

naA2dOpenEnd:
    A2D_SEMAPHORE_RELEASE;
    return result;
}





/*ad*
 * Use this function to close a channel.  
 *
 * The A/D channel is disabled and all configuration settings are lost.  Any 
 * pending automatic read buffers are returned to the caller.  After this 
 * function completes, other processes will be able to use naA2dOpen to 
 * acquire ownership of the channel.
 *
 * @since 7.3
 * @external    
 * @category BSP:Device_Drivers:a2d
 *
 * @param handle        handle acquired with naA2dOpen
 *
 * @return NA_SUCCESS           channel has been closed
 * @return NA_A2D_BAD_HANDLE    handle is invalid
*ad*/
NaStatus naA2dClose(NAA2dHandle_t handle)
{
    NaStatus result = NA_SUCCESS;
    NAA2dChannelTable_t *channel = (NAA2dChannelTable_t *) handle;
    
    A2D_SEMAPHORE_GET;
    
    if (channel == NULL)
    {
        result = NA_A2D_BAD_HANDLE;
        goto naA2dCloseEnd;
    }
    
    if ((channel->state != A2D_CHANNEL_STATE_OPEN) || (channel->handleSignature != A2D_VALID_SIGNATURE))
    {
        result = NA_A2D_BAD_HANDLE;
        goto naA2dCloseEnd;
    }
    
    channel->autoStop(channel->context);
    channel->autoFlushBuffers(channel->context);
    channel->interruptEnable(channel->context, FALSE);
    channel->isrUninstall(channel->context);
    result = channel->close(channel->context);

    channel->handleSignature = A2D_INVALID_SIGNATURE;
    channel->state = A2D_CHANNEL_STATE_CLOSED;

naA2dCloseEnd:
    A2D_SEMAPHORE_RELEASE;
    
    return result;
}



/*ad*
 * This function must be called to initialize the API before using any other
 * functions.  You usually do not need to include a call to this function in 
 * your code since NET+OS calls it during start up on platforms that support
 * an A/D converter.
 *
 * @since 7.3
 * @category BSP:Device_Drivers:a2d
 *
 * @return NA_SUCCESS               The API has been initialized
 * @return NA_A2D_OUT_OF_RESOURCES  out of memory
*ad*/
NaStatus naA2dInitialize(void)
{
    static BOOLEAN initialized = FALSE;
    
    if (!initialized)
    {
        if (tx_semaphore_create(&naA2dSemaphore, "a2d", 1) != TX_SUCCESS)
        {
            return NA_A2D_OUT_OF_RESOURCES;
        }
        
        A2D_SEMAPHORE_GET;
        
        head.next = &head;
        head.prev = &head;
        
        notifyHead.next = &notifyHead;
        notifyHead.prev = &notifyHead;
        
        A2D_SEMAPHORE_RELEASE;
        
        initialized = TRUE;
    }
    
    return NA_SUCCESS;
}

