/************************************************************************
 * au1200_serial.c - NDS serial port driver for AMD au1200 serial ports * 
 ************************************************************************
 *																		*
 *	Copyright (c) 2003-2006 Inside Out Networks, Inc.					*
 *	All rights reserved.												*
 *																		*
 ************************************************************************/
//
// 	This driver is roughly based on the mercury serial port driver, mercury_serl.c
//	(version 1.25).
//
//	This DDI serial port driver exports the following driver functions:
//		netos_serlenter
//		netos_serlinit
//		netos_serlopen
//		netos_serlread
//		netos_serlwrite
//		netos_serlioctl
//		netos_serlclose
//		netos_serlpoll
//
//	These are the functions for a serial port of major device code
// 	MAJOR_NETOS_SERL.
//
//	The minor device code represents the port number.  For example, a
//	minor device code of 0 is UART0, 1 for UART1
//
//	Each serial port is mapped to a device name of form "/com/x"
//	where x is a digit (e.g. 0, 1, etc).  The device name to
//	major/minor code mappings are found the "device_names" table
//	in devices.c.
//	
//	As an example, say we had a device name table as shown below:
//		device_names_t device_names[] = {
//		    {"/com/0",		MAJOR_NETOS_SERL,   0},	/* serial port 0 */
//		    {"/com/1",		MAJOR_NETOS_SERL,   1},	/* serial port 1 */
//		}
//
//	When an app. opens "/com/1", the open function searches
//	for that name and finds the "deviceinfo" entry that matches major device code
//	MAJOR_NETOS_SERL and minor device code 0.  It then calls the "device"
//	open function as shown:
//		major_table[MAJOR_NETOS_SERL].open(1, flags);
//
//	DDIFirstLevelInitialize calls netos_serenter.
//	DDISecondLevelInitialize calls netos_serinit.
//

#include "au1200_serl.h"
#include "namacro.h"

#ifdef NET_OS_SPECIFIC
#include "minmax.h"
#endif


#define MONITOR_STACK_SIZE 2048
#define	MONITOR_WAKEUP	(1 << 5)
#define	SERL_POLL_RATE	(NS_MILLISECONDS_TO_TICKS(100))	// 10 ms

//  
// DATA DECLARATIONS
//

TX_EVENT_FLAGS_GROUP    	global_event_flags;
au1200_port_t 				ports[NUM_SERIAL_PORTS];

BOOLEAN USE_AUTOFLOW = 0;

static
void
write_uart_tx_fifo(au1200_port_t * port);

static
void
read_uart_rx_fifo(au1200_port_t * port);

static struct termios tios_support[NUM_SERIAL_PORTS] =
{
		//UART 1 support info
	{
	    UART1_SUPPORTED_IFLAGS,
	    UART1_SUPPORTED_OFLAGS,
	    UART1_SUPPORTED_CFLAGS,
	    UART1_SUPPORTED_LFLAGS,
	    UART1_SUPPORTED_XFLAGS,
	    {
	    1, 					/* VSTART */
	    1, 					/* VSTOP */
	    1, 					/* VLNEXT */
	    1, 					/* VXSTART */
	    1, 					/* VXSTOP */
	    0, 					/* VPREDELAY */
	    0, 					/* VPOSTDELAY */
	    0, 					/* VBITGAP */
		1, 					/* VIDLETIME */
	    },
	    1, /* baud */
	},

		//UART 0 support info
	{
	    UART0_SUPPORTED_IFLAGS,
	    UART0_SUPPORTED_OFLAGS,
	    UART0_SUPPORTED_CFLAGS,
	    UART0_SUPPORTED_LFLAGS,
	    UART0_SUPPORTED_XFLAGS,
	    {
	    1, 					/* VSTART */
	    1, 					/* VSTOP */
	    1, 					/* VLNEXT */
	    1, 					/* VXSTART */
	    1, 					/* VXSTOP */
	    0, 					/* VPREDELAY */
	    0, 					/* VPOSTDELAY */
	    0, 					/* VBITGAP */
	    },
	    1, /* baud */
	}
};




//
// static function prototypes.
//

static int
reconfig_hw(au1200_port_t * port);

static
void
set_auto_flow(au1200_port_t *port);

static
int
set_baud_rate(au1200_port_t * port, ULONG baud);

static
int
au1200_serl_isr(void *isrParameter);

static inline void
addchar(au1200_port_t * port, uint8 c);

static
inline
int
int_fc_scan(au1200_port_t *port, char c);

static
void
handle_line_status(au1200_port_t * port);

static
void
handle_modem_status(au1200_port_t * port);

int
netos_serlioctl(int channel, int request, char *arg);

static void
serl_monitor(void);

static
void
change_tx_flow(au1200_port_t *port);

static
void
change_rx_flow(au1200_port_t *port);

static
void
uart_enable(au1200_port_t *port);


static
void
configure_uart_fifo_ctrl(au1200_port_t *port);

static
VOID
rtsup_fn(ULONG arg);

static
VOID
rtsdown_fn(ULONG arg);

static
inline
int	rx_fifo_read(au1200_port_t * port, char *buffer, int max_len);

static int
serial_isopen_int(int channel);


/*
 *  Function: netos_serlenter
 *
 *  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
 */

// channel 0 is uart1, channel 1 is uart0
int
netos_serlenter(int channel)
{

	UINT	tx_status;

	// todo - is anything needed here that be put in
	// netos_serlinit()?
	SER_TRACE(ZONE_INIT, "+netos_serenter (%u)", channel);

	// Initialize the port structure
    au1200_port_t * port = &ports[channel];

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

	port->channel = channel;

	port->termios_support	= tios_support[channel];
	port->uart				= (channel == 0) ? 1 : 0;

	// Create the transmit and receive fifo's
	TX_FIFO_CREATE(port);
	RX_FIFO_CREATE(port);

	// These are currently unused.
    tx_status = tx_event_flags_create(
					&global_event_flags,
					"Serial Global Event Flags");

	SER_TRACE(ZONE_INIT, "-netos_serenter (%u)", channel);
    return EXIT_SUCCESS;
}

/*
 *  Function: netos_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
 *  Return Values:
 *
 *      EXIT_SUCCESS
 *      EINVAL
 *      EBUSY
 */
int
netos_serlinit(int channel)
{
    static int thread_started = 0;
	UINT	tx_status;

    au1200_port_t * port = &ports[channel];
    struct termios tios;

	uart_enable(port);

	SER_TRACE(ZONE_INIT, "+netos_serlinit (%u)", channel);

	port->serial_channel = channel;

    // Remaining threadx member configuration left over from serlenter
    tx_status = tx_mutex_create(
					&port->tx_mutex,
					"Au1200 Serial Transmit Mutex",
				    TX_INHERIT);

	SER_ASSERT (tx_status == TX_SUCCESS);

	tx_status =	tx_mutex_create(
					&port->rx_mutex,
					"Au1200 Serial Receive Mutex",
				    TX_INHERIT);

	SER_ASSERT (tx_status == TX_SUCCESS);

    tx_status = tx_event_flags_create(
					&port->blocking_flags,
					"Flags for Serial blocking");

	SER_ASSERT (tx_status == TX_SUCCESS);

    tx_status = tx_timer_create(
					&port->rtsup_timer,
					"RTS Up timer",
					rtsup_fn,
					0,
				    1,
				    0,
				    TX_NO_ACTIVATE);

	SER_ASSERT (tx_status == TX_SUCCESS);


    tx_status = tx_timer_create(
					&port->rtsdown_timer,
					"RTS Down timer",
					rtsdown_fn,
					0,
				    1,
				    0,
				    TX_NO_ACTIVATE);

	SER_ASSERT (tx_status == TX_SUCCESS);

    // Initial termios config values
    memset(&tios, 0, sizeof(tios));

    tios.c_iflag = IXON | IXOFF ;
    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 DIGI_SPECIFIC
    // Reconcile with NV termios settings
    extern void serial_init(int, struct termios *);
    serial_init(channel, &tios);
#endif /* DIGI_SPECIFIC */

	configure_uart_fifo_ctrl(port);

    netos_serlioctl(channel, TCSETA, (char *) &tios);

    /* Install interrupt */
    NAInstallIsr( CHANNEL_NUM_TO_INT_ID( channel ), au1200_serl_isr, port );

    // Start monitoring thread
    if (!thread_started) {

		tx_status = tx_thread_create(
						 NULL,
						 "Au1200 Serial Monitor",
						 (VOID (*)(ULONG)) serl_monitor,
						 0,     						//dummy
						 NULL,
						 MONITOR_STACK_SIZE,    		// stack size
						 14,    						// arbitrary priority
						 14,    						// arbitrary threshold
						 10,    						// timeslice
						 TX_AUTO_START );

		SER_ASSERT (tx_status == TX_SUCCESS);

		thread_started = 1;
    }

	SER_TRACE(ZONE_INIT, "-netos_serlinit (%u)", channel);

	port->configured = 1;

    return EXIT_SUCCESS;
}

/*
 *  Function: netos_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
netos_serlopen(int channel, unsigned flags)
{

    au1200_port_t * port = &ports[channel];

	SER_TRACE(ZONE_INIT, "+netos_serlopen (%u)", channel);

    if (!port->configured)
	{
		return EINVAL;
	}

    if (port->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|O_CREAT|O_TRUNC))
    {
		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;
    }

    port->opened = 1;
    port->openflags = flags;
	port->forced_transmit_char = 0;
	port->scan_rx_data = 0;
	port->rx_noted = port->rx_note = port->got_rx = 0;
	port->breakstatus = BRK_IDLE;

	PROTECTED_EVENT_STATUS_CLR(port, 0xffff );

	// Clear counters.
	memset(&port->sigchange, 0, sizeof(port->sigchange));
	memset(&port->counters,  0, sizeof(port->counters));

	configure_uart_fifo_ctrl(port);

    if ( !(flags & O_NOFLUSH) )
	{
		ULONG fifo_ctrl;

		SER_TRACE(ZONE_INIT, "+netos_serlopen (%u) - clearing FIFO's.", channel);

		RX_FIFO_CLEAR(port);
		TX_FIFO_CLEAR(port);

		// Clear UART fifo's.
		fifo_ctrl = UART_FIFOCTRL(port);
		fifo_ctrl |= AU_UART_FIFOCTRL_TR | AU_UART_FIFOCTRL_RR;
		UART_FIFOCTRL(port) = fifo_ctrl;
    }

	// Enable DTR and RTS
	#ifndef DIGI_SPECIFIC
    netos_serlioctl(port->channel, TCSETSIGNALS, (char *)(MS_DTR | MS_RTS));
	#endif

	#ifdef DIGI_SPECIFIC
    if ( !(flags & O_NOFLUSH) )
	#endif /* DIGI_SPECIFIC */
    {
		netos_serlioctl(port->channel, TCFLUSH, (char *)TCIOFLUSH);
    }

	set_auto_flow(port);

	port->uart_mdmstat_shadow = UART_MDMSTAT(port);

	// Enable Line status, receiver and modem status interrupts.
	UART_INTEN(port) = AU_UART_INTEN_LIE  | AU_UART_INTEN_RIE | AU_UART_INTEN_TIE;

	if (UART_SUPPORTS_HARDWARE_FLOW(port))
	{
		UART_INTEN(port) |= AU_UART_INTEN_MIE;
	}

	SER_TRACE(ZONE_INIT, "+netos_serlopen (%u)", channel);

    return EXIT_SUCCESS;
}



