/*
 *  Copyright (c) 1996-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
 *
 * =======================================================================
 * This module contains APIs for I/O processor interface.
 *
 * Edit Date/Ver   Edit Description
 * ==============  =======================================================
 *
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iop_api.h>
#include "iop_private.h"
#include <iop_reg.h>
#include <iohub_reg.h>
#include <NA_ISR.h>
#include <nammu.h>
#include <netoserr.h>

#ifdef NETOS_GNU_TOOLS
static iop_bdp_t iopTxBds[NA_IOP_MAX_PICS][IOP_MAX_DMA_DESCRIPTORS] __attribute__ ((aligned(32)));
static iop_bdp_t iopRxBds[NA_IOP_MAX_PICS][IOP_MAX_DMA_DESCRIPTORS] __attribute__ ((aligned(32)));
#else
#pragma alignvar (32)
static iop_bdp_t iopTxBds[NA_IOP_MAX_PICS][IOP_MAX_DMA_DESCRIPTORS];
#pragma alignvar (32)
static iop_bdp_t iopRxBds[NA_IOP_MAX_PICS][IOP_MAX_DMA_DESCRIPTORS];
#endif

static iop_isr_cb_t iopIsrCb[NA_IOP_MAX_PICS] = 
{
    {NULL, NULL, 0},
    {NULL, NULL, 0}
};

static iop_sem_cb_t iopSemCb[NA_IOP_MAX_PICS] = 
{
    {NULL, NULL, NULL}, 
    {NULL, NULL, NULL}
};

static iop_dma_t iopDmaCnfg[NA_IOP_MAX_PICS];


/*ad*
 *
 * The NS9210 and NS9215 processors have two I/O processors, called
 * flexible interface modules (FIMs).  These processors
 * can be used to implement various I/O functions.  The I/O processors consist
 * of the following:
 *
 * <list type="ordered">
 * <item> A DRPIC1655X  processor that runs at 4 times the AHB clock speed </item>
 * <item> 1024 X 14-bits of instruction memory </item>
 * <item> 192-bytes of data memory </item>
 * <item> 16 bytes of registers dedicated to passing information from the ARM to the FIM </item>
 * <item> 16 bytes of registers dedicated to passing information from the FIM to the ARM </item>
 * <item> Transmit and receive FIFOs used to transfer bulk data between the FIM and ARM </item> 
 * <item> DMA channels that can automate the transfer of the bulk data </item>
 * <item> H/W extensions to support CAN protocol </item>
 * <item> H/W extensions to support bus transfers (referred to as "generic" hardware assist) </item>
 * <item> Access to numerous GPIO pins as determined by the configuration of the GPIO multiplexer </item>
 * </list>
 *
 * The CAN Hardware assist module is configured by the FIM processor
 *
 * The Generic Hardware Assist module provides the features to help implement
 * 16 and 24 bit data buses, and eight general purpose I/O lines.  Altogether, 
 * there are 2 buses, 8 general purpose I/Os, 4 control lines, and two clocks.
 *
 * In order to use an I/O processor, software on the ARM must write a program 
 * for the FIM into its memory, select the hardware assistance mode, and then 
 * start the FIM.  If the generic hardware assistance mode is selected, then 
 * the signals and buses for it must also be configured before the FIM is 
 * started.  
 *
 * @note Since naIopProcessorDownloadProgram() also does the initialization,
 * like enabling the clock to FIMs, arming the FIM interrupt, and setting
 * up DMA buffer descriptors, the user must call this function before calling 
 * any DMA related IOP APIs.
 *
 * @overview IOP
 * @category BSP:Device_Drivers:IOP_DEVICE_API
 * @include "iop_api.h"
 * @platform    All_9210_Platforms, All_9215_Platforms
 * @library     bsp
 * @since 7.3
 *
*ad*/



/***************************************************************************
 *  Local IOP driver functions                                             *
 ***************************************************************************/

/*
 * Checks the validity of the processor. Update switch case if you have more
 * processors
 */
static int iopGetPicIndex(NaIopProcessor_t processor)
{
    int indx = -1;

    switch (processor)
    {
        case NA_IOP_PROCESSOR_PIC0:
            indx = 0;
            break;
            
        case NA_IOP_PROCESSOR_PIC1:
            indx = 1;
            break;
        
        default:
            break;
    }

    if (indx < NA_IOP_MAX_PICS)
        return indx;
    else
        return -1; /* make sure to update NA_IOP_MAX_PICS */
}


/*
 * Function services the DMA receive interrupt
 * Note: Inputs are not checked for their validity to reduce the
 * process time. Since it is a local function hoping to receive
 * valid input paramaters :-). 
 */
static void iopServiceRxDma(NaIopProcessor_t processor, int indx, unsigned long status)
{
    iop_dma_t    *cnfg  = &iopDmaCnfg[indx];
    iop_dma_cb_t *rxCb  = &cnfg->rx_cb[cnfg->rx_first_idx];
    iop_bdp_t    *rxBdp = &cnfg->rx_bd[cnfg->rx_first_idx];
    unsigned     result = 0;

    if (status & NA_IOHUB_IFS_RXECIP_MASK)
        result |= NA_IOP_DMA_ECIP;

    if (status & NA_IOHUB_IFS_RXCAIP_MASK)
    {
        /* clear if aborted from user */
        if (narm_read_reg (NA_IOHUB_DMA_RX_CONTROL, NA_IOHUB_DMA_RX_CONTROL_getAddress(indx), ca))
            narm_write_reg (NA_IOHUB_DMA_RX_CONTROL, NA_IOHUB_DMA_RX_CONTROL_getAddress(indx), ca, 0);
        //else panic("iopServiceRxDma() aborted\n");
            
        result |= NA_IOP_DMA_CANCELLED;
    }

    if (status & (NA_IOHUB_IFS_RXNCIP_MASK | NA_IOHUB_IFS_RXNRIP_MASK))
        result |= NA_IOP_DMA_SUCCESS;

    while (rxBdp->src_addr && (rxBdp->flags & DMA_FULL))
    {
        NAAfterDMA (NA_DEVICE_TO_MEMORY_DMA, NULL, rxBdp->src_addr, rxCb->buf_len);
        
        if (rxCb->cb_f)
        {
            rxCb->cb_f(processor, rxBdp->src_addr, rxBdp->length, result, rxCb->context);    
        }

        rxBdp->src_addr = NULL;
        rxBdp->length   = 0;

        rxCb->cb_f      = NULL;
        rxCb->context   = NULL;

        cnfg->rx_first_idx = (cnfg->rx_first_idx >= (IOP_MAX_DMA_DESCRIPTORS - 1)) ? 0 : cnfg->rx_first_idx+1; 

        if (cnfg->rx_first_idx == cnfg->rx_free_idx)
            break; /* no more buffer available to read */

        rxCb  = &cnfg->rx_cb[cnfg->rx_first_idx];
        rxBdp = &cnfg->rx_bd[cnfg->rx_first_idx];                
    }
    
    /* more to receive? */    
    if ((cnfg->rx_first_idx != cnfg->rx_free_idx) && ((result & NA_IOP_DMA_CANCELLED) == 0))
    {
        narm_write_reg (NA_IOHUB_DMA_RX_CONTROL, NA_IOHUB_DMA_RX_CONTROL_getAddress(indx), ce, 0);
        narm_write_reg (NA_IOHUB_DMA_RX_CONTROL, NA_IOHUB_DMA_RX_CONTROL_getAddress(indx), ce, 1);
    }
}

/*
 * Function services the DMA transmit interrupt
 * Note: Inputs are not checked for their validity to reduce the
 * process time. Since it is a local function hoping to receive
 * valid input paramaters :-). 
 */
