mame/src/devices/machine/i8255.cpp
Vas Crabb c0ab1c5aa4 (nw) srcclean and some cleanup:
* Make more #include guards follow standard format - using MAME_ as the prefix makes it easy to see which ones come from our code in a preprocessor dump, and having both src/devices/machine/foo.h and src/mame/machine/foo.h causes issues anyway
* Get #include "emu.h" out of headers - it should only be the first thing in a complilation unit or we get differences in behaviour with PCH on/off
* Add out-of-line destructors to some devices - it forces the compiler to instantiate the vtable in a certain location and avoids some non-deterministic compiler behaviours
2018-07-22 20:41:57 +10:00

921 lines
17 KiB
C++

// license:BSD-3-Clause
// copyright-holders:Curt Coder
/**********************************************************************
Intel 8255(A) Programmable Peripheral Interface emulation
**********************************************************************/
#include "emu.h"
#include "i8255.h"
//#define VERBOSE 1
#include "logmacro.h"
//**************************************************************************
// MACROS / CONSTANTS
//**************************************************************************
enum
{
PORT_A = 0,
PORT_B,
PORT_C,
CONTROL
};
enum
{
GROUP_A = 0,
GROUP_B
};
enum
{
MODE_0 = 0,
MODE_1,
MODE_2
};
enum
{
MODE_OUTPUT = 0,
MODE_INPUT
};
#define CONTROL_PORT_C_LOWER_INPUT 0x01
#define CONTROL_PORT_B_INPUT 0x02
#define CONTROL_GROUP_B_MODE_1 0x04
#define CONTROL_PORT_C_UPPER_INPUT 0x08
#define CONTROL_PORT_A_INPUT 0x10
#define CONTROL_GROUP_A_MODE_MASK 0x60
#define CONTROL_MODE_SET 0x80
//**************************************************************************
// DEVICE DEFINITIONS
//**************************************************************************
DEFINE_DEVICE_TYPE(I8255, i8255_device, "i8255", "Intel 8255 PPI")
decltype(I8255) I8255A = I8255;
DEFINE_DEVICE_TYPE(AMS40489_PPI, ams40489_ppi_device, "ams40489_ppi", "Amstrad AMS40489 PPI")
//**************************************************************************
// INLINE HELPERS
//**************************************************************************
inline void i8255_device::check_interrupt(int port)
{
switch (group_mode(port))
{
case MODE_1:
switch (port_mode(port))
{
case MODE_INPUT:
set_intr(port, (m_inte[port] && m_ibf[port]));
break;
case MODE_OUTPUT:
set_intr(port, (m_inte[port] && m_obf[port]));
break;
}
break;
case MODE_2:
set_intr(port, ((m_inte1 && m_obf[port]) || (m_inte2 && m_ibf[port])));
break;
}
}
inline void i8255_device::set_ibf(int port, int state)
{
LOG("I8255 Port %c IBF: %u\n", 'A' + port, state);
m_ibf[port] = state;
check_interrupt(port);
}
inline void i8255_device::set_obf(int port, int state)
{
LOG("I8255 Port %c OBF: %u\n", 'A' + port, state);
m_obf[port] = state;
check_interrupt(port);
}
inline void i8255_device::set_inte(int port, int state)
{
LOG("I8255 Port %c INTE: %u\n", 'A' + port, state);
m_inte[port] = state;
check_interrupt(port);
}
inline void i8255_device::set_inte1(int state)
{
LOG("I8255 Port A INTE1: %u\n", state);
m_inte1 = state;
check_interrupt(PORT_A);
}
inline void i8255_device::set_inte2(int state)
{
LOG("I8255 Port A INTE2: %u\n", state);
m_inte2 = state;
check_interrupt(PORT_A);
}
inline void i8255_device::set_intr(int port, int state)
{
LOG("I8255 Port %c INTR: %u\n", 'A' + port, state);
m_intr[port] = state;
output_pc();
}
inline int i8255_device::group_mode(int group)
{
int mode = 0;
switch (group)
{
case GROUP_A:
switch ((m_control & CONTROL_GROUP_A_MODE_MASK) >> 5)
{
case 0: mode = MODE_0; break;
case 1: mode = MODE_1; break;
case 2: case 3: mode = MODE_2; break;
}
break;
case GROUP_B:
mode = m_control & CONTROL_GROUP_B_MODE_1 ? MODE_1 : MODE_0;
break;
}
return mode;
}
inline int i8255_device::port_mode(int port)
{
int mode = 0;
switch (port)
{
case PORT_A: mode = m_control & CONTROL_PORT_A_INPUT ? MODE_INPUT : MODE_OUTPUT; break;
case PORT_B: mode = m_control & CONTROL_PORT_B_INPUT ? MODE_INPUT : MODE_OUTPUT; break;
}
return mode;
}
inline int i8255_device::port_c_lower_mode()
{
return m_control & CONTROL_PORT_C_LOWER_INPUT ? MODE_INPUT : MODE_OUTPUT;
}
inline int i8255_device::port_c_upper_mode()
{
return m_control & CONTROL_PORT_C_UPPER_INPUT ? MODE_INPUT : MODE_OUTPUT;
}
//**************************************************************************
// LIVE DEVICE
//**************************************************************************
//-------------------------------------------------
// i8255_device - constructor
//-------------------------------------------------
i8255_device::i8255_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock, bool is_ams40489)
: device_t(mconfig, type, tag, owner, clock)
, m_force_portb_in(is_ams40489)
, m_force_portc_out(is_ams40489)
, m_dont_clear_output_latches(is_ams40489)
, m_in_pa_cb(*this)
, m_in_pb_cb(*this)
, m_in_pc_cb(*this)
, m_out_pa_cb(*this)
, m_out_pb_cb(*this)
, m_out_pc_cb(*this)
, m_tri_pa_cb(*this)
, m_tri_pb_cb(*this)
, m_control(0)
, m_intr{ 0, 0 }
{
}
i8255_device::i8255_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: i8255_device(mconfig, I8255, tag, owner, clock, false)
{
}
void i8255_device::device_resolve_objects()
{
// resolve callbacks
m_in_pa_cb.resolve_safe(0);
m_in_pb_cb.resolve_safe(0);
m_in_pc_cb.resolve_safe(0);
m_out_pa_cb.resolve_safe();
m_out_pb_cb.resolve_safe();
m_out_pc_cb.resolve_safe();
m_tri_pa_cb.resolve_safe(0xff);
m_tri_pb_cb.resolve_safe(0xff);
}
//-------------------------------------------------
// device_start - device-specific startup
//-------------------------------------------------
void i8255_device::device_start()
{
// register for state saving
save_item(NAME(m_control));
save_item(NAME(m_output));
save_item(NAME(m_input));
save_item(NAME(m_ibf));
save_item(NAME(m_obf));
save_item(NAME(m_inte));
save_item(NAME(m_inte1));
save_item(NAME(m_inte2));
save_item(NAME(m_intr));
}
//-------------------------------------------------
// device_reset - device-specific reset
//-------------------------------------------------
void i8255_device::device_reset()
{
set_mode(0x9b);
}
uint8_t i8255_device::read_mode0(int port)
{
uint8_t data;
if (port_mode(port) == MODE_OUTPUT)
{
// read data from output latch
data = m_output[port];
}
else
{
// read data from port
data = (port == PORT_A) ? m_in_pa_cb(0) : ((port == PORT_B) ? m_in_pb_cb(0) : m_in_pc_cb(0));
}
return data;
}
uint8_t i8255_device::read_mode1(int port)
{
uint8_t data;
if (port_mode(port) == MODE_OUTPUT)
{
// read data from output latch
data = m_output[port];
}
else
{
// read data from input latch
data = m_input[port];
// clear input buffer full flag
set_ibf(port, 0);
// clear interrupt
set_intr(port, 0);
// clear input latch
m_input[port] = 0;
}
return data;
}
uint8_t i8255_device::read_mode2()
{
// read data from input latch
uint8_t const data = m_input[PORT_A];
// clear input buffer full flag
set_ibf(PORT_A, 0);
// clear interrupt
set_intr(PORT_A, 0);
// clear input latch
m_input[PORT_A] = 0;
return data;
}
uint8_t i8255_device::read_pc()
{
uint8_t data = 0;
uint8_t mask = 0;
uint8_t b_mask = 0x0f;
// PC upper
switch (group_mode(GROUP_A))
{
case MODE_0:
if (port_c_upper_mode() == MODE_OUTPUT)
{
// read data from output latch
data |= m_output[PORT_C] & 0xf0;
}
else
{
// read data from port
mask |= 0xf0;
}
break;
case MODE_1:
data |= m_intr[PORT_A] ? 0x08 : 0x00;
if (port_mode(PORT_A) == MODE_OUTPUT)
{
data |= m_obf[PORT_A] ? 0x80 : 0x00;
data |= m_inte[PORT_A] ? 0x40 : 0x00;
mask |= 0x30;
}
else
{
data |= m_ibf[PORT_A] ? 0x20 : 0x00;
data |= m_inte[PORT_A] ? 0x10 : 0x00;
mask |= 0xc0;
}
if (port_c_upper_mode() == MODE_OUTPUT)
{
// read data from output latch
data |= m_output[PORT_C] & mask;
mask = 0;
}
break;
case MODE_2:
b_mask = 0x07;
data |= m_intr[PORT_A] ? 0x08 : 0x00;
data |= m_inte2 ? 0x10 : 0x00;
data |= m_ibf[PORT_A] ? 0x20 : 0x00;
data |= m_inte1 ? 0x40 : 0x00;
data |= m_obf[PORT_A] ? 0x80 : 0x00;
break;
}
// PC lower
switch (group_mode(GROUP_B))
{
case MODE_0:
if (port_c_lower_mode() == MODE_OUTPUT)
{
// read data from output latch
data |= m_output[PORT_C] & b_mask;
}
else
{
// read data from port
mask |= b_mask;
}
break;
case MODE_1:
data |= m_inte[PORT_B] ? 0x04 : 0x00;
data |= m_intr[PORT_B] ? 0x01 : 0x00;
if (port_mode(PORT_B) == MODE_OUTPUT)
{
data |= m_obf[PORT_B] ? 0x02 : 0x00;
}
else
{
data |= m_ibf[PORT_B] ? 0x02 : 0x00;
}
}
if (mask)
{
// read data from port
data |= m_in_pc_cb(0) & mask;
}
return data;
}
void i8255_device::write_mode0(int port, uint8_t data)
{
if (port_mode(port) == MODE_OUTPUT)
{
// latch output data
m_output[port] = data;
// write data to port
if (port == PORT_A)
m_out_pa_cb((offs_t)0, m_output[port]);
else if (port == PORT_B)
m_out_pb_cb((offs_t)0, m_output[port]);
else
m_out_pc_cb((offs_t)0, m_output[port]);
}
}
void i8255_device::write_mode1(int port, uint8_t data)
{
if (port_mode(port) == MODE_OUTPUT)
{
// latch output data
m_output[port] = data;
// write data to port
if (port == PORT_A)
m_out_pa_cb((offs_t)0, m_output[port]);
else if (port == PORT_B)
m_out_pb_cb((offs_t)0, m_output[port]);
else
m_out_pc_cb((offs_t)0, m_output[port]);
// set output buffer full flag
set_obf(port, 0);
// clear interrupt
set_intr(port, 0);
}
}
void i8255_device::write_mode2(uint8_t data)
{
// latch output data
m_output[PORT_A] = data;
// write data to port
m_out_pa_cb((offs_t)0, data);
// set output buffer full flag
set_obf(PORT_A, 0);
// clear interrupt
set_intr(PORT_A, 0);
}
void i8255_device::output_pc()
{
uint8_t data = 0;
uint8_t mask = 0;
uint8_t b_mask = 0x0f;
// PC upper
switch (group_mode(GROUP_A))
{
case MODE_0:
if (port_c_upper_mode() == MODE_OUTPUT)
{
mask |= 0xf0;
}
else
{
// TTL inputs float high
data |= 0xf0;
}
break;
case MODE_1:
data |= m_intr[PORT_A] ? 0x08 : 0x00;
if (port_mode(PORT_A) == MODE_OUTPUT)
{
data |= m_obf[PORT_A] ? 0x80 : 0x00;
mask |= 0x30;
}
else
{
data |= m_ibf[PORT_A] ? 0x20 : 0x00;
mask |= 0xc0;
}
break;
case MODE_2:
b_mask = 0x07;
data |= m_intr[PORT_A] ? 0x08 : 0x00;
data |= m_ibf[PORT_A] ? 0x20 : 0x00;
data |= m_obf[PORT_A] ? 0x80 : 0x00;
break;
}
// PC lower
switch (group_mode(GROUP_B))
{
case MODE_0:
if (port_c_lower_mode() == MODE_OUTPUT)
{
mask |= b_mask;
}
else
{
// TTL inputs float high
data |= b_mask;
}
break;
case MODE_1:
data |= m_intr[PORT_B] ? 0x01 : 0x00;
if (port_mode(PORT_B) == MODE_OUTPUT)
{
data |= m_obf[PORT_B] ? 0x02 : 0x00;
}
else
{
data |= m_ibf[PORT_B] ? 0x02 : 0x00;
}
}
data |= m_output[PORT_C] & mask;
m_out_pc_cb((offs_t)0, data);
}
void i8255_device::set_mode(uint8_t data)
{
m_control = data;
if (m_force_portb_in)
m_control = m_control | CONTROL_PORT_B_INPUT;
if (m_force_portc_out)
{
m_control = m_control & ~CONTROL_PORT_C_UPPER_INPUT;
m_control = m_control & ~CONTROL_PORT_C_LOWER_INPUT;
}
// group A
if (!m_dont_clear_output_latches)
m_output[PORT_A] = 0;
m_input[PORT_A] = 0;
m_ibf[PORT_A] = 0;
m_obf[PORT_A] = 1;
m_inte[PORT_A] = 0;
m_inte1 = 0;
m_inte2 = 0;
if (port_mode(PORT_A) == MODE_OUTPUT)
{
m_out_pa_cb((offs_t)0, m_output[PORT_A]);
}
else
{
// TTL inputs floating
m_out_pa_cb((offs_t)0, m_tri_pa_cb(0));
}
LOG("I8255 Group A Mode: %u\n", group_mode(GROUP_A));
LOG("I8255 Port A Mode: %s\n", (port_mode(PORT_A) == MODE_OUTPUT) ? "output" : "input");
LOG("I8255 Port C Upper Mode: %s\n", (port_c_upper_mode() == MODE_OUTPUT) ? "output" : "input");
LOG("I8255 Group B Mode: %u\n", group_mode(GROUP_B));
LOG("I8255 Port B Mode: %s\n", (port_mode(PORT_B) == MODE_OUTPUT) ? "output" : "input");
LOG("I8255 Port C Lower Mode: %s\n", (port_c_lower_mode() == MODE_OUTPUT) ? "output" : "input");
// group B
if (!m_dont_clear_output_latches)
m_output[PORT_B] = 0;
m_input[PORT_B] = 0;
m_ibf[PORT_B] = 0;
m_obf[PORT_B] = 1;
m_inte[PORT_B] = 0;
if (port_mode(PORT_B) == MODE_OUTPUT)
{
m_out_pb_cb((offs_t)0, m_output[PORT_B]);
}
else
{
// TTL inputs floating
m_out_pb_cb((offs_t)0, m_tri_pb_cb(0));
}
if (!m_dont_clear_output_latches)
m_output[PORT_C] = 0;
m_input[PORT_C] = 0;
output_pc();
}
void i8255_device::set_pc_bit(int bit, int state)
{
// set output latch bit
m_output[PORT_C] &= ~(1 << bit);
m_output[PORT_C] |= state << bit;
switch (group_mode(GROUP_A))
{
case MODE_1:
if (port_mode(PORT_A) == MODE_OUTPUT)
{
switch (bit)
{
case 3: set_intr(PORT_A, state); break;
case 6: set_inte(PORT_A, state); break;
case 7: set_obf(PORT_A, state); break;
default: break;
}
}
else
{
switch (bit)
{
case 3: set_intr(PORT_A, state); break;
case 4: set_inte(PORT_A, state); break;
case 5: set_ibf(PORT_A, state); break;
default: break;
}
}
break;
case MODE_2:
switch (bit)
{
case 3: set_intr(PORT_A, state); break;
case 4: set_inte2(state); break;
case 5: set_ibf(PORT_A, state); break;
case 6: set_inte1(state); break;
case 7: set_obf(PORT_A, state); break;
default: break;
}
break;
}
if (group_mode(GROUP_B) == MODE_1)
{
switch (bit)
{
case 0: set_intr(PORT_B, state); break;
case 1:
if (port_mode(PORT_B) == MODE_OUTPUT)
set_obf(PORT_B, state);
else
set_ibf(PORT_B, state);
break;
case 2: set_inte(PORT_B, state); break;
default: break;
}
}
output_pc();
}
READ8_MEMBER( i8255_device::read )
{
uint8_t data = 0;
switch (offset & 0x03)
{
case PORT_A:
switch (group_mode(GROUP_A))
{
case MODE_0: data = read_mode0(PORT_A); break;
case MODE_1: data = read_mode1(PORT_A); break;
case MODE_2: data = read_mode2(); break;
}
LOG("I8255 Port A Read: %02x\n", data);
break;
case PORT_B:
switch (group_mode(GROUP_B))
{
case MODE_0: data = read_mode0(PORT_B); break;
case MODE_1: data = read_mode1(PORT_B); break;
}
LOG("I8255 Port B Read: %02x\n", data);
break;
case PORT_C:
data = read_pc();
LOG("I8255 Port C Read: %02x\n", data);
break;
case CONTROL:
data = m_control;
LOG("I8255 Mode Control Word Read: %02x\n", data);
break;
}
return data;
}
WRITE8_MEMBER( i8255_device::write )
{
switch (offset & 0x03)
{
case PORT_A:
LOG("I8255 Port A Write: %02x\n", data);
switch (group_mode(GROUP_A))
{
case MODE_0: write_mode0(PORT_A, data); break;
case MODE_1: write_mode1(PORT_A, data); break;
case MODE_2: write_mode2(data); break;
}
break;
case PORT_B:
LOG("I8255 Port B Write: %02x\n", data);
switch (group_mode(GROUP_B))
{
case MODE_0: write_mode0(PORT_B, data); break;
case MODE_1: write_mode1(PORT_B, data); break;
}
break;
case PORT_C:
LOG("I8255 Port C Write: %02x\n", data);
m_output[PORT_C] = data;
output_pc();
break;
case CONTROL:
if (data & CONTROL_MODE_SET)
{
LOG("I8255 Mode Control Word: %02x\n", data);
set_mode(data);
}
else
{
int bit = (data >> 1) & 0x07;
int state = BIT(data, 0);
LOG("I8255 %s Port C Bit %u\n", state ? "Set" : "Reset", bit);
set_pc_bit(bit, state);
}
break;
}
}
READ8_MEMBER( i8255_device::pa_r )
{
return read_pa();
}
//-------------------------------------------------
// read_pa - port A read
//-------------------------------------------------
uint8_t i8255_device::read_pa()
{
uint8_t data = 0xff;
if (port_mode(PORT_A) == MODE_OUTPUT)
data = m_output[PORT_A];
return data;
}
READ8_MEMBER( i8255_device::pb_r )
{
return read_pb();
}
//-------------------------------------------------
// read_pb - port B read
//-------------------------------------------------
uint8_t i8255_device::read_pb()
{
uint8_t data = 0xff;
if (port_mode(PORT_B) == MODE_OUTPUT)
{
data = m_output[PORT_B];
}
return data;
}
WRITE_LINE_MEMBER( i8255_device::pc2_w )
{
if (group_mode(GROUP_B) == 1)
{
if (port_mode(PORT_B) == MODE_OUTPUT)
{
// port B acknowledge
if (!m_obf[PORT_B] && !state)
{
LOG("I8255 Port B Acknowledge\n");
// clear output buffer flag
set_obf(PORT_B, 1);
}
}
else
{
// port B strobe
if (!m_ibf[PORT_B] && !state)
{
LOG("I8255 Port B Strobe\n");
// read port into latch
m_input[PORT_B] = m_in_pb_cb(0);
// set input buffer flag
set_ibf(PORT_B, 1);
}
}
}
}
WRITE_LINE_MEMBER( i8255_device::pc4_w )
{
if ((group_mode(GROUP_A) == 2) || ((group_mode(GROUP_A) == 1) && (port_mode(PORT_A) == MODE_INPUT)))
{
// port A strobe
if (!m_ibf[PORT_A] && !state)
{
LOG("I8255 Port A Strobe\n");
// read port into latch
m_input[PORT_A] = m_in_pa_cb(0);
// set input buffer flag
set_ibf(PORT_A, 1);
}
}
}
WRITE_LINE_MEMBER( i8255_device::pc6_w )
{
if ((group_mode(GROUP_A) == 2) || ((group_mode(GROUP_A) == 1) && (port_mode(PORT_A) == MODE_OUTPUT)))
{
// port A acknowledge
if (!m_obf[PORT_A] && !state)
{
LOG("I8255 Port A Acknowledge\n");
// clear output buffer flag
set_obf(PORT_A, 1);
}
}
}
// AMS40489 (Amstrad Plus/GX4000 ASIC PPI implementation)
ams40489_ppi_device::ams40489_ppi_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: i8255_device(mconfig, AMS40489_PPI, tag, owner, clock, true)
{
}