/*
 * Function: netos_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
netos_serlioctl(int channel, int request, char *arg)
{
    INTERRUPT_SAVE_AREA;

    au1200_port_t 			   *port = &ports[channel];
    int 						status;           /* status to return */
    int 						queue_selector, action;
    int 					   *done;
    struct serial_status_t 	   *serl_status;
    struct serial_buffer_t 	   *bufs;
    unsigned char 				mout;
    unsigned char 				prev;
    int 						ret;
    uint32 						opt;
	uint32						fifo_ctrl = 0;
	unsigned short 				estat_pre;

//	SER_TRACE(ZONE_INIT, "+netos_serlioctl %u", channel);

	switch (request)
	{
	case TCGETA:
		SER_TRACE(ZONE_IOCTL, "TCGETA (%d)", channel);
		status = EXIT_SUCCESS;
		memcpy((struct termios *)arg, &port->current_termios, sizeof(port->current_termios));
		break;

	case TCSETA:
		SER_TRACE(ZONE_IOCTL, "TCSETA (%d)", channel);
		status = tcseta((struct termios*)arg, &port->current_termios, &port->termios_support);
		ret = reconfig_hw(port);

		if (ret >= 0 && (status == EXIT_SUCCESS) )
		{
			tx_timer_change(&port->rtsup_timer,
				CC(port,VPREDELAY),
				0);
			tx_timer_change(&port->rtsdown_timer,
				CC(port,VPOSTDELAY),
				0);

			if (C_OFLAG(port) != 0)
			{
				port->write_fn = write_opost_data;
			}
			else
			{
				port->write_fn = write_raw_data;
			}
			status = EXIT_SUCCESS;
		}
		else
		{
			status = EIO;
		}
		break;

	case TCSENDBREAK:
		SER_TRACE(ZONE_IOCTL, "TCSENDBREAK (%d)", channel);
		status = EXIT_SUCCESS;
		serl_cb_event(port->serial_channel, TXB_EVENT);
		PROTECTED_EVENT_STATUS_SET(port, EV_TXB);
		port->breaktime = (int)arg;
		port->breakstatus = BRK_REQUESTED;
		break;

	case TCDRAIN:
		// post a "drain" request to serial monitor
		// and wait for serial monitor to signal completion.
		SER_TRACE(ZONE_IOCTL, "TCDRAIN (%d)", channel);
		status = EXIT_SUCCESS;
		port->tcdrain_req = 1;

		ret = tx_event_flags_get(
					&port->blocking_flags,
					DRAIN_BLOCKING,
					TX_OR_CLEAR,
					&port->actual_flags,
					TX_WAIT_FOREVER);

		assert(ret == TX_SUCCESS);
		break;

	case TCFLUSH:
		SER_TRACE(ZONE_IOCTL, "TCFLUSH (%d)", channel);

		fifo_ctrl = 0;

		status = EXIT_SUCCESS;
		queue_selector = (enum flush_opts)arg;

		if (queue_selector & TCIFLUSH)
		{ // toss input
			// Clear RXFIFO
			// TODO - shouldn't we grab the RX Mutex
			SER_TRACE(ZONE_IOCTL, "TCFLUSH (%d) - flushing Rx", channel);
			RX_FIFO_CLEAR(port);

			// set Receive Reset
			fifo_ctrl |= AU_UART_FIFOCTRL_RR;
			
			if (EVENT_STATUS(port) & EV_IPS)
			{
				PROTECTED_EVENT_STATUS_CLR(port, EV_IPS);
				change_rx_flow(port);
				serl_cb_event(port->serial_channel, IPS_EVENT);
			}
		}

		if (queue_selector & TCOFLUSH)
		{ // toss output
			// Clear TXFIFO
			SER_TRACE(ZONE_IOCTL, "TCFLUSH (%d) - flushing TX", channel);
			TX_FIFO_CLEAR(port);

			// set Receive Reset
			fifo_ctrl |= AU_UART_FIFOCTRL_TR;
		}

		UART_FIFOCTRL(port) |= fifo_ctrl;

		assert(!(queue_selector & ~TCIOFLUSH ));

		if (queue_selector & ~TCIOFLUSH)
		{
			status = EIO;
		}

		break;

	case TCFLOW:
		SER_TRACE(ZONE_IOCTL, "TCFLOW (%d)", channel);
		status = EXIT_SUCCESS;
		estat_pre = EVENT_STATUS(port);

		action = (enum flow_opts)arg;
		switch (action)
		{
		case TCOOFF:
			SER_TRACE(ZONE_IOCTL, "TCFLOW (%d) - USER OUTPUT FLOW OFF", channel);
			PROTECTED_EVENT_STATUS_CLR(port, EV_OPU);
			change_tx_flow(port);
		    if ( (estat_pre ^ EVENT_STATUS(port)) & EV_OPU )
			{
                serl_cb_event(port->serial_channel, OPU_EVENT);
			}
			break;

		case TCOON:
			SER_TRACE(ZONE_IOCTL, "TCFLOW (%d) - USER OUTPUT FLOW ON", channel);
			PROTECTED_EVENT_STATUS_SET(port, EV_OPU);
			change_tx_flow(port);
		    if ( (estat_pre ^ EVENT_STATUS(port)) & EV_OPU )
			{
                serl_cb_event(port->serial_channel, OPU_EVENT);
			}
			break;

		case TCIOFF:
			SER_TRACE(ZONE_IOCTL, "TCFLOW (%d) - USER INPUT FLOW OFF", channel);
			PROTECTED_EVENT_STATUS_CLR(port, EV_IPU);
			change_rx_flow(port);
		    if ( (estat_pre ^ EVENT_STATUS(port)) & EV_IPU )
			{
                serl_cb_event(port->serial_channel, IPU_EVENT);
			}
			break;

		case TCION:
			SER_TRACE(ZONE_IOCTL, "TCFLOW (%d) - USER INPUT FLOW ON", channel);
			PROTECTED_EVENT_STATUS_SET(port, EV_IPU);
			change_rx_flow(port);
		    if ( (estat_pre ^ EVENT_STATUS(port)) & EV_IPU )
			{
                serl_cb_event(port->serial_channel, IPU_EVENT);
			}
			break;

		case TCIAOFF: // pattern alarm exit flow
			PROTECTED_EVENT_STATUS_CLR(port, EV_IPA);
			change_rx_flow(port);
			break;

		case TCOSOFF:
			SER_TRACE(ZONE_IOCTL, "TCFLOW (%d) - SW OUTPUT FLOW OFF", channel);
			PROTECTED_EVENT_STATUS_CLR(port, EV_OPS);
			change_tx_flow(port);
		    if ( (estat_pre ^ EVENT_STATUS(port)) & EV_OPS )
			{
                serl_cb_event(port->serial_channel, OPS_EVENT);
			}
			break;

		case TCOSON:
			SER_TRACE(ZONE_IOCTL, "TCFLOW (%d) - SW OUTPUT FLOW ON", channel);
			PROTECTED_EVENT_STATUS_SET(port, EV_OPS);
			change_tx_flow(port);
		    if ( (estat_pre ^ EVENT_STATUS(port)) & EV_OPS )
			{
                serl_cb_event(port->serial_channel, OPS_EVENT);
			}
			break;

		case TCISOFF:
			SER_TRACE(ZONE_IOCTL, "TCFLOW (%d) - SW INPUT FLOW OFF", channel);
			PROTECTED_EVENT_STATUS_CLR(port, EV_IPS);
			change_rx_flow(port);
		    if ( (estat_pre ^ EVENT_STATUS(port)) & EV_IPS )
			{
                serl_cb_event(port->serial_channel, IPS_EVENT);
			}
			break;

		case TCISON:
			SER_TRACE(ZONE_IOCTL, "TCFLOW (%d) - SW INPUT FLOW ON", channel);
			PROTECTED_EVENT_STATUS_SET(port, EV_IPS);
			change_rx_flow(port);
		    if ( (estat_pre ^ EVENT_STATUS(port)) & EV_IPS )
			{
                serl_cb_event(port->serial_channel, IPS_EVENT);
			}
			break;

		case TCOXOFF:
			SER_TRACE(ZONE_IOCTL, "TCFLOW (%d) - EXTRA SW OUTPUT FLOW OFF", channel);
			PROTECTED_EVENT_STATUS_CLR(port, EV_OPX);
			change_tx_flow(port);
		    if ( (estat_pre ^ EVENT_STATUS(port)) & EV_OPX )
			{
                serl_cb_event(port->serial_channel, OPX_EVENT);
			}
			break;

		case TCOXON:
			SER_TRACE(ZONE_IOCTL, "TCFLOW (%d) - EXTRA SW OUTPUT FLOW ON", channel);
			PROTECTED_EVENT_STATUS_SET(port, EV_OPX);
			change_tx_flow(port);
		    if ( (estat_pre ^ EVENT_STATUS(port)) & EV_OPX )
			{
                serl_cb_event(port->serial_channel, OPX_EVENT);
			}
			break;

		case TCIXOFF:
		case TCIXON:
			//TODO - all the extra flow states

		default:
			status = EIO;
			break;
		}
		break;

	case TCSENDNOW:
		// What happens if the transmitter is being flow controlled?
		
		//

		break;

	case TCGETCOUNTERS:
		SER_TRACE(ZONE_IOCTL, "TCGETCOUNTERS");
		status = EXIT_SUCCESS;
		memcpy((struct serial_cntr_t*)arg,
				(const void *)&port->counters,
				sizeof(struct serial_cntr_t));
		break;

	case TCGETSTATS:
		status = EXIT_SUCCESS;

		serl_status = (struct serial_status_t*)arg;
		serl_status->mstat = 0;

		opt = port->uart_mdmstat_shadow;

		if (C_XFLAG(port) & XALTPIN)
		{
			/* Swap DCD and DSR */
			if (opt & AU_UART_MDMSTAT_CD)
			{
				serl_status->mstat |= MS_DSR;
			}

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

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

		if (C_XFLAG(port) & XFORCEDCD)
		{
			serl_status->mstat |= MS_DCD;
		}

		if (opt & AU_UART_MDMSTAT_CT )
		{
			serl_status->mstat |= MS_CTS;
		}

		opt = UART_MDMCTRL(port);

		if (opt & AU_UART_MDMCTRL_DT)
		{
			serl_status->mstat |= MS_DTR;
		}

		if (opt & AU_UART_MDMCTRL_RT)
		{
			serl_status->mstat |= MS_RTS;
		}

		{
			uint8 mstat = serl_status->mstat;
			SER_TRACE(ZONE_IOCTL, "GETSTATS(%d) CD %c, DTR %c, RTS %c, CTS %c, DSR %c",
									channel,
									mstat & MS_DCD ? '+' : '-',
									mstat & MS_DTR ? '+' : '-',
									mstat & MS_RTS ? '+' : '-',
									mstat & MS_CTS ? '+' : '-',
									mstat & MS_DSR ? '+' : '-'
 								   );
		}
		serl_status->estat = EVENT_STATUS(port);
		break;

	case TCGETSIGCHANGE:
		SER_TRACE(ZONE_IOCTL, "TCGETSIGCHANGE");
		status = EXIT_SUCCESS;
		memcpy((struct sigchange_stats_t *)arg, (const void *)&port->sigchange,
			sizeof(struct sigchange_stats_t));
		break;

	case TCSETSIGNALS:

		DISABLE_INTS;

		opt = UART_MDMCTRL(port);

		prev = 0;

		if (opt & AU_UART_MDMCTRL_DT)
		{
			prev |= MS_DTR;
		}

		if (opt & AU_UART_MDMCTRL_RT)
		{
			prev |= MS_RTS;
		}

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

		if ( !(C_CFLAG(port) & CDTR) )
		{
			if (mout & MS_DTR)
			{
				SER_TRACE(ZONE_IOCTL, "TCSETSIGNALS (%d) Set DTR", channel);
				opt |= AU_UART_MDMCTRL_DT;
			}
			else
			{
				SER_TRACE(ZONE_IOCTL, "TCSETSIGNALS (%d) Clr DTR", channel);
				opt &= ~AU_UART_MDMCTRL_DT;
			}
		}
		if ( !(C_CFLAG(port) & CRTS) ||
			!(C_CFLAG(port) & CRTSTOGGLE) )
		{
			if (mout & MS_RTS)
			{
				SER_TRACE(ZONE_IOCTL, "TCSETSIGNALS (%d) Set RTS", channel);
				opt |= AU_UART_MDMCTRL_RT;
			}
			else
			{
				SER_TRACE(ZONE_IOCTL, "TCSETSIGNALS (%d) Clr RTS", channel);
				opt &= ~AU_UART_MDMCTRL_RT;
			}
		}

		UART_MDMCTRL(port) = opt;
		RESTORE_INTS;

		if ( (prev ^ mout) & MS_DTR )
		{ /* DTR changed */
			SER_TRACE(ZONE_IOCTL, "TCSETSIGNALS (%d) DTR changed", channel);
			port->sigchange.dtr_counter++;
			serl_cb_event(port->serial_channel, DTR_DELTA);

			//Do we need this here if serl_monitor will catch it?
			INTERLOCKED_BIT_XOR(MODEM_STATUS(port), MS_DTR);
		}

		if ( (prev ^ mout) & MS_RTS )
		{ /* RTS changed */
			SER_TRACE(ZONE_IOCTL, "TCSETSIGNALS (%d) RTS changed", channel);
			port->sigchange.rts_counter++;
			serl_cb_event(port->serial_channel, RTS_DELTA);

			//Do we need this here if serl_monitor will catch it?
			INTERLOCKED_BIT_XOR(MODEM_STATUS(port), MS_RTS);
		}
		break;

	case TCGETBUFFERS:
		status = EXIT_SUCCESS;
		bufs = (struct serial_buffer_t *)arg;
		bufs->rxbuf = RX_FIFO_LEVEL(port);
		bufs->txbuf = TX_FIFO_LEVEL(port);
		SER_TRACE(ZONE_IOCTL, "TCGETBUFS(%d), rxbuf level = %u, txbuf level = %u", 
					channel, bufs->rxbuf, bufs->txbuf );
		break;

	case TCTXDONE:
		SER_TRACE(ZONE_IOCTL, "TCTXDONE");
		status = EXIT_SUCCESS;

		done = (int *)arg;

		*done = (TX_FIFO_EMPTY(port) && TRANSMITTER_EMPTY(port))
					? 1 : 0;
		break;

	case ISOPEN:
		SER_TRACE(ZONE_IOCTL, "ioctl ISOPEN");
		*(int*)arg = serial_isopen_int(channel);
		status = EXIT_SUCCESS;
		break;

    case DIPSWITCHMODE:
        *(int*)arg = DIPSWITCH_232;
        status = EXIT_SUCCESS;
        break;

	default:
		assert(0);
		/* Return ENOSYS: unsupported request */
		break;
	}

//	SER_TRACE(ZONE_INIT, "-netos_serlioctl %u", channel);

	return status;
}

