/*
 *            Copyright (c) 1998-2004 by NETsilicon Inc.
 *
 *  This software is copyrighted by and is the sole property of
 *  NETsilicon.  All rights, title, ownership, or other interests
 *  in the software remain the property of NETsilicon.  This
 *  software may only be used in accordance with the corresponding
 *  license agreement.  Any unauthorized use, duplication, transmission,
 *  distribution, or disclosure of this software is expressly forbidden.
 *
 *  This Copyright notice may not be removed or modified without prior
 *  written consent of NETsilicon.
 *
 *  NETsilicon, reserves the right to modify this software
 *  without notice.
 *
 *  NETsilicon
 *  411 Waverley Oaks Road                  USA 781.647.1234
 *  Suite 227                               http://www.netsilicon.com
 *  Waltham, MA 02452                       AmericaSales@netsilicon.com
 *
 * Description:
 *
 *  This file contains the functions for DDI interface
 *
 */
 /*
  *	 Edit History
  *  Date		Initials		Changes Description
  * 
  *  06/22/04	JZW				Removed references to O_SPI_CLOCK_HI. The option does
  *                             not exist
  */
 
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <tx_port.h>
#include <semaphore.h>
#include <ddi.h>
#include <netosIo.h>
#include <reg_def.h>

#include <serParam.h>

#include "netos_spi.h"
#include <NA_ISR.h>
#include <Dma_def.h>
#include <sysClock.h>
#include <reg_def.h>
#include <bool.h>
#include <tx_api.h>
#include <ncc_init.h>
#include "spi_mod.h"

/* #define EEPROM_SUPPORT   1 */

#define GET_SEMA(x)  if (sem_trywait(x) != TX_SUCCESS) return EBUSY;
#define PUT_SEMA(x)  sem_post(x);

/* global device table, defined in ind_os.c */
extern deviceInfo deviceTable[];
unsigned static AllSPIOptins = (O_RDONLY|O_WRONLY|O_RDWR|O_DMA|O_SPI_SLAVE|O_SPI_BLOCKSEL);

static ONCC_SIO_CHAN_PARAS devParas[] =
{
    {INT_VEC_SERIAL1_RX, SER1RX_INT_LVL, INT_VEC_SERIAL1_TX, SER1TX_INT_LVL},
    {INT_VEC_SERIAL2_RX, SER2RX_INT_LVL, INT_VEC_SERIAL2_TX, SER2TX_INT_LVL},
};

static ONCC_SIO_CHAN_PARAS devDmaParas[] =
{
    {DMA_VEC_SERIAL1_RX, SER1RX_DMA_LVL,  /* interrupt level for the DMA channels */
     DMA_VEC_SERIAL1_TX, SER1TX_DMA_LVL},
    {DMA_VEC_SERIAL2_RX, SER2RX_DMA_LVL,
     DMA_VEC_SERIAL2_TX, SER2TX_DMA_LVL},
};

/*
 * semaphore for reading and writing
 * Unlike Serial RS-232 driver SPI has to read and write at the same time
 */
static sem_t   *spi_sem[NUM_NCC_SER_CHAN];

/* Semaphore to indicate end of read/write operation */
#define semintertext "_spiint"
static char sem_intr_name[NUM_NCC_SER_CHAN][sizeof(semintertext)+2]; /* semaphore uses pointer to this string for the names */

/* Semaphore to block multiple accesses to the SPI routines */
#define semtext "_spi"  
static char sem_name[NUM_NCC_SER_CHAN][sizeof(semtext)+2]; /* semaphore uses pointer to this string for the names */

static ONSPI_CHAN  onspiChan[NUM_NCC_SER_CHAN];

/*
 * Array of pointers to all serial channels configured in system.
 * It is this array that maps channel pointers
 * to standard device names.
 */

static ONSPI_CHAN * sysSPIChanals[] =
{
    &onspiChan[0],
    &onspiChan[1] 
};

/*
 * Function: InstallSpiDevice ()
 *
 * This function goes through the deviceTable to find an empty slot to install
 * the serial device. The first empty slot will be used.
 */