static void iopServiceTxDma(NaIopProcessor_t processor, int indx, unsigned long status)
{
    iop_dma_t    *cnfg  = &iopDmaCnfg[indx];
    iop_dma_cb_t *txCb  = &cnfg->tx_cb[cnfg->tx_first_idx];
    iop_bdp_t    *txBdp = &cnfg->tx_bd[cnfg->tx_first_idx];
    unsigned     result = 0;

    if (status & NA_IOHUB_IFS_TXECIP_MASK)
        result |= NA_IOP_DMA_ECIP;

    if (status & NA_IOHUB_IFS_TXCAIP_MASK)
    {
        /* clear if aborted from user */
        if (narm_read_reg (NA_IOHUB_DMA_TX_CONTROL, NA_IOHUB_DMA_TX_CONTROL_getAddress(indx), ca))
            narm_write_reg (NA_IOHUB_DMA_TX_CONTROL, NA_IOHUB_DMA_TX_CONTROL_getAddress(indx), ca, 0);
        //else panic("iopServiceTxDma() aborted\n");
            
        result |= NA_IOP_DMA_CANCELLED;
    }

    if (status & (NA_IOHUB_IFS_TXNCIP_MASK | NA_IOHUB_IFS_TXNRIP_MASK))
        result |= NA_IOP_DMA_SUCCESS;

    while (txBdp->src_addr && ((txBdp->flags & DMA_FULL) == 0))
    {
        NAAfterDMA (NA_MEMORY_TO_DEVICE_DMA, txBdp->src_addr, NULL, txCb->buf_len);

        if (txCb->cb_f)
        {
            txCb->cb_f(processor, txBdp->src_addr, (txCb->buf_len - txBdp->length), result, txCb->context);    
        }

        txBdp->src_addr = NULL;
        txBdp->length   = 0;
        txBdp->flags    = 0;

        txCb->cb_f      = NULL;
        txCb->context   = NULL;

        cnfg->tx_first_idx = (cnfg->tx_first_idx >= (IOP_MAX_DMA_DESCRIPTORS - 1)) ? 0 : cnfg->tx_first_idx+1; 

        if (cnfg->tx_first_idx == cnfg->tx_free_idx)
            break; /* no more buffer available for DMA to send */

        txCb  = &cnfg->tx_cb[cnfg->tx_first_idx];
        txBdp = &cnfg->tx_bd[cnfg->tx_first_idx];                
    }   

    /* more to transmit? */    
    if ((cnfg->tx_first_idx != cnfg->tx_free_idx) && ((result & NA_IOP_DMA_CANCELLED) == 0))
    {
        narm_write_reg (NA_IOHUB_DMA_TX_CONTROL, NA_IOHUB_DMA_TX_CONTROL_getAddress(indx), ce, 0);
        narm_write_reg (NA_IOHUB_DMA_TX_CONTROL, NA_IOHUB_DMA_TX_CONTROL_getAddress(indx), ce, 1);
    }     
}

/*
 * Function services the interrupt from FIM to ARM
 * Note: Inputs are not checked for their validity to reduce the
 * process time. Since it is a local function hoping to receive
 * valid input paramaters :-). 
 */
static void iopServicePic(NaIopProcessor_t processor, int indx)
{
    unsigned long volatile * const addr = NA_IOP_REG_GEN_CONTROL_REG_getAddress(indx);
    unsigned intrCode;

    intrCode = narm_read_reg(NA_IOP_REG_GEN_CONTROL, addr, intfrompic);

    /* call user callback function if it is registered */
    if (iopIsrCb[indx].cb_f)
    {
        iopIsrCb[indx].cb_f(processor, iopIsrCb[indx].context, intrCode);
    }

    /* ack the pic */
    narm_write_reg(NA_IOP_REG_GEN_CONTROL, addr, intackwr, 1);

    /* wait for pic to clear the interrupt code */
    while (narm_read_reg(NA_IOP_REG_GEN_CONTROL, addr, intfrompic) != 0){}

    /* clear the INTACKWR */
    narm_write_reg(NA_IOP_REG_GEN_CONTROL, addr, intackwr, 0);
}


/*
 * Service FIM 0 interrupt, can be generated by DMA engine or by FIM0
 */
static int iop0Isr(void *context)
{
    unsigned long status;
    (void) context;

    status = *NA_IOHUB_IFS_getAddress(NA_IOHUB_IOP_0_INDEX);

    /* clear the interrupt */
    *NA_IOHUB_IFS_getAddress(NA_IOHUB_IOP_0_INDEX) = status;

    if (status & IOP_DMA_RX_MASK)
        iopServiceRxDma(NA_IOP_PROCESSOR_PIC0, 0, status);
    
    if (status & IOP_DMA_TX_MASK)
        iopServiceTxDma(NA_IOP_PROCESSOR_PIC0, 0, status);

    if (status & NA_IOHUB_IFS_MODIP_MASK) /* interrupt from FIM 0? */
        iopServicePic(NA_IOP_PROCESSOR_PIC0, 0);

    /* clear the interrupt */
//    *NA_IOHUB_IFS_getAddress(NA_IOHUB_IOP_0_INDEX) = status;

    return 0;
}

/*
 * Service FIM 1 interrupt, can be generated by DMA engine or by FIM1
 */
static int iop1Isr(void *context)
{
    unsigned long status;
    (void) context;

    status = *NA_IOHUB_IFS_getAddress(NA_IOHUB_IOP_1_INDEX);

    /* clear the interrupt */
    *NA_IOHUB_IFS_getAddress(NA_IOHUB_IOP_1_INDEX) = status;

    if (status & IOP_DMA_RX_MASK)
        iopServiceRxDma(NA_IOP_PROCESSOR_PIC1, 1, status);
    
    if (status & IOP_DMA_TX_MASK)
        iopServiceTxDma(NA_IOP_PROCESSOR_PIC1, 1, status);

    if (status & NA_IOHUB_IFS_MODIP_MASK) /* interrupt from FIM 1? */
        iopServicePic(NA_IOP_PROCESSOR_PIC1, 1);

    /* clear the interrupt */
//    *NA_IOHUB_IFS_getAddress(NA_IOHUB_IOP_1_INDEX) = status;

    return 0;
}

/*
 * Initialize IOP DMA channel for a given processor
 */
