/*
 *  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
 *
 * =======================================================================
 * FIM UART driver
 *
 * Edit Date/Ver   Edit Description
 * ==============  =======================================================
 *
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <errno.h>

#include "netosIo.h"
#include "iop_serial.h"
#include "termios_int.h"
#include "fim_uart.h"
#include "sysClock.h"
#include "NA_ISR.h"
#include "bsp_iop.h"

#define INTERRUPT_SAVE_AREA int interrupt_save
#define DISABLE_INTS        interrupt_save = NASetIRQ(IRQ_DISABLED)
#define RESTORE_INTS        NASetIRQ(interrupt_save)

static iop_serl_port_t *iop_ports[IOP_MAX_UART_PORTS];

#ifdef NETOS_GNU_TOOLS
static unsigned char iop_rx_buf[NA_IOP_MAX_PICS][IOP_RX_BUF_SZ]  __attribute__ ((aligned(32)));
#else
#pragma alignvar (32)
static unsigned char iop_rx_buf[NA_IOP_MAX_PICS][IOP_RX_BUF_SZ];
#endif

static unsigned char iop_tx_buf[NA_IOP_MAX_PICS][IOP_TX_BUF_SZ];

#define IOP_DMA_BUF_SIZE   (IOP_RX_BUF_SZ / IOP_MAX_DMA_DESCRIPTORS)

/* function returns port structure pointer for valid serial channel */
static iop_serl_port_t * getPortInfo(int ch)
{
    if ((ch >= NA_IOP_PROCESSOR_PIC0) && (ch <= NA_IOP_PROCESSOR_PIC1))
    {
        if (ch < IOP_MAX_UART_PORTS)
        {
            return iop_ports[ch];
        }
    }
    
    return NULL;
}

/* function returns the calculated parity bit */
static unsigned char getParity(unsigned char data, unsigned char mask, int odd)
{
    unsigned char ones = 0; 

    mask = (mask >> 1) + 1;
    do
    {
        ones += (data & mask) ? 1 : 0;
        mask >>= 1;
    } while (mask);

    if (odd)
        return ((ones & 1)^1);
    else
        return (ones & 1);
}

/* function inserts the character in transmit data stream */
static int insert_char(iop_serl_port_t *port, unsigned char ch)
{
    short data = 1;

    /* wait for PIC to process any pending inserted character */
    while (*NA_IOHUB_EXP_getAddress(port->processor, 0) & 0x1);
     
    if (port->tios.c_cflag & CSTOPB)
        data = (data << 1) | 0x1;

    if (port->tios.c_cflag & PARENB)
        data = (data << 1) | getParity(ch, port->mask, port->tios.c_cflag & PARODD);

    data = (data << port->numBits) | (ch & port->mask);

    *NA_IOHUB_CONTROL_getAddress(port->processor, 0) = data & 0xFF;
    *NA_IOHUB_CONTROL_getAddress(port->processor, 1) = (data >> 8) & 0xFF;
    if (naIopInterruptProcessor(port->processor, IOP_INSERT_CHAR) != NASTATUS_SUCCESS)
        return EINVAL;

    return EXIT_SUCCESS;    
}

/* function updates software flow control on transmit side */
static void change_tx_flow(iop_serl_port_t *port)
{
    assert(port != NULL);

    if (port->tios.c_iflag & IXON)
    {
        if (port->status.estat & EV_OPALL)
        {
            /* disable transmit */
            *NA_IOHUB_CONTROL_getAddress(port->processor, 6) &= ~IOP_TX_ENABLE;
        }
        else
        {
            /* enable transmit */
            *NA_IOHUB_CONTROL_getAddress(port->processor, 6) |= IOP_TX_ENABLE;
        }
    }
}

/* function sends VSTART or VSTOP to control rx flow */
static int change_rx_flow(iop_serl_port_t *port)
{
    int ret = EXIT_SUCCESS;
    assert(port != NULL);

    if (port->tios.c_iflag & IXOFF)
    {
        if (port->status.estat & EV_IPALL)
        {
            /* send VSTOP */
            ret = insert_char(port, port->tios.c_cc[VSTOP]);
        }
        else
        {
            /* send VSTART */
            ret = insert_char(port, port->tios.c_cc[VSTART]);
        }
    }

    return ret;
}

/* transmit DMA callback, called when IOP driver completes DMA transfer */
static void fimDmaTxCb(NaIopProcessor_t processor, void * buffer, unsigned length, unsigned result, void * context)
{
    iop_serl_port_t *port = (iop_serl_port_t *)context;

    (void)processor;
    (void)buffer;

    assert(port != NULL);

    if (result != NA_IOP_DMA_SUCCESS)
    {
        port->info.tx_dma_error++;
    }
    else
    {
        if (port->totalBits > 8)
            port->counters.tbytes += length/2;
        else
            port->counters.tbytes += length;

        port->info.tx_out = (port->info.tx_out + length) & IOP_TX_BUF_MASK;

        if ((port->info.tx_out + 2) > IOP_TX_BUF_SZ)
        {
            /* we skipped the tx_in at this stage, so skip here too */
            port->info.tx_out = 0;
        }        
    }
}