int InstallSpiDevice(char *port)
{
    int i=0;

    while (i < MAX_DEV && deviceTable[i].name)  /* check for empty device slot */
        i++;
    if (i == MAX_DEV)
        return -1;

    INSTALL_DEVICE (i,                  /* registers the serial device and its driver functions */
        port,
        0,
        netos_spienter,
        netos_spiinit,
        netos_spiopen,
        netos_spiclose,
        NULL,
        netos_spiread,
        netos_spiwrite,
        netos_spiioctl,
        (unsigned)-1);

    return 0;
}

/**********************************************************************************
 **********************************************************************************
 ***                                                                            ***
 *** Function Name:  SpiDevInit()                                               ***
 ***                                                                            ***
 *** Called by:                                                                 ***
 ***                                                                            ***
 *** Comments: This routine initializeS some SIO_CHANfunction pointers and     ***
 *** then resets the serial port in a quiescent state.  Before this routine is  ***
 *** called, the BSP must already have initialized all the device addresses,    ***
 *** etc. in the ONSPI_CHAN structure.  (Reference WRS template & example.)      ***
 **********************************************************************************
 **********************************************************************************/

static void SpiDevInit (ONSPI_CHAN * pChan)
{ 

   onspiInitChannel(pChan,true);
}

/*
 *  Function: netos_spi_enter
 *
 *  Description: First level initialization. Called before
 *               kernel starts.
 *
 *  called by: ANSI layer _enter() (DDIFirstLevelInitalize())
 *
 *  Parameter:
 *
 *      channel      channel number as set in the channel's
 *                   device table entry
 *  Return Values:
 *
 *      EXIT_SUCCESS
 */

int netos_spienter (int i/*channel */)
{

#ifdef DEBUG_SPI
    printf("netos spi init enter \n");
#endif
    onspiChan[i].rxlevel = devParas[i].rxintLevel;
    onspiChan[i].txlevel = devParas[i].txintLevel;
    onspiChan[i].rxDmalevel = devDmaParas[i].rxintLevel;
    onspiChan[i].txDmalevel = devDmaParas[i].txintLevel;
    onspiChan[i].slaveMode = false;
    onspiChan[i].LSBfirst = false;
    onspiChan[i].hwConfiguration = SIO_SPI_DIRECT;

    onspiChan[i].SSport = NULL;
    onspiChan[i].ISRlevel = -1;
    onspiChan[i].initSeq = true;

    onspiChan[i].blockSEL = false;
    onspiChan[i].slaveTimeOutValue = TX_WAIT_FOREVER; /* For the slave wait forever; */    
	 
    onspiChan[i].startClockHigh = false;
    onspiChan[i].TXCINVset = false;
    onspiChan[i].RXCINVset = false;


    /* Eventually, this will come from non-volatile storage */
    onspiChan[i].baudRate = DEFAULT_SPI_BAUD_RATE;

    /* System port address for this serial channel */
    onspiChan[i].port = i;


    /*
     * initialize UART
     */
    SpiDevInit (&onspiChan[i]);
   
    return EXIT_SUCCESS;
}


/*
 *  Function: netos_spiinit
 *
 *  Description: Second level initialization, after kernel is loaded.
 *               Complete any initialization needed by the driver.
 *
 *  called by: DDISecondLevelInitialize()
 *
 *  Parameter:
 *
 *      channel      channel number as set in the channel's
 *                   device table entry
 *  Return Values:
 *
 *      EXIT_SUCCESS
 *      EACCES	DLC channel open
 */
int netos_spiinit (int i/*channel */)
{

    ONSPI_CHAN * pch;
#ifdef DEBUG_SPI
    printf("netos spi init \n");
#endif
    /* create semaphore here */
    sprintf(sem_name[i],"%s%d",semtext,i+1);
    spi_sem[i] = sem_open (sem_name[i], O_EXCL | O_CREAT, 0, 1);
    
    NAInstallIsr (devParas[i].rxvector, (NA_ISR_HANDLER)onspiIntRd, (void*) &onspiChan[i]);  /*install receive interrupt function */
    if (chipRevision < 19)
      NAInstallIsr (devParas[i].txvector, (NA_ISR_HANDLER) onspiIntWr40, (void*) &onspiChan[i]);  /*install send interrupt function */
    else
      NAInstallIsr (devParas[i].txvector, (NA_ISR_HANDLER) onspiIntWr50, (void*) &onspiChan[i]);  /*install send interrupt function */
    NAInstallIsr (devDmaParas[i].txvector, (NA_ISR_HANDLER)onspiDmaWr, (void*) &onspiChan[i]);  /*install send interrupt function */
    NAInstallIsr (devDmaParas[i].rxvector, (NA_ISR_HANDLER)onspiDmaRd, (void*) &onspiChan[i]);  /*install receive interrupt function */

    pch = sysSPIChanals[i];  /* pch points to the device table for channel */

    sprintf(sem_intr_name[i],"%s%d",semintertext,i+1);
    pch->spi_int_sem = sem_open (sem_intr_name[i], O_EXCL | O_CREAT, 0, 1); /* Create semaphore for ISR */

    spiDataBuffer[i].ptrDmaBuffer = NULL;
    spiInBuffer[i].ptrDmaBuffer = NULL;
    
    /* Lock serial channel resource, so HDLC can't open a chsannel */
    if (!GetResource(&serl_resource_lock[i], pch))
	    return (EACCES);	/* HDLC channel open */
	return (EXIT_SUCCESS);
}

