diff options
Diffstat (limited to 'src/mame/machine/interpro_ioga.cpp')
-rw-r--r-- | src/mame/machine/interpro_ioga.cpp | 691 |
1 files changed, 691 insertions, 0 deletions
diff --git a/src/mame/machine/interpro_ioga.cpp b/src/mame/machine/interpro_ioga.cpp new file mode 100644 index 00000000000..67a47f2cfd0 --- /dev/null +++ b/src/mame/machine/interpro_ioga.cpp @@ -0,0 +1,691 @@ +// license:BSD-3-Clause +// copyright-holders:Patrick Mackinlay + +/* + * An implementation of the IOGA device found on Intergraph InterPro family workstations. There is no + * public documentation on this device, so the implementation is being built to follow the logic of the + * system boot ROM and its diagnostic tests. + * + * The device handles most of the I/O for the system, including timers, interrupts, DMA and target device + * interfacing. There remains a significant amount of work to be completed before the boot diagnostics will + * pass without errors, let alone successfully booting CLIX. + * + * Please be aware that code in here is not only broken, it's likely wrong in many cases. + * + * TODO + * - too long to list + */ +#include "interpro_ioga.h" + +#define VERBOSE 0 +#if VERBOSE +#define LOG_TIMER_MASK 0xff +#define LOG_TIMER(timer, ...) if (LOG_TIMER_MASK & (1 << timer)) logerror(__VA_ARGS__) +#define LOG_INTERRUPT(...) logerror(__VA_ARGS__) +#define LOG_IOGA(...) logerror(__VA_ARGS__) +#define LOG_DMA(...) logerror(__VA_ARGS__) +#else +#define LOG_TIMER_MASK 0x00 +#define LOG_TIMER(timer, ...) +#define LOG_INTERRUPT(...) +#define LOG_IOGA(...) +#define LOG_DMA(...) +#endif + +DEVICE_ADDRESS_MAP_START(map, 32, interpro_ioga_device) + AM_RANGE(0x0c, 0x1b) AM_READWRITE(dma_plotter_r, dma_plotter_w) + AM_RANGE(0x1c, 0x1f) AM_READWRITE(dma_plotter_eosl_r, dma_plotter_eosl_w) + AM_RANGE(0x20, 0x2f) AM_READWRITE(dma_scsi_r, dma_scsi_w) + AM_RANGE(0x30, 0x3f) AM_READWRITE(dma_floppy_r, dma_floppy_w) + + AM_RANGE(0x5c, 0x7f) AM_READWRITE16(icr_r, icr_w, 0xffffffff) + AM_RANGE(0x80, 0x83) AM_READWRITE16(icr18_r, icr18_w, 0x0000ffff) + AM_RANGE(0x80, 0x83) AM_READWRITE8(softint_r, softint_w, 0x00ff0000) + AM_RANGE(0x80, 0x83) AM_READWRITE8(nmictrl_r, nmictrl_w, 0xff000000) + + AM_RANGE(0x88, 0x8b) AM_READWRITE(timer_prescaler_r, timer_prescaler_w) + AM_RANGE(0x8c, 0x8f) AM_READWRITE(timer0_r, timer0_w) + AM_RANGE(0x90, 0x93) AM_READWRITE(timer1_r, timer1_w) + AM_RANGE(0x94, 0x97) AM_READ(error_address_r) + AM_RANGE(0x98, 0x9b) AM_READ(error_businfo_r) + AM_RANGE(0x9c, 0x9f) AM_READWRITE16(arbctl_r, arbctl_w, 0x0000ffff) + + AM_RANGE(0xa8, 0xab) AM_READWRITE(timer3_r, timer3_w) + + AM_RANGE(0xb0, 0xbf) AM_READWRITE16(softint_vector_r, softint_vector_w, 0xffffffff) +ADDRESS_MAP_END + +const device_type INTERPRO_IOGA = &device_creator<interpro_ioga_device>; + +interpro_ioga_device::interpro_ioga_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) + : device_t(mconfig, INTERPRO_IOGA, "InterPro IOGA", tag, owner, clock, "ioga", __FILE__), + m_out_nmi_func(*this), + m_out_int_func(*this), + m_memory_space(nullptr), + m_dma_channel{ + { 0,0,0,0,false, 0, {*this}, {*this} }, + { 0,0,0,0,false, 0, {*this}, {*this} }, + { 0,0,0,0,false, 0, {*this}, {*this} }, + { 0,0,0,0,false, 0, {*this}, {*this} } }, + m_fdc_tc_func(*this) +{ +} + +void interpro_ioga_device::device_start() +{ + // resolve callbacks + m_out_nmi_func.resolve(); + m_out_int_func.resolve(); + + // TODO: parameterise the cammu name and space number + // grab the main memory space from the mmu so we can do DMA to/from it + device_memory_interface *mmu; + siblingdevice("mmu")->interface(mmu); + m_memory_space = &mmu->space(AS_0); + + for (int i = 0; i < IOGA_DMA_CHANNELS; i++) + { + m_dma_channel[i].device_r.resolve_safe(0xff); + m_dma_channel[i].device_w.resolve(); + } + + m_fdc_tc_func.resolve(); + + // allocate ioga timers + m_timer[0] = timer_alloc(IOGA_TIMER_0); + m_timer[1] = timer_alloc(IOGA_TIMER_1); + m_timer[2] = timer_alloc(IOGA_TIMER_2); + m_timer[3] = timer_alloc(IOGA_TIMER_3); + + for (auto & elem : m_timer) + elem->enable(false); + + // allocate timer for DMA controller + m_dma_timer = timer_alloc(IOGA_TIMER_DMA); + m_dma_timer->adjust(attotime::never); +} + +void interpro_ioga_device::device_reset() +{ + m_nmi_pending = false; + + m_interrupt_active = 0; + m_irq_forced = 0; + + // configure timer 0 at 60Hz + m_timer_reg[0] = 0; + m_timer[0]->adjust(attotime::zero, IOGA_TIMER_0, attotime::from_hz(60)); +} + +/****************************************************************************** + Timers +******************************************************************************/ +READ32_MEMBER(interpro_ioga_device::timer1_r) +{ + uint32_t result = m_timer1_count & IOGA_TIMER1_VMASK; + + // set the start bit if the timer is currently enabled + if (m_timer[1]->enabled()) + result |= IOGA_TIMER1_START; + else if (m_timer[1]->param()) + result |= IOGA_TIMER1_EXPIRED; + + return result; +} + +READ32_MEMBER(interpro_ioga_device::timer3_r) +{ + uint32_t result = m_timer3_count & IOGA_TIMER3_VMASK; + + if (m_timer[3]->enabled()) + result |= IOGA_TIMER3_START; + else if (m_timer[3]->param()) + result |= IOGA_TIMER3_EXPIRED; + + return result; +} + +void interpro_ioga_device::write_timer(int timer, uint32_t value, device_timer_id id) +{ + switch (id) + { + case IOGA_TIMER_1: + // disable the timer + m_timer[timer]->enable(false); + + // store the timer count value + m_timer1_count = value; + + // start the timer if necessary + if (value & IOGA_TIMER1_START) + { + LOG_TIMER(1, "timer 1: started prescaler %d value %d\n", m_prescaler & 0x7fff, value & IOGA_TIMER1_VMASK); + + // FIXME: this division by 50 is sufficient to pass iogadiag timer 1 tests + m_timer[timer]->adjust(attotime::zero, false, attotime::from_usec((m_prescaler & 0x7fff) / 50)); + } + break; + + case IOGA_TIMER_3: + // stop the timer so it won't trigger while we're fiddling with it + m_timer[timer]->enable(false); + + // write the new value to the timer register + m_timer3_count = value & IOGA_TIMER3_VMASK; + + // start the timer if necessary + if (value & IOGA_TIMER3_START) + { + LOG_TIMER(3, "timer 3: started value %d\n", value & IOGA_TIMER3_VMASK); + + m_timer[timer]->adjust(attotime::zero, false, attotime::from_hz(XTAL_25MHz)); + } + break; + + default: + // save the value + m_timer_reg[timer] = value; + + // timer_set(attotime::from_usec(500), id); + + LOG_TIMER(0xf, "timer %d: set to 0x%x (%d)\n", timer, m_timer_reg[timer], m_timer_reg[timer]); + break; + } +} + +void interpro_ioga_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) +{ + switch (id) + { + case IOGA_TIMER_0: + m_timer_reg[0]++; + set_irq_line(IOGA_TIMER0_IRQ, ASSERT_LINE); + break; + + case IOGA_TIMER_1: + // decrement timer count value + m_timer1_count--; + + // check if timer has expired + if (m_timer1_count == 0) + { + LOG_TIMER(1, "timer 1: stopped\n"); + + // disable timer and set the zero flag + timer.enable(false); + timer.set_param(true); + + // throw an interrupt + set_irq_line(IOGA_TIMER1_IRQ, ASSERT_LINE); + } + break; + + case IOGA_TIMER_3: + // decrement timer count value + m_timer3_count--; + + // check for expiry + if (m_timer3_count == 0) + { + LOG_TIMER(3, "timer 3: stopped\n"); + + // disable timer and set the zero flag + timer.enable(false); + timer.set_param(true); + + // throw an interrupt + set_irq_line(IOGA_TIMER3_IRQ, ASSERT_LINE); + } + break; + + case IOGA_TIMER_DMA: + // transfer data between device and main memory + + // TODO: figure out what indicates dma write (memory -> device) + // TODO: implement multiple dma channels + // TODO: virtual memory? + + if (!m_dma_channel[param].dma_active) + { + LOG_DMA("dma: transfer started, channel = %d, control 0x%08x, real address 0x%08x count 0x%08x\n", + param, m_dma_channel[param].control, m_dma_channel[param].real_address, m_dma_channel[param].transfer_count); + m_dma_channel[param].dma_active = true; + } + + // while the device is requesting a data transfer and the transfer count is not zero + while (m_dma_channel[param].drq_state && m_dma_channel[param].transfer_count) + { + // transfer a byte between device and memory + if (true) + m_memory_space->write_byte(m_dma_channel[param].real_address, m_dma_channel[param].device_r()); + else + m_dma_channel[param].device_w(m_memory_space->read_byte(m_dma_channel[param].real_address)); + + // increment addresses and decrement count + m_dma_channel[param].real_address++; + m_dma_channel[param].virtual_address++; + m_dma_channel[param].transfer_count--; + } + + // if there are no more bytes remaining, terminate the transfer + if (m_dma_channel[param].transfer_count == 0) + { + LOG_DMA("dma: transfer stopped, control 0x%08x, real address 0x%08x count 0x%08x\n", + m_dma_channel[param].control, m_dma_channel[param].fdc_real_address, m_dma_channel[param].transfer_count); + + if (param == IOGA_DMA_FLOPPY) + { + LOG_DMA("dma: asserting fdc terminal count line\n"); + + m_fdc_tc_func(ASSERT_LINE); + m_fdc_tc_func(CLEAR_LINE); + } + + m_dma_channel[param].dma_active = false; + } + break; + } +} + +/****************************************************************************** + Interrupts +******************************************************************************/ + +static const uint16_t irq_enable_mask[IOGA_INTERRUPT_COUNT] = +{ + IOGA_INTERRUPT_ENABLE_EXTERNAL, + IOGA_INTERRUPT_ENABLE_EXTERNAL, + IOGA_INTERRUPT_ENABLE_EXTERNAL | IOGA_INTERRUPT_ENABLE_INTERNAL, // external interrupt 0: SCSI + IOGA_INTERRUPT_ENABLE_EXTERNAL | IOGA_INTERRUPT_ENABLE_INTERNAL, // external interrupt 1: floppy + IOGA_INTERRUPT_ENABLE_EXTERNAL | IOGA_INTERRUPT_ENABLE_INTERNAL, // external interrupt 2: plotter + IOGA_INTERRUPT_ENABLE_EXTERNAL, + IOGA_INTERRUPT_ENABLE_EXTERNAL, + IOGA_INTERRUPT_ENABLE_EXTERNAL, + + IOGA_INTERRUPT_ENABLE_EXTERNAL, + IOGA_INTERRUPT_ENABLE_EXTERNAL, + IOGA_INTERRUPT_ENABLE_EXTERNAL, + IOGA_INTERRUPT_ENABLE_EXTERNAL, + IOGA_INTERRUPT_ENABLE_EXTERNAL, + IOGA_INTERRUPT_ENABLE_EXTERNAL, + IOGA_INTERRUPT_ENABLE_EXTERNAL, + IOGA_INTERRUPT_ENABLE_EXTERNAL, + + // internal interrupt 5: serial DMA - one interrupt enable per DMA channel + IOGA_INTERRUPT_ENABLE_EXTERNAL << 0 | IOGA_INTERRUPT_ENABLE_EXTERNAL << 1 | IOGA_INTERRUPT_ENABLE_EXTERNAL << 2, + IOGA_INTERRUPT_ENABLE_EXTERNAL, + IOGA_INTERRUPT_ENABLE_EXTERNAL | IOGA_INTERRUPT_ENABLE_INTERNAL // external interrupt 12: Ethernet +}; + +void interpro_ioga_device::set_nmi_line(int state) +{ + switch (state) + { + case ASSERT_LINE: + + LOG_INTERRUPT("nmi: ctrl = 0x%02x\n", m_nmictrl); + + if ((m_nmictrl & IOGA_NMI_ENABLE) == IOGA_NMI_ENABLE) + { + // if edge triggered mode, clear enable in + if (m_nmictrl & IOGA_NMI_EDGE) + m_nmictrl &= ~IOGA_NMI_ENABLE_IN; + + m_nmi_pending = true; + update_interrupt(ASSERT_LINE); + } + break; + + case CLEAR_LINE: + m_nmi_pending = false; + update_interrupt(ASSERT_LINE); + break; + } +} + +void interpro_ioga_device::set_irq_line(int irq, int state) +{ + LOG_INTERRUPT("set_irq_line(%d, %d)\n", irq, state); + switch (state) + { + case ASSERT_LINE: + if (m_int_vector[irq] & irq_enable_mask[irq]) + { + // set pending bit + m_int_vector[irq] |= IOGA_INTERRUPT_PENDING; + + // update irq line state + update_interrupt(state); + } + else + LOG_INTERRUPT("received disabled interrupt irq %d vector 0x%04x\n", irq, m_int_vector[irq]); + break; + + case CLEAR_LINE: + // clear pending bit + m_int_vector[irq] &= ~IOGA_INTERRUPT_PENDING; + + // update irq line state + update_interrupt(state); + break; + } +} + +void interpro_ioga_device::set_irq_soft(int irq, int state) +{ + LOG_INTERRUPT("set_irq_soft(%d, %d)\n", irq, state); + switch (state) + { + case ASSERT_LINE: + // set pending bit + if (irq < 8) + m_softint |= 1 << irq; + else + m_softint_vector[irq - 8] |= IOGA_INTERRUPT_PENDING; + + update_interrupt(state); + break; + + case CLEAR_LINE: + // clear pending bit + if (irq < 8) + m_softint &= ~(1 << irq); + else + m_softint_vector[irq - 8] &= ~IOGA_INTERRUPT_PENDING; + + // update irq line state + update_interrupt(state); + break; + } +} + +IRQ_CALLBACK_MEMBER(interpro_ioga_device::inta_cb) +{ + switch (irqline) + { + case INPUT_LINE_IRQ0: + // FIXME: clear pending bit - can't rely on device callbacks + switch (m_interrupt_active) + { + case IOGA_INTERRUPT_INTERNAL: + case IOGA_INTERRUPT_EXTERNAL: + m_int_vector[m_irq_current] &= ~IOGA_INTERRUPT_PENDING; + break; + + case IOGA_INTERRUPT_SOFT_LO: + m_softint &= ~(1 << m_irq_current); + break; + + case IOGA_INTERRUPT_SOFT_HI: + m_softint_vector[m_irq_current] &= ~IOGA_INTERRUPT_PENDING; + break; + } + + // clear irq line + update_interrupt(CLEAR_LINE); + + // fall through to return interrupt vector + case -1: + // return vector for current interrupt without clearing irq line + switch (m_interrupt_active) + { + case IOGA_INTERRUPT_EXTERNAL: + case IOGA_INTERRUPT_INTERNAL: + return m_int_vector[m_irq_current] & 0xff; + + case IOGA_INTERRUPT_SOFT_LO: + return 0x8f + m_irq_current * 0x10; + + case IOGA_INTERRUPT_SOFT_HI: + return m_softint_vector[m_irq_current] & 0xff; + } + break; + + case INPUT_LINE_NMI: + // clear pending flag + m_nmi_pending = false; + + // clear line + update_interrupt(CLEAR_LINE); + + // return vector + return 0; + } + + return 0; +} + +void interpro_ioga_device::update_interrupt(int state) +{ + switch (state) + { + case CLEAR_LINE: + if (m_interrupt_active) + { + // the cpu has acknowledged the active interrupt, deassert the nmi/irq line + m_interrupt_active == IOGA_INTERRUPT_NMI ? m_out_nmi_func(CLEAR_LINE) : m_out_int_func(CLEAR_LINE); + + // clear the active status + m_interrupt_active = 0; + } + // fall through to handle any pending interrupts + + case ASSERT_LINE: + // if an interrupt is currently active, don't do anything + if (m_interrupt_active == 0) + { + // check for pending nmi + if (m_nmi_pending) + { + m_interrupt_active = IOGA_INTERRUPT_NMI; + + m_out_nmi_func(ASSERT_LINE); + return; + } + + // check for any pending irq + for (int i = 0; i < IOGA_INTERRUPT_COUNT; i++) + { + if (m_int_vector[i] & IOGA_INTERRUPT_PENDING) + { + m_interrupt_active = IOGA_INTERRUPT_INTERNAL; // TODO: flag internal/external + m_irq_current = i; + + m_out_int_func(ASSERT_LINE); + return; + } + } + + // check for any pending soft interrupts (low type) + for (int i = 0; i < 8; i++) + { + if (m_softint & (1 << i)) + { + m_interrupt_active = IOGA_INTERRUPT_SOFT_LO; + m_irq_current = i; + + m_out_int_func(ASSERT_LINE); + return; + } + } + + // check for any pending soft interrupts (high type) + for (int i = 0; i < 8; i++) + { + if (m_softint_vector[i] & IOGA_INTERRUPT_PENDING) + { + m_interrupt_active = IOGA_INTERRUPT_SOFT_HI; + m_irq_current = i; + + m_out_int_func(ASSERT_LINE); + return; + } + } + } + break; + } +} + +WRITE16_MEMBER(interpro_ioga_device::icr_w) +{ + LOG_INTERRUPT("interrupt vector %d set to 0x%04x at pc 0x%08x\n", offset, data, space.device().safe_pc()); + + // FIXME: now that the interrupt handling only depends on IOGA_INTERRUPT_PENDING, we might be able + // to avoid this hack + if (data & IOGA_INTERRUPT_PENDING) + { + m_irq_forced |= 1 << offset; + m_int_vector[offset] = data & ~IOGA_INTERRUPT_PENDING; + } + else if (m_irq_forced & 1 << offset) + { + m_int_vector[offset] = data; + + // clear forced flag + m_irq_forced &= ~(1 << offset); + + // force an interrupt + set_irq_line(offset, ASSERT_LINE); + } + else + m_int_vector[offset] = data; +} + +WRITE8_MEMBER(interpro_ioga_device::softint_w) +{ + // save the existing value + uint8_t previous = m_softint; + + // store the written value + m_softint = data; + + // force soft interrupt for any bit written from 1 to 0 + for (int i = 0; i < 8; i++) + { + uint8_t mask = 1 << i; + + // check for transition from 1 to 0 and force a soft interrupt + if (previous & mask && !(data & mask)) + set_irq_soft(i, ASSERT_LINE); + } +} + +WRITE8_MEMBER(interpro_ioga_device::nmictrl_w) +{ + // save the existing value + uint8_t previous = m_nmictrl; + + // store the written value + m_nmictrl = data; + + // force an nmi when pending bit is written low + if (previous & IOGA_NMI_PENDING && !(data & IOGA_NMI_PENDING)) + set_nmi_line(ASSERT_LINE); +} + +WRITE16_MEMBER(interpro_ioga_device::softint_vector_w) +{ + // save the existing value + uint16_t previous = m_softint_vector[offset]; + + // store the written value + m_softint_vector[offset] = data; + + // check for transition from 1 to 0 and force a soft interrupt + if (previous & IOGA_INTERRUPT_PENDING && !(data & IOGA_INTERRUPT_PENDING)) + set_irq_soft(offset + 8, ASSERT_LINE); +} + +/****************************************************************************** + DMA +******************************************************************************/ +void interpro_ioga_device::drq(int state, int channel) +{ + // this member is called when the device has data ready for reading via dma + m_dma_channel[channel].drq_state = state; + + if (state) + { + // TODO: check if dma is enabled + m_dma_timer->adjust(attotime::zero, channel); + } +} +/* +0x94: error address reg: expect 0x7f200000 after bus error (from dma virtual address) +0x98: error cycle type: expect 0x52f0 (after failed dma?) + 0x5331 - forced berr with nmi/interrupts disabled? + 0xc2f0 + 0x62f0 +*/ +// TODO: 7.0266 - forced BERR not working + +uint32_t interpro_ioga_device::dma_r(address_space &space, offs_t offset, uint32_t mem_mask, int channel) +{ + switch (offset) + { + case 0: + return m_dma_channel[channel].real_address; + + case 1: + return m_dma_channel[channel].virtual_address; + + case 2: + return m_dma_channel[channel].transfer_count; + + case 3: + return m_dma_channel[channel].control; + } + + logerror("dma_r: unknown channel %d\n", channel); + return 0; +} + +void interpro_ioga_device::dma_w(address_space &space, offs_t offset, uint32_t data, uint32_t mem_mask, int channel) +{ + switch (offset) + { + case 0: + m_dma_channel[channel].real_address = data; + break; + + case 1: + m_dma_channel[channel].virtual_address = data & ~0x3; + break; + + case 2: + m_dma_channel[channel].transfer_count = data; + break; + + case 3: + m_dma_channel[channel].control = data & IOGA_DMA_CTRL_WMASK; + + logerror("dma: channel = %d, control = 0x%08x, ra = 0x%08x, va = 0x%08x, tc = 0x%08x\n", + channel, data, m_dma_channel[channel].real_address, m_dma_channel[channel].virtual_address, m_dma_channel[channel].transfer_count); + + // iogadiag test 7.0265 + if (data == IOGA_DMA_CTRL_START) + { + uint32_t mask = 0; + + switch (channel) + { + case IOGA_DMA_PLOTTER: + mask = IOGA_ARBCTL_BGR_PLOT; + break; + + case IOGA_DMA_SCSI: + mask = IOGA_ARBCTL_BGR_SCSI; + break; + + case IOGA_DMA_FLOPPY: + mask = IOGA_ARBCTL_BGR_FDC; + break; + } + + // if bus grant is not enabled, set the busy flag + if (!(m_arbctl & mask)) + m_dma_channel[channel].control |= IOGA_DMA_CTRL_BUSY; + } + break; + } +} |