/*************************************************************************
*
*                  Copyright 2012 Mentor Graphics Corporation
*                         All Rights Reserved.
*
* THIS WORK CONTAINS TRADE SECRET AND PROPRIETARY INFORMATION WHICH IS THE
* PROPERTY OF MENTOR GRAPHICS CORPORATION OR ITS LICENSORS AND IS SUBJECT
* TO LICENSE TERMS.
*
**************************************************************************

**************************************************************************
* FILE NAME
*
*       lwspi.c
*
* COMPONENT
*
*       Nucleus lightweight SPI
*
* DESCRIPTION
*
*       This file contains data structures and APIs provided with
*       Nucleus lightweitht SPI.
*
* DATA STRUCTURES
*
*       None
*
* FUNCTIONS
*
*       nu_os_conn_lwspi_init               Entry function of lightweight 
*                                           SPI component.
*       LWSPI_Bus_Register                  SPI device register 
*                                           notification callback 
*                                           function.
*       LWSPI_Bus_Unregister                SPI device un-register 
*                                           notification callback 
*                                           function.
*       NU_SPI_Register_Slave               Registers a slave with SPI 
*                                           bus.
*       NU_SPI_Unregister_Slave             Un-register a slave with
*                                           SPI bus.
*       NU_SPI_Read                         Reads data from a specified 
*                                           SPI slave.
*       NU_SPI_Write                        Writes data to a specified
*                                           SPI slave.
*       NU_SPI_Write_Read                   Perform read after write from 
*                                           a specified SPI slave.
*
* DEPENDENCIES
*
*       nucleus.h
*       nu_kernel.h
*       nu_services.h
*       lwspi.h
*
*************************************************************************/
#include    "nucleus.h"
#include    "kernel/nu_kernel.h"
#include    "services/nu_services.h"
#include    "connectivity/lwspi.h"
#include    "os/connectivity/lwspi/inc/lwspi_defs.h"

/**********************/
/* External Variables */
/**********************/
extern  NU_MEMORY_POOL  System_Memory;

/*******************/
/* Local Variables */
/******************/
NU_SPI_BUS  SPI_Bus_CB[LWSPI_NUM_BUSES];

/*************************************************************************
* FUNCTION
*
*       nu_os_conn_lwspi_init       
*
* DESCRIPTION
*
*       This is the entry function of lightweight SPI component. It 
*       is called durint initialization from specified run level.
*
* INPUTS
*
*       key                                 Path to registry entry of 
*                                           this component.
*       compctrl                            A flag specifying desired 
*                                           operation on component. 
*                                           Its value will be one of 
*                                           these.
*                                            - RUNLEVEL_STOP
*                                            - RUNLEVEL_START
*                                            - RUNLEVEL_HIBERNATE
*                                            - RUNLEVEL_RESUME
*
* OUTPUTS
*
*       NU_SUCCESS                          Service completed 
*                                           successfully.
*
*************************************************************************/
STATUS nu_os_conn_lwspi_init(const CHAR * key, INT compctrl)
{
    STATUS              status;
    DV_DEV_LABEL        device_type_label[] = {{LWSPI_LABEL}}; 
    DV_LISTENER_HANDLE  listener_handle;

    if(compctrl == RUNLEVEL_START)
    {
        /* Reset global bus structure. */
        memset(&SPI_Bus_CB, 0x00, (sizeof(NU_SPI_BUS) * LWSPI_NUM_BUSES));
        
        /* Register a listener for SPI label with device manager. */
        status = DVC_Reg_Change_Notify(device_type_label,
                                    1,
                                    LWSPI_Bus_Register,
                                    LWSPI_Bus_Unregister,
                                    NU_NULL,
                                    &listener_handle);
    }
    else
    {
        /* Always return success. */
        status = NU_SUCCESS;
    }
     
    /* Return the completion status of the service. */
    return (status);
}

