/*****************************************************************************
* 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 "nastatus.h"
#include "mcdma_def.h"
#include "scm_reg.h"
#include "nammu.h"
#include "NA_ISR.h"
#include "tx_api.h"




#if (PROCESSOR == ns9215)

#include "ns9215_a2d.h"
#include "iohub_reg.h"
#include "uart_16750.h"
#include "a2d_ns9215.h"


#define MAX_DMA_DESCRIPTORS         (128)

#define STATE_CLOSED            (0)
#define STATE_OPEN              (1)
#define STATE_CONFIGURED        (2)
#define STATE_DMA_CONFIGURED    (3)
#define STATE_DMA_RUNNING       (4)

#define SERIAL_PORT_D           (3)


static NAA2dAutoBufferReturnFn_t volatile bufferReturnFn = NULL;
static void *bufferReturnContext = NULL;

static NA_DMA_DESCRIPTOR_TYPE volatile *bufferDescriptor = NULL;
static NA_DMA_DESCRIPTOR_TYPE *bufferDescriptorVa = NULL;

static unsigned dmaHead = 0;
static unsigned dmaTail = 0;
static unsigned inputCount = 0;
static unsigned state = STATE_CLOSED;

#define NEXT_DMA_BUFFER_INDEX(x)    ((x+1) % MAX_DMA_DESCRIPTORS)

static unsigned volatile isBufferDescriptorAvailable[MAX_DMA_DESCRIPTORS];
static void *dmaBufferVA[MAX_DMA_DESCRIPTORS];
static unsigned volatile dmaBufferCount = 0;
static BOOLEAN dmaNeedsToBeReset = TRUE;

static NAA2dIsr_t callerIsr = NULL;
static BOOLEAN isCallerIsrEnabled = FALSE;


static NaStatus na9215A2dInit(void *context);
static NaStatus na9215A2dOpen(void *context);
static NaStatus na9215A2dConfigure(void *context, BYTE * inputs, WORD64 desiredClockRate, WORD64 * actualClockRate);
static NaStatus na9215A2dReadInput(void *context, unsigned int input, unsigned int * value);
static NaStatus na9215A2dAutoConfigure(void *context, NAA2dAutoBufferReturnFn_t newBufferReturnFn, void *callbackContext);
static NaStatus na9215A2dAutoLoadBuffer(void *context, void * buffer, unsigned int length);
static NaStatus na9215A2dAutoStart(void *context);
static NaStatus na9215A2dAutoStop(void *context);
static NaStatus na9215A2dAutoFlushBuffers(void *context);
static NaStatus na9215A2dIsrInstall(void *context, NAA2dIsr_t isr, void * isrContext);
static NaStatus na9215A2dIsrUninstall(void *context);
static NaStatus na9215A2dInterruptEnable(void * context, unsigned int isEnabled);
static NaStatus na9215A2dClose(void * context);


/*
 * This function is called when the channel is registered.  Set the state 
 * variable and return.
 */
static NaStatus na9215A2dInit(void *context)
{
    (void) context;
    
    state = STATE_CLOSED;
    
    return NA_SUCCESS;
}

/*
 * This function is called when the channel is opened.  Make sure we are
 * in the closed state, and then advance the state to opened.
 */
static NaStatus na9215A2dOpen(void *context)
{
    NaStatus result = NA_A2D_NOT_AVAILABLE;
    
    (void) context;
    
    if (state == STATE_CLOSED)
    {
        state = STATE_OPEN;
        result = NA_SUCCESS;
        callerIsr = NULL;
        isCallerIsrEnabled = FALSE;
    }
    
    return result;
}





/*
 * Use this function to configure an A/D channel.  This function determines which
 * inputs the channel will scan and the scan rate. 
 *
 * The argument inputs points to an array of bytes which must be at least 
 * ((maxInputs/8) + 1)) in length, where maxInputs is the maximum number of inputs
 * the channel supports.  An input is enabled by setting the corresponding bit in
 * the array.  Bit 0 of byte 0 determines whether input 0 is enabled, bit 1 of byte 1
 * determines if input 1 is enabled, bit 0 of byte 1 determines if input 8 is enabled,
 * and so on.
 *
 * The sampling rate is the number of inputs sampled per second.  The function will 
 * set the channel's sampling rate as close to desiredClockRate as possible.  The 
 * actual clock rate is returned in *actualClockRate.  The caller can pass 
 * actualClockRate as NULL if it is not interested in it.
 *
 * @since 7.3
 * @external   
 * @category BSP:Device_Drivers:a2d
 *
 * @param handle            handle acquired with na9215A2dOpen
 * @param inputs            array of bytes which determines which inputs are to be enabled
 * @param desiredClockRate  desired scan rate in inputs sampled per second
 * @param actualClockRate   pointer to 64-bit word to load with the actual scan rate set
 *
 * @return NA_SUCCESS               channel has been configured
 * @return NA_A2D_BAD_HANDLE        the handle is invalid
 * @return NA_A2D_BAD_INPUT         numberOfInputs is too large
 * @return NA_A2D_BAD_CLOCK_RATE    desiredClockRate is out of range
 */