/* receive DMA callback, called when IOP driver receives data from FIM */
static void fimDmaRxCb(NaIopProcessor_t processor, void * buffer, unsigned length, unsigned result, void * context)
{
    iop_serl_port_t *port = (iop_serl_port_t *)context;

    (void)processor;

    assert(port != NULL);

    if (result != NA_IOP_DMA_SUCCESS)
    {
        port->info.rx_dma_error++;
        return;
    }
    else
    {
        if (buffer != NULL)
        {
            port->info.rx_len[port->info.rx_in_idx]    = length;
            port->info.rx_offset[port->info.rx_in_idx] = 0;
        }

        
        port->counters.rbytes += length/2;
    
        port->info.rx_in_idx = (port->info.rx_in_idx + 1) & IOP_RX_DESCR_MASK;

        if (port->tios.c_iflag & IXOFF)
        {
            if (((port->info.rx_in_idx + 2) & IOP_RX_DESCR_MASK) == port->info.rx_out_idx)
            {
                /* one or two more DMA descriptors are left to fill */
                port->status.estat |= EV_IPS;
                change_rx_flow(port); 
            }
        }
    }
}

/* this callback function is called when IOP driver receives interrupt from FIM */
static void fimIsrCb(NaIopProcessor_t processor, void * context, unsigned interruptCode)
{
    iop_serl_port_t *port = (iop_serl_port_t *)context;

    assert(port != NULL);

    /* handle all the interrupt here */
    switch (interruptCode)
    {
        case IOP_MATCH_CHAR1:
            /* handle character VSTART match here */
            if (port->tios.c_iflag & IXON)
                port->status.estat &= ~EV_OPS;
            change_tx_flow(port);
            break;

        case IOP_MATCH_CHAR2:
            /* handle character VSTOP match here */
            if (port->tios.c_iflag & IXON)
                port->status.estat |= EV_OPS;
            change_tx_flow(port);
            break;

        case IOP_RX_OVERFLOW:
            port->counters.noflow++;
            break;

        default:
            /* unknown error code */
            break;
    }

    /* clear the interrupt */
    *NA_IOHUB_CONTROL_getAddress(processor, 7) = interruptCode;    
}

/* function fills up DMA with provided data */
static int sendData(iop_serl_port_t *port, void *buffer, unsigned length)
{
    NaStatus ccode;
    int ret = EXIT_SUCCESS;

    assert(port != NULL);

    /* send the current buffer we have so far */
    ccode = naIopDmaTransmit(port->processor, buffer, length, fimDmaTxCb, 
                             (void *)port);
    if (ccode != NASTATUS_SUCCESS)
    {
        if (ccode == NASTATUS_IOP_QUEUE_IS_FULL)
            ret = EAGAIN;
        else
        {
            port->info.tx_dma_error++;    
            ret = EINVAL;
        } 
    }
    else
    {
        ret = EXIT_SUCCESS;
    }

    return ret;
} 

/* function adds parity, stop bits to the given buffer and initates the transmision */
static int frameTxData(iop_serl_port_t *port, char *buffer, int length, int *bytesWritten)
{
    struct termios *tp;
    int ret = EXIT_SUCCESS;
    unsigned char *startPtr, *curPtr;
    unsigned short data;

    assert(port != NULL);
    
    tp = &port->tios;

    startPtr = &port->txBuf[port->info.tx_in];
    curPtr   = startPtr;
    *bytesWritten = 0;

    while (length > 0)
    {
        if ((((port->info.tx_in + 1) & IOP_TX_BUF_MASK) == port->info.tx_out) ||
            (((port->info.tx_in + 2) & IOP_TX_BUF_MASK) == port->info.tx_out))
        {
            break;
        }

        if ((port->info.tx_in + 2) > IOP_TX_BUF_SZ)
        {
            /* don't have enough place to send 2 bytes */
            port->info.tx_in = 0;
            break;        
        }

        data = 1;
        if (tp->c_cflag & CSTOPB)
        {
            data = (data << 1) | 0x1;
        } 

        if (tp->c_cflag & PARENB)
        {
            data = (data << 1) | getParity(*buffer, port->mask, tp->c_cflag & PARODD);
        }

        data = (data << port->numBits) | (*buffer & port->mask);
        
        *curPtr++ = data & 0xFF;
        port->info.tx_in = (port->info.tx_in + 1) & IOP_TX_BUF_MASK;

        if (data > 0xFF)
        {
            *curPtr++ = (data >> 8) & 0xFF;
            port->info.tx_in = (port->info.tx_in + 1) & IOP_TX_BUF_MASK;
        }        

        length--;
        buffer++;
        *bytesWritten += 1;
    }

    /* send rest of the data */
    if ((curPtr-startPtr) > 0)
        ret = sendData(port, (void *)startPtr, (unsigned)(curPtr - startPtr));
    else
        ret = EAGAIN;
    
    return ret;
}

/* function checks for the parity and frame errors and extract the data from 
   the received data stream */
