diff options
Diffstat (limited to 'trunk/src/emu/machine/ins8250.c')
-rw-r--r-- | trunk/src/emu/machine/ins8250.c | 715 |
1 files changed, 715 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..8fa8aea7aec --- /dev/null +++ b/trunk/src/emu/machine/ins8250.c @@ -0,0 +1,715 @@ +/********************************************************************** + + 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); |