From f79593ee04e60c7b5560e01f40080cc8fa52d76c Mon Sep 17 00:00:00 2001 From: Michael Zapf Date: Mon, 23 Jul 2012 19:02:56 +0000 Subject: [PATCH] Moved TMS9901/9902 from mess/machine into emu/machine subtree --- .gitattributes | 4 + src/emu/emu.mak | 2 + src/emu/machine/tms9901.c | 542 ++++++++++++++++++++++++ src/emu/machine/tms9901.h | 142 +++++++ src/emu/machine/tms9902.c | 837 ++++++++++++++++++++++++++++++++++++++ src/emu/machine/tms9902.h | 195 +++++++++ 6 files changed, 1722 insertions(+) create mode 100644 src/emu/machine/tms9901.c create mode 100644 src/emu/machine/tms9901.h create mode 100644 src/emu/machine/tms9902.c create mode 100644 src/emu/machine/tms9902.h diff --git a/.gitattributes b/.gitattributes index 79a152677f5..d3009ccf667 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1016,6 +1016,10 @@ src/emu/machine/tmp68301.c svneol=native#text/plain src/emu/machine/tmp68301.h svneol=native#text/plain src/emu/machine/tms6100.c svneol=native#text/plain src/emu/machine/tms6100.h svneol=native#text/plain +src/emu/machine/tms9901.c svneol=native#text/plain +src/emu/machine/tms9901.h svneol=native#text/plain +src/emu/machine/tms9902.c svneol=native#text/plain +src/emu/machine/tms9902.h svneol=native#text/plain src/emu/machine/upd1990a.c svneol=native#text/plain src/emu/machine/upd1990a.h svneol=native#text/plain src/emu/machine/upd4701.c svneol=native#text/plain diff --git a/src/emu/emu.mak b/src/emu/emu.mak index 4dfaefc57dc..12b54dde26b 100644 --- a/src/emu/emu.mak +++ b/src/emu/emu.mak @@ -253,6 +253,8 @@ EMUMACHINEOBJS = \ $(EMUMACHINE)/timekpr.o \ $(EMUMACHINE)/tmp68301.o \ $(EMUMACHINE)/tms6100.o \ + $(EMUMACHINE)/tms9901.o \ + $(EMUMACHINE)/tms9902.o \ $(EMUMACHINE)/upd1990a.o \ $(EMUMACHINE)/upd4701.o \ $(EMUMACHINE)/upd7201.o \ diff --git a/src/emu/machine/tms9901.c b/src/emu/machine/tms9901.c new file mode 100644 index 00000000000..9082dd96ac0 --- /dev/null +++ b/src/emu/machine/tms9901.c @@ -0,0 +1,542 @@ +/**************************************************************************** + + TMS9901 Programmable System Interface + +Overview: + TMS9901 is a support chip for TMS9900. It handles interrupts, provides + several I/O pins, and a timer (a.k.a. clock: it is merely a register which + decrements regularly and can generate an interrupt when it reaches 0). + + It communicates with the TMS9900 with the CRU bus, and with the rest of the + world with a number of parallel I/O pins. + + I/O and timer functions should work with any other 990/99xx/99xxx CPU. + On the other hand, interrupt handling was primarily designed for tms9900 + and 99000 based systems: other CPUs can support interrupts, but not the 16 + distinct interrupt vectors. + +Pins: + Vcc, Vss: power supply + Phi*: system clock (connected to TMS9900 Phi3* or TMS9980 CLKOUT*) + RST1*: reset input + CRUIN, CRUOUT, CRUCLK, CE*, S0-S4: CRU bus (CPU interface) + INTREQ*, IC0-IC3: interrupt bus (CPU interface) + INT*1-INT*6: used as interrupt/input pins. + P0-P6: used as input/output pins. + INT*7/P15-INT*15/P7: used as either interrupt/input or input/output pins. + Note that a pin cannot be used simultaneously as output and as interrupt. + (This is mostly obvious, but it implies that you cannot trigger an + interrupt by setting the output state of a pin, which is not SO obvious.) + +Interrupt handling: + After each clock cycle, TMS9901 latches the state of INT1*-INT15* (except + pins which are set as output pins). If the clock is enabled, it replaces + INT3* with an internal timer interrupt flag. Then it inverts the value and + performs a bit-wise AND with the interrupt mask. + + If there are some unmasked interrupt bits, INTREQ* is asserted and the code + of the lowest active interrupt is placed on IC0-IC3. If these pins are + duly connected to the tms9900 INTREQ* and IC0-IC3 pins, the result is that + asserting an INTn* on tms9901 will cause a level-n interrupt request on the + tms9900, provided that this interrupt pin is not masked in tms9901, and + that no unmasked higher-priority (i.e. lower-level) interrupt pin is set. + + This interrupt request lasts for as long as the interrupt pin and the + relevant bit in the interrupt mask are set (level-triggered interrupts). + (The request may be shadowed by a higher-priority interrupt request, but + it will resume when the higher-priority request ends.) + + TIMER interrupts are kind of an exception, since they are not associated + with an external interrupt pin. I think there is an internal timer + interrupt flag that is set when the decrementer reaches 0, and is cleared + by a write to the 9901 int*3 enable bit ("SBO 3" in interrupt mode). + +TODO: + * Emulate the RST1* input. Note that RST1* active (low) makes INTREQ* + inactive (high) with IC0-IC3 = 0. + * the clock read register is updated every time the timer decrements when + the TMS9901 is not in clock mode. This probably implies that if the + clock mode is cleared and re-asserted immediately, the tms9901 may fail + to update the clock read register: this is not emulated. + * The clock mode is entered when a 1 is written to the control bit. It is + exited when a 0 is written to the control bit or the a tms9901 select bit + greater than 15 is accessed. According to the data sheet, "when CE* is + inactive (HIGH), the PSI is not disabled from seeing the select lines. + As the CPU is accessing memory, A10-A14 could very easily have a value of + 15 or greater" (this is assuming that S0-S4 are connected to A10-A14, + which makes sense with most tms9900 family members). There is no way + this "feature" (I would call it a hardware bug) can be emulated + efficiently, as we would need to watch every memory access. + +MZ: According to the description in + A. Osborne, G. Kane: Osborne 16-bit microprocessor handbook + page 3-81 + the 9901 only temporarily leaves the timer mode as long as S0 is set to 1. + In the meantime the timer function continues but cannot be queried. This + makes it possible to continue using the chip as a timer while working with + its I/O pins. Thus I believe the above TODO concering the exit of the timer + mode is not applicable. + The problem is that the original 9901 specification is not clear about this. + +MZ: Turned to class (January 2012) + +TODO: Tests on a real machine +- Set an interrupt input (e.g. keyboard for Geneve), trigger RST2*, check whether + interrupt mask has been reset +- Check whether the clock_read_register is updated whenever clock mode is exited + (in particular when S0=1, i.e. A10=1 -> addresses xxxx xxxx xx1x xxxx + requires to write a program that fits into 32 bytes; workspace elsewhere) + + Raphael Nabet, 2000-2004 + Michael Zapf + + February 2012: Rewritten as class + +*****************************************************************************/ + +#include +#include "emu.h" + +#include "tms9901.h" + +#define VERBOSE 1 +#define LOG logerror + +/* + Constructor +*/ +tms9901_device::tms9901_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) +: device_t(mconfig, TMS9901, "TMS9901 Programmable System Interface", tag, owner, clock) +{ +} + +/* + should be called after any change to int_state or enabled_ints. +*/ +void tms9901_device::field_interrupts(void) +{ + int current_ints; + + /* int_state: state of lines int1-int15 */ + current_ints = m_int_state; + if (m_clock_register != 0) + { /* if timer is enabled, INT3 pin is overriden by timer */ + if (m_timer_int_pending) + { + if (VERBOSE>8) LOG("tms9901: timer fires\n"); + current_ints |= TMS9901_INT3; + } + else + { + if (VERBOSE>8) LOG("tms9901: timer clear\n"); + current_ints &= ~TMS9901_INT3; + } + } + + /* enabled_ints: enabled interrupts */ + /* mask out all int pins currently set as output */ + current_ints &= m_enabled_ints & (~m_pio_direction_mirror); + + // Check whether we have a new state. For systems that use level-triggered + // interrupts it should not do any harm if the line is re-asserted + // but we may as well avoid this. + if (current_ints == m_old_int_state) + return; + + m_old_int_state = current_ints; + + if (current_ints) + { + // find which interrupt tripped us: + // the number of the first (i.e. least significant) non-zero bit among + // the 16 first bits + // we simply look for the first bit set to 1 in current_ints... */ + int level = 0; + + while ((current_ints & 1)==0) + { + current_ints >>= 1; /* try next bit */ + level++; + } + m_int_pending = true; + if (!m_interrupt.isnull()) + m_interrupt(level, 1); // the offset carries the IC0-3 level + } + else + { + m_int_pending = false; + if (!m_interrupt.isnull()) + m_interrupt(0xf, 0); //Spec: INTREQ*=1 <=> IC0,1,2,3 = 1111 + } +} + +/* + function which should be called by the driver when the state of an INTn* + pin changes (only required if the pin is set up as an interrupt pin) + + state == 0: INTn* is inactive (high) + state != 0: INTn* is active (low) + + 0<=pin_number<=15 +*/ +void tms9901_device::set_single_int(int pin_number, int state) +{ + /* remember new state of INTn* pin state */ + if (state==ASSERT_LINE) + m_int_state |= 1 << pin_number; + else + m_int_state &= ~(1 << pin_number); + + /* we do not need to always call this function - time for an optimization */ + field_interrupts(); +} + +/* + load the content of m_clock_register into the decrementer +*/ +void tms9901_device::timer_reload(void) +{ + if (m_clock_register != 0) + { /* reset clock interval */ + m_decrementer_value = m_clock_register; + m_decrementer->enable(true); + } + else + { /* clock interval == 0 -> no timer */ + m_decrementer->enable(false); + } +} + +/*---------------------------------------------------------------- + TMS9901 CRU interface. +----------------------------------------------------------------*/ + +/* + Read a 8 bit chunk from tms9901. + + signification: + bit 0: m_clock_mode + if (m_clock_mode == false) + bit 1-15: current status of the INT1*-INT15* pins + else + bit 1-14: current timer value + bit 15: value of the INTREQ* (interrupt request to TMS9900) pin. + + bit 16-31: current status of the P0-P15 pins (quits timer mode, too...) +*/ +READ8_MEMBER( tms9901_device::read ) +{ + int answer = 0; + + offset &= 0x003; + + switch (offset) + { + case 0: + if (m_clock_mode) + { /* clock mode */ + answer = ((m_clock_read_register & 0x7F) << 1) | 0x01; + } + else + { /* interrupt mode */ + // m_int_state stores the INTx values, which are inverted to the pin levels (INTx*) + answer = ((~m_int_state) & m_supported_int_mask) & 0xFF; + + if (!m_read_block.isnull()) + answer |= m_read_block(TMS9901_CB_INT7); + + answer &= ~ m_pio_direction_mirror; + answer |= (m_pio_output_mirror & m_pio_direction_mirror) & 0xFF; + } + break; + case 1: + if (m_clock_mode) + { /* clock mode */ + answer = (m_clock_read_register & 0x3F80) >> 7; + if (!m_int_pending) + answer |= 0x80; + } + else + { /* interrupt mode */ + answer = ((~m_int_state) & m_supported_int_mask) >> 8; + + if (!m_read_block.isnull()) + answer |= m_read_block(TMS9901_INT8_INT15); + + answer &= ~ (m_pio_direction_mirror >> 8); + answer |= (m_pio_output_mirror & m_pio_direction_mirror) >> 8; + } + break; + case 2: + /* exit timer mode */ + // MZ: See comments at the beginning. I'm pretty sure this is not correct. + // m_clock_mode = false; + + if (!m_read_block.isnull()) + answer = m_read_block(TMS9901_P0_P7); + else + answer = 0; + + answer &= ~ m_pio_direction; + answer |= (m_pio_output & m_pio_direction) & 0xFF; + + break; + case 3: + // MZ: see above + // m_clock_mode = false; // exit timer mode + if (!m_read_block.isnull()) + answer = m_read_block(TMS9901_P8_P15); + else + answer = 0; + + answer &= ~ (m_pio_direction >> 8); + answer |= (m_pio_output & m_pio_direction) >> 8; + + break; + } + + return answer; +} + +/* + Write 1 bit to tms9901. + + signification: + bit 0: write m_clock_mode + if (!m_clock_mode) + bit 1-15: write interrupt mask register + else + bit 1-14: write timer period + bit 15: if written value == 0, soft reset (just resets all I/O pins as input) + + bit 16-31: set output state of P0-P15 (and set them as output pin) (quit timer mode, too...) +*/ +WRITE8_MEMBER ( tms9901_device::write ) +{ + data &= 1; /* clear extra bits */ + offset &= 0x01F; + switch (offset) + { + case 0x00: /* write to mode bit */ + if (data == 0) + { + /* we are quitting clock mode */ + m_clock_mode = false; + if (VERBOSE>5) LOG("tms9901: int mode\n"); + } + else + { + m_clock_mode = true; + if (VERBOSE>5) LOG("tms9901: clock mode\n"); + // we are switching to clock mode: latch the current value of + // the decrementer register + if (m_clock_register != 0) + m_clock_read_register = m_decrementer_value; + else + m_clock_read_register = 0; /* timer inactive... */ + } + break; + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + case 0x08: + case 0x09: + case 0x0A: + case 0x0B: + case 0x0C: + case 0x0D: + case 0x0E: + // write one bit to 9901 (bits 1-14) + // + // m_clock_mode==false ? Disable/Enable an interrupt + // : Bit in clock interval + // + // offset is the index of the modified bit of register (-> interrupt number -1) + if (m_clock_mode) + { /* modify clock interval */ + int mask = 1 << ((offset & 0x0F) - 1); /* corresponding mask */ + + if (data) + m_clock_register |= mask; /* set bit */ + else + m_clock_register &= ~mask; /* clear bit */ + + /* reset clock timer (page 8) */ + if (VERBOSE>6) LOG("tms9901: clock register = %04x\n", m_clock_register); + timer_reload(); + } + else + { /* modify interrupt enable mask */ + int mask = 1 << (offset & 0x0F); /* corresponding mask */ + + if (data) + m_enabled_ints |= mask; /* set bit */ + else + m_enabled_ints &= ~mask; /* unset bit */ + + if (offset == 3) + m_timer_int_pending = false; /* SBO 3 clears pending timer interrupt (??) */ + + if (VERBOSE>6) LOG("tms9901: interrupts = %04x\n", m_enabled_ints); + field_interrupts(); /* changed interrupt state */ + } + break; + case 0x0F: + if (m_clock_mode) + { /* in clock mode this is the soft reset bit */ + if (!data) + { // TMS9901 soft reset (RST2*) + // Spec: "Writing a 0 to bit 15 while in the clock mode executes a soft reset on the I/O pins. + // [...] RST2* will program all ports to the input mode" + m_pio_direction = 0; + m_pio_direction_mirror = 0; + + // "RST1* (power-up reset) will reset all mask bits low." + // Spec is not clear on whether the mask bits are also reset by RST2* + // TODO: Check on a real machine. (I'd guess from the text they are not touched) + m_enabled_ints = 0; + if (VERBOSE>5) LOG("tms9901: Soft reset (RST2*)\n"); + } + } + else + { /* modify interrupt enable mask */ + if (data) + m_enabled_ints |= 0x4000; /* set bit */ + else + m_enabled_ints &= ~0x4000; /* unset bit */ + + if (VERBOSE>6) LOG("tms9901: interrupts = %04x\n", m_enabled_ints); + field_interrupts(); /* changed interrupt state */ + } + break; + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + case 0x16: + case 0x17: + case 0x18: + case 0x19: + case 0x1A: + case 0x1B: + case 0x1C: + case 0x1D: + case 0x1E: + case 0x1F: + int pin = offset & 0x0F; + if (VERBOSE>6) LOG("tms9901: output on P%d = %d\n", pin, data); + int mask = (1 << pin); + + // MZ: see above - I think this is wrong + // m_clock_mode = false; // exit timer mode + + m_pio_direction |= mask; /* set up as output pin */ + + if (data) + m_pio_output |= mask; + else + m_pio_output &= ~mask; + + if (pin >= 7) + { /* pins P7-P15 are mirrored as INT15*-INT7* */ + int pin2 = 22 - pin; + int mask2 = (1 << pin2); + + m_pio_direction_mirror |= mask2; /* set up as output pin */ + + if (data) + m_pio_output_mirror |= mask2; + else + m_pio_output_mirror &= ~ mask2; + } + + if (!m_write_line[pin].isnull()) + (m_write_line[pin])(data); + + break; + } +} + +/* + Timer callback + Decrementer counts down the value set in clock mode; when it reaches 0, + raises an interrupt and resets to the start value + The decrementer works as long as the clock_register contains a non-zero value. +*/ +void tms9901_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) +{ + if (id==DECREMENTER) // we have only that one + { + m_decrementer_value--; + if (VERBOSE>6) LOG("tms9901: decrementer = %d\n", m_decrementer_value); + if (m_decrementer_value<=0) + { + m_timer_int_pending = true; // decrementer interrupt requested + field_interrupts(); + m_decrementer_value = m_clock_register; + } + } +} + +/*------------------------------------------------- + DEVICE_STOP( tms9901 ) +-------------------------------------------------*/ + +void tms9901_device::device_stop(void) +{ +} + +/*------------------------------------------------- + DEVICE_RESET( tms9901 ) +-------------------------------------------------*/ + +void tms9901_device::device_reset(void) +{ + m_timer_int_pending = false; + m_enabled_ints = 0; + + m_pio_direction = 0; + m_pio_direction_mirror = 0; + m_pio_output = m_pio_output_mirror = 0; + + m_int_state = 0; + m_old_int_state = -1; + field_interrupts(); + + m_clock_mode = false; + + m_clock_register = 0; + timer_reload(); +} + + +/*------------------------------------------------- + DEVICE_START( tms9901 ) +-------------------------------------------------*/ + +void tms9901_device::device_start(void) +{ + const tms9901_interface *intf = reinterpret_cast(static_config()); + m_supported_int_mask = intf->interrupt_mask; + + m_decrementer = timer_alloc(DECREMENTER); + m_decrementer->adjust(attotime::from_hz(clock() / 64.), 0, attotime::from_hz(clock() / 64.)); + m_decrementer->enable(false); + + m_read_block.resolve(intf->read_handler, *this); + + for (int i=0; i < 16; i++) + { + m_write_line[i].resolve(intf->write_handler[i], *this); + } + + m_interrupt.resolve(intf->interrupt_callback, *this); +} + +const device_type TMS9901 = &device_creator; diff --git a/src/emu/machine/tms9901.h b/src/emu/machine/tms9901.h new file mode 100644 index 00000000000..df154ab1da8 --- /dev/null +++ b/src/emu/machine/tms9901.h @@ -0,0 +1,142 @@ +/**************************************************************************** + + TMS9901 Programmable System Interface + See tms9901.c for documentation + + Raphael Nabet + Michael Zapf + + February 2012: Rewritten as class + +*****************************************************************************/ + +#ifndef __TMS9901_H__ +#define __TMS9901_H__ + +#include "emu.h" + +extern const device_type TMS9901; + +/*************************************************************************** + MACROS +***************************************************************************/ + +/* Masks for the interrupts levels available on TMS9901 */ +#define TMS9901_INT1 0x0002 +#define TMS9901_INT2 0x0004 +#define TMS9901_INT3 0x0008 // overriden by the timer interrupt +#define TMS9901_INT4 0x0010 +#define TMS9901_INT5 0x0020 +#define TMS9901_INT6 0x0040 +#define TMS9901_INT7 0x0080 +#define TMS9901_INT8 0x0100 +#define TMS9901_INT9 0x0200 +#define TMS9901_INTA 0x0400 +#define TMS9901_INTB 0x0800 +#define TMS9901_INTC 0x1000 +#define TMS9901_INTD 0x2000 +#define TMS9901_INTE 0x4000 +#define TMS9901_INTF 0x8000 + +enum +{ + TMS9901_CB_INT7 = 0, + TMS9901_INT8_INT15 = 1, + TMS9901_P0_P7 = 2, + TMS9901_P8_P15 = 3 +}; + +/*************************************************************************** + CLASS DEFINITION +***************************************************************************/ + +typedef struct _tms9901_interface +{ + int interrupt_mask; // a bit for each input pin whose state is always notified to the TMS9901 core + devcb_read8 read_handler; // 4*8 bits, to be selected using the offset (0-3) + devcb_write_line write_handler[16]; // 16 Pn outputs + devcb_write8 interrupt_callback; // called when interrupt bus state changes +} tms9901_interface; + +class tms9901_device : public device_t +{ +public: + tms9901_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock); + + void set_single_int(int pin_number, int state); + + DECLARE_READ8_MEMBER( read ); + DECLARE_WRITE8_MEMBER( write ); + +private: + static const device_timer_id DECREMENTER = 0; + + void device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr); + void timer_reload(void); + void field_interrupts(void); + + void device_start(void); + void device_stop(void); + void device_reset(void); + + /* interrupt registers */ + // mask: bit #n is set if pin #n is supported as an interrupt pin, + // i.e. the driver sends a notification whenever the pin state changes + // setting these bits is not required, but it saves you the trouble of + // saving the state of interrupt pins and feeding it to the port read + // handlers again + int m_supported_int_mask; + int m_int_state; // state of the int1-int15 lines (must be inverted when queried) + int m_old_int_state; // stores the previous value to avoid useless INT line assertions + int m_enabled_ints; // interrupt enable mask + + bool m_int_pending; // status of the int* pin (connected to TMS9900) + bool m_timer_int_pending; // timer int pending (overrides int3 pin if timer enabled) + + // PIO registers + int m_pio_direction; // direction register for PIO + + // current PIO output (to be masked with pio_direction) + int m_pio_output; + + // mirrors used for INT7*-INT15* + int m_pio_direction_mirror; + int m_pio_output_mirror; + + // ======================================================================= + + // TMS9901 clock mode + // false = so-called interrupt mode (read interrupt state, write interrupt enable mask) + // true = clock mode (read/write clock interval) + bool m_clock_mode; + + // MESS timer, used to emulate the decrementer register + emu_timer *m_decrementer; + + // clock interval, loaded in decrementer when it reaches 0. + // 0 means decrementer off + int m_clock_register; + + // Current decrementer value + int m_decrementer_value; + + // when we go into timer mode, the decrementer is copied there to allow to read it reliably + int m_clock_read_register; + + // ======================================================================= + + // Callbacks + devcb_resolved_read8 m_read_block; + devcb_resolved_write_line m_write_line[16]; + devcb_resolved_write8 m_interrupt; // also delivers the interrupt level +}; + +/*************************************************************************** + DEVICE CONFIGURATION MACROS +***************************************************************************/ + +#define MCFG_TMS9901_ADD(_tag, _intrf, _rate) \ + MCFG_DEVICE_ADD(_tag, TMS9901, _rate) \ + MCFG_DEVICE_CONFIG(_intrf) + +#endif /* __TMS9901_H__ */ diff --git a/src/emu/machine/tms9902.c b/src/emu/machine/tms9902.c new file mode 100644 index 00000000000..653afc190dd --- /dev/null +++ b/src/emu/machine/tms9902.c @@ -0,0 +1,837 @@ +/**************************************************************************** + + TMS9902 Asynchronous Communication Controller + + TMS9902 is an asynchronous serial controller for use with the TI990 and + TMS9900 family. It provides serial I/O, three extra I/O pins (namely RTS, + DSR and CTS), and a timer. It communicates with the CPU through the CRU + I/O bus, and one interrupt pin. + + +----+--+----+ + <- /INT |1 \--/ 18| VCC + <- XOUT |2 17| /CE <- + -> RIN |3 16| /PHI <- + <- CRUIN |4 15| CRUCLK <- + <- /RTS |5 14| S0 <- + -> /CTS |6 13| S1 <- + -> /DSR |7 12| S2 <- + -> CRUOUT |8 11| S3 <- + VSS |9 10| S4 <- + +------------+ + + The CRUIN line borrows its name from the connector of the connected CPU + where it is an input, so CRUIN is an output of this chip. The same is true + for CRUOUT. + + /PHI is a TTL clock input with 4 MHz maximum rate. + + IMPORTANT NOTE: The previous versions of TMS9902 attempted to write their + output to a file. This implementation is able to communicate with an external + UART via a socket connection and an external bridge. However, the work is + not done yet, and until then the file writing is disabled. + + Raphael Nabet, 2003 + Michael Zapf, 2011 + February 2012: Rewritten as class + +*****************************************************************************/ + +#include +#include "tms9902.h" + +#define VERBOSE 1 +#define LOG logerror + +enum +{ + DECTIMER, + RECVTIMER, + SENDTIMER +}; + +// Polling frequency. We use a much higher value to allow for line state changes +// happening between character transmissions (which happen in parallel in real +// communications but which must be serialized here) +#define POLLING_FREQ 20000 + + +/* + Constructor +*/ +tms9902_device::tms9902_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) +: device_t(mconfig, TMS9902, "TMS9902 Asynchronous Communication Controller", tag, owner, clock) +{ +} + +/* + should be called after any change to int_state or enabled_ints. +*/ +void tms9902_device::field_interrupts() +{ + bool new_int = (m_DSCH && m_DSCENB) + || (m_RBRL && m_RIENB) + || (m_XBRE && m_XBIENB) + || (m_TIMELP && m_TIMENB); +// if (VERBOSE>3) LOG("TMS9902: interrupt flags (DSCH = %02x, DSCENB = %02x), (RBRL = %02x, RIENB = %02x), (XBRE = %02x, XBIENB = %02x), (TIMELP = %02x, TIMENB = %02x)\n", +// m_DSCH, m_DSCENB, m_RBRL, m_RIENB, m_XBRE, m_XBIENB, m_TIMELP, m_TIMENB); + if (new_int != m_INT) + { + // Only consider edges + m_INT = new_int; + if (VERBOSE>3) LOG("TMS9902: /INT = %s\n", (m_INT)? "asserted" : "cleared"); + int_callback(m_INT? ASSERT_LINE : CLEAR_LINE); + } +} + +/* + Called whenever the incoming CTS* line changes. This should be called by + the device that contains the UART. +*/ +void tms9902_device::rcv_cts(line_state state) +{ + bool previous = m_CTSin; + + // CTSin is an internal register of the TMS9902 with positive logic + m_CTSin = (state==ASSERT_LINE); + + if (VERBOSE>3) LOG("TMS9902: CTS* = %s\n", (state==ASSERT_LINE)? "asserted" : "cleared"); + + if (m_CTSin != previous) + { + m_DSCH = true; + field_interrupts(); + + // If CTS becomes asserted and we have been sending + if (state==ASSERT_LINE && m_RTSout) + { + // and if the byte buffer is empty + if (m_XBRE) + { + // and we want to have a BRK, send it + if (m_BRKON) send_break(true); + } + else + { + // Buffer is not empty, we can send it + // If the shift register is empty, transfer the data + if (m_XSRE && !m_BRKout) + { + initiate_transmit(); + } + } + } + } + else + { + m_DSCH = false; + if (VERBOSE>4) LOG("TMS9902: no change in CTS line, no interrupt."); + } +} + +void tms9902_device::set_clock(bool state) +{ + if (state) + m_recvtimer->adjust(attotime::from_msec(1), 0, attotime::from_hz(POLLING_FREQ)); + else + m_recvtimer->reset(); +} + +/* + Called whenever the incoming DSR* line changes. This should be called by + the device that contains the UART. +*/ +void tms9902_device::rcv_dsr(line_state state) +{ + bool previous = m_DSRin; + if (VERBOSE>3) LOG("TMS9902: DSR* = %s\n", (state==ASSERT_LINE)? "asserted" : "cleared"); + m_DSRin = (state==ASSERT_LINE); + + if (m_DSRin != previous) + { + m_DSCH = true; + field_interrupts(); + } + else + { + m_DSCH = false; + if (VERBOSE>4) LOG("TMS9902: no change in DSR line, no interrupt."); + } +} + +/* + Called whenever the incoming RIN line changes. This should be called by + the device that contains the UART. Unlike the real thing, we deliver + complete bytes in one go. +*/ +void tms9902_device::rcv_data(UINT8 data) +{ + // Put the received byte into the 1-byte receive buffer + m_RBR = data; + + // Clear last errors + m_RFER = false; + m_RPER = false; + + if (!m_RBRL) + { + // Receive buffer was empty + m_RBRL = true; + m_ROVER = false; + if (VERBOSE>3) LOG("TMS9902: Receive buffer loaded with byte %02x\n", data); + field_interrupts(); + } + else + { + // Receive buffer was full + m_ROVER = true; + if (VERBOSE>1) LOG("TMS9902: Receive buffer still loaded; overflow error\n"); + } +} + +//------------------------------------------------ + +/* + Framing error. This can only be detected by a remotely attached real UART; + if we get a report on a framing error we use it to announce the framing error + as if it occurred here. + The flag is reset by the next correctly received byte. +*/ +void tms9902_device::rcv_framing_error() +{ + if (VERBOSE>2) LOG("TMS9902: Detected framing error\n"); + m_RFER = true; +} + +/* + Parity error. This can only be detected by a remotely attached real UART; + if we get a report on a parity error we use it to announce the parity error + as if it occurred here. + The flag is reset by the next correctly received byte. +*/ +void tms9902_device::rcv_parity_error() +{ + if (VERBOSE>2) LOG("TMS9902: Detected parity error\n"); + m_RPER = true; +} + +/* + Incoming BREAK condition. The TMS9902 does not show any directly visible + reactions on a BREAK (no interrupt, no flag set). A BREAK is a time period + of low level on the RIN pin which makes the chip re-synchronize on the + next rising edge. +*/ +void tms9902_device::rcv_break(bool value) +{ + if (VERBOSE>2) LOG("TMS9902: Receive BREAK=%d (no effect)\n", value? 1:0); +} + +//------------------------------------------------ + +/* + Timer callback +*/ +void tms9902_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) +{ + switch (id) + { + // This call-back is called by the MESS timer system when the decrementer + // reaches 0. + case DECTIMER: + m_TIMERR = m_TIMELP; + break; + + // Callback for the autonomous operations of the chip. This is normally + // controlled by an external clock of 3-4 MHz, internally divided by 3 or 4, + // depending on CLK4M. With this timer, reception of characters becomes + // possible. + case RECVTIMER: + rcv_callback(ASSERT_LINE); + break; + + case SENDTIMER: + // Byte has been sent + m_XSRE = true; + + // In the meantime, the CPU may have pushed a new byte into the XBR + // so we loop until all data are transferred + if (!m_XBRE && m_CTSin) + { + initiate_transmit(); + } + break; + } +} + +/* + load the content of clockinvl into the decrementer +*/ +void tms9902_device::reload_interval_timer() +{ + if (m_TMR) + { /* reset clock interval */ + m_dectimer->adjust( + attotime::from_double((double) m_TMR / (m_clock_rate / ((m_CLK4M) ? 4. : 3.) / 64.)), + 0, + attotime::from_double((double) m_TMR / (m_clock_rate / ((m_CLK4M) ? 4. : 3.) / 64.))); + } + else + { /* clock interval == 0 -> no timer */ + m_dectimer->enable(0); + } +} + +void tms9902_device::send_break(bool state) +{ + if (state != m_BRKout) + { + m_BRKout = state; + if (VERBOSE>2) LOG("TMS9902: Sending BREAK=%d\n", state? 1:0); + + // Signal BRK (on/off) to the remote site + ctrl_callback((EXCEPT | BRK), state? 1:0); + } +} + +/* + Baudpoll value allows the callback function to know when the next data byte shall be delivered. +*/ +double tms9902_device::get_baudpoll() +{ + return m_baudpoll; +} + +// ========================================================================== + +/* + Sets the data rate for the receiver part. If a remote UART is attached, + propagate this setting. + The TMS9902 calculates the baud rate from the external clock, and the result + does not match the known baud rates precisely (e.g. for 9600 baud the + closest value is 9615). Other UARTs may have a different way to set baud + rates. Thus we transmit the bit pattern and leave it up to the remote UART + to calculate its own baud rate from it. Apart from that, the callback + function should add information about the UART. + + CLK4M RDV8 RDR9 RDR8 | RDR7 RDR6 RDR5 RDR4 | RDR3 RDR2 RDR1 RDR0 +*/ +void tms9902_device::set_receive_data_rate() +{ + int value = (m_CLK4M? 0x800 : 0) | (m_RDV8? 0x400 : 0) | m_RDR; + if (VERBOSE>3) LOG("TMS9902: receive rate = %04x\n", value); + + // Calculate the ratio between receive baud rate and polling frequency + double fint = m_clock_rate / ((m_CLK4M) ? 4.0 : 3.0); + double baud = fint / (2.0 * ((m_RDV8)? 8:1) * m_RDR); + + // We assume 10 bit per character (7 data usually add 1 parity; 1 start, 1 stop) + // This value represents the ratio of data inputs of one poll. + // Thus the callback function should add up this value on each poll + // and deliver a data input not before it sums up to 1. + m_baudpoll = (double)(baud / (10*POLLING_FREQ)); + if (VERBOSE>3) LOG ("TMS9902: baudpoll = %lf\n", m_baudpoll); + + m_last_config_value = value; + ctrl_callback(CONFIG, RATERECV); +} + +/* + Sets the data rate for the sender part. If a remote UART is attached, + propagate this setting. +*/ +void tms9902_device::set_transmit_data_rate() +{ + int value = (m_CLK4M? 0x800 : 0) | (m_XDV8? 0x400 : 0) | m_XDR; + if (VERBOSE>3) LOG("TMS9902: set transmit rate = %04x\n", value); + m_last_config_value = value; + ctrl_callback(CONFIG, RATEXMIT); +} + +void tms9902_device::set_stop_bits() +{ + int value = m_STOPB; + if (VERBOSE>3) LOG("TMS9902: set stop bits = %02x\n", value); + m_last_config_value = value; + ctrl_callback(CONFIG, STOPBITS); +} + +void tms9902_device::set_data_bits() +{ + int value = m_RCL; + if (VERBOSE>3) LOG("TMS9902: set data bits = %02x\n", value); + m_last_config_value = value; + ctrl_callback(CONFIG, DATABITS); +} + +void tms9902_device::set_parity() +{ + int value = (m_PENB? 2:0) | (m_ODDP? 1:0); + if (VERBOSE>3) LOG("TMS9902: set parity = %02x\n", value); + m_last_config_value = value; + ctrl_callback(CONFIG, PARITY); +} + +void tms9902_device::transmit_line_state() +{ + // 00ab cdef = setting line RTS=a, CTS=b, DSR=c, DCD=d, DTR=e, RI=f + // The 9902 only outputs RTS and BRK + if (VERBOSE>3) LOG("TMS9902: transmitting line state (only RTS) = %02x\n", (m_RTSout)? 1:0); + m_last_config_value = (m_RTSout)? RTS : 0; + ctrl_callback(LINES, RTS); +} + +void tms9902_device::set_rts(line_state state) +{ + bool lstate = (state==ASSERT_LINE); + + if (lstate != m_RTSout) + { + // Signal RTS to the modem + if (VERBOSE>3) LOG("TMS9902: Set RTS=%d\n", lstate? 1:0); + m_RTSout = lstate; + transmit_line_state(); + } +} + +int tms9902_device::get_config_value() +{ + return m_last_config_value; +} + +// ========================================================================== + +void tms9902_device::initiate_transmit() +{ + if (m_BRKON && m_CTSin) + /* enter break mode */ + send_break(true); + else + { + if (!m_RTSON && (!m_CTSin || (m_XBRE && !m_BRKout))) + /* clear RTS output */ + set_rts(CLEAR_LINE); + else + { + if (VERBOSE>5) LOG("TMS9902: transferring XBR to XSR; XSRE=false, XBRE=true\n"); + m_XSR = m_XBR; + m_XSRE = false; + m_XBRE = true; + + field_interrupts(); + + if (VERBOSE>4) LOG("TMS9902: transmit XSR=%02x, RCL=%02x\n", m_XSR, m_RCL); + + xmit_callback(0, m_XSR & (0xff >> (3-m_RCL))); + + // Should store that somewhere (but the CPU is fast enough, can afford to recalc :-) ) + double fint = m_clock_rate / ((m_CLK4M) ? 4.0 : 3.0); + double baud = fint / (2.0 * ((m_RDV8)? 8:1) * m_RDR); + + // Time for transmitting 10 bit (8 bit + start + stop) + m_sendtimer->adjust(attotime::from_hz(baud/10.0)); + } + } +} + + + +/*---------------------------------------------------------------- + TMS9902 CRU interface. +----------------------------------------------------------------*/ + +/* + Read a 8 bit chunk from tms9902. + + signification: + bit 0-7: RBR0-7 Receive Buffer register + bit 8: not used (always 0) + bit 9: RCVERR Receive Error (RFER | ROVER | RPER) + bit 10: RPER Receive Parity Error + bit 11: ROVER Receive Overrun Error + bit 12: RFER Receive Framing Error + bit 13-15: not emulated, normally used for diagnostics + bit 16: RBINT (RBRL&RIENB) +*/ +READ8_MEMBER( tms9902_device::cruread ) +{ + UINT8 answer = 0; + + offset &= 0x0003; + + switch (offset) + { + case 3: // Bits 31-24 + if (m_INT) answer |= 0x80; + if (m_LDCTRL || m_LDIR || m_LRDR || m_LXDR || m_BRKON) answer |= 0x40; + if (m_DSCH) answer |= 0x20; + if (m_CTSin) answer |= 0x10; + if (m_DSRin) answer |= 0x08; + if (m_RTSout) answer |= 0x04; + if (m_TIMELP) answer |= 0x02; + if (m_TIMERR) answer |= 0x01; + break; + + case 2: // Bits 23-16 + if (m_XSRE) answer |= 0x80; + if (m_XBRE) answer |= 0x40; + if (m_RBRL) answer |= 0x20; + if (m_DSCH && m_DSCENB) answer |= 0x10; + if (m_TIMELP && m_TIMENB) answer |= 0x08; + if (m_XBRE && m_XBIENB) answer |= 0x02; + if (m_RBRL && m_RIENB) answer |= 0x01; + break; + + case 1: // Bits 15-8 + if (m_RIN) answer |= 0x80; + if (m_RSBD) answer |= 0x40; + if (m_RFBD) answer |= 0x20; + if (m_RFER) answer |= 0x10; + if (m_ROVER) answer |= 0x08; + if (m_RPER) answer |= 0x04; + if (m_RPER || m_RFER || m_ROVER) answer |= 0x02; + break; + + case 0: // Bits 7-0 + answer = m_RBR; + break; + } + if (VERBOSE>7) LOG("TMS9902: Reading flag bits %d - %d = %02x\n", ((offset+1)*8-1), offset*8, answer); + return answer; +} + +static inline void set_bits8(UINT8 *reg, UINT8 bits, bool set) +{ + if (set) + *reg |= bits; + else + *reg &= ~bits; +} + +static inline void set_bits16(UINT16 *reg, UINT16 bits, bool set) +{ + if (set) + *reg |= bits; + else + *reg &= ~bits; +} + +void tms9902_device::reset_uart() +{ + if (VERBOSE>1) LOG("TMS9902: resetting\n"); + + /* disable all interrupts */ + m_DSCENB = false; // Data Set Change Interrupt Enable + m_TIMENB = false; // Timer Interrupt Enable + m_XBIENB = false; // Transmit Buffer Interrupt Enable + m_RIENB = false; // Read Buffer Interrupt Enable + + /* initialize transmitter */ + m_XBRE = true; // Transmit Buffer Register Empty + m_XSRE = true; // Transmit Shift Register Empty + + /* initialize receiver */ + m_RBRL = false; // Read Buffer Register Loaded + + /* clear RTS */ + m_RTSON = false; // Request-to-send on (flag) + m_RTSout = true; // Note we are doing this to ensure the state is sent to the interface + set_rts(CLEAR_LINE); + m_RTSout = false; // what we actually want + + /* set all register load flags to 1 */ + m_LDCTRL = true; + m_LDIR = true; + m_LRDR = true; + m_LXDR = true; + + /* clear break condition */ + m_BRKON = false; + m_BRKout = false; + + m_TMR = 0; + m_STOPB = 0; + m_RCL = 0; + m_XDR = 0; + m_RDR = 0; + m_RBR = 0; + m_XBR = 0; + m_XSR = 0; + + field_interrupts(); +} + +/* + TMS9902 CRU write +*/ +WRITE8_MEMBER( tms9902_device::cruwrite ) +{ + data &= 1; /* clear extra bits */ + + offset &= 0x1F; + if (VERBOSE>5) LOG("TMS9902: Setting bit %d = %02x\n", offset, data); + + if (offset <= 10) + { + UINT16 mask = (1 << offset); + + if (m_LDCTRL) + { // Control Register mode. Values written to bits 0-7 are copied + // into the control register. + switch (offset) + { + case 0: + set_bits8(&m_RCL, 0x01, (data!=0)); + // we assume that bits are written in increasing order + // so we do not transmit the data bits twice + // (will fail when bit 1 is written first) + break; + case 1: + set_bits8(&m_RCL, 0x02, (data!=0)); + set_data_bits(); + break; + case 2: + break; + case 3: + m_CLK4M = (data!=0); + break; + case 4: + m_ODDP = (data!=0); + // we also assume that the parity type is set before the parity enable + break; + case 5: + m_PENB = (data!=0); + set_parity(); + break; + case 6: + set_bits8(&m_STOPB, 0x01, (data!=0)); + break; + case 7: + set_bits8(&m_STOPB, 0x02, (data!=0)); + // When bit 7 is written the control register mode is automatically terminated. + m_LDCTRL = false; + set_stop_bits(); + break; + default: + if (VERBOSE>1) LOG("tms9902: Invalid control register address %d\n", offset); + } + } + else if (m_LDIR) + { // Interval Register mode. Values written to bits 0-7 are copied + // into the interval register. + if (offset <= 7) + { + set_bits8(&m_TMR, mask, (data!=0)); + + if (offset == 7) + { + reload_interval_timer(); + // When bit 7 is written the interval register mode is automatically terminated. + m_LDIR = false; + } + } + } + else if (m_LRDR || m_LXDR) + { + if (m_LRDR) + { // Receive rate register mode. Values written to bits 0-10 are copied + // into the receive rate register. + if (offset < 10) + { + set_bits16(&m_RDR, mask, (data!=0)); + } + else + { + // When bit 10 is written the receive register mode is automatically terminated. + m_RDV8 = (data!=0); + m_LRDR = false; + set_receive_data_rate(); + } + } + if (m_LXDR) + { + // The transmit rate register can be set together with the receive rate register. + if (offset < 10) + { + set_bits16(&m_XDR, mask, (data!=0)); + } + else + { + // Note that the transmit rate register is NOT terminated when + // writing bit 10. This must be done by unsetting bit 11. + m_XDV8 = (data!=0); + set_transmit_data_rate(); + } + } + } + else + { // LDCTRL=LDIR=LRDR=LXRD=0: Transmit buffer register mode. Values + // written to bits 0-7 are transferred into the transmit buffer register. + if (offset <= 7) + { + set_bits8(&m_XBR, mask, (data!=0)); + + if (offset == 7) + { /* transmit */ + m_XBRE = false; + // Spec: When the transmitter is active, the contents of the Transmit + // Buffer Register are transferred to the Transmit Shift Register + // each time the previous character has been completely transmitted + // We need to check XSRE=true as well, as the implementation + // makes use of a timed transmission, during which XSRE=false + if (m_XSRE && m_RTSout && m_CTSin && !m_BRKout) + { + initiate_transmit(); + } + } + } + } + return; + } + switch (offset) + { + case 11: + m_LXDR = (data!=0); + break; + case 12: + m_LRDR = (data!=0); + break; + case 13: + m_LDIR = (data!=0); + // Spec: Each time LDIR is reset the contents of the Interval + // Register are loaded into the Interval Timer, thus restarting + // the timer. + if (data==0) + reload_interval_timer(); + break; + case 14: + m_LDCTRL = (data!=0); + break; + case 15: + m_TSTMD = (data!=0); // Test mode not implemented + break; + case 16: + if (data!=0) + { + m_RTSON = true; + set_rts(ASSERT_LINE); + if (m_CTSin) + { + if (m_XSRE && !m_XBRE && !m_BRKout) + initiate_transmit(); + else if (m_BRKON) + send_break(true); + } + } + else + { + m_RTSON = false; + if (m_XBRE && m_XSRE && !m_BRKout) + { + set_rts(CLEAR_LINE); + } + } + return; + case 17: + if (VERBOSE>3) LOG("TMS9902: set BRKON=%d; BRK=%d\n", data, m_BRKout? 1:0); + m_BRKON = (data!=0); + if (m_BRKout && data==0) + { + // clear BRK + m_BRKout = false; + if ((!m_XBRE) && m_CTSin) + { + /* transmit next byte */ + initiate_transmit(); + } + else if (!m_RTSON) + { + /* clear RTS */ + set_rts(CLEAR_LINE); + } + } + else if (m_XBRE && m_XSRE && m_RTSout && m_CTSin) + { + send_break(data!=0); + } + return; + case 18: + // Receiver Interrupt Enable + // According to spec, (re)setting this flag clears the RBRL flag + // (the only way to clear the flag!) + m_RIENB = (data!=0); + m_RBRL = false; + if (VERBOSE>4) LOG("TMS9902: set RBRL=0, set RIENB=%d\n", data); + field_interrupts(); + return; + case 19: + /* Transmit Buffer Interrupt Enable */ + m_XBIENB = (data!=0); + if (VERBOSE>4) LOG("TMS9902: set XBIENB=%d\n", data); + field_interrupts(); + return; + case 20: + /* Timer Interrupt Enable */ + m_TIMENB = (data!=0); + m_TIMELP = false; + m_TIMERR = false; + field_interrupts(); + return; + case 21: + /* Data Set Change Interrupt Enable */ + m_DSCENB = (data!=0); + m_DSCH = false; + if (VERBOSE>4) LOG("TMS9902: set DSCH=0, set DSCENB=%d\n", data); + field_interrupts(); + return; + case 31: + /* RESET */ + reset_uart(); + return; + default: + if (VERBOSE>1) LOG("TMS9902: Writing to undefined flag bit position %d = %01x\n", offset, data); + } +} + +/*------------------------------------------------- + DEVICE_STOP( tms9902 ) +-------------------------------------------------*/ + +void tms9902_device::device_stop() +{ + if (m_dectimer) + { + m_dectimer->reset(); + m_dectimer = NULL; + } +} + +/*------------------------------------------------- + DEVICE_RESET( tms9902 ) +-------------------------------------------------*/ + +void tms9902_device::device_reset() +{ + reset_uart(); +} + +/*------------------------------------------------- + DEVICE_START( tms9902 ) +-------------------------------------------------*/ + +void tms9902_device::device_start() +{ + const tms9902_interface *intf = reinterpret_cast(static_config()); + + m_clock_rate = clock(); + + int_callback.resolve(intf->int_callback, *this); + rcv_callback.resolve(intf->rcv_callback, *this); + xmit_callback.resolve(intf->xmit_callback, *this); + ctrl_callback.resolve(intf->ctrl_callback, *this); + + m_dectimer = timer_alloc(DECTIMER); + m_recvtimer = timer_alloc(RECVTIMER); + m_sendtimer = timer_alloc(SENDTIMER); +} + +const device_type TMS9902 = &device_creator; + diff --git a/src/emu/machine/tms9902.h b/src/emu/machine/tms9902.h new file mode 100644 index 00000000000..530814deb6f --- /dev/null +++ b/src/emu/machine/tms9902.h @@ -0,0 +1,195 @@ +/**************************************************************************** + + TMS9902 Asynchronous Communication Controller + See tms9902.c for documentation + + Michael Zapf + + February 2012: Rewritten as class + +*****************************************************************************/ + +#ifndef __TMS9902_H__ +#define __TMS9902_H__ + +#include "emu.h" + +// Serial control protocol values +#define TYPE_TMS9902 0x01 + +// Configuration (output only) +#define CONFIG 0x80 +#define RATERECV 0x70 +#define RATEXMIT 0x60 +#define DATABITS 0x50 +#define STOPBITS 0x40 +#define PARITY 0x30 + +// Exceptional states (BRK: both directions; FRMERR/PARERR: input only) +#define EXCEPT 0x40 +#define BRK 0x02 +#define FRMERR 0x04 +#define PARERR 0x06 + +// Line states (RTS, DTR: output; CTS, DSR, RI, DCD: input) +#define LINES 0x00 +#define RTS 0x20 +#define CTS 0x10 +#define DSR 0x08 +#define DCD 0x04 +#define DTR 0x02 +#define RI 0x01 + +extern const device_type TMS9902; + +typedef struct _tms9902_interface +{ + devcb_write_line int_callback; + devcb_write_line rcv_callback; + devcb_write8 xmit_callback; + devcb_write8 ctrl_callback; +} tms9902_interface; + +class tms9902_device : public device_t +{ +public: + tms9902_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock); + void set_clock(bool state); + + void rcv_cts(line_state state); + void rcv_dsr(line_state state); + void rcv_data(UINT8 data); + void rcv_break(bool value); + void rcv_framing_error(); + void rcv_parity_error(); + + double get_baudpoll(); + + int get_config_value(); + + DECLARE_READ8_MEMBER( cruread ); + DECLARE_WRITE8_MEMBER( cruwrite ); + +protected: + void device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr); + void device_start(); + void device_reset(); + void device_stop(); + +private: + void field_interrupts(); + void reload_interval_timer(); + void send_break(bool state); + void set_receive_data_rate(); + void set_transmit_data_rate(); + void set_stop_bits(); + void set_data_bits(); + void set_parity(); + void transmit_line_state(); + void set_rts(line_state state); + void initiate_transmit(); + void reset_uart(); + + devcb_resolved_write_line int_callback; + devcb_resolved_write_line rcv_callback; + devcb_resolved_write8 xmit_callback; + devcb_resolved_write8 ctrl_callback; // needs to be used with get_config_value + + // tms9902 clock rate (PHI* pin, normally connected to TMS9900 Phi3*) + // Official range is 2MHz-3.3MHz. Some tms9902s were sold as "MP9214", and + // were tested for speeds up to 4MHz, provided the clk4m control bit is set. + // (warning: 3MHz on a tms9900 is equivalent to 12MHz on a tms9995 or tms99000) + double m_clock_rate; + + /* Modes */ + bool m_LDCTRL; // Load control register + bool m_LDIR; // Load interval register + bool m_LRDR; // Load receive data register + bool m_LXDR; // Load transmit data register + bool m_TSTMD; // Test mode + + /* output pin */ + bool m_RTSON; // RTS-on request + + /* transmitter registers */ + bool m_BRKON; // BRK-on request + bool m_BRKout; // indicates the current BRK state + + UINT8 m_XBR; // transmit buffer register + UINT8 m_XSR; // transmit shift register + + /* receiver registers */ + UINT8 m_RBR; // Receive buffer register + + /* Interrupt enable flags */ + bool m_DSCENB; // Data set change interrupt enable + bool m_RIENB; // Receiver interrupt enable + bool m_XBIENB; // Tansmit buffer interrupt enable + bool m_TIMENB; // Timer interrupt enable + + /* + Rate registers. The receive bit rate calculates as + bitrate = clock1 / (2 * (8 ^ RDV8) * RDR) + (similarly for transmit) + + where clock1 = clock_rate / (CLK4M? 4:3) + */ + UINT16 m_RDR; // Receive data rate + bool m_RDV8; // Receive data rate divider + UINT16 m_XDR; // Transmit data rate + bool m_XDV8; // Transmit data rate divider + + /* Status flags */ + bool m_INT; // mirrors /INT output line, inverted + bool m_DSCH; // Data set status change + + bool m_CTSin; // Inverted /CTS input (i.e. CTS) + bool m_DSRin; // Inverted /DSR input (i.e. DSR) + bool m_RTSout; // Current inverted /RTS line state (i.e. RTS) + + bool m_TIMELP; // Timer elapsed + bool m_TIMERR; // Timer error + + bool m_XSRE; // Transmit shift register empty + bool m_XBRE; // Transmit buffer register empty + bool m_RBRL; // Receive buffer register loaded + + bool m_RIN; // State of the RIN pin + bool m_RSBD; // Receive start bit detect + bool m_RFBD; // Receive full bit detect + bool m_RFER; // Receive framing error + bool m_ROVER; // Receiver overflow + bool m_RPER; // Receive parity error + + UINT8 m_RCL; // Character length + bool m_ODDP; + bool m_PENB; + UINT8 m_STOPB; + bool m_CLK4M; // /PHI input divide select + + UINT8 m_TMR; /* interval timer */ + + /* clock registers */ + emu_timer *m_dectimer; /* MESS timer, used to emulate the decrementer register */ + emu_timer *m_recvtimer; + emu_timer *m_sendtimer; + + // This value is the ratio of data input versus the poll rate. The + // data source should deliver data bytes at every 1/baudpoll call. + // This is to ensure that data is delivered at a rate that is expected + // from the emulated program. + double m_baudpoll; + + // Caches the last configuration setting (used with the ctrl_callback) + int m_last_config_value; +}; + +/*************************************************************************** + DEVICE CONFIGURATION MACROS +***************************************************************************/ + +#define MCFG_TMS9902_ADD(_tag, _intrf, _clock) \ + MCFG_DEVICE_ADD(_tag, TMS9902, _clock) \ + MCFG_DEVICE_CONFIG(_intrf) + +#endif /* __TMS9902_H__ */