/***************************************************************************
These C++ classes are copyright 1990, by William Herrera.
All those who put this code or its derivatives in a commercial product MUST
mention this copyright in their documentation for users of the products in
which this code or its derivative classes are used.  Otherwise, this code
may be freely distributed and freely used for any purpose.

Enhancements: 1991 by David Orme
	*  General cleanup.
			- I/O now takes advantage of C++ overloading.
			- Serial port I/O functionality now only in Serial class.
			- Modem functionality now only in Modem class.
	*  Possible to easily implement file Xfr prots now.
	*  CCITT CRC-16 class added							-- 2-20-1991
	*  BIOS Timer class added								-- 2-22-1991
	*  Optional timeout on all input routines added	-- 2-25-1991

***************************************************************************/

// File uart.cpp, class definitions for the uart class.
// By William Herrera.

// See your modem manual (I used MultiTech's) or the IBM Technical
// reference manual for more information on the 8250 UART used in the PC.

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


#include "uart.hpp"


// C++ 2.0 requires that static data members be declared globally

char uart::old_intmask[NUM_PORTS];
char uart::old_MCR[NUM_PORTS];
char uart::old_IER[NUM_PORTS];

const int uart::portvector_num[NUM_PORTS] = { 0xC, 0xB, 0xC, 0xB };

const char uart::intmaskbit[NUM_PORTS] = { 0x10, 0x08, 0x10, 0x08 };

#ifdef __TURBOC__
DRIVER uart::old_driver[NUM_PORTS] = { NULL, NULL, NULL, NULL };
#else ifdef __ZTC__
void far * uart::old_driver[NUM_PORTS] = { NULL, NULL, NULL, NULL };
#endif

uart::uart() { ; }

uart::~uart() { ; }


// the following function sets up the PC for interrupt-driven serial
// communications through the UART.  Returns 0 for success, -1 for
// port disallowed, -2 for uart interrupts not resettable.
int uart::RegisterDriver(int portnum, DRIVER driv)
{
	int retval;
	if(
		(portnum < 1) || (portnum > NUM_PORTS) || 
			(old_driver[portnum - 1] != NULL) )
	{
		fputs("uart::RegisterDriver() cannot set port requested.\n", 
				stderr);
		retval = -1;
	}
	else
	{
		disable();
		// Hardware interrupts off til we change the settings.
		// Make sure DLAB of LSR is 0 to allow access to IER and MCR.
		SetLCR_DLAB(false);
		// Save old IER and MCR.
		old_IER[portnum -1] = GetIER();
		old_MCR[portnum- 1] = GetMCR();
		// Then check the UART for accessibility.
		SetIER(0);
		if(GetIER() != 0)
		{
			fputs("uart error:  unable to reset IER\n", stderr);
			retval = -2;
		}
		else
		{
			// save the old interrupt mask.
			old_intmask[portnum - 1] = inportb(0x21);
				
			// reset vector to point to our handler.
#ifdef __TURBOC__
			old_driver[portnum - 1] = getvect(portvector_num[portnum - 1]);
			setvect(portvector_num[portnum - 1], driv);
#else ifdef __ZTC__
			unsigned int iseg, ioff;
			int_getvector(portvector_num[portnum - 1], &ioff, &iseg);
			old_driver[portnum - 1] = MK_FP(iseg, ioff);
			int_intercept(portvector_num[portnum - 1], driv, 0);
#endif
			SetMCR(9);	// turn on DTR and interrupts by modem (1|8).
			SetIER(13);	// turn on data ready, line, modem ints (1|4|8).

			// Now enable controller for serial port interrupts.
			outportb( 0x21, (inportb(0x21) & ~(intmaskbit[portnum - 1])) );

			// set speed to a default baud rate of 19200.
			SetSpeed(19200);
			// Set port to default parameters of 8 data, 1 stop, no parity.
			SetParity(NOPAR);
			SetWordLength(8);
			SetStopBits(1);

			retval = 0;
		}
		enable();
	}
	return retval;
}