static void iopInitDmaChannel(NaIopProcessor_t processor)
{
    int i, indx = iopGetPicIndex(processor);
    physical_address_t physAddr;
    
    if ((indx >= 0) && (indx < NA_IOP_MAX_PICS))
    {
        memset(&iopDmaCnfg[indx], 0, sizeof (iop_dma_t));
    
        NAInvalidateBuffer(iopTxBds[indx], sizeof iopTxBds[indx]);
        NAInvalidateBuffer(iopRxBds[indx], sizeof iopRxBds[indx]);

        iopDmaCnfg[indx].tx_bd = NAVaToUncachedVa(iopTxBds[indx]);
        iopDmaCnfg[indx].rx_bd = NAVaToUncachedVa(iopRxBds[indx]);

        memset(iopDmaCnfg[indx].tx_bd, 0, sizeof iopTxBds[indx]);
        memset(iopDmaCnfg[indx].rx_bd, 0, sizeof iopRxBds[indx]);

        /* set all the F bits in receive side */
        for (i = 0; i < IOP_MAX_DMA_DESCRIPTORS; i++)
            iopDmaCnfg[indx].rx_bd[i].flags = DMA_FULL;    

        /* setup IOP DMA for Rx */
        narm_write_reg (NA_IOHUB_DMA_RX_CONTROL, NA_IOHUB_DMA_RX_CONTROL_getAddress(indx), ce, 0);
        narm_write_reg (NA_IOHUB_DMA_RX_CONTROL, NA_IOHUB_DMA_RX_CONTROL_getAddress(indx), ca, 0);
        narm_write_reg (NA_IOHUB_DMA_RX_CONTROL, NA_IOHUB_DMA_RX_CONTROL_getAddress(indx), flex_io, NA_IOHUB_DMA_RX_CONTROL_CONTROLLED_BY_CPU);
        narm_write_reg (NA_IOHUB_DMA_RX_CONTROL, NA_IOHUB_DMA_RX_CONTROL_getAddress(indx), direct, NA_IOHUB_DMA_RX_CONTROL_DMA_MODE);

        /* setup IOP DMA Rx interrupts */
        narm_write_reg (NA_IOHUB_DMA_RX_ICR, NA_IOHUB_DMA_RX_ICR_getAddress(indx), rxncie, NA_IOHUB_DMA_RX_ICR_ENABLED);
        narm_write_reg (NA_IOHUB_DMA_RX_ICR, NA_IOHUB_DMA_RX_ICR_getAddress(indx), rxecie, NA_IOHUB_DMA_RX_ICR_ENABLED);
        narm_write_reg (NA_IOHUB_DMA_RX_ICR, NA_IOHUB_DMA_RX_ICR_getAddress(indx), rxnrie, NA_IOHUB_DMA_RX_ICR_ENABLED);
        narm_write_reg (NA_IOHUB_DMA_RX_ICR, NA_IOHUB_DMA_RX_ICR_getAddress(indx), rxcaie, NA_IOHUB_DMA_RX_ICR_ENABLED);
        
        /* setup IOP DMA for Tx */
        narm_write_reg (NA_IOHUB_DMA_TX_CONTROL, NA_IOHUB_DMA_TX_CONTROL_getAddress(indx), ce, 0);
        narm_write_reg (NA_IOHUB_DMA_TX_CONTROL, NA_IOHUB_DMA_TX_CONTROL_getAddress(indx), ca, 0);
        narm_write_reg (NA_IOHUB_DMA_TX_CONTROL, NA_IOHUB_DMA_TX_CONTROL_getAddress(indx), flex_io, NA_IOHUB_DMA_TX_CONTROL_CONTROLLED_BY_CPU);
        narm_write_reg (NA_IOHUB_DMA_TX_CONTROL, NA_IOHUB_DMA_TX_CONTROL_getAddress(indx), direct, NA_IOHUB_DMA_TX_CONTROL_DMA_MODE);

        /* setup IOP DMA Tx interrupts */
        narm_write_reg (NA_IOHUB_DMA_TX_ICR, NA_IOHUB_DMA_TX_ICR_getAddress(indx), txncie, NA_IOHUB_DMA_RX_ICR_ENABLED);
        narm_write_reg (NA_IOHUB_DMA_TX_ICR, NA_IOHUB_DMA_TX_ICR_getAddress(indx), txecie, NA_IOHUB_DMA_RX_ICR_ENABLED);
        narm_write_reg (NA_IOHUB_DMA_TX_ICR, NA_IOHUB_DMA_TX_ICR_getAddress(indx), txnrie, NA_IOHUB_DMA_RX_ICR_ENABLED);
        narm_write_reg (NA_IOHUB_DMA_TX_ICR, NA_IOHUB_DMA_TX_ICR_getAddress(indx), txcaie, NA_IOHUB_DMA_RX_ICR_ENABLED);
        
        /*
         * setup DMA buffer descriptor pointer
         */
        if (!NAVaToPhys ((const void *) iopDmaCnfg[indx].tx_bd, &physAddr))
            netosFatalError ("IOP tx DMA Cache error", 1, 1);

        narm_write_reg (NA_IOHUB_DMA_TX_BDP, NA_IOHUB_DMA_TX_BDP_getAddress(indx), reg, physAddr); 

        /* Convert to physical address */
        if (!NAVaToPhys ((const void *) iopDmaCnfg[indx].rx_bd, &physAddr))
            netosFatalError ("IOP rx DMA Cache error", 1, 1);

        narm_write_reg (NA_IOHUB_DMA_RX_BDP, NA_IOHUB_DMA_RX_BDP_getAddress(indx), reg, physAddr); 
    }
}

/*
 * Just to avoid some duplicate code...
 */
static NaStatus iopCheckInput(void *buffer, unsigned maxLength, NaIopDmaCallback_t callback)
{
    if (buffer == NULL)
        return NASTATUS_IOP_BAD_BUFFER;

    if ((maxLength == 0) || (maxLength > 0xFFFF))
        return NASTATUS_IOP_BAD_LENGTH;

    if (callback == NULL)
        return NASTATUS_IOP_BAD_CALLBACK;

    return NASTATUS_SUCCESS;
}

/*ad*
 * The purpose of this API is to provide the user to access the registered
 * semaphore functions, so that user can share the IOP register access with
 * IOP driver.  
 *
 * Function calls the pre-registered semaphore get callback. Does nothing if 
 * semaphore is not registered
 *
 * @param processor  set to indicate which processor
 *
 * @category BSP:Device_Drivers:IOP_DEVICE_API
 * @since 7.3
 *
 * @see @link BSP:Device_Drivers:IOP_DEVICE_API:naIopRegisterSemaphore
 * @see @link BSP:Device_Drivers:IOP_DEVICE_API:naIopPutSemaphore
 * @see @link BSP:Device_Drivers:IOP_DEVICE_API:NaIopProcessor_t
 *
 *ad*/
void naIopGetSemaphore(NaIopProcessor_t processor)
{
    int indx = iopGetPicIndex(processor);

    if ((indx >= 0) && (indx < NA_IOP_MAX_PICS))
    {
        if (iopSemCb[indx].getSem != NULL)
        {
            iopSemCb[indx].getSem(iopSemCb[indx].context);
        }
    }
}


/*ad*
 * The purpose of this API is to provide the user to access the registered
 * semaphore functions, so that user can share the IOP register access with
 * IOP driver.  
 *
 * Function calls the pre-registered semaphore put callback. Does nothing if 
 * semaphore is not registered
 *
 * @param processor  set to indicate which processor
 *
 * @category BSP:Device_Drivers:IOP_DEVICE_API
 * @since 7.3
 *
 * @see @link BSP:Device_Drivers:IOP_DEVICE_API:naIopRegisterSemaphore
 * @see @link BSP:Device_Drivers:IOP_DEVICE_API:naIopPutSemaphore
 * @see @link BSP:Device_Drivers:IOP_DEVICE_API:NaIopProcessor_t
 *ad*/
void naIopPutSemaphore(NaIopProcessor_t processor)
{
    int indx = iopGetPicIndex(processor);

    if ((indx >= 0) && (indx < NA_IOP_MAX_PICS))
    {
        if (iopSemCb[indx].putSem != NULL)
        {
            iopSemCb[indx].putSem(iopSemCb[indx].context);
        }
    }
}


/*ad*
 * This function flushes a DMA ring for a I/O processor.  All buffers in the 
 * ring are returned to the owner by calling the buffer return callback 
 * function provided when the buffer was enqueued.  If a buffer is currently 
 * being DMA'ed, then the transfer is aborted and the buffer is returned to 
 * the caller.
 *
 * @note make sure naIopProcessorDownloadProgram() was already called once 
 * before calling this function. Read the note in overview for more info on 
 * this.
 *
 * @param processor         set to indicate which processor
 * @param isTransmitQueue   set to 1 to flush the transmit DMA queue, clear to flush
 *                          the receive DMA queue
 *
 * @return     NASTATUS_SUCCESS	            the function call succeeded
 * @return     NASTATUS_IOP_BAD_PROCESSOR   the processor argument is out of range
 * @return     NASTATUS_IOP_BAD_ARGUMENT    isTransmitQueue is neither 1 nor 0
 *
 * @category BSP:Device_Drivers:IOP_DEVICE_API
 * @since 7.3
 *
 * @see @link BSP:Device_Drivers:IOP_DEVICE_API:naIopProcessorDownloadPicProgram
 * @see @link BSP:Device_Drivers:IOP_DEVICE_API:naIopDmaReceive
 * @see @link BSP:Device_Drivers:IOP_DEVICE_API:naIopDmaTransmit
 * @see @link BSP:Device_Drivers:IOP_DEVICE_API:NaIopProcessor_t
 *ad*/
