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

    National Semiconductor 8250 UART interface and emulation

   More information on the different models can be found in
   section 1.6 at this location:
     http://www.freebsd.org/doc/en_US.ISO8859-1/articles/serial-uart/

Model overview (from page above):

INS8250
This part was used in the original IBM PC and IBM PC/XT. The original name
for this part was the INS8250 ACE (Asynchronous Communications Element) and
it is made from NMOS technology.

The 8250 uses eight I/O ports and has a one-byte send and a one-byte receive
buffer. This original UART has several race conditions and other flaws. The
original IBM BIOS includes code to work around these flaws, but this made
the BIOS dependent on the flaws being present, so subsequent parts like the
8250A, 16450 or 16550 could not be used in the original IBM PC or IBM PC/XT.

INS8250-B
This is the slower speed of the INS8250 made from NMOS technology. It contains
the same problems as the original INS8250.

INS8250A
An improved version of the INS8250 using XMOS technology with various functional
flaws corrected. The INS8250A was used initially in PC clone computers by vendors
who used "clean" BIOS designs. Because of the corrections in the chip, this part
could not be used with a BIOS compatible with the INS8250 or INS8250B.

INS82C50A
This is a CMOS version (low power consumption) of the INS8250A and has similar
functional characteristics.

NS16450
Same as NS8250A with improvements so it can be used with faster CPU bus designs.
IBM used this part in the IBM AT and updated the IBM BIOS to no longer rely on
the bugs in the INS8250.

NS16C450
This is a CMOS version (low power consumption) of the NS16450.

NS16550
Same as NS16450 with a 16-byte send and receive buffer but the buffer design
was flawed and could not be reliably be used.

NS16550A
Same as NS16550 with the buffer flaws corrected. The 16550A and its successors
have become the most popular UART design in the PC industry, mainly due to
its ability to reliably handle higher data rates on operating systems with
sluggish interrupt response times.

NS16C552
This component consists of two NS16C550A CMOS UARTs in a single package.

PC16550D
Same as NS16550A with subtle flaws corrected. This is revision D of the
16550 family and is the latest design available from National Semiconductor.


Known issues:
- MESS does currently not handle all these model specific features.


History:
    KT - 14-Jun-2000 - Improved Interrupt setting/clearing
    KT - moved into separate file so it can be used in Super I/O emulation and
        any other system which uses a PC type COM port
    KT - 24-Jun-2000 - removed pc specific input port tests. More compatible
        with PCW16 and PCW16 doesn't requre the PC input port definitions
        which are not required by the PCW16 hardware

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

#include "emu.h"
#include "machine/ins8250.h"


#define LOG(LEVEL,N,M,A)  \
	do { \
		if(LEVEL>=N) \
		{ \
			if( M ) \
				logerror("%-24s",(char*)M ); \
			logerror A; \
		} \
	} while (0)


/* device types */
enum {
	TYPE_INS8250 = 0,
	TYPE_INS8250A,
	TYPE_NS16450,
	TYPE_NS16550,
	TYPE_NS16550A,
	TYPE_PC16550D,

	NUM_TYPES
};


/* device tags */
static const char * const device_tags[NUM_TYPES] = { "ins8250", "ins8250a", "ns16450", "ns16550", "ns16550a", "pc16550d" };


#define VERBOSE_COM 0
#define COM_LOG(n,m,a) LOG(VERBOSE_COM,n,m,a)

typedef struct {
	devcb_resolved_write_line	out_intr_func;
	const ins8250_interface *interface;
	int	device_type;

	UINT8 thr;  /* 0 -W transmitter holding register */
	UINT8 rbr; /* 0 R- receiver buffer register */
	UINT8 ier;  /* 1 RW interrupt enable register */
	UINT8 dll;  /* 0 RW divisor latch lsb (if DLAB = 1) */
	UINT8 dlm;  /* 1 RW divisor latch msb (if DLAB = 1) */
	UINT8 iir;  /* 2 R- interrupt identification register */
	UINT8 lcr;  /* 3 RW line control register (bit 7: DLAB) */
	UINT8 mcr;  /* 4 RW modem control register */
	UINT8 lsr;  /* 5 R- line status register */
	UINT8 msr;  /* 6 R- modem status register */
	UINT8 scr;  /* 7 RW scratch register */

/* holds int pending state for com */
	UINT8 int_pending;

	// sending circuit
	struct {
		int active;
		UINT8 data;
		double time;
	} send;
} ins8250_t;