#if NETWORK_STACK == TRECK
/*
 * Function: netos_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 netos_serlpoll(int channel, int insert_remove, int *checkMask,
                   ulong context)
{
    int ret = 0;

    au1200_port_t * port = &ports[channel];

    assert(port);

    if (!port->configured || (channel != port->channel))
	{
		return EINVAL;
	}

    // save checkMask and context.  We only support one select at a time
    // in this driver.
    if (insert_remove == NA_SELECT_INSERT)
    {
		port->select_context = context;
		port->checkMask = *checkMask;
    }
    else
    {
		port->select_context = 0;
		port->checkMask = 0;
    }
    if ((*checkMask & NA_SELECT_READ)
	     && (!RX_FIFO_EMPTY(port)))
	{
     	    ret |= NA_SELECT_READ;
	}

    if ((*checkMask & NA_SELECT_WRITE)
	    && (TX_FIFO_LEVEL(port) < (AU_SERL_TX_BUF_SIZE / 2)))
	{
    	    ret |= NA_SELECT_WRITE;
	}

    *checkMask = ret;
    return 0;
}
#endif /* NETWORK_STACK == TRECK */

/*
 *  Function: netos_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
netos_serlclose(int channel)
{
    au1200_port_t * port = &ports[channel];

	SER_TRACE(ZONE_INIT, "+close (%u)", channel);

	/* Netos_serlinit not called */
	if (!port->configured)
	{
		assert(0);
		return EBADF;
	}

	/* Block for transmit data to drain */
   close_logic(MAJOR_NETOS_SERL, channel);

	port->opened = 0;

	// Disable interrupts.
	UART_INTEN(port) = 0;

	serl_cb_event(port->serial_channel, CLOSE_EVENT);

	SER_TRACE(ZONE_INIT, "-close (%u)", channel);

	return EXIT_SUCCESS;
}


/*
 * Function: netos_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
netos_serlread(int channel, char * buffer, int size, int *bytesRead)
{
    au1200_port_t * port = &ports[channel];
	int 			ret;
	char 		  *	origbuffer = buffer;
	int				length = size;

	SER_TRACE(ZONE_RX, "+rd(%u): buf:%08x, len:%u",
				channel, buffer, length);

	if (!port->configured)
	{
		return EINVAL;
	}

	ret = tx_mutex_get(
			&port->rx_mutex,
			(port->openflags & O_NONBLOCK)	? TX_NO_WAIT : TX_WAIT_FOREVER
			);
	
	if (ret != TX_SUCCESS)
	{
		SER_TRACE(ZONE_RX, "rd(%u): tx_mutex_get failed %u",
					channel, ret);
		return (ret == TX_NOT_AVAILABLE ? EAGAIN : EINVAL);
	}

	*bytesRead = 0;
	ret = EXIT_SUCCESS;

	while (length)
	{
		int temp = rx_fifo_read(port, buffer, length);

		SER_TRACE(ZONE_RX, "rd(%u): rx_fifo_rd(%u byt)",
							channel, temp);

		length 		-= temp;
		*bytesRead 	+= temp;

		if (*bytesRead != 0)
		{
			break;
		}

		// No more data to read right now
		if (port->openflags & O_NONBLOCK)
		{
			ret = EAGAIN;
			break;
		}

		// block until we get something to read.
		SER_TRACE(ZONE_RX, "rd(%u): BLOCKING", channel);

		tx_event_flags_get(
			&port->blocking_flags,
			READ_BLOCKING,
			TX_OR_CLEAR,
			&port->actual_flags,
			TX_WAIT_FOREVER
			);

		SER_TRACE(ZONE_RX, "rd(%u): Back", channel);
	}

	if ( (RX_FIFO_LO_WATER(port)) && (EVENT_STATUS(port) & EV_IPS) )
	{
		SER_TRACE(ZONE_RX, "rd(%u): Fifo Level < thresh (%u)",channel, FLOW_THRESHOLD);
		PROTECTED_EVENT_STATUS_CLR(port, EV_IPS);
		change_rx_flow(port);
		serl_cb_event(port->serial_channel, IPS_EVENT);
	}

	tx_mutex_put(&port->rx_mutex);

	// This currently has no effect.
	HANDLE_PORT_BUFFERING(port, *bytesRead, origbuffer);
	
	SER_TRACE(ZONE_INIT, "-rd(%u): read %u\n", channel, *bytesRead);
	return ret;
}


/*
 * Function: netos_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
 *
 * 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
netos_serlwrite(int channel, char * buffer, int length, int *bytesWritten)
{
    au1200_port_t * port = &ports[channel];
	UINT ret;
	int written = 0;
	int firsttime = 1;
	INTERRUPT_SAVE_AREA;

	SER_TRACE(ZONE_TX, "+wr(%u): buf:%08x, len:%u", channel, buffer, length);

	/* Netos_serlinit not called */
	if (!port->configured)
	{
		SER_ASSERT(0);
		return EINVAL;
	}

	ret = tx_mutex_get(
			&port->tx_mutex,
			(port->openflags & O_NONBLOCK)	? TX_NO_WAIT : TX_WAIT_FOREVER
			);
	
	if (ret != TX_SUCCESS)
	{
		SER_TRACE(ZONE_TX, "wr(%u): tx_mutex_get failed %u",channel, ret);
		return (ret == TX_NOT_AVAILABLE ? EAGAIN : EINVAL);
	}

	if (port->breakstatus != BRK_IDLE)
	{
		SER_TRACE(ZONE_TX, "wr(%u): can't write, sending BREAK",channel, ret);

		if (port->openflags & O_NONBLOCK)
		{
			tx_mutex_put(&port->tx_mutex);
			return EAGAIN;
		}
		else
		{
			//Block for break to finish.
			tx_event_flags_get(
					&port->blocking_flags,
					BRK_BLOCKING,
					TX_OR_CLEAR,
					&port->actual_flags,
					TX_WAIT_FOREVER);
		}
	}

	// RTS Toggle support
	if (C_CFLAG(port) & CRTSTOGGLE)
	{
		// De-activate any RTS down timer
		tx_timer_deactivate(&port->rtsdown_timer);

		// Wait (optional) pre-delay
		if (CC(port,VPREDELAY))
		{
			PROTECTED_EVENT_STATUS_SET(port, EV_OPT);
			change_tx_flow(port);
			tx_timer_change(
				&port->rtsup_timer,
				CC(port,VPREDELAY),
				0);
			tx_timer_activate(&port->rtsup_timer);
		}
	}

	do
	{
		int temp;

		if (!firsttime )
		{
			if (port->openflags & O_NONBLOCK)
			{
				ret = (written == length) ? 0 : EAGAIN;
				break;
			}
			else
			{
				SER_TRACE(ZONE_TX, "wr(%u): BLOCKED...",channel);
				// Block
				tx_event_flags_get( 
						&port->blocking_flags, 
						WRITE_BLOCKING,
						TX_OR_CLEAR, 
						&port->actual_flags,
						TX_WAIT_FOREVER
						);
				SER_TRACE(ZONE_TX, "wr(%u): UN-BLOCKED...",channel);
			}
		}
		else
		{
			firsttime = 0;
		}

		temp = port->write_fn(	
					buffer + written,
					length - written,
					port->tx_buffer,
					AU_SERL_TX_BUF_SIZE,
					AU_SERL_TX_BUF_MASK,
					&port->ocol,
					&port->current_termios
					);

		written += temp;

	} while (written != length);

	if (bytesWritten)
	{
		*bytesWritten = written;
	}

	// kick transmitter if any data is written.
	if (written)
	{
		DISABLE_INTS;
		write_uart_tx_fifo(port);
		RESTORE_INTS;
	}

	tx_mutex_put(&port->tx_mutex);

	SER_TRACE(ZONE_TX, "-wr (%u): wrote %u", channel, written);

	return 0;
}


