/*
 *************************************************************************************************
 *
 * Emulation of the NS9750 Serial Interfaces 
 *
 *  State: working in non DMA mode. Timing of Modem status signals 
 *  	   not correct.
 *
 * Copyright 2004 Jochen Karrer. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 *   1. Redistributions of source code must retain the above copyright notice, this list of
 *       conditions and the following disclaimer.
 *
 *   2. Redistributions in binary form must reproduce the above copyright notice, this list
 *       of conditions and the following disclaimer in the documentation and/or other materials
 *       provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY Jochen Karrer ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * The views and conclusions contained in the software and documentation are those of the
 * authors and should not be interpreted as representing official policies, either expressed
 * or implied, of Jochen Karrer.
 *
 *************************************************************************************************
 */


#include <string.h>
#include <fcntl.h>
#include <stdint.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <termios.h>
#include <sys/ioctl.h>
#include "bus.h"
#include "cycletimer.h"
#include "ns9750_serial.h"
#include "ns9750_timer.h"
#include "ns9750_bbus.h"
#include "ns9750_bbdma.h"
#include "fio.h"
#include "configfile.h"
#include "signode.h"
#include "sgstring.h"

#if 0
#define dbgprintf(x...) { fprintf(stderr,x); }
#else
#define dbgprintf(x...)
#endif

#define RX_FIFO_SIZE (32)
#define RX_FIFO_MASK (RX_FIFO_SIZE-1)
#define RX_FIFO_COUNT(ser) (((RX_FIFO_SIZE + (ser)->rxfifo_wp - (ser)->rxfifo_rp)) & RX_FIFO_MASK)
#define RX_FIFO_ROOM(ser) (RX_FIFO_SIZE - RX_FIFO_COUNT(ser) - 1)

#define TX_FIFO_SIZE (32)
#define TX_FIFO_MASK (TX_FIFO_SIZE-1)
#define TX_FIFO_COUNT(ser) ((((ser)->txfifo_wp-(ser)->txfifo_rp)+TX_FIFO_SIZE)&TX_FIFO_MASK)
#define TX_FIFO_ROOM(ser) (TX_FIFO_SIZE-TX_FIFO_COUNT(ser)-1)


typedef struct Serial {
	char *name;
	uint32_t txcount;
	/* registers */
	uint32_t ctrlA;
	uint32_t ctrlB;
	uint32_t statA;
	uint32_t rate; 
	uint32_t fifo;
	uint32_t rxbuf_gap;
	uint32_t rxchar_gap;
	uint32_t rxmatch;
	uint32_t rxmatch_mask;
	uint32_t flow_ctrl;
	uint32_t flow_force;
	uint8_t rx_fifo[RX_FIFO_SIZE];
	int rxfifo_wp;
	int rxfifo_rp;

	uint8_t tx_fifo[TX_FIFO_SIZE];
	unsigned int txfifo_wp;
	unsigned int txfifo_rp;
	SigNode *RxDmaGnt;
	SigNode *TxDmaReq;
	int tx_dma_request; // local copy 

	int tx_irq_no;
	int rx_irq_no;
	int rx_interrupt_posted;
	int tx_interrupt_posted;

	
	int fd;
	int emu_baudrate;
	FIO_FileHandler input_fh;
	FIO_FileHandler output_fh;
	int ifh_is_active;
	int ofh_is_active;
	BBusDMA_Channel *rx_dmachan;
	BBusDMA_Channel *tx_dmachan;
	SigNode *endianNode;
	SigTrace *endianTrace;
	int endian;
} Serial;

struct SerialInfo {
	char *name;
	uint32_t base;
	int rx_irq;
	int tx_irq;
	int rxdma_chan;
	int txdma_chan;
};

static struct SerialInfo serials[]={
	{
		name: "serialA",
		base: SER_BASE_A,
		rx_irq: 4,
		tx_irq: 5,
		rxdma_chan: BBDMA_CHAN_1,
		txdma_chan: BBDMA_CHAN_2
	},
	{
		name: "serialB",
		base: SER_BASE_B,
		rx_irq: 2,
		tx_irq: 3,
		rxdma_chan: BBDMA_CHAN_3,
		txdma_chan: BBDMA_CHAN_4
	},
	{
		name: "serialC",
		base: SER_BASE_C,
		rx_irq: 6,
		tx_irq: 7,
		rxdma_chan: BBDMA_CHAN_5,
		txdma_chan: BBDMA_CHAN_6
	},
	{
		name: "serialD",
		base: SER_BASE_D,
		rx_irq: 8,
		tx_irq: 9,
		rxdma_chan: BBDMA_CHAN_7,
		txdma_chan: BBDMA_CHAN_8
	},
};