/* int's pending */
#define COM_INT_PENDING_RECEIVED_DATA_AVAILABLE	0x0001
#define COM_INT_PENDING_TRANSMITTER_HOLDING_REGISTER_EMPTY 0x0002
#define COM_INT_PENDING_RECEIVER_LINE_STATUS 0x0004
#define COM_INT_PENDING_MODEM_STATUS_REGISTER 0x0008


INLINE ins8250_t *get_safe_token(device_t *device)
{
	assert( device != NULL );
	assert( ( device->type() == INS8250 ) ||
			( device->type() == INS8250A ) ||
			( device->type() == NS16450 ) ||
			( device->type() == NS16550 ) ||
			( device->type() == NS16550A ) ||
			( device->type() == PC16550D ) );
	return (ins8250_t *)downcast<legacy_device_base *>(device)->token();
}


/* setup iir with the priority id */
static void ins8250_setup_iir(device_t *device)
{
	ins8250_t	*ins8250 = get_safe_token(device);

	ins8250->iir &= ~(0x04|0x02);

	/* highest to lowest */
	if (ins8250->ier & ins8250->int_pending & COM_INT_PENDING_RECEIVER_LINE_STATUS)
	{
		ins8250->iir |=0x04|0x02;
		return;
	}

	if (ins8250->ier & ins8250->int_pending & COM_INT_PENDING_RECEIVED_DATA_AVAILABLE)
	{
		ins8250->iir |=0x04;
		return;
	}

	if (ins8250->ier & ins8250->int_pending & COM_INT_PENDING_TRANSMITTER_HOLDING_REGISTER_EMPTY)
	{
		ins8250->iir |=0x02;
		return;
	}

	/* modem status has both bits clear */
}


/* ints will continue to be set for as long as there are ints pending */
static void ins8250_update_interrupt(device_t *device)
{
	ins8250_t	*ins8250 = get_safe_token(device);
	int state;

	/* if any bits are set and are enabled */
	if (((ins8250->int_pending&ins8250->ier) & 0x0f) != 0)
	{
		/* trigger next highest priority int */

		/* set int */
		state = 1;
		ins8250_setup_iir(device);

		/* int pending */
		ins8250->iir &= ~0x01;
	}
	else
	{
		/* clear int */
		state = 0;

		/* no ints pending */
		ins8250->iir |= 0x01;
		/* priority level */
		ins8250->iir &= ~(0x04|0x02);
	}

	/* set or clear the int */
	ins8250->out_intr_func(state);
}



/* set pending bit and trigger int */
static void ins8250_trigger_int(device_t *device, int flag)
{
	ins8250_t	*ins8250 = get_safe_token(device);

	ins8250->int_pending |= flag;
	ins8250_update_interrupt(device);
}



/* clear pending bit, if any ints are pending, then int will be triggered, otherwise it
will be cleared */
static void ins8250_clear_int(device_t *device, int flag)
{
	ins8250_t	*ins8250 = get_safe_token(device);

	ins8250->int_pending &= ~flag;
	ins8250_update_interrupt(device);
}