static int
reconfig_hw(au1200_port_t * port)
{
	uint32 lcr, mcr;
    int word_size;
	struct serial_status_t status;

	SER_TRACE(ZONE_INIT, "+reconfig_hw (%u)", port->channel);

	switch(C_CFLAG(port) & CSIZE)
	{
	case CS8:
		lcr = AU_UART_LINECTRL_WLS_8;
		word_size = 8;
		break;
	case CS7:
		lcr = AU_UART_LINECTRL_WLS_7;
		word_size = 7;
		break;
	case CS6:
		lcr = AU_UART_LINECTRL_WLS_6;
		word_size = 6;
		break;
	case CS5:
		lcr = AU_UART_LINECTRL_WLS_5;
		word_size = 5;
		break;
	default:
		NA_ASSERT(0);
		break;
	};

	SER_TRACE(ZONE_INIT, "reconfig_hw (%u), charlen = %u",
				port->channel,
				word_size);

	if (C_CFLAG(port) & CSTOPB)
	{
		lcr |= AU_UART_LINECTRL_ST_2;
	}

	if (C_CFLAG(port) & PARENB)
	{
		SER_TRACE(ZONE_INIT, "reconfig_hw (%u), parity enabled ", port->channel);
		lcr |= AU_UART_LINECTRL_PE;
	}
	else
	{
		SER_TRACE(ZONE_INIT, "reconfig_hw (%u), parity disabled ", port->channel);
	}

	if (C_XFLAG(port) & XPAR)
	{ /* Mark/space parity? */
		if (C_CFLAG(port) & PARODD)
		{
			SER_TRACE(ZONE_INIT, "reconfig_hw (%u), mark parity ", port->channel);
			lcr |= AU_UART_LINECTRL_PAR_MARK;
		}
		else /* !PARODD */
		{
			SER_TRACE(ZONE_INIT, "reconfig_hw (%u), space parity ", port->channel);
			lcr |= AU_UART_LINECTRL_PAR_SPACE;
		}
	}
	else /* !XPAR */{
		if (C_CFLAG(port) & PARODD)
		{
			SER_TRACE(ZONE_INIT, "reconfig_hw (%u), odd parity ", port->channel);
			lcr |= AU_UART_LINECTRL_PAR_ODD;
		}
		else /* !PARODD */
		{
			SER_TRACE(ZONE_INIT, "reconfig_hw (%u), even parity ", port->channel);
			lcr |= AU_UART_LINECTRL_PAR_EVEN;
		}
	}

	SER_TRACE(ZONE_INIT, "reconfig_hw (%u), setting LCR to 0x%02x",
							port->channel,
							lcr);
	UART_LINECTRL(port) = lcr;

	// TODO - 	resolve conflicts if both h/w and s/w flow control
	// 			are selected?
	// Hardware flow control
	if (C_CFLAG(port) & CCTS)
	{
		SER_TRACE(ZONE_INIT, "reconfig_hw (%u), use hardware f/c ", port->channel);
	}

	// Check if we need to do software flow control.
	else
	if (C_IFLAG(port) & (IXANY | IXON | XIXON))
	{
		SER_TRACE(ZONE_INIT, "reconfig_hw (%u), use software f/c ", port->channel);
		port->scan_rx_data = 1;
	}
	else
	{
		port->scan_rx_data = 0;
	}

	set_baud_rate(port, BAUD(port));

	if (!(EVENT_STATUS(port) & EV_IPALL))
	{
		mcr = UART_MDMCTRL(port);

		if (C_CFLAG(port) & CDTR)
		{
			mcr |= AU_UART_MDMCTRL_DT;
		}
		if (C_CFLAG(port) & CRTS)
		{
			mcr |= AU_UART_MDMCTRL_RT;
		}

		UART_MDMCTRL(port) |= mcr;
	}

	// get the port stats and save them
	netos_serlioctl(port->channel, TCGETSTATS, (char *)&status);
	MODEM_STATUS(port) = status.mstat;

	set_auto_flow(port);

	SER_TRACE(ZONE_INIT, "-reconfig_hw (%u)", port->channel);

	return 0; // Success
}


static
void
set_auto_flow(au1200_port_t *port)
{
	// If the UART channel supports hardware flow control and RTS/CTS h/w 
	// flow control is enabled, then enable AUTOFLOW.
	if (UART_SUPPORTS_HARDWARE_FLOW(port) && HARDWARE_FLOW_CONFIGURED(port) && USE_AUTOFLOW)
	{
		SER_TRACE(ZONE_INIT, "set_auto_flow (%u) - enable AUTOFLOW", port->channel);

		// Sometimes when you enable AUTOFLOW, au1200 UART ignores it and behaves
		// as if it were disabled. When you get in this state, the only way I've found
		// to force the channel into AUTOFLOW mode is to clear MDMCTRL.RT amd 
		// write a 1 to AUTOFLOW.AFE bit. So to be safe, always clear MDMCTRL.RT 
		// before setting AUTOFLOW.AFE.
		UART_MDMCTRL(port) &= ~AU_UART_MDMCTRL_RT;
		UART_ENABLE_AUTOFLOW(port);
		port->autoflow_enabled = 1;
	}
	else
	{
	SER_TRACE(ZONE_INIT, "set_auto_flow (%u) - disable AUTOFLOW", port->channel);
	UART_DISABLE_AUTOFLOW(port);
	port->autoflow_enabled = 0;
	}
}


static
int
set_baud_rate(au1200_port_t * port, ULONG baud)
{
	ULONG clkdiv;
	ULONG cpuclock = GetCpuFrequency();
	ULONG system_bus_divider = 2;
	ULONG actual_baud;

	SER_TRACE(ZONE_INIT, "+set_baud_rate(%u) to %u", port->channel, baud);

	// Check baud rate values
	SER_ASSERT( (baud >= 110) && (baud <= 1546875));

	// Calculate clock divider
	// AU1200 databook says that Baudrate =  CPU/(SD * 2 * CLKDIV * 16)
	// Multiply by 10 and round up by adding 5 and dividing by 10.
	clkdiv = (cpuclock * 10) / (system_bus_divider * 2 * baud * 16);
	clkdiv = (clkdiv + 5) / 10;

	actual_baud = cpuclock / (system_bus_divider * 2 * clkdiv * 16);

	SER_TRACE(ZONE_INIT, "set_baud_rate(%u) clock divisor = %u", port->channel, clkdiv);

	UART_CLKDIV(port) = clkdiv;

	SER_TRACE(ZONE_INIT, "set_baud_rate(%u) desired baud = %u, actual = %u", 
				port->channel, baud, actual_baud);

	SER_TRACE(ZONE_INIT, "-set_baud_rate(%u)", port->channel);

	return EXIT_SUCCESS;
}


/******************************************************************************
* au1200_serl_isr															  *
*	parms:																	  *
*		port handle															  *
*																			  *
*	returns:																  *
*		nothing																  *
*																			  *
*	descr:																	  *
*		Handles serial port interrupts:										  *
*			1. Receive Line Status											  *
*			2. Receive Data Available										  *
*			3. Transmit Buffer Available									  *
*																			  *
******************************************************************************/

#define HEX_TO_ASCII(h)	( (h < 0x0a) ? h + '0' : (h - 0xa) + 'a' )
#define HI_NIBBLE(c)	( (c & 0xf0) >> 4)
#define LO_NIBBLE(c)	( (c & 0x0f)     )

#define	DATA_MSG_APPEND_HEX_CHAR(msgbuf, i, c)					\
	if (i <= (sizeof(msgbuf) - 4))								\
	{															\
		msgbuf[i++] = HEX_TO_ASCII(HI_NIBBLE(c));				\
		msgbuf[i++] = HEX_TO_ASCII(LO_NIBBLE(c));				\
	 	msgbuf[i++] = ' ';						  				\
		msgbuf[i]   = 0;										\
	}

