#include "sp_serl.h"
#include "assert.h"
#include "errno.h"
#include "tx_api.h"
#include <string.h>
#include "NA_ISR.h"
#include <ddi.h>
#ifdef NDS_SPECIFIC
#include "regs.h"
#include "hw_strapping.h"
#if PLATFORM == amat4
#include "gpio.h"
#endif

#ifdef SERIAL_ACTIVITY_LED_ENABLED
#include "led_driver.h"
#endif
#ifdef SERIAL_4_RX_LEDS_ENABLED
#include "led_driver.h"
#endif
#endif /* NDS_SPECIFIC */

#include "termios.h"
#include "termios_int.h"
#include "serial_help.h"
#include "event_handler.h"
#ifdef NDS_SPECIFIC
#include "alarm_if.hh"
#ifdef PORTBUFFERING_ENABLED
#include "portbuffer.hh"
#endif
#endif /* NDS_SPECIFIC */

#include "netosIo.h"
#ifdef NDS_SPECIFIC
#include "hwtypes.h"
#endif /* NDS_SPECIFIC */

#ifdef NET_OS_SPECIFIC
#include "reg_def.h"

#define MONITOR_STACK_SIZE 0x2000
TX_THREAD serl_monitor_thread;
char serl_monitor_stack[MONITOR_STACK_SIZE];  

extern int serial_channel(ushort major, ushort minor);
#define MAJOR_SP_SERL  0         /*Not used in NETOS */
#define	O_ACCMODE	(O_RDONLY|O_WRONLY|O_RDWR)
#define O_NOFLUSH	0x10000000
unsigned int NAgetCrystalFrequencyForConnectSP (void);
unsigned int NAgetSPStrapType(void);
#endif

/* Function to notify event handling routines of event occurance */
extern void serl_cb_event(int, enum events);

static int serial_isopen_int(int chan);

#define EXIT_SUCCESS	0
#define OK		EXIT_SUCCESS
#define ERROR		(-1)

#if (PLATFORM == amat4 && defined (NDS_SPECIFIC))
static int external_uart_interrupt;
#define SERIAL_INT      external_uart_interrupt
#else
#define SERIAL_INT	PCPC2_INT
#endif

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

#if defined (NDS_SPECIFIC) || defined (NETOS_GNU_TOOLS)
SP_SERL_REGS_T *sp_serl_regs_ptr = &sp_serl_regs;
#else
SP_SERL_REGS_T *sp_serl_regs_ptr = (SP_SERL_REGS_T *)__ghsbegin_sp_serl_regs;
#endif /* defined (NDS_SPECIFIC) || defined (NETOS_GNU_TOOLS) */


//Shadow registers for WR-only registers.  Useful for debugging
static uint16 TRG_rd_shadow, TRG_wr_shadow, FCR_shadow, 
	      EMSR_shadow, FCTR_shadow;

static unsigned int uart_hz;

static int half_duplex;

static struct termios tios_support = {
    SUPPORTED_IFLAGS,
    SUPPORTED_OFLAGS,
    SUPPORTED_CFLAGS,
    SUPPORTED_LFLAGS,
    SUPPORTED_XFLAGS,
    {
	1, /* VSTART */
	1, /* VSTOP */
	1, /* VLNEXT */
	1, /* VXSTART */
	1, /* VXSTOP */
	1, /* VPREDELAY */
	1, /* VPOSTDELAY */
	0, /* VBITGAP */
    },
    1, /* baud */
};

//TODO: Do we leave these as arrays??
static struct termios current_termios[1/*TODO*/];
static struct serial_cntr_t serial_counters[1/*TODO*/];
static struct serial_status_t serial_status[1/*TODO*/];
static struct sigchange_stats_t serial_sigchange[1/*TODO*/];

static unsigned long rx_note[1/*TODO*/];
static unsigned long rx_noted[1/*TODO*/];

//Initialized implicitly to zero
static char rx_data_buf[SP_SERL_BUF_SIZE];
static char tx_data_buf[SP_SERL_BUF_SIZE];

static struct circ_queue rx_buffer = {0, 0, rx_data_buf};
static struct circ_queue tx_buffer = {0, 0, tx_data_buf};

struct serial_info_t {
    int channel;
    int serial_channel; // Overall channel number
    int configured;
    int opened;
    uint32 openflags;
    int scan_rx_data;
    int tcdrain_req;
    
    write_fn_t write_fn;
    
    int chars_per_interval;
    uint16 TRG_wr; // Precomputed to restore value after RTS toggle

    enum brk_states breakstatus;
    int breaktime;

    int ocol;

    TX_TIMER rtsup_timer;
    TX_TIMER rtsdown_timer;

#if NETWORK_STACK == TRECK
    uint32 select_context;
    int checkMask;
#endif
} serial_info;

static TX_MUTEX tx_mutex; //Serializes transmit
static TX_MUTEX rx_mutex; // Serializes receive

static TX_EVENT_FLAGS_GROUP blocking_flags;
static ULONG actual_flags;

#define BRK_BLOCKING	(1 << 0)
#define DRAIN_BLOCKING  (1 << 1)
#define READ_BLOCKING	(1 << 2)
#define WRITE_BLOCKING	(1 << 3)


int sp_serlioctl(int channel, int request, char *args);


 // Given a particular flow control state as expressed in the status varables
// this function will take the actions necessary to enable/disable the 
// transmitter
//
// Called by 
//	serial_monitor
//	sp_int_handler rx handling
//	tcflow processing
static void
change_tx_flow(void) 
{
    INTERRUPT_SAVE_AREA;
    DISABLE_INTS; // Protect estat from other tasks and the serial int
    if ( serial_status[0].estat & EV_OPALL ) {
	// Turn off transmit interrupt
	sp_serl_regs_ptr->IER &= ~TX_EMPTY_INT_EN;
    } else {
	// Turn on transmit interrupt
	sp_serl_regs_ptr->IER |= TX_EMPTY_INT_EN;
    }
    RESTORE_INTS;
}

// Given a particular flow control state as expressed in the status varables
// this function will take the actions necessary to enable/disable the 
// remote transmitter
//
// Called by:
//	sp_int_handler rx handling
//	sp_serlread
//	pattern alarm processing
//	tcflow processing
static void
change_rx_flow(void)
{
    INTERRUPT_SAVE_AREA;
    uint16 MCR;
    if (current_termios[0].c_cflag & CMDMOUT) {
	// Change DTR/RTS accordingly
	DISABLE_INTS; //Protect MCR from other tasks
	MCR = sp_serl_regs_ptr->MCR;
	if (serial_status[0].estat & EV_IPALL) {
	    if (current_termios[0].c_cflag & CDTR) {
		MCR &= ~DTR_OUTPUT;
	    }
	    if (current_termios[0].c_cflag & CRTS) {
		MCR &= ~RTS_OUTPUT;
	    }
	} else {
	    if (current_termios[0].c_cflag & CDTR) {
		MCR |= DTR_OUTPUT;
	    }
	    if (current_termios[0].c_cflag & CRTS) {
		MCR |= RTS_OUTPUT;
	    }
	}
	sp_serl_regs_ptr->MCR = MCR;
	RESTORE_INTS;
    }

    if (current_termios[0].c_iflag & IXOFF) {
	if (serial_status[0].estat & EV_IPALL) {
	    sp_serl_regs_ptr->THR = current_termios[0].c_cc[VSTOP];
	} else {
	    sp_serl_regs_ptr->THR = current_termios[0].c_cc[VSTART];
	}
    }
}

#ifdef NDS_SPECIFIC
#if defined(MG_ENABLED)
#define MG_485 0x04
#define MG_2W  0x40

static int
DipSwitchMode_int(int portnum)
{
    int ret = 0;
    ULONG tmp = 0;

    assert(portnum == 0);
    if (portnum != 0)
	return DIPSWITCH_232;
    
    /* A2 = 485/232, A6 = 2W/4W */
    assert( (*NARM_PORTA & 0x44440000) == 0x00000000); //Dips are inputs

    tmp = *NARM_PORTA;

    if ( (tmp & MG_485) == 0 ) {
	ret |= DIPSWITCH_232;
    }
    
    if ( (tmp & MG_2W) != 0 ) {
	ret |= DIPSWITCH_2W;
    }
    return ret;
}
#elif defined(WiSP_ENABLED)
/*
 * A0 = 485/232
 * A2 = 4W/2W
 * A6 = TERM
 */
#define WISP_485  0x01
#define WISP_2W   0x04
#define WISP_TERM 0x40
#define WISP_SPECIAL (WISP_485 | WISP_2W | WISP_TERM)
static int
DipSwitchMode_int(int portnum)
{
    int ret = 0;
    ULONG tmp = 0;
    
    assert(portnum == 0);
    if (portnum != 0)
	return DIPSWITCH_232;

    if (GetHardwareType() == DIGI_CONNECT_WISP_RS232_2) {
        return DIPSWITCH_232;
    }

    tmp = ReadReg(NARM_PORTA);

#ifdef SERIAL_TERM_ENABLED
    assert( (tmp & 0x45004500) == 0x00000000);
    /* check for the special mode */
    if ( (tmp & WISP_SPECIAL) == (WISP_2W | WISP_TERM))
    {
        /* make 2w/4w and term as output mode and hold them low */
        tmp |= 0x00440000;
        WriteReg(NARM_PORTA, tmp);
        tmp &= ~(WISP_2W | WISP_TERM);
        WriteReg(NARM_PORTA, tmp); 
        return (DIPSWITCH_232 | DIPSWITCH_HD);
    }
    else if (tmp & WISP_485) 
    {
        if ((tmp & 0x00440000) == 0x00440000)
        {
            /* ...back to input mode */
            tmp &= ~0x00440000;
            WriteReg(NARM_PORTA, tmp); 
        }
    }
#else
    assert( (tmp & 0x45454500) == 0x00000000 ); // Dips are inputs
#endif

    if ( (tmp & WISP_485) == 0 ) {
	ret |= DIPSWITCH_232;
    }

    if ( (tmp & WISP_2W) != 0 ) {
	ret |= DIPSWITCH_2W;
    }

    if ( (tmp & WISP_TERM) != 0 ) {
	ret |= DIPSWITCH_TERM;
    }
    return ret;
}
#elif (PLATFORM == amat4 && defined (NDS_SPECIFIC))
#define DIPSWITCH_MASK	  0xF
#define DIPSWITCH_INVERT  (DIPSWITCH_232|DIPSWITCH_HD)
#define AMAT_RS232        0x01
#define AMAT_RS485_2W     0x04
#define AMAT_RS485_TERM   0x08
#define AMAT_SPECIAL      (AMAT_RS485_2W | AMAT_RS485_TERM)

static int
DipSwitchMode_int(int portnum)
{
    int dipswitch_config = 0;
    int returnConfig = 0;
   
    assert(portnum == 0);
    if (portnum != 0)
        return DIPSWITCH_232;

    if (GpioFunctionExists(GPIO_DIPSWITCH_A1))
    {
        /*somewhere the gpio function changes to zero ! temporary */
        GpioSetInput(GPIO_DIPSWITCH_A1);
        GpioSetInput(GPIO_DIPSWITCH_A4);

        /* Retrieve MEI port configuration from dipswitch */
        dipswitch_config |= (GpioGetValue(GPIO_DIPSWITCH_A8)? 0:1);
        dipswitch_config <<= 1;
        dipswitch_config |= (GpioGetValue(GPIO_DIPSWITCH_A4)? 0:1);
        dipswitch_config <<= 2;
        dipswitch_config |= (GpioGetValue(GPIO_DIPSWITCH_A1)? 0:1);

        if (dipswitch_config & AMAT_RS232)
	  returnConfig |=DIPSWITCH_232;
        if (dipswitch_config & AMAT_RS485_2W)
	  returnConfig |=DIPSWITCH_2W;
        if (dipswitch_config & AMAT_RS485_TERM)
	  returnConfig |=DIPSWITCH_TERM;                

        return returnConfig;
    }
    else
        return DIPSWITCH_232;
}
#else /* PLATFORM != mg && PLATFORM != wisp && PLATFORM != amat4 */
// Mask out unconnected HD signal
#define DIPSWITCH_MASK		0xd
#define DIPSWITCH_INVERT	(DIPSWITCH_232)
#define SP_485    0x01
#define SP_2W     0x04
#define SP_TERM   0x08
#define SP_SPECIAL  (SP_485 | SP_2W | SP_TERM)

