/*
 *  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
 *
 */

#include <string.h>
#include <bum_reg.h>
#include <gpiomux_def.h>
#include "npttypes.h"
#include "nammu.h"
#include "nainvalidate.h"
#include "bsp_drivers.h"

#include "namacro.h"
#include "reg_def.h"
#include "NA_ISR.h"
#include "tx_api.h"
#include "netoserr.h"
#include "nawait.h"
#include "spi_master.h"
#include "nastatus.h"

#ifdef DOTNETMF
#include "spi_master_ex.h" 
#endif

#define LOCAL_SPI_PORT_MASTER  BSP_SPI_MASTER


/*
 * Cache related for ns9210 and ns9215. these are here so that the spi boot loader will not try to link to 
 * MMU routines.
 */
static void *DoesNothing1 (const void *);
static void DoesNothing2 (na_dma_operation_t, const void *, const void *, unsigned long);
static int DoesNothing3 (const void *virtual_address, physical_address_t * physical_address);
void *(*spiNAVaToUncachedVa) (const void *) = DoesNothing1;
void (*spiNABeforeDMA) (na_dma_operation_t, const void *, const void *, unsigned long) = DoesNothing2;
int (*spiNAVaToPhys) (const void *virtual_address, physical_address_t * physical_address) = DoesNothing3;
void (*spiNAAfterDMA) (na_dma_operation_t, const void *, const void *, unsigned long) = DoesNothing2;
static void *DoesNothing1 (const void *p1)
{
    return (void *) p1;
}

static void DoesNothing2 (na_dma_operation_t p1, const void *p2, const void *p3, unsigned long p4)
{
    NA_UNUSED_PARAM(p1);
    NA_UNUSED_PARAM(p2);
    NA_UNUSED_PARAM(p3);
    NA_UNUSED_PARAM(p4);
    return;
}
static int DoesNothing3 (const void *virtual_address, physical_address_t * physical_address)
{
    *physical_address = (physical_address_t) virtual_address;
    return TRUE;
}

/* #define _DEBUG_PRINTF */     /* enable printf */


#define SPI_DMA_RX_NCIP_MASK    0x80000000
#define SPI_DMA_RX_ECIP_MASK    0x40000000
#define SPI_DMA_RX_NRIP_MASK    0x20000000
#define SPI_DMA_RX_CAIP_MASK    0x10000000

#define SPI_DMA_TX_NCIP_MASK    0x01000000
#define SPI_DMA_TX_ECIP_MASK    0x00800000
#define SPI_DMA_TX_NRIP_MASK    0x00400000
#define SPI_DMA_TX_CAIP_MASK    0x00200000

#define SPI_DMA_RX_RXPBUSY_MASK (0x1 << 15)
#define SPI_DMA_TX_TXPBUSY_MASK (0x1 << 12)


#define SPI_MIN_DIVISOR_VALUE           9   /* SPI minimum divisor value for clock genertaion */
#define SPI_RECV_BUFFER_ALIGN           32   /* 32-byte align (cache-line align) */

#define SPI_DISABLED                    0
#define SPI_ENABLED                     1

#define SPI_DMA_CPU_CONTROLLED          0
#define SPI_DMA_FLEXIBLE_IO_CONTROLLED  1
#define SPI_DMA_MODE                    0
#define SPI_DIRECT_MODE                 1
#define SPI_ONE_RXBYTE                  1
#define SPI_FOUR_RXBYTE                 0

#define NARM_IO_MOD_DMA_CONTROL_ce      31, 31
#define NARM_IO_MOD_DMA_CONTROL_ca      30, 30
#define NARM_IO_MOD_DMA_CONTROL_flexio  29, 29
#define NARM_IO_MOD_DMA_CONTROL_direct  28, 28

#define NARM_SPI_MOD_all                    31, 0
#define NARM_IO_MOD_DMA_CONFIG_IRQ_BITS     0x01E00000
//#define NARM_IO_MOD_DMA_CONFIG_IRQ_BITS     0x01A00000

#define NARM_SPI_IRQ_STATUS_REG     (unsigned long volatile *)0x90030000

#define NARM_SPI_DMA_RX_CONTROL_REG (unsigned long volatile *)0x90030004
#define NARM_SPI_DMA_RX_DESC_REG    (unsigned long volatile *)0x90030008
#define NARM_SPI_RX_CONFIG_REG      (unsigned long volatile *)0x9003000C

#define NARM_SPI_DMA_TX_CONTROL_REG (unsigned long volatile *)0x90030018
#define NARM_SPI_DMA_TX_DESC_REG    (unsigned long volatile *)0x9003001C
#define NARM_SPI_TX_CONFIG_REG      (unsigned long volatile *)0x90030020

#define NARM_SPI_RX_STATUS_FIFO_REG (unsigned long volatile *)0x90030010
#define NARM_SPI_RX_FIFO_REG        (unsigned long volatile *)0x90030014