static
int
au1200_serl_isr(void *isrParameter)		// Parameter passed to NAInstallIsr
{
	uint32 iid;
    au1200_port_t * port = isrParameter;
	uint32 int_cause = UART_INTCAUSE(port);

	SER_TRACE(ZONE_RX + ZONE_TX, "+isr(%u), cause %08x", port->channel, int_cause);

	SER_ASSERT((int_cause & AU_UART_INTCAUSE_IP) == 0);

	iid = (int_cause & AU_UART_INTCAUSE_IID) >> 1;

	// Rx Data Available or Rx Character Timeout 
	if (	(iid == AU_UART_INTCAUSE_IID_RDA) ||
			(iid == AU_UART_INTCAUSE_IID_CTO)
		)
	{
		read_uart_rx_fifo(port);
	}

	// line status
	else if (iid == AU_UART_INTCAUSE_IID_RLS)
	{
		handle_line_status(port);
	}

	// Tx Buffer Available
	else if (iid == AU_UART_INTCAUSE_IID_TBA)
	{
		write_uart_tx_fifo(port);
	}

	// modem status
	else if (iid == AU_UART_INTCAUSE_IID_MS)
	{
		handle_modem_status(port);
	}

	SER_TRACE(ZONE_RX + ZONE_TX, "-isr (%u)", port->channel);

	return 0;
}

static
void
read_uart_rx_fifo(au1200_port_t * port)
{
	int got_rx_char = 0;
	uint32 ix = 0;
	char DataMsg[3 * 16 + 1];

	DataMsg[0] = '\0';

	while (TRUE)
	{
		uint32 	lsr				= UART_LINESTAT(port);
		uint8	dropchar 		= 0;
		uint8	ch;

		if ( (lsr & AU_UART_LINESTAT_DR) == 0)
		{
			// no more characters, so get out.
			break;
		}

		ch = UART_RXDATA(port);
		DATA_MSG_APPEND_HEX_CHAR(DataMsg, ix, ch);

		if (C_IFLAG(port) & ISTRIP)
		{
			ch &= 0x7f;
		}

		else if( (ch == 0xff) //Most likely to fail, so goes first
			&& (C_IFLAG(port) & (IGNPAR|PARMRK))
			== PARMRK
			&& ((C_XFLAG(port) & XEDATA) == 0)
			&& (!(C_IFLAG(port) & ISTRIP)) )
		{
			addchar(port, 0xff); //Escape the existing 0xff
		}

		// Scan data for XON/XOFF chars if using S/W flow control.
		if (port->scan_rx_data)
		{
			dropchar = int_fc_scan(port, ch);
		}

		if (!dropchar)
		{
			addchar(port, ch);
			got_rx_char = 1;
		}
		else
		{
			SER_TRACE(ZONE_RX, "dropchar\n");
		}
	}
	SER_TRACE(ZONE_RX, "RX chars: %s", DataMsg);

	// flow control remote transmitter if rx buffer level is above high
	// water mark
	if ( (RX_FIFO_HI_WATER(port)) && !(EVENT_STATUS(port) & EV_IPS) )
	{
		SER_TRACE(ZONE_RX + ZONE_TX, "isr (%u) - pause IN traffic",
			port->channel);
		EVENT_STATUS(port) |= EV_IPS;
		serl_cb_event(port->serial_channel, IPS_EVENT);
		change_rx_flow(port);
	}

	// Wakeup monitor thread.  This is going very often.
	if (got_rx_char)
	{
		tx_event_flags_set(
			&global_event_flags,
			MONITOR_WAKEUP,
			TX_OR);
	}
}



static
void
write_uart_tx_fifo(au1200_port_t * port)
{
	char 	DataMsg[3 * 16 + 1];
	uint32 	count;
	uint32 linestat = UART_LINESTAT(port);

	// If autoflow disabled, RTS h/w fc config'd, and CTS low then get out.
	if ( 	!port->autoflow_enabled 		&& 
			HARDWARE_FLOW_CONFIGURED(port) 	&& 
			((port->uart_mdmstat_shadow & AU_UART_MDMSTAT_CT) == 0)
		)
	{
		SER_TRACE(ZONE_TX, "write_uart_tx_fifo - CTS flow control asserted!!", port->forced_transmit_char);
		return;
	}
	

	// If transmitter is empty, then there's space for the entire size of the fifo.
	if (linestat & AU_UART_LINESTAT_TE)
	{
		count = port->uart_tx_fifo_len;
	}
	// if below threshhold, then we have at least room for fifo len - fifo threshold.
	else if (linestat & AU_UART_LINESTAT_TT)
	{
		count = port->uart_tx_fifo_len - port->uart_tx_fifo_thresh;
	}
	// We only know that we're above the threshhold so we must assume we're full.
	else
	{
		count = 0;
	}

	SER_TRACE(ZONE_TX, "+write_uart_tx_fifo(%u) space = %d", port->channel, count);

	if (port->forced_transmit_char && count)
	{
		SER_TRACE(ZONE_INIT, "isr TBA, forcibly tx'ing 0x%02x", port->forced_transmit_char);
		UART_TXDATA(port) = port->forced_transmit_char;
		port->forced_transmit_char = 0;
		count--;
	}

	// If we're not flow-controlled.
	if ( (EVENT_STATUS(port) & EV_OPALL) == 0)
	{
		DataMsg[0] = '\0';
		uint32 ix = 0;

		// Keep going while there are characters in the logical TX FIFO.
		while (!TX_FIFO_EMPTY(port))
		{
			uint8 ch;

			if (count == 0)
			{
				// Keep going if we're below tx fifo threshhold or we'll miss TBA interrupt.
				if (!TRANSMITTER_BELOW_THRESHOLD(port))
				{
					break;
				}
				else
				{
					SER_TRACE(ZONE_TX, "write_uart_tx_fifo: zero count, but still below TT!");
				}
			}
			else
			{
				count--;
			}

			TX_FIFO_GET_CHAR(port,ch);
			UART_TXDATA(port) = ch;
			DATA_MSG_APPEND_HEX_CHAR(DataMsg, ix, ch);
			INC_SERIAL_COUNTER(port,tbytes);
		}

		SER_TRACE(ZONE_TX, "TX chars: %s", DataMsg);
	}

	// If tx buffer is empty, turn off transmit interrupt enable
	if (TX_FIFO_EMPTY(port))
	{
		if ((C_CFLAG(port) & CRTSTOGGLE) && TRANSMITTER_EMPTY(port))
		{
			// TX buffer drained, RTS Toggle on and 
			if (CC(port,VPOSTDELAY))
			{
				tx_timer_change(
					&port->rtsdown_timer,
					CC(port,VPOSTDELAY),
					0);

				tx_timer_activate(&port->rtsdown_timer);
			}
			else
			{
				// MCR protected by being in interrupt
				UART_MDMCTRL(port) &= ~AU_UART_MDMCTRL_RT;
			}
		}
	}
	SER_TRACE(ZONE_TX, "-write_uart_tx_fifo");
}



// NEVER CALL THIS FROM ANYWHERE OTHER THAN THE INTERRUPT
static inline void
addchar(au1200_port_t * port, uint8 ch)
{
	if (RX_FIFO_FULL(port))
	{
		INC_SERIAL_COUNTER(port,noflow);
	}
	else
	{
		RX_FIFO_PUT_CHAR(port, ch);

		#if 0
		if((patternAlarmEnabled != NULL) &&
		(patternAlarmEnabled[0]==PATTERN_ALARM_ENABLED))
		{
			int alarmret = PM_FLOW_CONTROL_OFF;
			{
				// TODO - defensive code?
				alarmret = patternAlarmNewData(
								port->serial_channel,
								(char*)&ch, 	// TODO - this is ugly
								1);
			}
			if (alarmret > PM_FLOW_CONTROL_OFF)
			{
				EVENT_STATUS(port) |= EV_IPA;
				change_rx_flow(port);
			}
		}
		#endif
		INC_SERIAL_COUNTER(port,rbytes);
		port->rx_note++;
		port->got_rx = 1;
	}
}

static
void
handle_modem_status(au1200_port_t * port)
{

	// Read modem status to clear the MIE interrupt and save the
	// status in a shadow register so that this is the only place
	// where the register is read (other than in when the port is
	// first opened).  If we read it in other contexts, we might
	// miss this modem status interrupt and not awaken the serial
	// monitor thread in time and then we might overrun a remote
	// device which is trying to flow control us by dropping CTS.
	port->uart_mdmstat_shadow = UART_MDMSTAT(port);

	// Transmitter might need a kick.
	write_uart_tx_fifo(port);

	SER_TRACE(ZONE_RX + ZONE_TX, "+handle_modem_status(%u) = 0x%02x",
				port->channel,
				port->uart_mdmstat_shadow);

	// Wake up the serial monitor thread.
	tx_event_flags_set(
			&global_event_flags,
			MONITOR_WAKEUP,
			TX_OR);

	SER_TRACE(ZONE_RX + ZONE_TX, "-handle_modem_status(%u)", port->channel);
}


static
void
handle_line_status(au1200_port_t * port)
{
	uint32 lsr;

	// Read the line status
	lsr = UART_LINESTAT(port);

	SER_TRACE(ZONE_RX + ZONE_TX, "+handle_line_status(%u) = 0x%02x",
				port->channel,
				lsr);

	// According to au1200 databook, RF bit indicates parity error,
	// framing error, or break indication.  For any of these events,
	// we should read and discard the character.
	if (lsr & AU_UART_LINESTAT_RF)
	{
		uint32 temp;
		temp = UART_RXDATA(port);
	}

	// Break indication?
	if (lsr & AU_UART_LINESTAT_BI)
	{
		INC_SERIAL_COUNTER(port, nbreak);
		SER_TRACE(ZONE_RX + ZONE_TX, "lsr (%u) - Break Indicator",port->channel);
	}

	if (lsr & AU_UART_LINESTAT_FE)
	{
		INC_SERIAL_COUNTER(port, nframe);
		SER_TRACE(ZONE_RX + ZONE_TX, "lsr (%u) - Framing Error",port->channel);
	}

	
	if (lsr & AU_UART_LINESTAT_PE)
	{
		INC_SERIAL_COUNTER(port, nparity);
		SER_TRACE(ZONE_RX + ZONE_TX, "lsr (%u) - Parity error",port->channel);
	}

	if (lsr & AU_UART_LINESTAT_OE)
	{
		INC_SERIAL_COUNTER(port, norun);
		SER_TRACE(ZONE_RX + ZONE_TX, "lsr (%u) - RX FIFO overflow error", port->channel);
	}

	SER_TRACE(ZONE_RX + ZONE_TX, "-handle_line_status(%u)", port->channel);
}

