Moved TMS9901/9902 from mess/machine into emu/machine subtree
This commit is contained in:
parent
799b4d1951
commit
f79593ee04
4
.gitattributes
vendored
4
.gitattributes
vendored
@ -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
|
||||
|
@ -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 \
|
||||
|
542
src/emu/machine/tms9901.c
Normal file
542
src/emu/machine/tms9901.c
Normal file
@ -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 <math.h>
|
||||
#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<const tms9901_interface *>(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<tms9901_device>;
|
142
src/emu/machine/tms9901.h
Normal file
142
src/emu/machine/tms9901.h
Normal file
@ -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__ */
|
837
src/emu/machine/tms9902.c
Normal file
837
src/emu/machine/tms9902.c
Normal file
@ -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 <math.h>
|
||||
#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<const tms9902_interface *>(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<tms9902_device>;
|
||||
|
195
src/emu/machine/tms9902.h
Normal file
195
src/emu/machine/tms9902.h
Normal file
@ -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__ */
|
Loading…
Reference in New Issue
Block a user