/*
 *  Function: netos_spiopen()
 *
 *  Description: open a new session on a channel
 *
 *  called by: open (), creat () in DDI
 *
 *  Parameter:
 *
 *      channel      channel number as set in the channel's
 *                   device table entry
 *      flags        a bit field formed by orring one or more
 *                   of O_RDONLY, O_WRONLY, O_RDWR, O_SPI_SLAVE or O_SPI_BLOCKSEL
 *
 *  Return Values:
 *
 *      EXIT_SUCCESS
 *      EINVAL
 *      EBUSY
 */
int netos_spiopen (int channel, unsigned flags)
{
    ONSPI_CHAN  *pch;
#define RWOPTIONS   (O_RDONLY|O_WRONLY|O_RDWR)
    unsigned rwmode = (flags & RWOPTIONS);
   
#ifdef DEBUG_SPI
    printf("netos spi open \n");
#endif
  
    if (channel != 0 && channel != 1)     /* Only support two serial ports */
        return EINVAL;

    pch = sysSPIChanals[channel];  /* pch points to the device table for channel */

    if ((rwmode & (O_RDONLY|O_APPEND))
        || (rwmode & RWOPTIONS) == RWOPTIONS)
    {
        return EINVAL;
    }

    if (!((rwmode & O_RDONLY) == O_RDONLY || (rwmode & O_RDWR) || (rwmode & O_WRONLY)))
    {
        return EINVAL;
    }
  
    if ((flags & ~AllSPIOptins) != 0)  /* this is the only illegal value */
        return EINVAL;


    /* return EROFS if flags are inappropriate,
     i.e. Write request on a read only file system */
  
    GET_SEMA(spi_sem[channel])                /* get the semaphore */

    if (pch->opened)/* no need to open if its already open */
    {
        PUT_SEMA(spi_sem[channel])                /* releases semaphore when done */
        return EBUSY;
    }
   
    spiResetChannel (pch,true);     /* clean all interrupts, just in case */
     
   pch->opened = true;                      /* Updated the opened flag */
   pch->openmode = flags;                /* updated the opened mode */

#ifdef EEPROM_SUPPORT
   pch->writeAddr = 0;     /* Starting address for the next write operation */
   pch->readAddr = 0;      /* Starting address for the next read operation */
#endif

   pch->IOinProgress = false; /* we are not reading or writing */

   if (pch->initSeq)
   {
       /*
	* Master-Slave or O_SPI_BLOCKSEL
	* mode can be set only during
	* initialization because it will change
	* direction of the electrical signal.
	* It cannnot be done in the middle of the operation
       */

        if ((flags & O_SPI_SLAVE) == O_SPI_SLAVE)
        {                        
    	    pch->slaveMode = true;
        }
        else
        {
	        pch->slaveMode = false;
        }
       
        if ((flags & O_SPI_BLOCKSEL) == O_SPI_BLOCKSEL)
        {
	        if (pch->slaveMode)
	        {
	            /* O_SPI_BLOCKSEL is valid only with the master mode */
	            PUT_SEMA(spi_sem[channel]);
	            return EINVAL;
	        }
	        else
            {
	            pch->blockSEL = true;
            }
        }
        else
        {
            pch->blockSEL = false;
        }

        pch->initSeq = false;

        onspiInitChannel(pch, false);

        if (pch->SSport != NULL)
        {
	        *(pch->SSport) |= pch->SSsetHiValue;
        }
   }
   
   PUT_SEMA(spi_sem[channel])                /* releases semaphore when done */
   return EXIT_SUCCESS;
}