#define NARM_SPI_CONFIG_REG         (unsigned long volatile *)0x90031000
#define NARM_SPI_RESET_REG          (unsigned long volatile *)0xA0900180

#define NARM_SPI_ENABLE_REG         (unsigned long volatile *)0x90031020
#define NARM_SPI_STATUS_REG         (unsigned long volatile *)0x90031024

#define NARM_SPI_CONFIG_mode                5, 4
#define NARM_SPI_CONFIG_bitOrder            2, 2
#define NARM_SPI_CONFIG_slave               1, 1
#define NARM_SPI_CONFIG_master              0, 0
#define NARM_SPI_CONFIG_rxbyte              3, 3
#define NARM_SPI_CONFIG_discard             11, 8
#define NARM_SPI_CONFIG_mlb                 12, 12

#define NARM_SPI_CLK_GEN_REG        (unsigned long volatile *)0x90031010
#define NARM_SPI_CLK_GEN_enable             16, 16
#define NARM_SPI_CLK_GEN_divisor             9, 0


#define SPI_RESET   (0x1 << 5)


/* 
 * device table for the whole system 
 */
static NASpiDeviceConfigType spiDevices[NASPI_MAX_DEVICE];

#if (LOCAL_SPI_PORT_MASTER == BSP_SPI_MASTER)
static NaSpiMasterType spiMaster;
#endif

NaSpiMasterType         *gSpiMaster = NULL;

static TX_MUTEX     gSpiMasterMutex;

#define SPI_MASTER_CREATE_LOCK      tx_mutex_create(&gSpiMasterMutex, "SPI Master Mutex", TX_INHERIT)
#define SPI_MASTER_DELETE_LOCK      tx_mutex_delete(&gSpiMasterMutex)
#define SPI_MASTER_LOCK             tx_mutex_get(&gSpiMasterMutex, TX_WAIT_FOREVER)
#define SPI_MASTER_UNLOCK           tx_mutex_put(&gSpiMasterMutex)

#if (LOCAL_SPI_PORT_MASTER == BSP_SPI_MASTER)

#ifdef NETOS_GNU_TOOLS
/* aligned at cache-line alignment */
    static NA_DMA_DESCRIPTOR_TYPE txc_buffer_descriptors[NASPI_MAX_DESCRIPTORS] __attribute__ ((aligned(32)));
    static NA_DMA_DESCRIPTOR_TYPE rxc_buffer_descriptors[NASPI_MAX_DESCRIPTORS] __attribute__ ((aligned(32)));
#else
#pragma alignvar(32)
    static NA_DMA_DESCRIPTOR_TYPE txc_buffer_descriptors[NASPI_MAX_DESCRIPTORS];
#pragma alignvar(32)
    static NA_DMA_DESCRIPTOR_TYPE rxc_buffer_descriptors[NASPI_MAX_DESCRIPTORS];
#endif

#endif /* (LOCAL_SPI_PORT_MASTER == BSP_SPI_MASTER) */

unsigned long readV = 0;
/*
 * This routine configures SPI master to use DMA.
 * It sets up SPI and DMA registers.
 */
static void spi_hardware_setup(NASpiDeviceConfigType * devicePtr)
{
    int divisor;

//    narm_write_reg (NARM_SPI_CONFIG,  NARM_SPI_CONFIG_REG, mlb, SPI_ENABLED); 
    narm_write_reg (NARM_SPI_CONFIG,  NARM_SPI_CONFIG_REG, mode, devicePtr->spiMode); 
    narm_write_reg (NARM_SPI_CONFIG,  NARM_SPI_CONFIG_REG, bitOrder, devicePtr->bitOrder); 
    narm_write_reg (NARM_SPI_CONFIG,  NARM_SPI_CONFIG_REG, rxbyte, SPI_FOUR_RXBYTE); 
    narm_write_reg (NARM_SPI_CONFIG,  NARM_SPI_CONFIG_REG, slave, SPI_DISABLED); 
    narm_write_reg (NARM_SPI_CONFIG,  NARM_SPI_CONFIG_REG, master, SPI_ENABLED); 
   
    /* Setup SPI Clock Generation */
    divisor = NAgetPllOutputClkFreq()/devicePtr->baudRate;

    if (divisor < SPI_MIN_DIVISOR_VALUE)
        divisor = SPI_MIN_DIVISOR_VALUE;

    /* set up divisor */
    narm_write_reg (NARM_SPI_CLK_GEN,  NARM_SPI_CLK_GEN_REG, enable, SPI_DISABLED); 
    narm_write_reg (NARM_SPI_CLK_GEN,  NARM_SPI_CLK_GEN_REG, divisor, divisor); 
    narm_write_reg (NARM_SPI_CLK_GEN,  NARM_SPI_CLK_GEN_REG, enable, SPI_ENABLED); 
}

/*
 * This routine sets up DMA buffer descriptors.
 */