WRITE8_DEVICE_HANDLER( ins8250_w )
{
    static const char P[] = "NONENHNL";  /* names for parity select */
	ins8250_t	*ins8250 = get_safe_token(device);
    int tmp;

	switch (offset)
	{
		case 0:
			if (ins8250->lcr & 0x80)
			{
				ins8250->dll = data;
				tmp = ins8250->dlm * 256 + ins8250->dll;
				COM_LOG(1,"COM_dll_w",("COM \"%s\" $%02x: [$%04x = %d baud]\n", device->tag(),
					 data, tmp, (tmp)?(int)(ins8250->interface->clockin/16/tmp):0));
			}
			else
			{
				ins8250->thr = data;
				COM_LOG(2,"COM_thr_w",("COM $%02x\n", data));

				if ( ins8250->mcr & 0x10 )
				{
					ins8250->lsr |= 1;
					ins8250->rbr = data;
					ins8250_trigger_int( device, COM_INT_PENDING_RECEIVED_DATA_AVAILABLE );
				}

				if ( ins8250->interface->transmit )
					ins8250->interface->transmit(device, ins8250->thr);

				/* writing to thr will clear the int */
				ins8250_clear_int(device, COM_INT_PENDING_TRANSMITTER_HOLDING_REGISTER_EMPTY);
			}
			break;
		case 1:
			if (ins8250->lcr & 0x80)
			{
				ins8250->dlm = data;
				tmp = ins8250->dlm * 256 + ins8250->dll;
                COM_LOG(1,"COM_dlm_w",("COM \"%s\" $%02x: [$%04x = %d baud]\n", device->tag(),
					data, tmp, (tmp)?(int)(ins8250->interface->clockin/16/tmp):0));
			}
			else
			{
				ins8250->ier = data;
				COM_LOG(2,"COM_ier_w",("COM \"%s\" $%02x: enable int on RX %d, THRE %d, RLS %d, MS %d\n", device->tag(),
					data, data&1, (data>>1)&1, (data>>2)&1, (data>>3)&1));
				COM_LOG(2,"COM_ier_w",("COM \"%s\" lsr = $%02x, int_pending = $%02x\n", device->tag(), ins8250->lsr, ins8250->int_pending ));
				ins8250_update_interrupt(device);
			}
            break;
		case 2:
			COM_LOG(1,"COM_fcr_w",("COM \"%s\" $%02x (16550 only)\n", device->tag(), data));
            break;
		case 3:
			ins8250->lcr = data;
			COM_LOG(1,"COM_lcr_w",("COM \"%s\" $%02x word length %d, stop bits %d, parity %c, break %d, DLAB %d\n", device->tag(),
				data, 5+(data&3), 1+((data>>2)&1), P[(data>>3)&7], (data>>6)&1, (data>>7)&1));
            break;
		case 4:
			if ( ( ins8250->mcr & 0x1f ) != ( data & 0x1f ) )
			{
				ins8250->mcr = data & 0x1f;
				COM_LOG(1,"COM_mcr_w",("COM \"%s\" $%02x DTR %d, RTS %d, OUT1 %d, OUT2 %d, loopback %d\n", device->tag(),
					data, data&1, (data>>1)&1, (data>>2)&1, (data>>3)&1, (data>>4)&1));
				if (ins8250->interface->handshake_out)
					ins8250->interface->handshake_out(device,data);

				if ( ins8250->mcr & 0x10 )		/* loopback test */
				{
					data = ( ( ins8250->mcr & 0x0c ) << 4 ) | ( ( ins8250->mcr & 0x01 ) << 5 ) | ( ( ins8250->mcr & 0x02 ) << 3 );
					if ( ( ins8250->msr & 0x20 ) != ( data & 0x20 ) )
					{
						data |= 0x02;
					}
					if ( ( ins8250->msr & 0x10 ) != ( data & 0x10 ) )
					{
						data |= 0x01;
					}
					if ( ( ins8250->msr & 0x40 ) && ! ( data & 0x40 ) )
					{
						data |= 0x04;
					}
					if ( ( ins8250->msr & 0x80 ) != ( data & 0x80 ) )
					{
						data |= 0x08;
					}
					ins8250->msr = data;
				}
			}
            break;
		case 5:
			/*
              This register can be written, but if you write a 1 bit into any of
              bits 5 - 0, you could cause an interrupt if the appropriate IER bit
              is set.
            */
			COM_LOG(1,"COM_lsr_w",("COM \"%s\" $%02x\n", device->tag(), data ));

			ins8250->lsr = data;

			tmp = 0;
			tmp |= ( ins8250->lsr & 0x01 ) ? COM_INT_PENDING_RECEIVED_DATA_AVAILABLE : 0;
			tmp |= ( ins8250->lsr & 0x1e ) ? COM_INT_PENDING_RECEIVER_LINE_STATUS : 0;
			tmp |= ( ins8250->lsr & 0x20 ) ? COM_INT_PENDING_TRANSMITTER_HOLDING_REGISTER_EMPTY : 0;
			ins8250_trigger_int( device, tmp );

			break;
		case 6:
			/*
              This register can be written, but if you write a 1 bit into any of
              bits 3 - 0, you could cause an interrupt if the appropriate IER bit
              is set.
             */
			COM_LOG(1,"COM_msr_w",("COM \"%s\" $%02x\n", device->tag(), data ));

			ins8250->msr = data;

			if ( ins8250->msr & 0x0f )
			{
				ins8250_trigger_int( device, COM_INT_PENDING_MODEM_STATUS_REGISTER );
			}
			break;
		case 7:
			ins8250->scr = data;
			COM_LOG(2,"COM_scr_w",("COM \"%s\" $%02x\n", device->tag(), data));
            break;
	}

	if (ins8250->interface->refresh_connected)
		ins8250->interface->refresh_connected(device);
}