static NaStatus na9215A2dConfigure(void *context, BYTE * inputs, WORD64 desiredClockRate, WORD64 * actualClockRate)
{
    NaStatus result = NA_A2D_BAD_HANDLE;
    long long divisor, numerator;
    unsigned n;
    
    (void) context;

    if ((state != STATE_OPEN) && (state != STATE_CONFIGURED))
    {
        result = NA_A2D_INVALID_STATE;
        goto na9215A2dConfigureEnd;
    }
    
    inputCount = 0;
    
    /*
     * Make sure the clock rate is within range for this part.
     */
    if ((desiredClockRate.x > NA_A2D_MAX_CLOCK_RATE) || (desiredClockRate.x < NA_A2D_MIN_CLOCK_RATE))
    {
        result = NA_A2D_BAD_CLOCK_RATE;
        goto na9215A2dConfigureEnd;
    }
    
    /*
     * Determine which inputs are selected.  This part only
     * lets us select a range from 1 to n where n <= 8.  All inputs from 1 to n
     * must be used.  We figure out how many will be used and make sure the
     * use isn't trying to skip some.
     */
    switch (inputs[0])
    {
        case 0x1:
            inputCount = 1;
            break;
        case 0x3:
            inputCount = 2;
            break;
        case 0x7:
            inputCount = 3;
            break;
        case 0xf:
            inputCount = 4;
            break;
        case 0x1f:
            inputCount = 5;
            break;
        case 0x3f:
            inputCount = 6;
            break;
        case 0x7f:
            inputCount = 7;
            break;
        case 0xff:
            inputCount = 8;
            break;
        default:
            result = NA_A2D_BAD_INPUT;
            goto na9215A2dConfigureEnd;
    }
    
    /*
     * Make sure A/D module is enabled in SCM.
     */
    narm_write_reg(NA_SCM_CCR_REG, NA_SCM_CCR_ADDRESS, adc, SCM_CCR_ENABLE_CLOCK);
    narm_write_reg(NA_SCM_RSR_REG, NA_SCM_RSR_ADDRESS, adc, SCM_RSR_ENABLE_MODULE);
    
    /*
     * Make sure channel is disabled for now.
     */
    narm_write_reg(NA_A2D_CONFIG, NA_A2D_CONFIG_ADDRESS, adcen, FALSE);
    narm_write_reg(NA_A2D_CONFIG, NA_A2D_CONFIG_ADDRESS, dmaen, FALSE);
    
    /*
     * Make sure interrupts are disabled.
     */
    na9215A2dInterruptEnable(context, FALSE);
    
    /*
     * Configure number of inputs.
     */
    narm_write_reg(NA_A2D_CONFIG, NA_A2D_CONFIG_ADDRESS, sel, inputCount - 1);
    
    /*
     * Calculate value for N in clock divisor.  Use baby steps because the 
     * compiler sometimes generates bad code if you use complex equations
     * with long longs.
     *
     * In order to correct for rounding errors in integer math, we calculate
     * 10 times the divisor, add 5, and then divide by 10.
     *
     * The equation in the HRM is:  ADC clock = PLL clock / (2 x (N+1))
     * It takes 14 clock cycles to process each sample, so the sampling rate S
     * is: S = (299827200/(2(N+1)))/14
     *
     * Solving for N given a particular sampling rate gives:
     *
     * S = (PLL/(2(N+1)))/14
     * 14S = (PLL/(2(N+1)))
     * 14S(2(N+1)) = PLL
     * 2(N+1) = PLL/14S
     * (N+1) = PLL/28S
     * N = PLL/28S - 1
     */
    numerator = 10LL * NAgetPllOutputClkFreq();
    divisor = (28LL * desiredClockRate.x);
    divisor = numerator / divisor;
    divisor = divisor + 5LL;
    divisor = divisor / 10LL;
    divisor -= 1;
     
    n = (unsigned) (divisor & 0x3f);
    
    narm_write_reg(NA_A2D_CLOCK_CONFIG, NA_A2D_CLOCK_CONFIG_ADDRESS, n, n);
    narm_write_reg(NA_A2D_CLOCK_CONFIG, NA_A2D_CLOCK_CONFIG_ADDRESS, wait, 0);
    
    if (actualClockRate != NULL)
    {
        /* 
         * Calculate actual clock rate set.  Use baby steps because the 
         * compiler sometimes generates bad code if you use complex equations
         * with long longs.
         *
         * S = (299827200/(2(N+1)))/14
         */
        divisor += 1;
        divisor *= 2LL;
        numerator = 10LL * NAgetPllOutputClkFreq();
        numerator = numerator / divisor;
        numerator = numerator / 14LL;
        numerator += 5LL;
        numerator /= 10LL;
        actualClockRate->x = numerator;
    }
    
    /*
     * Turn the channel on, but leave DMA off until we actually configure
     * it.
     */
    narm_write_reg(NA_A2D_CONFIG, NA_A2D_CONFIG_ADDRESS, adcen, TRUE);

    state = STATE_CONFIGURED;
    result = NA_SUCCESS;
    
na9215A2dConfigureEnd:
    return result;
}





/*
 * Read the last sampled value for one input.
 *
 * The A/D converter continuously samples the inputs once it has been
 * set up.  This function reads the last value sampled for the specified
 * input pin.
 *
 * @since 7.3
 * @external    
 * @category BSP:Device_Drivers:a2d
 *
 * @param handle    handle acquired with na9215A2dOpen
 * @param input     indicates which input to scan
 * @param value     pointer to word to load with last sampled value for the input
 *
 * @return NA_SUCCESS               the function call succeeded
 * @return NA_A2D_BAD_HANDLE        the handle is invalid
 * @return NA_A2D_BAD_INPUT         input is larger than numberOfInputs passed to na9215A2dConfigure
 * @return NA_A2D_NOT_CONFIGURED    channel has not been configured
 * @return NA_A2D_BAD_ARGUMENT      value argument is NULL
 */