static inline void enable_rx(Serial *ser);
static inline void disable_rx(Serial *ser); 
static inline void enable_tx(Serial *ser);
static inline void disable_tx(Serial *ser); 

static void 
serial_close(Serial *ser) {
	if(ser->fd<0) {
		return;
	}
	disable_rx(ser);
	disable_tx(ser);
	close(ser->fd);
	ser->fd = -1;
	return;
}

/*
 * -------------------------------------------------------
 * change_endian
 * 	Invoked when the Endian Signal Line changes
 * -------------------------------------------------------
 */
static void 
change_endian(SigNode *node,int value,void *clientData)
{
	Serial *ser = clientData;
	if(value == SIG_HIGH) {
		fprintf(stderr,"Serial now big endian\n");
		ser->endian = en_BIG_ENDIAN;
	} else if(value==SIG_LOW) {
		ser->endian = en_LITTLE_ENDIAN;
	} else {
		fprintf(stderr,"NS9750 Serial: Endian is neither Little nor Big\n");
		exit(3424);
	}
}

static void
update_rx_interrupt(Serial *ser) {
	uint32_t rints = SER_RRDY  | SER_RHALF | SER_RBC | SER_DCDI | SER_RII | SER_DSRI;
	if(ser->statA & ser->ctrlA & rints) {
		if(!ser->rx_interrupt_posted) {
			BBus_PostIRQ(ser->rx_irq_no);
			ser->rx_interrupt_posted=1;
		}
	} else {
		if(ser->rx_interrupt_posted) {
			BBus_UnPostIRQ(ser->rx_irq_no);
			ser->rx_interrupt_posted=0;
		}
	}
}

static void
update_tx_interrupt(Serial *ser) {
	uint32_t tints =  SER_CTSI |  SER_TRDY | SER_THALF | SER_TEMPTY; 
	if(ser->statA & ser->ctrlA & tints) {
		if(!ser->tx_interrupt_posted) {
			BBus_PostIRQ(ser->tx_irq_no);
			ser->tx_interrupt_posted=1;
		}
	} else {
		if(ser->tx_interrupt_posted) {
			BBus_UnPostIRQ(ser->tx_irq_no);
			ser->tx_interrupt_posted=0;
		}
	}
}

/*
 * ----------------------------------------------------
 * Set TX-DMA request when there is room in tx-fifo
 * and clear when tx-fifo is full
 * ----------------------------------------------------
 */
static inline void
set_txdma_request(Serial *ser) 
{
	if(!ser->tx_dma_request) {
		SigNode_Set(ser->TxDmaReq,SIG_HIGH);
		ser->tx_dma_request = 1;
	}
}

static inline void
clear_txdma_request(Serial *ser) 
{
	if(ser->tx_dma_request) {
		SigNode_Set(ser->TxDmaReq,SIG_LOW);
		ser->tx_dma_request = 0;
	}
}

static inline void
update_txdma_request(Serial *ser) {
	int room=TX_FIFO_ROOM(ser);
	if((room>=4) && (ser->ctrlA & SER_ETXDMA)) {
		set_txdma_request(ser);
	} else {
		clear_txdma_request(ser);
	}
}

/*
 * --------------------------------------------------------
 * Filehandler for TX-Fifo
 * 	Write chars from TX-Fifo to file as long
 *	as TX-Fifo is not empty and write would not block
 * --------------------------------------------------------
 */
static int
serial_output(void *cd,int mask) {
	Serial *ser=cd;
	int fill;
	int room;
	while(ser->txfifo_rp!=ser->txfifo_wp) {
		int count,len;
		fill=TX_FIFO_COUNT(ser);
		len=fill;	
		if((ser->txfifo_rp+fill)>TX_FIFO_SIZE) {
			len = TX_FIFO_SIZE - ser->txfifo_rp;
		}
		count=write(ser->fd,&ser->tx_fifo[ser->txfifo_rp],len);
		if(count>0) {
			ser->txfifo_rp = (ser->txfifo_rp+count)%TX_FIFO_SIZE;
		} else if (count==0) { // EOF
			fprintf(stderr,"EOF on output\n");
			serial_close(ser);	
			break;
		} else if ((count<0) && (errno==EAGAIN)) {
			break;
		} else {
			fprintf(stderr,"Error on output\n");
			serial_close(ser);	
			break;
		}	
	}	
	room=TX_FIFO_ROOM(ser);
	if(room>=(TX_FIFO_SIZE/2)) {
		if(!(ser->statA & SER_THALF)) {
			ser->statA |= SER_THALF | SER_TRDY;
		}
		update_tx_interrupt(ser);
		if(ser->ctrlA & SER_ETXDMA) {
			set_txdma_request(ser);
		}
	} else if(room>=4) {
		if(!(ser->statA & SER_TRDY)) {
			ser->statA |= SER_TRDY;
			update_tx_interrupt(ser);
		}
		if(ser->ctrlA & SER_ETXDMA) {
			set_txdma_request(ser);
		}
	} 
	if(TX_FIFO_COUNT(ser) == 0) {
		disable_tx(ser);
		ser->statA |= SER_TEMPTY;
	}
	return 0;
}
/*
 * --------------------------------------------
 * Write one character to the TX-Fifo
 * --------------------------------------------
 */