static void setup_dma(NASpiDeviceConfigType * devicePtr, char *const writeBuffer, char *readBuffer, int bufferLength)
{

    physical_address_t physTx, physRx;

    int byteLeft = bufferLength;
    int i;
    NA_DMA_DESCRIPTOR_TYPE *txDescPtr;
    NA_DMA_DESCRIPTOR_TYPE *rxDescPtr;

    NA_UNUSED_PARAM(devicePtr);

    /*
     * setup all buffer descriptors
     */

    /* Setup Tx buffer */
    if (!spiNAVaToPhys ((const void *) writeBuffer, &physTx))
        netosFatalError ("spi tx DMA Cache error", 1, 1);

    spiNABeforeDMA (NA_MEMORY_TO_DEVICE_DMA, writeBuffer, NULL, bufferLength);

    /* Setup Rx buffer */
    if (!spiNAVaToPhys ((const void *) readBuffer, &physRx))
        netosFatalError ("spi rx DMA Cache error", 1, 1);


    spiNABeforeDMA (NA_DEVICE_TO_MEMORY_DMA, NULL, readBuffer, bufferLength);

    for (i = 0; i < NASPI_MAX_DESCRIPTORS; i++)
    {
        txDescPtr = &gSpiMaster->tx_buffer_descriptors[i];
        rxDescPtr = &gSpiMaster->rx_buffer_descriptors[i];
        /*Setup the tx buffer descriptor, and use the physical address in the DMA buffer descriptor */
        txDescPtr->src_addr = (unsigned long)(physTx + i * NASPI_MAX_DMABUFFER_LENGTH);
        rxDescPtr->src_addr = (unsigned long)(physRx + i * NASPI_MAX_DMABUFFER_LENGTH);
        txDescPtr->dst_addr = 0;
        rxDescPtr->dst_addr = 0;

        if (byteLeft <= NASPI_MAX_DMABUFFER_LENGTH)
        {
            txDescPtr->buf_len = byteLeft;
            rxDescPtr->buf_len = byteLeft;
            rxDescPtr->flag = DMA_WRAP_MASK | DMA_INTERRUPT_MASK | DMA_LAST_MASK;
            txDescPtr->flag = DMA_WRAP_MASK | DMA_INTERRUPT_MASK | DMA_LAST_MASK | DMA_FULL_MASK;
            break;
        }
        else
        {
            txDescPtr->buf_len = NASPI_MAX_DMABUFFER_LENGTH;
            rxDescPtr->buf_len = NASPI_MAX_DMABUFFER_LENGTH;
            txDescPtr->flag = DMA_FULL_MASK;       
            rxDescPtr->flag = 0;  
        }

        byteLeft -= NASPI_MAX_DMABUFFER_LENGTH;
    }

    gSpiMaster->length = bufferLength;
    gSpiMaster->buffer_descriptor_count = i+1;

}

/*
 * This routine clears DMA buffer descriptors. If DMA controller is not done with the descriptor,
 * it waits (sleep) until it's done.
 */
static void done_dma(NASpiDeviceConfigType * devicePtr, char *const writeBuffer, char *readBuffer, int bufferLength)
{
    NA_UNUSED_PARAM(devicePtr);

    spiNAAfterDMA (NA_MEMORY_TO_DEVICE_DMA, writeBuffer, NULL, bufferLength);
    spiNAAfterDMA (NA_DEVICE_TO_MEMORY_DMA, NULL, readBuffer, bufferLength);

}


/*
 * This routine enables SPI TX/RX DMA
 */
static void enable_spi(NASpiDeviceConfigType * devicePtr)
{

    NA_UNUSED_PARAM(devicePtr);

    spiMasterEnableIsr(SPI_INTERRUPT);

    /* enable SPI DMA by toggle the CE bit */
    narm_write_reg (NARM_IO_MOD_DMA_CONTROL, NARM_SPI_DMA_RX_CONTROL_REG, ce, SPI_DISABLED);
    narm_write_reg (NARM_IO_MOD_DMA_CONTROL, NARM_SPI_DMA_RX_CONTROL_REG, ce, SPI_ENABLED);      
      
    narm_write_reg (NARM_IO_MOD_DMA_CONTROL, NARM_SPI_DMA_TX_CONTROL_REG, ce, SPI_DISABLED);
    narm_write_reg (NARM_IO_MOD_DMA_CONTROL, NARM_SPI_DMA_TX_CONTROL_REG, ce, SPI_ENABLED);      
   
}


/*
 * This routine disables SPI TX/RX DMA
 */
static void disable_spi(NASpiDeviceConfigType *devicePtr)
{

    NA_UNUSED_PARAM(devicePtr);

    spiMasterDisableIsr(SPI_INTERRUPT);

    /* disable SPI DMA */
    narm_write_reg (NARM_IO_MOD_DMA_CONTROL, NARM_SPI_DMA_RX_CONTROL_REG, ce, SPI_DISABLED);      
    narm_write_reg (NARM_IO_MOD_DMA_CONTROL, NARM_SPI_DMA_TX_CONTROL_REG, ce, SPI_DISABLED);      

}

