// license:BSD-3-Clause
// copyright-holders:Patrick Mackinlay
/*
* An implementation of the IOGA (I/O Gate Array) devices found in Intergraph
* InterPro family systems. There is no public documentation for these devices,
* so the implementation is being built to follow the logic of the system boot
* ROM and diagnostic tests.
*
* The device handles most of the I/O for the system, including timers, DMA,
* interrupts, and target device interfacing.
*
* TODO
* - tidy up dma control flags
* - timer 2 and 3
* - correct serial dma per-channel interrupt handling
* - multi-channel Ethernet?
* - save/restore state
*/
#include "emu.h"
#include "interpro_ioga.h"
// enables hacks which allow iogadiag tests to complete but break scsi dma
#define IOGA_DMA_DIAG_HACK 0
#define LOG_GENERAL (1U << 0)
#define LOG_NMI (1U << 1)
#define LOG_INT (1U << 2)
#define LOG_DMA (1U << 3)
#define LOG_FLOPPY (1U << 4)
#define LOG_SCSI (1U << 5)
#define LOG_NETWORK (1U << 6)
#define LOG_MOUSE (1U << 7)
#define LOG_SERIALDMA (1U << 8)
#define LOG_TIMER0 (1U << 0x10)
#define LOG_TIMER1 (1U << 0x11)
#define LOG_TIMER2 (1U << 0x12)
#define LOG_TIMER3 (1U << 0x13)
#define LOG_TIMERRD (1U << 0x14)
#define LOG_TIMERS (LOG_TIMER0 | LOG_TIMER1 | LOG_TIMER2 | LOG_TIMER3)
//#define VERBOSE (LOG_GENERAL | LOG_INT | LOG_NMI | LOG_DMA | LOG_SERIALDMA | LOG_NETWORK)
//#define VERBOSE_IRQ ((1 << IRQ_SCSI) | (1 << IRQ_FLOPPY) | (1 << IRQ_MOUSE) | (1 << IRQ_SERDMA) | (1 << IRQ_SERIAL) | (1 << IRQ_ETHERNET))
//#define VERBOSE_DMA ((1 << DMA_PLOTTER) | (1 << DMA_SCSI) | (1 << DMA_FLOPPY))
#define VERBOSE 0
#define VERBOSE_IRQ 0
#define VERBOSE_DMA 0
#include "logmacro.h"
#define LOGIRQ(irq, ...) do { if (VERBOSE_IRQ & (1U << irq)) (LOG_OUTPUT_FUNC)(__VA_ARGS__); } while (false)
#define LOGDMA(dma, ...) do { if (VERBOSE_DMA & (1U << dma)) (LOG_OUTPUT_FUNC)(__VA_ARGS__); } while (false)
void interpro_ioga_device::map(address_map &map)
{
map(0x0c, 0x1b).rw(FUNC(interpro_ioga_device::dma_plotter_r), FUNC(interpro_ioga_device::dma_plotter_w));
map(0x1c, 0x1f).rw(FUNC(interpro_ioga_device::dma_plotter_eosl_r), FUNC(interpro_ioga_device::dma_plotter_eosl_w));
map(0x20, 0x2f).rw(FUNC(interpro_ioga_device::dma_scsi_r), FUNC(interpro_ioga_device::dma_scsi_w));
map(0x30, 0x3f).rw(FUNC(interpro_ioga_device::dma_floppy_r), FUNC(interpro_ioga_device::dma_floppy_w));
map(0x40, 0x43).rw(FUNC(interpro_ioga_device::serial_dma0_addr_r), FUNC(interpro_ioga_device::serial_dma0_addr_w));
map(0x44, 0x47).rw(FUNC(interpro_ioga_device::serial_dma0_ctrl_r), FUNC(interpro_ioga_device::serial_dma0_ctrl_w));
map(0x48, 0x4b).rw(FUNC(interpro_ioga_device::serial_dma1_addr_r), FUNC(interpro_ioga_device::serial_dma1_addr_w));
map(0x4c, 0x4f).rw(FUNC(interpro_ioga_device::serial_dma1_ctrl_r), FUNC(interpro_ioga_device::serial_dma1_ctrl_w));
map(0x50, 0x53).rw(FUNC(interpro_ioga_device::serial_dma2_addr_r), FUNC(interpro_ioga_device::serial_dma2_addr_w));
map(0x54, 0x57).rw(FUNC(interpro_ioga_device::serial_dma2_ctrl_r), FUNC(interpro_ioga_device::serial_dma2_ctrl_w));
map(0x84, 0x87).rw(FUNC(interpro_ioga_device::mouse_status_r), FUNC(interpro_ioga_device::mouse_status_w));
map(0x88, 0x8b).rw(FUNC(interpro_ioga_device::prescaler_r), FUNC(interpro_ioga_device::prescaler_w));
map(0x8c, 0x8f).rw(FUNC(interpro_ioga_device::timer0_r), FUNC(interpro_ioga_device::timer0_w));
map(0x90, 0x93).rw(FUNC(interpro_ioga_device::timer1_r), FUNC(interpro_ioga_device::timer1_w));
map(0x94, 0x97).r(FUNC(interpro_ioga_device::error_address_r));
map(0x98, 0x9b).r(FUNC(interpro_ioga_device::error_businfo_r));
map(0x9c, 0x9d).rw(FUNC(interpro_ioga_device::arbctl_r), FUNC(interpro_ioga_device::arbctl_w));
}
void emerald_ioga_device::map(address_map &map)
{
interpro_ioga_device::map(map);
map(0x00, 0x03).rw(FUNC(emerald_ioga_device::eth_base_r), FUNC(emerald_ioga_device::eth_base_w));
map(0x04, 0x05).rw(FUNC(emerald_ioga_device::eth_control_r), FUNC(emerald_ioga_device::eth_control_w));
map(0x60, 0x83).rw(FUNC(interpro_ioga_device::hardint_r), FUNC(interpro_ioga_device::hardint_w));
map(0x82, 0x82).rw(FUNC(interpro_ioga_device::softint_r), FUNC(interpro_ioga_device::softint_w));
map(0x83, 0x83).rw(FUNC(interpro_ioga_device::nmictrl_r), FUNC(interpro_ioga_device::nmictrl_w));
}
void turquoise_ioga_device::map(address_map &map)
{
interpro_ioga_device::map(map);
map(0x00, 0x03).rw(FUNC(turquoise_ioga_device::eth_base_r), FUNC(turquoise_ioga_device::eth_base_w));
map(0x04, 0x05).rw(FUNC(turquoise_ioga_device::eth_control_r), FUNC(turquoise_ioga_device::eth_control_w));
map(0x60, 0x83).rw(FUNC(turquoise_ioga_device::hardint_r), FUNC(turquoise_ioga_device::hardint_w));
map(0x82, 0x82).rw(FUNC(turquoise_ioga_device::softint_r), FUNC(turquoise_ioga_device::softint_w));
map(0x83, 0x83).rw(FUNC(turquoise_ioga_device::nmictrl_r), FUNC(turquoise_ioga_device::nmictrl_w));
//map(0x9e, 0x9f).rw(FUNC(turquoise_ioga_device::?), FUNC(turquoise_ioga_device::?)); // ip2000 boot code writes 0x7f18
}
void sapphire_ioga_device::map(address_map &map)
{
interpro_ioga_device::map(map);
map(0x00, 0x03).rw(FUNC(sapphire_ioga_device::eth_remap_r), FUNC(sapphire_ioga_device::eth_remap_w));
map(0x04, 0x07).rw(FUNC(sapphire_ioga_device::eth_mappg_r), FUNC(sapphire_ioga_device::eth_mappg_w));
map(0x08, 0x0b).rw(FUNC(sapphire_ioga_device::eth_control_r), FUNC(sapphire_ioga_device::eth_control_w));
// 5a - sib control?
//map(0x58, 0x5b).rw(FUNC(interpro_ioga_device::sib_r), FUNC(interpro_ioga_device::sib_w));
map(0x5c, 0x83).rw(FUNC(interpro_ioga_device::hardint_r), FUNC(interpro_ioga_device::hardint_w));
map(0x82, 0x82).rw(FUNC(interpro_ioga_device::softint_r), FUNC(interpro_ioga_device::softint_w));
map(0x83, 0x83).rw(FUNC(interpro_ioga_device::nmictrl_r), FUNC(interpro_ioga_device::nmictrl_w));
map(0xa0, 0xa3).rw(FUNC(sapphire_ioga_device::timer2_count_r), FUNC(sapphire_ioga_device::timer2_count_w));
map(0xa4, 0xa7).rw(FUNC(sapphire_ioga_device::timer2_value_r), FUNC(sapphire_ioga_device::timer2_value_w));
map(0xa8, 0xab).rw(FUNC(sapphire_ioga_device::timer3_r), FUNC(sapphire_ioga_device::timer3_w));
map(0xac, 0xaf).rw(FUNC(sapphire_ioga_device::bus_timeout_r), FUNC(sapphire_ioga_device::bus_timeout_w)); // boot writes 0x64
map(0xb0, 0xbf).rw(FUNC(sapphire_ioga_device::softint_vector_r), FUNC(sapphire_ioga_device::softint_vector_w));
//c0, c4, c8 -ethernet address a,b,c?
}
DEFINE_DEVICE_TYPE(EMERALD_IOGA, emerald_ioga_device, "ioga_e", "I/O Gate Array (Emerald)")
DEFINE_DEVICE_TYPE(TURQUOISE_IOGA, turquoise_ioga_device, "ioga_t", "I/O Gate Array (Turquoise)")
DEFINE_DEVICE_TYPE(SAPPHIRE_IOGA, sapphire_ioga_device, "ioga_s", "I/O Gate Array (Sapphire)")
interpro_ioga_device::interpro_ioga_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock)
: device_t(mconfig, type, tag, owner, clock)
, m_memory_space(*this, finder_base::DUMMY_TAG, -1, 32)
, m_out_nmi_func(*this)
, m_out_irq_func(*this)
, m_out_irq_vector_func(*this)
, m_fdc_tc_func(*this)
, m_eth_ca_func(*this)
, m_force_state(0)
, m_softint(0)
, m_dma_channel{
{ 0,0,0,0,CLEAR_LINE, {*this}, {*this}, ARBCTL_BGR_PLOT, DMA_PLOTTER, "plotter" },
{ 0,0,0,0,CLEAR_LINE, {*this}, {*this}, ARBCTL_BGR_SCSI, DMA_SCSI, "scsi" },
{ 0,0,0,0,CLEAR_LINE, {*this}, {*this}, ARBCTL_BGR_FDC, DMA_FLOPPY, "floppy" } }
, m_serial_dma_channel{
{ 0,0,CLEAR_LINE, {*this}, {*this}, ARBCTL_BGR_SER0, 0, "serial0" },
{ 0,0,CLEAR_LINE, {*this}, {*this}, ARBCTL_BGR_SER1, 1, "serial1" },
{ 0,0,CLEAR_LINE, {*this}, {*this}, ARBCTL_BGR_SER2, 2, "serial2" } }
{
}
emerald_ioga_device::emerald_ioga_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: interpro_ioga_device(mconfig, EMERALD_IOGA, tag, owner, clock)
{
}
turquoise_ioga_device::turquoise_ioga_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: interpro_ioga_device(mconfig, TURQUOISE_IOGA, tag, owner, clock)
{
}
sapphire_ioga_device::sapphire_ioga_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: interpro_ioga_device(mconfig, SAPPHIRE_IOGA, tag, owner, clock)
{
std::fill(std::begin(m_swicr), std::end(m_swicr), 0);
}
void interpro_ioga_device::device_start()
{
m_memory_space->cache(m_memory);
// resolve callbacks
m_out_nmi_func.resolve();
m_out_irq_func.resolve();
m_out_irq_vector_func.resolve();
m_fdc_tc_func.resolve();
m_eth_ca_func.resolve();
m_hwicr = std::make_unique<u16[]>(get_int_count());
for (dma_channel_t &dma_channel : m_dma_channel)
{
dma_channel.device_r.resolve();
dma_channel.device_w.resolve();
}
for (serial_dma_channel_t &dma_channel : m_serial_dma_channel)
{
dma_channel.device_r.resolve();
dma_channel.device_w.resolve();
}
// allocate timers
m_interrupt_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(interpro_ioga_device::interrupt_check), this));
m_dma_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(interpro_ioga_device::dma), this));
m_serial_dma_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(interpro_ioga_device::serial_dma), this));
m_timer0 = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(interpro_ioga_device::timer0), this));
m_timer1 = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(interpro_ioga_device::timer1), this));
m_timer_60hz = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(interpro_ioga_device::timer_60hz), this));
m_eth_reset_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(interpro_ioga_device::eth_reset), this));
}
void sapphire_ioga_device::device_start()
{
interpro_ioga_device::device_start();
m_timer2 = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(sapphire_ioga_device::timer2), this));
m_timer3 = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(sapphire_ioga_device::timer3), this));
}
void interpro_ioga_device::device_reset()
{
// initialise interrupt state
m_active_interrupt_type = INT_NONE;
m_nmi_state = CLEAR_LINE;
m_irq_state = CLEAR_LINE;
m_irq_vector = 0;
m_line_state = 0;
std::fill_n(m_hwicr.get(), get_int_count(), u16(0));
// initialise dma state
for (dma_channel_t &dma_channel : m_dma_channel)
dma_channel.drq_state = CLEAR_LINE;
for (serial_dma_channel_t &dma_channel : m_serial_dma_channel)
dma_channel.drq_state = CLEAR_LINE;
// disable timers
m_interrupt_timer->enable(false);
m_dma_timer->enable(false);
m_serial_dma_timer->enable(false);
m_timer0->enable(false);
m_timer1->enable(false);
m_eth_reset_timer->enable(false);
m_timer0_count = 0;
m_timer1_count = 0;
m_timer_60hz->adjust(attotime::zero, 0, attotime::from_hz(60));
m_prescaler = 0;
}
void sapphire_ioga_device::device_reset()
{
interpro_ioga_device::device_reset();
m_timer2->enable(false);
m_timer3->enable(false);
m_timer2_count = 0;
m_timer2_value = 0;
m_timer3_count = 0;
}
/*
* Interrupts
*/
void interpro_ioga_device::bus_error(offs_t offset, u32 data)
{
LOG("bus_error address 0x%08x businfo 0x%08x\n", data, offset);
m_error_address = data;
m_error_businfo = offset;
set_nmi_line(ASSERT_LINE);
}
void interpro_ioga_device::set_nmi_line(int state)
{
LOGMASKED(LOG_NMI, "nmi: %s (%s)\n", state ? "asserted" : "cleared", machine().describe_context());
if (state == ASSERT_LINE)
{
// check if nmi is enabled
if (((m_nmictrl & NMI_IE) == NMI_IE) || ((m_nmictrl & (NMI_ALL | NMI_ENABLE1)) == (NMI_ALL | NMI_ENABLE1)))
{
LOGMASKED(LOG_NMI, "nmi: asserting output nmi line\n");
// if level triggered, disable input from pin
if ((m_nmictrl & NMI_EDGE) == 0)
m_nmictrl &= ~NMI_ENABLE2;
nmi(ASSERT_LINE);
}
}
else
nmi(CLEAR_LINE);
}
void interpro_ioga_device::nmi(int state)
{
if (m_nmi_state != state)
{
m_nmi_state = state;
m_out_nmi_func(state);
}
}
void interpro_ioga_device::set_int_line(int number, int state)
{
const u8 offset = get_reg_offset(number);
LOGIRQ(number, "irq: hard interrupt %d state %d\n", number, state);
if (state == CLEAR_LINE)
{
m_line_state &= ~(1 << number);
return;
}
// check already pending
if (m_hwicr[offset] & IRQ_PENDING)
return;
// record line state for level triggered interrupts
if (!(m_hwicr[offset] & IRQ_EDGE))
m_line_state |= (1 << number);
// check enabled
if ((number != IRQ_SERDMA && !(m_hwicr[offset] & (IRQ_ENABLE | IRQ_ENABLE_INT)))
|| ((number == IRQ_SERDMA && !(m_hwicr[offset] & IRQ_ENABLE_SERDMA))))
return;
// set pending
if (BIT(m_line_state, number) || (m_hwicr[offset] & IRQ_EDGE))
{
LOGIRQ(number, "irq: hard interrupt %d pending\n", number);
m_hwicr[offset] |= IRQ_PENDING;
// schedule interrupt check
m_interrupt_timer->adjust(attotime::zero);
}
}
TIMER_CALLBACK_MEMBER(interpro_ioga_device::interrupt_check)
{
// find highest priority pending interrupt
u16 irq_vector = get_irq_vector();
// hard interrupts
for (u8 i = 0; i < get_int_count(); i++)
{
// test interrupt pending
if (m_hwicr[i] & IRQ_PENDING)
{
// check priority
if (m_active_interrupt_type == INT_NONE || (m_hwicr[i] & IRQ_VECTOR) < irq_vector)
{
m_active_interrupt_type = INT_HARD;
m_active_interrupt_number = i;
irq_vector = m_hwicr[i] & IRQ_VECTOR;
}
}
}
// soft interrupts
if (m_softint)
{
for (u8 i = 0; i < 8; i++)
{
// test interrupt pending
if (m_softint & (1 << i))
{
// check priority
if ((m_active_interrupt_type == INT_NONE) || (0x8f + i * 0x10) < irq_vector)
{
m_active_interrupt_type = INT_SOFT;
m_active_interrupt_number = i;
irq_vector = 0x8f + i * 0x10;
}
}
}
}
// assert irq and ivec
if (m_active_interrupt_type != INT_NONE)
irq(ASSERT_LINE, irq_vector);
}
TIMER_CALLBACK_MEMBER(sapphire_ioga_device::interrupt_check)
{
// find highest priority pending interrupt
u16 irq_vector = get_irq_vector();
// hard interrupts
for (u8 i = 0; i < get_int_count(); i++)
{
// test interrupt pending
if (m_hwicr[i] & IRQ_PENDING)
{
// check priority
if (m_active_interrupt_type == INT_NONE || (m_hwicr[i] & IRQ_VECTOR) < irq_vector)
{
m_active_interrupt_type = INT_HARD;
m_active_interrupt_number = i;
irq_vector = m_hwicr[i] & IRQ_VECTOR;
}
}
}
// soft interrupts (low type)
if (m_softint)
{
for (u8 i = 0; i < 8; i++)
{
// test interrupt pending
if (m_softint & (1 << i))
{
// check priority
if (m_active_interrupt_type == INT_NONE || (0x8f + i * 0x10) < irq_vector)
{
m_active_interrupt_type = INT_SOFT;
m_active_interrupt_number = i;
irq_vector = 0x8f + i * 0x10;
}
}
}
}
// soft interrupts (high type)
for (u8 i = 0; i < 8; i++)
{
// test interrupt pending
if (m_swicr[i] & IRQ_PENDING)
{
// check priority
if (m_active_interrupt_type == INT_NONE || (m_swicr[i] & IRQ_VECTOR) < irq_vector)
{
m_active_interrupt_type = INT_SOFT;
m_active_interrupt_number = i + 8;
irq_vector = m_swicr[i] & IRQ_VECTOR;
}
}
}
// assert irq and ivec
if (m_active_interrupt_type != INT_NONE)
irq(ASSERT_LINE, irq_vector);
}
void interpro_ioga_device::irq(int state, u8 irq_vector)
{
if (irq_vector != m_irq_vector)
{
LOGIRQ(m_active_interrupt_number, "irq: setting irq vector 0x%02x\n", irq_vector);
m_irq_vector = irq_vector;
machine().scheduler().synchronize(timer_expired_delegate(FUNC(interpro_ioga_device::set_irq_vector), this));
}
if (m_irq_state != state)
{
LOGIRQ(m_active_interrupt_number, "irq: %s interrupt type %d number %d\n",
state ? "asserting" : "clearing", m_active_interrupt_type, m_active_interrupt_number);
m_irq_state = state;
m_out_irq_func(state);
if (state == CLEAR_LINE)
m_active_interrupt_type = INT_NONE;
}
}
IRQ_CALLBACK_MEMBER(interpro_ioga_device::acknowledge_interrupt)
{
switch (irqline)
{
case INPUT_LINE_IRQ0:
LOGIRQ(m_active_interrupt_number, "irq: interrupt type %d number %d acknowledged\n",
m_active_interrupt_type, m_active_interrupt_number);
// clear pending
switch (m_active_interrupt_type)
{
case INT_HARD:
m_hwicr[m_active_interrupt_number] &= ~IRQ_PENDING;
break;
case INT_SOFT:
m_softint &= ~(1 << m_active_interrupt_number);
break;
default:
// can't happen
fatalerror("irq: interrupt acknowledged without active interrupt\n");
break;
}
// clear irq and irq vector
irq(CLEAR_LINE, 0);
break;
case INPUT_LINE_NMI:
nmi(CLEAR_LINE);
break;
}
// schedule interrupt check
m_interrupt_timer->adjust(attotime::zero);
return 0;
}
IRQ_CALLBACK_MEMBER(sapphire_ioga_device::acknowledge_interrupt)
{
switch (irqline)
{
case INPUT_LINE_IRQ0:
LOGIRQ(m_active_interrupt_number, "irq: interrupt type %d number %d acknowledged\n",
m_active_interrupt_type, m_active_interrupt_number);
// clear pending
switch (m_active_interrupt_type)
{
case INT_HARD:
m_hwicr[m_active_interrupt_number] &= ~IRQ_PENDING;
break;
case INT_SOFT:
if (m_active_interrupt_number < 8)
m_softint &= ~(1 << m_active_interrupt_number);
else
m_swicr[m_active_interrupt_number - 8] &= ~IRQ_PENDING;
break;
default:
// can't happen
fatalerror("irq: interrupt acknowledged without active interrupt\n");
break;
}
// clear irq and irq vector
irq(CLEAR_LINE, 0);
break;
case INPUT_LINE_NMI:
nmi(CLEAR_LINE);
break;
}
// schedule interrupt check
m_interrupt_timer->adjust(attotime::zero);
return 0;
}
void interpro_ioga_device::hardint_w(offs_t offset, u16 data, u16 mem_mask)
{
const int number = get_int_number(offset);
bool force = false;
LOGIRQ(offset, "irq: interrupt %d offset 0x%02x data 0x%04x mem_mask 0x%04x (%s)\n", number, offset, data, mem_mask, machine().describe_context());
if (!(m_hwicr[offset] & IRQ_PENDING))
{
// handle forcing
if (data & IRQ_PENDING)
{
LOGIRQ(number, "irq: interrupt %d force pending\n", number);
// set force pending
m_force_state |= (1 << number);
}
else if (BIT(m_force_state, number))
{
LOGIRQ(number, "irq: interrupt %d forced\n", number);
// clear force pending
m_force_state &= ~(1 << number);
// force the interrupt
force = true;
}
}
// store the data
mem_mask &= ~IRQ_PENDING;
COMBINE_DATA(&m_hwicr[offset]);
if (force)
{
// force an interrupt
set_int_line(number, ASSERT_LINE);
set_int_line(number, CLEAR_LINE);
}
else if (m_line_state & (1 << number))
// re-assert line
set_int_line(number, ASSERT_LINE);
}
void interpro_ioga_device::softint_w(u8 data)
{
// store the written value
m_softint |= data;
// schedule interrupt check
m_interrupt_timer->adjust(attotime::zero);
}
void interpro_ioga_device::nmictrl_w(u8 data)
{
// check for a forced nmi (NMI_NEGPOL written from 1 to 0 with NMI_IE set)
const bool forced = (m_nmictrl & NMI_NEGPOL) && ((data & (NMI_NEGPOL | NMI_IE)) == NMI_IE);
LOGMASKED(LOG_NMI, "nmi: nmictrl 0x%02x (%s)\n", data, machine().describe_context());
m_nmictrl = data;
if (forced)
{
LOGMASKED(LOG_NMI, "nmi: forced\n");
set_nmi_line(ASSERT_LINE);
}
}
void sapphire_ioga_device::softint_vector_w(offs_t offset, u16 data, u16 mem_mask)
{
const int number = offset + 8;
LOG("irq: soft interrupt %d data 0x%04x\n", number, data);
if (!(m_swicr[offset] & IRQ_PENDING))
{
// handle forcing
if (data & IRQ_PENDING)
{
LOG("irq: soft interrupt %d force pending\n", number);
// set force pending
m_force_state |= (1 << (offset + 24));
mem_mask &= ~IRQ_PENDING;
}
else if (BIT(m_force_state, offset + 24))
{
LOG("irq: soft interrupt %d forced\n", number);
// clear force pending
m_force_state &= ~(1 << (offset + 24));
// set interrupt pending
data |= IRQ_PENDING;
}
}
// update the register
COMBINE_DATA(&m_swicr[offset]);
// schedule interrupt check
m_interrupt_timer->adjust(attotime::zero);
}
/*
* DMA
*/
TIMER_CALLBACK_MEMBER(interpro_ioga_device::dma)
{
for (dma_channel_t &dma_channel : m_dma_channel)
{
// check if the channel is enabled
if (!(dma_channel.control & DMA_CTRL_ENABLE))
continue;
// check if the device is requesting a transfer
if (dma_channel.drq_state == CLEAR_LINE)
continue;
// check if there's something to transfer
if (dma_channel.transfer_count == 0)
continue;
// check if the bus is available
if (!(m_arbctl & dma_channel.arb_mask))
continue;
if (dma_channel.control & DMA_CTRL_BERR)
continue;
// transfer from the memory to device or device to memory
while (dma_channel.transfer_count && dma_channel.drq_state)
{
// transfer from the memory to device or device to memory
if (dma_channel.control & DMA_CTRL_WRITE)
dma_channel.device_w(m_memory.read_byte(dma_channel.real_address));
else
m_memory.write_byte(dma_channel.real_address, dma_channel.device_r());
// increment address and decrement count
dma_channel.real_address++;
dma_channel.transfer_count--;
// check for page wrap
if ((dma_channel.real_address & 0xfff) == 0 && dma_channel.transfer_count)
{
LOGDMA(dma_channel.channel, "dma: wrapped to next memory page\n");
// translate virtual address
if (dma_channel.control & DMA_CTRL_VIRTUAL)
{
const u32 ptde = m_memory.read_dword(dma_channel.virtual_address);
// FIXME: ignore the page fault flag?
dma_channel.real_address = ptde & ~0xfff;
LOGDMA(dma_channel.channel, "dma: translated virtual 0x%08x real 0x%08x\n",
dma_channel.virtual_address, dma_channel.real_address);
dma_channel.virtual_address += 4;
}
}
}
// check if the transfer is complete
if (dma_channel.transfer_count == 0)
{
LOGDMA(dma_channel.channel, "dma: transfer %s device ended channel %d control 0x%08x real address 0x%08x virtual address 0x%08x count 0x%08x\n",
(dma_channel.control & DMA_CTRL_WRITE) ? "to" : "from", dma_channel.channel, dma_channel.control, dma_channel.real_address, dma_channel.virtual_address, dma_channel.transfer_count);
if (dma_channel.channel == DMA_FLOPPY)
{
LOGDMA(dma_channel.channel, "dma: asserting fdc terminal count line\n");
m_fdc_tc_func(ASSERT_LINE);
m_fdc_tc_func(CLEAR_LINE);
}
// set transfer count zero flag
dma_channel.control |= DMA_CTRL_TCZERO;
// disable the channel
dma_channel.control &= ~DMA_CTRL_ENABLE;
}
#if IOGA_DMA_DIAG_HACK
#define TAG ((dma_channel.control & DMA_CTRL_TAG) >> 3)
// hacks for forced dma bus error diagnostic tests
if ((dma_channel.control & 0xfe000000 && dma_channel.control & 0xe00) || ((dma_channel.control & DMA_CTRL_WMASK) == 0x41000000))
if (dma_channel.real_address & 0xff000000 || dma_channel.real_address == 0)
{
LOGDMA(dma_channel.channel, "dma: forced bus error hack, control 0x%08x\n", dma_channel.control);
// (7.0267) trigger an interrupt
m_hwicr[dma_channel.channel + 1] |= IRQ_PENDING;
// (7.0268) set bus error bit
dma_channel.control |= DMA_CTRL_BERR;
// 7.0269, 7.0276, 7.0281, 7.0289: set error address from virtual or real dma address
// HACK: don't set error address for 7.0276 special case
if (!(dma_channel.control == 0x65400600 && dma_channel.real_address != 0))
m_error_address = dma_channel.control & DMA_CTRL_VIRTUAL ? dma_channel.virtual_address : dma_channel.real_address;
// compute bus error cycle type from control register
u8 cycle_type = 0x30;
switch ((dma_channel.control >> 24) & 0x8c)
{
case 0x00: cycle_type |= 2; break;
case 0x04: cycle_type |= 1; break;
case 0x08: cycle_type |= 3; break;
case 0x80: cycle_type |= 4; break;
case 0x84: cycle_type |= 8; break;
}
switch (dma_channel.control & ~DMA_CTRL_BERR)
{
case 0x61000800: // VIRTUAL | WRITE | TAG(3)
// (7.0266) trigger an nmi
m_nmi_pending = true;
// (7.0270) set error cycle type 0x52f0: SNAPOK | BERR | BG(IOD) | TAG(0c0) | CT(30)
m_error_businfo = BINFO_SNAPOK | BINFO_BERR | BINFO_BG_IOD | 0xf0;
break;
case 0x65000600: // VIRTUAL | WRITE | X | TAG(4)
if (dma_channel.real_address != 0)
{
// (7.0275) control register expect 0x64400800
dma_channel.control &= ~0x600;
dma_channel.control |= 0x800;
// (7.0277) set error cycle type 0x5331: SNAPOK | BERR | BG(IOD) | TAG(100) | CT(31)
m_error_businfo = BINFO_SNAPOK | BINFO_BERR | BINFO_BG_IOD | TAG | cycle_type;
}
else
{
// (7.0287) set error cycle type 0x62f0: SNAPOK | MMBE | BG(IOD) | TAG(0c0) | CT(30)
m_error_businfo = BINFO_SNAPOK | BINFO_MMBE | BINFO_BG_IOD | TAG | 0x30;
}
break;
default:
m_error_businfo = BINFO_SNAPOK | BINFO_BERR | BINFO_BG_IOD | TAG | cycle_type;
break;
}
dma_channel.state = COMPLETE;
}
#endif
}
}
void interpro_ioga_device::drq(int state, int channel)
{
dma_channel_t &dma_channel = m_dma_channel[channel];
dma_channel.drq_state = state;
// log every 256 bytes
if ((dma_channel.transfer_count & 0xff) == 0)
LOGDMA(channel, "dma: drq for channel %d %s transfer_count 0x%08x\n",
channel, state ? "asserted" : "deasserted", dma_channel.transfer_count);
if (state)
m_dma_timer->adjust(attotime::zero);
}
u32 interpro_ioga_device::dma_r(offs_t offset, dma_channel channel) const
{
const dma_channel_t &dma_channel = m_dma_channel[channel];
switch (offset)
{
case 0:
return dma_channel.real_address;
case 1:
return dma_channel.virtual_address;
case 2:
return dma_channel.transfer_count;
case 3:
return dma_channel.control & ~DMA_CTRL_VIRTUAL;
default:
logerror("dma_r: unknown dma register %d\n", offset);
return 0;
}
}
void interpro_ioga_device::dma_w(offs_t offset, u32 data, u32 mem_mask, dma_channel channel)
{
dma_channel_t &dma_channel = m_dma_channel[channel];
switch (offset)
{
case 0:
LOGDMA(channel, "dma: channel %d real address 0x%08x mem_mask 0x%08x (%s)\n",
channel, data, mem_mask, machine().describe_context());
COMBINE_DATA(&dma_channel.real_address);
break;
case 1:
LOGDMA(channel, "dma: channel %d virtual address 0x%08x mem_mask 0x%08x (%s)\n",
channel, data, mem_mask, machine().describe_context());
COMBINE_DATA(&dma_channel.virtual_address);
dma_channel.virtual_address &= ~0x3;
break;
case 2:
LOGDMA(channel, "dma: channel %d transfer count 0x%08x mem_mask 0x%08x (%s)\n",
channel, data, mem_mask, machine().describe_context());
COMBINE_DATA(&dma_channel.transfer_count);
dma_channel.control &= ~DMA_CTRL_TCZERO;
break;
case 3:
LOGDMA(channel, "dma: channel %d control 0x%08x mem_mask 0x%08x (%s)\n",
channel, data, mem_mask, machine().describe_context());
dma_channel.control = (data & mem_mask & (DMA_CTRL_WMASK|DMA_CTRL_VIRTUAL)) | (dma_channel.control & (~mem_mask | ~(DMA_CTRL_WMASK|DMA_CTRL_VIRTUAL)));
// translate virtual address
if (data & DMA_CTRL_VIRTUAL)
{
const u32 ptde = m_memory.read_dword(dma_channel.virtual_address);
// FIXME: ignore the page fault flag?
dma_channel.real_address = (ptde & ~0xfff) | (dma_channel.real_address & 0xfff);
LOGDMA(dma_channel.channel, "dma: translated virtual 0x%08x real 0x%08x\n",
dma_channel.virtual_address, dma_channel.real_address);
dma_channel.virtual_address += 4;
}
// (7.0272) if bus error flag is written, clear existing bus error (otherwise retain existing state)
if (data & DMA_CTRL_BERR)
dma_channel.control &= ~DMA_CTRL_BERR;
break;
}
}
TIMER_CALLBACK_MEMBER(interpro_ioga_device::serial_dma)
{
LOGMASKED(LOG_SERIALDMA, "dma: serial_dma()\n");
for (serial_dma_channel_t &dma_channel : m_serial_dma_channel)
{
// check if there's something to transfer
if ((dma_channel.control & SDMA_COUNT) == 0)
continue;
// check if the device is requesting a transfer
if (dma_channel.drq_state == CLEAR_LINE)
continue;
// check if the bus is available
if ((m_arbctl & dma_channel.arb_mask) == 0)
continue;
// transfer from the memory to device or device to memory
while ((dma_channel.control & SDMA_COUNT) && dma_channel.drq_state)
{
if (dma_channel.control & SDMA_WRITE)
{
u8 data = m_memory.read_byte(dma_channel.address++);
LOGMASKED(LOG_SERIALDMA, "dma: writing byte 0x%02x to serial channel %d\n",
data, dma_channel.channel);
dma_channel.device_w(data);
}
else
{
u8 data = dma_channel.device_r();
LOGMASKED(LOG_SERIALDMA, "dma: reading byte 0x%02x from serial channel %d\n",
data, dma_channel.channel);
m_memory.write_byte(dma_channel.address++, data);
}
// decrement transfer count
dma_channel.control = (dma_channel.control & ~SDMA_COUNT) | ((dma_channel.control & SDMA_COUNT) - 1);
}
if ((dma_channel.control & SDMA_COUNT) == 0)
{
// transfer count zero
dma_channel.control |= SDMA_TCZERO;
dma_channel.control &= ~SDMA_ENABLE;
// raise an interrupt
// FIXME: assume edge-triggered?
set_int_line(IRQ_SERDMA, ASSERT_LINE);
set_int_line(IRQ_SERDMA, CLEAR_LINE);
}
}
}
void interpro_ioga_device::serial_drq(int state, int channel)
{
serial_dma_channel_t &dma_channel = m_serial_dma_channel[channel];
dma_channel.drq_state = state;
LOGMASKED(LOG_SERIALDMA, "dma: drq for serial channel %d %s count 0x%04x\n",
channel, state ? "asserted" : "deasserted", dma_channel.control & SDMA_COUNT);
if (state && (dma_channel.control & SDMA_ENABLE))
m_serial_dma_timer->adjust(attotime::zero);
}
void interpro_ioga_device::serial_dma_addr_w(offs_t offset, u32 data, u32 mem_mask, int channel)
{
LOGMASKED(LOG_SERIALDMA, "dma: serial channel %d address 0x%08x mask 0x%08x (%s)\n",
channel, data, mem_mask, machine().describe_context());
COMBINE_DATA(&m_serial_dma_channel[channel].address);
}
void interpro_ioga_device::serial_dma_ctrl_w(offs_t offset, u32 data, u32 mem_mask, int channel)
{
serial_dma_channel_t &dma_channel = m_serial_dma_channel[channel];
LOGMASKED(LOG_SERIALDMA, "dma: serial channel %d control 0x%08x mask 0x%08x (%s)\n",
channel, data, mem_mask, machine().describe_context());
COMBINE_DATA(&dma_channel.control);
if (dma_channel.control & SDMA_ENABLE)
m_serial_dma_timer->adjust(attotime::zero);
}
/*
* Bus arbitration and control
*/
void interpro_ioga_device::arbctl_w(u16 data)
{
LOGMASKED(LOG_DMA | LOG_SERIALDMA, "dma: arbctl = 0x%04x (%s)\n",
data, machine().describe_context());
m_arbctl = data;
// trigger serial dma waiting for bus access
if (m_arbctl & (ARBCTL_BGR_SER0 | ARBCTL_BGR_SER1 | ARBCTL_BGR_SER2))
m_serial_dma_timer->adjust(attotime::zero);
}
u32 interpro_ioga_device::error_businfo_r()
{
const u32 result = m_error_businfo;
// clear register after reading
if (!machine().side_effects_disabled())
m_error_businfo = 0;
return result;
}
/*
* Timers
*/
u32 interpro_ioga_device::timer0_r(offs_t offset, u32 mem_mask)
{
LOGMASKED(LOG_TIMERRD, "timer0_r data 0x%08x mask 0x%08x (%s)\n",
m_timer0_count, mem_mask, machine().describe_context());
return m_timer0_count;
}
void interpro_ioga_device::timer0_w(offs_t offset, u32 data, u32 mem_mask)
{
LOGMASKED(LOG_TIMER0, "timer0_w data 0x%08x mask 0x%08x prescaler 0x%08x (%s)\n",
data, mem_mask, m_prescaler, machine().describe_context());
// store the timer count value
m_timer0_count = data;
// restart the timer
attotime period = attotime::from_ticks(m_prescaler ? m_prescaler & 0xffff : 0x10000, 10_MHz_XTAL);
m_timer0->adjust(period, 0, period);
}
TIMER_CALLBACK_MEMBER(interpro_ioga_device::timer0)
{
m_timer0_count++;
// check if the timer has expired
if (m_timer0_count == 0)
{
// stop the timer
m_timer0->enable(false);
// raise an interrupt
set_int_line(IRQ_TIMER0, ASSERT_LINE);
set_int_line(IRQ_TIMER0, CLEAR_LINE);
}
}
TIMER_CALLBACK_MEMBER(interpro_ioga_device::timer_60hz)
{
set_int_line(IRQ_60HZ, ASSERT_LINE);
set_int_line(IRQ_60HZ, CLEAR_LINE);
}
TIMER_CALLBACK_MEMBER(sapphire_ioga_device::timer_60hz)
{
set_int_line(IRQ_TIMER0, ASSERT_LINE);
set_int_line(IRQ_TIMER0, CLEAR_LINE);
}
u32 interpro_ioga_device::timer1_r(offs_t offset, u32 mem_mask)
{
u32 result = m_timer1_count & TIMER1_COUNT;
// set the start bit if the timer is currently enabled
if (m_timer1->enabled())
result |= TIMER1_START;
else if (m_timer1->param())
result |= TIMER1_EXPIRED;
LOGMASKED(LOG_TIMERRD, "timer1_r data 0x%08x mask 0x%08x (%s)\n",
result, mem_mask, machine().describe_context());
return result;
}
void interpro_ioga_device::timer1_w(offs_t offset, u32 data, u32 mem_mask)
{
// disable the timer
m_timer1->enable(false);
// store the timer count value
m_timer1_count = data & TIMER1_COUNT;
// start the timer if necessary
if (data & TIMER1_START)
{
LOGMASKED(LOG_TIMER1, "timer1_w data 0x%08x mask 0x%08x prescaler 0x%08x (%s)\n",
data, mem_mask, m_prescaler, machine().describe_context());
attotime period = attotime::from_ticks(m_prescaler ? m_prescaler & 0xffff : 0x10000, 10_MHz_XTAL);
m_timer1->adjust(period, 0, period);
}
}
TIMER_CALLBACK_MEMBER(interpro_ioga_device::timer1)
{
// decrement timer count value
m_timer1_count--;
// check if timer has expired
if (m_timer1_count == 0)
{
LOGMASKED(LOG_TIMER1, "timer1 expired\n");
// disable timer and set the zero flag
m_timer1->enable(false);
m_timer1->set_param(1);
// raise an interrupt
set_int_line(IRQ_TIMER1, ASSERT_LINE);
set_int_line(IRQ_TIMER1, CLEAR_LINE);
}
}
u32 sapphire_ioga_device::timer2_count_r(offs_t offset, u32 mem_mask)
{
LOGMASKED(LOG_TIMERRD, "timer2_count_r data 0x%08x mask 0x%08x (%s)\n",
m_timer2_count, mem_mask, machine().describe_context());
return m_timer2_count;
}
void sapphire_ioga_device::timer2_count_w(offs_t offset, u32 data, u32 mem_mask)
{
m_timer2_count = data;
LOGMASKED(LOG_TIMER2, "timer2_count_w data 0x%08x mask 0x%08x (%s)\n",
data, mem_mask, machine().describe_context());
}
u32 sapphire_ioga_device::timer2_value_r(offs_t offset, u32 mem_mask)
{
LOGMASKED(LOG_TIMERRD, "timer2_value_r data 0x%08x mask 0x%08x (%s)\n",
m_timer2_value, mem_mask, machine().describe_context());
return m_timer2_value;
}
void sapphire_ioga_device::timer2_value_w(offs_t offset, u32 data, u32 mem_mask)
{
m_timer2_value = data;
m_timer2_count = data; // ?
LOGMASKED(LOG_TIMER2, "timer2_value_w data 0x%08x mask 0x%08x (%s)\n",
data, mem_mask, machine().describe_context());
}
u32 sapphire_ioga_device::timer3_r(offs_t offset, u32 mem_mask)
{
u32 result = m_timer3_count & TIMER3_COUNT;
if (m_timer3->enabled())
result |= TIMER3_START;
else if (m_timer3->param())
result |= TIMER3_EXPIRED;
LOGMASKED(LOG_TIMERRD, "timer3_r data 0x%08x mask 0x%08x (%s)\n",
result, mem_mask, machine().describe_context());
return result;
}
void sapphire_ioga_device::timer3_w(offs_t offset, u32 data, u32 mem_mask)
{
// stop the timer so it won't trigger while we're fiddling with it
m_timer3->enable(false);
// write the new value to the timer register
m_timer3_count = data & TIMER3_COUNT;
// start the timer if necessary
if (data & TIMER3_START)
{
LOGMASKED(LOG_TIMER3, "timer3_w data 0x%08x mask 0x%08x (%s)\n",
data, mem_mask, machine().describe_context());
// theory: timer 3 is 12.5MHz (typical value of 12500 giving a delay of 1ms)
m_timer3->adjust(attotime::zero, false, attotime::from_hz(XTAL(12'500'000)));
}
}
TIMER_CALLBACK_MEMBER(sapphire_ioga_device::timer3)
{
// decrement timer count value
m_timer3_count--;
// check for expiry
if (m_timer3_count == 0)
{
LOGMASKED(LOG_TIMER3, "timer3 expired\n");
// disable timer and set the zero flag
m_timer3->enable(false);
m_timer3->set_param(true);
// raise an interrupt
set_int_line(IRQ_TIMER3, ASSERT_LINE);
}
}
u32 interpro_ioga_device::prescaler_r()
{
return (m_prescaler ^ 0xffff0000) - 0x10000;
}
void interpro_ioga_device::prescaler_w(offs_t offset, u32 data, u32 mem_mask)
{
LOGMASKED(LOG_TIMER0 | LOG_TIMER1, "prescaler_w data 0x%08x mask 0x%08x (%s)\n",
data, mem_mask, machine().describe_context());
COMBINE_DATA(&m_prescaler);
}
/*
* Mouse
*/
u32 interpro_ioga_device::mouse_status_r(offs_t offset, u32 mem_mask)
{
const u32 result = m_mouse_status ^ MOUSE_BUTTONS;
LOGMASKED(LOG_MOUSE, "mouse_status_r status 0x%08x mask 0x%08x (%s)\n",
result, mem_mask, machine().describe_context());
// clear xpos and ypos fields and interrupt
if (!machine().side_effects_disabled())
{
if (mem_mask & MOUSE_XPOS)
m_mouse_status &= ~(MOUSE_XPOS);
if (mem_mask & MOUSE_YPOS)
m_mouse_status &= ~(MOUSE_YPOS);
set_int_line(IRQ_MOUSE, CLEAR_LINE);
}
return result;
}
void interpro_ioga_device::mouse_status_w(offs_t offset, u32 data, u32 mem_mask)
{
LOGMASKED(LOG_MOUSE, "mouse_status_w status 0x%08x mask 0x%08x\n",
data, mem_mask);
COMBINE_DATA(&m_mouse_status);
set_int_line(IRQ_MOUSE, ASSERT_LINE);
}
/*
* Ethernet
*/
void emerald_ioga_device::eth_base_w(offs_t offset, u32 data, u32 mem_mask)
{
LOGMASKED(LOG_NETWORK, "eth: base_w 0x%08x mem_mask 0x%08x (%s)\n",
data, mem_mask, machine().describe_context());
m_eth_base = ((m_eth_base & ~mem_mask) | (data & mem_mask)) & ETH_BASE_MASK;
}
void emerald_ioga_device::eth_control_w(offs_t offset, u16 data, u16 mem_mask)
{
LOGMASKED(LOG_NETWORK, "eth: control_w 0x%04x mem_mask 0x%04x (%s)\n",
data, mem_mask, machine().describe_context());
m_eth_control = data;
// ethernet device reset (active low)
if ((data & ETH_RESET) == 0)
{
// reset the ethernet device
siblingdevice("eth")->reset();
// clear the reset flag (the 250ns delay is long enough to pass diagnostic tests)
m_eth_reset_timer->adjust(attotime::from_nsec(250));
}
// ethernet channel attention
if (data & ETH_CA)
{
m_eth_ca_func(ASSERT_LINE);
m_eth_ca_func(CLEAR_LINE);
}
}
void emerald_ioga_device::eth_w(offs_t offset, u16 data, u16 mem_mask)
{
const u32 address = m_eth_base | ((offset << 1) & ~ETH_BASE_MASK);
LOGMASKED(LOG_NETWORK, "eth_w address 0x%08x mask 0x%04x data 0x%04x\n",
address, mem_mask, data);
m_memory.write_word(address, data, mem_mask);
}
u16 emerald_ioga_device::eth_r(offs_t offset, u16 mem_mask)
{
const u32 address = m_eth_base | ((offset << 1) & ~ETH_BASE_MASK);
const u16 data = m_memory.read_word(address, mem_mask);
LOGMASKED(LOG_NETWORK, "eth_r 0x%08x mask 0x%04x data 0x%04x\n",
address, mem_mask, data);
return data;
}
TIMER_CALLBACK_MEMBER(emerald_ioga_device::eth_reset)
{
LOGMASKED(LOG_NETWORK, "eth: reset flag cleared\n");
// clear ethernet reset flag
m_eth_control &= ~ETH_RESET;
}
void turquoise_ioga_device::eth_base_w(offs_t offset, u32 data, u32 mem_mask)
{
LOGMASKED(LOG_NETWORK, "eth: base_w 0x%08x mem_mask 0x%08x (%s)\n",
data, mem_mask, machine().describe_context());
m_eth_base = ((m_eth_base & ~mem_mask) | (data & mem_mask)) & ETH_BASE_MASK;
}
void turquoise_ioga_device::eth_control_w(offs_t offset, u16 data, u16 mem_mask)
{
LOGMASKED(LOG_NETWORK, "eth: control_w 0x%04x mem_mask 0x%04x (%s)\n",
data, mem_mask, machine().describe_context());
m_eth_control = data;
// ethernet device reset (active low)
if ((data & ETH_RESET) == 0)
{
// reset the ethernet device
siblingdevice("eth")->reset();
// clear the reset flag (the 250ns delay is long enough to pass diagnostic tests)
m_eth_reset_timer->adjust(attotime::from_nsec(250));
}
// ethernet channel attention
if (data & ETH_CA)
{
m_eth_ca_func(ASSERT_LINE);
m_eth_ca_func(CLEAR_LINE);
}
}
void turquoise_ioga_device::eth_w(offs_t offset, u16 data, u16 mem_mask)
{
const u32 address = m_eth_base | ((offset << 1) & ~ETH_BASE_MASK);
LOGMASKED(LOG_NETWORK, "eth_w address 0x%08x mask 0x%04x data 0x%04x\n",
address, mem_mask, data);
m_memory.write_word(address, data, mem_mask);
}
u16 turquoise_ioga_device::eth_r(offs_t offset, u16 mem_mask)
{
const u32 address = m_eth_base | ((offset << 1) & ~ETH_BASE_MASK);
const u16 data = m_memory.read_word(address, mem_mask);
LOGMASKED(LOG_NETWORK, "eth_r 0x%08x mask 0x%04x data 0x%04x\n",
address, mem_mask, data);
return data;
}
TIMER_CALLBACK_MEMBER(turquoise_ioga_device::eth_reset)
{
LOGMASKED(LOG_NETWORK, "eth: reset flag cleared\n");
// clear ethernet reset flag
m_eth_control &= ~ETH_RESET;
}
void sapphire_ioga_device::eth_remap_w(u32 data)
{
LOGMASKED(LOG_NETWORK, "eth: remap = 0x%08x (%s)\n",
data, machine().describe_context());
m_eth_remap = data & ~0xf;
}
void sapphire_ioga_device::eth_mappg_w(u32 data)
{
LOGMASKED(LOG_NETWORK, "eth: map page = 0x%08x (%s)\n",
data, machine().describe_context());
m_eth_mappg = data & ~0xf;
}
u32 sapphire_ioga_device::eth_control_r()
{
LOGMASKED(LOG_NETWORK, "eth: control_r 0x%08x (%s)\n",
m_eth_control, machine().describe_context());
return m_eth_control;
}
void sapphire_ioga_device::eth_control_w(offs_t offset, u32 data, u32 mem_mask)
{
LOGMASKED(LOG_NETWORK, "eth: control_w 0x%08x mem_mask 0x%08x (%s)\n",
data, mem_mask, machine().describe_context());
m_eth_control = data & ETH_MASK;
// ethernet device reset
if ((data & ETH_RESET) == 0)
{
// reset the ethernet device
siblingdevice("eth")->reset();
// clear the reset flag (the 250ns delay is long enough to pass diagnostic tests)
m_eth_reset_timer->adjust(attotime::from_nsec(250));
}
// ethernet channel attention
if (data & ETH_CA)
{
m_eth_ca_func(ASSERT_LINE);
m_eth_ca_func(CLEAR_LINE);
}
}
void sapphire_ioga_device::eth_w(offs_t offset, u16 data, u16 mem_mask)
{
// top two bits give channel (0=A, 4=B, 8=C, f=?)
const int channel = offset >> 29;
u32 address = (offset << 1) & 0x3fffffff;
if ((m_eth_control & ETH_MAPEN) && (address & ETH_MAPPG) == (m_eth_mappg & ETH_MAPPG))
{
address &= ~(m_eth_mappg & ETH_MAPPG);
address |= (m_eth_remap & ETH_REMAP_ADDR);
LOGMASKED(LOG_NETWORK, "eth_w address 0x%08x remapped 0x%08x\n",
offset << 1, address);
}
LOGMASKED(LOG_NETWORK, "eth_w channel %c address 0x%08x mask 0x%08x data 0x%04x\n",
channel + 'A', address, mem_mask, data);
m_memory.write_word(address, data, mem_mask);
}
u16 sapphire_ioga_device::eth_r(offs_t offset, u16 mem_mask)
{
// top two bits give channel (0=A, 4=B, 8=C, f=?)
const int channel = offset >> 29;
u32 address = (offset << 1) & 0x3fffffff;
if ((m_eth_control & ETH_MAPEN) && (address & ETH_MAPPG) == (m_eth_mappg & ETH_MAPPG))
{
address &= ~(m_eth_mappg & ETH_MAPPG);
address |= (m_eth_remap & ETH_REMAP_ADDR);
address &= 0x3fffffff;
LOGMASKED(LOG_NETWORK, "eth_r address 0x%08x remapped 0x%08x\n",
offset << 1, address);
}
u16 data = m_memory.read_word(address, mem_mask);
LOGMASKED(LOG_NETWORK, "eth_r channel %c address 0x%08x mask 0x%08x data 0x%04x\n",
channel + 'A', address, mem_mask, data);
return data;
}
TIMER_CALLBACK_MEMBER(sapphire_ioga_device::eth_reset)
{
LOGMASKED(LOG_NETWORK, "eth: reset flag cleared\n");
// clear ethernet reset flag
m_eth_control &= ~ETH_RESET;
}