static void 
serial_tx_fifo_put(Serial *ser,uint8_t c) {
	int room;
	ser->tx_fifo[ser->txfifo_wp] = c;
	ser->txfifo_wp = (ser->txfifo_wp+1) % TX_FIFO_SIZE;
	ser->statA &= ~SER_TEMPTY; 
	room=TX_FIFO_ROOM(ser);
	if((room<(TX_FIFO_SIZE/2)) && (ser->statA & SER_THALF)) {
		ser->statA &= ~SER_THALF; 
		update_tx_interrupt(ser);
	}
	if((room<4))  {
		if(ser->statA & SER_TRDY) {
			ser->statA=ser->statA & ~(SER_TRDY|SER_THALF);
			update_tx_interrupt(ser);
		}
		clear_txdma_request(ser);
	}
	enable_tx(ser);
}
/*
 * ---------------------------------------------------------------
 * serial_txdma_sink
 * 	This is the callback handler invoked by the
 *	DMA Controller when data is available and TX-DMA request 
 *	is set. It returns the number of bytes written to
 *	the output fifo
 * ---------------------------------------------------------------
 */

static int
serial_txdma_sink(BBusDMA_Channel *chan, uint8_t *buf,int len,void *clientData) 
{
	Serial *ser = clientData;	
	int count;
	for(count=0;count<len;count++) {
		if(!ser->tx_dma_request) {
			break;
		}
		serial_tx_fifo_put(ser,buf[count]);
		count++;
	}
	return count;
}

/*
 * --------------------------------------------------------------------
 * Update terminal settings whenever a register is changed which
 * affects speed or parameters 
 * --------------------------------------------------------------------
 */