/* 
 * This routine setup GPIO pin to use SPI.
 */
static void enable_cs (NASpiDeviceConfigType * devicePtr)
{
    if (devicePtr->csAssertFn)
    {
        devicePtr->csAssertFn ();
    }
}

/* 
 * 
 */
static void disable_cs (NASpiDeviceConfigType * devicePtr)
{

    if (devicePtr->csDessertFn)
    {
        devicePtr->csDessertFn ();
    }
}

unsigned int gTxFlag = 0;
unsigned int gRxFlag = 0;

/*
 * SPI Interrupt Service Routine.
 */
#if (LOCAL_SPI_PORT_MASTER == BSP_SPI_MASTER)
static void spi_master_isr(int isr)
{
    unsigned long status;

    if (isr != SPI_INTERRUPT)
    {
        return;
    }

    status = *NARM_SPI_IRQ_STATUS_REG;

    /* Acknowledge interrupt */
    *NARM_SPI_IRQ_STATUS_REG = status; 

    /* Normal RX completion */
    if ((status & SPI_DMA_RX_NCIP_MASK) || (status & SPI_DMA_RX_NRIP_MASK) )
    {
       
        if (status & SPI_DMA_RX_NCIP_MASK)
        {
        }
        if (status & SPI_DMA_RX_NRIP_MASK)
        {   
            gRxFlag++;
        }   
    }

    /* Normal TX completion */
    if ((status & SPI_DMA_TX_NCIP_MASK) || (status & SPI_DMA_TX_NRIP_MASK) )
    {
        
        if (status & SPI_DMA_TX_NCIP_MASK)
        {
        }
        if (status & SPI_DMA_TX_NRIP_MASK)
        {   
            gTxFlag++;
        }
    }

#if 0
    /* The following is error bit mask */
    if ((status & SPI_DMA_RX_ECIP_MASK) || (status & SPI_DMA_RX_CAIP_MASK))
    {   
        netosFatalError ("spi rx DMA error", 1, 1);
    }

    if ((status & SPI_DMA_TX_ECIP_MASK) || (status & SPI_DMA_TX_CAIP_MASK))
    {   /* set TX ECIP error event */
        netosFatalError ("spi rx DMA error", 1, 1);
    }
#endif

}
#endif /* (LOCAL_SPI_PORT_MASTER == BSP_SPI_MASTER) */


/*
 * This is the initialization function called from netosStartup() in BSP
 */