// This function resets the interrupt controller and UART and 
// generally cleans up after the RegisterDriver function.
// Returns 0 on success, -1 on port not restorable.
int uart::RestoreDriver(int portnum)
{
	int retval;
	if( (portnum < 1) || (portnum > NUM_PORTS) || 
			(old_driver[portnum - 1] == NULL) )
	{
		fputs("UART Error:  Cannot restore port.\n", stderr);
		retval = -1;
	}
	else 
	{
		// reset the interrupt vector.
		disable();
#ifdef __TURBOC__
		setvect(portvector_num[portnum - 1], old_driver[portnum - 1]);
#else ifdef __ZTC__
		int_restore(portvector_num[portnum - 1]);	
#endif
		// reset the i8259 mask to its original state.
		outportb(0x21, old_intmask[portnum - 1]);
		// reset UART registers.
		SetIER(old_IER[portnum -1]);	
		SetMCR(old_MCR[portnum - 1]);
		enable();
		old_driver[portnum - 1] = NULL;
		retval = 0;
	}
	return retval;
}

char uart::GetLCR()
{
	return inportb(LCR());
}

char uart::GetDLL()
{
	return inportb(DLL());
}

char uart::GetDLM()
{ 	
	return inportb(DLM());
}

char uart::GetLSR()
{  	
	return inportb(LSR());
}

char uart::GetMCR()
{   
	return inportb(MCR());
}

char uart::GetMSR()
{	
	return inportb(MSR());
}

char uart::GetRBR()
{	
	return inportb(RBR());
}

char uart::GetIER()
{	
	return inportb(IER());
}

char uart::GetIIR()
{
	return inportb(IIR());
}

boolean uart::GetLSR_THRE()
{
	return (GetLSR() & 32) ? true : false;
}

void uart::SetLCR(char byte)
{
	outportb(LCR(), byte);
}

void uart::SetDLL(char byte)
{
	outportb(DLL(), byte);
}

void uart::SetDLM(char byte)
{
	outportb(DLM(), byte);
}

void uart::SetLSR(char byte)
{
	outportb(LSR(), byte);
}

void uart::SetMCR(char byte)
{
	outportb(MCR(), byte);
}

void uart::SetMSR(char byte)
{
	outportb(MSR(), byte);
}

void uart::SetTHR(char byte)
{
	outportb(THR(), byte);
}

void uart::SetIER(char byte)
{
	outportb(IER(), byte);
}

void uart::SetIER_Recieve(boolean bit)
{
	SetIER( (bit) ? GetIER() | 1 : GetIER() & (~1) );
}

void uart::SetIER_Transmit(boolean bit)
{
	SetIER( (bit) ? GetIER() | 2 : GetIER() & (~2) );
}

void uart::SetIER_Line(boolean bit)
{
	SetIER( (bit) ? GetIER() | 4 : GetIER() & (~4) );
}

void uart::SetIER_Modem(boolean bit)
{
	SetIER( (bit) ? GetIER() | 8 : GetIER() & (~8) );
}

void uart::SetLCR_DLAB(boolean bit)
{
	SetLCR( (bit) ? GetLCR() | 128 : GetLCR() & (~128) );
}

void uart::SetLSR_DR(boolean bit)
{
	SetLSR( (bit) ? GetLSR() | 1 : GetLSR() & (~1) );
}

void uart::SetBaudRate(int speed)
{
	int divisor = 115200L / speed;
	char lsb = divisor & 0xFF;
	char msb = (divisor >> 8) & 0xFF;
	SetLCR_DLAB(true);		
	SetDLL(lsb);
	SetDLM(msb);
	SetLCR_DLAB(false);
}
		
int uart::GetBaudRate()
{
	SetLCR_DLAB(true);
	int lsb = GetDLL() & 0xFF;	
	int msb = GetDLM() & 0xFF;
	SetLCR_DLAB(false);
	return ( 115200L / ((msb << 8) + lsb) );
}

void uart::SetParity(parity_t p)
{
	SetLCR( (GetLCR() & 0xC7) | p);
}	

parity_t uart::GetParity()
{
	return (parity_t)(GetLCR() & 0x38);
}