static void
update_serconfig(Serial *ser) 
{
	int rx_baudrate;	
	int tx_baudrate;	
	int clksource;
	uint32_t clk_rate=0;
	int rdiv=1,tdiv=1;
	int N,divisor;
	tcflag_t bits;
	tcflag_t parodd;
	tcflag_t parenb;
	tcflag_t crtscts;
	struct termios termios;
	if(ser->fd<0) {
		return;
	}
	if(!isatty(ser->fd)) {
		return;
	}
	if(ser->ctrlA & SER_CTSTX) {
		crtscts=CRTSCTS;
	} else {
		crtscts=0;
	}
	if(ser->ctrlA & SER_EPS) {
		parodd=0;
	} else {
		parodd=PARODD;
	}
	if(ser->ctrlA & SER_PE) {
		parenb=PARENB;
	} else {
		parenb=0;
	}
	switch(ser->ctrlA & SER_WLS_MASK) {
		case SER_WLS_5: 
			bits=CS5; break;
		case SER_WLS_6: 
			bits=CS6; break;
		case  SER_WLS_7:
			bits=CS7; break;
		case  SER_WLS_8: 
			bits=CS8; break;
		/* Can not be reached */
		default:
			bits=CS8; break;
	}
	clksource = (ser->rate&SER_CLKMUX_MASK)>>SER_CLKMUX_SHIFT;
	switch(clksource) {
		case 0:
			// doesn't work in revision 0 of CPU
			break;
			
		case 1: /* Source is BCLK */
			clk_rate = CycleTimerRate_Get() / 2;
			break;

		case 2:
			// not implemented
		case 3:
			// not implemented
		break;
	}
	if(!clk_rate) {
		return;
	}
	switch(ser->rate & SER_RDCR_MASK) {
		case	SER_RDCR_1X:
			rdiv=1;
		case 	SER_RDCR_8X:
			rdiv=8;
		case    SER_RDCR_16X:
			rdiv=16;
		case	SER_RDCR_32X:
			rdiv=32;
		break;
	}
	switch(ser->rate & SER_TDCR_MASK) {
		case	SER_TDCR_1X:
			tdiv=1;
		case 	SER_TDCR_8X:
			tdiv=8;
		case    SER_TDCR_16X:
			tdiv=16;
		case	SER_TDCR_32X:
			tdiv=32;
		break;
	}
	N=ser->rate & SER_N_MASK;
	divisor=N+1;
	rx_baudrate=clk_rate/(rdiv*divisor);
	tx_baudrate=clk_rate/(tdiv*divisor);
	if(rx_baudrate!=tx_baudrate) {
		fprintf(stderr,"Rx baudrate %d Tx baudrate %d not the same !\n",rx_baudrate,tx_baudrate);
	} else {
//		fprintf(stderr,"Baudrate %d\n",rx_baudrate);
	}
	if((rx_baudrate > 440000) && (rx_baudrate < 480000)) {
		ser->emu_baudrate=B460800;
	} else if((rx_baudrate > 220000) && (rx_baudrate < 240000)) {
		ser->emu_baudrate=B230400;
	} else if((rx_baudrate > 110000) && (rx_baudrate < 120000)) {
		ser->emu_baudrate=B115200;
	} else if((rx_baudrate > 55000) && (rx_baudrate < 60000)) {
		ser->emu_baudrate=B57600;
	} else if((rx_baudrate > 36571 ) && (rx_baudrate < 40320)) {
		ser->emu_baudrate=B38400;
	} else if((rx_baudrate > 18285 ) && (rx_baudrate < 20160)) {
		ser->emu_baudrate=B19200;
	} else if((rx_baudrate > 9142 ) && (rx_baudrate < 10080)) {
		ser->emu_baudrate=B9600;
	} else if((rx_baudrate > 4571 ) && (rx_baudrate < 5040)) {
		ser->emu_baudrate=B4800;
	} else if((rx_baudrate > 2285 ) && (rx_baudrate < 2520)) {
		ser->emu_baudrate=B2400;
	} else if((rx_baudrate > 1890 ) && (rx_baudrate < 1714)) {
		ser->emu_baudrate=B1800;
	} else if((rx_baudrate > 1142 ) && (rx_baudrate < 1260)) {
		ser->emu_baudrate=B1200;
	} else if((rx_baudrate > 570 ) && (rx_baudrate < 630)) {
		ser->emu_baudrate=B600;
	} else if((rx_baudrate > 285 ) && (rx_baudrate < 315)) {
		ser->emu_baudrate=B300;
	} else if((rx_baudrate > 190 ) && (rx_baudrate < 210)) {
		ser->emu_baudrate=B200;
	} else if((rx_baudrate > 142 ) && (rx_baudrate < 158)) {
		ser->emu_baudrate=B150;
	} else if((rx_baudrate > 128 ) && (rx_baudrate < 141)) {
		ser->emu_baudrate=B134;
	} else if((rx_baudrate > 105 ) && (rx_baudrate < 116)) {
		ser->emu_baudrate=B110;
	} else if((rx_baudrate > 71 ) && (rx_baudrate < 79)) {
		ser->emu_baudrate=B75;
	} else if((rx_baudrate > 47 ) && (rx_baudrate < 53)) {
		ser->emu_baudrate=B50;
	} else {
		fprintf(stderr,"NS9750-Serial emulator: Can not handle Baudrate %d\n",rx_baudrate);
		ser->emu_baudrate=B0;
	}
	dbgprintf("NS9750 %s: baudrate %d\n",ser->name,rx_baudrate);
	if(tcgetattr(ser->fd,&termios)<0) {
		fprintf(stderr,"Can not  get terminal attributes\n");
		return;
	}
	if(ser->fd) {
		cfmakeraw(&termios);
	} else {
		termios.c_lflag &= ~(ISIG);
	}
	if(cfsetispeed ( &termios, ser->emu_baudrate)<0) {
		fprintf(stderr,"Can not change Baudrate\n");
	}
	if(cfsetospeed ( &termios, ser->emu_baudrate)<0) {
		fprintf(stderr,"Can not change Baudrate\n");
	}
	termios.c_cflag &= ~(CSIZE|PARENB|PARODD|CRTSCTS);
	termios.c_cflag |= bits | parenb | parodd | crtscts;

	/* Shit: TCSADRAIN would be better but tcsettattr seems to block */
	if(tcsetattr(ser->fd,TCSANOW,&termios)<0) {
		fprintf(stderr,"Can not  set terminal attributes\n");
		return;
	}
	return;
}

static void 
ser_ctrla_write(void *clientData,uint32_t value,uint32_t address,int rqlen) {
	Serial *ser=clientData;
	uint32_t diff = value ^ ser->ctrlA;
	//fprintf(stderr,"Write ctrla %08x\n",value);
	if((value&SER_CE) && !(ser->ctrlA&SER_CE)) {
		ser->rxfifo_rp = ser->rxfifo_wp=0;	
		ser->statA=SER_TRDY | SER_THALF;
	}
	if(!(value&SER_CE)){
		return;	
	}
	ser->ctrlA=value;
	if(diff & (SER_PE | SER_STP | SER_WLS_MASK | SER_CTSTX | SER_RTSRX)) {
		update_serconfig(ser);
	}
	if(diff & (SER_DTR | SER_RTS)) {
		unsigned int clear=0;
		unsigned int set=0;
		if(value & SER_DTR) {
			set |= TIOCM_DTR;
		} else {
			clear |= TIOCM_DTR;
		}
		if(value & SER_RTS) {
			set |= TIOCM_RTS;
		} else {
			clear |= TIOCM_RTS;
		}
		if(isatty(ser->fd) && clear) 
			ioctl(ser->fd,TIOCMBIC,clear);
		if(isatty(ser->fd) && set)
			ioctl(ser->fd,TIOCMSET,set);
	}
	if(value & SER_ETXDMA) {
		fprintf(stderr,"NS9750 %s: TX-DMA enabled\n",ser->name);
	}
	if(value & SER_ERXDMA) {
		fprintf(stderr,"NS9750 Serial: RX-DMA mode not implemented\n");
	}
	if(diff & SER_ETXDMA) {
		update_txdma_request(ser);
	}
	update_tx_interrupt(ser);
	dbgprintf("New config %08x\n",value);
	return;
}