static NaStatus na9215A2dReadInput(void *context, unsigned int input, unsigned int * value)
{
    NaStatus result = NA_SUCCESS;
    
    (void) context;
    
    if (input >= inputCount)
    {
        result = NA_A2D_BAD_INPUT;
        goto na9215A2dReadInputEnd;
    }
    
    if (value == NULL)
    {
        result = NA_A2D_BAD_ARGUMENT;
        goto na9215A2dReadInputEnd;
    }
    
    if (state < STATE_CONFIGURED)
    {
        result = NA_A2D_NOT_CONFIGURED;
        goto na9215A2dReadInputEnd;
    }

    *value = narm_read_reg(NA_A2D_OUTPUT, NA_A2D_OUTPUT_ADDRESS(input), dout);

na9215A2dReadInputEnd:
    return result;
}

    

/*
 * Use this function to set the buffer callback function used in auto scan mode.
 *
 * Note that UART-D and the A/D converter share the same DMA channel.  Only one
 * of the two can be used at a time.
 *
 * This function does the following:
 *
 *  1.  Edit checks the function's parameters.
 *  2.  Disables the DMA channel.
 *  3.  Sets the DMA enable bit in the A/D converter configuration register.  
 *      This has the effect of claiming the DMA channel for the A/D converter.
 *  4.  Mallocs a buffer to be used for the DMA buffer descriptors and
 *      invalidates it. 
 *  5.  Initializes the buffer descriptors with the FULL bits set so the 
 *      DMA channel does not attempt to transfer empty buffers.
 *  6.  Loads the buffer's physical address into the  DMA channel's buffer 
 *      descriptor register.
 *  7.  Save's the caller's buffer return function.
 *
 * @since 7.3
 * @external    
 * @category BSP:Device_Drivers:a2d
 *
 * @param handle            handle acquired with na9215A2dOpen
 * @param bufferReturnFn    callback function to be used to return buffers to the API user
 *
 * @return NA_SUCCESS               the function call succeeded
 * @return NA_A2D_BAD_HANDLE        the handle is invalid
 * @return NA_A2D_NOT_SUPPORTED     the A/D channel does not support automatic reads
 * @return NA_A2D_BAD_ARGUMENT      bufferReturnFn is NULL
 * @return NA_A2D_OUT_OF_RESOURCES    resources needed are in use by another process 
 */
static NaStatus na9215A2dAutoConfigure(void *context, NAA2dAutoBufferReturnFn_t newBufferReturnFn, void *callbackContext)
{
    NaStatus result = NA_SUCCESS;
    static int isFirstTime = TRUE;
    
    (void) context;
    
    if (newBufferReturnFn == NULL)
    {
        result = NA_A2D_BAD_ARGUMENT;
        goto na9215A2dAutoConfigureEnd;
    }
    
    if ((state != STATE_CONFIGURED) && (state != STATE_DMA_CONFIGURED))
    {
        result = NA_A2D_NOT_CONFIGURED;
        goto na9215A2dAutoConfigureEnd;
    }
    
    /*
     * Make sure DMA is turned off.
     */
    narm_write_reg(NA_IOHUB_DMA_RX_CONTROL, NA_IOHUB_DMA_RX_CONTROL_getAddress(NA_IOHUB_A2D_INDEX), ce, FALSE);
    narm_write_reg(NA_IOHUB_DMA_RX_CONTROL, NA_IOHUB_DMA_RX_CONTROL_getAddress(NA_IOHUB_A2D_INDEX), direct, NA_IOHUB_DMA_RX_CONTROL_DMA_MODE);

    /*
     * Set the enable bit in the A/D converter control register.  This switches the 
     * channel from UART D to the A/D converter.
     */
    narm_write_reg(NA_A2D_CONFIG, NA_A2D_CONFIG_ADDRESS, dmaen, TRUE);
    
    /*
     * Allocate DMA buffer descriptors.
     */
    if (isFirstTime)
    {
        unsigned bdvAddress = (unsigned) malloc((2*NA_CACHE_LINE_SIZE) + (MAX_DMA_DESCRIPTORS * sizeof(NA_DMA_DESCRIPTOR_TYPE)));
        
        if (bdvAddress == 0)
        {
            panic("A/D Converter driver was not able to allocate memory.");
        }
        bufferDescriptorVa = (NA_DMA_DESCRIPTOR_TYPE *) ((bdvAddress + NA_CACHE_LINE_SIZE) & ~(NA_CACHE_LINE_SIZE-1));
        isFirstTime = FALSE;
        if (bdvAddress == 0)
        {
            result = NA_A2D_OUT_OF_RESOURCES;
            goto na9215A2dAutoConfigureEnd;
        }
    }
    dmaHead = narm_read_reg(NA_IOHUB_DMA_RX_CONTROL, NA_IOHUB_DMA_RX_CONTROL_getAddress(NA_IOHUB_A2D_INDEX), index);
    dmaTail = dmaHead;
    
    {
        unsigned i;
        physical_address_t physicalAddress;
        
        NAInvalidateBuffer(bufferDescriptorVa, MAX_DMA_DESCRIPTORS * sizeof(NA_DMA_DESCRIPTOR_TYPE));
        bufferDescriptor = NA_UNCACHED((void *)bufferDescriptorVa);
        dmaBufferCount = 0;
        
        for (i = 0; i < MAX_DMA_DESCRIPTORS; i++)
        {
            bufferDescriptor[i].flag = DMA_FULL_MASK;
            bufferDescriptor[i].src_addr = 0;
            bufferDescriptor[i].buf_len = 0;
            isBufferDescriptorAvailable[i] = TRUE;
            dmaBufferVA[i] = NULL;
        }
        bufferDescriptor[i].flag |= DMA_WRAP_MASK;
        
        if (!NAVaToPhys((void *) bufferDescriptor, &physicalAddress))
        {
            panic("Unable to map A/D buffer descriptor array to physical address.");
        }
        narm_write_reg(NA_IOHUB_DMA_RX_BDP, NA_IOHUB_DMA_RX_BDP_getAddress(NA_IOHUB_A2D_INDEX), reg, (unsigned) physicalAddress);
    }

    bufferReturnFn = newBufferReturnFn;
    bufferReturnContext = callbackContext;
    state = STATE_DMA_CONFIGURED;
    dmaNeedsToBeReset = TRUE;
    
na9215A2dAutoConfigureEnd:
    return result;
}
    