NaStatus naIopDmaFlushQueue(NaIopProcessor_t processor, unsigned isTransmitQueue)
{
    NaStatus  ret = NASTATUS_SUCCESS;
    int       indx = iopGetPicIndex(processor);
    iop_dma_t *cnfg;
    TX_INTERRUPT_SAVE_AREA 

    indx = iopGetPicIndex(processor);
    if (indx < 0)
    {
        ret = NASTATUS_IOP_BAD_PROCESSOR;
        goto dmaFlushDone;
    }

    IOP_GET_SEM(processor);

    cnfg  = &iopDmaCnfg[indx];

    if (isTransmitQueue == 1)
    {
        if (cnfg->tx_bd != NULL)
        {
            if (cnfg->tx_bd[cnfg->tx_first_idx].flags & DMA_FULL)
            {
                if (iopIsrCb[indx].enabled)
                {
                    /* abort the current receive DMA queue */
                    narm_write_reg (NA_IOHUB_DMA_TX_CONTROL, NA_IOHUB_DMA_TX_CONTROL_getAddress(indx), ca, 1);
                    /* wait until it aborts successfully */
                    while (narm_read_reg (NA_IOHUB_DMA_TX_CONTROL, NA_IOHUB_DMA_TX_CONTROL_getAddress(indx), state) != NA_IOHUB_DMA_STATE_IDLE);
                }
    
                TX_DISABLE
    
                narm_write_reg (NA_IOHUB_DMA_TX_CONTROL, NA_IOHUB_DMA_TX_CONTROL_getAddress(indx), ce, 0);
                narm_write_reg (NA_IOHUB_DMA_TX_CONTROL, NA_IOHUB_DMA_TX_CONTROL_getAddress(indx), ca, 0);
                narm_write_reg (NA_IOHUB_DMA_TX_CONTROL, NA_IOHUB_DMA_TX_CONTROL_getAddress(indx), index, 0);

                /* flush the rest */
                while (cnfg->tx_bd[cnfg->tx_first_idx].flags & DMA_FULL)
                {
                    iop_dma_cb_t *txCb  = &cnfg->tx_cb[cnfg->tx_first_idx];
                    iop_bdp_t    *txBdp = &cnfg->tx_bd[cnfg->tx_first_idx];
    
                    if (txCb->cb_f)
                    {
                        txCb->cb_f(processor, txBdp->src_addr, txCb->buf_len, NA_IOP_DMA_FLUSHED, txCb->context);    
                    }
                    
                    memset(txCb, 0, sizeof *txCb);
                    memset(txBdp, 0, sizeof *txBdp);
    
                    cnfg->tx_first_idx = (cnfg->tx_first_idx >= (IOP_MAX_DMA_DESCRIPTORS - 1))?0:cnfg->tx_first_idx+1;                
                }
                
                cnfg->tx_first_idx = cnfg->tx_free_idx = 0;
                TX_RESTORE            
            }
        }
    }
    else if (isTransmitQueue == 0)
    {
        if (cnfg->rx_bd != NULL)
        {
            if (!(cnfg->rx_bd[cnfg->rx_first_idx].flags & DMA_FULL))
            {
                if (iopIsrCb[indx].enabled)
                {
                    /* abort the current receive DMA queue */
                    narm_write_reg (NA_IOHUB_DMA_RX_CONTROL, NA_IOHUB_DMA_RX_CONTROL_getAddress(indx), ca, 1);
                    /* wait until it aborts successfully */
                    while (narm_read_reg (NA_IOHUB_DMA_RX_CONTROL, NA_IOHUB_DMA_RX_CONTROL_getAddress(indx), state) != NA_IOHUB_DMA_STATE_IDLE);
                }
    
                TX_DISABLE
                
                narm_write_reg (NA_IOHUB_DMA_RX_CONTROL, NA_IOHUB_DMA_RX_CONTROL_getAddress(indx), ce, 0);
                narm_write_reg (NA_IOHUB_DMA_RX_CONTROL, NA_IOHUB_DMA_RX_CONTROL_getAddress(indx), ca, 0);
                         
                /* flush the rest */
                while (!(cnfg->rx_bd[cnfg->rx_first_idx].flags & DMA_FULL))
                {
                    iop_dma_cb_t *rxCb  = &cnfg->rx_cb[cnfg->rx_first_idx];
                    iop_bdp_t    *rxBdp = &cnfg->rx_bd[cnfg->rx_first_idx];
    
                    if (rxCb->cb_f)
                    {
                        rxCb->cb_f(processor, rxBdp->src_addr, rxBdp->length, NA_IOP_DMA_FLUSHED, rxCb->context);    
                    }
                    
                    memset(rxCb, 0, sizeof *rxCb);
                    memset(rxBdp, 0, sizeof *rxBdp);
                    rxBdp->flags = DMA_FULL;
    
                    cnfg->rx_first_idx = (cnfg->rx_first_idx >= (IOP_MAX_DMA_DESCRIPTORS - 1))?0:cnfg->rx_first_idx+1;          
                }
                
                /* we are not sure where the current DMA index will be, 
                   so read it from register */                
                cnfg->rx_first_idx = cnfg->rx_free_idx = narm_read_reg (NA_IOHUB_DMA_RX_CONTROL, 
                                                                NA_IOHUB_DMA_RX_CONTROL_getAddress(indx), index) 
                                                                & (IOP_MAX_DMA_DESCRIPTORS - 1);
                                                                           
                TX_RESTORE                        
            }
        }
    }
    else
    {
        ret = NASTATUS_IOP_BAD_ARGUMENT;
    }
    
    IOP_PUT_SEM(processor);

    dmaFlushDone:
    return ret;
}


/*ad*
 * This function adds a buffer onto the receive queue. As data is received 
 * from the FIM, it will be DMA'ed into buffers in the queue. The buffers 
 * will be returned to the caller when they are either full, or when the FIM 
 * closes the current buffer.
 *
 * The buffer must be aligned on a 32-bit boundary.
 *
 * The callback will be invoked from interrupt context.
 *
 * @note make sure naIopProcessorDownloadProgram() was already called once 
 * before calling this function. Read the note in overview for more info on 
 * this.
 *
 * @param processor       indicates which processor
 * @param buffer          pointer to buffer to place in receive queue  
 * @param maxLength       length of buffer in bytes
 * @param receiveCallback pointer to callback routine to call when buffer 
 *                        completes or is flushed  
 * @param context         value set will be passed to callback routine
 *
 * @return     NASTATUS_SUCCESS             the function call succeeded
 * @return     NASTATUS_IOP_BAD_PROCESSOR   the processor argument is out of range
 * @return     NASTATUS_IOP_BAD_BUFFER      buffer pointer is NULL or invalid
 * @return     NASTATUS_IOP_BAD_LENGTH      the maxLength argument is invalid
 * @return     NASTATUS_IOP_BAD_CALLBACK    the callback is invalid
 * @return     NASTATUS_IOP_QUEUE_IS_FULL   the receive queue is full
 *
 * @category BSP:Device_Drivers:IOP_DEVICE_API
 * @since 7.3
 *
 * @see @link BSP:Device_Drivers:IOP_DEVICE_API:naIopProcessorDownloadPicProgram
 * @see @link BSP:Device_Drivers:IOP_DEVICE_API:naIopDmaFlushQueue
 * @see @link BSP:Device_Drivers:IOP_DEVICE_API:naIopDmaTransmit
 * @see @link BSP:Device_Drivers:IOP_DEVICE_API:NaIopProcessor_t
 * @see @link BSP:Device_Drivers:IOP_DEVICE_API:NaIopDmaCallback_t
 *ad*/