static uint32_t 
ser_ctrla_read(void *clientData,uint32_t address,int rqlen) {
	Serial *ser=clientData;
	return ser->ctrlA;
}

static void 
ser_ctrlb_write(void *clientData,uint32_t value,uint32_t address,int rqlen) {
	Serial *ser=clientData;
	uint32_t diff = value ^ ser->ctrlB;
	ser->ctrlB=value;
	if(diff & (SER_RTSTX)) {
		update_serconfig(ser);
	}
	return;
}

static uint32_t 
ser_ctrlb_read(void *clientData,uint32_t address,int rqlen) {
	Serial *ser=clientData;
	return ser->ctrlB;	
}
static uint32_t 
ser_rate_read(void *clientData,uint32_t address,int rqlen) {
	Serial *ser=clientData;
	return ser->rate;	
}

static void 
ser_rate_write(void *clientData,uint32_t value,uint32_t address,int rqlen) {
	Serial *ser=clientData;
	ser->rate=value;
	update_serconfig(ser);
	//fprintf(stderr,"Ser rate reg %08x\n",value);
	return;
}

static uint32_t 
ser_rxfifo_read(void *clientData,uint32_t address,int rqlen) {
	Serial *ser=clientData;
	int bytes;
	int i;
	int fifocount;
	unsigned long data=0;
	//fprintf(stderr,"Read from Rx-Fifo\n");
	if(!(ser->statA&SER_RRDY)) {
		fprintf(stderr,"Nix in der Fifo\n");
		update_rx_interrupt(ser);
		return 0;
	}
	bytes=(ser->statA & SER_RXFDB) >> SER_RXFDB_SHIFT;
	if(!bytes) {
		bytes=4;
	}
	//fprintf(stderr,"bytes %d, fifobytes %d stata %08x\n",bytes,ser->rxfifo_bytes,ser->statA);
	if(bytes > RX_FIFO_COUNT(ser)) {
		update_rx_interrupt(ser);
		fprintf(stderr,"Emulator Bug in line %d file %s\n",__LINE__,__FILE__);
		return 0;
	} 
	for(i=0;i<bytes;i++) {
		if(ser->endian == en_LITTLE_ENDIAN) {
			data=data|(ser->rx_fifo[ser->rxfifo_rp]<<(8*i));	
		} else if(ser->endian == en_BIG_ENDIAN) {
			data=data|(ser->rx_fifo[ser->rxfifo_rp]<<(8*(3-i)));	
		} 
		ser->rxfifo_rp = (ser->rxfifo_rp+1) & RX_FIFO_MASK;
	}
	
	fifocount=RX_FIFO_COUNT(ser);
	if(fifocount>=4) { 
		ser->statA=(ser->statA & ~(SER_RBC|SER_RXFDB)) | SER_RRDY;
	} else if(fifocount>0) {
		ser->statA = (ser->statA & ~(SER_RRDY|SER_RXFDB)) | SER_RBC;
	} else {
		ser->statA = ser->statA & ~(SER_RXFDB | SER_RRDY | SER_RBC); 
	}
	if(fifocount < 20) {
		ser->statA = ser->statA & ~(SER_RHALF); 
	} 
	if(RX_FIFO_ROOM(ser) <  4) {
		ser->statA = ser->statA & ~(SER_RFS); 
	}
	enable_rx(ser);
	update_rx_interrupt(ser);
	return data;
}

static void 
ser_txfifo_write(void *clientData,uint32_t value,uint32_t address,int rqlen) {
	int i;
	Serial *ser=clientData;
	for(i=0;i<rqlen;i++) {
		if(ser->endian == en_LITTLE_ENDIAN) {
			serial_tx_fifo_put(ser,(value>>(i<<3))&0xff);
		} else if(ser->endian == en_BIG_ENDIAN) {
			serial_tx_fifo_put(ser,(value>>((rqlen-1-i)<<3))&0xff);
		}
	}
	return;
}