//
// int_fc_scan - interrupt flow control scan
//
//	Params:
//		port - serial port instance (unused)
//		c	 - character to examine.
//
//	Returns:
//		true if character should be dropped.
//
//	Description:
//	this function is called from the interrupt handler
//	if software flow control is enabled (e.g. XON/XOFF)
//
//	If the transmitter is "XOFF'd", it will restart for any of
//  of the following:
//	 	1.	configured for any received character to server
//			as XON (IXANY).  The received character will be
//			"dropped" if XTOSS is enabled.
//	 	2. 	configured for XON flow control and character
//			matches the configured XON character (VSTART)
//			The received character will always be dropped.
//	 	3. 	configured for extended XON flow control and character
//			matches the configured XXON character (VXSTART)
//			The received character will always be dropped.
//
// 	If the transmitter is enabled, it will be stopped for
//	any of the following:
//	 	1.	configured for XON flow control and character
//			matches the configured XOFF character (VSTOP).
//			Received character will always be dropped.
//	 	2.	configured for extended XON/XOFF (XIXON) and
//			character matches XXOFF character (VXSTOP).
//			The received character will always be dropped.


static
int
int_fc_scan(au1200_port_t *port, char c)
{
	int drop_char = 0;

	SER_TRACE(ZONE_FC_SCAN, "+int_fc_scan(%u) for char 0x%02x",
				port->channel, c);

	// If TX currently XOFF'd and configured to accept any character as XON.
	if ( XON_ANY_CHAR(port) && TX_IS_XOFFED(port) )
	{
		// Resume flow control
		//TODO: Protect estat - in interrupt now
		SER_TRACE(ZONE_FC_SCAN, "int_fc_scan(%u) remote XON (any char) ", port->channel);

		EVENT_STATUS_CLEAR(port, EV_OPS);
		change_tx_flow(port);
		serl_cb_event(port->serial_channel, OPS_EVENT);

		if (C_XFLAG(port) & XTOSS)
		{
			SER_TRACE(ZONE_RX, "int_fc_scan(%u) toss 'XON' character ",
					port->channel, c);
			// If necessary, Scan for XTOSS characters
			//Throw away the character
			drop_char = 1;
		}
	}

	// Scan for VSTART/VSTOP characters if configured for standard XON.
	else
	if (XON_SPECIFIC_CHAR(port))
	{
		// If IXON, scan for XON2, XOFF2 characters
		if (MATCHES_VSTART(port,c))
		{
			if (TX_IS_XOFFED(port))
			{
				SER_TRACE(ZONE_FC_SCAN, "int_fc_scan(%u) specific XON from remote", port->channel);
				//TODO: Protect estat - in interrupt now
				EVENT_STATUS_CLEAR(port, EV_OPS);
				change_tx_flow(port);
				serl_cb_event(port->serial_channel, OPS_EVENT);
			}
			drop_char = 1;
		}
		else if ((MATCHES_VSTOP(port,c)))
		{
			if (!TX_IS_XOFFED(port))
			{
				SER_TRACE(ZONE_FC_SCAN, "int_fc_scan(%u) XOFF from remote", port->channel);
				//TODO: Protect estat - in interrupt now
				EVENT_STATUS_SET(port, EV_OPS);
				change_tx_flow(port);
				serl_cb_event(port->serial_channel, OPS_EVENT);
			}
			drop_char = 1;
		}
	}

	// Scan for VXSTART/VXSTOP characters if configured for extended XON.
	else
	if (XXON_SPECIFIC_CHAR(port))
	{
		if (MATCHES_VXSTART(port,c))
		{
			if (TX_IS_X_XOFFED(port))
			{
				SER_TRACE(ZONE_RX, "int_fc_scan(%u) specific X-XON from remote", port->channel);
				//TODO: Protect estat - in interrupt now
				EVENT_STATUS_CLEAR(port, EV_OPX);
				change_tx_flow(port);
				serl_cb_event(port->serial_channel, OPX_EVENT);
			}
			drop_char = 1;
		}
		else
		if (MATCHES_VXSTOP(port,c))
		{
			if (!TX_IS_X_XOFFED(port))
			{
				//TODO: Protect estat - in interrupt now
				SER_TRACE(ZONE_RX, "int_fc_scan(%u) specific X-XOFF from remote", port->channel);
				EVENT_STATUS_SET(port, EV_OPX);
				change_tx_flow(port);
				serl_cb_event(port->serial_channel, OPX_EVENT);
			}
			drop_char = 1;
		}
	}

	SER_TRACE(ZONE_FC_SCAN, "+int_fc_scan(%u) dropchar = %u", port->channel, drop_char);
	return drop_char;
}




/*
 * Function: serl_monitor()
 *
 * This function provides most, or all, of the periodic processing for the
 * system.  Its responsibilities usually include signal monitoring, event
 * generation, blocking task wakes, flow control, etc.
 */
static void
serl_monitor(void)
{
	INTERRUPT_SAVE_AREA;
	struct serial_status_t status;
	int i;
	ULONG actual_flags;
	UINT	tx_status;

	SER_TRACE(ZONE_MON, "+mon");

	while (1)
	{
		for (i = 0 ; i < NUM_SERIAL_PORTS ; i++)
		{
		    au1200_port_t * port = &ports[i];
			unsigned char mprev;
			unsigned char mdiff;
			unsigned char mflow;

			if (!port->opened)
				continue;

			mprev = MODEM_STATUS(port);

			SER_TRACE(ZONE_MON, "mon - get stats");

			netos_serlioctl(i, TCGETSTATS, (char *)&status);

			// see which modem signals have changed.
			mdiff = mprev ^ status.mstat;

			if (mdiff != 0)
			{
				SER_TRACE(ZONE_MON, "mon - modem diff = 0x%02x", mdiff);
			}

			if (mdiff & MS_DSR)
			{
				SER_TRACE(ZONE_MON, "mon - DSR changed");
				port->sigchange.dsr_counter++;
				serl_cb_event(port->serial_channel, DSR_DELTA);
			}
			if (mdiff & MS_DCD)
			{
				SER_TRACE(ZONE_MON, "mon - DCD changed");
				port->sigchange.dcd_counter++;
				serl_cb_event(port->serial_channel, DCD_DELTA);
			}
			if (mdiff & MS_RI)
			{
				SER_TRACE(ZONE_MON, "mon - RI changed");
				port->sigchange.ri_counter++;
				serl_cb_event(port->serial_channel, RI_DELTA);
			}
			if (mdiff & MS_CTS)
			{
				SER_TRACE(ZONE_MON, "mon - CTS changed");
				port->sigchange.cts_counter++;
				serl_cb_event(port->serial_channel, CTS_DELTA);
			}
			if (mdiff & MS_RTS)
			{
				SER_TRACE(ZONE_MON, "mon - RTS changed");
				port->sigchange.rts_counter++;
				serl_cb_event(port->serial_channel, RTS_DELTA);
			}
			if (mdiff & MS_DTR)
			{
				SER_TRACE(ZONE_MON, "mon - DTR changed");
				port->sigchange.dtr_counter++;
				serl_cb_event(port->serial_channel, DTR_DELTA);
			}

			//Flow control for incoming sigs other than CTS
			mflow = (C_CFLAG(port) & CMDMIN)  >> MFLOW_SHIFT;
			if (status.estat & EV_OPH)
			{   // We are flow-controlled...
				SER_TRACE(ZONE_MON, "mon - we are flow-controlled");
				if ( (mflow & status.mstat) == mflow)
				{
						// and don't need to be
					SER_TRACE(ZONE_MON, "mon - but shouldn't be flow-controlled");
					PROTECTED_EVENT_STATUS_CLR(port, EV_OPH);
					change_tx_flow(port);
					serl_cb_event(port->serial_channel, OPH_EVENT);
				}
			}
			else
			{   //We're not flow controlled
				SER_TRACE(ZONE_MON, "mon - we aren't flow-controlled");
				if ( (mflow & status.mstat) != mflow)
				{
					//and should be
					SER_TRACE(ZONE_MON, "mon - but should be flow-controlled");
					PROTECTED_EVENT_STATUS_SET(port, EV_OPH);
					change_tx_flow(port);
					serl_cb_event(port->serial_channel, OPH_EVENT);
				}
			}

			// TODO - simplify this...
			if (port->rx_note != port->rx_noted)
			{
				SER_TRACE(ZONE_MON, "mon - rx_note != rx_noted");
				port->rx_noted = port->rx_note;
				serl_cb_event(port->serial_channel, RX_DATA);

				#if NETWORK_STACK == TRECK
				if (port->checkMask & NA_SELECT_READ)
				{
					SER_TRACE(ZONE_MON, "mon - calling SelectNotify");
					tfDeviceSelectNotify(NA_SELECT_READ, port->select_context);
				}
				#endif

				// Wake any blocked reads
				SER_TRACE(ZONE_MON, "mon - wake READ_BLOCK'd thread");
				tx_event_flags_set( &port->blocking_flags, READ_BLOCKING, TX_OR);
			}

			if ( TX_FIFO_LEVEL(port) < (AU_SERL_TX_BUF_SIZE / 2) )
			{
				#if NETWORK_STACK == TRECK
				if (port->checkMask & NA_SELECT_WRITE)
				{
					tfDeviceSelectNotify(NA_SELECT_WRITE, port->select_context);
				}
				#endif

				//Wake any blocked writes
				SER_TRACE(ZONE_MON, "mon - wake WRITE_BLOCK'd thread");
				tx_event_flags_set( &port->blocking_flags, WRITE_BLOCKING, TX_OR);
			}

			if (port->breakstatus == BRK_ACTIVE)
			{
				SER_TRACE(ZONE_MON, "mon - break status active!!");
				// TODO - this needs fixin', we should save tick time since break time
				// was invoked and compare with current ticks.
				port->breaktime -= SERL_POLL_RATE;
				if (port->breaktime <= 0)
				{
					DISABLE_INTS;
					UART_DISABLE_BREAK(port);
					PROTECTED_EVENT_STATUS_CLR(port, EV_TXB);
					RESTORE_INTS;
					serl_cb_event(port->serial_channel, TXB_EVENT);

					port->breaktime = 0;
					port->breakstatus = BRK_IDLE;
					tx_event_flags_set( &port->blocking_flags, BRK_BLOCKING, TX_OR);
				}
			}
			// If the transmitter is empty, then see if a break
			// has been requested.
			if (TRANSMITTER_EMPTY(port) && TX_FIFO_EMPTY(port))
			{
				if (port->breakstatus == BRK_REQUESTED)
				{
					// Start a break
					DISABLE_INTS;
					UART_ENABLE_BREAK(port);
					RESTORE_INTS;

					port->breakstatus = BRK_ACTIVE;
				}

				if (port->tcdrain_req)
				{
					tx_event_flags_set(	&port->blocking_flags, DRAIN_BLOCKING, TX_OR);
					port->tcdrain_req = 0;
				}
			}

			/* TODO: poll_dips(); when we're ready to do MEI*/

			// Record port status.
			MODEM_STATUS(port) = status.mstat;

			// Kick transmitter in case its stuck.
			if (!port->autoflow_enabled)
			{
				DISABLE_INTS;
				write_uart_tx_fifo(port);
				RESTORE_INTS;
			}
		}

		tx_status = tx_event_flags_get(
						&global_event_flags,
						MONITOR_WAKEUP,
						TX_OR_CLEAR,
						&actual_flags,
						100);					// timeout after 100 ms.

		SER_TRACE(ZONE_MON, "mon - thread awakened by %s", (tx_status == TX_SUCCESS) ? "event" : "timeout");
	}
}

 // 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(au1200_port_t *port)
{
	INTERRUPT_SAVE_AREA;
	DISABLE_INTS; // Protect estat from other tasks and the serial int

	SER_TRACE(ZONE_RX, "+change_tx_flow (%u)", port->channel);

	if ( EVENT_STATUS(port) & EV_OPALL )
	{
		// Turn off transmit interrupt
		SER_TRACE(ZONE_RX, "change_tx_flow (%u), disable transmit interrupt", port->channel);
	}
	else
	{
		SER_TRACE(ZONE_RX, "change_tx_flow (%u), enable transmit interrupt", port->channel);
		write_uart_tx_fifo(port);
	}

	RESTORE_INTS;

	SER_TRACE(ZONE_RX, "-change_tx_flow (%u)", port->channel);
}