NaStatus naIopDmaReceive(NaIopProcessor_t processor, void * buffer, unsigned maxLength, NaIopDmaCallback_t receiveCallback, void * context)
{
    NaStatus ret = NASTATUS_SUCCESS;
    int indx = iopGetPicIndex(processor);
    iop_dma_t *cnfg;
    physical_address_t physAddr;
    TX_INTERRUPT_SAVE_AREA 

    indx = iopGetPicIndex(processor);
    if (indx < 0)
    {
        ret = NASTATUS_IOP_BAD_PROCESSOR;
        goto dmaRxDone;
    }

    if ((ret = iopCheckInput(buffer, maxLength, receiveCallback)) != NASTATUS_SUCCESS)
        goto dmaRxDone;

    /* make sure address is 32-bit aligned */
    if ((int)buffer % 4)
    {
        ret = NASTATUS_IOP_BAD_BUFFER;
        goto dmaRxDone;
    }

    cnfg = &iopDmaCnfg[indx];
    if (!(cnfg->rx_bd[cnfg->rx_free_idx].flags & DMA_FULL))
    {
        ret = NASTATUS_IOP_QUEUE_IS_FULL;
        goto dmaRxDone;
    }

    IOP_GET_SEM(processor);

    TX_DISABLE

    cnfg->rx_cb[cnfg->rx_free_idx].cb_f    = receiveCallback;
    cnfg->rx_cb[cnfg->rx_free_idx].buf_len = maxLength;
    cnfg->rx_cb[cnfg->rx_free_idx].context = context;

    /* Setup buffer */
    if (!NAVaToPhys ((const void *) buffer, &physAddr))
        netosFatalError ("IOP rx DMA Cache error", 1, 1);

    NABeforeDMA (NA_DEVICE_TO_MEMORY_DMA, NULL, buffer, maxLength);

    cnfg->rx_bd[cnfg->rx_free_idx].src_addr = (void *)physAddr;
    cnfg->rx_bd[cnfg->rx_free_idx].dst_addr = 0;
    cnfg->rx_bd[cnfg->rx_free_idx].length   = maxLength;

    if (cnfg->rx_free_idx >= (IOP_MAX_DMA_DESCRIPTORS - 1))
    {
        cnfg->rx_bd[cnfg->rx_free_idx].flags = DMA_WRAP | DMA_INT;
        cnfg->rx_free_idx = 0;
    }
    else
    {
        cnfg->rx_bd[cnfg->rx_free_idx].flags = DMA_INT;
        cnfg->rx_free_idx++;
    }
            
    narm_write_reg (NA_IOHUB_DMA_RX_CONTROL, NA_IOHUB_DMA_RX_CONTROL_getAddress(indx), ce, 0);
    narm_write_reg (NA_IOHUB_DMA_RX_CONTROL, NA_IOHUB_DMA_RX_CONTROL_getAddress(indx), ce, 1);

    TX_RESTORE

    IOP_PUT_SEM(processor);
    
    dmaRxDone:
    return ret;
}

/*ad*
 * This function places a buffer on the transmit DMA ring. Buffers in the ring
 * are transmitted to the FIM through its transmit data FIFO.  The buffer will 
 * be returned to the caller via the supplied callback function when the buffer 
 * has been emptied, or flushed from the ring.
 *
 * The buffer must be aligned on a 32-bit boundary.
 *
 * The callback will be invoked from interrupt context.
 *
 * @note make sure naIopProcessorDownloadProgram() was already called once 
 * before calling this function. Read the note in overview for more info on 
 * this.
 *
 * @param processor        indicates which processor
 * @param buffer           pointer to buffer to place in transmit queue  
 * @param length           length of buffer in bytes
 * @param transmitCallback pointer to callback routine to call when buffer 
 *                         completes or is flushed  
 * @param context          value set will be passed to callback routine
 *
 * @return     NASTATUS_SUCCESS             the function call succeeded
 * @return     NASTATUS_IOP_BAD_PROCESSOR   the processor argument is out of range
 * @return     NASTATUS_IOP_BAD_BUFFER      buffer pointer is NULL or invalid
 * @return     NASTATUS_IOP_BAD_LENGTH      the length argument is invalid
 * @return     NASTATUS_IOP_BAD_CALLBACK    the callback is invalid
 * @return     NASTATUS_IOP_QUEUE_IS_FULL   the transmit queue is full
 *
 * @category BSP:Device_Drivers:IOP_DEVICE_API
 * @since 7.3
 *
 * @see @link BSP:Device_Drivers:IOP_DEVICE_API:naIopProcessorDownloadPicProgram
 * @see @link BSP:Device_Drivers:IOP_DEVICE_API:naIopDmaFlushQueue
 * @see @link BSP:Device_Drivers:IOP_DEVICE_API:naIopDmaReceive
 * @see @link BSP:Device_Drivers:IOP_DEVICE_API:NaIopProcessor_t
 * @see @link BSP:Device_Drivers:IOP_DEVICE_API:NaIopDmaCallback_t
 *ad*/
NaStatus naIopDmaTransmit(NaIopProcessor_t processor, void * buffer, unsigned length, NaIopDmaCallback_t transmitCallback, void * context)
{
    NaStatus ret = NASTATUS_SUCCESS;
    int indx = iopGetPicIndex(processor);
    iop_dma_t *cnfg;
    physical_address_t physAddr;
    TX_INTERRUPT_SAVE_AREA 

    indx = iopGetPicIndex(processor);
    if (indx < 0)
    {
        ret = NASTATUS_IOP_BAD_PROCESSOR;
        goto dmaTxDone;
    }

    if ((ret = iopCheckInput(buffer, length, transmitCallback)) != NASTATUS_SUCCESS)
        goto dmaTxDone;


    cnfg = &iopDmaCnfg[indx];
    if (cnfg->tx_bd[cnfg->tx_free_idx].flags & DMA_FULL)
    {
        ret = NASTATUS_IOP_QUEUE_IS_FULL;
        goto dmaTxDone;
    }

    IOP_GET_SEM(processor);

    TX_DISABLE

    cnfg->tx_cb[cnfg->tx_free_idx].cb_f    = transmitCallback;
    cnfg->tx_cb[cnfg->tx_free_idx].buf_len = length;
    cnfg->tx_cb[cnfg->tx_free_idx].context = context;

    /* Setup buffer */
    if (!NAVaToPhys ((const void *) buffer, &physAddr))
        netosFatalError ("IOP tx DMA Cache error", 1, 1);

    NABeforeDMA (NA_MEMORY_TO_DEVICE_DMA, buffer, NULL, length);

    cnfg->tx_bd[cnfg->tx_free_idx].src_addr = (void *)physAddr;
    cnfg->tx_bd[cnfg->tx_free_idx].dst_addr = 0;
    cnfg->tx_bd[cnfg->tx_free_idx].length   = length;

    if (cnfg->tx_free_idx >= (IOP_MAX_DMA_DESCRIPTORS - 1))
    {
        cnfg->tx_bd[cnfg->tx_free_idx].flags = DMA_WRAP | DMA_INT | DMA_LAST | DMA_FULL;
        cnfg->tx_free_idx = 0;
    }
    else
    {
        cnfg->tx_bd[cnfg->tx_free_idx].flags = DMA_INT | DMA_LAST | DMA_FULL;
        cnfg->tx_free_idx++;
    }
            
    narm_write_reg (NA_IOHUB_DMA_TX_CONTROL, NA_IOHUB_DMA_TX_CONTROL_getAddress(indx), ce, 0);
    narm_write_reg (NA_IOHUB_DMA_TX_CONTROL, NA_IOHUB_DMA_TX_CONTROL_getAddress(indx), ce, 1);

    TX_RESTORE

    IOP_PUT_SEM(processor);
    
    dmaTxDone:
    return ret;
}