READ8_DEVICE_HANDLER( ins8250_r )
{
	ins8250_t	*ins8250 = get_safe_token(device);
	int data = 0x0ff;

	switch (offset)
	{
		case 0:
			if (ins8250->lcr & 0x80)
			{
				data = ins8250->dll;
				COM_LOG(1,"COM_dll_r",("COM \"%s\" $%02x\n", device->tag(), data));
			}
			else
			{
				data = ins8250->rbr;
				if( ins8250->lsr & 0x01 )
				{
					ins8250->lsr &= ~0x01;		/* clear data ready status */
					COM_LOG(2,"COM_rbr_r",("COM \"%s\" $%02x\n", device->tag(), data));
				}

				ins8250_clear_int(device, COM_INT_PENDING_RECEIVED_DATA_AVAILABLE);
			}
			break;
		case 1:
			if (ins8250->lcr & 0x80)
			{
				data = ins8250->dlm;
				COM_LOG(1,"COM_dlm_r",("COM \"%s\" $%02x\n", device->tag(), data));
			}
			else
			{
				data = ins8250->ier & 0x0f;
				COM_LOG(2,"COM_ier_r",("COM \"%s\" $%02x\n", device->tag(), data));
            }
            break;
		case 2:
			data = ins8250->iir;
			COM_LOG(2,"COM_iir_r",("COM \"%s\" $%02x\n", device->tag(), data));
			/* The documentation says that reading this register will
            clear the int if this is the source of the int */
			if ( ins8250->ier & COM_INT_PENDING_TRANSMITTER_HOLDING_REGISTER_EMPTY )
			{
				ins8250_clear_int(device, COM_INT_PENDING_TRANSMITTER_HOLDING_REGISTER_EMPTY);
			}
            break;
		case 3:
			data = ins8250->lcr;
			COM_LOG(2,"COM_lcr_r",("COM \"%s\" $%02x\n", device->tag(), data));
            break;
		case 4:
			data = ins8250->mcr;
			COM_LOG(2,"COM_mcr_r",("COM \"%s\" $%02x\n", device->tag(), data));
            break;
		case 5:

#if 0
			if (ins8250->send.active && (machine.time()-ins8250->send.time>uart_byte_time(n)))
			{
				// currently polling is enough for pc1512
				ins8250->lsr |= 0x40; /* set TSRE */
				ins8250->send.active = 0;
				if ( ins8250->mcr & 0x10 )
				{
					ins8250->lsr |= 1;
					ins8250->rbr = ins8250->send.data;
				}
			}
#endif
			ins8250->lsr |= 0x60; /* set THRE */
			data = ins8250->lsr;
			if( ins8250->lsr & 0x1f )
			{
				ins8250->lsr &= 0xe1; /* clear FE, PE and OE and BREAK bits */
				COM_LOG(2,"COM_lsr_r",("COM \"%s\" $%02x, DR %d, OE %d, PE %d, FE %d, BREAK %d, THRE %d, TSRE %d\n", device->tag(),
					data, data&1, (data>>1)&1, (data>>2)&1, (data>>3)&1, (data>>4)&1, (data>>5)&1, (data>>6)&1));
			}

			/* reading line status register clears int */
			ins8250_clear_int(device, COM_INT_PENDING_RECEIVER_LINE_STATUS);
            break;
		case 6:
			data = ins8250->msr;
			ins8250->msr &= 0xf0; /* reset delta values */
			COM_LOG(2,"COM_msr_r",("COM \"%s\" $%02x\n", device->tag(), data));

			/* reading msr clears int */
			ins8250_clear_int(device, COM_INT_PENDING_MODEM_STATUS_REGISTER);

			break;
		case 7:
			data = ins8250->scr;
			COM_LOG(2,"COM_scr_r",("COM \"%s\" $%02x\n", device->tag(), data));
            break;
	}

	if (ins8250->interface->refresh_connected)
		ins8250->interface->refresh_connected(device);

    return data;
}