// 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(au1200_port_t *port)
{
	INTERRUPT_SAVE_AREA;

	SER_TRACE(ZONE_RX, "+change_rx_flow (%u)", port->channel);

	// TODO - are software and hardware flow control
	// mutually exclusive?
	if (C_CFLAG(port) & CMDMOUT)
	{
		// Change DTR/RTS accordingly
		DISABLE_INTS; //Protect MCR from other tasks

		if (EVENT_STATUS(port) & EV_IPALL)
		{
			SER_TRACE(ZONE_RX, "change_rx_flow (%u), assert hw f/c", port->channel);

			if (C_CFLAG(port) & CDTR)
			{
				SER_TRACE(ZONE_RX, "change_rx_flow (%u), drop DTR", port->channel);
				UART_MDMCTRL(port) &= ~AU_UART_MDMCTRL_DT;
			}
			if (C_CFLAG(port) & CRTS)
			{
				// if AUTOFLOW is enabled, then disable the RX interrupt and let the
				// hardware handle flow control.  AUTOFLOW asserts flow control when 
				// the RX_FIFO level reaches the programmed threshold.  
				if (port->autoflow_enabled)
				{
					SER_TRACE(ZONE_RX, "change_rx_flow (%u) autoflow - disable RX int", port->channel);
					DISABLE_UART_RX_INTERRUPT(port);
				}
				else
				{
					SER_TRACE(ZONE_RX, "change_rx_flow (%u) - drop RTS", port->channel);
					UART_MDMCTRL(port) &= ~AU_UART_MDMCTRL_RT;
				}
			}
		}
		else
		{
			SER_TRACE(ZONE_RX, "change_rx_flow (%u), DE-assert hw f/c", port->channel);
			if (C_CFLAG(port) & CDTR)
			{
				SER_TRACE(ZONE_RX, "change_rx_flow (%u), raise DTR", port->channel);
				UART_MDMCTRL(port) |= AU_UART_MDMCTRL_DT;
			}
			if (C_CFLAG(port) & CRTS)
			{
				if (port->autoflow_enabled)
				{
					SER_TRACE(ZONE_RX, "change_rx_flow (%u) autoflow - reenable RX int", port->channel);
					read_uart_rx_fifo(port);
					ENABLE_UART_RX_INTERRUPT(port);
				}
				else
				{
					SER_TRACE(ZONE_RX, "change_rx_flow (%u) - raise RTS", port->channel);
					UART_MDMCTRL(port) |= AU_UART_MDMCTRL_RT;
				}
			}
		}
		RESTORE_INTS;
	}

	// TODO - let's get a macro something like "INBOUND_XON_FC(port)"
	// and "OUTBOUND_XON_FC(port)" instead of using IXOFF for inbound
	// and IXON for outbound.  How weird!

	// What happens if the TX fifo is full and furthermore it is being
	// flow controlled?  XONing is less critical but XOFF needs to
	// happen in a timely order.  Presumably, transmits are not physically
	// blocked at this time since we're probably doing XON/XOFF in both
	// directions.
	if (C_IFLAG(port) & IXOFF)
	{
		if (EVENT_STATUS(port) & EV_IPALL)
		{
			// if we haven't even received a character yet don't send xoff.
			if (port->got_rx)
			{
				
				SER_TRACE(ZONE_RX, "change_rx_flow (%u), assert xon/xoff f/c with 0x%02x",
						port->channel, CC(port, VSTOP));
				port->forced_transmit_char = CC(port, VSTOP);
			}
		}
		else
		{
			// if we haven't even received a character yet, then don't send xon
			if (port->got_rx)
			{
				SER_TRACE(ZONE_RX, "change_rx_flow (%u), DE-assert xon/xoff f/c with 0x%02x",
					port->channel, CC(port, VSTART));
				port->forced_transmit_char = CC(port, VSTART);
			}
		}
	}

	if (port->forced_transmit_char)
	{
		DISABLE_INTS;
		write_uart_tx_fifo(port);
		RESTORE_INTS;
	}

	SER_TRACE(ZONE_RX, "-change_rx_flow (%u)", port->channel);
}

static
void
uart_enable(au1200_port_t *port)
{
	UART_ENABLE(port) = AU_UART_ENABLE_CE + AU_UART_ENABLE_E;
}


static
void
configure_uart_fifo_ctrl(au1200_port_t *port)
{
	uint32 uart_fifo_ctrl;

	uart_fifo_ctrl = 0;

	port->uart_rx_fifo_thresh 	= (HARDWARE_FLOW_CONFIGURED(port) && !USE_AUTOFLOW) ? 8 : 1;
	port->uart_tx_fifo_thresh	= (HARDWARE_FLOW_CONFIGURED(port) &&  USE_AUTOFLOW) ? 8 : 0;
	port->uart_tx_fifo_len		= (HARDWARE_FLOW_CONFIGURED(port) &&  USE_AUTOFLOW) ? 16 : 2;

	SER_TRACE(ZONE_INIT, "+config_uart_fifos (%u) - Rx Thresh = %d, Tx Thresh = %d, Tx Len = %d ", 
				port->channel,
				port->uart_rx_fifo_thresh,
				port->uart_tx_fifo_thresh,
				port->uart_tx_fifo_len
				);

	switch (port->uart_tx_fifo_thresh)
	{
		case 0:
			uart_fifo_ctrl |= AU_UART_FIFOCTRL_TFT_0;
			break;

		case 4:
			uart_fifo_ctrl |= AU_UART_FIFOCTRL_TFT_4;
			break;

		case 8:
			uart_fifo_ctrl |= AU_UART_FIFOCTRL_TFT_8;
			break;

		case 12:
			uart_fifo_ctrl |= AU_UART_FIFOCTRL_TFT_12;
			break;

		default:
			SER_ASSERT(0);
			break;
	}

	switch (port->uart_rx_fifo_thresh)
	{
		case 1:
			uart_fifo_ctrl |= AU_UART_FIFOCTRL_RFT_1;
			break;

		case 4:
			uart_fifo_ctrl |= AU_UART_FIFOCTRL_RFT_4;
			break;

		case 8:
			uart_fifo_ctrl |= AU_UART_FIFOCTRL_RFT_8;
			break;

		case 12:
			uart_fifo_ctrl |= AU_UART_FIFOCTRL_RFT_14;
			break;

		default:
			SER_ASSERT(0);
			break;
	}

	// Enable Fifo (FE) and reset Transmit and Receive Fifo's.
	uart_fifo_ctrl |= AU_UART_FIFOCTRL_FE | AU_UART_FIFOCTRL_TR | AU_UART_FIFOCTRL_RR;

	UART_FIFOCTRL(port) = uart_fifo_ctrl;

}

// TODO - these are kludged and need to be redone.
static
VOID
rtsup_fn(ULONG arg)
{
    INTERRUPT_SAVE_AREA;

    au1200_port_t * port = &ports[0];

	NA_UNUSED_PARAM(arg);


    DISABLE_INTS; // Protect estat
    EVENT_STATUS(port) &= ~EV_OPT;
    RESTORE_INTS;
    change_tx_flow(port);
}

static
VOID
rtsdown_fn(ULONG arg)
{
    INTERRUPT_SAVE_AREA;

    au1200_port_t * port = &ports[0];

	NA_UNUSED_PARAM(arg);


    if (C_CFLAG(port) & CRTSTOGGLE)
	{ // May have been turned off
		DISABLE_INTS;  //Protect MCR
		UART_MDMCTRL(port) &= ~AU_UART_MDMCTRL_RT;
		RESTORE_INTS;
    }
}


static
inline
int	rx_fifo_read(au1200_port_t * port, char *buffer, int max_len)
{
	int i;

	int len_to_copy = min(RX_FIFO_LEVEL(port), max_len);

	for (i = 0; i < len_to_copy; i++)
	{
		char c;

		RX_FIFO_GET_CHAR(port,c);

		buffer[i] = c;
	}

	return len_to_copy;
}


/*
 * 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 channel)
{
	// Initialize the port structure
    au1200_port_t * port = &ports[channel];

    if ( (channel >= max_serial_ports() ) || ( channel < 0) ) {
	return 1; // A non-existant port is always open.
    }

    /* Netos_serlinit not called */
    if (!port->configured) {
	return 1; // A non-existant port is always open.
    }

    return port->opened;
}



// SERIAL RAW DUMP CODE  - netos_serldump().
//	This section is used for dumping trace messages over serial port 1
//	if we get an ASSERT.  This code assumes that interrupts are disabled.

#define HEX_TO_ASCII(h)	( (h < 0x0a) ? h + '0' : (h - 0xa) + 'a' )
#define HI_NIBBLE(c)	( (c & 0xf0) >> 4)
#define LO_NIBBLE(c)	( (c & 0x0f)     )

const char *Signature = "TraceDmp";

static
struct {
	ULONG Read;
	ULONG Write;
	UCHAR Buf[16];
} InFifo;
	

