summaryrefslogtreecommitdiffstatshomepage
path: root/trunk/src/emu/machine/ins8250.c
diff options
context:
space:
mode:
Diffstat (limited to 'trunk/src/emu/machine/ins8250.c')
-rw-r--r--trunk/src/emu/machine/ins8250.c619
1 files changed, 619 insertions, 0 deletions
diff --git a/trunk/src/emu/machine/ins8250.c b/trunk/src/emu/machine/ins8250.c
new file mode 100644
index 00000000000..0b13058c775
--- /dev/null
+++ b/trunk/src/emu/machine/ins8250.c
@@ -0,0 +1,619 @@
+/**********************************************************************
+
+ 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.
+
+The original 8250 pulses the interrupt line if a higher priority interrupt is
+cleared but a lower priority one is still active. It also clears the tsre bit
+for a moment before loading the tsr from the thr. These may be the bugs the
+PC and XT depend on as the 8250A and up fix them.
+
+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.
+
+The 16550 sometimes will send more then one character over the bus from the fifo
+when the rbr is read making the rx fifo useless. It's unlikely anything depends
+on this behavior.
+
+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 "machine/ins8250.h"
+
+const device_type INS8250 = &device_creator<ins8250_device>;
+const device_type NS16450 = &device_creator<ns16450_device>;
+const device_type NS16550 = &device_creator<ns16550_device>;
+
+ins8250_uart_device::ins8250_uart_device(const machine_config &mconfig, device_type type, const char* name, const char *tag, device_t *owner, UINT32 clock)
+ : device_t(mconfig, type, name, tag, owner, clock),
+ device_serial_interface(mconfig, *this)
+{
+}
+
+ins8250_device::ins8250_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
+ : ins8250_uart_device(mconfig, INS8250, "ins8250", tag, owner, clock)
+{
+ m_device_type = TYPE_INS8250;
+}
+
+ns16450_device::ns16450_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
+ : ins8250_uart_device(mconfig, NS16450, "ns16450", tag, owner, clock)
+{
+ m_device_type = TYPE_NS16450;
+}
+
+ns16550_device::ns16550_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
+ : ins8250_uart_device(mconfig, NS16550, "ns16550", tag, owner, clock)
+{
+ m_device_type = TYPE_NS16550;
+}
+
+/* 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
+#define COM_INT_PENDING_CHAR_TIMEOUT 0x0011
+
+/* ints will continue to be set for as long as there are ints pending */
+void ins8250_uart_device::update_interrupt()
+{
+ int state;
+
+ /* if any bits are set and are enabled */
+ if (((m_int_pending & m_regs.ier) & 0x0f) != 0)
+ {
+ /* trigger next highest priority int */
+
+ /* set int */
+ state = 1;
+ m_regs.iir &= ~(0x08|0x04|0x02);
+
+ /* highest to lowest */
+ if (m_regs.ier & m_int_pending & COM_INT_PENDING_RECEIVER_LINE_STATUS)
+ m_regs.iir |=0x04|0x02;
+ else if (m_regs.ier & m_int_pending & COM_INT_PENDING_RECEIVED_DATA_AVAILABLE)
+ {
+ m_regs.iir |=0x04;
+ if ((m_int_pending & COM_INT_PENDING_CHAR_TIMEOUT) == 0x11)
+ m_regs.iir |= 0x08;
+ }
+ else if (m_regs.ier & m_int_pending & COM_INT_PENDING_TRANSMITTER_HOLDING_REGISTER_EMPTY)
+ m_regs.iir |=0x02;
+
+ /* int pending */
+ m_regs.iir &= ~0x01;
+ }
+ else
+ {
+ /* clear int */
+ state = 0;
+
+ /* no ints pending */
+ m_regs.iir |= 0x01;
+ /* priority level */
+ m_regs.iir &= ~(0x08|0x04|0x02);
+ }
+
+ /* set or clear the int */
+ m_out_int_func(state);
+}
+
+/* set pending bit and trigger int */
+void ins8250_uart_device::trigger_int(int flag)
+{
+ m_int_pending |= flag;
+ update_interrupt();
+}
+
+/* clear pending bit, if any ints are pending, then int will be triggered, otherwise it
+will be cleared */
+void ins8250_uart_device::clear_int(int flag)
+{
+ m_int_pending &= ~flag;
+ update_interrupt();
+}
+
+void ins8250_uart_device::update_clock()
+{
+ int baud;
+ if(m_regs.dl == 0)
+ {
+ set_tra_rate(0);
+ set_rcv_rate(0);
+ return;
+ }
+ baud = clock()/(m_regs.dl*16);
+ set_tra_rate(baud);
+ set_rcv_rate(baud);
+}
+
+WRITE8_MEMBER( ins8250_uart_device::ins8250_w )
+{
+ int tmp;
+
+ switch (offset)
+ {
+ case 0:
+ if (m_regs.lcr & 0x80)
+ {
+ m_regs.dl = (m_regs.dl & 0xff00) | data;
+ update_clock();
+ }
+ else
+ {
+ m_regs.thr = data;
+ m_regs.lsr &= ~0x20;
+ if ( m_regs.mcr & 0x10 )
+ {
+ m_regs.lsr |= 0x61;
+ m_regs.rbr = data;
+ trigger_int(COM_INT_PENDING_RECEIVED_DATA_AVAILABLE);
+ }
+ else
+ {
+ if((m_device_type >= TYPE_NS16550) && (m_regs.fcr & 1))
+ push_tx(data);
+ clear_int(COM_INT_PENDING_TRANSMITTER_HOLDING_REGISTER_EMPTY);
+ if(m_regs.lsr & 0x40)
+ tra_complete();
+ }
+ }
+ break;
+ case 1:
+ if (m_regs.lcr & 0x80)
+ {
+ m_regs.dl = (m_regs.dl & 0xff) | (data << 8);
+ update_clock();
+ }
+ else
+ {
+ m_regs.ier = data;
+ update_interrupt();
+ }
+ break;
+ case 2:
+ set_fcr(data);
+ break;
+ case 3:
+ m_regs.lcr = data;
+ switch ((m_regs.lcr>>3) & 7)
+ {
+ case 1:
+ tmp = SERIAL_PARITY_ODD;
+ break;
+ case 3:
+ tmp = SERIAL_PARITY_EVEN;
+ break;
+ case 5:
+ tmp = SERIAL_PARITY_MARK;
+ break;
+ case 7:
+ tmp = SERIAL_PARITY_SPACE;
+ break;
+ default:
+ tmp = SERIAL_PARITY_NONE;
+ break;
+ }
+ // if 5 data bits and stb = 1, stop bits is supposed to be 1.5
+ set_data_frame((m_regs.lcr & 3) + 5, (m_regs.lcr & 4)?2:1, tmp);
+ break;
+ case 4:
+ if ( ( m_regs.mcr & 0x1f ) != ( data & 0x1f ) )
+ {
+ m_regs.mcr = data & 0x1f;
+
+ if ( m_regs.mcr & 0x10 ) /* loopback test */
+ {
+ data = ( ( m_regs.mcr & 0x0c ) << 4 ) | ( ( m_regs.mcr & 0x01 ) << 5 ) | ( ( m_regs.mcr & 0x02 ) << 3 );
+ if ( ( m_regs.msr & 0x20 ) != ( data & 0x20 ) )
+ data |= 0x02;
+ if ( ( m_regs.msr & 0x10 ) != ( data & 0x10 ) )
+ data |= 0x01;
+ if ( ( m_regs.msr & 0x40 ) && ! ( data & 0x40 ) )
+ data |= 0x04;
+ if ( ( m_regs.msr & 0x80 ) != ( data & 0x80 ) )
+ data |= 0x08;
+ m_regs.msr = data;
+ }
+ else
+ {
+ m_out_dtr_func(m_regs.mcr & 1);
+ m_out_rts_func(m_regs.mcr & 2);
+ m_out_out1_func(m_regs.mcr & 4);
+ m_out_out2_func(m_regs.mcr & 8);
+ }
+ }
+ 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.
+ */
+ m_regs.lsr = data;
+
+ tmp = 0;
+ tmp |= ( m_regs.lsr & 0x01 ) ? COM_INT_PENDING_RECEIVED_DATA_AVAILABLE : 0;
+ tmp |= ( m_regs.lsr & 0x1e ) ? COM_INT_PENDING_RECEIVER_LINE_STATUS : 0;
+ tmp |= ( m_regs.lsr & 0x20 ) ? COM_INT_PENDING_TRANSMITTER_HOLDING_REGISTER_EMPTY : 0;
+ trigger_int(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.
+ */
+ m_regs.msr = data;
+
+ if ( m_regs.msr & 0x0f )
+ trigger_int(COM_INT_PENDING_MODEM_STATUS_REGISTER);
+ break;
+ case 7:
+ m_regs.scr = data;
+ break;
+ }
+}
+
+READ8_MEMBER( ins8250_uart_device::ins8250_r )
+{
+ int data = 0x0ff;
+
+ switch (offset)
+ {
+ case 0:
+ if (m_regs.lcr & 0x80)
+ data = (m_regs.dl & 0xff);
+ else
+ {
+ if((m_device_type >= TYPE_NS16550) && (m_regs.fcr & 1) && !(m_regs.mcr & 0x10))
+ m_regs.rbr = pop_rx();
+ else
+ {
+ clear_int(COM_INT_PENDING_RECEIVED_DATA_AVAILABLE);
+ if( m_regs.lsr & 0x01 )
+ m_regs.lsr &= ~0x01;
+ }
+ data = m_regs.rbr;
+ }
+ break;
+ case 1:
+ if (m_regs.lcr & 0x80)
+ data = (m_regs.dl >> 8);
+ else
+ data = m_regs.ier & 0x0f;
+ break;
+ case 2:
+ data = m_regs.iir;
+ /* The documentation says that reading this register will
+ clear the int if this is the source of the int */
+ if ( m_regs.ier & COM_INT_PENDING_TRANSMITTER_HOLDING_REGISTER_EMPTY )
+ clear_int(COM_INT_PENDING_TRANSMITTER_HOLDING_REGISTER_EMPTY);
+ break;
+ case 3:
+ data = m_regs.lcr;
+ break;
+ case 4:
+ data = m_regs.mcr;
+ break;
+ case 5:
+ data = m_regs.lsr;
+ if( m_regs.lsr & 0x1f )
+ m_regs.lsr &= 0xe1; /* clear FE, PE and OE and BREAK bits */
+
+ /* reading line status register clears int */
+ clear_int(COM_INT_PENDING_RECEIVER_LINE_STATUS);
+ break;
+ case 6:
+ data = m_regs.msr;
+ m_regs.msr &= 0xf0; /* reset delta values */
+
+ /* reading msr clears int */
+ clear_int(COM_INT_PENDING_MODEM_STATUS_REGISTER);
+
+ break;
+ case 7:
+ data = m_regs.scr;
+ break;
+ }
+ return data;
+}
+
+void ns16550_device::rcv_complete()
+{
+ if(!(m_regs.fcr & 1))
+ return ins8250_uart_device::rcv_complete();
+
+ receive_register_extract();
+
+ if(m_rnum == 16)
+ {
+ m_regs.lsr |= 0x02; //overrun
+ trigger_int(COM_INT_PENDING_RECEIVER_LINE_STATUS);
+ return;
+ }
+
+ m_regs.lsr |= 0x01;
+ m_rfifo[m_rhead] = get_received_char();
+ ++m_rhead &= 0x0f;
+ m_rnum++;
+ if(m_rnum >= m_rintlvl)
+ trigger_int(COM_INT_PENDING_RECEIVED_DATA_AVAILABLE);
+ set_timer();
+}
+
+void ns16550_device::tra_complete()
+{
+ if(!(m_regs.fcr & 1))
+ return ins8250_uart_device::tra_complete();
+
+ if(m_ttail != m_thead)
+ {
+ transmit_register_setup(m_tfifo[m_ttail]);
+ ++m_ttail &= 0x0f;
+ m_regs.lsr &= ~0x40;
+ if(m_ttail == m_thead)
+ {
+ m_regs.lsr |= 0x20;
+ trigger_int(COM_INT_PENDING_TRANSMITTER_HOLDING_REGISTER_EMPTY);
+ }
+ }
+ else
+ m_regs.lsr |= 0x40;
+}
+
+void ins8250_uart_device::rcv_complete()
+{
+ if(m_regs.lsr & 0x01)
+ {
+ m_regs.lsr |= 0x02; //overrun
+ trigger_int(COM_INT_PENDING_RECEIVER_LINE_STATUS);
+ receive_register_reset();
+ }
+ else
+ {
+ m_regs.lsr |= 0x01;
+ receive_register_extract();
+ m_regs.rbr = get_received_char();
+ trigger_int(COM_INT_PENDING_RECEIVED_DATA_AVAILABLE);
+ }
+}
+
+void ins8250_uart_device::tra_complete()
+{
+ if(!(m_regs.lsr & 0x20))
+ {
+ transmit_register_setup(m_regs.thr);
+ m_regs.lsr &= ~0x40;
+ m_regs.lsr |= 0x20;
+ trigger_int(COM_INT_PENDING_TRANSMITTER_HOLDING_REGISTER_EMPTY);
+ }
+ else
+ m_regs.lsr |= 0x40;
+}
+
+void ins8250_uart_device::tra_callback()
+{
+ m_out_tx_func(transmit_register_get_data_bit());
+}
+
+void ins8250_uart_device::update_msr(int bit, UINT8 state)
+{
+ UINT8 mask = (1<<bit);
+ if((m_regs.msr & mask) == (state<<bit))
+ return;
+ m_regs.msr |= mask;
+ m_regs.msr = (m_regs.msr & ~(mask << 4)) | (state<<(bit+4));
+ trigger_int(COM_INT_PENDING_MODEM_STATUS_REGISTER);
+}
+
+WRITE_LINE_MEMBER(ins8250_uart_device::dcd_w)
+{
+ update_msr(3, (state&1));
+}
+
+WRITE_LINE_MEMBER(ins8250_uart_device::dsr_w)
+{
+ update_msr(1, (state&1));
+}
+
+WRITE_LINE_MEMBER(ins8250_uart_device::ri_w)
+{
+ update_msr(2, (state&1));
+}
+
+WRITE_LINE_MEMBER(ins8250_uart_device::cts_w)
+{
+ update_msr(0, (state&1));
+}
+
+void ins8250_uart_device::device_start()
+{
+ m_out_tx_func.resolve(m_out_tx_cb, *this);
+ m_out_dtr_func.resolve(m_out_dtr_cb, *this);
+ m_out_rts_func.resolve(m_out_rts_cb, *this);
+ m_out_int_func.resolve(m_out_int_cb, *this);
+ m_out_out1_func.resolve(m_out_out1_cb, *this);
+ m_out_out2_func.resolve(m_out_out2_cb, *this);
+ set_tra_rate(0);
+ set_rcv_rate(0);
+}
+
+void ins8250_uart_device::device_reset()
+{
+ memset(&m_regs, '\0', sizeof(m_regs));
+ m_regs.ier = 0;
+ m_regs.iir = 1;
+ m_regs.lcr = 0;
+ m_regs.mcr = 0;
+ m_regs.lsr = (1<<5) | (1<<6);
+ m_int_pending = 0;
+ receive_register_reset();
+ transmit_register_reset();
+ m_out_rts_func(0);
+ m_out_dtr_func(0);
+ m_out_out1_func(0);
+ m_out_out2_func(0);
+ m_out_tx_func(1);
+}
+
+void ins8250_uart_device::device_config_complete()
+{
+ const ins8250_interface *intf = reinterpret_cast<const ins8250_interface *>(static_config());
+ if(intf != NULL)
+ {
+ *static_cast<ins8250_interface *>(this) = *intf;
+ }
+ else
+ {
+ memset(&m_out_tx_cb, 0, sizeof(m_out_tx_cb));
+ memset(&m_out_dtr_cb, 0, sizeof(m_out_dtr_cb));
+ memset(&m_out_rts_cb, 0, sizeof(m_out_rts_cb));
+ memset(&m_out_int_cb, 0, sizeof(m_out_int_cb));
+ memset(&m_out_out1_cb, 0, sizeof(m_out_out1_cb));
+ memset(&m_out_out2_cb, 0, sizeof(m_out_out2_cb));
+ }
+}
+
+void ns16550_device::device_reset()
+{
+ memset(&m_rfifo, '\0', sizeof(m_rfifo));
+ memset(&m_tfifo, '\0', sizeof(m_tfifo));
+ m_rhead = m_rtail = m_rnum = 0;
+ m_thead = m_ttail = 0;
+ m_timeout->adjust(attotime::never);
+ ins8250_uart_device::device_reset();
+}
+
+void ns16550_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
+{
+ trigger_int(COM_INT_PENDING_CHAR_TIMEOUT);
+ m_timeout->adjust(attotime::never);
+}
+
+void ns16550_device::push_tx(UINT8 data)
+{
+ m_tfifo[m_thead] = data;
+ ++m_thead &= 0x0f;
+}
+
+UINT8 ns16550_device::pop_rx()
+{
+ UINT8 data = m_rfifo[m_rtail];
+ clear_int(COM_INT_PENDING_CHAR_TIMEOUT & ~1); // don't clear bit 1 yet
+
+ if(m_rnum)
+ {
+ ++m_rtail &= 0x0f;
+ m_rnum--;
+ }
+ else
+ data = 0;
+
+ if(m_rnum < m_rintlvl)
+ clear_int(COM_INT_PENDING_RECEIVED_DATA_AVAILABLE);
+
+ if(m_rnum)
+ set_timer();
+ else
+ {
+ m_timeout->adjust(attotime::never);
+ m_regs.lsr &= ~1;
+ }
+
+ return data;
+}
+
+void ns16550_device::set_fcr(UINT8 data)
+{
+ const int bytes_per_int[] = {1, 4, 8, 14};
+ if(!(data & 1))
+ {
+ m_regs.fcr = 0;
+ m_regs.iir &= ~0xc8;
+ return;
+ }
+ if(!(m_regs.fcr & 1) && (data & 1))
+ data |= 0x06;
+ if(data & 2)
+ {
+ memset(&m_rfifo, '\0', sizeof(m_rfifo));
+ m_rhead = m_rtail = m_rnum = 0;
+ }
+ if(data & 4)
+ {
+ memset(&m_tfifo, '\0', sizeof(m_tfifo));
+ m_thead = m_ttail = 0;
+ }
+ m_rintlvl = bytes_per_int[(data>>6)&3];
+ m_regs.iir |= 0xc0;
+ m_regs.fcr = data & ~0xc9;
+ m_regs.lsr |= 0x20;
+ trigger_int(COM_INT_PENDING_TRANSMITTER_HOLDING_REGISTER_EMPTY);
+}