diff options
Diffstat (limited to 'src/devices/bus/ti99/peb/ti_rs232.cpp')
-rw-r--r-- | src/devices/bus/ti99/peb/ti_rs232.cpp | 1150 |
1 files changed, 1150 insertions, 0 deletions
diff --git a/src/devices/bus/ti99/peb/ti_rs232.cpp b/src/devices/bus/ti99/peb/ti_rs232.cpp new file mode 100644 index 00000000000..c3a25ecd009 --- /dev/null +++ b/src/devices/bus/ti99/peb/ti_rs232.cpp @@ -0,0 +1,1150 @@ +// license:LGPL-2.1+ +// copyright-holders:Michael Zapf +/**************************************************************************** + + TI-99 RS232 and Parallel interface card + + TI99 RS232 card ('rs232') + TMS9902 ('rs232:tms9902_0') + TMS9902 ('rs232:tms9902_1') + TI99 RS232 attached serial device ('rs232:serdev0') + TI99 RS232 attached serial device ('rs232:serdev1') + TI99 PIO attached parallel device ('rs232:piodev') + + Currently this emulation does not directly interact with the serial + interface on the host computer. However, using a socket connection it is + possible to attach an external bridge which interacts with a real UART. + + TI RS232 card wiring + -------------------- + The card uses this wiring (inverters not included) + + +-----+ Pins of the connector + | 9902| (common naming) + | RIN |---<------- 2 (TD) + | XOUT|--->------- 3 (RD) + | RTS |--->------- 8 (DCD) + | CTS |-<-+------- 20 (DTR) + | DSR |-<-+ H--> 6 (DSR) + +-----+ +-----> 5 (CTS) + / + +-----+ / + | CRU |--+ + +-----+ + + This wiring is typical for a DCE, not a DTE. The TI RS232 was obviously + designed to look like a modem. The advantage is that you can use the same + cables for connecting a modem to the RS232 interface or for connecting + a second TI via its interface. To connect to a DTE you can use a 1-1 + wiring cable (1 on 1, 2 on 2 ...) + + The TI manual for the RS232 card suggests the following cables: + + TI RS232 - Modem or other TI RS232 + 2 -----<----- 3 + 3 ----->----- 2 + 6 ----->---- 20 (crossover cable) + 20 -----<----- 6 + + TI RS232 - Terminal (DTE) + 2 ----<------ 2 + 3 ---->------ 3 + 5 ---->------ 5 + 6 ---->------ 6 (1-1 cable) + 8 ---->------ 8 + 20 ----<------20 + + If we want to use a PC serial interface to play the role of the TI + interface we have to map the TI wiring to a suitable wiring for PC + interfaces which are designed as DTEs. This is achieved by the functions + map_lines_in, map_lines_out. + + Note that we now have to swap the cable types: Use a 1-1 cable to connect + another TI or a modem on the other end, and use a crossover cable for + another PC (the usual way of connecting). + + RS232 Over IP protocol + ---------------------- + This implementation can make use of such an external bridge. Normal data + are forwarded to the bridge and back, while line control is organized via + special byte sequences. These sequences are introduced by a 0x1B byte (ESC). + + The protocol has two modes: normal and escape + + normal mode: transmit byte (!= 0x1b) unchanged + escape mode: entered by ESC, bytes following: + ESC = plain ESC byte + length byte[length] = control sequence (length != 0x1b) + + byte[]: + All configuration settings are related to a specified UART; UARTs may + differ in their capabilities and may require specific settings + (e.g. the TMS9902 specifies the line speed by a clock ratio, while + others may have indexed, fixed rates or use integers) + + (x=unused) + + 1ccc xaaa = configuration of parameter ccc; UART type aaa + 1111 xaaa rrrr rrrr rrrr 0000 = config receive rate on aaa + 1110 xaaa rrrr rrrr rrrr 0000 = config transmit rate on aaa + 1101 xaaa xxxx xxbb = config databits bb (00=5 ... 11=8) + 1100 xaaa xxxx xxss = config stop bits ss (00=1.5, 01=2, 1x=1) + 1011 xaaa xxxx xxpp = config parity pp (1x=enable, x1=odd) + + 00ab cdef = line state of RTS=a, CTS=b, DSR=c, DCD=d, DTR=e, RI=f + 01gh i000 = exception g=BRK, h=FRMERR, i=PARERR + + The protocol changes back to normal mode after transmitting the control + sequence. + + Michael Zapf + February 2012: Rewritten as class + +*****************************************************************************/ + +#include "emu.h" +#include "ti_rs232.h" + +#define SENILA_0_BIT 0x80 +#define SENILA_1_BIT 0x40 + +#define RECV_MODE_NORMAL 1 +#define RECV_MODE_ESC 2 +#define RECV_MODE_ESC_LINES 3 + +#define TRACE_LINES 0 +#define TRACE_SETTING 0 +#define TRACE_STATE 0 +#define TRACE_MAP 0 +#define TRACE_IN 0 +#define TRACE_OUT 0 +#define TRACE_ILA 0 + +#define ESC 0x1b + +ti_rs232_pio_device::ti_rs232_pio_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) + : ti_expansion_card_device(mconfig, TI99_RS232, tag, owner, clock), m_piodev(nullptr), m_dsrrom(nullptr), m_pio_direction_in(false), m_pio_handshakeout(false), m_pio_handshakein(false), m_pio_spareout(false), m_pio_sparein(false), m_flag0(false), m_led(false), m_pio_out_buffer(0), m_pio_in_buffer(0), m_pio_readable(false), m_pio_writable(false), m_pio_write(false), m_ila(0) +{ +} + + +/**************************************************************************/ +/* Ports */ + +ti_rs232_attached_device::ti_rs232_attached_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) + : device_t(mconfig, TI99_RS232_DEV, tag, owner, clock), + device_image_interface(mconfig, *this) +{ +} + +ti_pio_attached_device::ti_pio_attached_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) + : device_t(mconfig, TI99_PIO_DEV, tag, owner, clock), + device_image_interface(mconfig, *this) +{ +} + +void ti_rs232_attached_device::device_start() +{ +} + +void ti_pio_attached_device::device_start() +{ +} + +/* + Find the index of the image name. We assume the format + <name><number>, i.e. the number is the longest string from the right + which can be interpreted as a number. +*/ +int ti_rs232_attached_device::get_index_from_tagname() +{ + const char *mytag = tag(); + int maxlen = strlen(mytag); + int i; + for (i=maxlen-1; i >=0; i--) + if (mytag[i] < 48 || mytag[i] > 57) break; + + return atoi(mytag+i+1); +} + +/* + Initialize rs232 unit and open image +*/ +image_init_result ti_rs232_attached_device::call_load() +{ + tms9902_device* tms9902; + + int devnumber = get_index_from_tagname(); + if (devnumber==0) + { + tms9902 = siblingdevice<tms9902_device>("tms9902_0"); + // Turn on polling + tms9902->set_clock(true); + } + else if (devnumber==1) + { + tms9902 = siblingdevice<tms9902_device>("tms9902_1"); + // Turn on polling + tms9902->set_clock(true); + } + else + { + logerror("Could not find device tag number\n"); + return image_init_result::FAIL; + } + + // The following line may cause trouble in the init phase + // card->incoming_dtr(devnumber, (m_file!=nullptr)? ASSERT_LINE : CLEAR_LINE); + + return image_init_result::PASS; // OK +} + +void ti_rs232_attached_device::call_unload() +{ + tms9902_device* tms9902; + + int devnumber = get_index_from_tagname(); + if (devnumber==0) + { + tms9902 = siblingdevice<tms9902_device>("tms9902_0"); + // Turn off polling + tms9902->set_clock(false); + } + else if (devnumber==1) + { + tms9902 = siblingdevice<tms9902_device>("tms9902_1"); + // Turn off polling + tms9902->set_clock(false); + } +} + +/* + Initialize pio unit and open image +*/ +image_init_result ti_pio_attached_device::call_load() +{ + ti_rs232_pio_device* card = static_cast<ti_rs232_pio_device*>(owner()); + + // tell whether the image is readable + card->m_pio_readable = true; + // tell whether the image is writable + card->m_pio_writable = !is_readonly(); + + if (card->m_pio_write && card->m_pio_writable) + card->m_pio_handshakein = false; // receiver ready + else + card->m_pio_handshakein = true; + + return image_init_result::PASS; // OK +} + +/* + close a pio image +*/ +void ti_pio_attached_device::call_unload() +{ + ti_rs232_pio_device* card = static_cast<ti_rs232_pio_device*>(owner()); + + card->m_pio_writable = false; + card->m_pio_handshakein = true; /* receiver not ready */ + card->m_pio_sparein = false; +} + +/****************************************************************************/ + +/* + CRU read +*/ +READ8Z_MEMBER(ti_rs232_pio_device::crureadz) +{ + if ((offset & 0xff00)==m_cru_base) + { + if ((offset & 0x00c0)==0x0000) + { + uint8_t reply = 0x00; + if (m_pio_direction_in) reply |= 0x02; + if (m_pio_handshakein) reply |= 0x04; + if (m_pio_sparein) reply |= 0x08; + if (m_flag0) reply |= 0x10; + // The CTS line is realized as CRU bits + // Mind that this line is handled as an output going to the remote CTS + if ((m_signals[0] & tms9902_device::CTS)!=0) reply |= 0x20; + if ((m_signals[1] & tms9902_device::CTS)!=0) reply |= 0x40; + if (m_led) reply |= 0x80; + *value = reply; + return; + } + if ((offset & 0x00c0)==0x0040) + { + *value = m_uart[0]->cruread(space, offset>>4, 0xff); + return; + } + if ((offset & 0x00c0)==0x0080) + { + *value = m_uart[1]->cruread(space, offset>>4, 0xff); + return; + } + } +} + +/* + CRU write +*/ +WRITE8_MEMBER(ti_rs232_pio_device::cruwrite) +{ + if ((offset & 0xff00)==m_cru_base) + { + if ((offset & 0x00c0)==0x0040) + { + m_uart[0]->cruwrite(space, offset>>1, data, 0xff); + return; + } + if ((offset & 0x00c0)==0x0080) + { + m_uart[1]->cruwrite(space, offset>>1, data, 0xff); + return; + } + + device_image_interface *image = dynamic_cast<device_image_interface *>(m_piodev); + + int bit = (offset & 0x00ff)>>1; + switch (bit) + { + case 0: + m_selected = (data!=0); + break; + + case 1: + m_pio_direction_in = (data!=0); + break; + + case 2: + if ((data!=0) != m_pio_handshakeout) + { + m_pio_handshakeout = (data!=0); + if (m_pio_write && m_pio_writable && (!m_pio_direction_in)) + { /* PIO in output mode */ + if (!m_pio_handshakeout) + { /* write data strobe */ + /* write data and acknowledge */ + uint8_t buf = m_pio_out_buffer; + int ret = image->fwrite(&buf, 1); + if (ret) + m_pio_handshakein = 1; + } + else + { + /* end strobe */ + /* we can write some data: set receiver ready */ + m_pio_handshakein = 0; + } + } + if ((!m_pio_write) && m_pio_readable /*&& pio_direction_in*/) + { /* PIO in input mode */ + if (!m_pio_handshakeout) + { /* receiver ready */ + /* send data and strobe */ + uint8_t buf; + if (image->fread(&buf, 1)) + m_pio_in_buffer = buf; + m_pio_handshakein = 0; + } + else + { + /* data acknowledge */ + /* we can send some data: set transmitter ready */ + m_pio_handshakein = 1; + } + } + } + break; + + case 3: + m_pio_spareout = (data!=0); + break; + + case 4: + m_flag0 = (data!=0); + break; + + case 5: + // Set the CTS line for RS232/1 + if (TRACE_LINES) logerror("(1/3) Setting CTS* via CRU to %d\n", data); + output_line_state(0, tms9902_device::CTS, (data==0)? tms9902_device::CTS : 0); + break; + + case 6: + // Set the CTS line for RS232/2 + if (TRACE_LINES) logerror("(2/4) Setting CTS* via CRU to %d\n", data); + output_line_state(1, tms9902_device::CTS, (data==0)? tms9902_device::CTS : 0); + break; + + case 7: + m_led = (data!=0); + break; + } + return; + } +} + +/* + Memory read +*/ +READ8Z_MEMBER( ti_rs232_pio_device::readz ) +{ + if (m_senila==ASSERT_LINE) + { + if (TRACE_ILA) logerror("Sensing ILA\n"); + *value = m_ila; + // The card ROM must be unselected, or we get two values + // on the data bus + + // Not sure whether this is correct; there is no software that makes + // use of it + m_ila = 0; + } + if (((offset & m_select_mask)==m_select_value) && m_selected) + { + if ((offset & 0x1000)==0x0000) + { + *value = m_dsrrom[offset&0x0fff]; + } + else + { + *value = m_pio_direction_in ? m_pio_in_buffer : m_pio_out_buffer; + } + } +} + +/* + Memory write +*/ +WRITE8_MEMBER( ti_rs232_pio_device::write ) +{ + if (((offset & m_select_mask)==m_select_value) && m_selected) + { + if ((offset & 0x1001)==0x1000) + { + m_pio_out_buffer = data; + } + } +} + +/**************************************************************************/ + +/* + The DTR line of the interface card is wired to the CTS and DSR + of the UART. +*/ +void ti_rs232_pio_device::incoming_dtr(int uartind, line_state value) +{ + if (TRACE_LINES) logerror("(RS232/%d) Incoming DTR = %d\n", uartind+1, (value==ASSERT_LINE)? 1:0); + + m_uart[uartind]->rcv_cts(value); + m_uart[uartind]->rcv_dsr(value); +} + +/* + Data transmission +*/ +void ti_rs232_pio_device::transmit_data(int uartind, uint8_t value) +{ + uint8_t buf = value; + + device_image_interface *serial; + serial = dynamic_cast<device_image_interface *>(m_serdev[uartind]); + if (!serial->exists()) + { + logerror("(RS232/%d) No serial output attached\n", uartind+1); + return; + } + + // Send a double ESC if this is not a control operation + if (buf==0x1b) + { + if (TRACE_OUT) logerror("(RS232/%d) send ESC (requires another ESC)\n", uartind+1); + serial->fwrite(&buf, 1); + } + if (TRACE_OUT) + { + char cbuf = (buf < 0x20 || buf > 0x7e)? '.' : (char)buf; + logerror("(RS232/%d) send %c <%02x>\n", uartind+1, cbuf, buf); + } + serial->fwrite(&buf, 1); +} + +/* + Map the DCE-like wiring to a DTE-like wiring and vice versa (mapping==0) + No handshake + + Emulated PC serial + TI RS232 interface + XOUT 2 TXD ----->-----( 3) ---> TXD + RIN 3 RXD -----<-----( 2) <--- RXD + CRU 5 CTS -| |-( 8) <--- DCD (cable) + +12V 6 DSR ----->-----(20) ---> DTR + RTS 8 DCD ----->-----( 4) ---> RTS + DSR+CTS 20 DTR -----<-----( 6) <--- DSR + |-( 5) <--- CTS + + Alternative mapping: (mapping==1) + RTS/CTS handshake + + Emulated PC serial + TI RS232 interface + XOUT 2 TXD ----->-----( 3) ---> TXD + RIN 3 RXD -----<-----( 2) <--- RXD + CRU 5 CTS ----->-----( 4) ---> RTS + +12V 6 DSR -| |-( 6) <--- DSR + RTS 8 DCD ----->-----(20) ---> DTR + DSR+CTS 20 DTR -----<-----( 8) <--- DCD + |-( 5) <--- CTS + + Yet another mapping: (mapping==2) + CRU-based handshake + + Emulated PC serial + TI RS232 interface + XOUT 2 TXD ----->-----( 3) ---> TXD + RIN 3 RXD -----<-----( 2) <--- RXD + CRU 5 CTS ----->-----(20) ---> DTR + +12V 6 DSR -| |-( 6) <--- DSR + RTS 8 DCD ----->-----( 4) ---> RTS + DSR+CTS 20 DTR -----<-----( 5) <--- CTS (cable) + |-( 8) <--- DCD + +*/ +uint8_t ti_rs232_pio_device::map_lines_out(int uartind, uint8_t value) +{ + uint8_t ret = 0; + int mapping = ioport("SERIALMAP")->read(); + + // 00ab cdef = setting line RTS=a, CTS=b, DSR=c, DCD=d, DTR=e, RI=f + + if (TRACE_LINES) logerror("(RS232/%d) out connector pins = 0x%02x; translate for DTE\n", uartind+1, value); + + if (value & tms9902_device::BRK) + { + if (TRACE_MAP) logerror("(RS232/%d) Sending BRK\n", uartind+1); + ret |= tms9902_device::EXCEPT | tms9902_device::BRK; + } + + if (mapping==0) + { + // V1 + if (value & tms9902_device::CTS) + { + if (TRACE_MAP) logerror("(RS232/%d) Cannot map CTS line, ignoring\n", uartind+1); + } + if (value & tms9902_device::DSR) + { + ret |= tms9902_device::DTR; + if (TRACE_MAP) logerror("(RS232/%d) Setting DTR line\n", uartind+1); + } + if (value & tms9902_device::DCD) + { + ret |= tms9902_device::RTS; + if (TRACE_MAP) logerror("(RS232/%d) Setting RTS line\n", uartind+1); + } + } + else + { + if (mapping==1) + { + // V2 + if (value & tms9902_device::CTS) + { + ret |= tms9902_device::RTS; + if (TRACE_MAP) logerror("(RS232/%d) Setting RTS line\n", uartind+1); + } + if (value & tms9902_device::DCD) + { + ret |= tms9902_device::DTR; + if (TRACE_MAP) logerror("(RS232/%d) Setting DTR line\n", uartind+1); + } + } + else + { + // v3 + if (value & tms9902_device::CTS) + { + ret |= tms9902_device::DTR; + if (TRACE_MAP) logerror("(RS232/%d) Setting DTR line\n", uartind+1); + } + if (value & tms9902_device::DSR) + { + if (TRACE_MAP) logerror("(RS232/%d) Cannot map DSR line, ignoring\n", uartind+1); + } + if (value & tms9902_device::DCD) + { + ret |= tms9902_device::RTS; + if (TRACE_MAP) logerror("(RS232/%d) Setting RTS line\n", uartind+1); + } + } + } + + return ret; +} + +uint8_t ti_rs232_pio_device::map_lines_in(int uartind, uint8_t value) +{ + uint8_t ret = 0; + int mapping = ioport("SERIALMAP")->read(); + + // 00ab cdef = setting line RTS=a, CTS=b, DSR=c, DCD=d, DTR=e, RI=f + + if (TRACE_LINES) logerror("(RS232/%d) in connector pins = 0x%02x; translate from DTE\n", uartind+1, value); + + if (value & tms9902_device::BRK) + { + if (TRACE_MAP) logerror("(RS232/%d) Getting BRK\n", uartind+1); + ret |= tms9902_device::EXCEPT | tms9902_device::BRK; + } + + if (mapping==0) + { + // V1 + if (value & tms9902_device::CTS) + { + if (TRACE_MAP) logerror("(RS232/%d) Cannot map CTS line, ignoring\n", uartind+1); + } + if (value & tms9902_device::DSR) + { + ret |= tms9902_device::DTR; + if (TRACE_MAP) logerror("(RS232/%d) Setting DTR line\n", uartind+1); + } + if (value & tms9902_device::DCD) + { + if (TRACE_MAP) logerror("(RS232/%d) Cannot map DCD line, ignoring\n", uartind+1); + } + } + else + { + if (mapping==1) + { + if (value & tms9902_device::DCD) + { + ret |= tms9902_device::DTR; + if (TRACE_MAP) logerror("(RS232/%d) Setting DTR line\n", uartind+1); + } + if (value & tms9902_device::DSR) + { + if (TRACE_MAP) logerror("(RS232/%d) Cannot map DSR line, ignoring\n", uartind+1); + } + if (value & tms9902_device::CTS) + { + if (TRACE_MAP) logerror("(RS232/%d) Cannot map CTS line, ignoring\n", uartind+1); + } + } + else + { + if (value & tms9902_device::CTS) + { + ret |= tms9902_device::DTR; + if (TRACE_MAP) logerror("(RS232/%d) Setting DTR line\n", uartind+1); + } + if (value & tms9902_device::DSR) + { + if (TRACE_MAP) logerror("(RS232/%d) Cannot map DSR line, ignoring\n", uartind+1); + } + if (value & tms9902_device::DCD) + { + if (TRACE_MAP) logerror("(RS232/%d) Cannot map DCD line, ignoring\n", uartind+1); + } + } + } + + return ret; +} +/* + Receive a character or a line state from the remote site. This method + is called by a timer with some sufficiently high polling frequency. Note + that the control lines are not subject to baud rates. + The higher polling frequency will cause overloads in the TMS9902 which has + a one-byte buffer only: Since the data source (e.g. the PC UART and the + socket connection) may be buffered, we may get a cluster of bytes in rapid + succession. In order to avoid this, this function uses the parameter + "baudpoll" which is the ratio of the characters/second and the polling + frequency. (char/sec is baud rate divided by 10.) + Whenever we receive a character that is passed to the UART, we have to + pause for 1/baudpoll iterations before getting the next byte from the + data source. + + FIXME: This may fail when the emulated system tries to stop the remote + system by deactivating RTS or DTR, but there are still incoming + bytes in the socket or PC UART buffer. The buffered bytes may then cause + an overflow in the emulated UART, since the application program expects + the remote system to stop sending instantly. + The only way to handle this is to mirror the activity within the serial + bridge: Whenever a RTS=0 or DTR=0 is transmitted to the remote site, the + serial bridge must stop delivering data bytes until the handshake opens the + channel again. +*/ +void ti_rs232_pio_device::receive_data_or_line_state(int uartind) +{ + device_image_interface *serial; + uint8_t buffer; + + serial = dynamic_cast<device_image_interface *>(m_serdev[uartind]); + + if (!serial->exists()) + { + logerror("(RS232/%d) No serial input attached\n", uartind+1); + return; + } + + double baudpoll = m_uart[uartind]->get_baudpoll(); + + // If more than the minimum waiting time since the last data byte has + // elapsed, we can get a new value. + if (m_time_hold[uartind] > 1.0) + { + // Buffer empty? + if (m_bufpos[uartind] == m_buflen[uartind]) + { + // Get all out of sdlsocket + m_buflen[uartind] = serial->fread(m_recvbuf[uartind].get(), 512); + m_bufpos[uartind] = 0; + if (m_buflen[uartind]==0) return; + } + buffer = m_recvbuf[uartind][m_bufpos[uartind]++]; + } + else + { + // number of polls has not yet elapsed; we have to wait. + m_time_hold[uartind] += baudpoll; + return; + } + + char cbuf = (buffer < 0x20 || buffer > 0x7e)? '.' : (char)buffer; + + // No config parameters here, only data or line setting + switch (m_recv_mode[uartind]) + { + case RECV_MODE_NORMAL: + if (buffer==0x1b) + { + if (TRACE_IN) logerror("(RS232/%d) Received: %c <%02x>, switch to ESC mode\n", uartind+1, cbuf, buffer); + m_recv_mode[uartind] = RECV_MODE_ESC; + } + else + { + if (TRACE_IN) logerror("(RS232/%d) Received: %c <%02x>, pass to UART\n", uartind+1, cbuf, buffer); + m_uart[uartind]->rcv_data(buffer); + m_time_hold[uartind] = 0.0; + } + break; + case RECV_MODE_ESC: + if (buffer==0x1b) + { + m_recv_mode[uartind] = RECV_MODE_NORMAL; + if (TRACE_STATE) logerror("(RS232/%d) Received another ESC, passing to UART, leaving ESC mode\n", uartind+1); + m_uart[uartind]->rcv_data(buffer); + m_time_hold[uartind] = 0.0; + } + else + { + // the byte in buffer is the length byte + if (TRACE_STATE) logerror("(RS232/%d) Received length byte <%02x> in ESC mode\n", uartind+1, buffer); + if (buffer != 1) + { + logerror("(RS232/%d) ** ERROR: Expected length byte 1 but got 0x%02x, leaving ESC mode.\n", uartind+1, buffer); + m_recv_mode[uartind] = RECV_MODE_NORMAL; + } + else + m_recv_mode[uartind] = RECV_MODE_ESC_LINES; + } + break; + + case RECV_MODE_ESC_LINES: + // Map the real serial interface lines to our emulated connector + // The mapping is the same for both directions, so we use the same function + if (buffer & tms9902_device::EXCEPT) + { + // Exception states: BRK, FRMERR, PARERR + if (TRACE_LINES) logerror("(RS232/%d) Received BRK or ERROR <%02x>\n", uartind+1, buffer); + m_uart[uartind]->rcv_break(((buffer & tms9902_device::BRK)!=0)); + + if (buffer & tms9902_device::FRMERR) m_uart[uartind]->rcv_framing_error(); + if (buffer & tms9902_device::PARERR) m_uart[uartind]->rcv_parity_error(); + } + else + { + buffer = map_lines_in(uartind, buffer); + if (TRACE_LINES) logerror("(RS232/%d) Received (remapped) <%02x> in ESC mode\n", uartind+1, buffer); + + // The DTR line on the RS232 connector of the board is wired to both the + // CTS and the DSR pin of the TMS9902 + // Apart from the data line, DTR is the only input line + incoming_dtr(uartind, (buffer & tms9902_device::DTR)? ASSERT_LINE : CLEAR_LINE); + } + + m_recv_mode[uartind] = RECV_MODE_NORMAL; + break; + + default: + logerror("(RS232/%d) Unknown mode: %d\n", uartind+1, m_recv_mode[uartind]); + } +} + +/* + Control operations like configuration or line changes +*/ +void ti_rs232_pio_device::configure_interface(int uartind, int type, int value) +{ + uint8_t bufctrl[4]; + device_image_interface *serial; + uint8_t esc = ESC; + + serial = dynamic_cast<device_image_interface *>(m_serdev[uartind]); + + if (!serial->exists()) + { + logerror("(RS232/%d) No serial output attached\n", uartind+1); + return; + } + + serial->fwrite(&esc, 1); + bufctrl[0] = 0x02; + bufctrl[1] = tms9902_device::CONFIG | tms9902_device::TYPE_TMS9902; + + switch (type) { + case tms9902_device::RATERECV: + if (TRACE_SETTING) logerror("(RS232/%d) Send receive rate %04x\n", uartind+1, value); + // value has 12 bits + // 1ccc xaaa = config adapter type a + // 1111 xaaa rrrr rrrr rrrr 0000 = config receive rate on a + // 1110 xaaa rrrr rrrr rrrr 0000 = config transmit rate on a + bufctrl[0] = 0x03; // length + bufctrl[1] |= tms9902_device::RATERECV; + bufctrl[2] = (value & 0x0ff0)>>4; + bufctrl[3] = (value & 0x0f)<<4; + break; + case tms9902_device::RATEXMIT: + if (TRACE_SETTING) logerror("(RS232/%d) Send transmit rate %04x\n", uartind+1, value); + bufctrl[0] = 0x03; // length + bufctrl[1] |= tms9902_device::RATEXMIT; + bufctrl[2] = (value & 0x0ff0)>>4; + bufctrl[3] = (value & 0x0f)<<4; + break; + case tms9902_device::STOPBITS: + if (TRACE_SETTING) logerror("(RS232/%d) Send stop bit config %02x\n", uartind+1, value&0x03); + bufctrl[1] |= tms9902_device::STOPBITS; + bufctrl[2] = (value & 0x03); + break; + case tms9902_device::DATABITS: + if (TRACE_SETTING) logerror("(RS232/%d) Send data bit config %02x\n", uartind+1, value&0x03); + bufctrl[1] |= tms9902_device::DATABITS; + bufctrl[2] = (value & 0x03); + break; + case tms9902_device::PARITY: + if (TRACE_SETTING) logerror("(RS232/%d) Send parity config %02x\n", uartind+1, value&0x03); + bufctrl[1] |= tms9902_device::PARITY; + bufctrl[2] = (value & 0x03); + break; + default: + logerror("(RS232/%d) Error - unknown config type %02x\n", uartind+1, type); + } + + serial->fwrite(bufctrl, bufctrl[0]+1); +} + +void ti_rs232_pio_device::set_bit(int uartind, int line, int value) +{ + if (TRACE_LINES) + { + switch (line) + { + case tms9902_device::CTS: logerror("(RS232/%d) Set CTS(out)=%s\n", uartind+1, (value!=0)? "asserted" : "cleared"); break; + case tms9902_device::DCD: logerror("(RS232/%d) Set DCD(out)=%s\n", uartind+1, (value!=0)? "asserted" : "cleared"); break; + case tms9902_device::BRK: logerror("(RS232/%d) Set BRK(out)=%s\n", uartind+1, (value!=0)? "asserted" : "cleared"); break; + } + } + + if (value!=0) m_signals[uartind] |= line; + else m_signals[uartind] &= ~line; +} + +/* + Line changes +*/ +void ti_rs232_pio_device::output_exception(int uartind, int param, uint8_t value) +{ + device_image_interface *serial; + uint8_t bufctrl[2]; + uint8_t esc = ESC; + + serial = dynamic_cast<device_image_interface *>(m_serdev[uartind]); + + if (!serial->exists()) + { + logerror("(RS232/%d) No serial output attached\n", uartind+1); + return; + } + + serial->fwrite(&esc, 1); + + bufctrl[0] = 1; + // 0100 0xxv = exception xx: 02=BRK, 04=FRMERR, 06=PARERR; v=0,1 (only for BRK) + // BRK is the only output exception + bufctrl[1] = tms9902_device::EXCEPT | param | (value&1); + serial->fwrite(bufctrl, 2); +} + +/* + Line changes +*/ +void ti_rs232_pio_device::output_line_state(int uartind, int mask, uint8_t value) +{ + device_image_interface *serial; + uint8_t bufctrl[2]; + uint8_t esc = ESC; + + serial = dynamic_cast<device_image_interface *>(m_serdev[uartind]); + + if (!serial->exists()) + { + logerror("(RS232/%d) No serial output attached\n", uartind+1); + return; + } + + // Send ESC to serial bridge + serial->fwrite(&esc, 1); + + // Length 1 + bufctrl[0] = 1; + + // 01ab cdef = setting line RTS=a, CTS=b, DSR=c, DCD=d, DTR=e, RI=f + + // The CTS line (coming from a CRU bit) is connected to the CTS pin + if (mask & tms9902_device::CTS) set_bit(uartind, tms9902_device::CTS, value & tms9902_device::CTS); + + // The RTS line (from 9902) is connected to the DCD pin + if (mask & tms9902_device::RTS) set_bit(uartind, tms9902_device::DCD, value & tms9902_device::RTS); + + // The DSR pin is hardwired to +5V + set_bit(uartind, tms9902_device::DSR, 1); + + // As of here, the lines are set according to the schematics of the + // serial interface. + + // Now translate the signals of the board to those of a DTE-like device + // so that we can pass the signal to the real PC serial interface + // (can be imagined as if we emulated the cable) + bufctrl[1] = map_lines_out(uartind, m_signals[uartind]); + serial->fwrite(bufctrl, 2); +} + +/*********************************************************************** + callbacks +************************************************************************/ +/* + Propagates the /INT signal of the UARTs to the /INT line of the pbox. +*/ +WRITE_LINE_MEMBER( ti_rs232_pio_device::int0_callback ) +{ + int senila_bit = SENILA_0_BIT; + + if (state==ASSERT_LINE) m_ila |= senila_bit; + else m_ila &= ~senila_bit; + + m_slot->set_inta(state); +} + +WRITE_LINE_MEMBER( ti_rs232_pio_device::int1_callback ) +{ + int senila_bit = SENILA_1_BIT; + + if (state==ASSERT_LINE) m_ila |= senila_bit; + else m_ila &= ~senila_bit; + + m_slot->set_inta(state); +} + +/* + Called from the UART when it wants to receive a character + However, characters are not passed to it at this point + Instead, we check for signal line change or data transmission + and call the respective function +*/ +WRITE_LINE_MEMBER( ti_rs232_pio_device::rcv0_callback ) +{ + receive_data_or_line_state(0); +} + +WRITE_LINE_MEMBER( ti_rs232_pio_device::rcv1_callback ) +{ + receive_data_or_line_state(1); +} + +WRITE8_MEMBER( ti_rs232_pio_device::xmit0_callback ) +{ + transmit_data(0, data); +} + +WRITE8_MEMBER( ti_rs232_pio_device::xmit1_callback ) +{ + transmit_data(1, data); +} + +void ti_rs232_pio_device::ctrl_callback(int uartind, int offset, uint8_t data) +{ + if ((offset & tms9902_device::CONFIG)!=0) + { + // We cannot pass the configuration data as they need more than 8 bits. + // Could be done by a write16 function as well. + configure_interface(uartind, data, m_uart[uartind]->get_config_value()); + } + else + { + if ((offset & tms9902_device::EXCEPT)!=0) + { + output_exception(uartind, offset & ~tms9902_device::EXCEPT, data); + } + else + { + output_line_state(uartind, offset, data); + } + } +} + +WRITE8_MEMBER( ti_rs232_pio_device::ctrl0_callback ) +{ + ctrl_callback(0, offset, data); +} + +WRITE8_MEMBER( ti_rs232_pio_device::ctrl1_callback ) +{ + ctrl_callback(1, offset, data); +} + +void ti_rs232_pio_device::device_start() +{ + m_dsrrom = memregion(DSRROM)->base(); + m_uart[0] = subdevice<tms9902_device>("tms9902_0"); + m_uart[1] = subdevice<tms9902_device>("tms9902_1"); + m_serdev[0] = subdevice<ti_rs232_attached_device>("serdev0"); + m_serdev[1] = subdevice<ti_rs232_attached_device>("serdev1"); + m_piodev = subdevice<ti_pio_attached_device>("piodev"); + // Prepare the receive buffers + m_recvbuf[0] = std::make_unique<uint8_t[]>(512); + m_recvbuf[1] = std::make_unique<uint8_t[]>(512); + m_pio_write = true; // required for call_load of pio_attached_device + m_pio_writable = false; + m_pio_handshakein = false; + + // We don't save the receive buffers for persistent state + save_pointer(NAME(m_signals),2); + save_pointer(NAME(m_recv_mode),2); + save_pointer(NAME(m_time_hold),2); + save_item(NAME(m_pio_direction_in)); + save_item(NAME(m_pio_handshakeout)); + save_item(NAME(m_pio_handshakein)); + save_item(NAME(m_pio_spareout)); + save_item(NAME(m_pio_sparein)); + save_item(NAME(m_flag0)); + save_item(NAME(m_led)); + save_item(NAME(m_pio_out_buffer)); + save_item(NAME(m_pio_in_buffer)); + save_item(NAME(m_pio_readable)); + save_item(NAME(m_pio_writable)); + save_item(NAME(m_pio_write)); + save_item(NAME(m_ila)); +} + +void ti_rs232_pio_device::device_stop() +{ + m_recvbuf[0] = nullptr; + m_recvbuf[1] = nullptr; +} + +void ti_rs232_pio_device::device_reset() +{ + m_pio_direction_in = false; + m_pio_handshakeout = false; + m_pio_spareout = false; + m_flag0 = false; + + set_bit(0, tms9902_device::CTS, 0); + set_bit(1, tms9902_device::CTS, 0); + + m_led = false; + m_recv_mode[0] = RECV_MODE_NORMAL; + m_recv_mode[1] = RECV_MODE_NORMAL; + + m_bufpos[0] = m_bufpos[1] = m_buflen[0] = m_buflen[1] = 0; + + if (m_genmod) + { + m_select_mask = 0x1fe000; + m_select_value = 0x174000; + } + else + { + m_select_mask = 0x7e000; + m_select_value = 0x74000; + } + + m_selected = false; + + m_cru_base = (ioport("CRURS232")->read()==0)? 0x1300 : 0x1500; + + m_time_hold[0] = m_time_hold[1] = 0.0; + + // Both DTRs are pulled up + incoming_dtr(0, ASSERT_LINE); + incoming_dtr(1, ASSERT_LINE); +} + +static MACHINE_CONFIG_FRAGMENT( ti_rs232 ) + MCFG_DEVICE_ADD("tms9902_0", TMS9902, 3000000) + MCFG_TMS9902_INT_CB(WRITELINE(ti_rs232_pio_device, int0_callback)) /* called when interrupt pin state changes */ + MCFG_TMS9902_RCV_CB(WRITELINE(ti_rs232_pio_device, rcv0_callback)) /* called when a character is received */ + MCFG_TMS9902_XMIT_CB(WRITE8(ti_rs232_pio_device, xmit0_callback)) /* called when a character is transmitted */ + MCFG_TMS9902_CTRL_CB(WRITE8(ti_rs232_pio_device, ctrl0_callback)) + MCFG_DEVICE_ADD("tms9902_1", TMS9902, 3000000) + MCFG_TMS9902_INT_CB(WRITELINE(ti_rs232_pio_device, int1_callback)) /* called when interrupt pin state changes */ + MCFG_TMS9902_RCV_CB(WRITELINE(ti_rs232_pio_device, rcv1_callback)) /* called when a character is received */ + MCFG_TMS9902_XMIT_CB(WRITE8(ti_rs232_pio_device, xmit1_callback)) /* called when a character is transmitted */ + MCFG_TMS9902_CTRL_CB(WRITE8(ti_rs232_pio_device, ctrl1_callback)) + MCFG_DEVICE_ADD("serdev0", TI99_RS232_DEV, 0) + MCFG_DEVICE_ADD("serdev1", TI99_RS232_DEV, 0) + MCFG_DEVICE_ADD("piodev", TI99_PIO_DEV, 0) +MACHINE_CONFIG_END + +ROM_START( ti_rs232 ) + ROM_REGION(0x1000, DSRROM, 0) + ROM_LOAD("rs232pio_dsr.u1", 0x0000, 0x1000, CRC(eab382fb) SHA1(ee609a18a21f1a3ddab334e8798d5f2a0fcefa91)) /* TI rs232 DSR ROM */ +ROM_END + +INPUT_PORTS_START( ti_rs232 ) + PORT_START( "CRURS232" ) + PORT_DIPNAME( 0x01, 0x00, "TI-RS232 CRU base" ) + PORT_DIPSETTING( 0x00, "1300" ) + PORT_DIPSETTING( 0x00, "1500" ) + + PORT_START( "SERIALMAP" ) + PORT_CONFNAME( 0x03, 0x00, "Serial cable pin mapping" ) + PORT_CONFSETTING( 0x00, "6-20" ) + PORT_CONFSETTING( 0x01, "8-20" ) + PORT_CONFSETTING( 0x02, "5-20" ) +INPUT_PORTS_END + +machine_config_constructor ti_rs232_pio_device::device_mconfig_additions() const +{ + return MACHINE_CONFIG_NAME( ti_rs232 ); +} + +const tiny_rom_entry *ti_rs232_pio_device::device_rom_region() const +{ + return ROM_NAME( ti_rs232 ); +} + +ioport_constructor ti_rs232_pio_device::device_input_ports() const +{ + return INPUT_PORTS_NAME(ti_rs232); +} + +DEFINE_DEVICE_TYPE(TI99_RS232, ti_rs232_pio_device, "ti99_rs232", "TI-99 RS232/PIO interface") +DEFINE_DEVICE_TYPE(TI99_RS232_DEV, ti_rs232_attached_device, "ti99_rs232_atttached", "TI-99 Serial attached device") +DEFINE_DEVICE_TYPE(TI99_PIO_DEV, ti_pio_attached_device, "ti99_pio_attached", "TI-99 Parallel attached device") |