NaStatus initializeSpiMaster (void)
{
#if (LOCAL_SPI_PORT_MASTER == BSP_SPI_MASTER)
    physical_address_t physTx, physRx;
    NaStatus ret = NASTATUS_SUCCESS;
    int ccode;
    
    if (gSpiMaster != NULL)
    {   /* already initialized */
        goto _ret;
    }        

    ccode = SPI_MASTER_CREATE_LOCK;
    if (ccode != TX_SUCCESS)
    {
        ret = NASTATUS_RESOURCE_ERROR;
#ifdef _DEBUG_PRINTF
        printf("initializeSpiMaster: create_lock fails %d\n", ccode);
#endif
        goto _ret;
    }

    SPI_MASTER_LOCK;

    if (gSpiMaster == NULL)
    {
        gSpiMaster = &spiMaster;

        if (gSpiMaster == NULL)
        {
#ifdef _DEBUG_PRINTF
            printf("initializeSpiMaster: malloc fails\n");
#endif
            ret = NASTATUS_RESOURCE_ERROR;
            SPI_MASTER_UNLOCK;
            goto _ret;
        }
        memset(gSpiMaster, 0, sizeof(NaSpiMasterType));

        gSpiMaster->rx_buffer_descriptors = spiNAVaToUncachedVa(rxc_buffer_descriptors);
        gSpiMaster->tx_buffer_descriptors = spiNAVaToUncachedVa(txc_buffer_descriptors);

        memset(gSpiMaster->rx_buffer_descriptors, 0, (sizeof(NA_DMA_DESCRIPTOR_TYPE) * NASPI_MAX_DESCRIPTORS));
        memset(gSpiMaster->tx_buffer_descriptors, 0, (sizeof(NA_DMA_DESCRIPTOR_TYPE) * NASPI_MAX_DESCRIPTORS));

        gSpiMaster->active_device_index = -1;
        gSpiMaster->buffer_descriptor_count = 0;
        gSpiMaster->length = 0;


        narm_write_reg(NA_SCM_CCR_REG, NA_SCM_CCR_ADDRESS, spi, SCM_CCR_ENABLE_CLOCK);
        narm_write_reg(NA_SCM_RSR_REG, NA_SCM_RSR_ADDRESS, spi, SCM_RSR_RESET_MODULE);
        NAuWait(100);
        narm_write_reg(NA_SCM_RSR_REG, NA_SCM_RSR_ADDRESS, spi, SCM_RSR_ENABLE_MODULE);

        spiMasterInstallIsr (SPI_INTERRUPT, (void *)spi_master_isr, (void *)SPI_INTERRUPT);

        spiMasterDisableIsr (SPI_INTERRUPT);

        narm_write_reg (NARM_SPI_CONFIG,  NARM_SPI_CONFIG_REG, rxbyte, SPI_FOUR_RXBYTE); 
        narm_write_reg (NARM_SPI_CONFIG,  NARM_SPI_CONFIG_REG, slave, SPI_DISABLED); 
        narm_write_reg (NARM_SPI_CONFIG,  NARM_SPI_CONFIG_REG, master, SPI_ENABLED); 

        /* setup SPI DMA for Rx */
        narm_write_reg (NARM_SPI_MOD, NARM_SPI_DMA_RX_CONTROL_REG, all, 0);
        narm_write_reg (NARM_IO_MOD_DMA_CONTROL, NARM_SPI_DMA_RX_CONTROL_REG, ce, SPI_DISABLED);      
        narm_write_reg (NARM_IO_MOD_DMA_CONTROL, NARM_SPI_DMA_RX_CONTROL_REG, flexio, SPI_DMA_CPU_CONTROLLED);       
        narm_write_reg (NARM_IO_MOD_DMA_CONTROL, NARM_SPI_DMA_RX_CONTROL_REG, direct, SPI_DMA_MODE); 
        /* enable irq bits for Rx */
        narm_write_reg (NARM_SPI_MOD,  NARM_SPI_RX_CONFIG_REG, all, NARM_IO_MOD_DMA_CONFIG_IRQ_BITS); 

        /* setup SPI DMA for Tx */
        narm_write_reg (NARM_SPI_MOD, NARM_SPI_DMA_TX_CONTROL_REG, all, 0);
        narm_write_reg (NARM_IO_MOD_DMA_CONTROL, NARM_SPI_DMA_TX_CONTROL_REG, ce, SPI_DISABLED);      
        narm_write_reg (NARM_IO_MOD_DMA_CONTROL, NARM_SPI_DMA_TX_CONTROL_REG, flexio, SPI_DMA_CPU_CONTROLLED);       
        narm_write_reg (NARM_IO_MOD_DMA_CONTROL, NARM_SPI_DMA_TX_CONTROL_REG, direct, SPI_DMA_MODE); 
        /* enable irq bits for Tx */
        narm_write_reg (NARM_SPI_MOD,  NARM_SPI_TX_CONFIG_REG, all, NARM_IO_MOD_DMA_CONFIG_IRQ_BITS); 

        /* setup SPI Configuration */
        narm_write_reg (NARM_SPI_MOD,  NARM_SPI_CONFIG_REG, all, 0); 

        /*
         * setup DMA buffer descriptor pointer
         */
        if (!spiNAVaToPhys ((const void *) gSpiMaster->tx_buffer_descriptors, &physTx))
            netosFatalError ("spi tx DMA Cache error", 1, 1);

    //    narm_write_reg (NARM_SPI_MOD, NARM_SPI_DMA_TX_DESC_REG, all, physTx); 
        *NARM_SPI_DMA_TX_DESC_REG = physTx; 

        /*Convert to physical address */
        if (!spiNAVaToPhys ((const void *) gSpiMaster->rx_buffer_descriptors, &physRx))
            netosFatalError ("spi rx DMA Cache error", 1, 1);

    //    narm_write_reg (NARM_SPI_MOD, NARM_SPI_DMA_RX_DESC_REG, all, physRx); 
        *NARM_SPI_DMA_RX_DESC_REG = physRx; 
    }

    SPI_MASTER_UNLOCK;

_ret:
    return ret;
#else
    gSpiMaster = NULL;

    return NASTATUS_SUCCESS;
#endif /* (LOCAL_SPI_PORT_MASTER == BSP_SPI_MASTER) */

}


