// port.c
// Created by Id Software for SERSETUP.EXE, 1993, 1994.
// Hacked by Russell Gilbert and others SER4.EXE, SER5.EXE, and SER6.EXE 1994.
// Hacked by Paul T. Hermann for SER5.EXE, SER6.EXE 1994.
//
// Compiled under Borland C++ 3.1 using the Compact Memory module
// Using 80386 instruction set and 80387 emualation support
// Optimizations for speed enabled
//

#include "doomnet.h"
#include "sersetup.h"

void jump_start( void );
void interrupt isr_8250 (void);
void interrupt isr_16550 (void);
void interrupt (*oldirqvect) (void);

extern int uart;			// io address
extern int irq;
extern unsigned long baud;
extern int comport;
extern int nodropdtr;

union	REGS	regs;
struct SREGS	sregs;
que_t inque, outque;
int uart_type;
int modem_status = -1;
int line_status = -1;
int irqintnum;
int irqvectorhooked=FALSE;


/* showUartErrors() counters. */
extern unsigned long numBreak;
extern unsigned long numFramingError;
extern unsigned long numParityError;
extern unsigned long numOverrunError;
extern unsigned long numTxInterrupts;
extern unsigned long numRxInterrupts;


/*
==============
=
= GetUart
=
==============
*/

void GetUart (void)
{
	char   far *system_data;
	static int ISA_uarts[] = {0x3f8,0x2f8,0x3e8,0x2e8};
	static int ISA_IRQs[] = {4,3,4,3};
	static int MCA_uarts[] = {0x03f8,0x02f8,0x3220,0x3228};
	static int MCA_IRQs[] = {4,3,3,3};

	regs.h.ah = 0xc0;
	int86x( 0x15, &regs, &regs, &sregs );
	if ( regs.x.cflag )
	{
		if (irq == -1)
			irq = ISA_IRQs[ comport-1 ];
		if (uart == -1)
			uart = ISA_uarts[ comport-1 ];
		return;
	}
	system_data = ( char far *) ( ( (long) sregs.es << 16 ) + regs.x.bx );
	if ( system_data[ 5 ] & 0x02 )
	{
		if (irq == -1)
			irq = MCA_IRQs[ comport-1 ];
		if (uart == -1)
			uart = MCA_uarts[ comport-1 ];
	}
	else
	{
		if (irq == -1)
			irq = ISA_IRQs[ comport-1 ];
		if (uart == -1)
			uart = ISA_uarts[ comport-1 ];
	}
}

/*
===============
=
= InitPort
=
===============
*/

void InitPort (void)
{
	int mcr;
	int	temp;
	unsigned long divisor;

//
// init com port settings
//

/*******
	regs.x.ax = 0xf3;		//f3= 9600 n 8 1
	regs.x.dx = comport - 1;
	int86 (0x14, &regs, &regs);
*******/

	if ((divisor = baud) == 14400)
		divisor = 19200;

	divisor = CLOCK_FREQUENCY / (16 * divisor);					/* Calc. divisor */
	OUTPUT( uart + LINE_CONTROL_REGISTER, LCR_DLAB );			/* Enable divisor */
	OUTPUT( uart + DIVISOR_LATCH_HIGH, 0 );						/* Set divisor */
	OUTPUT( uart + DIVISOR_LATCH_LOW, (unsigned char) divisor);
	OUTPUT( uart + LINE_CONTROL_REGISTER, 3 );					/* Set 8,n,1 */

//
// check for a 16550
//
	OUTPUT( uart + FIFO_CONTROL_REGISTER, FCR_FIFO_ENABLE + FCR_TRIGGER_04 );
	temp = INPUT( uart + INTERRUPT_ID_REGISTER );
	if ( ( temp & 0xf8 ) == 0xc0 )
	{
		uart_type = UART_16550;
		/** printf ("UART is a 16550\n"); **/
	}
	else
	{
		uart_type = UART_8250;
		OUTPUT( uart + FIFO_CONTROL_REGISTER, 0 );
		/** printf ("UART is an 8250\n"); **/
	}

//
// prepare for interrupts
//
	OUTPUT( uart + INTERRUPT_ENABLE_REGISTER, 0 );	/* Turn off interrupts */

	mcr = INPUT( uart + MODEM_CONTROL_REGISTER );	/* Get modem status */
	mcr |= MCR_OUT2;											/* Set GPO 2 */
	mcr &= ~MCR_LOOPBACK;									/* Turn off loopback test */
	mcr |= MCR_DTR;											/* Set DTR */
	mcr |= MCR_RTS;											/* Set RTS */
	OUTPUT( uart + MODEM_CONTROL_REGISTER, mcr );	/* Set modem status */

	INPUT( uart );												/* Clear Rx interrupts */
	INPUT( uart + INTERRUPT_ID_REGISTER );				/* Clear Tx interrupts */

//
// hook the irq vector
//
	irqintnum = irq + 8;

	if (!irqvectorhooked)
	{
		oldirqvect = getvect (irqintnum);
		irqvectorhooked = TRUE;
		if( uart_type == UART_8250 )			/* Use different interrupt routines */
			setvect (irqintnum, isr_8250);
		else
			setvect (irqintnum, isr_16550);
	}

	OUTPUT( 0x20 + 1, INPUT( 0x20 + 1 ) & ~(1<<irq) );

	CLI();

// enable RX and TX interrupts at the uart
// also enable Line Status interrupts to watch for errors.

	OUTPUT( uart + INTERRUPT_ENABLE_REGISTER,
		IER_RX_DATA_READY + IER_TX_HOLDING_REGISTER_EMPTY + IER_LINE_STATUS );

// enable interrupts through the interrupt controller

	OUTPUT( 0x20, 0xc2 );

	STI();
}