static int parseData(iop_serl_port_t *port, char *dest, unsigned char *src, int length, int *bytesCopied)
{
    struct termios *tp;
    unsigned short data;
    int ret = EXIT_SUCCESS;

    assert(port != NULL);
    
    tp = &port->tios;

    *bytesCopied = 0;

    while (length > 1) /* 2 bytes per character */
    {
        if (port->totalBits < 8)
        {
            data = *src >> (8 - port->totalBits);
            src++;
        }
        else
        {
            data = *src++;
            data |= ((unsigned short)*src >> (16 - port->totalBits)) << 8;
        }

        /* ignore VSTART and VSTOP characters if s/w handshake is enabled */
        if (port->tios.c_iflag & IXOFF)
        {
            /* ignore, if it is matching character */
            if (((data & port->mask) == port->tios.c_cc[VSTART]) || 
                ((data & port->mask) == port->tios.c_cc[VSTOP])) 
            {
                src++;
                length -= 2;
                continue;
            }
        }

        *dest = data & port->mask;

        if (tp->c_cflag & PARENB)
        {
            if (((data >> port->numBits) & 0x1) != getParity(*dest, port->mask, tp->c_cflag & PARODD))
            {
                port->counters.nparity++;
                ret = EINVAL;
            }
        }

        if (tp->c_cflag & CSTOPB)
        {
            if (((data >> (port->totalBits - 2)) & 3) != 3)
            {
                port->counters.nframe++;
                ret = EINVAL;
            }
        } 
        else
        {
            if (!((data >> (port->totalBits - 1)) & 1))
            {
                port->counters.nframe++;
                ret = EINVAL;
            }
        }

        src++;
        dest++;
        *bytesCopied += 1;
        length -= 2;
    }

    return ret;
}

/* function extracts the data from DMA descriptors and puts the completed 
   descriptors back in the receive queue */
static int assembleRxData(iop_serl_port_t *port, char *buffer, int length, int *bytesRead)
{
    int ret = EXIT_SUCCESS;
    unsigned char *curPtr;
    int bytesCopied;
    NaStatus ccode;
    int l_len, l_offset;
    INTERRUPT_SAVE_AREA;

    assert(port != NULL);
    
    while ((length > 0) && (ret == EXIT_SUCCESS))
    {
        curPtr = &port->rxBuf[port->info.rx_out_idx*IOP_DMA_BUF_SIZE];
        l_len = (int)port->info.rx_len[port->info.rx_out_idx];
        l_offset = (int)port->info.rx_offset[port->info.rx_out_idx];

        if (l_len == 0)
        {
            if (*bytesRead == 0)
                ret = EAGAIN;
            break; // no more data to read
        }
                
        if (length >= l_len/2)
        {
            /* returns error only for the data error */
            ret = parseData(port, buffer, &curPtr[l_offset], l_len, &bytesCopied); 

            DISABLE_INTS;                        
            port->info.rx_len[port->info.rx_out_idx]    = 0;
            port->info.rx_offset[port->info.rx_out_idx] = 0;                

            // put the buffer back in DMA
            ccode = naIopDmaReceive(port->processor, curPtr, 
                                    IOP_DMA_BUF_SIZE, fimDmaRxCb,
                                    (void *)port);
            if (ccode != NASTATUS_SUCCESS)
            {
                port->info.rx_dma_error++;    
                ret = EINVAL;
            }
            else
            {
                port->info.rx_out_idx = (port->info.rx_out_idx + 1) & IOP_RX_DESCR_MASK;

                /* flow controlled? */
                if ((port->tios.c_iflag & IXOFF) && (port->status.estat & EV_IPS))
                {
                    port->status.estat &= ~EV_IPS;
                    change_rx_flow(port); 
                }
            }
            RESTORE_INTS;
        }
        else
        {
            l_len = length*2;
            ret = parseData(port, buffer, &curPtr[l_offset], l_len, &bytesCopied); 
            
            DISABLE_INTS;                        
            port->info.rx_len[port->info.rx_out_idx]    -= (unsigned)l_len;
            port->info.rx_offset[port->info.rx_out_idx] += (unsigned)l_len;                
            RESTORE_INTS;
        }

        length      -= bytesCopied;
        buffer      += bytesCopied;
        *bytesRead  += bytesCopied;        
    }

    return ret;
}

/* function calculates the clock divisor  and interrupts the FIM to implement
   the change. function also sets the character gap */ 
static int setBaudrate(iop_serl_port_t *port, int baud)
{
    NaStatus ccode;
    unsigned i, timerDiv, preScale, clocksPerBit, charGap;
    
    /* Calculate timer divider */
    clocksPerBit = (NAgetCpuClkFreq()*2)/baud;
    timerDiv     = clocksPerBit/256 + 1;

    /* Must round up to next power of 2 */
    for (i = 1; i <= 8; i++)
    {
        if (timerDiv < (unsigned)(1 << i))
        {
            timerDiv = 1 << i;
            preScale = i - 1;
            break;
        }
    }
    if (i > 8)
        return EINVAL;
        
    *NA_IOHUB_CONTROL_getAddress(port->processor, 0) = (clocksPerBit / timerDiv) + 1;
    ccode = naIopInterruptProcessor(port->processor, IOP_SET_BIT_TIME);
    if (ccode != NASTATUS_SUCCESS)
        return EINVAL;

    /* Setup Character gap timer */
    charGap = (2 * clocksPerBit * port->totalBits) / 30;   /* Char gap to 2 character times */
    *NA_IOHUB_CONTROL_getAddress(port->processor, 0) = (charGap & 0xff) + 1;         /* charGap[7:0]  + 1 */
    *NA_IOHUB_CONTROL_getAddress(port->processor, 1) = ((charGap>>8) & 0xff) + 1;   /* charGap[15:8] + 1 */
    ccode = naIopInterruptProcessor(port->processor, IOP_SET_CHAR_GAP);
    if (ccode != NASTATUS_SUCCESS)
        return EINVAL;

    *NA_IOHUB_CONTROL_getAddress(port->processor, 0) = preScale;
    ccode = naIopInterruptProcessor(port->processor, IOP_SET_TIMER_PRESCALE);
    if (ccode != NASTATUS_SUCCESS)
        return EINVAL;

    return EXIT_SUCCESS;
}