/*ad*
 * This function selects the divisor for the output clock. The FIM clock, 
 * which runs at four times the AHB clock speed, is divided down by the 
 * selected amount to generate the output clock. 
 *
 * Note that the clock should already have been set when the FIM program was 
 * loaded.  This function allows ARM software to override this setting.
 *
 * @param processor  set to indicate which processor
 * @param divisor    selects the divisor for the output clock 
 *
 * @return NASTATUS_SUCCESS             the function call succeeded
 * @return NASTATUS_IOP_BAD_PROCESSOR   the processor argument is out of range
 * @return NASTATUS_IOP_BAD_DIVISOR     divisor is invalid
 *
 * @category BSP:Device_Drivers:IOP_DEVICE_API
 * @since 7.3
 *
 * @see @link BSP:Device_Drivers:IOP_DEVICE_API:NaIopProcessor_t
 * @see @link BSP:Device_Drivers:IOP_DEVICE_API:NaIopOutputClockDivisor_t
 *ad*/
NaStatus naIopHwaGenericSetOutputClockDivisor(NaIopProcessor_t processor, NaIopOutputClockDivisor_t divisor)
{
    NaStatus ret = NASTATUS_SUCCESS;
    int clkd, indx;

    indx = iopGetPicIndex(processor);
    if (indx < 0)
    {
        ret = NASTATUS_IOP_BAD_PROCESSOR;
        goto clkSetDone;
    }

    switch (divisor)
    {
        case NA_IOP_CLOCK_DIVISOR_2:
            clkd = NA_IOP_HWA_GEN_CFG_CLKSEL_DIVIDE_BY_2;
            break;
            
        case NA_IOP_CLOCK_DIVISOR_4:
            clkd = NA_IOP_HWA_GEN_CFG_CLKSEL_DIVIDE_BY_4;
            break;
            
        case NA_IOP_CLOCK_DIVISOR_8:
            clkd = NA_IOP_HWA_GEN_CFG_CLKSEL_DIVIDE_BY_8;
            break;
            
        case NA_IOP_CLOCK_DIVISOR_16:
            clkd = NA_IOP_HWA_GEN_CFG_CLKSEL_DIVIDE_BY_16;
            break;
            
        case NA_IOP_CLOCK_DIVISOR_32:
            clkd = NA_IOP_HWA_GEN_CFG_CLKSEL_DIVIDE_BY_32;
            break;
            
        case NA_IOP_CLOCK_DIVISOR_64:
            clkd = NA_IOP_HWA_GEN_CFG_CLKSEL_DIVIDE_BY_64;
            break;
            
        case NA_IOP_CLOCK_DIVISOR_128:
            clkd = NA_IOP_HWA_GEN_CFG_CLKSEL_DIVIDE_BY_128;
            break;
            
        case NA_IOP_CLOCK_DIVISOR_256:
            clkd = NA_IOP_HWA_GEN_CFG_CLKSEL_DIVIDE_BY_256;
            break;
            
        default:            
            ret = NASTATUS_IOP_BAD_DIVISOR;
            goto clkSetDone;
    }

    IOP_GET_SEM(processor);    
    narm_write_reg (NA_IOP_HWA_GEN_CFG, NA_IOP_HWA_GEN_CONF_REG_getAddress(indx), clksel, clkd);
    IOP_PUT_SEM(processor);

    clkSetDone:
    return ret;
}

/*ad*
 * Use this function to register an ISR callback routine. This routine will be
 * called whenever the FIM generates an interrupt to the ARM.  The interrupt 
 * will be acknowledged automatically when the ISR callback returns.
 *
 * @param processor     set to indicate which processor
 * @param isr           pointer to ISR callback routine   
 * @param context       value to be passed to the callback 
 *
 * @return NASTATUS_SUCCESS             the function call succeeded
 * @return NASTATUS_IOP_BAD_PROCESSOR   the processor argument is out of range
 * @return NASTATUS_IOP_BAD_CALLBACK    the isr argument is invalid
 * @return NASTATUS_IOP_INUSE           an ISR callback has already been registered  
 *
 * @category BSP:Device_Drivers:IOP_DEVICE_API
 * @since 7.3
 *
 * @see @link BSP:Device_Drivers:IOP_DEVICE_API:NaIopProcessor_t
 * @see @link BSP:Device_Drivers:IOP_DEVICE_API:NaIopIsrCallback_t
 * @see @link BSP:Device_Drivers:IOP_DEVICE_API:naIopUninstallIsr
 * @see @link BSP:Device_Drivers:IOP_DEVICE_API:naIopInterruptProcessor
 *ad*/
NaStatus naIopRegisterIsrCallback(NaIopProcessor_t processor, NaIopIsrCallback_t isr, void * context)
{
    NaStatus ret = NASTATUS_SUCCESS;
    int indx;

    indx = iopGetPicIndex(processor);
    if (indx < 0)
    {
        return NASTATUS_IOP_BAD_PROCESSOR;
    }

    IOP_GET_SEM(processor);

    if (isr == NULL)
    {
        ret = NASTATUS_IOP_BAD_CALLBACK;
        goto isrRegDone;
    }

    /* register isr callback function */    
    iopIsrCb[indx].cb_f    = isr;
    iopIsrCb[indx].context = context;
    ret = NASTATUS_SUCCESS;

    isrRegDone:
    IOP_PUT_SEM(processor);
    return ret;
}

/*ad*
 * Use this function to interrupt the FIM. The function will write the 
 * interruptCode into the INTTOPIC field of the FIM's Control Register, 
 * thereby generating an interrupt.  The function will then wait in a spin 
 * loop until the FIM acknowledges the interrupt.  Then it will clear the 
 * INTTOPIC field and return.
 *
 * @param processor     set to indicate which processor
 * @param interruptCode	interrupt code to send to the FIM   
 *
 * @return NASTATUS_SUCCESS             the function call succeeded
 * @return NASTATUS_IOP_BAD_PROCESSOR   the processor argument is out of range
 * @return NASTATUS_IOP_BAD_CODE        the interruptCode field is invalid
 *
 * @category BSP:Device_Drivers:IOP_DEVICE_API
 * @since 7.3
 *
 * @see @link BSP:Device_Drivers:IOP_DEVICE_API:NaIopProcessor_t
 * @see @link BSP:Device_Drivers:IOP_DEVICE_API:NaIopIsrCallback_t
 * @see @link BSP:Device_Drivers:IOP_DEVICE_API:naIopUninstallIsr
 *ad*/
NaStatus naIopInterruptProcessor(NaIopProcessor_t processor, unsigned interruptCode)
{
    NaStatus ret = NASTATUS_SUCCESS;
    int indx;
    unsigned long volatile * addr;

    indx = iopGetPicIndex(processor);
    if (indx < 0)
    {
        ret = NASTATUS_IOP_BAD_PROCESSOR;
        goto intrProcessorDone;
    }

    /* upto 7 bits and has to be non zero */
    if ((interruptCode == 0) || (interruptCode > 0x7F))
    {
        ret = NASTATUS_IOP_BAD_CODE;
        goto intrProcessorDone;
    }

    IOP_GET_SEM(processor);
    
    addr = NA_IOP_REG_GEN_CONTROL_REG_getAddress(indx);
    narm_write_reg (NA_IOP_REG_GEN_CONTROL, addr, inttopic, interruptCode);

    /* wait for pic to acknowledge */
    while (narm_read_reg(NA_IOP_REG_GEN_CONTROL, addr, intackrd) == 0){}

    /* clear INTOPIC */
    narm_write_reg (NA_IOP_REG_GEN_CONTROL, addr, inttopic, 0);

    /* wait for pic to clear the INTACKRD */
    while (narm_read_reg(NA_IOP_REG_GEN_CONTROL, addr, intackrd) != 0){}

    IOP_PUT_SEM(processor);
    
    intrProcessorDone:
    return ret;
}