#define FifoSize(f) 		sizeof(f.Buf)
#define FifoInc(f, ptr)		f.ptr = (f.ptr + 1) % FifoSize(f)
#define FifoWrite(f, c)		f.Buf[f.Write] = c; FifoInc(f, Write)
#define FifoPeek(f,ix)		f.Buf[(f.Read + ix) % FifoSize(f)]
#define	FifoInit(f)			f.Read = f.Write = 0;

static void dumpwait(ULONG UartNum);
static void dumpwritechar(ULONG UartNum, UCHAR c);
static UCHAR dumpreadchar(ULONG UartNum);
static VOID  dumptermios(au1200_port_t * port);

void	netos_serldump(PUCHAR Buf, ULONG Len)
{

	ULONG clkdiv;
	ULONG cpuclock = GetCpuFrequency();
	ULONG system_bus_divider = 2;
	ULONG baudrate = 115200;
	ULONG UartNum = 1;
	ULONG i;

	// Enable UART.
	WriteReg(AU_UART_ENABLE(UartNum), AU_UART_ENABLE_CE + AU_UART_ENABLE_E);

	// Enable AUTOFLOW
	WriteReg(AU_UART_MDMCTRL(UartNum), 0);
	WriteReg(AU_UART_AUTOFLOW(UartNum), AU_UART_AUTOFLOW_AE);

	// Configure UART FIFO CTRL
	WriteReg(AU_UART_FIFOCTRL(UartNum), 	AU_UART_FIFOCTRL_TFT_4	+
											AU_UART_FIFOCTRL_RFT_4	+
											AU_UART_FIFOCTRL_FE		+
											AU_UART_FIFOCTRL_TR		+
											AU_UART_FIFOCTRL_RR);

	// Configure Baud Rate for 115K
	// Calculate clock divider
	// AU1200 databook says that Baudrate =  CPU/(SD * 2 * CLKDIV * 16)
	// Multiply by 10 and round up by adding 5 and dividing by 10.
	clkdiv = (cpuclock * 10) / (system_bus_divider * 2 * baudrate * 16);
	clkdiv = (clkdiv + 5) / 10;

	WriteReg(AU_UART_CLKDIV(UartNum), clkdiv);

	// 8 bits, no parity, 1 stop bit.
	WriteReg(AU_UART_LINECTRL(UartNum), AU_UART_LINECTRL_WLS_8);

	// Set DTR, autoflow will handle RTS
	WriteReg(AU_UART_MDMCTRL(UartNum), AU_UART_MDMCTRL_DT);

	// Wait for user to synchronize but typing in special signature.
	dumpwait(UartNum);

	for (i = 0; i < Len; i++)
	{
		UCHAR c = Buf[i];
		UCHAR hi, lo;
		hi = HEX_TO_ASCII(HI_NIBBLE(c));
		lo = HEX_TO_ASCII(LO_NIBBLE(c));
		dumpwritechar(UartNum, hi);
		dumpwritechar(UartNum, lo);

		if ((i & 0x3f) == 0)
		{
			// Add new line every 128 characters
			dumpwritechar(UartNum, 0x0d);
			dumpwritechar(UartNum, 0x0a);
		}
	}
}

static void dumpwritechar(ULONG UartNum, UCHAR c)
{
	// Wait til TT bit is set.
	while (TRUE)
	{
		ULONG ls = ReadReg(AU_UART_LINESTAT(UartNum));
		if (ls &  AU_UART_LINESTAT_TT)
		{
			break;
		}
	}

	WriteReg(AU_UART_TXDATA(UartNum), c);
}


static void dumpwait(ULONG UartNum)
{
	int 	i;
	UCHAR 	c;

	FifoInit(InFifo);	

	// Fill Fifo til its size is that of "Signature".
	for (i = 0; i < strlen(Signature); i++)
	{
		c = dumpreadchar(UartNum);
		FifoWrite(InFifo, c);
	}

	while (TRUE)
	{
		// Compare input fifo against signature and break if mismatch
		for (i = 0; i < strlen(Signature); i++)
		{
			if (Signature[i] != FifoPeek(InFifo,i))
			{
				// mismatch
				break;
			}
		}
		if (i == strlen(Signature))
		{
			// we matched!!
			break;
		}
		// bump read pointer, get another character to replace it, and
		// run compare again.
		FifoInc(InFifo, Read);
		c = dumpreadchar(UartNum);
		FifoWrite(InFifo,c);
	}
}

static UCHAR dumpreadchar(ULONG UartNum)
{
	UCHAR c;

	// Wait til there's a character.
	while (TRUE)
	{
		ULONG ls = ReadReg(AU_UART_LINESTAT(UartNum));
		if (ls &  AU_UART_LINESTAT_DR)
		{
			break;
		}
	}

	c = ReadReg(AU_UART_RXDATA(UartNum));

	return c;
}

VOID dumpportinfo(VOID)
{
	int i;
	for (i = 0; i < 2; i++)
	{
	    au1200_port_t * port = &ports[i];
		SER_TRACE(ZONE_RX,  "Dumping info for port %u", i + 1);
		SER_TRACE(ZONE_RX,  "   Port is %s", port->opened ? "Open" : "Closed");

		if (!port->opened) continue;

		SER_TRACE(ZONE_RX,  "   Autoflow - %d", port->autoflow_enabled);

		SER_TRACE(ZONE_RX,  "   mdmstat shadow - CD%c DS%c CT%c",
								(port->uart_mdmstat_shadow & AU_UART_MDMSTAT_CD) ? '+' : '-',
								(port->uart_mdmstat_shadow & AU_UART_MDMSTAT_DS) ? '+' : '-',
								(port->uart_mdmstat_shadow & AU_UART_MDMSTAT_CT) ? '+' : '-'
				);

		SER_TRACE(ZONE_RX,  "   RX_FIFO_THRESH = %d, TX_FIFO_THRESH = %d, TX_FIFO_LEN = %d",
							port->uart_rx_fifo_thresh,
							port->uart_tx_fifo_thresh,
							port->uart_tx_fifo_len
							);

		dumptermios(port);

		SER_TRACE(ZONE_RX,  "   port event flags (0x%08x):", port->status.estat);
		SER_TRACE(ZONE_RX,  "     EV_OPU %d", port->status.estat & EV_OPU ? 1 : 0);
		SER_TRACE(ZONE_RX,  "     EV_OPS %d", port->status.estat & EV_OPS ? 1 : 0);
		SER_TRACE(ZONE_RX,  "     EV_OPX %d", port->status.estat & EV_OPX ? 1 : 0);
		SER_TRACE(ZONE_RX,  "     EV_OPH %d", port->status.estat & EV_OPH ? 1 : 0);
		SER_TRACE(ZONE_RX,  "     EV_OPT %d", port->status.estat & EV_OPT ? 1 : 0);
		SER_TRACE(ZONE_RX,  "     EV_IPU %d", port->status.estat & EV_IPU ? 1 : 0);
		SER_TRACE(ZONE_RX,  "     EV_IPS %d", port->status.estat & EV_IPS ? 1 : 0);
		SER_TRACE(ZONE_RX,  "     EV_IPA %d", port->status.estat & EV_IPA ? 1 : 0);
		SER_TRACE(ZONE_RX,  "     EV_TXB %d", port->status.estat & EV_TXB ? 1 : 0);
		SER_TRACE(ZONE_RX,  "     EV_TXI %d", port->status.estat & EV_TXI ? 1 : 0);
		SER_TRACE(ZONE_RX,  "     EV_TXF %d", port->status.estat & EV_TXF ? 1 : 0);
		SER_TRACE(ZONE_RX,  "     EV_RXB %d", port->status.estat & EV_RXB ? 1 : 0);
	}
}


static VOID dumptermios(au1200_port_t * port)
{
	struct termios *t = &port->current_termios;

	SER_TRACE(ZONE_RX,  "   t->c_iflags (0x%08x):", t->c_iflag);
	SER_TRACE(ZONE_RX,  "      IGNBRK  %d", t->c_iflag & IGNBRK   	? 1 : 0);
	SER_TRACE(ZONE_RX,  "      BRKINT  %d", t->c_iflag & BRKINT   	? 1 : 0);
	SER_TRACE(ZONE_RX,  "      IGNPAR  %d", t->c_iflag & IGNPAR   	? 1 : 0);
	SER_TRACE(ZONE_RX,  "      PARMRK  %d", t->c_iflag & PARMRK   	? 1 : 0);
	SER_TRACE(ZONE_RX,  "      INPCK   %d", t->c_iflag & INPCK    	? 1 : 0);
	SER_TRACE(ZONE_RX,  "      ISTRIP  %d", t->c_iflag & ISTRIP   	? 1 : 0);
	SER_TRACE(ZONE_RX,  "      IXON    %d", t->c_iflag & IXON     	? 1 : 0);
	SER_TRACE(ZONE_RX,  "      IXANY   %d", t->c_iflag & IXANY    	? 1 : 0);
	SER_TRACE(ZONE_RX,  "      IXOFF   %d", t->c_iflag & IXOFF    	? 1 : 0);
	SER_TRACE(ZONE_RX,  "      DOSMODE %d", t->c_iflag & DOSMODE  	? 1 : 0);

	SER_TRACE(ZONE_RX,  "   t->c_oflags (0x%08x):", t->c_oflag);
	SER_TRACE(ZONE_RX,  "      ONLCR   %d", t->c_oflag & ONLCR  		? 1 : 0);
	SER_TRACE(ZONE_RX,  "      OCRNL   %d", t->c_oflag & OCRNL  		? 1 : 0);
	SER_TRACE(ZONE_RX,  "      ONOCR   %d", t->c_oflag & ONOCR  		? 1 : 0);
	SER_TRACE(ZONE_RX,  "      ONLRET  %d", t->c_oflag & ONLRET 		? 1 : 0);
	SER_TRACE(ZONE_RX,  "      TABDLY  %d", t->c_oflag & TABDLY 		? 1 : 0);

	SER_TRACE(ZONE_RX,  "   t->c_xflags (0x%08x):", t->c_xflag);
	SER_TRACE(ZONE_RX,  "      XPAR     %d", t->c_xflag & XPAR   	? 1 : 0);
	SER_TRACE(ZONE_RX,  "      XEDATA   %d", t->c_xflag & XEDATA 	? 1 : 0);
	SER_TRACE(ZONE_RX,  "      XTOSS    %d", t->c_xflag & XTOSS  	? 1 : 0);
	SER_TRACE(ZONE_RX,  "      XIXON    %d", t->c_xflag & XIXON      ? 1 : 0);
	SER_TRACE(ZONE_RX,  "      XIXON_U  %d", t->c_xflag & XIXON_UNIX ? 1 : 0);

	SER_TRACE(ZONE_RX,  "   t->c_cflags (0x%08x):", t->c_cflag);
}