/*ad*
 * Registers a device to SPI Master module.
 * 
 *
 * @category BSP:Device_Drivers:SPI:SPIMasterAPI
 * @since 7.3
 * @external
 *
 * @param spiDevice 			Device settings
 *
 * @return NASTATUS_SPI_MASTER_DUPLICATE_DEVICE		The device can't be registered with this name because
 *													a device with the same name already exits.
 * @return NASTATUS_RESOURCE_ERROR			        The device table is full.
 * @return NASTATUS_SPI_MASTER_INVALID_SETTINGS		The device settings are invalid.
 * @return NASTATUS_NOT_INITIALIZED				    SPI is not initialized yet.BSP must be configure SPI module for SPI master.
 * @return NASTATUS_SUCCESS				            The operation was successful.
 * 
 *
 * @see @link NASpiDeviceConfigType
 * @see @link NaStatusValues
 * @see @link LOCAL_SPI_PORT_MASTER
*ad*/
NaStatus NASpiRegisterDevice(NASpiDeviceConfigType * spiDevice)
{
    int i;
    int maxSpeed, minSpeed;
    NaStatus ret = NASTATUS_SPI_MASTER_INVALID_SETTINGS;
    NASpiDeviceConfigType   *devicePtr;

    SPI_MASTER_LOCK;

    if (spiDevice == NULL)
    {   /* invalid device */
        goto _ret;
    }

    maxSpeed = NAgetPllOutputClkFreq()/9; 
    minSpeed = NAgetPllOutputClkFreq()/0x3FF; 

    /* max speed */
    if (spiDevice->baudRate < minSpeed || spiDevice->baudRate > maxSpeed) 
    {
#ifdef _DEBUG_PRINTF
        printf("NASpiRegisterDevice: clk freq %d max speed %d min speed %d\n", NAgetSysOscFreq(), maxSpeed, minSpeed);
#endif
        goto _ret;
    }

    if (spiDevice->name == NULL || strlen (spiDevice->name) == 0 || strlen (spiDevice->name) > NASPI_MAX_NAME_LENGTH)
    {
#ifdef _DEBUG_PRINTF
        printf("NASpiRegisterDevice: invalid name\n");
#endif
        goto _ret;
    }

    if (gSpiMaster == NULL)
    {   /* SPI not initialized yet */
    	ret = initializeSpiMaster();
//        ret = NASTATUS_NOT_INITIALIZED;
        if (ret != NASTATUS_SUCCESS)
        {
        goto _ret;
        }
    }

    if (gSpiMaster->device_count >= NASPI_MAX_DEVICE)
    {   /* reach the max devices */
        ret = NASTATUS_RESOURCE_ERROR;
        goto _ret;
    }

    /* See whether there is duplicate device */
    for (i=0; i < NASPI_MAX_DEVICE; i++)
    {
        devicePtr = gSpiMaster->devices[i];

        if (devicePtr != NULL && !strcmp (spiDevice->name, devicePtr->name))
        {
            ret = NASTATUS_SPI_MASTER_DUPLICATE_DEVICE;
            goto _ret;
        }
    }

    /* Add the device */
    for (i=0; i < NASPI_MAX_DEVICE; i++)
    {
        devicePtr = gSpiMaster->devices[i];
        if (devicePtr == NULL)
        {
            devicePtr = &spiDevices[i];       
            gSpiMaster->devices[i] = devicePtr;
            break;
        }
    }

    memcpy (devicePtr, spiDevice, sizeof(NASpiDeviceConfigType));

    gSpiMaster->device_count++; 

    spi_hardware_setup(devicePtr);

    ret = NASTATUS_SUCCESS;

_ret:

    SPI_MASTER_UNLOCK;
    return ret;

}

/*ad*
 * Remove a device from the SPI master module. 
 * This function remove a device from the SPI master module given NASpiRegisterDevice.
 * 
 *
 * @category BSP:Device_Drivers:SPI:SPIMasterAPI
 * @since 6.2
 * @external
 *
 * @param name 			The name of the device
 *
 * @return NASTATUS_NOT_INITIALIZED				SPI is not initialized yet.
 * @return NASTATUS_SPI_MASTER_DEVICE_NOT_FOUND		The device is not registed and can't be used.
 * @return NASTATUS_SPI_MASTER_DEVICE_ACTIVE				The device is in use and can't be deregistered.
 * @return NASTATUS_SUCCESS				The operation was successful.
 * 
 *
 * @see @link NASpiDeviceConfigType
 * @see @link NaStatusValues
*ad*/
NaStatus NASpiDeregisterDevice(char * name)
{
    int i;
    NaStatus ret = NASTATUS_SPI_MASTER_DEVICE_NOT_FOUND;
    NASpiDeviceConfigType   *devicePtr;

    TX_INTERRUPT_SAVE_AREA;

    SPI_MASTER_LOCK;

    if (name == NULL || strlen (name) > NASPI_MAX_NAME_LENGTH)
        goto _ret;

    if (gSpiMaster == NULL)
    {
        ret = NASTATUS_NOT_INITIALIZED;
        goto _ret;
    }
    /*
     * does it exist?
     */
    TX_DISABLE;
    for (i=0; i < NASPI_MAX_DEVICE; i++)
    {
        devicePtr = gSpiMaster->devices[i];

        if (devicePtr != NULL && !strcmp (name, devicePtr->name))
        {
            /* if not actively running, then let's remove it */
            if (gSpiMaster->active_device_index != i)
            {
                gSpiMaster->devices[i] = NULL;
                gSpiMaster->device_count--;

                ret = NASTATUS_SUCCESS;
            }
            else
            {
                /* We cannot remove it while it's running */
                ret = NASTATUS_SPI_MASTER_DEVICE_ACTIVE;
            }
            break;
        }
    }
    TX_RESTORE;

_ret:
    SPI_MASTER_UNLOCK;
    return ret;
}