/*************************************************************************
* FUNCTION
*
*       LWSPI_Bus_Register
*
* DESCRIPTION
*
*       This is the callback function, called by device manager when a 
*       new SPI device will register with device manager using lightweight 
*       SPI label.
*
* INPUTS
*
*       spi_dev_id                          Device ID of newly registered
*                                           SPI device.
*       context                             An option context.
*
* OUTPUTS
*
*       NU_SPI_NO_FREE_BUS_SLOT             If no free bus slot is 
*                                           found.
*       NU_SPI_INVLD_IO_PTR                 Fetched I/O pointers are 
*                                           invalid.
*       NU_SUCCESS                          Service completed 
*                                           successfully.
*
*************************************************************************/
static STATUS LWSPI_Bus_Register(DV_DEV_ID spi_dev_id, VOID *context)
{
    DV_DEV_LABEL        device_type_label[] = {{LWSPI_LABEL}};
    NU_SPI_BUS          *spi_bus;
    STATUS              status;
    DV_IOCTL0_STRUCT    ioctl0;
    
    /* Get a free NU_SPI_BUS control block. */
    status = LWSPI_Find_Vacant_Bus_Slot(&spi_bus);
    if(status == NU_SUCCESS)
    {
        /* Open the device */
        status =  DVC_Dev_ID_Open (spi_dev_id,
                                   device_type_label, 
                                   1,
                                   &spi_bus->dev_handle);
        if(status == NU_SUCCESS)
        {
            /* Save device ID for later use. */
            spi_bus->dev_id = spi_dev_id;
            
            /* Copy lightweight SPI label for executing IOCTL 0. */
            memcpy(&ioctl0.label, device_type_label, sizeof(DV_DEV_LABEL));
            
            /* Get SPI base IOCTL ID. */
            status = DVC_Dev_Ioctl(spi_bus->dev_handle, DV_IOCTL0, &ioctl0, sizeof(ioctl0));
            if(status == NU_SUCCESS)
            {
                /* Save SPI ioctl base in the SPI bus control block. */
                spi_bus->ioctl_base = ioctl0.base;
                
                /* Initialize bus lock. */
                status = NU_Create_Semaphore(&spi_bus->bus_lock,
                                            "SPI_BL",
                                            1,
                                            NU_PRIORITY_INHERIT);
                if(status == NU_SUCCESS)
                {
                    /* Fetch necessary information about this SPI bus. This include following.
                     *  - Name of SPI device to be used as bus name. 
                     *  - I/O pointers for this SPI bus.
                     *  - Get device context. Value returned in this IOCTL will be passed 
                     *   during I/O operations.
                     */
                    status = DVC_Dev_Ioctl(spi_bus->dev_handle,
                                            (spi_bus->ioctl_base + LWSPI_IOCTL_GET_BUS_INFO),
                                            spi_bus,
                                            0);
#if (CFG_NU_OS_CONN_LWSPI_ERR_CHECK_ENABLE == NU_TRUE)
                    if(status == NU_SUCCESS)
                    {
                        /* Check if retrieved bus information is valid. */
                        if((spi_bus->dev_context == NU_NULL)        ||
                            (spi_bus->io_ptrs.configure == NU_NULL) ||
#if (CFG_NU_OS_CONN_LWSPI_INT_MODE_IO_ENABLE == NU_TRUE)
                            (spi_bus->io_ptrs.isr_read == NU_NULL)  ||
                            (spi_bus->io_ptrs.isr_write == NU_NULL) ||
                            (spi_bus->io_ptrs.isr_write_read == NU_NULL)  ||
#endif
                            (spi_bus->io_ptrs.read == NU_NULL)      ||
                            (spi_bus->io_ptrs.write == NU_NULL)     ||
                            (spi_bus->io_ptrs.write_read == NU_NULL))
                        {
                            /* If retrieved bus information is not correct then 
                             * do clean-up befor exiting.
                             *  - Close device.
                             *  - Detelete bus lock.
                             *  - Free bus immediately .
                             */
                            DVC_Dev_Close(spi_bus->dev_handle);
                            
                            NU_Delete_Semaphore(&spi_bus->bus_lock);
                            
                            spi_bus->is_used = NU_FALSE;
                            
                            /* Return invalid pointers status. */
                            status = NU_SPI_INVLD_IO_PTR;
                        }
                    }
#endif
                }
            }
        }
    }
    
    return (status);
}

/*************************************************************************
* FUNCTION
*
*       LWSPI_Bus_Unregister
*
* DESCRIPTION
*
*       This is the callback function, called by device manager when a 
*       device with lightweight SPI label un-registers itself with device 
*       manager.
*
* INPUTS
*
*       spi_dev_id                          Device ID of un-registered 
*                                           SPI device.
*       context                             An optional context.
*
* OUTPUTS
*
*       NU_SPI_BUS_SLOT_NOT_FOUND           Bus slot with specified ID 
*                                           is not found.
*       NU_SUCCESS                          Service completed 
*                                           successfully.
*
*************************************************************************/
static STATUS LWSPI_Bus_Unregister(DV_DEV_ID spi_dev_id, VOID *context)
{
    UINT8           slave_idx;
    NU_SPI_BUS      *spi_bus;
    NU_SPI_DEVICE   *spi_slave;
    STATUS          status;
    
    /* Search SPI bus identified by 'spi_dev_id'. */
    status = LWSPI_Find_Bus_Slot_By_ID(spi_dev_id, &spi_bus);
    if(status == NU_SUCCESS)
    {
        /* Close SPI device. */
        DVC_Dev_Close(spi_bus->dev_handle);
        
        /* Unregister all slaves. */
        for(slave_idx=0; slave_idx<LWSPI_NUM_SLAVES; slave_idx++)
        {
            spi_slave = spi_bus->slaves[slave_idx];
            if(spi_slave != NU_NULL)
            {
                NU_SPI_Unregister_Slave(spi_slave->handle);
            }
        }
        
        /* Delete bus lock. */
        NU_Delete_Semaphore(&spi_bus->bus_lock);

        /* Mark bus slot free for later use. */
        spi_bus->is_used = NU_FALSE;
    }
    
    return (status);
}