static uint32_t 
ser_stata_read(void *clientData,uint32_t address,int rqlen) {
	unsigned int host_status;
	Serial *ser=clientData;
	uint32_t stata=ser->statA;
	uint32_t diff;
	if(stata&SER_RRDY) {
		int bytes=RX_FIFO_COUNT(ser);
		if(bytes >= 4) {
			bytes=4;
		}
		if(!bytes) {
			stata=stata&~SER_RRDY;
		} else {
			stata = (stata & ~SER_RXFDB) | ((bytes&3)<<SER_RXFDB_SHIFT); 
		}
	}

	//fprintf(stderr,"stata read RRDY %08x\n",stata);
	if(isatty(ser->fd) && (ioctl(ser->fd,TIOCMGET,&host_status)>=0)) {
		stata=stata & ~(SER_DCD | SER_RI | SER_DSR | SER_CTS);
		if(host_status & TIOCM_CAR) {
			stata |= SER_DCD;
		}
		if(host_status & TIOCM_RNG) {
			stata |= SER_RI;
		}
		if(host_status & TIOCM_DSR) {
			stata |= SER_DSR;
		}
		if(host_status & TIOCM_CTS) {
			stata |= SER_CTS;
		}
	}
	diff = stata ^ ser->statA;
	stata = stata | ((diff & (SER_DCD | SER_RI | SER_DSR | SER_CTS))>>12);
	ser->statA = stata;
	return ser->statA;
}
/* 
 * -----------------------------------------
 * acknowledging RBC sets RRDY
 * -----------------------------------------
 */
static void 
ser_stata_write(void *clientData,uint32_t value,uint32_t address,int rqlen) {
	Serial *ser=clientData;
	uint32_t clear_ints;
	if(ser->statA & SER_RBC) {
		if(value & SER_RBC) {
			ser->statA = (ser->statA & ~SER_RBC)|SER_RRDY;
		}
	}
	clear_ints = value & (SER_DCDI|SER_RII|SER_DSRI|SER_CTSI);
	ser->statA ^= clear_ints;	
	return;
}

/*
 * ------------------------------------
 * Put one byte to the rxfifo
 * ------------------------------------
 */
static inline int  
serial_rx_char(Serial *ser,uint8_t c) {
	int room = RX_FIFO_ROOM(ser);
	if(room<1) {
		return -1;
	}
	ser->rx_fifo[ser->rxfifo_wp]=c;
	ser->rxfifo_wp=(ser->rxfifo_wp+1) & RX_FIFO_MASK;	
	if(room==1) {
		disable_rx(ser);
		return 0;
	}
	return 1;
}

static int 
serial_input(void *cd,int mask) {
	Serial *ser = cd;
	int fifocount;
	if(ser->fd<0) {
		fprintf(stderr,"Serial input with illegal fd %d\n",ser->fd);
		return -1;
	}
	while(1) {
		char c;
		int count=read(ser->fd,&c,1);
		if(count==1) {
			serial_rx_char(ser,c);
			//fprintf(stdout,"Console got %c\n",c);
		} else if (count==0) { // EOF
			fprintf(stderr,"EOF reading from serial\n");
			serial_close(ser);	
			break;
		} else if ((count<0) && (errno==EAGAIN)) {
			break;
		} else {
			fprintf(stderr,"Error on input\n");
			serial_close(ser);	
			break;
		}	
	}	
	if(!(ser->ctrlA & SER_CE)) {
		fprintf(stderr,"Serial line not yet enabled\n");
		return 0; 
	}
	fifocount = RX_FIFO_COUNT(ser);
	if(fifocount) {
		if(fifocount>=4) {
			ser->statA = (ser->statA & ~SER_RBC) | SER_RRDY;
		} else {
			ser->statA |= SER_RBC;
		}
		if(fifocount > 20) {
			ser->statA |= SER_RHALF;
		}
		if(RX_FIFO_ROOM(ser) < 4) {
			ser->statA |= SER_RFS;
		}
		update_rx_interrupt(ser);
	} 
	return 0;
}

/*
 * ------------------------------------------------------
 * Flow control for receiver
 * ------------------------------------------------------
 */
static inline void
enable_rx(Serial *ser) {
	if((ser->fd >= 0) && !(ser->ifh_is_active)) {
		FIO_AddFileHandler(&ser->input_fh,ser->fd,FIO_READABLE,serial_input,ser);
	}
	ser->ifh_is_active=1;
}