/* function sets the matching characters for VSTART (XON) and VSTOP (XOFF) */
static int setupSwFlowCtrl(iop_serl_port_t *port, int enable, struct termios *tios)
{
    unsigned short match = 1;
    unsigned short mask;


    if (enable)
    {
        if (tios->c_cflag & CSTOPB)
            match = (match << 1) | 1;
        
        if (tios->c_cflag & PARENB)
            match = (match << 1) | getParity(tios->c_cc[VSTART], port->mask, tios->c_cflag & PARODD);
        
        match = (match << port->numBits) | (tios->c_cc[VSTART] & port->mask);            
        mask  =  match | port->mask;

        if (port->totalBits < 8)
        {
            match = match << (8 - port->totalBits);
            mask  = mask << (8 - port->totalBits);
        }
                    
        /* set match character */
        *NA_IOHUB_CONTROL_getAddress(port->processor, 0) = match & 0xFF; /* Match char 1 LSB */
        *NA_IOHUB_CONTROL_getAddress(port->processor, 1) = 0; /* Match char 1 MSB */

        match = 1;
        if (tios->c_cflag & CSTOPB)
            match = (match << 1) | 1;
        
        if (tios->c_cflag & PARENB)
            match = (match << 1) | getParity(tios->c_cc[VSTOP], port->mask, tios->c_cflag & PARODD);
        
        match = (match << port->numBits) | (tios->c_cc[VSTOP] & port->mask);            

        if (port->totalBits < 8)
            match = match << (8 - port->totalBits);
                    
        *NA_IOHUB_CONTROL_getAddress(port->processor, 2) = match & 0xFF; /* Match char 2 LSB */
        *NA_IOHUB_CONTROL_getAddress(port->processor, 3) = 0; /* Match char 2 MSB */

        *NA_IOHUB_CONTROL_getAddress(port->processor, 4) = mask & 0xFF; /* Match Mask LSB */
        *NA_IOHUB_CONTROL_getAddress(port->processor, 5) = 0; /* Match Mask MSB */
    }
    else
    {
        /* disable match character */
        *NA_IOHUB_CONTROL_getAddress(port->processor, 0) = 0xFF; /* Match char 1 LSB */
        *NA_IOHUB_CONTROL_getAddress(port->processor, 1) = 0xFF; /* Match char 1 MSB */
        *NA_IOHUB_CONTROL_getAddress(port->processor, 2) = 0xFF; /* Match char 2 LSB */
        *NA_IOHUB_CONTROL_getAddress(port->processor, 3) = 0xFF; /* Match char 2 MSB */
        *NA_IOHUB_CONTROL_getAddress(port->processor, 4) = 0x00; /* Match Mask LSB */
        *NA_IOHUB_CONTROL_getAddress(port->processor, 5) = 0x00; /* Match Mask MSB */
    }
    
    if (naIopInterruptProcessor(port->processor, IOP_SETUP_MATCH_CHAR) != NASTATUS_SUCCESS)
        return EINVAL;

    return EXIT_SUCCESS;
}

/*
 * Function: iop_serlioctl()
 *
 * Description  This function sets the serial parameters like baudrate, flow 
 *              control, stop bits, character gap
 * returns
 *              EINVAL when channel is not valid
 *              EBADF when channel is not opened
 *              EXIT_SUCCESS for success
 */