/*************************************************************************
* FUNCTION
*
*       LWSPI_Find_Vacant_Bus_Slot
*
* DESCRIPTION
*
*       This function finds a vacant bus slot. If a vacant slot is 
*       available then its pointer is returned in output argument 
*       spi_bus.
*
* INPUTS
*
*       spi_bus                             An output argument containing 
*                                           pointer to the vacant bus slot 
*                                           if avaialble, otherwise 
*                                           NU_NULL.
*
* OUTPUTS
*
*       NU_SPI_NO_FREE_BUS_SLOT             No free bus slot is available.
*       NU_SUCCESS                          Service completed 
*                                           successfully.
*
*************************************************************************/
static STATUS LWSPI_Find_Vacant_Bus_Slot(NU_SPI_BUS **spi_bus)
{
    UINT8   bus_idx;
    STATUS  status;
    
    /* Initialize local variables. */
    *spi_bus    = NU_NULL;
    status      = NU_SPI_NO_FREE_BUS_SLOT;

    /* Look for a vacant bus slot. */
    for(bus_idx=0; bus_idx<LWSPI_NUM_BUSES; bus_idx++)
    {
        if(!SPI_Bus_CB[bus_idx].is_used)
        {
            *spi_bus            = &SPI_Bus_CB[bus_idx];
            (*spi_bus)->is_used = NU_TRUE;
            (*spi_bus)->index   = bus_idx;
            status              = NU_SUCCESS;
            break;
        }
    }
    
    return (status);
}

/*************************************************************************
* FUNCTION
*
*       LWSPI_Find_Vacant_Slave_Slot
*
* DESCRIPTION
*
*       This function finds a vacant slave slot. If a vacant slot is 
*       available then its pointer is returned in output argument 
*       spi_slave.
*
* INPUTS
*
*       spi_bus                             Pointer to SPI bus with which
*                                           slave is associated.
*       spi_slave                           An output argument containing 
*                                           address to SPI slave wehen 
*                                           function returns.
*                                           If no vacant slot is available
*                                           then this will contain NU_NULL.
*
* OUTPUTS
*
*       NU_SPI_NO_FREE_SLAVE_SLOT           No free slave slot is 
*                                           available.
*       NU_SUCCESS                          Service completed 
*                                           successfully.
*
*************************************************************************/
static STATUS LWSPI_Find_Vacant_Slave_Slot( NU_SPI_BUS      *spi_bus,
                                            NU_SPI_DEVICE   **spi_slave)
{
    UINT8   slave_idx;
    STATUS  status;
    
    /* Initialize local variables. */
    *spi_slave  = NU_NULL;
    status      = NU_SPI_NO_FREE_SLAVE_SLOT;

    /* Look for a vacant slave slot. */
    for(slave_idx=0; slave_idx<LWSPI_NUM_SLAVES; slave_idx++)
    {
        if(spi_bus->slaves[slave_idx] == NU_NULL)
        {
            /* Allocate memory for slave. */
            status = NU_Allocate_Memory(&System_Memory,
                                        (VOID**)spi_slave,
                                        sizeof(NU_SPI_DEVICE),
                                        NU_NO_SUSPEND);
            if(status == NU_SUCCESS)
            {
                /* Reset contents of newly allocated SPI slave. */
                memset(*(spi_slave), 0x00, sizeof(NU_SPI_DEVICE));

                (*spi_slave)->index         = slave_idx;
                spi_bus->slaves[slave_idx]  = *spi_slave;
            }
            break;
        }
    }
    
    return (status);
}

/*************************************************************************
* FUNCTION
*
*       LWSPI_Find_Bus_Slot_By_Name
*
* DESCRIPTION
*
*       This function finds a bus slot by name. If specified bus slot is 
*       available then its pointer is returned in output argument spi_bus.
*
* INPUTS
*
*       name                                Name of bus.
*       spi_bus                             An output argument containing 
*                                           address to SPI bus wehen 
*                                           function returns.
*                                           If no bus with specified name 
*                                           exists then it contains 
*                                           NU_NULL.
*
* OUTPUTS
*
*       NU_SPI_BUS_SLOT_NOT_FOUND           No bus with specified name was
*                                           found.
*       NU_SUCCESS                          Service completed 
*                                           successfully.
*
*************************************************************************/
static STATUS LWSPI_Find_Bus_Slot_By_Name(CHAR *name, NU_SPI_BUS **spi_bus)
{
    STATUS  status;
    UINT8   bus_index;
    
    /* Initialize local variables. */
    status      = NU_SPI_BUS_SLOT_NOT_FOUND;
    *spi_bus    = NU_NULL;
    
    /* Look for a bus slot with specified name. */
    for(bus_index=0; bus_index<LWSPI_NUM_BUSES; bus_index++)
    {
        if(!strncmp(SPI_Bus_CB[bus_index].name,
                    name,
                    NU_SPI_BUS_NAME_LEN))
        {
            *spi_bus    = &SPI_Bus_CB[bus_index];
            status      = NU_SUCCESS;
            break;
        }
    }
    
    return (status);
}