static NaStatus spi_read_write(NASpiDeviceConfigType * devicePtr, char * const writeBuffer, char * readBuffer, int bufferLength)
{
    NaStatus	ret;
#ifdef _DEBUG_PRINTF
    int tx_descriptor_count;
    int rx_descriptor_count;
#endif
//    int timeout = 200000;    /* 20 seconds */
    int timeout = 100000000;    /*100 seconds */

    gTxFlag = 0;
    gRxFlag = 0;

    disable_cs(devicePtr);

    spi_hardware_setup(devicePtr);

    setup_dma(devicePtr, writeBuffer, readBuffer, bufferLength);
    
    /* enable irq bits for Rx */
    enable_cs(devicePtr);

#ifdef DOTNETMF
	if (((NASpiDeviceConfigTypeEx *) devicePtr)->selectTime)
		NAuWait (((NASpiDeviceConfigTypeEx *) devicePtr)->selectTime);
#endif
    enable_spi(devicePtr);

    /* wait for spi done */
    while (timeout && (!gTxFlag || !gRxFlag))
    {
        /* Should this be a tx_thread_sleep(1) or something in order to 
         * allow lower priority threads to run as well? 
         */
     	if (bufferLength < 8000)
 			NAuWait(1);
 		else
			tx_thread_sleep(NS_MILLISECONDS_TO_TICKS(1));

        timeout--;
    }

    if (!timeout)
        ret = NASTATUS_SPI_MASTER_DMA_FAILED;
    else
        ret = NASTATUS_SUCCESS;

#ifdef _DEBUG_PRINTF
    if (ret == NASTATUS_SUCCESS)
    {
		int		i;

        tx_descriptor_count = gSpiMaster->buffer_descriptor_count;
        rx_descriptor_count = gSpiMaster->buffer_descriptor_count;


        /* Make sure DMA controller is done with the buffer descriptor.
         * If not done, wait until its done.
         */
        while (rx_descriptor_count > 0 || tx_descriptor_count > 0)
        {
            for (i = 0; i < gSpiMaster->buffer_descriptor_count; i++)
            {
                if (gSpiMaster->tx_buffer_descriptors[i].src_addr != 0 && 
                    (gSpiMaster->tx_buffer_descriptors[i].flag & DMA_FULL_MASK) != DMA_FULL_MASK)
                {
                    gSpiMaster->tx_buffer_descriptors[i].src_addr = 0;
                    tx_descriptor_count--;
                }

            }
            for (i = 0; i < gSpiMaster->buffer_descriptor_count; i++)
            {
                if (gSpiMaster->rx_buffer_descriptors[i].src_addr != 0 && 
                    (gSpiMaster->rx_buffer_descriptors[i].flag & DMA_FULL_MASK) == DMA_FULL_MASK)
                {
                    gSpiMaster->rx_buffer_descriptors[i].src_addr = 0;
                    rx_descriptor_count--;
                }
            }
            /* wait if not done */
            if (rx_descriptor_count > 0 || tx_descriptor_count > 0)
            {
                tx_thread_sleep(NS_MILLISECONDS_TO_TICKS(100));
            }
        }

    }
#endif
    
    gSpiMaster->buffer_descriptor_count = 0;

    done_dma(devicePtr, writeBuffer, readBuffer, bufferLength);

    if (devicePtr->deselectTime)
    {
        NAuWait (devicePtr->deselectTime);
    }

    disable_spi(devicePtr);

    disable_cs(devicePtr);

	return ret;
}

#ifdef DOTNETMF


NaStatus NASpiReadWriteDevice(NASpiDeviceConfigTypeEx * devicePtr, char * const writeBuffer, char * readBuffer, int bufferLength)
{
	char * allignedBuffer = readBuffer;
	NaStatus ret;


	if (gSpiMaster == NULL)
    {   /* SPI not initialized yet */
		ret = initializeSpiMaster();
        if (ret != NASTATUS_SUCCESS)
		{
            ret = NASTATUS_NOT_INITIALIZED;
			goto _ret;
		}
    }
	if (devicePtr == NULL)
    {   /* non-exist device */
		ret = NASTATUS_SPI_MASTER_DEVICE_NOT_FOUND;
        goto _ret;
    }
	    /* buffer error */
    if (readBuffer == NULL || writeBuffer == NULL || 
		bufferLength > NASPI_MAX_DATA_LENGTH || bufferLength == 0)
    {
        ret = NASTATUS_SPI_MASTER_INVALID_BUFFER;
        goto _ret;
    }

	if ((int) readBuffer % SPI_RECV_BUFFER_ALIGN)
	{
		allignedBuffer = malloc(bufferLength);
		if (allignedBuffer == NULL)
		{
			ret = NASTATUS_SPI_MASTER_INVALID_BUFFER;
			goto _ret;
		}
	}

	/* Call spi_read_write to do actual read-write */
	ret = spi_read_write((NASpiDeviceConfigType *)devicePtr, writeBuffer, allignedBuffer, bufferLength);

	if (allignedBuffer != readBuffer)
	{
		memcpy(readBuffer, allignedBuffer, bufferLength);
		free(allignedBuffer);
	}

_ret:
    return ret;	
}
#endif