void ins8250_receive(device_t *device, int data)
{
	ins8250_t	*ins8250 = get_safe_token(device);

    /* check if data rate 1200 baud is set */
	if( ins8250->dlm != 0x00 || ins8250->dll != 0x60 )
        ins8250->lsr |= 0x08; /* set framing error */

    /* if data not yet serviced */
	if( ins8250->lsr & 0x01 )
		ins8250->lsr |= 0x02; /* set overrun error */

    /* put data into receiver buffer register */
    ins8250->rbr = data;

    /* set data ready status */
    ins8250->lsr |= 0x01;

	/* set pending state for this interrupt. */
	ins8250_trigger_int(device, COM_INT_PENDING_RECEIVED_DATA_AVAILABLE);


//  /* OUT2 + received line data avail interrupt enabled? */
//  if( (COM_mcr[n] & 0x08) && (COM_ier[n] & 0x01) )
//  {
//      if (com_interface.interrupt)
//          com_interface.interrupt(4-(n&1), 1);
//
//  }
}

/**************************************************************************
 *  change the modem status register
 **************************************************************************/
void ins8250_handshake_in(device_t *device, int new_msr)
{
	ins8250_t	*ins8250 = get_safe_token(device);

	/* no change in modem status bits? */
	if( ((ins8250->msr ^ new_msr) & 0xf0) == 0 )
		return;

	/* set delta status bits 0..3 and new modem status bits 4..7 */
    ins8250->msr = (((ins8250->msr ^ new_msr) >> 4) & 0x0f) | (new_msr & 0xf0);

	ins8250_trigger_int(device, COM_INT_PENDING_MODEM_STATUS_REGISTER);

//  /* set up interrupt information register */
  //  COM_iir[n] &= ~(0x06 | 0x01);

//    /* OUT2 + modem status interrupt enabled? */
//  if( (COM_mcr[n] & 0x08) && (COM_ier[n] & 0x08) )
//  {
//      if (com_interface.interrupt)
//          com_interface.interrupt(4-(n&1), 1);
//  }
}


static void common_start( device_t *device, int device_type )
{
	ins8250_t	*ins8250 = get_safe_token(device);

	ins8250->interface = (const ins8250_interface*)device->static_config();
	ins8250->device_type = device_type;

	ins8250->out_intr_func.resolve(ins8250->interface->out_intr_cb, *device);
}


static DEVICE_START( ins8250 )
{
	common_start( device, TYPE_INS8250 );
}


static DEVICE_START( ins8250a )
{
	common_start( device, TYPE_INS8250A );
}


static DEVICE_START( ns16450 )
{
	common_start( device, TYPE_NS16450 );
}


static DEVICE_START( ns16550 )
{
	common_start( device, TYPE_NS16550 );
}


static DEVICE_START( ns16550a )
{
	common_start( device, TYPE_NS16550A );
}


static DEVICE_START( pc16550d )
{
	common_start( device, TYPE_PC16550D );
}


static DEVICE_RESET( ins8250 )
{
	ins8250_t	*ins8250 = get_safe_token(device);

	ins8250->ier = 0;
	ins8250->iir = 1;
	ins8250->lcr = 0;
	ins8250->mcr = 0;
	ins8250->lsr = (1<<5) | (1<<6);

	ins8250->send.active=0;

	/* refresh with reset state of register */
	if (ins8250->interface->refresh_connected)
		ins8250->interface->refresh_connected(device);
}