/*************************************************************************
* FUNCTION
*
*       LWSPI_Find_Bus_Slot_By_ID
*
* DESCRIPTION
*
*       This function finds a bus slot by ID. If specified bus slot is 
*       available then its pointer is returned in output argument spi_bus.
*
* INPUTS
*
*       id                                  SPI bus ID.
*       spi_bus                             An output argument containing 
*                                           address to SPI bus wehen 
*                                           function returns.
*                                           If no bus with specified ID 
*                                           exists then it contains 
*                                           NU_NULL.
*
* OUTPUTS
*
*       NU_SPI_BUS_SLOT_NOT_FOUND           No bus with specified name was
*                                           found.
*       NU_SUCCESS                          Service completed 
*                                           successfully.
*
*************************************************************************/
static STATUS LWSPI_Find_Bus_Slot_By_ID(DV_DEV_ID id, NU_SPI_BUS **spi_bus)
{
    STATUS  status;
    UINT8   bus_index;
    
    /* Initialize local variables. */
    status      = NU_SPI_BUS_SLOT_NOT_FOUND;
    *spi_bus    = NU_NULL;
    
    /* Look for a bus slot with specified device ID. */
    for(bus_index=0; bus_index<LWSPI_NUM_BUSES; bus_index++)
    {
        if(SPI_Bus_CB[bus_index].dev_id == id)
        {
            *spi_bus    = &SPI_Bus_CB[bus_index];
            status      = NU_SUCCESS;
            break;
        }
    }
    
    return (status);
}

/*************************************************************************
* FUNCTION
*
*       LWSPI_Get_Params_From_Handle
*
* DESCRIPTION
*
*       This function decodes the input handle and returns SPI bus and 
*       slave parameters as output arguments.
*
* INPUTS
*
*       handle                              Input handle of device.
*       spi_bus                             An output argument containing 
*                                           address to SPI bus wehen 
*                                           function returns.
*       spi_slave                           An output argument containing 
*                                           address to SPI slave wehen 
*                                           function returns.
*
* OUTPUTS
*
*       NU_SPI_INVLD_ARG                    If an input argument is 
*                                           invalid.
*       NU_SUCCESS                          Service completed 
*                                           successfully.
*
*************************************************************************/
static STATUS LWSPI_Get_Params_From_Handle( NU_SPI_HANDLE   handle, 
                                            NU_SPI_BUS      **spi_bus,
                                            NU_SPI_DEVICE   **spi_slave)
{
    UINT8   bus_idx, slave_idx;
    STATUS  status;

    /* Initialize status with an error case. */
    status = NU_SPI_INVLD_ARG;
    
#if (CFG_NU_OS_CONN_LWSPI_ERR_CHECK_ENABLE == NU_TRUE)
    /* Check for valid handle. */
    if(NU_SPI_VALIDATE_HANDLE(handle))
#endif
    {
        /* Decode handle to get index of SPI bus and slave. */
        NU_SPI_DECODE_HANDLE(handle, &bus_idx, &slave_idx);
#if (CFG_NU_OS_CONN_LWSPI_ERR_CHECK_ENABLE == NU_TRUE)
        if((bus_idx < LWSPI_NUM_BUSES) && (slave_idx < LWSPI_NUM_SLAVES))
#endif
        {
            /* Get pointer to SPI bus. */
            *spi_bus = &SPI_Bus_CB[bus_idx];
            
#if (CFG_NU_OS_CONN_LWSPI_ERR_CHECK_ENABLE == NU_TRUE)
            /* Check validity of requested bus. */
            if((*spi_bus)->is_used)
#endif
            {
                /* Get pointer to SPI slave. */
                *spi_slave = (*spi_bus)->slaves[slave_idx];
                
#if (CFG_NU_OS_CONN_LWSPI_ERR_CHECK_ENABLE == NU_TRUE)
                /* Check validity of requested SPI slave. */
                if(*spi_slave)
#endif
                {
                    status = NU_SUCCESS;
                }
            }
        }
    }
    
    return (status);
}