int iop_serlioctl(int channel, int request, char *arg)
{
    NaStatus ccode;
    iop_serl_port_t *port;
    int ret = EXIT_SUCCESS;
    struct termios *tiosp;
    struct serial_cntr_t *cntrs;
    enum flow_opts action;
    int force_change = 0;
    INTERRUPT_SAVE_AREA;

    if ((port = getPortInfo(channel)) == NULL)
    {
        ret = EBADF;
        goto done_ioctl;
    }

    switch (request)
    {
        case TCSETA:
            tiosp = (struct termios *)arg;
            assert(tiosp != NULL);
            
            /* need to verify the input here with supported functionality */

            /* set number of bits */
            if ((port->tios.c_cflag & (CSIZE | PARENB | CSTOPB)) != 
                        (tiosp->c_cflag & (CSIZE | PARENB | CSTOPB)))
            {
                port->numBits = 5;

                switch(tiosp->c_cflag & CSIZE) 
                {
                    case CS8:
                        port->numBits++;
                    case CS7:
                        port->numBits++;
                    case CS6:
                        port->numBits++;
                    case CS5:
                        break;
                }

                port->totalBits = port->numBits + 1; /* stop bit */
                port->mask      = (1 << port->numBits) - 1;
                if (tiosp->c_cflag & PARENB)
                    port->totalBits++;
                if (tiosp->c_cflag & CSTOPB)
                    port->totalBits++;

                /* copy this value to control0 and interrupt the FIM */
                *NA_IOHUB_CONTROL_getAddress(port->processor, 0) = port->totalBits;
                ccode = naIopInterruptProcessor(port->processor, IOP_SET_BITS_IN_CHAR);
                if (ccode != NASTATUS_SUCCESS)
                    return EINVAL;
                force_change = 1;                
            }

            /* set hardware handshaking */            
            if ((port->tios.c_cflag & CRTSCTS) != (tiosp->c_cflag & CRTSCTS))
            {
                /*  First setup the bit positions for GPIO based on processor selected */
                *NA_IOHUB_CONTROL_getAddress(port->processor, 2) = 0;   
                *NA_IOHUB_CONTROL_getAddress(port->processor, 3) = 0;

                if (port->processor == NA_IOP_PROCESSOR_PIC1)
                {
                    *NA_IOHUB_CONTROL_getAddress(port->processor, 0) = 1 << BSP_IOP_FIM1_UART_TXD; 
                    *NA_IOHUB_CONTROL_getAddress(port->processor, 1) = 1 << BSP_IOP_FIM1_UART_RXD; 
                    if (tiosp->c_cflag & CRTS) 
                        *NA_IOHUB_CONTROL_getAddress(port->processor, 2) = 1 << BSP_IOP_FIM1_UART_RTS;
                    if (tiosp->c_cflag & CCTS) 
                        *NA_IOHUB_CONTROL_getAddress(port->processor, 3) = 1 << BSP_IOP_FIM1_UART_CTS;
                }
                else
                {
                    *NA_IOHUB_CONTROL_getAddress(port->processor, 0) = 1 << BSP_IOP_FIM0_UART_TXD; 
                    *NA_IOHUB_CONTROL_getAddress(port->processor, 1) = 1 << BSP_IOP_FIM0_UART_RXD; 
                    if (tiosp->c_cflag & CRTS) 
                        *NA_IOHUB_CONTROL_getAddress(port->processor, 2) = 1 << BSP_IOP_FIM0_UART_RTS;
                    if (tiosp->c_cflag & CCTS) 
                        *NA_IOHUB_CONTROL_getAddress(port->processor, 3) = 1 << BSP_IOP_FIM0_UART_CTS;
                }

                ccode = naIopInterruptProcessor(port->processor, IOP_SET_BIT_POSITION);
                if (ccode != NASTATUS_SUCCESS)
                {
                    ret = EINVAL;
                    goto done_ioctl;
                }

                if (tiosp->c_cflag & CRTSCTS)
                    *NA_IOHUB_CONTROL_getAddress(port->processor, 6) |= IOP_EN_HW_FLOW_CNTRL;
                else
                    *NA_IOHUB_CONTROL_getAddress(port->processor, 6) &= ~IOP_EN_HW_FLOW_CNTRL;    
            }

            /* setup software handshake */
            if (!(tiosp->c_cflag & CRTSCTS))
            {
                if (((port->tios.c_iflag & IXON) != (tiosp->c_iflag & IXON)) 
                    || ((tiosp->c_iflag & IXON) && force_change))
                {
                    if ((ret = setupSwFlowCtrl(port, (tiosp->c_iflag & IXON), tiosp)) != EXIT_SUCCESS)
                    {
                        ret = EINVAL;
                        goto done_ioctl;
                    }

                    if (tiosp->c_iflag & IXON)
                        *NA_IOHUB_CONTROL_getAddress(port->processor, 6) |= (IOP_EN_MATCH_CHAR1 | IOP_EN_MATCH_CHAR2);
                    else
                        *NA_IOHUB_CONTROL_getAddress(port->processor, 6) &= ~(IOP_EN_MATCH_CHAR1 | IOP_EN_MATCH_CHAR2);
                }
            }

            if (port->tios.baud != tiosp->baud)
            {
                if ((ret = setBaudrate(port, tiosp->baud)) != EXIT_SUCCESS)
                    goto done_ioctl;
            }

            /* set the rest */
            memcpy(&port->tios, tiosp, sizeof(port->tios));
            break;

        case TCGETA:
            tiosp = (struct termios *)arg;
            assert(tiosp != NULL);
            memcpy(tiosp, &port->tios, sizeof(port->tios));
            break;

        case TCFLOW: 
        {
            action = (enum flow_opts)arg;
            DISABLE_INTS;
            switch (action) 
            {
                case TCOOFF:
                    if (port->status.estat & EV_OPU)
                    { 
                        port->status.estat &= ~EV_OPU;
                        change_tx_flow(port);
                    }
                    break;
    
                case TCOON:
                    if (!(port->status.estat & EV_OPU))
                    {
                        port->status.estat |= EV_OPU;
                        change_tx_flow(port);
                    }
                    break;
    
                case TCIOFF:
                    if (port->status.estat & EV_IPU) 
                    {
                        port->status.estat &= ~EV_IPU;
                        change_rx_flow(port);
                    }
                    break;

                case TCION:
                    if (!(port->status.estat & EV_IPU))
                    {
                        port->status.estat |= EV_IPU;
                        change_rx_flow(port);
                    }
                    break;

                case TCIAOFF: // pattern alarm exit flow
                    if (port->status.estat & EV_IPA) 
                    {
                        port->status.estat &= ~EV_IPA;
                        change_rx_flow(port);
                    }
                    break;

                case TCOSOFF:
                    if (port->status.estat & EV_OPS)
                    {
                        port->status.estat &= ~EV_OPS;
                        change_tx_flow(port);
                    }
                    break;

                case TCOSON:
                    if (!(port->status.estat & EV_OPS))
                    {
                        port->status.estat |= EV_OPS;
                        change_tx_flow(port);
                    }
                    break;
    
                case TCISOFF:
                    if (port->status.estat & EV_IPS) 
                    {
                        port->status.estat &= ~EV_IPS;
                        change_rx_flow(port);
                    }
                    break;
    
                case TCISON:
                    if (!(port->status.estat & EV_IPS))
                    {
                        port->status.estat |= EV_IPS;
                        change_rx_flow(port);
                    }
                    break;

                case TCOXOFF:
                    if (port->status.estat & EV_OPX)
                    {
                        port->status.estat &= ~EV_OPX;
                        change_tx_flow(port);
                    }
                    break;
    
                case TCOXON:
                    if (!(port->status.estat & EV_OPX))
                    {
                        port->status.estat |= EV_OPX;
                        change_tx_flow(port);
                    }
                    break;

                case TCIXOFF:
                case TCIXON:
                    //TODO - all the extra flow states
    
                default:
                    ret = EINVAL;
                    break;
            }
            RESTORE_INTS;                
        }
        break;

        case TCGETCOUNTERS:
            cntrs = (struct serial_cntr_t *)arg;
            memcpy(cntrs, &port->counters, sizeof(struct serial_cntr_t));
	        break;
        
        default:
            break;
    }

    done_ioctl:
        return ret;
}