/*
 *  Function: netos_spiclose()
 *
 *  Description: close session on a channel
 *
 *  called by: close () in DDI
 *
 *  Parameter:
 *
 *      channel      channel number as set in the channel's
 *                   device table entry
 *  Return Values:
 *
 *      EXIT_SUCCESS
 *      EINVAL
 *      EBUSY
 */
int netos_spiclose (int channel)
{
  ONSPI_CHAN  *pch;
  if (channel != 0 && channel != 1)     /* check to see if channel number is valid */
  {
    return EINVAL;
  }

  pch = sysSPIChanals[channel];  /* pch points to the device table for channel */

  pch->IOinProgress = false;

#ifdef EEPROM_SUPPORT
#else
    if (spiDataBuffer[channel].ptrDmaBuffer != NULL)
    {
    	free (spiDataBuffer[channel].ptrDmaBuffer);
	    spiDataBuffer[channel].ptrDmaBuffer = NULL;
    }

    if (spiInBuffer[channel].ptrDmaBuffer != NULL)
    {
    	free (spiInBuffer[channel].ptrDmaBuffer);
	    spiInBuffer[channel].ptrDmaBuffer = NULL;
    }
#endif

  GET_SEMA(spi_sem[channel])                /* get the semaphore */

  if (!pch->opened)                     /* check to see if device is already closed */
  {
      PUT_SEMA(spi_sem[channel])    /* releases semaphore when done */ 
      return EBADF;
  }

  pch->opened = false;                      /* updates the opened flag */
  pch->openmode = 0;                    /* updates the openmode flag */
  PUT_SEMA(spi_sem[channel])    /* releases semaphore when done */ 
  return EXIT_SUCCESS;
}

/*
 * Function: netos_spiread()
 *
 * Description: read data from driver
 *
 * Called by: DDI read
 *
 * Input : channel - 0 for port com1, 1 for port com2
 *         buffer - data to be stored
 *         length - size of data buffer
 *         bytesWritten - store actual size being sent
 *
 * Return: EBADF 
 *         EINVAL
 *         EBUSY
 *         EAGAIN
 *         EXIT_SUCCESS
 *
 */