/*************************************************************************
* FUNCTION
*
*       NU_SPI_Register_Slave
*
* DESCRIPTION
*
*       Application should call this function to register a SPI slave 
*       device with a SPI bus. Only after registering a slave device 
*       with a bus, application can perform I/O with this slave device.
*
* INPUTS
*
*       bus_name                            Name of the SPI bus.
*       baud_rate                           Operational baud rate of slave
*                                           device.
*       max_transfer_size                   Maximum supported transfer 
*                                           size of the slave device.
*       mode_order_polarity                 Bitwise OR of mode, order, and 
*                                           polarity of slave select.
*                                               * SPI mode of operation
*                                                   Possible values:
*                                                   - SPI_MODE_POL_LO_PHA_LO
*                                                   - SPI_MODE_POL_LO_PHA_HI
*                                                   - SPI_MODE_POL_HI_PHA_LO
*                                                   - SPI_MODE_POL_HI_PHA_HI
*                                               * Bit order of slave
*                                                   Possible values:
*                                                   - SPI_BO_LSB_FIRST
*                                                   - SPI_BO_MSB_FIRST
*                                               * Polarity of slave select
*                                                   Possible values:
*                                                   - SPI_SS_POL_LO
*                                                   - SPI_SS_POL_HI
*                                                   - SPI_SS_POL_DONT_CARE
*       ss_cb                               Application supplied callback 
*                                           function. Only valid if 
*                                           application wants to use 
*                                           software driven slave select.
*                                           Application should pass 
*                                           SPI_SS_POL_DONT_CARE in 
*                                           'mode_order_polarity' if softwre 
*                                           driven slave select is used.
*       handle                              An output argument containing 
*                                           handle to newly registered 
*                                           slave when function returns.
*
* OUTPUTS
*
*       NU_SPI_BUS_SLOT_NOT_FOUND           SPI bus with specified name 
*                                           is not found.
*       NU_SPI_NO_FREE_SLAVE_SLOT           No free slave slot is found.
*       NU_SUCCESS                          Service completed 
*                                           successfully.
*
*************************************************************************/
STATUS NU_SPI_Register_Slave(CHAR           *bus_name, 
                            UINT32          baud_rate, 
                            UINT32          max_transfer_size, 
                            UINT32          mode_order_polarity,
                            SPI_SS_CALLBACK ss_cb,
                            NU_SPI_HANDLE   *handle)
{
    NU_SPI_BUS      *spi_bus;
    NU_SPI_DEVICE   *spi_slave;
    STATUS          status;
    
    /* Find bus index, identified byt 'bus_name'. */
    status = LWSPI_Find_Bus_Slot_By_Name(bus_name, &spi_bus);
    if(status == NU_SUCCESS)
    {
        /* Find a vacant slave slot. */
        status = LWSPI_Find_Vacant_Slave_Slot(spi_bus, &spi_slave);
        if(status == NU_SUCCESS)
        {
            spi_slave->ss_callback          = ss_cb;
            spi_slave->baud_rate            = baud_rate;
            spi_slave->max_transfer_size    = max_transfer_size;
            spi_slave->handle               = NU_SPI_ENCODE_HANDLE(spi_bus->index,spi_slave->index);
            spi_slave->spi_mode             = (mode_order_polarity & 0x00FF0000);
            spi_slave->bit_order            = (mode_order_polarity & 0x0000FF00);
            spi_slave->ss_polarity          = (mode_order_polarity & 0x000000FF);
            spi_slave->ss_index             = 0;
            spi_slave->bus                  = spi_bus;

            /* Create asynchronous I/O lock for this device. */
            status = NU_Create_Semaphore(&spi_slave->async_io_lock,
                                        "SPI_DL",
                                        0,
                                        NU_FIFO);
            if(status == NU_SUCCESS)
            {
#if (LWSPI_NUM_SLAVES == 1)
                /* If there is only one slave in system then configure 
                 * hardware once.
                 */
                /* Configure hardware parameters. */
                spi_bus->io_ptrs.configure(spi_bus->dev_context,
                                            spi_slave);
#endif

                /* Return device handle to the caller. */
                *handle = spi_slave->handle;
            }
        }
    }
    
    return (status);
}

/*************************************************************************
* FUNCTION
*
*       NU_SPI_Unregister_Slave
*
* DESCRIPTION
*
*       Application may call this function to un-register a slave device.
*       Once un-registered no further I/O can be performed on this device.
*
* INPUTS
*
*       handle                              Handle to SPI slave obtained 
*                                           during registeration.
*
* OUTPUTS
*
*       NU_SPI_INVLD_ARG                    Handle is invalid.
*       NU_SUCCESS                          Service completed successfully.
*
*************************************************************************/
STATUS NU_SPI_Unregister_Slave(NU_SPI_HANDLE handle)
{
    UINT8           bus_idx, slave_idx;
    NU_SPI_BUS      *spi_bus;
    NU_SPI_DEVICE   *spi_slave;
    STATUS          status;
    
    /* Initialize status to an invalid value. */
    status = NU_SPI_INVLD_ARG;
    
    /* Check for valid handle. */
    if(NU_SPI_VALIDATE_HANDLE(handle))
    {
        NU_SPI_DECODE_HANDLE(handle, &bus_idx, &slave_idx);
        if((bus_idx < LWSPI_NUM_BUSES) && (slave_idx < LWSPI_NUM_SLAVES))
        {
            spi_bus = &SPI_Bus_CB[bus_idx];
            spi_slave = spi_bus->slaves[slave_idx];
            
            if(spi_slave != NU_NULL)
            {
                /* Delete async I/O lock of this device. */
                status = NU_Delete_Semaphore(&spi_slave->async_io_lock);
                
                /* Deallocate device. */
                status |= NU_Deallocate_Memory(spi_slave);
                
                /* Mark slave slot as empty. */
                spi_bus->slaves[slave_idx] = NU_NULL;
            }
        }
    }
    
    return (status);

}