/*ad*
 * Starts SPI Master transfer.
 *
 * This function initiates SPI transfer. It starts setting DMA for transmit and receive data and 
 * waits until it's done transmitting and receiving data from DMA.
 * 
 * The readBuffer must be on cache-line boundary (32-byte align), and the readBuffer and writeBuffer must be the same length. 
 * This function blocks until it completes the data transfer. The serial Flash driver, NVRAM driver 
 * and SPI boot loader are built on top of this function.
 *
 * @category BSP:Device_Drivers:SPI:SPIMasterAPI
 * @since 6.2
 * @external
 *
 * @param name 			device name
 * @param writeBuffer	data buffer that holds the data to be transmitted
 * @param readBuffer    data buffer to hold the received data
 * @param bufferLength	the length of the transmitted data
 *
 * @return NASTATUS_NOT_INITIALIZED				    SPI is not initialized yet.
 * @return NASTATUS_SPI_MASTER_DEVICE_NOT_FOUND		The device is not registed and can't be used.
 * @return NASTATUS_SPI_MASTER_INVALID_BUFFER		The read buffer is either not cache-line aligned or is NULL,
 *													or the write buffer is NULL.
 * @return NASTATUS_SPI_MASTER_DMA_FAIL				The DMA transmit or receive failed.
 * @return NASTATUS_SUCCESS				            The operation was successful.
 * 
 *
 * @see @link NASpiDeviceConfigType
 * @see @link NaStatusValues
*ad*/
NaStatus NASpiReadWrite(char * name, char * const writeBuffer, char * readBuffer, int bufferLength)
{
    int                     i;
    NaStatus                ret = NASTATUS_SPI_MASTER_DEVICE_NOT_FOUND;
    NASpiDeviceConfigType   *devicePtr = NULL;


    TX_INTERRUPT_SAVE_AREA 

    if (gSpiMaster == NULL)
    {   /* SPI not initialized yet */
        ret = NASTATUS_NOT_INITIALIZED;
        goto _ret;
    }
    /* invalid device name */
    if (name == NULL || strlen (name) > NASPI_MAX_NAME_LENGTH)
        goto _ret;

    /* buffer error */
    if (readBuffer == NULL || writeBuffer == NULL || bufferLength > NASPI_MAX_DATA_LENGTH ||
        ((int) readBuffer % SPI_RECV_BUFFER_ALIGN)|| bufferLength == 0)
    {
        ret = NASTATUS_SPI_MASTER_INVALID_BUFFER;
        goto _ret;
    }

    SPI_MASTER_LOCK;
    TX_DISABLE
    /* If a device is currently running, must wait until it finishes */
    if (gSpiMaster->active_device_index >= 0)
    {
        TX_RESTORE
        SPI_MASTER_UNLOCK;
        ret = NASTATUS_SPI_MASTER_DEVICE_ACTIVE;
        goto _ret;
    }
    /* Get the device */
    for (i=0; i < NASPI_MAX_DEVICE; i++)
    {
        if (gSpiMaster->devices[i] != NULL && !strcmp (name, gSpiMaster->devices[i]->name))
        {
            devicePtr = gSpiMaster->devices[i];
            gSpiMaster->active_device_index = i;
//            printf("NASpiReadWrite: write 0x%x read 0x%x length %d\n", (unsigned)writeBuffer, (unsigned)readBuffer, bufferLength);
            break;
        }
    }
    TX_RESTORE
    SPI_MASTER_UNLOCK;

    if (devicePtr == NULL)
    {   /* non-exist device */
        goto _ret;
    }

	/* Call spi_read_write to do actual read-write */
	ret = spi_read_write(devicePtr, writeBuffer, readBuffer, bufferLength);

    SPI_MASTER_LOCK;
    gSpiMaster->active_device_index = -1;
    SPI_MASTER_UNLOCK;

_ret:
    return ret;
}

/*
 * This function checks if the named device is in spi master device table, and is 
 * used for serial flash driver only
 */
int deviceExist (char *name)
{
    int i;
    TX_INTERRUPT_SAVE_AREA;
    
    if (gSpiMaster != NULL)
    {
        TX_DISABLE;
        for (i = 0; i < NASPI_MAX_DEVICE; i++)
        {
            if (gSpiMaster->devices[i] != NULL && !strcmp (name, gSpiMaster->devices[i]->name))
            {
                TX_RESTORE;
                return 1;
            }
        }
        TX_RESTORE;
        return 0;
    }
    else
        return 0;

}