DEVICE_GET_INFO( ins8250 )
{
	switch ( state )
	{
		/* --- the following bits of info are returned as 64-bit signed integers --- */
		case DEVINFO_INT_TOKEN_BYTES:				info->i = sizeof(ins8250_t);				break;
		case DEVINFO_INT_INLINE_CONFIG_BYTES:		info->i = 0;								break;

		/* --- the following bits of info are returned as pointers to data or functions --- */
		case DEVINFO_FCT_START:						info->start = DEVICE_START_NAME(ins8250);	break;
		case DEVINFO_FCT_STOP:						/* nothing */								break;
		case DEVINFO_FCT_RESET:						info->reset = DEVICE_RESET_NAME(ins8250);	break;

		/* --- the following bits of info are returned as NULL-terminated strings --- */
		case DEVINFO_STR_NAME:						strcpy(info->s, "National Semiconductor INS8250/INS8250B");	break;
		case DEVINFO_STR_FAMILY:					strcpy(info->s, "INS8250");					break;
		case DEVINFO_STR_VERSION:					strcpy(info->s, "1.00");					break;
		case DEVINFO_STR_SOURCE_FILE:				strcpy(info->s, __FILE__);					break;
		case DEVINFO_STR_CREDITS:					strcpy(info->s, "Copyright the MESS Team");	break;
	}
}


DEVICE_GET_INFO( ins8250a )
{
	switch ( state )
	{
		/* --- the following bits of info are returned as 64-bit signed integers --- */
		case DEVINFO_STR_NAME:						strcpy(info->s, "National Semiconductor INS8250A/INS82C50A");	break;

		/* --- the following bits of info are returned as pointers to data or functions --- */
		case DEVINFO_FCT_START:						info->start = DEVICE_START_NAME(ins8250a);	break;

		default:									DEVICE_GET_INFO_CALL(ins8250);				break;
	}
}


DEVICE_GET_INFO( ns16450 )
{
	switch ( state )
	{
		/* --- the following bits of info are returned as 64-bit signed integers --- */
		case DEVINFO_STR_NAME:						strcpy(info->s, "National Semiconductor NS16450/PC16450");	break;

		/* --- the following bits of info are returned as pointers to data or functions --- */
		case DEVINFO_FCT_START:						info->start = DEVICE_START_NAME(ns16450);	break;

		default:									DEVICE_GET_INFO_CALL(ins8250);				break;
	}
}


DEVICE_GET_INFO( ns16550 )
{
	switch ( state )
	{
		/* --- the following bits of info are returned as 64-bit signed integers --- */
		case DEVINFO_STR_NAME:						strcpy(info->s, "National Semiconductor NS16550/PC16550");	break;

		/* --- the following bits of info are returned as pointers to data or functions --- */
		case DEVINFO_FCT_START:						info->start = DEVICE_START_NAME(ns16550);	break;

		default:									DEVICE_GET_INFO_CALL(ins8250);				break;
	}
}


DEVICE_GET_INFO( ns16550a )
{
	switch ( state )
	{
		/* --- the following bits of info are returned as 64-bit signed integers --- */
		case DEVINFO_STR_NAME:						strcpy(info->s, "National Semiconductor NS16550A/PC16550A");	break;

		/* --- the following bits of info are returned as pointers to data or functions --- */
		case DEVINFO_FCT_START:						info->start = DEVICE_START_NAME(ns16550a);	break;

		default:									DEVICE_GET_INFO_CALL(ins8250);				break;
	}
}


DEVICE_GET_INFO( pc16550d )
{
	switch ( state )
	{
		/* --- the following bits of info are returned as 64-bit signed integers --- */
		case DEVINFO_STR_NAME:						strcpy(info->s, "National Semiconductor PC16550D");	break;

		/* --- the following bits of info are returned as pointers to data or functions --- */
		case DEVINFO_FCT_START:						info->start = DEVICE_START_NAME(pc16550d);	break;

		default:									DEVICE_GET_INFO_CALL(ins8250);				break;
	}
}



DEFINE_LEGACY_DEVICE(INS8250, ins8250);
DEFINE_LEGACY_DEVICE(INS8250A, ins8250a);
DEFINE_LEGACY_DEVICE(NS16450, ns16450);
DEFINE_LEGACY_DEVICE(NS16550, ns16550);
DEFINE_LEGACY_DEVICE(NS16550A, ns16550a);
DEFINE_LEGACY_DEVICE(PC16550D, pc16550d);