/*************************************************************************
* FUNCTION
*
*       NU_SPI_Read
*
* DESCRIPTION
*
*       Application calls this function to read data from specified SPI 
*       device. The behavior if this function varies depending upon the 
*       value of 'io_type' argument.
*           - If caller passes 'io_type' as SPI_POLLED_IO then driver will 
*             complete read operation in polling mode. This way all 
*             data will be read within the calling context.
*           - If caller passes 'io_type' as SPI_INTERRUPT_IO then driver
*             will carry interrupt driven read operation. This way all 
*             data will be read within the context of SPI master controller 
*             driver HISR, but API will suspend calling context until 
*             the completion of read operation hence completion of read 
*             operation still be reported synchronously.
*       This function will block until the read request is completed 
*       with success or an error occurs in transfer. If an I/O request 
*       is already in progress on bus then call to this function will 
*       suspend the calling context until bus is free and transfer can 
*       progress. 
*
* INPUTS
*
*       handle                              Handle of SPI device.
*       io_type                             Specifies if polling or 
*                                           interrupt driven I/O should 
*                                           be used. value of this argument
*                                           should be one of these.
*                                            - SPI_POLLED_IO
*                                            - SPI_INTERRUPT_IO
*       buffer                              Pointer to buffer where data 
*                                           is to be read.
*       length                              length of data to be read.
*
* OUTPUTS
*
*       NU_SUCCESS                          Service completed 
*                                           successfully.
*
*************************************************************************/
STATUS NU_SPI_Read( NU_SPI_HANDLE   handle, 
                    UNSIGNED        io_type, 
                    UINT8           *buffer, 
                    UINT32          length)
{
    NU_SPI_BUS      *spi_bus;
    NU_SPI_DEVICE   *spi_slave;
    NU_SPI_IRP      *irp;
    STATUS          status;
    
    /* Get SPI bus and slave parameters from handle. */
    status = LWSPI_Get_Params_From_Handle(handle, &spi_bus, &spi_slave);
    if(status == NU_SUCCESS)
    {
        /* Claim exclusive access on bus. */
        status = NU_Obtain_Semaphore(&spi_bus->bus_lock, 
                                    NU_SUSPEND);
        if(status == NU_SUCCESS)
        {
            irp = &(spi_slave->rx_irp);
            
            irp->device         = spi_slave;
            irp->buffer         = buffer;
            irp->length         = length;
            irp->actual_length  = 0;
            
#ifdef CFG_NU_OS_SVCS_PWR_ENABLE
            /* Check if device power is on. If device power is off then driver 
            * may block the call until device power is on.
            */
            if (spi_bus->io_ptrs.check_power_on != NU_NULL)
            {
                spi_bus->io_ptrs.check_power_on(spi_bus->dev_context);
            }
#endif
            
#if (LWSPI_NUM_SLAVES > 1)
            /* Re-configure hardware only if current I/O operation is 
             * targeted for a different slave than the previous one. 
             */
            if(spi_bus->current_slave != spi_slave)
            {
                /* Configure hardware parameters. */
                spi_bus->io_ptrs.configure(spi_bus->dev_context,
                                            spi_slave);

                /* Save pointer to SPI slave for later use. */
                spi_bus->current_slave = spi_slave;
            }
#endif

            /* If user supplied slave select is available then assert
             * the slave select.
             */
            if (spi_slave->ss_callback != NU_NULL)
            {
                spi_slave->ss_callback(NU_TRUE);
            }
                
            /* Call target driver read function. */
            status = spi_bus->io_ptrs.read(spi_bus->dev_context, 
                                    (io_type == SPI_POLLED_IO ? NU_TRUE : NU_FALSE), 
                                    irp);
            if ((status == NU_SUCCESS) && (io_type == SPI_INTERRUPT_IO))
            {
                NU_Obtain_Semaphore(&spi_slave->async_io_lock, 
                                    NU_SUSPEND);
            }
            
            /* If user supplied slave select is available then de-assert
             * the slave select.
             */
            if (spi_slave->ss_callback != NU_NULL)
            {
                spi_slave->ss_callback(NU_FALSE);
            }
                
            /* Release SPI bus. */
            NU_Release_Semaphore(&spi_bus->bus_lock);
        }
    }

    return (status);
}