/*
 * This function is called to reset the A/D converter and the DMA channel.  
 * This is necessary whenever the DMA is started, or has to be restarted
 * after a buffer not ready condition.
 *
 * The steps are:
 *
 *  1. Shut down the A/D converter and configure it for DMA operation.
 *  2. Reset the A/D converter by toggling the ADC bit in the master reset
 *     register.  The A/D converter configuration settings should be retained
 *     across the reset.
 *  3. Flush the DMA channel FIFO.
 *  4. Enable the DMA channel.
 *  5. Enable the A/D converter.
 *
 * The buffer descriptors will already have been set up before this function
 * is called.
 *
 * Note that we are doing a couple of screwy things in this API as far as the
 * DMA goes.  First, the HRM says that we should leave the A/D disabled
 * until we are ready to start DMA transfers.  We don't want to do that because
 * we want to let users call na9215A2dReadInput to read the A/D on the fly, even
 * while DMA has been stopped.  Secondly, there is no way to reset the DMA 
 * channel's index without resetting all of the DMA channels.  So, we keep track of
 * what the index is in software and carefully avoid ever needed to reset it 
 * back to zero.
 */
static void resetDma(void)
{
    TX_INTERRUPT_SAVE_AREA;
    
    TX_DISABLE;
    /*
     * Shut down ADC but enable DMA on it.  The DMA channel is shared by both 
     * the ADC and serial port D.  Enabling DMA in the ADC config register grabs
     * the DMA channel away from the serial port.
     */
    narm_write_reg(NA_A2D_CONFIG, NA_A2D_CONFIG_ADDRESS, adcen, FALSE);
    narm_write_reg(NA_A2D_CONFIG, NA_A2D_CONFIG_ADDRESS, dmaen, TRUE);
    
    /*
     * Reset the ADC.  Configuration settings should be maintained across the
     * reset, or at least that's what the ASIC designer told me....
     */
    narm_write_reg(NA_SCM_RSR_REG, NA_SCM_RSR_ADDRESS, adc, SCM_RSR_RESET_MODULE);
    narm_write_reg(NA_SCM_RSR_REG, NA_SCM_RSR_ADDRESS, adc, SCM_RSR_ENABLE_MODULE);
    
    /*
     * Flush the DMA channel FIFO.  The DMA channel is shared with serial port D, which
     * is why we have to twiddle a serial port register to flush the DMA channel.
     */
    narm_write_reg(NA_16750_CONFIG, NA_16750_CONFIG_getAddress(SERIAL_PORT_D), rxflush, 1);
    narm_write_reg(NA_16750_CONFIG, NA_16750_CONFIG_getAddress(SERIAL_PORT_D), rxflush, 0);

    /*
     * Enable the DMA channel.
     */
    narm_write_reg(NA_IOHUB_DMA_RX_CONTROL, NA_IOHUB_DMA_RX_CONTROL_getAddress(NA_IOHUB_A2D_INDEX), ce, FALSE);
    narm_write_reg(NA_IOHUB_DMA_RX_CONTROL, NA_IOHUB_DMA_RX_CONTROL_getAddress(NA_IOHUB_A2D_INDEX), ce, TRUE);

    /*
     * Enable the A/D converter.
     */
    narm_write_reg(NA_A2D_CONFIG, NA_A2D_CONFIG_ADDRESS, adcen, TRUE);  
    
    /*
     * Clear reset flag.
     */
    dmaNeedsToBeReset = FALSE;
    
    TX_RESTORE;
}

        

/*
 * Use this function to load a read buffer when using the auto read function.
 *
 * This function registers a buffer with an A/D channel.  The buffer will be 
 * placed in a receive queue of buffers, and data will be copied into it from 
 * the A/D converter.  The buffer must be aligned on a 32-bit boundary. 
 *
 * Each sample is read into a 32-bit word.  The buffer length must be an even 
 * multiple of the number of selected inputs times four (4 bytes in each word).  
 * For example, if 3 inputs are selected, then the buffer length must be an 
 * even multiple of 12 (4 bytes per word times 3 inputs).
 *
 * Automatic reads are disabled until na9215A2dAutoStart is called.
 *
 * The software and hardware will already have been configured for DMA before 
 * this function is called.  This means there will be a set of DMA buffer 
 * descriptors ready, and the DMA channel's registers will be set up.
 *
 * This function does the following:
 *
 *  1.  Edit checks function parameters.
 *  2.  Verifies that there is a buffer descriptor available.
 *  3.  Prepares the buffer for DMA.
 *  4.  Updates the next DMA buffer descriptor with the buffers physical
 *      and sets the appropriate flags.
 *
 * @since 7.3
 * @external    
 * @category BSP:Device_Drivers:a2d
 *
 * @param handle            handle acquired with na9215A2dOpen
 * @param buffer            buffer to place into queue
 * @param length            length of buffer in bytes
 *
 * @return NA_SUCCESS               the function call succeeded
 * @return NA_A2D_NOT_CONFIGURED    the A/D converter channel has not been configured
 * @return NA_A2D_BAD_HANDLE        the handle is invalid
 * @return NA_A2D_BAD_LENGTH        length is invalid
 * @return NA_A2D_BAD_ARGUMENT      the buffer is NULL or invalid
 * @return NA_A2D_NOT_SUPPORTED     the channel does not support automatic reads
 * @return NA_A2D_BUFFER_Q_FULL     the channel cannot accept anymore buffers
 * @return NA_A2D_OUT_OF_RESOURCES	resources needed are in use by another process 
 */