/*ad*
 * This function is used to download a program to one of the FIM processors. 
 * The program is passed in a structure pointed to by program. This structure
 * contains both the FIM program and the configuration settings for the FIM 
 * H/W Assist module. This function configures the FIM H/W Assist module and
 * then downloads the program.
 *
 * The configuration settings for the generic H/W assist module are only 
 * written if the generic H/W assist mode is selected.
 *
 * @note Since naIopProcessorDownloadProgram() also does the initialization,
 * like enabling the clock to FIM modules, arming FIM interrupt and setting
 * up DMA buffer descriptors, user must call this function before calling 
 * any DMA related IOP APIs.
 *
 * @param processor     set to indicate which processor
 * @param program       pointer to FIM program   
 *
 * @return NASTATUS_SUCCESS                     the function call succeeded
 * @return NASTATUS_IOP_BAD_PROCESSOR           the processor argument is out of range
 * @return NASTATUS_IOP_BAD_PROGRAM             the program argument is invalid
 * @return NASTATUS_IOP_UNSUPPORTED_FORMAT      the format indicated by the format field in the program header 
 *                                              is not supported
 * @return NASTATUS_IOP_UNSUPPORTED_PROCESSOR   the processor indicated by the processorType field in the 
 *                                              program header is not supported
 * @return NASTATUS_IOP_BAD_LENGTH              the programLength numberOfInstructions is zero or too large 
 *                                              to fit in the FIM's program memory
 * @return NASTATUS_IOP_INVALID_CONFIGURATION   the hardware assist configuration is invalid
 * @return NASTATUS_IOP_PIC_IS_RUNNING          the FIM must be stopped before calling this function
 *
 * @category BSP:Device_Drivers:IOP_DEVICE_API
 * @since 7.3
 *
 * @see @link BSP:Device_Drivers:IOP_DEVICE_API:NaIopProcessor_t
 * @see @link BSP:Device_Drivers:IOP_DEVICE_API:NaIopProgram_t
 * @see @link BSP:Device_Drivers:IOP_DEVICE_API:naIopProcessorStartAtZero
 * @see @link BSP:Device_Drivers:IOP_DEVICE_API:naIopDmaReceive
 * @see @link BSP:Device_Drivers:IOP_DEVICE_API:naIopDmaTransmit
 *ad*/
NaStatus naIopProcessorDownloadPicProgram(NaIopProcessor_t processor, NaIopProgram_t const * program)
{
    NaStatus ret = NASTATUS_SUCCESS;
    int mode, indx = iopGetPicIndex(processor);
    unsigned offset;

    if (program == NULL)
    {
        ret = NASTATUS_IOP_BAD_PROGRAM;
        goto downloadDone;
    }

    if (indx < 0)
    {
        ret = NASTATUS_IOP_BAD_PROCESSOR;
        goto downloadDone;
    }

    if (narm_read_reg (NA_IOP_REG_GEN_CONTROL, NA_IOP_REG_GEN_CONTROL_REG_getAddress(indx), progmem))
    {
        ret = NASTATUS_IOP_PIC_IS_RUNNING;
        goto downloadDone;
    }

    if (!IOP_IS_PROCESSOR_TYPE_VALID(program->ns9215Header.processorType))
    {
        ret = NASTATUS_IOP_UNSUPPORTED_PROCESSOR;
        goto downloadDone;
    }

    if (!IOP_IS_FORMAT_VALID(program->ns9215Header.format))
    {
        ret = NASTATUS_IOP_UNSUPPORTED_FORMAT;
        goto downloadDone;
    }

    /* Enable the clock to IO processor */
    switch (processor)
    {
        case NA_IOP_PROCESSOR_PIC0:        
            narm_write_reg(NA_SCM_CCR_REG, NA_SCM_CCR_ADDRESS, iop0, SCM_CCR_ENABLE_CLOCK); 
            narm_write_reg(NA_SCM_RSR_REG, NA_SCM_RSR_ADDRESS, iop0, SCM_RSR_RESET_MODULE); 
            narm_write_reg(NA_SCM_RSR_REG, NA_SCM_RSR_ADDRESS, iop0, SCM_RSR_ENABLE_MODULE); 
            break;

        case NA_IOP_PROCESSOR_PIC1:        
            narm_write_reg(NA_SCM_CCR_REG, NA_SCM_CCR_ADDRESS, iop1, SCM_CCR_ENABLE_CLOCK); 
            narm_write_reg(NA_SCM_RSR_REG, NA_SCM_RSR_ADDRESS, iop1, SCM_RSR_RESET_MODULE); 
            narm_write_reg(NA_SCM_RSR_REG, NA_SCM_RSR_ADDRESS, iop1, SCM_RSR_ENABLE_MODULE); 
            break;

        default:
            ret = NASTATUS_IOP_BAD_PROCESSOR;
            goto downloadDone;
    }

    ret = naIopHwaGenericSetOutputClockDivisor(processor, program->ns9215Header.divisor);
    if (ret != NASTATUS_SUCCESS)
        goto downloadDone;

    switch (program->ns9215Header.hwMode)
    {
        case NA_IOP_HW_ASSIST_MODE_NONE:
            mode = 0;
            break;

        case NA_IOP_HW_ASSIST_MODE_GENERIC:
            mode = 1;
            break;

        case NA_IOP_HW_ASSIST_MODE_CAN:
            mode = 2;
            break;

        default:
            ret = NASTATUS_IOP_INVALID_CONFIGURATION;
            goto downloadDone;    
    }

    IOP_GET_SEM(processor);    
    
    narm_write_reg (NA_IOP_HWA_GEN_CFG, NA_IOP_HWA_GEN_CONF_REG_getAddress(indx), enable, NA_IOP_HW_ASSIST_MODE_NONE);
    narm_write_reg (NA_IOP_HWA_GEN_CFG, NA_IOP_HWA_GEN_CONF_REG_getAddress(indx), enable, mode);

    /* update HW assist config registers */
    for (offset = 0; offset < NA_IOP_NUMBER_OF_GENERIC_CONFIG_REGISTERS; offset++)
    {
        *(NA_IOP_HWA_SIGNAL_CONF_getAddress(indx, offset)) = program->ns9215Header.genericHwAssist[offset];
    }

    if (program->ns9215Header.numberOfInstructions > NA_IOP_NS9215_MAX_INSTRUCTIONS)
    {
        ret = NASTATUS_IOP_BAD_LENGTH;
    }
    else
    {
        for (offset = 0; offset < program->ns9215Header.numberOfInstructions; offset++)
        {
            *(NA_IOP_INSTRUCTION_getAddress(indx, offset)) = (unsigned long)(program->ns9215Header.instruction[offset] & NA_IOP_INSTRUCTION_MASK);
        }
    }

    /* setup DMA descriptors now */
    iopInitDmaChannel(processor);

    IOP_PUT_SEM(processor);    

    downloadDone:
    return ret;
}

/*ad*
 * This function starts the indicated FIM processor. The processor will begin
 * executing instructions at program address 0x0.  
 *
 * Software on the ARM must download a program to the FIM's program memory
 * and configure the hardware assistance modules before starting the FIM.
 *
 * @param processor     set to indicate which processor
 *
 * @return NASTATUS_SUCCESS             the function call succeeded
 * @return NASTATUS_IOP_BAD_PROCESSOR   the processor argument is out of range
 *
 * @category BSP:Device_Drivers:IOP_DEVICE_API
 * @since 7.3
 *
 * @see @link BSP:Device_Drivers:IOP_DEVICE_API:NaIopProcessor_t
 * @see @link BSP:Device_Drivers:IOP_DEVICE_API:naIopProcessorStopAndReset
 * @see @link BSP:Device_Drivers:IOP_DEVICE_API:naIopProcessorDownloadPicProgram
 *ad*/