/*************************************************************************
* FUNCTION
*
*       NU_SPI_Write
*
* DESCRIPTION
*
*       Application calls this function to write data to specified SPI 
*       device. The behavior if this function varies depending upon the 
*       value of 'io_type' argument.
*           - If caller passes 'io_type' as SPI_POLLED_IO then driver will 
*             complete write operation in polling mode. This way all 
*             data will be written within the calling context.
*           - If caller passes 'io_type' as SPI_INTERRUPT_IO then driver
*             will carry interrupt driven write operation. This way all 
*             data will be written within the context of SPI master 
*             controller driver HISR, but API will suspend calling context
*             until the completion of write operation hence completion of 
*             write operation still be reported synchronously.
*       This function will block until the write request is completed 
*       with success or an error occurs in transfer. If an I/O request 
*       is already in progress on bus then call to this function will 
*       suspend the calling context until bus is free and transfer can 
*       progress. 
*
* INPUTS
*
*       handle                              Handle of SPI device.
*       io_type                             Specifies if polling or 
*                                           interrupt driven I/O should 
*                                           be used. value of this argument
*                                           should be one of these.
*                                            - SPI_POLLED_IO
*                                            - SPI_INTERRUPT_IO
*       buffer                              Pointer to buffer containing 
*                                           data to be written.
*       length                              length of data to be written.
*
* OUTPUTS
*
*       NU_SUCCESS                          Service completed 
*                                           successfully.
*
*************************************************************************/
STATUS NU_SPI_Write(NU_SPI_HANDLE   handle, 
                    UNSIGNED        io_type, 
                    UINT8           *buffer, 
                    UINT32          length)
{
    NU_SPI_BUS      *spi_bus;
    NU_SPI_DEVICE   *spi_slave;
    NU_SPI_IRP      *irp;
    STATUS          status;
    
    /* Get SPI bus and slave parameters from handle. */
    status = LWSPI_Get_Params_From_Handle(handle, &spi_bus, &spi_slave);
    if(status == NU_SUCCESS)
    {
        /* Claim exclusive access on bus. */
        status = NU_Obtain_Semaphore(&spi_bus->bus_lock, 
                                    NU_SUSPEND);
        if(status == NU_SUCCESS)
        {
            irp = &(spi_slave->tx_irp);
            
            irp->device         = spi_slave;
            irp->buffer         = buffer;
            irp->length         = length;
            irp->actual_length  = 0;
            
#ifdef CFG_NU_OS_SVCS_PWR_ENABLE
            /* Check if device power is on. If device power is off then driver 
            * may block the call until device power is on.
            */
            if (spi_bus->io_ptrs.check_power_on != NU_NULL)
            {
                spi_bus->io_ptrs.check_power_on(spi_bus->dev_context);
            }
#endif
            
#if (LWSPI_NUM_SLAVES > 1)
            /* Re-configure hardware only if current I/O operation is 
             * targeted for a different slave than the previous one. 
             */
            if(spi_bus->current_slave != spi_slave)
            {
                /* Configure hardware parameters. */
                spi_bus->io_ptrs.configure(spi_bus->dev_context,
                                            spi_slave);

                /* Save pointer to SPI slave for later use. */
                spi_bus->current_slave = spi_slave;
            }
#endif

            /* If user supplied slave select is available then assert
             * the slave select.
             */
            if (spi_slave->ss_callback != NU_NULL)
            {
                spi_slave->ss_callback(NU_TRUE);
            }
                
            /* Call target driver read function. */
            status = spi_bus->io_ptrs.write(spi_bus->dev_context, 
                                    (io_type == SPI_POLLED_IO ? NU_TRUE : NU_FALSE),
                                    irp);
            if ((status == NU_SUCCESS) && (io_type == SPI_INTERRUPT_IO))
            {
                NU_Obtain_Semaphore(&spi_slave->async_io_lock, 
                                    NU_SUSPEND);
            }
            
            /* If user supplied slave select is available then de-assert
             * the slave select.
             */
            if (spi_slave->ss_callback != NU_NULL)
            {
                spi_slave->ss_callback(NU_FALSE);
            }
            
            /* Release SPI bus. */
            NU_Release_Semaphore(&spi_bus->bus_lock);
        }
    }

    return (status);
}