void uart::SetWordLength(int len)
{
	SetLCR( (GetLCR() & 0xFC) | ((len - 5) & 3) );
}

int uart::GetWordLength()
{
	return (GetLCR() & 3) + 5;
}

void uart::SetStopBits(int num)
{
	SetLCR( (GetLCR() & 0xFB) | ((num == 1) ? 0 : 4) );
}

int uart::GetStopBits()
{
	return (GetLCR() & 4) ? 1 : 2;
}

void uart::SetBreak()
{
	SetLCR(GetLCR() | 64);
}

void uart::StopBreak()
{
	SetLCR(GetLCR() & (~64));
}

void uart::Pause(int msec)
{
	// assumes the PC BIOS tick is 18.2 per second, or
	// 55 msec per tick.
	// DO NOT call this from an interrupt driver that uses the clock!
	union REGS regs;
	regs.x.ax = 0;
	int86(0x1A, &regs, &regs);
	int startcount = regs.x.dx;
	int endcount = startcount + (msec / 55);
	int i = startcount;
	while(i < endcount && i >= startcount)
	{
		regs.x.ax = 0;
		int86(0x1A, &regs, &regs);
		i = regs.x.dx;
	}
}

void uart::Break(int msec)
{
	// assumes the PC BIOS tick is 18.2 per second, or
	// 55 msec per tick.
	// DO NOT call this from an interrupt driver that uses the clock!
	SetBreak();
	Pause(msec);
	StopBreak();
}

void uart::SetCTS(boolean bit)
{
	SetMSR( (bit) ? (GetMSR() | 16) : (GetMSR() & (~16)) );
}

void uart::SetDSR(boolean bit)
{
	SetMSR( (bit) ? (GetMSR() | 32) : (GetMSR() & (~32)) );
}

boolean uart::CarrierPresent()
{
	// actually tests RLSD, bit 7 of MSR.
	return (GetMSR() & 128) ? true : false;
}

void uart::SetDTR(boolean bit)
{
	SetMCR( (bit) ? (GetMCR() | 1) : (GetMCR() & (~1)) );
}

boolean uart::GetDTR()
{
	return (GetMCR() & 1) ? true : false;
}

com_interrupt_t uart::GetIntrType()
{
	int type = (int)GetIIR() & 0xFF;
	if(type & 1)
		return NONE_PENDING;
	int r;
	switch(type)
	{
		case 0:
			r = GetMSR();
			if(r & 4)
				return RING;
			else if (r & 128)
				return CARRIER;
			else 
				return NO_CARRIER;
		case 2:
			r = GetLSR();
			if(r & 32)   // THRE
				return TRANSMIT_READY;
			else 
				return TRANSMIT_FALSE_ALARM;
		case 4:
			return RECEIVE_READY;
		case 6:
			r = GetLSR();
			if(r & 2)	// 
				return OVERRUN_ERROR;
			else if(r & 4)
				return PARITY_ERROR;
			else if(r & 8)
				return FRAMING_ERROR;
			else if(r & 16)
				return BREAK_RECEIVED;
			else 
				return UNKNOWN_ERROR;
		default : 
			return UNKNOWN_ERROR; 
	}
}


int uart::GetChar()
{
	// note: for this method to work, SetLCR_DLAB(false) must be called.
	return (GetLSR() & 1) ? ( (int)inportb(RBR()) & 0x00FF ) : -1;
}

void uart:: SendChar(char ch)
{   
	// note: for this method to work, SetLCR_DLAB(false) must be called.
	// this method works only if there is room in the transmit register.
	outportb(THR(), ch);
}

void uart::TransmitChar(char ch)
{
	// similar to last one -- allows other to be overloaded separately
	outportb(THR(), ch);
}

int uart::ReceiveChar()
{
	// similar to GetChar() -- allows other to be overloaded separately
	// note: for this method to work, SetLCR_DLAB(false) must be called.
	return (GetLSR() & 1) ? ( (int)inportb(RBR()) & 0x00FF ) : -1;	
}