static NaStatus na9215A2dAutoLoadBuffer(void *context, void * buffer, unsigned int length)
{
    NaStatus result = NA_SUCCESS;
    physical_address_t physicalAddress;
    TX_INTERRUPT_SAVE_AREA;
    
    (void) context;
    
    if (buffer == NULL)
    {
        result = NA_A2D_BAD_ARGUMENT;
        goto na9215A2dAutoLoadBufferEnd;
    }
    
    if ((length == 0) || (length > DMA_BBUS_MAX_BUFFER_SIZE) || ((length % (4*inputCount)) != 0))
    {
        result = NA_A2D_BAD_LENGTH;
        goto na9215A2dAutoLoadBufferEnd;
    }
    
    if (state < STATE_DMA_CONFIGURED)
    {
        result = NA_A2D_NOT_CONFIGURED;
        goto na9215A2dAutoLoadBufferEnd;
    }
    
/*
 * Make sure the buffer is aligned on a cache line boundary.
 */
    if ((((unsigned) buffer) & (NA_CACHE_LINE_SIZE - 1)) != 0)
    {
        result = NA_A2D_BAD_ARGUMENT;
        goto na9215A2dAutoLoadBufferEnd;
    }
    
    TX_DISABLE;
    if (isBufferDescriptorAvailable[dmaHead] == FALSE)
    {
        result = NA_A2D_BUFFER_Q_FULL;
        goto na9215A2dAutoLoadBufferRestoreInterrupts;
    }
    
    isBufferDescriptorAvailable[dmaHead] = FALSE;
    
    if (!NAVaToPhys(buffer, &physicalAddress))
    {
        panic("Unable to map buffer address to physical address.");
    }
    NABeforeDMA(NA_DEVICE_TO_MEMORY_DMA, NULL, buffer, (unsigned long) length);
    bufferDescriptor[dmaHead].src_addr = (unsigned) physicalAddress;
    bufferDescriptor[dmaHead].buf_len = length;
    dmaBufferVA[dmaHead] = buffer;
    dmaBufferCount++;
    
    if (dmaHead == (MAX_DMA_DESCRIPTORS - 1))
    {
        bufferDescriptor[dmaHead].flag = DMA_WRAP_MASK | DMA_INTERRUPT_MASK;
    }
    else
    {
        bufferDescriptor[dmaHead].flag = DMA_INTERRUPT_MASK;
    }
    
    dmaHead = NEXT_DMA_BUFFER_INDEX(dmaHead);
    
    /*
     * We must reset the DMA channel if we run out of buffers.  The ISR will set the
     * dmaNeedsToBeReset flag whenever this happens.
     */
    if ((state == STATE_DMA_RUNNING) && (dmaNeedsToBeReset))
    {
        resetDma();
    }
    
na9215A2dAutoLoadBufferRestoreInterrupts:
    TX_RESTORE;
    
na9215A2dAutoLoadBufferEnd:
    return result;
}


/*
 * This interrupt service routine is called when the DMA engine finishes
 * processing a buffer.  This could be because it has filled the buffer, which causes
 * a normal completion interrupt, or because the current buffer was aborted, which
 * causes an abort completion interrupt.
 *
 * We acknowledge the interrupt and send the buffer up to the API user.
 *
 * It is also possible to get an error completion interrupt.  This indicates a serious
 * error and causes us to panic.
 *
 * An NRIP interrupt indicates the channel ran out of buffers, and we must reset
 * it before it can start the next buffer.  Usually we just set a flag to tell the
 * na9215A2dAutoLoadBuffer routine to take care of that.  However, there is a race
 * condition where na9215A2dAutoLoadBuffer could add a new buffer just after this
 * interrupt is signalled (but while interrupts are disabled).  In that case we 
 * reset the DMA channel ourselves.
 */
static int dmaIsr(void *context)
{
    (void) context;
    
    if ((*NA_IOHUB_IFS_getAddress(NA_IOHUB_A2D_INDEX) & NA_IOHUB_IFS_RXECIP_MASK) != 0)
    {
        panic("Error completion interrupt generated by A/D DMA.");
    }
    
    if ((*NA_IOHUB_IFS_getAddress(NA_IOHUB_A2D_INDEX) & NA_IOHUB_IFS_RXNRIP_MASK) != 0)
    {
        *NA_IOHUB_IFS_getAddress(NA_IOHUB_A2D_INDEX) &= NA_IOHUB_IFS_RXNRIP_MASK;
        /*
         * If we get here, then the DMA engine ran out of buffers because software didn't
         * keep up.  In that case, we have to reset the DMA engine before we start the
         * next buffer in order to make sure we the inputs read into the buffer are placed
         * in the correct order.
         */
        if ((dmaBufferCount) && (state == STATE_DMA_RUNNING))
        {
            /*
             * It is possible software added a new buffer just as this interrupt was
             * signalled.  We catch that case here and handle it by resetting the DMA
             * controller within this ISR.
             */
            resetDma();
        }
        else
        {
            /*
             * Under normal conditions, we just set a flag that tells the 
             * na9215A2dAutoLoadBuffer routine to reset the channel when it
             * loads the next buffer.
             */
            dmaNeedsToBeReset = TRUE;
        }
    }            
    *NA_IOHUB_IFS_getAddress(NA_IOHUB_A2D_INDEX) &= (NA_IOHUB_IFS_RXNCIP_MASK | NA_IOHUB_IFS_RXCAIP_MASK);

    while ((bufferDescriptor[dmaTail].flag & DMA_FULL_MASK) && (dmaTail != dmaHead))
    {
        NAAfterDMA(NA_DEVICE_TO_MEMORY_DMA, NULL, dmaBufferVA[dmaTail], bufferDescriptor[dmaTail].buf_len);
        bufferReturnFn(bufferReturnContext, dmaBufferVA[dmaTail], bufferDescriptor[dmaTail].buf_len);
        dmaBufferVA[dmaTail] = NULL;
        dmaBufferCount--;
        isBufferDescriptorAvailable[dmaTail] = TRUE;
        dmaTail = NEXT_DMA_BUFFER_INDEX(dmaTail);
    }
    
    return 0;
}