NaStatus naIopProcessorStartAtZero(NaIopProcessor_t processor)
{
    NaStatus ret = NASTATUS_SUCCESS;
    int status = SUCCESS, indx = iopGetPicIndex(processor);

    if (indx < 0)
        return NASTATUS_IOP_BAD_PROCESSOR;

    IOP_GET_SEM(processor);

    /* isr already installed? */
    if (iopIsrCb[indx].enabled == 0)
    {
        switch (processor)
        {
            case NA_IOP_PROCESSOR_PIC0:
                status = naIsrInstall(IOP0_INTERRUPT, iop0Isr, NULL);
                if (status == SUCCESS)
                    naInterruptEnable(IOP0_INTERRUPT);
                break;

            case NA_IOP_PROCESSOR_PIC1:
                indx = 1;
                status = naIsrInstall(IOP1_INTERRUPT, iop1Isr, NULL);
                if (status == SUCCESS)
                    naInterruptEnable(IOP1_INTERRUPT);
                break;
    
            default:
                ret = NASTATUS_IOP_BAD_PROCESSOR;
                goto startDone;
        }

        if (status != SUCCESS)
        {
            ret = NASTATUS_IOP_SYSTEM_ERROR;
            goto startDone;
        }

        iopIsrCb[indx].enabled = 1;
    }

    
    narm_write_reg (NA_IOP_REG_GEN_CONTROL, NA_IOP_REG_GEN_CONTROL_REG_getAddress(indx), progmem, NA_IOP_REG_GEN_CONTROL_PROGMEM_START_PIC);

    startDone:
    IOP_PUT_SEM(processor);
    return ret;
}

/*ad*
 * This function stops the selected FIM and places it in reset. The FIM will
 * resume execution at program address zero when it is restarted.
 *
 * @param processor     set to indicate which processor
 *
 * @return NASTATUS_SUCCESS             the function call succeeded
 * @return NASTATUS_IOP_BAD_PROCESSOR   the processor argument is out of range
 *
 * @category BSP:Device_Drivers:IOP_DEVICE_API
 * @since 7.3
 *
 * @see @link BSP:Device_Drivers:IOP_DEVICE_API:NaIopProcessor_t
 * @see @link BSP:Device_Drivers:IOP_DEVICE_API:naIopProcessorStartAtZero
 * @see @link BSP:Device_Drivers:IOP_DEVICE_API:naIopProcessorDownloadPicProgram
 *ad*/
NaStatus naIopProcessorStopAndReset(NaIopProcessor_t processor)
{
    NaStatus ret = NASTATUS_SUCCESS;
    int status = SUCCESS, indx = iopGetPicIndex(processor);

    if (indx < 0)
        return NASTATUS_IOP_BAD_PROCESSOR;

    IOP_GET_SEM(processor);    

    /* isr already uninstalled? */
    if (iopIsrCb[indx].enabled)
    {
        switch (processor)
        {
            case NA_IOP_PROCESSOR_PIC0:
                naInterruptDisable(IOP0_INTERRUPT);
                status = naIsrUninstall(IOP0_INTERRUPT);
                break;

            case NA_IOP_PROCESSOR_PIC1:
                naInterruptDisable(IOP1_INTERRUPT);
                status = naIsrUninstall(IOP1_INTERRUPT);
                break;
    
            default:
                ret = NASTATUS_IOP_BAD_PROCESSOR;
                goto stopDone;
        }

        if (status != SUCCESS)
        {
            ret = NASTATUS_IOP_SYSTEM_ERROR;
            goto stopDone;
        }

        iopIsrCb[indx].enabled = 0;
    }

    narm_write_reg (NA_IOP_REG_GEN_CONTROL, NA_IOP_REG_GEN_CONTROL_REG_getAddress(indx), progmem, NA_IOP_REG_GEN_CONTROL_PROGMEM_STOP_PIC);

    stopDone:
    IOP_PUT_SEM(processor);
    return ret;
}

/*ad*
 * This module does not directly use NET+OS system calls in order to make it
 * easier to port it to other operating systems.  The module is not thread 
 * safe by default.  Call this function to register semaphore functions if 
 * you need to make access to a processor thread safe.  The get and set 
 * semaphore functions will be called whenever an operation is performed 
 * with the indicated processor.
 *
 * The getSemaphore function should wait forever for the semaphore. Both 
 * functions should call __panic if there is an error accessing the semaphore.
 * The value of the context argument will be passed to both functions.  
 * Different processors can have different functions and/or different context 
 * values
 *
 * @param processor         set to indicate which processor
 * @param getSemaphore      pointer to function to be used to get the semaphore  
 * @param releaseSemaphore  pointer to function to be used to release the semaphore  
 * @param context           argument passed to the semaphore functions
 *
 * @return NASTATUS_SUCCESS             the function call succeeded
 * @return NASTATUS_IOP_BAD_PROCESSOR   the processor argument is out of range
 * @return NASTATUS_IOP_BAD_ARGUMENT    either getSemaphore or releaseSemaphore is invalid
 *
 * @category BSP:Device_Drivers:IOP_DEVICE_API
 * @since 7.3
 *
 * @see @link BSP:Device_Drivers:IOP_DEVICE_API:NaIopProcessor_t
 * @see @link BSP:Device_Drivers:IOP_DEVICE_API:NaIopSemaphoreFn_t
 * @see @link BSP:Device_Drivers:IOP_DEVICE_API:naIopGetSemaphore
 * @see @link BSP:Device_Drivers:IOP_DEVICE_API:naIopPutSemaphore
 *ad*/
NaStatus naIopRegisterSemaphore(NaIopProcessor_t processor, NaIopSemaphoreFn_t getSemaphore, NaIopSemaphoreFn_t releaseSemaphore, void * context)
{
    NaStatus ret = NASTATUS_SUCCESS;
    int indx;

    indx = iopGetPicIndex(processor);
    if (indx < 0)
    {
        ret = NASTATUS_IOP_BAD_PROCESSOR;
        goto isrRegSemDone;
    }

    if ((getSemaphore == NULL) || (releaseSemaphore == NULL))
    {
        ret = NASTATUS_IOP_BAD_ARGUMENT;
        goto isrRegSemDone;
    }

    /* override if it is already registered */
    iopSemCb[indx].getSem  = getSemaphore;
    iopSemCb[indx].putSem  = releaseSemaphore;
    iopSemCb[indx].context = context;

    isrRegSemDone:
    return ret;
}

/*ad*
 * Use this function to uninstall an ISR callback. The interrupt from the FIM
 * will be disabled and the callback routine will be uninstalled.
 *
 * @param processor     set to indicate which processor
 *
 * @return NASTATUS_SUCCESS             the function call succeeded
 * @return NASTATUS_IOP_BAD_PROCESSOR   the processor argument is out of range
 * @return NASTATUS_IOP_NO_CALLBACK	no callback was installed
 *
 * @category BSP:Device_Drivers:IOP_DEVICE_API
 * @since 7.3
 *
 * @see @link BSP:Device_Drivers:IOP_DEVICE_API:NaIopProcessor_t
 * @see @link BSP:Device_Drivers:IOP_DEVICE_API:NaIopIsrCallback_t
 * @see @link BSP:Device_Drivers:IOP_DEVICE_API:naIopRegisterIsrCallback
 * @see @link BSP:Device_Drivers:IOP_DEVICE_API:naIopInterruptProcessor
 *ad*/
NaStatus naIopUninstallIsr(NaIopProcessor_t processor)
{
    NaStatus ret = NASTATUS_SUCCESS;

    int indx;

    indx = iopGetPicIndex(processor);
    if (indx < 0)
    {
        return NASTATUS_IOP_BAD_PROCESSOR;
    }

    IOP_GET_SEM(processor);

    if (iopIsrCb[indx].cb_f == NULL)
    {
        ret = NASTATUS_IOP_NO_CALLBACK;
        goto isrUninstallDone;
    }
    
    iopIsrCb[indx].cb_f    = NULL;
    iopIsrCb[indx].context = NULL;

    isrUninstallDone:
    IOP_PUT_SEM(processor);
    return ret;
}