static int
DipSwitchMode_int(int portnum)
{
    ULONG tmp = 0;
    unsigned char hw_type;
    
    assert(portnum == 0);
    if (portnum != 0)
	    return DIPSWITCH_232;

    hw_type = GetHardwareType();    
    tmp = ReadReg(NARM_PORTA);

#ifdef SERIAL_TERM_ENABLED
    assert( (tmp & 0x0F000F00) == 0x00000000);

    /* check for the special mode */
    if ( (tmp & SP_SPECIAL) == (SP_2W | SP_TERM))
    {
        /* make 2w/4w and term as output mode and hold them low */
        tmp |= 0x000C0000; 
        WriteReg(NARM_PORTA, tmp); 
        tmp &= ~(SP_2W | SP_TERM);
        WriteReg(NARM_PORTA, tmp); 
        return (DIPSWITCH_232 | DIPSWITCH_HD);
    }
    else if (hw_type == DIGI_CONNECT_SP_RS232)
    {
        return DIPSWITCH_232;
    }
    else if (hw_type == DIGI_CONNECT_SP_RS232_2)
    {
        return DIPSWITCH_232;
    }
    else if (tmp & SP_485) 
    {
        if ((tmp & 0x000C0000) == 0x000C0000)
        {
            /* ...back to input mode */
            tmp &= ~0x000C0000;
            WriteReg(NARM_PORTA, tmp); 
        }
    }
#else
    if (hw_type == DIGI_CONNECT_SP_RS232 || hw_type == DIGI_CONNECT_SP_RS232_2)
    {
        return DIPSWITCH_232;
    }
    assert( (tmp & 0x0F0F0F00) == 0x00000000); //Dips are inputs
#endif
    
    return (tmp & DIPSWITCH_MASK) ^ DIPSWITCH_INVERT;
}
#endif /* PLATFORM != mg */

#else /* NDS_SPECIFIC */
#define DIPSWITCH_232       0x1
#define DIPSWITCH_HD        0x2
#define DIPSWITCH_2W        0x4
#define DIPSWITCH_TERM      0x8
#define DIPSWITCH_MASK      0xf

#define DIPSWITCH_INVERT    (DIPSWITCH_232|DIPSWITCH_HD)

#define NARM_PORTA  NARM_PORTA_ADDR
static int
DipSwitchMode_int(int port)
{
    NA_UNUSED_PARAM(port);
    
    if ((*NARM_PORTA & 0x0f0f0f00) != 0x00000000 ) {
    *NARM_PORTA &= 0xf0f0f000;
    }

    /*
     *  Three flavors of the Connect-SP will now be available, due to the
     *  new clocking requirements of the external UART.  The original version,
     *  NA_CONNECTSP_ORIG_SP is the only SP that uses the old 18432000 HZ  
     *  crystal.  The other two versions, NA_CONNECTSP_NEW_SP and the  
     *  NA_CONNECTSP_NEW_SP_RS232_ONLY, use the new crystal 14745600 HZ.
     *  Additionally, the NA_CONNECTSP_NEW_SP_RS232_ONLY will have the external  
     *  UART specially de-populated.
     *
     *  When we detect the NA_CONNECTSP_NEW_SP_RS232_ONLY, we want to 
     *  return DIPSWITCH_232.
     */
    if (NAgetSPStrapType () == NA_CONNECTSP_NEW_SP_RS232_ONLY) {
    return DIPSWITCH_232;
    }

    assert( (*NARM_PORTA & 0x0f0f0f00) == 0x00000000); //Dips are inputs
    
    return (*NARM_PORTA & DIPSWITCH_MASK) ^ DIPSWITCH_INVERT;
}
#endif /* NDS_SPECIFIC */
/*
 *  Function: get_uart_frequency()
 *
 *  Description: Returns the uart frequency required for the 
 *  given device.
 *
 */
static unsigned int get_uart_frequency(void)
{
#ifdef NDS_SPECIFIC
    unsigned int uart_freq;

    switch (GetHardwareStrapping())
    {
	case DIGI_STRAPPING_SP_18MHz:
	case DIGI_STRAPPING_WAN_RG_18MHz:
	    uart_freq = UART_18_MHZ;
	    break;

	default:
	    uart_freq = UART_14_MHZ;
	    break;
    }
    
    return uart_freq;
#else
    return BRG_HZ;
#endif
}

/*
 *  Function: sp_serl_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
 *      EINVAL
 *      EBUSY
 */
#ifdef NDS_SPECIFIC
int 
sp_serlenter(int channel)
#else
int 
netos_serlenter(int channel)
#endif
{
    uint16 initial_baud;
    //Driver currently only supports one device
    if (serial_info.configured) {
	return 0;
    }
    /* Configure chip select for external uart. */
#if (PLATFORM == amat4 && defined (NDS_SPECIFIC))

      external_uart_interrupt = GpioSetInterrupt(GPIO_UINT,1,0); 
       
      //Write SM CS2 Memory Base Register  
      WriteReg(0xa0900200, 0x60000000);
      //Write SM CS2 Memory Mask Register 
      WriteReg(0xa0900204, 0xFFF00001);  

      /* Configure Static Chip Select 2 for external UART */
      WriteReg(MC_STATIC_MEM_CONFIG(2),MC_STATIC_MEM_CONFIG_WIDTH_16 | MC_STATIC_MEM_CONFIG_PB);
      
      /*Kumpf Wait write enable */
      WriteRegMaskData(MC_STATIC_MEM_WRITE_ENABLE_DELAY(2),
                  MC_STATIC_MEM_WED_WWEN_MASK,0);

      /*Wait output enable */
      //WriteRegMaskData(MC_STATIC_MEM_OUTPUT_ENABLE_DELAY(2),
      //          MC_STATIC_MEM_OUTPUT_WOEN_MASK,2);

      /*Kumpf Wait output enable */
      WriteRegMaskData(MC_STATIC_MEM_OUTPUT_ENABLE_DELAY(2),
                  MC_STATIC_MEM_OUTPUT_WOEN_MASK,1);

      /* Read delay */
      //WriteRegMaskData(MC_STATIC_MEM_READ_DELAY(2),
      //          MC_STATIC_MEM_RD_WTRD_MASK,9);

      /* Kumpf Read delay */
      WriteRegMaskData(MC_STATIC_MEM_READ_DELAY(2),
                  MC_STATIC_MEM_RD_WTRD_MASK,6);


      /* Page Mode Read delay */
      WriteRegMaskData(MC_STATIC_MEM_PAGEMODE_READ_DELAY(2),
                  MC_STATIC_MEM_RD_WTPG_MASK,2);

      /* Write delay */
      //WriteRegMaskData(MC_STATIC_MEM_WRITE_DELAY(2),
      //          MC_STATIC_MEM_WD_WTWR_MASK,9);

      /* Kumpf Write delay */
      WriteRegMaskData(MC_STATIC_MEM_WRITE_DELAY(2),
                  MC_STATIC_MEM_WD_WTWR_MASK,5);

      /* Turnaround delay */
      //WriteRegMaskData(MC_STATIC_MEM_TURNAROUND_DELAY(2),
      //          MC_STATIC_MEM_T_WTTN_MASK,2);

      /* Kumpf Turnaround delay */
      WriteRegMaskData(MC_STATIC_MEM_TURNAROUND_DELAY(2),
                  MC_STATIC_MEM_T_WTTN_MASK,3);
#elif defined (NDS_SPECIFIC)
    WriteReg(NARM_CSOR4, 0xFFF00000 | 0x500 | 0x40 | NARM_CSOR_BSIZE_32_EXTERNAL_DATA | 0x4);
    WriteReg(NARM_CSORB4, 0x4 | 0x1);
    WriteReg(NARM_CSAR4, 0x10000000 | NARM_CSAR_V);
#endif


    serial_info.channel = channel;
    serial_info.configured = 1;
    uart_hz = get_uart_frequency();

    //Switch to enhanced registers
    sp_serl_regs_ptr->LCR = ENHANCED_REGS;
    sp_serl_regs_ptr->EFR = ENHANCED_BITS_ENABLE;  //Turn on bits for extra func.
    //SPR will be FIFO Levels, we'll use custom triggers
    sp_serl_regs_ptr->FCTR  = FCTR_shadow = SCRATCHPAD_SWAP | TRIGGERS_CUSTOM;
    sp_serl_regs_ptr->XON1  = XON; //Standard XON
    sp_serl_regs_ptr->XOFF1 = XOFF;//Standard XOFF
    
    // Switch to baud divisor registers
    sp_serl_regs_ptr->LCR = BAUD_DIV_EN;

    initial_baud = uart_hz / 16 / 9600;
    sp_serl_regs_ptr->DLL = initial_baud & 0xff; 
    sp_serl_regs_ptr->DLM = (initial_baud & 0xff00) >> 8;

    // Switch to normal register set
    sp_serl_regs_ptr->LCR = 0;
    sp_serl_regs_ptr->IER = RX_RDY_INT_EN; // No interrupts to start

    //Turn on FIFOs
    sp_serl_regs_ptr->FCR = FCR_shadow = 
	FIFO_EN | RX_FIFO_RESET | TX_FIFO_RESET;
    sp_serl_regs_ptr->FCR = FCR_shadow = FIFO_EN;

    // Force prescaler, don't trust CLKSEL value
    sp_serl_regs_ptr->MCR &= ~BRG_PRESCALE;
    sp_serl_regs_ptr->MCR |= PRESCALE_X1;

#if (PLATFORM == amat4 && defined (NDS_SPECIFIC))
    /* This output OP1_OUTPUT needs to be set high all the time.  During
     * RS485 operations, it will enable the circuit.
     */
    sp_serl_regs_ptr->MCR |= AUTO_RS485_OUTPUT;  /* Need 1 always */
#endif

    // Scratchpad is receive FIFO count
    sp_serl_regs_ptr->EMSR = EMSR_shadow = 
	RX_FIFO_MODE;

    // 8, n, 1
    sp_serl_regs_ptr->LCR = WORD_LEN(8);

#ifdef NDS_SPECIFIC
    switch(GetHardwareType()) {
    case DIGI_CONNECT_WISP_RS232_2:
    case DIGI_CONNECT_WISP_MEI_2:
    case DIGI_CONNECT_SP_RS232_2:
    case DIGI_CONNECT_SP_MEI_2:
        // turn on 232 enable for the second serial port.  This may become 
        // configurable in the future
        //TODO: Make this configurable
        sp_serl_regs_ptr->MCR |= OP2_OUTPUT;
        break;
    }
#endif
    return 0;
}


// NEVER CALL THIS FROM ANYWHERE OTHER THAN THE INTERRUPT
static inline void
addchar(uint8 ch)
{
    if ( ((rx_buffer.in + 1) & SP_BUF_MASK) == rx_buffer.out ) {
	serial_counters[0].noflow++;
    } else {
	rx_buffer.buf[rx_buffer.in] = ch;
#ifdef NDS_SPECIFIC
	if((patternAlarmEnabled != NULL) &&
	   (patternAlarmEnabled[serial_info.serial_channel]==PATTERN_ALARM_ENABLED)) {
	    int alarmret = PM_FLOW_CONTROL_OFF;
            if (serial_info.serial_channel >= 0 
                && serial_info.serial_channel < max_serial_ports())
            {
                alarmret = patternAlarmNewData(serial_info.serial_channel, 
                                               &ch, 
                                               1);
            }
	    if (alarmret > PM_FLOW_CONTROL_OFF) {
		//TODO: Protect estat - only called from interrupt
		serial_status[0].estat |= EV_IPA;
		change_rx_flow();
	    }
	}
#endif /* NDS_SPECIFIC */
	rx_buffer.in = (rx_buffer.in + 1) & SP_BUF_MASK;
	serial_counters[0].rbytes++;
	rx_note[0]++;
    }
}