/*
 * Use this function to start automatic reads on the channel.
 *
 * This function enables automatic reads on an A/D channel.  The channel 
 * must be configured for automatic reads, and at least one buffer must 
 * have been loaded in the buffer queue.
 *
 * While the automatic read feature is enabled, the channel continuously 
 * samples its inputs and writes the values into buffers registered with 
 * na9215A2dAutoLoadBuffer.  The API passes full buffers to the caller by 
 * calling the buffer return function specified in na9215A2dAutoConfigure.  
 *
 * Automatic reads continue until naAd2AutoStop is called.  If channel runs 
 * out of buffers, then data read while there are no buffers available is lost.
 *
 * The API user must use the na9215A2dAutoConfigure and na9215A2dAutoLoadBufer
 * to setup DMA operations before calling this function.
 *
 * This function does the following:
 *
 *  1.  Verifies the channel has been set up and that buffers are available.
 *  2.  Installs an ISR for the DMA channel.
 *  3.  Starts the DMA transfers.
 *
 * @since 7.3
 * @external 
 * @category BSP:Device_Drivers:a2d
 *
 * @param handle
 *
 * @return NA_SUCCESS               the function call succeeded
 * @return NA_A2D_NOT_CONFIGURED    the A/D converter channel has not been configured
 * @return NA_A2D_BAD_HANDLE        the handle is invalid
 * @return NA_A2D_NO_BUFFERS        no buffers have been loaded into the receive ring
 * @return NA_A2D_NOT_SUPPORTED     the channel does not support automatic reads
 * @return NA_A2D_OUT_OF_RESOURCES    required resource is in use by another process 
 */
static NaStatus na9215A2dAutoStart(void *context)
{
    NaStatus result = NA_SUCCESS;
    
    (void) context;
    if (state != STATE_DMA_CONFIGURED)
    {
        result = NA_A2D_NOT_CONFIGURED;
        goto na9215A2dAutoStartEnd;
    }
    
    if (dmaBufferCount == 0)
    {
        result = NA_A2D_NO_BUFFERS;
        goto na9215A2dAutoStartEnd;
    }
    
    /*
     * Disable all interrupts from DMA controller and ACK all pending interrupts.
     */
    *NA_IOHUB_DMA_RX_ICR_getAddress(NA_IOHUB_A2D_INDEX) = 0;     /* start with all interrupts disabled*/
    *NA_IOHUB_IFS_getAddress(NA_IOHUB_A2D_INDEX) = *NA_IOHUB_IFS_getAddress(NA_IOHUB_A2D_INDEX);

    if (naIsrInstall(UARTD_INTERRUPT, dmaIsr, NULL) != SUCCESS)
    {
        panic("Unable to install A/D DMA ISR.");
    }

    /*
     * Enable normal completion, error completion, and channel abort interrupts.
     */
    narm_write_reg(NA_IOHUB_DMA_RX_ICR, NA_IOHUB_DMA_RX_ICR_getAddress(NA_IOHUB_A2D_INDEX), rxncie, NA_IOHUB_DMA_RX_ICR_ENABLED);
    narm_write_reg(NA_IOHUB_DMA_RX_ICR, NA_IOHUB_DMA_RX_ICR_getAddress(NA_IOHUB_A2D_INDEX), rxecie, NA_IOHUB_DMA_RX_ICR_ENABLED);
    narm_write_reg(NA_IOHUB_DMA_RX_ICR, NA_IOHUB_DMA_RX_ICR_getAddress(NA_IOHUB_A2D_INDEX), rxcaie, NA_IOHUB_DMA_RX_ICR_ENABLED);
    narm_write_reg(NA_IOHUB_DMA_RX_ICR, NA_IOHUB_DMA_RX_ICR_getAddress(NA_IOHUB_A2D_INDEX), rxnrie, NA_IOHUB_DMA_RX_ICR_ENABLED);

    state = STATE_DMA_RUNNING;
    /*
     * Reset the channel.  This will enable it.
     */
    resetDma();

na9215A2dAutoStartEnd:    
    return result;
}






/*
 * Use this function to stop automatic reads on an A/D channel.
 *
 * This function stops automatic reads on an A/D channel.  If a buffer 
 * has some data when the transfer is stopped, the buffer is closed and 
 * sent to the caller. 
 *
 * @since 7.3
 * @external 
 * @category BSP:Device_Drivers:a2d
 *
 * @param handle        handle acquired with na9215A2dOpen
 *
 * @return NA_SUCCESS               the function call succeeded
 * @return NA_A2D_NOT_RUNNING       the A/D converter channel has not been configured
 * @return NA_A2D_BAD_HANDLE        the handle is invalid
 * @return NA_A2D_NOT_SUPPORTED     the channel does not support automatic reads
 * @return NA_A2D_OUT_OF_RESOURCES    DMA channel is reserved for use by another process 
 */