/*************************************************************************
* FUNCTION
*
*       NU_SPI_Write_Read
*
* DESCRIPTION
*
*       Application calls this function to write followed by read to 
*       specified SPI device. The behavior if this function varies 
*       depending upon the value of 'io_type' argument.
*           - If caller passes 'io_type' as SPI_POLLED_IO then driver will 
*             complete I/O operation in polling mode. This way all 
*             data will be transferred within the calling context.
*           - If caller passes 'io_type' as SPI_INTERRUPT_IO then driver 
*             will carry interrupt driven I/O operation. This way all data 
*             will be transferred within the context of SPI master 
*             controller driver HISR, but API will suspend calling context
*             until the completion of I/O operation hence completion 
*             of I/O operation still be reported synchronously.
*       This function will block until the I/O request is completed 
*       with success or an error occurs in transfer. If an I/O request 
*       is already in progress on bus then call to this function will 
*       suspend the calling context until bus is free and transfer can 
*       progress. 
*
* INPUTS
*
*       handle                              Handle of SPI device.
*       io_type                             Specifies if polling or 
*                                           interrupt driven I/O should 
*                                           be used. value of this argument
*                                           should be one of these.
*                                            - SPI_POLLED_IO
*                                            - SPI_INTERRUPT_IO
*       buffer_tx                           Pointer to buffer containing 
*                                           data to be written.
*       buffer_rx                           Pointer to buffer where data 
*                                           is to be read.
*       io_length                           Length of data to be written/read.
*
* OUTPUTS
*
*       NU_SUCCESS                          Service completed 
*                                           successfully.
*
*************************************************************************/
STATUS NU_SPI_Write_Read(NU_SPI_HANDLE  handle,
                         UNSIGNED       io_type,
                         UINT8         *buffer_tx,
                         UINT8         *buffer_rx,
                         UINT32         io_length)
{
    NU_SPI_BUS      *spi_bus;
    NU_SPI_DEVICE   *spi_slave;
    NU_SPI_IRP      *tx_irp, *rx_irp;
    STATUS          status;
    
    /* Get SPI bus and slave parameters from handle. */
    status = LWSPI_Get_Params_From_Handle(handle, &spi_bus, &spi_slave);
    if(status == NU_SUCCESS)
    {
        /* Claim exclusive access on bus. */
        status = NU_Obtain_Semaphore(&spi_bus->bus_lock, 
                                    NU_SUSPEND);
        if(status == NU_SUCCESS)
        {
            tx_irp = &(spi_slave->tx_irp);
        
            /* Construct Tx IRP. */
            tx_irp->device         = spi_slave;
            tx_irp->buffer         = buffer_tx;
            tx_irp->length         = io_length;
            tx_irp->actual_length  = 0;
            
            rx_irp = &(spi_slave->rx_irp);
            
            /* Construct Rx IRP. */
            rx_irp->device         = spi_slave;
            rx_irp->buffer         = buffer_rx;
            rx_irp->length         = io_length;
            rx_irp->actual_length  = 0;
            
#ifdef CFG_NU_OS_SVCS_PWR_ENABLE
            /* Check if device power is on. If device power is off then driver 
            * may block the call until device power is on.
            */
            if (spi_bus->io_ptrs.check_power_on != NU_NULL)
            {
                spi_bus->io_ptrs.check_power_on(spi_bus->dev_context);
            }
#endif
        
#if (LWSPI_NUM_SLAVES > 1)
            /* Re-configure hardware only if current I/O operation is 
             * targeted for a different slave than the previous one. 
             */
            if(spi_bus->current_slave != spi_slave)
            {
                /* Configure hardware parameters. */
                spi_bus->io_ptrs.configure(spi_bus->dev_context,
                                            spi_slave);

                /* Save pointer to SPI slave for later use. */
                spi_bus->current_slave = spi_slave;
            }
#endif

            /* If user supplied slave select is available then assert
             * the slave select.
             */
            if (spi_slave->ss_callback != NU_NULL)
            {
                spi_slave->ss_callback(NU_TRUE);
            }
                
            /* Call target driver read function. */
            status = spi_bus->io_ptrs.write_read(spi_bus->dev_context, 
                                        (io_type == SPI_POLLED_IO ? NU_TRUE : NU_FALSE),
                                        tx_irp, 
                                        rx_irp);
            if ((status == NU_SUCCESS) && (io_type == SPI_INTERRUPT_IO))
            {
                NU_Obtain_Semaphore(&spi_slave->async_io_lock, 
                                    NU_SUSPEND);
            }
            
            /* If user supplied slave select is available then de-assert
             * the slave select.
             */
            if (spi_slave->ss_callback != NU_NULL)
            {
                spi_slave->ss_callback(NU_FALSE);
            }
            
            /* Release SPI bus. */
            NU_Release_Semaphore(&spi_bus->bus_lock);
        }
    }

    return (status);
}

#if (CFG_NU_OS_CONN_LWSPI_EXTENDED_API_ENABLE == NU_TRUE)
/*************************************************************************
* FUNCTION
*
*       NU_SPI_Set_Slave_Select_Index
*
* DESCRIPTION
*
*       Application should call this function if it needs to set slave 
*       select index for a particular slave. This is required in case 
*       when there are multiple slave selects in hardware.
*
* INPUTS
*
*       handle                              Handle of SPI device.
*       ss_index                            Slave select index.
*
* OUTPUTS
*
*       NU_SUCCESS                          Service completed 
*                                           successfully.
*
*************************************************************************/
STATUS NU_SPI_Set_Slave_Select_Index(NU_SPI_HANDLE handle, UINT8 ss_index)
{
    NU_SPI_BUS      *spi_bus;
    NU_SPI_DEVICE   *spi_slave;
    STATUS          status;
    
    /* Get SPI bus and slave parameters from handle. */
    status = LWSPI_Get_Params_From_Handle(handle, &spi_bus, &spi_slave);
    if(status == NU_SUCCESS)
    {
        /* Claim exclusive access on bus. */
        status = NU_Obtain_Semaphore(&spi_bus->bus_lock, 
                                    NU_SUSPEND);
        if(status == NU_SUCCESS)
        {
            /* Save assigned slave select index. */
            spi_slave->ss_index = ss_index;
    
#if (LWSPI_NUM_SLAVES == 1)
            /* If there is only one slave in system then re-configure 
             * hardware here.
             */
            /* Configure hardware parameters. */
            spi_bus->io_ptrs.configure(spi_bus->dev_context,
                                        spi_slave);
#else
            /* Invalidate current slave so that bus is reconfigured at 
             * next I/O operation. 
             */
            spi_bus->current_slave = NU_NULL;
#endif
            /* Release SPI bus. */
            NU_Release_Semaphore(&spi_bus->bus_lock);
        }
    }
    
    return (status);
}
#endif /* (CFG_NU_OS_CONN_LWSPI_EXTENDED_API_ENABLE == NU_TRUE) */