/* This function should be called form iop_serlopen and not from iop_serlinit.
 * function does the following 
 * 1. Loads the uart program to FIM
 * 2. Registers ISR callback function which is called when ARM receives
 *    interrupt from FIM
 * 3. Sets up GPIO for TX, RX line
 * 4. Configures the baudrate, flow control, bits per char, stop bits
 */
static int setupFim(iop_serl_port_t *port)
{
    NaStatus ccode;
    int ret = EXIT_SUCCESS;
    struct termios tios;

    /*
     * Stop the processor so we can download its program.
     */
    ccode = naIopProcessorStopAndReset(port->processor);
    if (ccode != NASTATUS_SUCCESS)
    {
        ret = EINVAL;
        goto done_setup;
    }

    /*
     * Download the code to the FIM.
     */
    ccode = naIopProcessorDownloadPicProgram(port->processor, &naUartProgram);
    if (ccode != NASTATUS_SUCCESS)
    {
        ret = EINVAL;
        goto done_setup;
    }

    /*
     * Register ISR callback routine.
     */
    ccode = naIopRegisterIsrCallback(port->processor, fimIsrCb, (void *)port);
    if (ccode != NASTATUS_SUCCESS)
    {
        ret = EINVAL;
        goto done_setup;
    }

    /* Disable everything until get going */
    *NA_IOHUB_CONTROL_getAddress(port->processor, 6) = 0;

    /*
     * Start the FIM
     */
    ccode = naIopProcessorStartAtZero(port->processor);
    if (ccode != NASTATUS_SUCCESS)
    {
        ret = EINVAL;
        goto done_setup;
    }

     tx_thread_sleep(2);  /* Let it get going */

    /*---------------------*/
    /* Setup PIC parameter */
    /*---------------------*/
    /*  First setup the bit positions for GPIO */
    if (port->processor == NA_IOP_PROCESSOR_PIC1)
    {
        *NA_IOHUB_CONTROL_getAddress(port->processor, 0) = 1 << BSP_IOP_FIM1_UART_TXD; 
        *NA_IOHUB_CONTROL_getAddress(port->processor, 1) = 1 << BSP_IOP_FIM1_UART_RXD; 
    }
    else
    {
        *NA_IOHUB_CONTROL_getAddress(port->processor, 0) = 1 << BSP_IOP_FIM0_UART_TXD; 
        *NA_IOHUB_CONTROL_getAddress(port->processor, 1) = 1 << BSP_IOP_FIM0_UART_RXD; 
    }
    ccode = naIopInterruptProcessor(port->processor, IOP_SET_BIT_POSITION);
    if (ccode != NASTATUS_SUCCESS)
    {
        ret = EINVAL;
        goto done_setup;
    }

    /* disable match character */
    *NA_IOHUB_CONTROL_getAddress(port->processor, 0) = 0xFF; /* Match char 1 LSB */
    *NA_IOHUB_CONTROL_getAddress(port->processor, 1) = 0xFF; /* Match char 1 MSB */
    *NA_IOHUB_CONTROL_getAddress(port->processor, 2) = 0xFF; /* Match char 2 LSB */
    *NA_IOHUB_CONTROL_getAddress(port->processor, 3) = 0xFF; /* Match char 2 MSB */
    *NA_IOHUB_CONTROL_getAddress(port->processor, 4) = 0x00; /* Match Mask LSB */
    *NA_IOHUB_CONTROL_getAddress(port->processor, 5) = 0x00; /* Match Mask MSB */
    ccode = naIopInterruptProcessor(port->processor, IOP_SETUP_MATCH_CHAR);
    if (ccode != NASTATUS_SUCCESS)
    {
        ret = EINVAL;
        goto done_setup;
    }

    /* set termios config values */
    memcpy(&tios, &port->tios, sizeof(tios));
    /* we need this for all changes to take effect */
    memset(&port->tios, 0, sizeof(tios)); 

    ret = iop_serlioctl((int)port->processor, TCSETA, (char *)&tios);

    done_setup:
    return ret;
}