int netos_spiread (int channel, void * buffer, int length, int *bytesRead)
{
    ONSPI_CHAN  *pch;
    int err_code = EXIT_SUCCESS;

#ifdef EEPROM_SUPPORT     
    int status;
    char * ioBuffer;
#endif

    *bytesRead = 0;

    if (channel != 0 && channel != 1)     /* Only two channels are valid  */
    {
    	return EINVAL;
    }

    pch =  sysSPIChanals[channel];  /* pch points to the serial device for the channel*/

    if (!pch->opened)                     /* make sure device is opened */
    {
	    return EBADF;
    }

    if (pch->openmode & O_WRONLY)         /* make sure device is not write only */
    {
	    return EINVAL;
    }

    if (length < 0 || length > 32767)     /* check if length is OK */
    {
	    return EINVAL;
    }

    GET_SEMA(spi_sem[channel])                /* get the semaphore */

#ifdef EEPROM_SUPPORT
    if (spiDataBuffer[channel].ptrDmaBuffer != NULL)
    {
    	free (spiDataBuffer[channel].ptrDmaBuffer);
	    spiDataBuffer[channel].ptrDmaBuffer = NULL;
    }

    if (spiInBuffer[channel].ptrDmaBuffer != NULL)
    {
    	free (spiInBuffer[channel].ptrDmaBuffer);
	    spiInBuffer[channel].ptrDmaBuffer = NULL;
    }
#else

    /* Can't read before a write which provides a DMA buffer */
    if (spiInBuffer[channel].ptrDmaBuffer == NULL)
    {
        return EINVAL;
    }

#endif

    spiResetChannel(pch,true);			/* Reset serial port */

    pch->readInProgres = true;
    pch->IOinProgress = true; 

#ifdef EEPROM_SUPPORT
    ioBuffer = spi_set_io_buffer (&spiInBuffer[channel],buffer,length,pch);

    if (ioBuffer == NULL)
    {
    	err_code = EBADF;
	    goto end;
    }
    if (!pch->slaveMode)
    {
    	ioBuffer = spi_set_io_buffer (&spiDataBuffer[channel],buffer,length,pch);
	    if (ioBuffer == NULL)
    	{
	        err_code = EBADF;
	        goto end;
	    }
    }

    if (pch->openmode & O_DMA)
    {
    	status = spi_init_rx_dma(pch);
	    if (!pch->slaveMode)
        {
	        status = spi_init_tx_dma(pch);
        }
    }
    else
    {
    	status = onspiRxStartup (pch);
	    if (!pch->slaveMode)
        {
	        status = onspiTxStartup (pch);
        }
    }

    if (status != TX_SUCCESS)
    {
    	err_code = status;
	    goto end;
    }
    
    if (!waitForCompletion(&spiInBuffer[channel],pch,length))
    {
    	err_code = EAGAIN;
#ifdef DEBUG_SPI
	    printf("Error waitForCompletion for read. Bytes left %d. Len = %d  \n",
	       (spiInBuffer[channel].countServData + spiInBuffer[channel].countForBufferData),length);
#endif
	    goto end;
    }
     
    pch->readAddr += length;
#endif
        
    *bytesRead = length;
    spi_copy_io_buffer (&spiInBuffer[channel],buffer,length,pch);

#ifdef EEPROM_SUPPORT
    end:
#endif
	
    pch->IOinProgress = false;

#ifdef EEPROM_SUPPORT
    if (spiDataBuffer[channel].ptrDmaBuffer != NULL)
    {
    	free (spiDataBuffer[channel].ptrDmaBuffer);
	    spiDataBuffer[channel].ptrDmaBuffer = NULL;
    }

    if (spiInBuffer[channel].ptrDmaBuffer != NULL)
    {
    	free (spiInBuffer[channel].ptrDmaBuffer);
	    spiInBuffer[channel].ptrDmaBuffer = NULL;
    }
#else
    pch->readInProgres = false; 

    if (spiInBuffer[channel].ptrDmaBuffer != NULL)
    {
    	free (spiInBuffer[channel].ptrDmaBuffer);
	    spiInBuffer[channel].ptrDmaBuffer = NULL;
    }
#endif

    PUT_SEMA(spi_sem[channel])

    return err_code;
}


/*
 * Function: netos_spiwrite()
 *
 * Description: write data to driver
 *
 * Called by: DDI write
 *
 * Input : channel - 0 for port com1, 1 for port com2
 *         buffer - data to be sent
 *         length - size of data
 *         bytesWritten - store actual size being sent
 *
 * Return: EBADF  
 *         EINVAL
 *         EBUSY
 *         EAGAIN
 *         EXIT_SUCCESS
 *
 */