static volatile uint16 curchar;

static void 
sp_int_handler(void)
{
    uint16 int_src = sp_serl_regs_ptr->ISR & INT_SRC_MASK;
    static int rxlnext = 0;
    uint8 lsr = 0;
    uint8 dropchar = 0;
    uint16 error_compare = 0;


#if (PLATFORM == amat4 && defined (NDS_SPECIFIC))
    //clear interrupt generated by the edge detect circuit
    WriteReg(0xa0900220,0x4 );
    WriteReg(0xa0900220,0x0 );
#endif

    switch(int_src) {
    case CTS_RTS_DELTA:
    case MSR_INT:
    case LSR_INT:
    case NO_INT:
	// No defined behavior yet
	break;
    case TXRDY: { //Transmit data from buffers
	int txcount = serial_info.chars_per_interval;
	while (txcount && (tx_buffer.in != tx_buffer.out) ) {
	    sp_serl_regs_ptr->THR = tx_buffer.buf[tx_buffer.out];
	    tx_buffer.out = ((tx_buffer.out + 1) & SP_BUF_MASK);
	    serial_counters[0].tbytes++;
	    txcount--;
	}

	// If tx_buffer is empty, turn off transmit interrupt enable
	if (tx_buffer.in == tx_buffer.out) {
	    if ( (current_termios[0].c_cflag & CRTSTOGGLE) == 0) {
		//IER protected by being in interrupt
		sp_serl_regs_ptr->IER &= ~TX_EMPTY_INT_EN;
	    } else if (sp_serl_regs_ptr->LSR & THR_TSR_EMPTY) {
		// TX buffer drained, RTS Toggle on and FIFO and TSR are empty
		if (current_termios[0].c_cc[VPOSTDELAY]) {
		    tx_timer_change(&serial_info.rtsdown_timer, 
				    current_termios[0].c_cc[VPOSTDELAY],
				    0);
		    tx_timer_activate(&serial_info.rtsdown_timer);
		} else {
		    // MCR protected by being in interrupt
		    sp_serl_regs_ptr->MCR &= ~RTS_OUTPUT;
		}
	    }
	}
    }
    break;

    case XOFF_SPEC:
    case RX_DATA_RDY:  //Receive from UART
    case RX_DATA_TIMEOUT: {

#if (PLATFORM == amat4 && defined (NDS_SPECIFIC))
        int level = 1; /* don't use FLVL register. bad value in it */
#else
	int level = (sp_serl_regs_ptr->FLVL & 0xff);
#endif

	

	if (sp_serl_regs_ptr->LSR & OVRN_ERR) {
	    serial_counters[0].norun++;
	    lsr |= OVRN_ERR;
	}
#ifdef NDS_SPECIFIC
	for (level; level > 0 ; level--) {
#else
	for (level = (sp_serl_regs_ptr->FLVL & 0xff) ; level > 0 ; level--) {
#endif
	    uint16 ch = sp_serl_regs_ptr->RHR;
	    curchar = ch;
	    
	    //FWIW, Could precompute error_compare during config
	    if (current_termios[0].c_iflag & INPCK) {
		error_compare = FRAMING_ERR | PARITY_ERR | OVRN_ERR;
	    } else {
		error_compare = FRAMING_ERR | OVRN_ERR;
	    }

	    lsr = ( ((ch >> 8) & ~OVRN_ERR) | lsr) & error_compare;
	    ch &= 0xff;
	    curchar = ch;

	    if (current_termios[0].c_iflag & ISTRIP) {
		ch &= 0x7f;
	    }

	    //Check for errors
	    if (lsr & BREAK_ERR) {
		serial_counters[0].nbreak++;
		//TODO: Q? We may need to move this call out of the interrupt
		// somehow
		serl_cb_event(serial_info.serial_channel, RXB_EVENT);
		if (current_termios[0].c_iflag & IGNBRK) {
		    // Ignore the break
		    dropchar = 1;
		} else if (!(current_termios[0].c_iflag & PARMRK) ) {
		    // Break becomes 00
		    ch = 0;
		} else {
		    // Encode break FF <err> 00
		    if ( ((rx_buffer.in - rx_buffer.out) & SP_BUF_MASK) 
			 < (SP_SERL_BUF_SIZE - 4) ) 
		    {
			addchar(0xff);
			addchar( current_termios[0].c_iflag & DOSMODE
				 ? 0x10
				 : 0x00 );
		    } else {
			//Can't fit the full notice in the buffer
			serial_counters[0].noflow++;
			dropchar = 1;
		    }
		}

	    } else if (lsr) {
		if (lsr & FRAMING_ERR) {
		    serial_counters[0].nframe++;
		}
		if (lsr & PARITY_ERR) {
		    serial_counters[0].nparity++;
		}

		if (current_termios[0].c_xflag & XEDATA) {
		    // If XEDATA, char is left as is
		    lsr = 0;
		} else if (current_termios[0].c_iflag & IGNPAR) {
		    // if !XEDATA && IGNPAR, drop character
		    dropchar = 1;
		} else if (!(current_termios[0].c_iflag & PARMRK) ) {
		    // if !XEDATA && !IGNPAR && !PARMRK, replace with 00
		    ch = 0;
		} else if(((rx_buffer.in - rx_buffer.out) & SP_BUF_MASK) 
			  < (SP_SERL_BUF_SIZE - 4) ) 
		{
		    // if !XEDATA && !IGNPAR && PARMRK
		    // encode as FF 00 xx (!DOSMODE) or FF zz xx (DOSMODE)
		    addchar(0xff);
		    addchar( current_termios[0].c_iflag & DOSMODE
			     ? lsr
			     : 0x00 );
		} else {
		    //Can't fit the full notice in the buffer
		    serial_counters[0].noflow++;
		    dropchar = 1;
		}
	    } else if( (ch == 0xff) //Most likely to fail, so goes first
		       && (current_termios[0].c_iflag & (IGNPAR|PARMRK)) 
		              == PARMRK
		       && (current_termios[0].c_xflag & XEDATA) == 0
		       && (!(current_termios[0].c_iflag & ISTRIP)) ) 
	    {
		addchar(0xff); //Escape the existing 0xff
	    }
	    lsr = 0;  // Lsr processing complete


	    if (serial_info.scan_rx_data && !rxlnext) {
		// If not using hardware-software flow control
		if (current_termios[0].c_cc[VLNEXT]) {
		    // If necessary, Scan for LNEXT
		    rxlnext = 1;
		} 

		if ( (current_termios[0].c_iflag & IXANY) &&
		     (serial_status[0].estat & EV_OPS ) ) 
		{
		    // Resume flow control
		    //TODO: Protect estat - in interrupt now
		    serial_status[0].estat &= ~EV_OPS;
		    change_tx_flow();
		    if (current_termios[0].c_xflag & XTOSS) {
			// If necessary, Scan for XTOSS characters
			//Throw away the character
			dropchar = 1; 
		    }
		} 
		
		if ( current_termios[0].c_iflag & IXON ) {
		    // If IXON, scan for XON2, XOFF2 characters
		    if (ch == current_termios[0].c_cc[VSTART]) {
			if (serial_status[0].estat & EV_OPS) {
			    //TODO: Protect estat - in interrupt now
			    serial_status[0].estat &= ~EV_OPS;
			    change_tx_flow();
                            serl_cb_event(serial_info.serial_channel, 
                                          OPS_EVENT);
			}
			dropchar = 1;
		    } else if (ch == current_termios[0].c_cc[VSTOP] ) {
			if (!(serial_status[0].estat & EV_OPS)) {
			    //TODO: Protect estat - in interrupt now
			    serial_status[0].estat |= EV_OPS;
			    change_tx_flow();
                            serl_cb_event(serial_info.serial_channel, 
                                          OPS_EVENT);
			}
			dropchar = 1;
		    }
		}

		if ( current_termios[0].c_iflag & XIXON ) {
		    if (ch == current_termios[0].c_cc[VXSTART]) {
			if (serial_status[0].estat & EV_OPX) {
			    //TODO: Protect estat - in interrupt now
			    serial_status[0].estat &= ~EV_OPX;
			    change_tx_flow();
                            serl_cb_event(serial_info.serial_channel, 
                                          OPX_EVENT);
			}
			dropchar = 1;
		    } else if (ch == current_termios[0].c_cc[VXSTOP] ) {
			if (!(serial_status[0].estat & EV_OPX) ) {
			    //TODO: Protect estat - in interrupt now
			    serial_status[0].estat &= ~EV_OPX;
			    change_tx_flow();
                            serl_cb_event(serial_info.serial_channel, 
                                          OPX_EVENT);
			}
			dropchar = 1;
		    }
		}
		     
	    } else {
		rxlnext = 0;
	    }

	    if (!dropchar) {
		addchar(ch);
	    }
	}

	if ( ((rx_buffer.in - rx_buffer.out) & SP_BUF_MASK)
	     > (SP_SERL_BUF_SIZE - FLOW_THRESHOLD) ) 
	{
	    //TODO: protect estat - in interrupt now
	    serial_status[0].estat |= EV_IPS;
	    serl_cb_event(serial_info.serial_channel, IPS_EVENT);
	    change_rx_flow();
	}
    }
    break;
    default:
	assert(0);
	break;
    };
}

struct baud_info_t {
    uint16	divisor;
    int		mcr_bit7;
};

struct prescalers_t {
    unsigned int value;
    unsigned int mcr_val;
};

static struct prescalers_t prescalers[] = {
    {1, PRESCALE_X1},
    {4, PRESCALE_X4},
    {0, 0}
};

static int
get_baud_info(speed_t baud, struct baud_info_t *binfo)
{
    int bestPrescaler = 0;
    unsigned int err, bestErr = 0xffffffff;
    uint16 divisor, bestDivisor = 0xffff;
    unsigned int realBaud;
    
    int prescaler_index = 0;

    while(prescalers[prescaler_index].value) {
	divisor = uart_hz / 16 / (prescalers[prescaler_index].value * baud);
	
	// Remainder more than half, round up
	if ( ( (uart_hz / 16) % (prescalers[prescaler_index].value * baud) )
	     > ( prescalers[prescaler_index].value * baud / 2 ) )
	{
	    divisor++;
	}

	realBaud = uart_hz / 16 / (prescalers[prescaler_index].value * divisor);
	if (realBaud > baud) {
	    err = realBaud - baud;
	} else {
	    err = baud - realBaud;
	}

	if (err < bestErr) {
	    bestErr = err;
	    bestPrescaler = prescaler_index;
	    bestDivisor = divisor;
	}

	if (bestErr == 0)
	    break;

	prescaler_index++;
    }

    binfo->divisor = bestDivisor;
    binfo->mcr_bit7 = prescalers[bestPrescaler].mcr_val;
    
    return bestErr * 100 / baud;
}

static int
reconfig_hw(struct termios *tios)
{
    INTERRUPT_SAVE_AREA;
    uint16 LCR = 0, 
	DLL = 0, 
	DLM = 0, 
	TRG_rd = 0, 
	TRG_wr = 0, 
	EFR = ENHANCED_BITS_ENABLE, 
	FCTR = FCTR_shadow,
	//Don't initialize MCR to zero, this would clobber RTS and DTR settings
	MCR = sp_serl_regs_ptr->MCR, 
	XON1 = 0,
	XOFF1 = 0,
	XON2 = 0,
	XOFF2 = 0;
    int word_size = 5;
    int frame_size;
    struct baud_info_t binfo;
    int pct_err;

    switch(tios->c_cflag & CSIZE) {
    case CS8:
	word_size++;
    case CS7:
	word_size++;
    case CS6:
	word_size++;
    case CS5:
	LCR |= WORD_LEN(word_size);
    };

    if (tios->c_cflag & CSTOPB) {
	LCR |= TWO_STOP_BITS;
    }

    if (tios->c_cflag & PARENB) {
	LCR |= PARITY_EN;
    }

    if (tios->c_xflag & XPAR) { /* Mark/space parity? */
	if (tios->c_cflag & PARODD) { 
	    LCR |= MARK_PARITY;
	} else /* !PARODD */ { 
	    LCR |= SPACE_PARITY;
	}
    } else /* !XPAR */ {
	if (tios->c_cflag & PARODD) { 
	    LCR |= ODD_PARITY;
	} else /* !PARODD */ { 
	    LCR |= EVEN_PARITY;
	}
    }

    XON2 = tios->c_cc[VSTART];
    XOFF2 = tios->c_cc[VSTOP];
    XON1 = tios->c_cc[VXSTART];
    XOFF1 = tios->c_cc[VXSTOP];

    // Hardware flow control
    if (tios->c_cflag & CCTS) {
	EFR |= AUTO_CTS_ENABLE;
    }

    // Software flow control
    if ( ((tios->c_iflag & IXANY) && (tios->c_xflag & XTOSS)) ||
	 (tios->c_cc[VLNEXT]) )
    {
	// Software scanning for flow control
	DISABLE_INTS;  //Protect changes to IER from other tasks and the int
	if (tios->c_iflag & IXON) {
	    // Take advantage of XOFF_SPEC interrupt, char match
	    EFR |= SPECIAL_CHAR_SELECT;
	    sp_serl_regs_ptr->IER |= XOFF_INT_EN;
	} else {
	    sp_serl_regs_ptr->IER &= ~XOFF_INT_EN;
	}
	RESTORE_INTS;
	serial_info.scan_rx_data = 1;
    } 
    else 
    {
	serial_info.scan_rx_data = 0;

	// Unless we're specifically scanning for flow characters we don't
	// want this interrupt
	DISABLE_INTS; // Protect IER from other tasks and the int
	sp_serl_regs_ptr->IER &= ~XOFF_INT_EN; 
	RESTORE_INTS;

	// Hardware can do all the flow recognition
	if (tios->c_iflag & IXON) {
	    EFR |= RX_SW_SET2;
	}
	if (tios->c_iflag & XIXON) {
	    EFR |= RX_SW_SET1;
	}
	if (tios->c_iflag & IXANY) {
	    MCR |= XON_ANY_EN;
	}
    }

    // Baud rate calculations
    pct_err = get_baud_info(tios->baud, &binfo);
    
    if (pct_err > 5) {
	return -1; //TODO: Report specific error
    }

    DLL = binfo.divisor & 0xff;
    DLM = (binfo.divisor & 0xff00) >> 8;
    MCR &= ~BRG_PRESCALE;
    MCR |= binfo.mcr_bit7;

    // Baud succeeded, to threshold calculatiosn
    frame_size = word_size + 2 + ((tios->c_cflag & CSTOPB) ? 1 : 0);
    serial_info.chars_per_interval = tios->baud * INTERRUPT_INTERVAL
	/ (frame_size * 1000);
    
    if (serial_info.chars_per_interval == 0)
	serial_info.chars_per_interval = 1;

    // Set RX threshold
    TRG_rd = serial_info.chars_per_interval;

    // Set TX threshold 
    // TX threshold is half RX threshold, but we will
    // fill the FIFO with the full chars_per_interval amount each
    // interrupt
    TRG_wr = serial_info.chars_per_interval / 2;
    if (TRG_wr == 0) TRG_wr = 1;

    // RTS Toggle
    if ( tios->c_cflag & CRTSTOGGLE ) {
	FCTR |= AUTO_RS485;
    } else {
	if (!half_duplex) {
	    FCTR &= ~AUTO_RS485;
	}
    }

    if (!(serial_status[0].estat & EV_IPALL)) {
	if (tios->c_cflag & CDTR) {
	    MCR |= DTR_OUTPUT;
	}
	if (tios->c_cflag & CRTS) {
	    MCR |= RTS_OUTPUT;
	}
    }

    //DISC: For most of these, reconfig_hw is the only place they'll
    //ever be modified.  However, LCR will need protection because it
    //can be changed elsewhere to do breaks and perhaps might be used
    //to flip register sets in order to change thresholds or some such.
    //
    //Anything protected by a register flip using LCR will need to be
    //protected from everything because other code won't know that the
    //registers aren't normal.
    //
    //MCR needs protection so that someone changing modem signals
    //won't critical
    //
    //IER always needs protection because it's modified by the
    //interrupt handler
    DISABLE_INTS;

    // Configure the hardware
    sp_serl_regs_ptr->LCR = ENHANCED_REGS;
    sp_serl_regs_ptr->EFR = 0; // Reset first
    sp_serl_regs_ptr->EFR = EFR;
    sp_serl_regs_ptr->XON1 = XON1;
    sp_serl_regs_ptr->XOFF1 = XOFF1;
    sp_serl_regs_ptr->XON2 = XON2;
    sp_serl_regs_ptr->XOFF2 = XOFF2;
    
    sp_serl_regs_ptr->FCTR = FCTR_shadow = FCTR & ~TRIGGER_SELECT_MASK;
    sp_serl_regs_ptr->FCTR = FCTR_shadow = FCTR | TX_TRIGGER_SELECT;
    sp_serl_regs_ptr->TRG = TRG_wr;
    TRG_wr_shadow = TRG_wr;
    serial_info.TRG_wr = TRG_wr;

    sp_serl_regs_ptr->FCTR = FCTR_shadow = FCTR & ~TRIGGER_SELECT_MASK;
    sp_serl_regs_ptr->FCTR = FCTR_shadow = FCTR | RX_TRIGGER_SELECT;
    sp_serl_regs_ptr->TRG = TRG_rd;
    TRG_rd_shadow = TRG_rd;

    sp_serl_regs_ptr->LCR = BAUD_DIV_EN;
    sp_serl_regs_ptr->DLL = DLL;
    sp_serl_regs_ptr->DLM = DLM;

    sp_serl_regs_ptr->LCR = LCR;
    sp_serl_regs_ptr->MCR = MCR;

    RESTORE_INTS;
    return 0; // Success
}


#if (PLATFORM == amat4 && defined (NDS_SPECIFIC))
static void
poll_dips(void)
{
    INTERRUPT_SAVE_AREA;
    static int last_poll = -1;	/* Force this to configure the correct mode
				 * the first time poll_dips is called.
				 */
    int poll = DipSwitchMode_int(0);
    uint16 LCR;

    if (last_poll == -1)
        last_poll = ~poll; // Trigger a full reconfiguration

    // dipswitch settings changed ?
    if ( poll ^ last_poll ) 
    { 
        //is it a RS485->RS485 reconfiguration ?
        if (((poll & DIPSWITCH_232) == 0) && ((last_poll & DIPSWITCH_232) == 0))
	{

	    // disable MEI RS485  mode of operation
	    // BillK says this is undesired - it causes the 232 charge pumps to
	    // glitch the power, and it also glitches the output lines.
//	    GpioSetOutputWithValue(GPIO_RS232_ENABLE_3, 1);

	    if ( (current_termios[0].c_cflag & CRTSTOGGLE) == 0) {
                //disable RS485
                DISABLE_INTS;
                LCR = sp_serl_regs_ptr->LCR;
                sp_serl_regs_ptr->LCR = ENHANCED_REGS;
                sp_serl_regs_ptr->FCTR = FCTR_shadow = FCTR_shadow & ~AUTO_RS485;
                sp_serl_regs_ptr->LCR = LCR;
                RESTORE_INTS;             
            }

            //reconfigure 2/4 wires               
            if (poll & DIPSWITCH_2W)
      	       GpioSetOutputWithValue(GPIO_RS485_2W, 1);
	    else
               GpioSetOutputWithValue(GPIO_RS485_2W, 0);

            //reconfigure termination resistors 
            if (poll & DIPSWITCH_TERM)
  	       GpioSetOutputWithValue(GPIO_RS485_TERM, 1);
      	    else
	       GpioSetOutputWithValue(GPIO_RS485_TERM, 0);

	    /* Auto-485 must only be set in 2 Wire modes. */
	    if (poll & DIPSWITCH_2W)
	    {
                half_duplex = 1;
             //re-enable RS485
		DISABLE_INTS;
		LCR = sp_serl_regs_ptr->LCR;
		sp_serl_regs_ptr->LCR = ENHANCED_REGS;
		sp_serl_regs_ptr->FCTR = FCTR_shadow = FCTR_shadow | AUTO_RS485;
		sp_serl_regs_ptr->LCR = LCR;
		RESTORE_INTS;
	    } else {
                half_duplex = 0;
            }

            //reenable  RS485  mode of operation
//            GpioSetOutputWithValue(GPIO_RS232_ENABLE_3, 0);

        }  
        //is it a RS232->RS485 reconfiguration ?
        else if  (((poll & DIPSWITCH_232) == 0) && ((last_poll & DIPSWITCH_232) == 1))
	{
	    //configure 2/4 wires
            if (poll & DIPSWITCH_2W)
      	       GpioSetOutputWithValue(GPIO_RS485_2W, 1);
	    else
               GpioSetOutputWithValue(GPIO_RS485_2W, 0);
               
            //configure termination resistors
            if (poll & DIPSWITCH_TERM)
  	       GpioSetOutputWithValue(GPIO_RS485_TERM, 1);
      	    else
	       GpioSetOutputWithValue(GPIO_RS485_TERM, 0);

	    //enable RS485 mode of operation
            GpioSetOutputWithValue(GPIO_RS232_ENABLE_3, 0);


	    /* Auto-485 must only be set in 2 Wire modes. */
	    if (poll & DIPSWITCH_2W)
	    {
                half_duplex = 1;
		//enable RS485
		DISABLE_INTS;
		LCR = sp_serl_regs_ptr->LCR;
		sp_serl_regs_ptr->LCR = ENHANCED_REGS;
		sp_serl_regs_ptr->FCTR = FCTR_shadow = FCTR_shadow | AUTO_RS485;
		sp_serl_regs_ptr->LCR = LCR;
		RESTORE_INTS;
	    } else {
                half_duplex = 0;
            }
	}
        //is it a RS485->RS232 reconfiguration ?
        else if  (((poll & DIPSWITCH_232) == 1) && ((last_poll & DIPSWITCH_232) == 0))
	{
            half_duplex = 0;

            //re-enable RS232 mode of operation
            GpioSetOutputWithValue(GPIO_RS232_ENABLE_3, 1);

	    //disable RS485
            //RTS Toggle uses the TXEMPTY side effect of AUTO-485, make
	    //sure we're not walking over that possible use
	    if ( (current_termios[0].c_cflag & CRTSTOGGLE) == 0) 
            {
		 DISABLE_INTS;
		 LCR = sp_serl_regs_ptr->LCR;
		 sp_serl_regs_ptr->LCR = ENHANCED_REGS;
		 sp_serl_regs_ptr->FCTR = FCTR_shadow = FCTR_shadow & ~AUTO_RS485;
		 sp_serl_regs_ptr->LCR = LCR;
		 RESTORE_INTS;
	    }
           
            // reset to inputs during RS232 operation 
            GpioSetInput(GPIO_RS485_2W);
            GpioSetInput(GPIO_RS485_TERM);
        }
    }

    last_poll = poll;
}
#else
static void
poll_dips(void)
{
    INTERRUPT_SAVE_AREA;
    static int last_poll = DIPSWITCH_232;
    int poll = DipSwitchMode_int(0);
    uint16 LCR;

    if ( poll ^ last_poll ) { // Change since last time
	    
	// 2-wire, differential mode
	if ( ((poll & (DIPSWITCH_232 | DIPSWITCH_2W)) 
	      == DIPSWITCH_2W) 
	     && (half_duplex == 0) ) 
	{
	    // Turn on Auto-485 mode
	    DISABLE_INTS;
	    LCR = sp_serl_regs_ptr->LCR;
	    sp_serl_regs_ptr->LCR = ENHANCED_REGS;
	    sp_serl_regs_ptr->FCTR = FCTR_shadow = FCTR_shadow | AUTO_RS485;
	    sp_serl_regs_ptr->LCR = LCR;
	    RESTORE_INTS;

	    half_duplex = 1;
	} else if ( half_duplex ) // Was 2-wire diff. mode
	{
	    //RTS Toggle uses the TXEMPTY side effect of AUTO-485, make
	    //sure we're not walking over that possible use
	    if ( (current_termios[0].c_cflag & CRTSTOGGLE) == 0) {
		// Turn off Auto-485 mode
		DISABLE_INTS;
		LCR = sp_serl_regs_ptr->LCR;
		sp_serl_regs_ptr->LCR = ENHANCED_REGS;
		sp_serl_regs_ptr->FCTR = FCTR_shadow = FCTR_shadow & ~AUTO_RS485;
		sp_serl_regs_ptr->LCR = LCR;
		RESTORE_INTS;
	    }

	    half_duplex = 0;
	} // end 2-wire differential mode change.

#ifdef NDS_SPECIFIC
    // check if serial CLI required? (switch mode - ON-ON-OFF-OFF)
#ifndef MG_ENABLED
#ifdef SERIAL_TERM_ENABLED
        if ((poll & (DIPSWITCH_232 | DIPSWITCH_HD)) == (DIPSWITCH_232 | DIPSWITCH_HD))
            enable_serial_cli(0);
#endif
#endif

#if defined(SP2PORT_ENABLED)
#define P2_422EN_PORT    NARM_PORTC
#define P2_422EN_PIN     0x01 // pin C0
#elif defined(WISP2PORT_ENABLED)
#define P2_422EN_PORT    NARM_PORTA
#define P2_422EN_PIN     0x02 // pin A1
#endif

#if defined(SP2PORT_ENABLED) || defined(WISP2PORT_ENABLED)
        switch (GetHardwareType()) {
        case DIGI_CONNECT_WISP_RS232_2:
        case DIGI_CONNECT_WISP_MEI_2:
        case DIGI_CONNECT_SP_RS232_2:
        case DIGI_CONNECT_SP_MEI_2:
            if (poll & DIPSWITCH_232) {
                // Make sure OP2 is on for the two port enable
                sp_serl_regs_ptr->MCR |= OP2_OUTPUT;
                // 422p2en off - active low
                WriteRegMaskData(P2_422EN_PORT, 
                                 P2_422EN_PIN, 
                                 ReadReg(P2_422EN_PORT) | P2_422EN_PIN);
            } else {
                // Don't do second port in MEI mode
                sp_serl_regs_ptr->MCR &= ~OP2_OUTPUT;
                // 422p2en on - active low
                WriteRegMaskData(P2_422EN_PORT, 
                                 P2_422EN_PIN, 
                                 ReadReg(P2_422EN_PORT) & ~P2_422EN_PIN);
            }
            break;
        }
#endif
#endif
    }
    last_poll = poll;
}
#endif

static void 
serl_monitor(void) 
{
    INTERRUPT_SAVE_AREA;
    struct serial_status_t status;

    while (1) {
	unsigned char mprev;
	unsigned char mdiff;
	unsigned char mflow;

	mprev = serial_status[0].mstat;

	sp_serlioctl(serial_info.channel, TCGETSTATS, (char *)&status);

	mdiff = mprev ^ status.mstat;

	if (mdiff & MS_DSR) {
	    serial_sigchange[0].dsr_counter++;
	    serl_cb_event(serial_info.serial_channel, DSR_DELTA);
	}
	if (mdiff & MS_DCD) {
	    serial_sigchange[0].dcd_counter++;
	    serl_cb_event(serial_info.serial_channel, DCD_DELTA);
	}
	if (mdiff & MS_RI) {
	    serial_sigchange[0].ri_counter++;
	    serl_cb_event(serial_info.serial_channel, RI_DELTA);
	}
	if (mdiff & MS_CTS) {
	    serial_sigchange[0].cts_counter++;
	    serl_cb_event(serial_info.serial_channel, CTS_DELTA);
	}
	if (mdiff & MS_RTS) {
	    serial_sigchange[0].rts_counter++;
	    serl_cb_event(serial_info.serial_channel, RTS_DELTA);
	}
	if (mdiff & MS_DTR) {
	    serial_sigchange[0].dtr_counter++;
	    serl_cb_event(serial_info.serial_channel, DTR_DELTA);
	}

	//Flow control for incoming sigs other than CTS
	mflow = (current_termios[0].c_cflag & CMDMIN)  >> MFLOW_SHIFT;
	if (status.estat & EV_OPH) {                // We are flow-controlled
	    if ( (mflow & status.mstat) == mflow) { // and don't need to be
		
		DISABLE_INTS; //Protect estat from other tasks and serial int
		serial_status[0].estat &= ~EV_OPH;
		RESTORE_INTS;
		change_tx_flow();
	    }
	} else {                                    //We're not flow controlled
	    if ( (mflow & status.mstat) != mflow) { //and should be
		DISABLE_INTS;
		serial_status[0].estat |= EV_OPH;
		RESTORE_INTS;
		change_tx_flow();
	    }
	}

	if (rx_note[0] != rx_noted[0]) {
	    rx_noted[0] = rx_note[0];
	    serl_cb_event(serial_info.serial_channel, RX_DATA);
#if NETWORK_STACK == TRECK
	    if (serial_info.checkMask & NA_SELECT_READ)
		tfDeviceSelectNotify(NA_SELECT_READ, serial_info.select_context);
#endif
	    // Wake any blocked reads
	    tx_event_flags_set( &blocking_flags, READ_BLOCKING, TX_OR);
#ifdef SERIAL_ACTIVITY_LED_ENABLED
	    led_mark_activity( SERIAL_ACTIVITY_LED0 );
#endif
#ifdef SERIAL_4_RX_LEDS_ENABLED
	    /* NOTE: This is amat4-specific.  The external UART is the 4th
	     * port.
	     */
	    led_mark_activity( SERIAL_ACTIVITY_LED3 );
#endif
	}

	if ( ((tx_buffer.in - tx_buffer.out) & SP_BUF_MASK )
	     < (SP_SERL_BUF_SIZE / 2) ) 
	{
#if NETWORK_STACK == TRECK
	    if (serial_info.checkMask & NA_SELECT_WRITE)
		tfDeviceSelectNotify(NA_SELECT_WRITE, serial_info.select_context);
#endif
	    //Wake any blocked writes
	    tx_event_flags_set( &blocking_flags, WRITE_BLOCKING, TX_OR);
	}

	if (serial_info.breakstatus == BRK_ACTIVE) {
	    serial_info.breaktime -= SERL_POLL_RATE;
	    if (serial_info.breaktime <= 0) {
		DISABLE_INTS; //Protect LCR and estat
		sp_serl_regs_ptr->LCR &= ~TX_BREAK_EN;  
		serial_status[0].estat &= ~EV_TXB;
		RESTORE_INTS;

		serial_info.breaktime = 0;
		serial_info.breakstatus = BRK_IDLE;
		tx_event_flags_set( &blocking_flags, BRK_BLOCKING, TX_OR);
		//TODO: Protect estat- int and others
	    }
	}
	
	if ((sp_serl_regs_ptr->LSR & THR_TSR_EMPTY)
	    && (tx_buffer.in == tx_buffer.out)) 
	{
	    if (serial_info.breakstatus == BRK_REQUESTED) {
		// Start a break
		DISABLE_INTS;
		sp_serl_regs_ptr->LCR |= TX_BREAK_EN; //TODO: Protect LCR?- other
		RESTORE_INTS;

		serial_info.breakstatus = BRK_ACTIVE;
	    }
	    if (serial_info.tcdrain_req) {
		tx_event_flags_set( &blocking_flags, DRAIN_BLOCKING, TX_OR);
		serial_info.tcdrain_req = 0;
	    }
	}
#ifdef SERIAL_ACTIVITY_LED_ENABLED
	else
	{
	    led_mark_activity( SERIAL_ACTIVITY_LED0 );
	}
#endif

	poll_dips();

	//Note: only serl_monitor should change mstat right?
	serial_status[0].mstat = status.mstat;
	tx_thread_sleep(SERL_POLL_RATE); 
    }
}

VOID 
rtsup_fn(ULONG arg __attribute__((unused)) )
{
    INTERRUPT_SAVE_AREA;
    DISABLE_INTS; // Protect estat
    serial_status[0].estat &= ~EV_OPT;
    RESTORE_INTS;
    change_tx_flow();
}

VOID
rtsdown_fn(ULONG arg __attribute__((unused)) )
{
    INTERRUPT_SAVE_AREA;

    if (current_termios[0].c_cflag & CRTSTOGGLE) { // May have been turned off
	DISABLE_INTS;  //Protect MCR
	sp_serl_regs_ptr->MCR &= ~RTS_OUTPUT;
	RESTORE_INTS;
    }
}

/*
 *  Function: sp_serl_init
 *
 *  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
 *      EINVAL
 *      EBUSY
 */
int 
sp_serlinit(int channel)
{
    static int thread_started = 0;
    struct termios tios;
    if (!serial_info.configured || channel != serial_info.channel)
	return EINVAL;

    serial_info.serial_channel = serial_channel(MAJOR_SP_SERL, channel);

    tx_mutex_create(&tx_mutex, "SP Serial Transmit Mutex", TX_INHERIT);
    tx_mutex_create(&rx_mutex, "SP Serial Receive Mutex", TX_INHERIT);

    tx_event_flags_create(&blocking_flags, "Flags for Serial blocking");

    tx_timer_create(&serial_info.rtsup_timer, "RTS Up timer", rtsup_fn, 0, 
		    1,
		    0,
		    TX_NO_ACTIVATE);
    tx_timer_create(&serial_info.rtsdown_timer, "RTS Down timer",rtsdown_fn,0,
		    1,
		    0,
		    TX_NO_ACTIVATE);

    memset( &tios, 0, sizeof(tios) );

#ifndef NET_OS_SPECIFIC
    //netos driver should come up with no flow control
	tios.c_iflag = IXON | IXOFF;
#endif
    tios.c_oflag = 0 ;
    tios.c_cflag = CS8 | CREAD ;
    tios.c_lflag = 0;
    tios.c_cc[VSTART] = XON;
    tios.c_cc[VSTOP] = XOFF;
    tios.baud = 9600 ;

#ifdef NDS_SPECIFIC
    extern void serial_init(int, struct termios *);
    serial_init(serial_info.serial_channel, &tios);
#endif
    sp_serlioctl(channel, TCSETA, (char *) &tios);

    /* Install interrupt */
    NAInstallIsr(SERIAL_INT, (NA_ISR_HANDLER)sp_int_handler, 0);

    /* Start up monitoring thread, for signal deltas etc. */
    /* Only one thread for all ports, serl_monitor() */
    if (!thread_started) {
#ifdef NDS_SPECIFIC
	tx_thread_create(
			 NULL,	//Dynamically allocate thread
			 "Serial Monitor",
			 (VOID (*)(ULONG)) serl_monitor,
			 0,		//dummy
			 NULL,	//dynamically allocate stack
			 4096,	// stack size
			 14,	// arbitrary priority
			 14,	// arbitrary threshold
			 10,	// timeslice
			 TX_AUTO_START );
#else		    
	tx_thread_create(
                             &serl_monitor_thread,
                             "Serial Monitor",
                             (VOID (*)(ULONG)) serl_monitor,
                             0,         //dummy
                             &serl_monitor_stack,
                             MONITOR_STACK_SIZE,        // stack size
                             15,        // arbitrary priority
                             15,        // arbitrary threshold
                             10,        // preemption from inetd_epoch
                             TX_AUTO_START );
#endif	    
	thread_started = 1;
    }

    return 0;
}

/*
 *  Function: sp_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 moew
 *                   of O_RDONLY, O_WRONLY, O_RDWR, O_NONBLOCK
 *
 *  Return Values:
 *
 *      EXIT_SUCCESS
 *      EINVAL
 *      EBUSY
 */

int 
sp_serlopen(int channel, unsigned flags)
{
    if (!serial_info.configured || channel != serial_info.channel)
	return EINVAL;

    if (serial_info.opened)        /* no need to open if its already open */
    {
        return EBUSY;
    }

    /* Fail if unsupported flags are requested. */
    if (flags & ~(O_ACCMODE|O_NONBLOCK|O_NOFLUSH))
    {
	return EINVAL;
    }

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

    serial_info.opened = 1;
    serial_info.openflags = flags;

    if ( !(flags & O_NOFLUSH) ) {
	// Reset transmit and receive buffers
	//TODO: move code to flush routine.
	rx_buffer.out = rx_buffer.in;
	tx_buffer.in = tx_buffer.out;
    }

    return EXIT_SUCCESS;
}

/*
 *  Function: sp_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
 *      EBUSY
 */
int 
sp_serlclose(int channel)
{
    if (!serial_info.configured || channel != serial_info.channel)
	return EBADF;

#ifdef NDS_SPECIFIC
    /* Block for transmit data to drain */
    close_logic(MAJOR_SP_SERL, channel);
#else
    sp_serlioctl(channel, TCDRAIN, 0);
#endif
    serial_info.opened = 0;

    serl_cb_event(serial_info.serial_channel, CLOSE_EVENT);

    return 0;
}

/*
 * Function: sp_serlread()
 *
 * 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 if channel does not exist
 *         EINVAL
 *         EBUSY
 *         EAGAIN
 *         EXIT_SUCCESS
 *
 */
int 
sp_serlread(int channel, char * buffer, int length, int *bytesRead)
{
    INTERRUPT_SAVE_AREA;
    ULONG wait_option;
    int ret;
#ifdef PORTBUFFERING_ENABLED
    char *origbuffer = buffer;
#endif
    if (!serial_info.configured || channel != serial_info.channel)
	return EINVAL;

    //Obtain rx_mutex to protect read synchronization
    wait_option = 
	(serial_info.openflags & O_NONBLOCK) 
	? TX_NO_WAIT
	: TX_WAIT_FOREVER;
    ret = tx_mutex_get(&rx_mutex, wait_option);

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

    ret = 0;
    *bytesRead = 0;
    while (length) {
	int to_end, to_in, to_read;
	
	to_end = SP_SERL_BUF_SIZE - rx_buffer.out;
	to_in = (rx_buffer.in - rx_buffer.out) & SP_BUF_MASK;
	to_read = (to_end > to_in) ? to_in : to_end;
	to_read = (length > to_read) ? to_read : length;

	memcpy(buffer, &rx_buffer.buf[rx_buffer.out], to_read);
	rx_buffer.out = (rx_buffer.out + to_read) & SP_BUF_MASK;
	buffer += to_read;
	*bytesRead += to_read;
	length -= to_read;

	if (rx_buffer.in == rx_buffer.out) {
	    // No more data to read right now
	    if (serial_info.openflags & O_NONBLOCK) {
		ret = (*bytesRead) ? 0 : EAGAIN;
		break;
	    } else {
		// Block
		if (*bytesRead) {
		    break; /* We've read data, no need to block */
		} else {
		    tx_event_flags_get( &blocking_flags, READ_BLOCKING, 
					TX_OR_CLEAR, &actual_flags, 
					TX_WAIT_FOREVER);
		}
	    }
	}
    }
    if ( (((rx_buffer.in - rx_buffer.out) & SP_BUF_MASK)
	 < FLOW_THRESHOLD) && (serial_status[0].estat & EV_IPS) ) 
    {
	DISABLE_INTS; //Protect estat from other tasks and the serial int
	serial_status[0].estat &= ~EV_IPS;
	RESTORE_INTS;
	change_rx_flow();
	serl_cb_event(serial_info.serial_channel, IPS_EVENT);
    }


    tx_mutex_put(&rx_mutex);

#ifdef PORTBUFFERING_ENABLED
    if (*bytesRead) { // Perhaps add a check for PB on here as well?
        if (serial_info.serial_channel >= 0 
            && serial_info.serial_channel < max_serial_ports())
        {
            iPBuff_SaveRxData(serial_info.serial_channel, *bytesRead, origbuffer);
        }
    }
#endif
    return ret;
}

#if 0 /* moved to serial_help.cc */
/* TODO: Parameterize so we can share it with mercury_serl.c
 * parameters:
 *  Buffer size
 *  tx_buffer struct definition and pointer
 *  Buffer mask
 *
 * OPOST additions
 *  Access to termios structure
 *  ocol value
 *  
 */

// Copy buffer data into tx_buffer, return bytes successfully written
static int 
write_raw_data(char *buffer, int length)
{
    int written = 0;
    while (length) {
	int to_end, to_out, to_write;

	to_end = SP_SERL_BUF_SIZE - tx_buffer.in;
	to_out = (tx_buffer.out - tx_buffer.in - 1) & SP_BUF_MASK;
	to_write = (to_end > to_out) ? to_out : to_end;
	to_write = (length > to_write) ? to_write : length;

	memcpy(&tx_buffer.buf[tx_buffer.in], buffer, to_write);
	tx_buffer.in = (tx_buffer.in + to_write) & SP_BUF_MASK;
	buffer += to_write;
	written += to_write;
	length -= to_write;

	if ( ((tx_buffer.in + 1) & SP_BUF_MASK) == tx_buffer.out) {
	    break;
	}
    }

    return written;
}

static inline void
inchar(char ch)
{
    tx_buffer.buf[tx_buffer.in] = ch;
    tx_buffer.in = ((tx_buffer.in + 1) & SP_BUF_MASK);
}

static int
write_everything(char *buffer, int length)
{
    int to_out = (tx_buffer.out - tx_buffer.in - 1) & SP_BUF_MASK;
    if (to_out >= length) {
	write_raw_data(buffer, length, 
		       &tx_buffer, SP_SERL_BUF_SIZE, SP_BUF_MASK);
	return 0;
    }
    return 1;
}

#define CT_CTRL 	0
#define CT_BS   	1
#define CT_TAB  	2
#define CT_NL   	3
#define CT_CR		4
#define CT_PRINT	5

char ct_ojump[256] =
{
    CT_CTRL, CT_CTRL, CT_CTRL, CT_CTRL,
    CT_CTRL, CT_CTRL, CT_CTRL, CT_CTRL,
    CT_BS,   CT_TAB,  CT_NL,   CT_CTRL,
    CT_CTRL, CT_CR,   CT_CTRL, CT_CTRL,
    CT_CTRL, CT_CTRL, CT_CTRL, CT_CTRL,
    CT_CTRL, CT_CTRL, CT_CTRL, CT_CTRL,
    CT_CTRL, CT_CTRL, CT_CTRL, CT_CTRL,
    CT_CTRL, CT_CTRL, CT_CTRL, CT_CTRL,

    CT_PRINT, CT_PRINT, CT_PRINT, CT_PRINT,
    CT_PRINT, CT_PRINT, CT_PRINT, CT_PRINT,
    CT_PRINT, CT_PRINT, CT_PRINT, CT_PRINT,
    CT_PRINT, CT_PRINT, CT_PRINT, CT_PRINT,
    CT_PRINT, CT_PRINT, CT_PRINT, CT_PRINT,
    CT_PRINT, CT_PRINT, CT_PRINT, CT_PRINT,
    CT_PRINT, CT_PRINT, CT_PRINT, CT_PRINT,
    CT_PRINT, CT_PRINT, CT_PRINT, CT_PRINT,

    CT_PRINT, CT_PRINT, CT_PRINT, CT_PRINT,
    CT_PRINT, CT_PRINT, CT_PRINT, CT_PRINT,
    CT_PRINT, CT_PRINT, CT_PRINT, CT_PRINT,
    CT_PRINT, CT_PRINT, CT_PRINT, CT_PRINT,
    CT_PRINT, CT_PRINT, CT_PRINT, CT_PRINT,
    CT_PRINT, CT_PRINT, CT_PRINT, CT_PRINT,
    CT_PRINT, CT_PRINT, CT_PRINT, CT_PRINT,
    CT_PRINT, CT_PRINT, CT_PRINT, CT_PRINT,

    CT_PRINT, CT_PRINT, CT_PRINT, CT_PRINT,
    CT_PRINT, CT_PRINT, CT_PRINT, CT_PRINT,
    CT_PRINT, CT_PRINT, CT_PRINT, CT_PRINT,
    CT_PRINT, CT_PRINT, CT_PRINT, CT_PRINT,
    CT_PRINT, CT_PRINT, CT_PRINT, CT_PRINT,
    CT_PRINT, CT_PRINT, CT_PRINT, CT_PRINT,
    CT_PRINT, CT_PRINT, CT_PRINT, CT_PRINT,
    CT_PRINT, CT_PRINT, CT_PRINT, CT_CTRL,

    CT_CTRL, CT_CTRL, CT_CTRL, CT_CTRL,
    CT_CTRL, CT_CTRL, CT_CTRL, CT_CTRL,
    CT_CTRL, CT_CTRL, CT_CTRL, CT_CTRL,
    CT_CTRL, CT_CTRL, CT_CTRL, CT_CTRL,
    CT_CTRL, CT_CTRL, CT_CTRL, CT_CTRL,
    CT_CTRL, CT_CTRL, CT_CTRL, CT_CTRL,
    CT_CTRL, CT_CTRL, CT_CTRL, CT_CTRL,
    CT_CTRL, CT_CTRL, CT_CTRL, CT_CTRL,

    CT_PRINT, CT_PRINT, CT_PRINT, CT_PRINT,
    CT_PRINT, CT_PRINT, CT_PRINT, CT_PRINT,
    CT_PRINT, CT_PRINT, CT_PRINT, CT_PRINT,
    CT_PRINT, CT_PRINT, CT_PRINT, CT_PRINT,
    CT_PRINT, CT_PRINT, CT_PRINT, CT_PRINT,
    CT_PRINT, CT_PRINT, CT_PRINT, CT_PRINT,
    CT_PRINT, CT_PRINT, CT_PRINT, CT_PRINT,
    CT_PRINT, CT_PRINT, CT_PRINT, CT_PRINT,

    CT_PRINT, CT_PRINT, CT_PRINT, CT_PRINT,
    CT_PRINT, CT_PRINT, CT_PRINT, CT_PRINT,
    CT_PRINT, CT_PRINT, CT_PRINT, CT_PRINT,
    CT_PRINT, CT_PRINT, CT_PRINT, CT_PRINT,
    CT_PRINT, CT_PRINT, CT_PRINT, CT_PRINT,
    CT_PRINT, CT_PRINT, CT_PRINT, CT_PRINT,
    CT_PRINT, CT_PRINT, CT_PRINT, CT_PRINT,
    CT_PRINT, CT_PRINT, CT_PRINT, CT_PRINT,

    CT_PRINT, CT_PRINT, CT_PRINT, CT_PRINT,
    CT_PRINT, CT_PRINT, CT_PRINT, CT_PRINT,
    CT_PRINT, CT_PRINT, CT_PRINT, CT_PRINT,
    CT_PRINT, CT_PRINT, CT_PRINT, CT_PRINT,
    CT_PRINT, CT_PRINT, CT_PRINT, CT_PRINT,
    CT_PRINT, CT_PRINT, CT_PRINT, CT_PRINT,
    CT_PRINT, CT_PRINT, CT_PRINT, CT_PRINT,
    CT_PRINT, CT_PRINT, CT_PRINT, CT_PRINT
};

static int
write_opost_data(char *buf, int buflen)
{
    char *bufstart = buf;
    char *bufend = buf + buflen;
    int tx_space = (tx_buffer.out - tx_buffer.in - 1) & SP_BUF_MASK;
    int ocol = serial_info.ocol;

    while (buf != bufend && tx_space) {
	char c = *buf;
	int writechar = 1;

	switch (ct_ojump[(int)c]) {
	case CT_BS:
	    if (ocol) ocol--;
	    break;
	case CT_TAB: 
	    {
		int nspaces = 1 + ((7 - ocol) & 7);
		if ( (current_termios[0].c_oflag & TABDLY) == TABDLY ) {
		    // Pad w/ spaces to a tabstop of eight
		    if (write_everything("        ", nspaces) != 0) {
			//Unable to write all spaces break the loop
			break;
		    }
		    writechar = 0;
		} else {
		    // Update column to a tabstop of eight
		    ocol += nspaces;
		}
	    }
	    break;
	case CT_NL:
	    if (current_termios[0].c_oflag & ONLRET) {
		ocol = 0;
	    } else if ( (current_termios[0].c_oflag & ONLCR) 
			&& ( (ocol != 0)
			     || !(current_termios[0].c_oflag & ONOCR) ) )
	    {
		if (write_everything("\r\n", 2) != 0) {
		    //Can't add everything, exit loop
		    break;
		}
		ocol = 0;
		writechar = 0;
	    }
	    break;
	case CT_CR:
	    if ( (current_termios[0].c_oflag & ONOCR) 
		 && (ocol == 0) )
	    {
		writechar = 0;
	    } else if (current_termios[0].c_oflag & OCRNL) {
		writechar = 0;
		if (write_everything("\r\n", 2) != 0) {
		    //Can't add everything, exit loop
		    break;
		}
	    }
	    
	    ocol = 0;
	    break;
	case CT_PRINT:
	    ocol++;
	    break;
	case CT_CTRL:
	    /* Do nothing, not printable, no special actions */
	    break;
	default:
	    // all cases should be handled...
	    assert(0);
	};
	
	if (writechar) {
	    tx_buffer.buf[tx_buffer.in] = *buf;
	    tx_buffer.in = (tx_buffer.in + 1) & SP_BUF_MASK;
	}

	buf++;
	tx_space = (tx_buffer.out - tx_buffer.in - 1) & SP_BUF_MASK;
    }
    serial_info.ocol = ocol;

    return buf - bufstart;
}
#endif /* moved to serial_help.cc */

/*
 * Function: sp_serlwrite()
 *
 * 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 if channel does not exist
 *         EINVAL
 *         EBUSY
 *         EAGAIN
 *         EXIT_SUCCESS
 *
 */
int 
sp_serlwrite(int channel, char * buffer, int length, int *bytesWritten)
{
    INTERRUPT_SAVE_AREA;
    ULONG wait_option;
    UINT ret;
    int written = 0;
    if (!serial_info.configured || channel != serial_info.channel)
	return EINVAL;

    //Obtain tx_semaphore to protect write synchronization
    wait_option = 
	(serial_info.openflags & O_NONBLOCK) 
	? TX_NO_WAIT
	: TX_WAIT_FOREVER;
    ret = tx_mutex_get(&tx_mutex, wait_option);

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


    if (serial_info.breakstatus != BRK_IDLE) {
	if (serial_info.openflags & O_NONBLOCK) {
            tx_mutex_put(&tx_mutex);
	    return EAGAIN;
	} else {
	    //Block for break to finish.
	    tx_event_flags_get( &blocking_flags, BRK_BLOCKING, 
				TX_OR_CLEAR, &actual_flags, TX_WAIT_FOREVER);
	}
    }

    // RTS Toggle support
    if (current_termios[0].c_cflag & CRTSTOGGLE) {
	// De-activate any RTS down timer
	tx_timer_deactivate(&serial_info.rtsdown_timer);
	// Raise RTS
	DISABLE_INTS; // Protect MCR 
	sp_serl_regs_ptr->MCR |= RTS_OUTPUT;
	RESTORE_INTS;
	// Wait (optional) pre-delay
	if ((current_termios[0].c_cc[VPREDELAY])
	    && (tx_buffer.in == tx_buffer.out)
	    && (sp_serl_regs_ptr->LSR & THR_TSR_EMPTY)
	    ) 
	{
	    DISABLE_INTS; //Protect estat
	    serial_status[0].estat |= EV_OPT;
	    RESTORE_INTS;
	    change_tx_flow();
	    tx_timer_change(&serial_info.rtsup_timer, 
			    current_termios[0].c_cc[VPREDELAY],
			    0);
	    tx_timer_activate(&serial_info.rtsup_timer);
	}
    }
    
    written += serial_info.write_fn(buffer + written, length - written,
				    &tx_buffer, SP_SERL_BUF_SIZE, SP_BUF_MASK,
				    &serial_info.ocol, &current_termios[0]);
    if ( (serial_status[0].estat & EV_OPALL) == 0) { //Not paused
	DISABLE_INTS;
	sp_serl_regs_ptr->IER |= TX_EMPTY_INT_EN;
	RESTORE_INTS;
    }

    ret = 0;
    while (written != length) {
	if (serial_info.openflags & O_NONBLOCK) {
	    ret = (written == length) ? 0 : EAGAIN;
	    break;
	} else {
	    // Block
	    tx_event_flags_get( &blocking_flags, WRITE_BLOCKING, 
				TX_OR_CLEAR, &actual_flags, TX_WAIT_FOREVER);
	}

	written += serial_info.write_fn(buffer + written, length - written,
					&tx_buffer, SP_SERL_BUF_SIZE, 
					SP_BUF_MASK,
					&serial_info.ocol, 
					&current_termios[0]);
	if ( (serial_status[0].estat & EV_OPALL) == 0) { //Not paused
	    DISABLE_INTS;
	    sp_serl_regs_ptr->IER |= TX_EMPTY_INT_EN;
	    RESTORE_INTS;
	}
    }

    if (bytesWritten)
	*bytesWritten = written;

    tx_mutex_put(&tx_mutex);
    if ( (written == 0) && (ret == EAGAIN) ) {
        return EAGAIN;
    } else {
        return 0;
    }
#ifdef NDS_SPECIFIC
    return ret;
#endif
}

/*
 * Function: sp_serlioctl()
 *
 * Description This function calls onccIoctl to sets the serial parameters
 * returns
 *              EINVAL when channel is not valid
 *              EBADF when channel is not opened
 *              err_code returned from onccIoctl
 */
int 
sp_serlioctl(int channel, int request, char *arg)
{
    INTERRUPT_SAVE_AREA;
    int status;           /* status to return */
    struct termios *termios_p;
    int duration, queue_selector, action, opt;
    int *done;
    struct serial_cntr_t *cntrs;
    struct serial_status_t *serl_status;
    struct sigchange_stats_t *sigchange;
    struct serial_buffer_t *bufs;
    unsigned char mout;
    unsigned char prev;
    int ret;

    if (!serial_info.configured || channel != serial_info.channel)
	return EINVAL;

    status = ENOSYS;

    switch (request) {
    case TCGETA: 
	status = OK;

	termios_p = (struct termios*)arg;
	memcpy(termios_p, &current_termios[0], sizeof(current_termios[0]));
	break; 
    case TCSETA:
	termios_p = (struct termios*)arg;
	status = tcseta(termios_p, &current_termios[0], &tios_support);

	ret = reconfig_hw(&current_termios[0]);
	
	if (ret >= 0 && (status == OK) ) {
	    tx_timer_change(&serial_info.rtsup_timer, 
			    current_termios[0].c_cc[VPREDELAY],
			    0);
	    tx_timer_change(&serial_info.rtsdown_timer, 
			    current_termios[0].c_cc[VPOSTDELAY],
			    0);
	    
	    if (current_termios[0].c_oflag) {
		serial_info.write_fn = write_opost_data;
	    } else {
		serial_info.write_fn = write_raw_data;
	    }
	    status = OK;
	} else {
	    status = EIO;
	}

	break;
    case TCSENDBREAK:
	status = OK; 

	duration = (int)arg;

	serl_cb_event(serial_info.serial_channel, TXB_EVENT);
	DISABLE_INTS;
	serial_status[0].estat |= EV_TXB;
	RESTORE_INTS;
	serial_info.breaktime = duration;
	serial_info.breakstatus = BRK_REQUESTED;
	break;
    case TCDRAIN:
	status = OK;
	serial_info.tcdrain_req = 1;
	ret = tx_event_flags_get( &blocking_flags, DRAIN_BLOCKING, TX_OR_CLEAR,
			    &actual_flags, TX_WAIT_FOREVER);
	assert(ret == TX_SUCCESS);
	break;
    case TCFLUSH:
	status = OK;
	queue_selector = (enum flush_opts)arg;
	if (queue_selector & TCIFLUSH) { /* Throw away input */
	    rx_buffer.out = rx_buffer.in;
	    if (serial_status[0].estat & EV_IPS) {
		DISABLE_INTS;
		serial_status[0].estat &= ~EV_IPS;
		RESTORE_INTS;
		change_rx_flow();
		serl_cb_event(serial_info.serial_channel, IPS_EVENT);
	    }
	} 

	if (queue_selector & TCOFLUSH) { /* Throw away output */
	    tx_buffer.in = tx_buffer.out;
	}

	assert(!(queue_selector & ~TCIOFLUSH ));
	if (queue_selector & ~TCIOFLUSH) {
	    status = EIO;
	}
	break;
    case TCFLOW: {
	unsigned short estat_pre = serial_status[0].estat;
	status = OK; 

	action = (enum flow_opts)arg;
	switch (action) {
	case TCOOFF:
	    DISABLE_INTS;
	    serial_status[0].estat &= ~EV_OPU;
	    RESTORE_INTS;
	    change_tx_flow();

	    if ( (estat_pre ^ serial_status[0].estat) & EV_OPU )
                serl_cb_event(serial_info.serial_channel, OPU_EVENT);
	    break;
	case TCOON:
	    DISABLE_INTS;
	    serial_status[0].estat |= EV_OPU;
	    RESTORE_INTS;
	    change_tx_flow();

	    if ( (estat_pre ^ serial_status[0].estat) & EV_OPU )
                serl_cb_event(serial_info.serial_channel, OPU_EVENT);
	    break;
	case TCIOFF:
	    DISABLE_INTS;
	    serial_status[0].estat &= ~EV_IPU;
	    RESTORE_INTS;
	    change_rx_flow();

	    if ( (estat_pre ^ serial_status[0].estat) & EV_IPU )
                serl_cb_event(serial_info.serial_channel, IPU_EVENT);
	    break;
	case TCION:
	    DISABLE_INTS;
	    serial_status[0].estat |= EV_IPU;
	    RESTORE_INTS;
	    change_rx_flow();

	    if ( (estat_pre ^ serial_status[0].estat) & EV_IPU )
                serl_cb_event(serial_info.serial_channel, IPU_EVENT);
	    break;
        case TCIAOFF: // Pattern alarm exit flow control
	    DISABLE_INTS;
	    serial_status[0].estat &= ~EV_IPA;
	    RESTORE_INTS;
	    change_rx_flow();
            break;
	case TCOSOFF:
	    DISABLE_INTS;
	    serial_status[0].estat &= ~EV_OPS;
	    RESTORE_INTS;
	    change_tx_flow();

	    if ( (estat_pre ^ serial_status[0].estat) & EV_OPS )
                serl_cb_event(serial_info.serial_channel, OPS_EVENT);
	    break;
	case TCOSON:
	    DISABLE_INTS;
	    serial_status[0].estat |= EV_OPS;
	    RESTORE_INTS;
	    change_tx_flow();

	    if ( (estat_pre ^ serial_status[0].estat) & EV_OPS )
                serl_cb_event(serial_info.serial_channel, OPS_EVENT);
	    break;
	case TCISOFF:
	    DISABLE_INTS;
	    serial_status[0].estat &= ~EV_IPS;
	    RESTORE_INTS;
	    change_rx_flow();

	    if ( (estat_pre ^ serial_status[0].estat) & EV_IPS )
                serl_cb_event(serial_info.serial_channel, IPS_EVENT);
            break;
	case TCISON:
	    DISABLE_INTS;
	    serial_status[0].estat |= EV_IPS;
	    RESTORE_INTS;
	    change_rx_flow();

	    if ( (estat_pre ^ serial_status[0].estat) & EV_IPS )
                serl_cb_event(serial_info.serial_channel, IPS_EVENT);
	    break;
	case TCOXOFF:
	    DISABLE_INTS;
	    serial_status[0].estat &= ~EV_OPX;
	    RESTORE_INTS;
	    change_tx_flow();

	    if ( (estat_pre ^ serial_status[0].estat) & EV_OPX )
                serl_cb_event(serial_info.serial_channel, OPX_EVENT);
	    break;
	case TCOXON:
	    DISABLE_INTS;
	    serial_status[0].estat |= EV_OPX;
	    RESTORE_INTS;
	    change_tx_flow();

	    if ( (estat_pre ^ serial_status[0].estat) & EV_OPX )
                serl_cb_event(serial_info.serial_channel, OPX_EVENT);
	    break;
	case TCIXOFF:
	case TCIXON:
	    //TODO - all the extra flow states
	default:
	    status = EIO;
	    break;
	}

	break;
    }
    case TCSENDNOW:
	status = OK;
	serl_cb_event(serial_info.serial_channel, TXI_EVENT);
	/* TODO: As simple as putting the character in the FIFO?? */
	//Cast to uint32 to avoid warning.  Truncation ok
	sp_serl_regs_ptr->THR = (uint32) arg; 
	break;
    case TCGETCOUNTERS:
	status = OK;

	cntrs = (struct serial_cntr_t*)arg;
	memcpy(cntrs, &serial_counters[0], 
	       sizeof(struct serial_cntr_t));
	break;
    case TCGETSTATS:
	status = OK;
	
	serl_status = (struct serial_status_t*)arg;
	serl_status->mstat = 0;

	opt = sp_serl_regs_ptr->MSR;

	if (current_termios[0].c_xflag & XALTPIN) {
	    /* Swap DCD and DSR */
	    if (opt & CD_INPUT)
		serl_status->mstat |= MS_DSR;

	    if (opt & DSR_INPUT)
		serl_status->mstat |= MS_DCD;
	} else {
	    if (opt & CD_INPUT)
		serl_status->mstat |= MS_DCD;

	    if (opt & DSR_INPUT)
		serl_status->mstat |= MS_DSR;
	}

	if (current_termios[0].c_xflag & XFORCEDCD) {
	    serl_status->mstat |= MS_DCD;
	}

	if (opt & RI_INPUT)
	    serl_status->mstat |= MS_RI;
	    
	if (opt & CTS_INPUT )
	    serl_status->mstat |= MS_CTS;

	// TODO: Test RTS/DTR behavior, with Auto-RTS, when we have
	// real hardware
	opt = sp_serl_regs_ptr->MCR; /*SER_REG (0, CTRL_REGA) */

	if (opt & DTR_OUTPUT)
	    serl_status->mstat |= MS_DTR;

	if (opt & RTS_OUTPUT)
	    serl_status->mstat |= MS_RTS;

	serl_status->estat = serial_status[0].estat;

        // 2-port SPs don't currently have DTR and DSR, they are the
        // second ports TXD and RXD.  TODO: Make this a configurable
        // option.  We can still get the user configured DTR from the
        // register, but need to fib about DSR because it will change
        // with the received data.
#ifdef NDS_SPECIFIC
        switch(GetHardwareType()) {
        case DIGI_CONNECT_WISP_RS232_2:
        case DIGI_CONNECT_WISP_MEI_2:
        case DIGI_CONNECT_SP_RS232_2:
        case DIGI_CONNECT_SP_MEI_2:
        
            serl_status->mstat &= ~MS_DSR;
            break;
        }
#endif
	//memcpy(serl_status, &serial_status[0], 
	//	sizeof(struct serial_status_t));
	break;
    case TCGETSIGCHANGE:
	status = OK;

	sigchange = (struct sigchange_stats_t *)arg;
	memcpy(sigchange, &serial_sigchange[0], 
	       sizeof(struct sigchange_stats_t));
	break;
    case TCSETSIGNALS:
	status = OK;

	DISABLE_INTS;
	opt = sp_serl_regs_ptr->MCR;
	prev = 0;

	if (opt & DTR_OUTPUT)
	    prev |= MS_DTR;
	
	if (opt & RTS_OUTPUT)
	    prev |= MS_RTS;

	mout = (uint32)arg;  //Cast to uint32 to avoid warning.  Truncation ok

	if ( !(current_termios[0].c_cflag & CDTR) ) {
	    if (mout & MS_DTR) {
		opt |= DTR_OUTPUT;
	    } else {
		opt &= ~DTR_OUTPUT;
	    }
	}
	if ( !(current_termios[0].c_cflag & CRTS) ||
	     !(current_termios[0].c_cflag & CRTSTOGGLE) ) 
	{
	    if (mout & MS_RTS) {
		opt |= RTS_OUTPUT;
	    } else {
		opt &= ~RTS_OUTPUT;
	    }
	}

	sp_serl_regs_ptr->MCR = opt;
	RESTORE_INTS;

	if ( (prev ^ mout) & MS_DTR ) { /* DTR changed */
	    serial_sigchange[0].dtr_counter++;
	    serl_cb_event(serial_info.serial_channel, DTR_DELTA);

	    //TODO: Protect mstat from serl_monitor?- int and others
	    //Do we need this here if serl_monitor will catch it?
	    serial_status[0].mstat ^= MS_DTR;
	}

	if ( (prev ^ mout) & MS_RTS ) { /* RTS changed */
	    serial_sigchange[0].rts_counter++;
	    serl_cb_event(serial_info.serial_channel, RTS_DELTA);

	    //TODO: Protect mstat from serl_monitor?- int and others
	    //Do we need this here if serl_monitor will catch it?
	    serial_status[0].mstat ^= MS_RTS;
	}

	break;
    case TCGETBUFFERS:
	status = OK;
	bufs = (struct serial_buffer_t *)arg;

	bufs->rxbuf = (rx_buffer.in - rx_buffer.out) & SP_BUF_MASK;
	bufs->txbuf = (tx_buffer.in - tx_buffer.out) & SP_BUF_MASK;
	break;
    case TCTXDONE:
	status = OK;

	done = (int *)arg;
	
	if ( (tx_buffer.in == tx_buffer.out) &&
	     (sp_serl_regs_ptr->LSR & THR_TSR_EMPTY) )
	{
	    *done = 1;
	} 
	else 
	{
	    *done = 0;
	}
	break;
    case ISOPEN:
        *(int*)arg = serial_isopen_int(channel);
        status = OK;
        break;
    case DIPSWITCHMODE:
        *(int*)arg = DipSwitchMode_int(channel);
        status = OK;
        break;
    default:
	assert(0);
	/* Return ENOSYS: unsupported request */
	break;
    }
    return status;
}

#if NETWORK_STACK == TRECK
/*
 * Function: sp_serlpoll()
 *
 * Description This function is called to indicate that a device select is
 * in progress.  It will be called two times.  The first time, context will
 * be non-zero, and checkMask indicates the fd_set bits that are requested.
 * The function must return the bits that the device satisfies.
 * The second time it is called, context is zero.  It must return the checkMask
 * bits that are satisfied. 
 * In the time between the two calls, if any event listed in the checkMask
 * becomes satisfied, the driver must call tfDeviceSelectNotify with the context.
 *
 * returns
 *              EINVAL when channel is not valid
 *              EBADF when channel is not opened
 */
int sp_serlpoll(int channel, int insert_remove, int *checkMask, ulong context)
{
    int ret = 0;
    
    if (!serial_info.configured || channel != serial_info.channel)
	return EINVAL;

    // save checkMask and context.  We only support one select at a time
    // in this driver.
    if (insert_remove == NA_SELECT_INSERT)
    {
	serial_info.select_context = context;
	serial_info.checkMask = *checkMask;
    }
    else
    {
	serial_info.select_context = 0;
	serial_info.checkMask = 0;
    }
    if ((*checkMask & NA_SELECT_READ) 
	    && (rx_buffer.in != rx_buffer.out))
     	    ret |= NA_SELECT_READ; 
    if ((*checkMask & NA_SELECT_WRITE) 
	&& ((tx_buffer.in - tx_buffer.out) & SP_BUF_MASK )
	     < (SP_SERL_BUF_SIZE / 2) ) 
    	    ret |= NA_SELECT_WRITE; 
    // if ((*checkMask & NA_SELECT_EXCEPTION) && device has an exception)
    //	    ret |= NA_SELECT_EXCEPTION; 
    *checkMask = ret;
    return 0;
}
#endif /* NETWORK_STACK == TRECK */

#if 0
/*
 * Function: isatty()
 *
 * Returns: 1 - if the device is an open serial port, 0 - otherwise
 *
 */
int 
isatty(int desc)
{
    extern int NAserialComPortFds[];

    if (desc == NAserialComPortFds[0])
    {
        /*  yes -- it's /com/0   */
        if (serial_info.opened)
        {
            return 1;
        }
    }
    return 0;
}
#endif

#if 0 // moved to serial_help.cc
/*
 * Function: max_serial_ports()
 *
 * Return: Returns the number of serial ports available on the sytem
 *
 */
int 
max_serial_ports(void)
{
    /* The SP only has one port */
    return 1;
}
#endif

/*
 * Function: serial_isopen()
 *
 * Description: Allows the system to query whether a port is being held open
 *
 * Input : Channel number of the port in the system, 0-based
 *
 * Return: 1 - Port is open, 0 - Port is not open
 *
 */
static int
serial_isopen_int(int chan)
{
    if (chan != 0) return 1; // A non-existant port is always open.

    return serial_info.opened;
}

#if 0 // Moved into TCFLOW - TCIAOFF
/* A function provided to allow the pattern alarm functionality to tell us
 * that is now able to continue receiving data
 */
static void
patternAlarmExitFlow_int(int port)
{
    INTERRUPT_SAVE_AREA;
    assert(port == 0); //Currently the SP only has one port
    DISABLE_INTS;
    serial_status[port].estat &= ~EV_IPA;
    RESTORE_INTS;
    change_rx_flow();
}
#endif

#ifdef NET_OS_SPECIFIC
/*
 *  This function is used to sense the Connect-SP board type, and thus
 *  determine the type of crystal connected to the external UART, and
 *  the presence of the MEI circiut.
 *
 *  Three flavors of the Connect-SP will now be available, due to the
 *  new clocking requirements of the external UART.  The original version,
 *  NA_CONNECTSP_ORIG_SP is the only SP that uses the old 18432000 HZ  
 *  crystal.  The other two versions, NA_CONNECTSP_NEW_SP and the  
 *  NA_CONNECTSP_NEW_SP_RS232_ONLY, use the new crystal 14745600 HZ.
 *  Additionally, the NA_CONNECTSP_NEW_SP_RS232_ONLY will have the external  
 *  UART specially de-populated.
 *
 * @category Clock_and_Timer
 * @since 6.2
 */
unsigned int NAgetSPStrapType(void)
{
    return narm_read_reg (NARM_GSSR_REG, NARM_GSSR_ADDR, gen_id1) & 0x7;
}

/*
 *  This function is used to sense the Connect-SP board type, and thus,
 *  return the proper value of crystal.  
 *
 *  Three flavors of the Connect-SP will now be available, due to the
 *  new clocking requirements of the external UART.  The original version,
 *  NA_CONNECTSP_ORIG_SP is the only SP that uses the old 18432000 HZ  
 *  crystal.  The other two versions, NA_CONNECTSP_NEW_SP and the  
 *  NA_CONNECTSP_NEW_SP_RS232_ONLY, use the new crystal 14745600 HZ.
 *  Additionally, the NA_CONNECTSP_NEW_SP_RS232_ONLY will have the external  
 *  MEI circuitry de-populated.
 *
 * @category Clock_and_Timer
 * @since 6.2
 */
unsigned int NAgetCrystalFrequencyForConnectSP (void)
{
    /*if (NetarmInitData[((*NCC_GEN).ssr.bits.rev)].usesInternalOscillator) */
    int boardType = NAgetSPStrapType();
    
	if (boardType == NA_CONNECTSP_ORIG_SP)
    {
        /*  Only the original SP uses the 18 Mhz crystal    */
        return CRYSTAL_FREQUENCY_FOR_ORIG_SP;
    }
    else
    {
        /*  All others use the new crystal  */
        return CRYSTAL_FREQUENCY_FOR_NEW_SP;
    }
}
#endif