/*
 *  Function: iop_serlinit
 *
 *  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, based on which FIM
 *                   to use
 *  Return Values:
 *
 *      EXIT_SUCCESS
 *      EINVAL
 *      EBUSY
 */
int iop_serlinit(int channel)
{
    iop_serl_port_t *port = NULL;
    int ret = EXIT_SUCCESS;    
    
    if ((channel < NA_IOP_PROCESSOR_PIC0) || (channel > NA_IOP_PROCESSOR_PIC1))
    {
        ret = EINVAL;
        goto done_init;
    }

    port = (iop_serl_port_t *) malloc(sizeof(iop_serl_port_t));
    if (port == NULL)
    {
        ret = EINVAL;
        goto done_init;
    }

    memset(port, 0, sizeof(iop_serl_port_t));

    iop_ports[channel] = port;
    port->processor = (NaIopProcessor_t)channel;
    
    port->tios.c_iflag = IXON | IXOFF ;
    port->tios.c_cflag = CS8 | CREAD ;
    port->tios.c_cc[VSTART] = XON;
    port->tios.c_cc[VSTOP] = XOFF;
    port->tios.baud = IOP_DEFAULT_BAUDRATE ;

    port->txBuf = iop_tx_buf[channel];
    port->rxBuf = iop_rx_buf[channel];

    memset(port->txBuf, 0, IOP_TX_BUF_SZ);
    memset(port->rxBuf, 0, IOP_RX_BUF_SZ);

    tx_mutex_create(&port->txMutex, "IOP Serial Tx Mutex", TX_INHERIT);
    tx_mutex_create(&port->rxMutex, "IOP Serial Rx Mutex", TX_INHERIT);
    
    done_init:
    if ((ret != EXIT_SUCCESS) && (port != NULL))
    {
        free(port);
    }

    return ret;            
}


/*
 *  Function: iop_serlopen()
 *
 *  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_NONBLOCK, O_DMA
 *
 *  Return Values:
 *
 *      EXIT_SUCCESS
 *      EINVAL
 *      EBUSY
 */
int iop_serlopen(int channel, unsigned flags)
{
    NaStatus ccode;
    iop_serl_port_t *port;
    int i, ret = EXIT_SUCCESS; 
     
    if ((port = getPortInfo(channel)) == NULL)
    {
        ret = EINVAL;
        goto done_open;
    }

    if (port->opened)  /* no need to open if its already open */
    {
        ret = EBUSY;
        goto done_open;
    }

    /* Fail if unsupported flags are requested. */
    if (flags & ~(O_ACCMODE|O_DMA|O_NONBLOCK|O_NOFLUSH))
    {
        ret = EINVAL;
        goto done_open;
    }

    /* Only one of these is allowed. */
    if (((flags & O_ACCMODE) != O_RDONLY)
    && ((flags & O_ACCMODE) != O_WRONLY)
        && ((flags & O_ACCMODE) != O_RDWR))
    {
        ret = EINVAL;
        goto done_open;
    }

    /* clear all the info */
    memset(&port->info, 0, sizeof(port->info));

    if ((ret = setupFim(port)) != EXIT_SUCCESS)
        goto done_open;

    /* flush the transmit DMA queue */
    ccode = naIopDmaFlushQueue(port->processor, TRUE);
    if (ccode != NASTATUS_SUCCESS)
    {
        ret = EINVAL;
        goto done_open;
    }

    if (!(flags & O_NOFLUSH))
    {
        /* flush the receive DMA queue */
        ccode = naIopDmaFlushQueue(port->processor, FALSE);
        if (ccode != NASTATUS_SUCCESS)
        {
            ret = EINVAL;
            goto done_open;
        }
    }

    /* setup initial receive DMA buffer descriptors */
    for (i = 0; i < IOP_MAX_DMA_DESCRIPTORS; i++)
    {
        ccode = naIopDmaReceive(port->processor, 
                                (void *)&port->rxBuf[i*IOP_DMA_BUF_SIZE], 
                                IOP_DMA_BUF_SIZE, fimDmaRxCb, (void *)port);
        if (ccode != NASTATUS_SUCCESS)
        {
            ret = EINVAL;
            goto done_open;
        }
    }

    /* all setup, tell PIC init is complete and we are ready to roll */
    *NA_IOHUB_CONTROL_getAddress(port->processor, 6) |= IOP_INIT_COMPLETE | IOP_TX_ENABLE;

    port->mode      = flags;
    port->opened    = 1;

    done_open:
        return ret;
}