int netos_spiwrite (int channel, void * buffer, int length, int *bytesWritten)
{
    int status;
    ONSPI_CHAN  *pch;
    int err_code = EXIT_SUCCESS;

    char * ioBuffer;

    *bytesWritten = 0;
  
    if (channel != 0 && channel != 1)     /* check if channel is ready */
    {
        return EINVAL;
    }

    if (length < 0 || length > 32767)     /* check if length is OK */
    {
        return EINVAL;
    }


    pch = sysSPIChanals[channel];  /* pch points to device drive table */

    if (!pch->opened)                     /* be sure port is opened */
    {
        return EBADF;
    }

    if (pch->slaveMode && pch->ISRlevel == -1 && length < 40 )
    {
        return EINVAL;
    }

    if (pch->openmode & O_RDONLY)         /* make sure device is not write only */
    {
        return EINVAL;
    }
  
    GET_SEMA(spi_sem[channel])                /* get the semaphore */

    /*
	The following code is probably over kill for EEPROMS
	where we are writing tens of bytes. May be useful for
	the application that writes hundred of bytes.
	*/
  
    if (spiDataBuffer[channel].ptrDmaBuffer != NULL)
    {
	    free (spiDataBuffer[channel].ptrDmaBuffer);
	    spiDataBuffer[channel].ptrDmaBuffer = NULL;
    }

#ifdef EEPROM_SUPPORT
#else
    /* Clear the previous incoming data not read as of yet  */
    /* Some devices don't sent data back during a write     */
    /* (e.g EEPROM for write command )                      */
    if (spiInBuffer[channel].ptrDmaBuffer != NULL)
    {
    	free (spiInBuffer[channel].ptrDmaBuffer);
	    spiInBuffer[channel].ptrDmaBuffer = NULL;
    }

#endif
  
    /* now write data itself */
#ifdef EEPROM_SUPPORT
    pch->readInProgres = false;
    pch->IOinProgress = true; 

 /* First enable write operation */

  err_code = sendWriteEnable(pch);

  if (err_code != EXIT_SUCCESS)
      goto end;

  /* now write data itself */
    spiResetChannel(pch,true);			/* Reset serial port */

    spi_set_io_buffer (&spiDataBuffer[channel], buffer, length, pch);

    if (pch->openmode & O_DMA)
    {
        status = spi_init_tx_dma(pch);  
    }
    else
    {
        status = onspiTxStartup (pch);
    }

    if (status != TX_SUCCESS)
    {
    	err_code = status;
	    goto end;
    }

#else

    pch->IOinProgress = true; 

#ifdef USE
    /* First enable write operation */

    err_code = sendWriteEnable(pch);

    if (err_code != EXIT_SUCCESS)
    {
        goto end;
    }
#endif

    spiResetChannel(pch,true);  /* Reset the channel because send write is doing IO */
  
    pch->readInProgres = true;
    ioBuffer = spi_set_io_buffer (&spiInBuffer[channel],buffer,length,pch);

    if (ioBuffer == NULL)
    {
    	err_code = EBADF;
	    goto end;
    }

    pch->readInProgres = false;
    if (!pch->slaveMode)
    {
    	ioBuffer = spi_set_io_buffer (&spiDataBuffer[channel],buffer,length,pch);
	    if (ioBuffer == NULL)
    	{
	        err_code = EBADF;
	        goto end;
	    }
    }
     
   
    if (pch->openmode & O_DMA)
    {
    	status = spi_init_rx_dma(pch);
	    if (!pch->slaveMode)
        {
	        status = spi_init_tx_dma(pch);
        }
    }
    else
    {
    	status = onspiRxStartup (pch);
	    if (!pch->slaveMode)
        {
	        status = onspiTxStartup (pch);
        }
    }

    if (status != TX_SUCCESS)
    {
        err_code = status;
        goto end;
    }
#endif

    if (!waitForCompletion(&spiDataBuffer[channel],pch,length))
    {
        err_code = EAGAIN;
#ifdef DEBUG_SPI
        printf("Error in waitForCompletion for write . Bytes left %d. Len = %d  \n",
	       (spiDataBuffer[channel].countServData + spiDataBuffer[channel].countForBufferData),length);
#endif
        goto end;
    }
   
   
    *bytesWritten = length ;

#ifdef EEPROM_SUPPORT
    pch->writeAddr += length;
#endif
  
end:
    pch->IOinProgress = false;
    if (spiDataBuffer[channel].ptrDmaBuffer != NULL)
    {
	    free (spiDataBuffer[channel].ptrDmaBuffer);
	    spiDataBuffer[channel].ptrDmaBuffer = NULL;
    }
    
    PUT_SEMA(spi_sem[channel])
    return (err_code);

}


/*
 * Function: netos_spiioctl()
 *
 * Description This function calls onspiIoctl to sets the serial parameters
 * returns
 *              EINVAL when channel is not valid
 *              EBADF when channel is not opened
 *              err_code returned from onspiIoctl
 */
int netos_spiioctl (int channel, int request, char *args)
{
    int err_code = EXIT_SUCCESS;
    ONSPI_CHAN  *pch;

    if (channel != 0 && channel != 1)     /* two valid channels */
    {
        return EINVAL;
    }

    pch = sysSPIChanals[channel];

    GET_SEMA(spi_sem[channel])      /* get the semaphore */

    pch->IOinProgress = false; 
    if (!pch->opened)                /* Port has to be opened */
    {
        PUT_SEMA(spi_sem[channel])   /* releases semaphore when done */
        return EBADF;
    }
      
#ifdef DEBUG_SPI
    printf("netos spi ioctl \n");
#endif
  
    err_code = onspiIoctl ( pch, request, (int *)args);
  
    PUT_SEMA(spi_sem[channel]) 
    return err_code;
}