static NaStatus na9215A2dAutoStop(void *context)
{
    NaStatus result = NA_SUCCESS;
    
    (void) context;
    if (state != STATE_DMA_RUNNING)
    {
        result = NA_A2D_NOT_RUNNING;
        goto na9215A2dAutoStopEnd;
    }
    
    narm_write_reg(NA_IOHUB_DMA_RX_CONTROL, NA_IOHUB_DMA_RX_CONTROL_getAddress(NA_IOHUB_A2D_INDEX), ce, FALSE);
    narm_write_reg(NA_IOHUB_DMA_RX_CONTROL, NA_IOHUB_DMA_RX_CONTROL_getAddress(NA_IOHUB_A2D_INDEX), ca, TRUE);
    narm_write_reg(NA_IOHUB_DMA_RX_CONTROL, NA_IOHUB_DMA_RX_CONTROL_getAddress(NA_IOHUB_A2D_INDEX), ca, FALSE);

    naIsrUninstall(UARTD_INTERRUPT);
    
    state = STATE_DMA_CONFIGURED;

na9215A2dAutoStopEnd:    
    return result;
}




/*
 * Use this function to flush all of the read buffers enqueued for a DMA channel.
 *
 * This function disables automatic reads and flushes all buffers in the receive 
 * queue.  The buffers are returned to the caller through the return function 
 * registered with na9215A2dAutoConfigure.
 *
 * @since 7.3
 * @external 
 * @category BSP:Device_Drivers:a2d
 *
 * @param handle        handle acquired with na9215A2dOpen
 *
 * @return NA_SUCCESS               the function call succeeded
 * @return NA_A2D_NOT_CONFIGURED    the A/D converter channel has not been configured
 * @return NA_A2D_BAD_HANDLE        the handle is invalid
 * @return NA_A2D_NOT_SUPPORTED     the channel does not support automatic reads
 * @return NA_A2D_OUT_OF_RESOURCES	DMA channel is reserved for use by another process 
 */
static NaStatus na9215A2dAutoFlushBuffers(void *context)
{
    NaStatus result = NA_A2D_BAD_HANDLE;
    unsigned i;
    
    if (state < STATE_DMA_CONFIGURED)
    {
        result = NA_A2D_NOT_CONFIGURED;
        goto na9215A2dAutoFlushBuffersEnd;
    }
    /*
     * The following call will stop DMA transfers and cause the ISR to send
     * any completed buffers to the API user.
     */
    na9215A2dAutoStop(context);
    
    for (i = 0; i < MAX_DMA_DESCRIPTORS; i++)
    {
        if (dmaBufferVA[i] != NULL)
        {
            bufferReturnFn(bufferReturnContext, dmaBufferVA[i], 0);
            dmaBufferVA[i] = NULL;
            isBufferDescriptorAvailable[i] = TRUE;
        }
    }

    dmaHead = narm_read_reg(NA_IOHUB_DMA_RX_CONTROL, NA_IOHUB_DMA_RX_CONTROL_getAddress(NA_IOHUB_A2D_INDEX), index);
    dmaTail = dmaHead;
    dmaBufferCount = 0;
    result = NA_SUCCESS;
    
na9215A2dAutoFlushBuffersEnd:
    return result;
}


/*
 * This is the interrupt service routine for the sampling interrupt.  It is used 
 * the API user registers a callback on the interrupt with na9215A2dIsrInstall.
 */
static int inputIsr(void *context)
{
    unsigned input = narm_read_reg(NA_A2D_CONFIG, NA_A2D_CONFIG_ADDRESS, instat);
    unsigned value = narm_read_reg(NA_A2D_OUTPUT, NA_A2D_OUTPUT_ADDRESS(input), dout);
    
    /*
     * Ack interrupt by writing 1 and then 0.
     */
    narm_write_reg(NA_A2D_CONFIG, NA_A2D_CONFIG_ADDRESS, intclr, 1);
    narm_write_reg(NA_A2D_CONFIG, NA_A2D_CONFIG_ADDRESS, intclr, 0);
    
    if ((callerIsr == NULL) || (isCallerIsrEnabled == FALSE))
    {
        naInterruptDisable(ADC_INTERRUPT);
    }
    else
    {
        callerIsr(context, input, value);
    }

    return 0;
}


/*
 * Use this function to install an interrupt service routine.
 *
 * This routine installs an interrupt service routine.  The A/D converter 
 * generates an interrupt every time it finishes sampling an input.  The 
 * ISR will be called with the channel number, input number, context, and 
 * sampled value.
 *
 * This function does not enable the interrupt.  Call na9215A2dIsrEnable to 
 * enable it.
 *
 * @since 7.3
 * @external 
 * @category BSP:Device_Drivers:a2d
 *
 * @param handle        handle acquired with na9215A2dOpen
 * @param isr           pointer to interrupt service routine to install
 * @param isrContext    this parameter is passed to the ISR
 *
 * @return NA_SUCCESS               the ISR has been installed
 * @return NA_A2D_NOT_CONFIGURED    the A/D converter channel has not been configured
 * @return NA_A2D_BAD_HANDLE        the handle is invalid
 * @return NA_A2D_NOT_SUPPORTED     the channel does not support ISRs
 * @return NA_A2D_OUT_OF_RESOURCES  cannot allocated buffers or other resources
 * @return NA_A2D_BAD_ARGUMENT      isr is NULL
 */
static NaStatus na9215A2dIsrInstall(void *context, NAA2dIsr_t isr, void * isrContext)
{
    NaStatus result = NA_SUCCESS;
    
    (void) context;
    
    if (isr == NULL)
    {
        result = NA_A2D_BAD_ARGUMENT;
        goto na9215A2dIsrInstallEnd;
    }
    
    if (state < STATE_CONFIGURED)
    {
        result = NA_A2D_NOT_CONFIGURED;
        goto na9215A2dIsrInstallEnd;
    }
    
    if (callerIsr != NULL)
    {
        result = NA_A2D_ISR_ALREADY_INSTALLED;
        goto na9215A2dIsrInstallEnd;
    }

    callerIsr = isr;
    isCallerIsrEnabled = FALSE;
    
    if (naIsrInstall(ADC_INTERRUPT, inputIsr, isrContext) != SUCCESS)
    {
        callerIsr = NULL;
        result = NA_A2D_NOT_SUPPORTED;
        goto na9215A2dIsrInstallEnd;
    }
    
na9215A2dIsrInstallEnd:    
    
    return result;
}