static inline void
disable_rx(Serial *ser) {
	if(ser->ifh_is_active) {
		FIO_RemoveFileHandler(&ser->input_fh);
	}
	ser->ifh_is_active=0;
}
static inline void
enable_tx(Serial *ser) {
	if((ser->fd>=0) && !(ser->ofh_is_active)) {
		FIO_AddFileHandler(&ser->output_fh,ser->fd,FIO_WRITABLE,serial_output,ser);
	}
	ser->ofh_is_active=1;
}
static inline void
disable_tx(Serial *ser) {
	if(ser->ofh_is_active) {
		FIO_RemoveFileHandler(&ser->output_fh);
	}
	ser->ofh_is_active=0;
}

void
TerminalRestore(int fd) {
        struct termios term;
        if(tcgetattr(fd,&term)<0) {
                perror("can't restore terminal settings\n");
                return;
        }
        term.c_lflag |= (ECHO|ECHONL|ICANON|ISIG|IEXTEN);
        if(tcsetattr(fd,TCSAFLUSH,&term)<0) {
                perror("can't restore terminal settings");
                return;
        }
}
void
TerminalConfigure(int fd) {
        struct termios term;
        if(tcgetattr(fd,&term)<0) {
                perror("can't restore terminal settings\n");
                return;
        }
        term.c_lflag  &= ~(ECHO|ECHONL|ICANON|IEXTEN);
        if(tcsetattr(fd,TCSAFLUSH,&term)<0) {
                perror("can't restore terminal settings");
                return;
        }
}

/*
 * ----------------------------------------------------
 * Exithandler is inserted into the chain which is
 * done on Exit()
 * ----------------------------------------------------
 */
void 
TerminalExit(void) {
	TerminalRestore(0);
}

void
sig_term(int sig) {
	fprintf(stderr,"SIGTERM\n");
	exit(0);
}

void
sig_int(int sig) {
	fprintf(stderr,"SIGINT\n");
	exit(0);
}
void
sig_cont(int sig) {
	fprintf(stderr,"SIGCONT\n");
	TerminalConfigure(0);
}
void
sig_stop(int sig) {
	fprintf(stderr,"SIGSTOP\n");
	TerminalRestore(0);
}

static void 
ser_rxbuf_gap_write(void *clientData,uint32_t value,uint32_t address,int rqlen) 
{
	Serial *ser=clientData;
	ser->rxbuf_gap = value;
	if(value & 0x7fff0000) {
		fprintf(stderr,"Serial: Illegal value in rxbuf_gap: %08x\n",value);
	}
}

static uint32_t 
ser_rxbuf_gap_read(void *clientData,uint32_t address,int rqlen) 
{
	Serial *ser=clientData;
	return  ser->rxbuf_gap;
}
static void 
ser_rxchar_gap_write(void *clientData,uint32_t value,uint32_t address,int rqlen) 
{
	Serial *ser=clientData;
	ser->rxchar_gap = value;
	if(value & 0x7ff00000) {
		fprintf(stderr,"Serial: Illegal value in rxchar_gap: %08x\n",value);
	}

}
static uint32_t 
ser_rxchar_gap_read(void *clientData,uint32_t address,int rqlen) 
{
	Serial *ser=clientData;
	return  ser->rxchar_gap;
}
static void 
ser_rxmatch_write(void *clientData,uint32_t value,uint32_t address,int rqlen) 
{
	Serial *ser=clientData;
	ser->rxmatch = value;	
	fprintf(stderr,"Serial: Warning, RX-Match emulation not implemented: value 0x%08x\n",value);

}
static uint32_t 
ser_rxmatch_read(void *clientData,uint32_t address,int rqlen) 
{
	Serial *ser=clientData;
	return ser->rxmatch;	

}
static void 
ser_rxmatch_mask_write(void *clientData,uint32_t value,uint32_t address,int rqlen) 
{
	Serial *ser=clientData;
	ser->rxmatch_mask = value;	
	fprintf(stderr,"Serial: Warning, RX-Match emulation not implemented\n");

}
static uint32_t 
ser_rxmatch_mask_read(void *clientData,uint32_t address,int rqlen) 
{
	Serial *ser=clientData;
	return ser->rxmatch_mask;	
}

static void 
ser_flow_ctrl_write(void *clientData,uint32_t value,uint32_t address,int rqlen) 
{
	Serial *ser=clientData;
	ser->flow_ctrl = value;
	fprintf(stderr,"Serial: Warning, Flow Control register not implemented\n");
	return ;
}
static uint32_t 
ser_flow_ctrl_read(void *clientData,uint32_t address,int rqlen) 
{
	Serial *ser=clientData;
	return ser->flow_ctrl;	

}

static void 
ser_flow_force_write(void *clientData,uint32_t value,uint32_t address,int rqlen) 
{

	Serial *ser = clientData;
	ser->flow_force = value;
	fprintf(stderr,"Serial: Warning, Flow Force register not implemented\n");
}

static uint32_t 
ser_flow_force_read(void *clientData,uint32_t address,int rqlen) 
{
	Serial *ser = clientData;
	return ser->flow_force;
}