/*
 *  Function: iop_serlclose()
 *
 *  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
 */
int iop_serlclose(int channel)
{
    NaStatus ccode;
    iop_serl_port_t *port;
    int ret = EXIT_SUCCESS; 
     
    if ((port = getPortInfo(channel)) == NULL)
        return EINVAL;

    /* wait for all the sent data to transmit */
    while (port->info.tx_dma_error == 0)
    {
        if (port->info.tx_out != port->info.tx_in)
            tx_thread_sleep(2);
        else
            break;
    }

    /*
     * Stop the processor and reset
     */
    ccode = naIopProcessorStopAndReset(port->processor);
    if (ccode != NASTATUS_SUCCESS)
        ret = EINVAL;

    port->opened  = 0;
    return ret;   
}


/*
 * Function: iop_serlread()
 *
 * Description: read data from FIM
 *
 * Called by: DDI read
 *
 * Input : channel - 0 for FIM0, 1 for FIM1
 *         buffer - data to be stored
 *         length - size of data buffer
 *         bytesWritten - store actual size being sent
 *
 * Return: EBADF if channel does not exist
 *         EINVAL
 *         EAGAIN
 *         EXIT_SUCCESS
 *
 */
int iop_serlread(int channel, char * buffer, int size, int *bytesRead)
{
    iop_serl_port_t *port;
    int ret = EXIT_SUCCESS; 
    unsigned wait_option;
     
    if ((port = getPortInfo(channel)) == NULL)
        return EBADF;

    //Obtain tx_semaphore to protect read synchronization
    wait_option = (port->mode & O_NONBLOCK) ? TX_NO_WAIT : TX_WAIT_FOREVER;
    ret = tx_mutex_get(&port->rxMutex, wait_option);
    
    if (ret == TX_NOT_AVAILABLE)
        return EAGAIN;
    else if (ret != TX_SUCCESS)
        return EINVAL;

    *bytesRead = 0;
    do
    {
        ret = assembleRxData(port, buffer, size, bytesRead);
        if (ret == EAGAIN)
        {
            if (wait_option == TX_WAIT_FOREVER)
                tx_thread_sleep(1);
            else
                break;
        }
    }while (ret == EAGAIN);

    tx_mutex_put(&port->rxMutex);
    return ret;
}

/*
 * Function: iop_serlwrite()
 *
 * Description: write data to FIM
 *
 * Called by: DDI write
 *
 * Input : channel - 0 for FIM0, 1 for FIM1
 *         buffer - data to be sent
 *         length - size of data
 *         bytesWritten - store actual size being sent
 *
 * Return: EBADF if channel does not exist
 *         EINVAL
 *         EAGAIN
 *         EXIT_SUCCESS
 *
 * Notes: Even though we're using DMA, in order to maintain the
 * semantics of the write call, we have to do a buffer copy of the
 * written data.  We can take advantage of this to perform slightly
 * more efficient BMD usage by consolidating all buffer data when
 * creating DMA buffers but that will not buy us near enough to offset
 * the copy unfortunately.
 *     
 */
int iop_serlwrite(int channel, char * buffer, int length, int *bytesWritten)
{
    iop_serl_port_t *port;
    int ret = EXIT_SUCCESS; 
    unsigned wait_option;
    int bytes_sent = 0;
     
    if ((port = getPortInfo(channel)) == NULL)
        return EBADF;

    /* obtain tx_semaphore to protect write synchronization */
    wait_option = (port->mode & O_NONBLOCK) ? TX_NO_WAIT : TX_WAIT_FOREVER;
    ret = tx_mutex_get(&port->txMutex, wait_option);

    *bytesWritten = 0;

    if (ret == TX_NOT_AVAILABLE)
        return EAGAIN;
    else if (ret != TX_SUCCESS)
        return EINVAL;

    /* software flow controlled? */
    while ((port->tios.c_iflag & IXON) && (port->status.estat & EV_OPS))
    {
        if (wait_option == TX_WAIT_FOREVER)
            tx_thread_sleep(1);
        else
        {
            tx_mutex_put(&port->txMutex);
            return EAGAIN;
        }
    }

    while (length > 0)
    {
        ret = frameTxData(port, buffer, length, &bytes_sent);
        if (wait_option == TX_WAIT_FOREVER)
        {
            if ((ret == EXIT_SUCCESS) || (ret == EAGAIN))
            {
                if (ret == EXIT_SUCCESS)
                {
                    length -= bytes_sent;
                    *bytesWritten = (*bytesWritten) + bytes_sent;
                    buffer += bytes_sent;
                }

                if (length > 0)
                {
                    tx_thread_sleep(1);
                    continue;
                }
            }
        }
        else
        {
            if (ret == EXIT_SUCCESS)
                *bytesWritten += bytes_sent;
        }
        break;
    }

    tx_mutex_put(&port->txMutex);
    return ret;
}