/*
=============
=
= ShutdownPort
=
=============
*/

void ShutdownPort ( void )
{
	if (nodropdtr != 1)
	{
		OUTPUT( uart + INTERRUPT_ENABLE_REGISTER, 0 );/* Turn off interrupts */
		OUTPUT( uart + MODEM_CONTROL_REGISTER, 0 );	 /* Clear modem status */
		OUTPUT( uart + FIFO_CONTROL_REGISTER, 0 );	 /* Clear fifo status */

		OUTPUT( 0x20 + 1, INPUT( 0x20 + 1 ) | (1<<irq) );
	}

	CLI();
	setvect (irqintnum,oldirqvect);						/* Return to orig. inter. */
	STI();
/* Leave port as it is.
*
*	regs.x.ax = 0xf3;		//f3= 9600 n 8 1
*	regs.x.dx = comport - 1;
*	int86 (0x14, &regs, &regs);
*/
}

int read_byte( void )
{
	int   c;

	if (inque.size == 0)
		return -1;

	c = inque.data[inque.tail++];
	inque.size--;

	if (inque.tail >= QUESIZE)
		inque.tail = 0;      // wrap around

	return c;
}


void write_byte( int c )
{
	outque.data[outque.head++] = c;
	outque.size++;

	if (outque.head >= QUESIZE)
		outque.head = 0;     // wrap around
}

//==========================================================================
/*
==============
=
= isr_8250
=
==============
*/

void interrupt isr_8250(void)
{
	int c;
	int	count;

	while (1)
	{
		switch( INPUT( uart + INTERRUPT_ID_REGISTER ) & 7 )
		{
//
// receive exactly one byte, since this is a UART 8250
//
		case IIR_RX_DATA_READY_INTERRUPT :
			++numRxInterrupts;
			c = INPUT( uart + RECEIVE_BUFFER_REGISTER );
			inque.data[inque.head++] = c;
			inque.size++;
			if (inque.head >= QUESIZE)
				inque.head = 0;      // wrap around
			break;
//
// transmit exactly one byte, since this is a UART 8250
//
		case IIR_TX_HOLDING_REGISTER_INTERRUPT :
			++numTxInterrupts;
			if (outque.size != 0)
			{
				c = outque.data[outque.tail++];
				outque.size--;
				if (outque.tail >= QUESIZE)
					outque.tail = 0;      // wrap around
				OUTPUT( uart + TRANSMIT_HOLDING_REGISTER, c );
			}
			break;

/* not enabled
*		case IIR_MODEM_STATUS_INTERRUPT :
*			modem_status = INPUT( uart + MODEM_STATUS_REGISTER );
*			break;
*/

//
// line status
//
		case IIR_LINE_STATUS_INTERRUPT :
			line_status = INPUT( uart + LINE_STATUS_REGISTER );

			if ( line_status & LSR_OVERRUN_ERROR )
				++numOverrunError;

			if ( line_status & LSR_PARITY_ERROR )
				++numParityError;

			if ( line_status & LSR_FRAMING_ERROR )
				++numFramingError;

			if ( line_status & LSR_BREAK_DETECT )
				++numBreak;

			break;
//
// done
//
		default :
			OUTPUT( 0x20, 0x20 );
			return;
		}
	}
}

/*
==============
=
= isr_16550
=
==============
*/

void interrupt isr_16550(void)
{
	int c;
	int	count;

	while (1)
	{
		switch( INPUT( uart + INTERRUPT_ID_REGISTER ) & 7 )
		{
//
// receive
//
		case IIR_RX_DATA_READY_INTERRUPT :
			++numRxInterrupts;
			do
			{
				c = INPUT( uart + RECEIVE_BUFFER_REGISTER );
				inque.data[inque.head++] = c;
				inque.size++;
				if (inque.head >= QUESIZE)
					inque.head = 0;      // wrap around
			}while ( INPUT( uart + LINE_STATUS_REGISTER ) & LSR_DATA_READY );

			break;
//
// transmit
//
		case IIR_TX_HOLDING_REGISTER_INTERRUPT :
			++numTxInterrupts;
			if (outque.size != 0)
			{
				count = 16;
				while (--count && outque.size != 0)
				{
					c = outque.data[outque.tail++];
					outque.size--;
					if (outque.tail >= QUESIZE)
						outque.tail = 0;      // wrap around
					OUTPUT( uart + TRANSMIT_HOLDING_REGISTER, c );
				}
			}
			break;
/* not enabled
*		case IIR_MODEM_STATUS_INTERRUPT :
*			modem_status = INPUT( uart + MODEM_STATUS_REGISTER );
*			break;
*/
//
// line status
//
		case IIR_LINE_STATUS_INTERRUPT :
			line_status = INPUT( uart + LINE_STATUS_REGISTER );

			if ( line_status & LSR_OVERRUN_ERROR )
				++numOverrunError;

			if ( line_status & LSR_PARITY_ERROR )
				++numParityError;

			if ( line_status & LSR_FRAMING_ERROR )
				++numFramingError;

			if ( line_status & LSR_BREAK_DETECT )
				++numBreak;
			break;
//
// done
//
		default :
			OUTPUT( 0x20, 0x20 );
			return;
		}
	}
}

/*
===============
=
= jump_start
=
= Start up the transmition interrupts by sending the first char
===============
*/

void jump_start( void )
{
	int c;

	if (outque.size != 0)
	{
		c = outque.data [outque.tail++];
		outque.size--;
		if (outque.tail >= QUESIZE)
			outque.tail = 0;      // wrap around
		OUTPUT( uart, c );
	}
}