/*
 * Use this routine to uninstall an interrupt service routine.
 *
 * This function disables the A/D interrupt and uninstalls 
 * the ISR previously installed by na9215A2dIsrInstall.
 *
 * @since 7.3
 * @external 
 * @category BSP:Device_Drivers:a2d
 *
 * @param handle        handle acquired with na9215A2dOpen
 *
 * @return NA_SUCCESS               the ISR has been uninstalled
 * @return NA_A2D_NOT_CONFIGURED    the A/D converter channel has not been configured
 * @return NA_A2D_BAD_HANDLE        the handle is invalid
 * @return NA_A2D_NOT_SUPPORTED     the channel does not support ISRs
 */
static NaStatus na9215A2dIsrUninstall(void *context)
{
    NaStatus result = NA_SUCCESS;
    
    (void) context;
    
    if (state < STATE_CONFIGURED)
    {
        result = NA_A2D_NOT_CONFIGURED;
        goto na9215A2dIsrUninstallEnd;
    }

    if (callerIsr)
    {
        naIsrUninstall(ADC_INTERRUPT);
        callerIsr = NULL;
    }
    isCallerIsrEnabled = FALSE;
    
na9215A2dIsrUninstallEnd:    
    
    return result;
}






/*
 * Use this function to enable or disable interrupts from a channel.
 *
 * @since 7.3
 * @external 
 * @category BSP:Device_Drivers:a2d
 *
 * @param handle        handle acquired with na9215A2dOpen
 * @param isEnabled     set to TRUE to enable interrupts, or FALSE to disable
 *
 * @return NA_SUCCESS               the new interrupt mask has been set
 * @return NA_A2D_NOT_CONFIGURED    the A/D converter channel has not been configured
 * @return NA_A2D_BAD_HANDLE        the handle is invalid
 * @return NA_A2D_NOT_SUPPORTED     the channel does not support ISRs
 * @return NA_A2D_ISR_NOT_INSTALLED use na9215A2dIsrInstall before calling this function
 */
static NaStatus na9215A2dInterruptEnable(void * context, unsigned int isEnabled)
{
    NaStatus result = NA_SUCCESS;
    
    (void) context;
    
    if (callerIsr != NULL)
    {
        isCallerIsrEnabled = isEnabled;
        if (isEnabled)
        {
            naInterruptEnable(ADC_INTERRUPT);
        }
        else
        {
            naInterruptDisable(ADC_INTERRUPT);
        }
    }
    else
    {
        result = NA_A2D_ISR_NOT_INSTALLED;
    }
    
    return result;
}



/*
 * Close the A/D channel.  
 *
 *  1.  Disable and remove the user's ISR.
 *  2.  Stop DMA and flush all buffers.
 *  3.  Release the buffer descriptor RAM previously allocated.
 *  4.  Mark state as closed.
 */
static NaStatus na9215A2dClose(void * context)
{
    na9215A2dInterruptEnable(context, FALSE);
    na9215A2dIsrUninstall(context);
    na9215A2dAutoStop(context);
    na9215A2dAutoFlushBuffers(context);
    
    /*
     * Make sure the DMA channel and A/D converter are both shut off.
     */
    narm_write_reg(NA_A2D_CONFIG, NA_A2D_CONFIG_ADDRESS, dmaen, FALSE);
    narm_write_reg(NA_A2D_CONFIG, NA_A2D_CONFIG_ADDRESS, adcen, FALSE);
    
    /*
     * Free the DMA buffer descriptors.
     */
    state = STATE_CLOSED;
    
    return NA_SUCCESS;
}


/*
 * This function is called by the NET+OS start up code to register the
 * the NS9215 A/D support functions in this file with the general 
 * purpose A/D API.
 */
void na9215A2dRegister(void)
{
    WORD64 minClockRate, maxClockRate;
    naA2dRegistrationSettings_t rg;
    
    minClockRate.x = NA_A2D_MIN_CLOCK_RATE;
    maxClockRate.x = NA_A2D_MAX_CLOCK_RATE;
    
    rg.structVersion =      NA_A2D_REGISTRATION_SETTINGS_VERSION_1;
    rg.identifier =         NS9215_A2D_IDENTIFIER;   
    rg.maxInputs =          NA_A2D_MAX_INPUTS;       
    rg.resolution =         NA_A2D_RESOLUTION;       
    rg.minClockRate =       minClockRate;            
    rg.maxClockRate =       maxClockRate;            
    rg.context =            NULL;                    
    rg.init =               na9215A2dInit;           
    rg.open =               na9215A2dOpen;           
    rg.close =              na9215A2dClose;          
    rg.configure =          na9215A2dConfigure;      
    rg.readInput =          na9215A2dReadInput;      
    rg.autoLoadBuffer =     na9215A2dAutoLoadBuffer; 
    rg.autoConfigure =      na9215A2dAutoConfigure;  
    rg.autoStart =          na9215A2dAutoStart;      
    rg.autoStop =           na9215A2dAutoStop;       
    rg.autoFlushBuffers =   na9215A2dAutoFlushBuffers;
    rg.isrInstall =         na9215A2dIsrInstall;     
    rg.isrUninstall =       na9215A2dIsrUninstall;   
    rg.interruptEnable =    na9215A2dInterruptEnable;
    
    if (naA2dRegisterChannel(&rg) != NA_SUCCESS)
    {
        panic("Unable to initialize A/D API");
    }
}



#endif