static Serial * 
NS9750_SerialNew(BBusDMACtrl *bbdma,int index) {

	uint32_t base=serials[index].base;
	int rx_irq=serials[index].rx_irq;
	int tx_irq=serials[index].tx_irq;
	int rxdma_chan = serials[index].rxdma_chan;
	int txdma_chan = serials[index].txdma_chan;
	char *serial_name=serials[index].name;	
	char *filename;
	Serial *ser = sg_new(Serial);
	ser->fd=-1;
	ser->rx_irq_no=rx_irq;
	ser->tx_irq_no=tx_irq;
	ser->emu_baudrate=B0;
	ser->rx_dmachan = BBDMA_Connect(bbdma,rxdma_chan); 
	ser->tx_dmachan = BBDMA_Connect(bbdma,txdma_chan) ;
	ser->name = serial_name; 

	ser->RxDmaGnt = SigNode_New("%s.RxDmaGnt",serial_name);
	if(!ser->RxDmaGnt) {
		fprintf(stderr,"Can not create Ser. RxDmaGntNode\n");
		exit(3427);
	}
	SigNode_Set(ser->RxDmaGnt,SIG_OPEN); /* controller sets RxDMAGnt  */

	ser->TxDmaReq = SigNode_New("%s.TxDmaReq",serial_name);
	if(!ser->TxDmaReq) {
		fprintf(stderr,"Can not create Ser. TxDmaReqNode\n");
		exit(3429);
	}
	SigNode_Set(ser->TxDmaReq,SIG_LOW); /* no request from me */

	ser->endianNode = SigNode_New("%s.endian",serial_name);
	if(!ser->endianNode) {
		fprintf(stderr,"Can not create Ser. EndianNode\n");
		exit(3429);
	}
	ser->endianTrace = SigNode_Trace(ser->endianNode,change_endian,ser);

	BBDMA_SetDataSink(ser->tx_dmachan,serial_txdma_sink,ser);

	ser->statA=SER_TRDY;
	IOH_New32(base+SER_CTRLA,ser_ctrla_read,ser_ctrla_write,ser); 
	IOH_New32(base+SER_CTRLB,ser_ctrlb_read,ser_ctrlb_write,ser); 
	
	IOH_New32(base+SER_STATA,ser_stata_read,ser_stata_write,ser); 
	IOH_New32(base+SER_RATE,ser_rate_read,ser_rate_write,ser);
	IOH_New32f(base+SER_FIFO,ser_rxfifo_read,ser_txfifo_write,ser,IOH_FLG_PA_CBSE | IOH_FLG_HOST_ENDIAN); 
	IOH_New32(base+SER_RXBUF_GAP,ser_rxbuf_gap_read,ser_rxbuf_gap_write,ser); 
	IOH_New32(base+SER_RXCHAR_GAP,ser_rxchar_gap_read,ser_rxchar_gap_write,ser); 
	IOH_New32(base+SER_RXMATCH,ser_rxmatch_read,ser_rxmatch_write,ser); 
	IOH_New32(base+SER_RXMATCH_MASK,ser_rxmatch_mask_read,ser_rxmatch_mask_write,ser); 
	IOH_New32(base+SER_FLOW_CTRL,ser_flow_ctrl_read,ser_flow_ctrl_write,ser); 
	IOH_New32(base+SER_FLOW_FORCE,ser_flow_force_read,ser_flow_force_write,ser); 

	filename=Config_ReadVar("ns9750",serial_name);
	if(filename) {
		/* does cygwin have /dev/stdin ? */
		if(!strcmp(filename,"stdin")) {
			ser->fd = 0;	
		} else {
			ser->fd = open(filename,O_RDWR);	
		}
		if(ser->fd<0) {
			fprintf(stderr,"%s: Cannot open %s\n",serial_name,filename);
			sleep(1);
		} else {
			fcntl(ser->fd,F_SETFL,O_NONBLOCK);
			enable_rx(ser);
			fprintf(stderr,"NS9750 %s Connected to %s\n",serial_name,filename);
		}
	} else {
		fprintf(stderr,"NS9750 %s connected to nowhere\n",serial_name);
	} 
	return ser;
}

/* Only for debugger */
Serial *g_ser[4];
/*
 * ------------------------------------------------
 * Create all four serial interfaces
 * ------------------------------------------------
 */
void
NS9750_SerialInit(BBusDMACtrl *bbdma) {
	int i;
	signal(SIGTERM,sig_term);
	signal(SIGINT,sig_int);
	signal(SIGCONT,sig_cont);
	signal(SIGSTOP,sig_stop);
	TerminalConfigure(0);
	atexit(TerminalExit);
	for(i=0;i<4;i++) {
		g_ser[i]=NS9750_SerialNew(bbdma,i); 
	}
}
