diff options
author | 2021-01-12 13:39:14 +0100 | |
---|---|---|
committer | 2021-01-12 23:39:14 +1100 | |
commit | 5a57d03cfe07bf47bc6862f403fb811bebbfa956 (patch) | |
tree | 21dbf21537b820694af31d6897f091838b08c280 /src/devices/machine/acorn_ioc.cpp | |
parent | fad7c9e0beb50f6c96da9ea8f93082bc349a25c2 (diff) |
-Acorn Archimedes code reorganization: (#7627)
* Separated Acorn IOC and MEMC into devices.
* Emulated 8051-based serial keyboard.
* acorn_machine/memc.cpp: Ensure only one logical page is mapped to a single physical page.
* Fixed RISC OS POST IOC register test.
* aa310.cpp: Added debug code to display RISC OS POST failures.
-machine/archimedes_keyb.cpp: Dumped Acorn Archimedes keyboard microcontroller. [Phil Pemberton]
Diffstat (limited to 'src/devices/machine/acorn_ioc.cpp')
-rw-r--r-- | src/devices/machine/acorn_ioc.cpp | 411 |
1 files changed, 411 insertions, 0 deletions
diff --git a/src/devices/machine/acorn_ioc.cpp b/src/devices/machine/acorn_ioc.cpp new file mode 100644 index 00000000000..1a879cb7a2d --- /dev/null +++ b/src/devices/machine/acorn_ioc.cpp @@ -0,0 +1,411 @@ +// license:LGPL-2.1+ +// copyright-holders:Angelo Salese, R. Belmont, Juergen Buchmueller, Sandro Ronco +/************************************************************************************************** + + Acorn RISC Machine Input/Output Controller (IOC) + + TODO: + - support IOEB used in the ARM250 (partially implemented in aristmk5.cpp) + +**************************************************************************************************/ + +#include "emu.h" +#include "acorn_ioc.h" + +//#define VERBOSE 1 +#include "logmacro.h" + + +DEFINE_DEVICE_TYPE(ACORN_IOC, acorn_ioc_device, "ioc", "Acorn IOC") + +//************************************************************************** +// LIVE DEVICE +//************************************************************************** + +acorn_ioc_device::acorn_ioc_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) + : device_t(mconfig, ACORN_IOC, tag, owner, clock) + , device_serial_interface(mconfig, *this) + , m_peripherals_r(*this) + , m_peripherals_w(*this) + , m_giop_r(*this) + , m_giop_w(*this) + , m_irq_w(*this) + , m_fiq_w(*this) + , m_kout_w(*this) + , m_baud_w(*this) +{ +} + +void acorn_ioc_device::device_resolve_objects() +{ + m_peripherals_r.resolve_all_safe(0xffffffff); + m_peripherals_w.resolve_all_safe(); + m_giop_r.resolve_all_safe(1); + m_giop_w.resolve_all_safe(); + m_irq_w.resolve_safe(); + m_fiq_w.resolve_safe(); + m_kout_w.resolve_safe(); + m_baud_w.resolve(); +} + +void acorn_ioc_device::device_start() +{ + for (int i=0; i <4; i++) + m_timers[i] = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(acorn_ioc_device::timer_tick), this)); + + save_item(NAME(m_ir)); + save_item(NAME(m_if)); + save_item(NAME(m_baud)); + save_item(NAME(m_timercnt)); + save_item(NAME(m_timerout)); + save_item(NAME(m_regs)); +} + +void acorn_ioc_device::device_reset() +{ + std::fill(std::begin(m_regs), std::end(m_regs), 0); + m_regs[IRQ_STATUS_A] = 0x10 | 0x80; // set up POR (Power On Reset) and Force IRQ at start-up + m_regs[IRQ_STATUS_B] = 0x40; // set up KART Tx empty + m_regs[FIQ_STATUS] = 0x80; // set up Force FIQ + + m_ir = CLEAR_LINE; + m_if = CLEAR_LINE; + m_baud = CLEAR_LINE; + + // KART interface + set_data_frame(1, 8, PARITY_NONE, STOP_BITS_2); + set_rate(31250); + + for (int i = 0; i < 6; i++) + m_giop_w[i](1); + + for (int i=0; i < 2; i++) + { + m_timercnt[i] = 0; + m_timerout[i] = 0; + set_timer(i); + } + + transmit_register_reset(); + receive_register_reset(); +} + +void acorn_ioc_device::map(address_map &map) +{ + // Typical configuration + // IOA[2:6] --> A[2:6] Internal registers address + // IOA[16:18] --> B[0:2] Peripherals select + // IOA[19:20] --> T[0:1] Peripherals access timing + // IOA[21] --> CS Chip select + map(0x00200000, 0x0020007f).mirror(0x0018ff80).rw(FUNC(acorn_ioc_device::registers_r), FUNC(acorn_ioc_device::registers_w)); + map(0x00210000, 0x0021ffff).select(0x00180000).rw(FUNC(acorn_ioc_device::periph_r<1>), FUNC(acorn_ioc_device::periph_w<1>)); + map(0x00220000, 0x0022ffff).select(0x00180000).rw(FUNC(acorn_ioc_device::periph_r<2>), FUNC(acorn_ioc_device::periph_w<2>)); + map(0x00230000, 0x0023ffff).select(0x00180000).rw(FUNC(acorn_ioc_device::periph_r<3>), FUNC(acorn_ioc_device::periph_w<3>)); + map(0x00240000, 0x0024ffff).select(0x00180000).rw(FUNC(acorn_ioc_device::periph_r<4>), FUNC(acorn_ioc_device::periph_w<4>)); + map(0x00250000, 0x0025ffff).select(0x00180000).rw(FUNC(acorn_ioc_device::periph_r<5>), FUNC(acorn_ioc_device::periph_w<5>)); + map(0x00260000, 0x0026ffff).select(0x00180000).rw(FUNC(acorn_ioc_device::periph_r<6>), FUNC(acorn_ioc_device::periph_w<6>)); + map(0x00270000, 0x0027ffff).select(0x00180000).rw(FUNC(acorn_ioc_device::periph_r<7>), FUNC(acorn_ioc_device::periph_w<7>)); +} + +TIMER_CALLBACK_MEMBER(acorn_ioc_device::timer_tick) +{ + // all timers always run + set_timer(param); + + // but only timers 0 and 1 generate IRQs + switch (param) + { + case 0: + change_interrupt(IRQ_STATUS_A, 0x20, ASSERT_LINE); + break; + + case 1: + change_interrupt(IRQ_STATUS_A, 0x40, ASSERT_LINE); + break; + + case 2: + m_baud ^= 1; + m_baud_w(m_baud ? ASSERT_LINE : CLEAR_LINE); + break; + } +} + +void acorn_ioc_device::change_interrupt(int reg, uint8_t mask, int state) +{ + if (state) + m_regs[reg] |= mask; + else + m_regs[reg] &= ~mask; + + update_interrups(); +} + +void acorn_ioc_device::tra_complete() +{ + change_interrupt(IRQ_STATUS_B, 0x40, ASSERT_LINE); // KART Rx empty +} + +void acorn_ioc_device::rcv_complete() +{ + receive_register_extract(); + m_regs[KART] = get_received_char(); + change_interrupt(IRQ_STATUS_B, 0x80, ASSERT_LINE); // KART Rx full +} + +void acorn_ioc_device::tra_callback() +{ + m_kout_w(transmit_register_get_data_bit()); +} + +void acorn_ioc_device::set_timer(int tmr) +{ + double freq = 0; + + switch (tmr) + { + case 0: // Timers + case 1: + if (m_timercnt[tmr] == 0) + m_timers[tmr]->adjust(attotime::never, tmr); + else + m_timers[tmr]->adjust(attotime::from_usec(m_timercnt[tmr] / 2), tmr); // TODO: ARM timings are quite off there, it should be latch and not latch/2 + break; + + case 2: // Baud generator + freq = (double)clock() / 8 / (double)(m_timercnt[tmr] + 1); + if (!m_baud_w.isnull()) + m_timers[tmr]->adjust(attotime::from_usec(freq), tmr); + break; + + case 3: // KART clock + freq = (double)clock() / 8 / (double)((m_timercnt[tmr] + 1) * 16); + set_rate((int)freq); + break; + } +} + +void acorn_ioc_device::latch_timer_cnt(int tmr) +{ + // find out how many 2 MHz ticks have gone by + m_timerout[tmr] = m_timercnt[tmr] - (uint32_t)m_timers[tmr]->elapsed().as_ticks(clock() / 4); +} + +WRITE_LINE_MEMBER(acorn_ioc_device::if_w) +{ + // set on falling edge + if (m_if && !state) + change_interrupt(IRQ_STATUS_A, 0x04, ASSERT_LINE); + + m_if = state; +} + +WRITE_LINE_MEMBER(acorn_ioc_device::ir_w) +{ + // set on rising edge + if (!m_ir && state) + change_interrupt(IRQ_STATUS_A, 0x08, ASSERT_LINE); + + m_ir = state; +} + +void acorn_ioc_device::update_interrups() +{ + if ((m_regs[IRQ_STATUS_A] & m_regs[IRQ_MASK_A]) || (m_regs[IRQ_STATUS_B] & m_regs[IRQ_MASK_B])) + m_irq_w(ASSERT_LINE); + else + m_irq_w(CLEAR_LINE); + + if (m_regs[FIQ_STATUS] & m_regs[FIQ_MASK]) + m_fiq_w(ASSERT_LINE); + else + m_fiq_w(CLEAR_LINE); +} + +uint32_t acorn_ioc_device::registers_r(offs_t offset, uint32_t mem_mask) +{ + LOG("%s: IOC R %02x = %02x\n", machine().describe_context(), offset, m_regs[offset]); + + uint8_t data = 0; + switch (offset & 0x1f) + { + case CONTROL: + // x--- ---- IR line + // -x-- ---- IF line + // --xx xxxx GPIO (C0-C5) + + for (int i = 0; i < 6; i++) + data |= m_giop_r[i]() << i; + + data |= m_if << 6; + data |= m_ir << 7; + return data; + + case KART: + change_interrupt(IRQ_STATUS_B, 0x80, CLEAR_LINE); + return m_regs[KART]; + + case IRQ_STATUS_A: + // x--- ---- Always 1 (force IRQ) + // -x-- ---- Timer 1 + // --x- ---- Timer 0 + // ---x ---- POR line + // ---- x-- IR line + // ---- -x-- IF line + // ---- --x- IL7 line + // ---- ---x IL6 line + + return m_regs[IRQ_STATUS_A]; + + case IRQ_REQUEST_A: + return m_regs[IRQ_STATUS_A] & m_regs[IRQ_MASK_A]; + + case IRQ_MASK_A: + return m_regs[IRQ_MASK_A]; + + case IRQ_STATUS_B: + // x--- ---- KART Rx full + // -x-- ---- KART Tx empty + // --xx xxxx IL0-IL5 lines + + return m_regs[IRQ_STATUS_B]; + + case IRQ_REQUEST_B: + return m_regs[IRQ_STATUS_B] & m_regs[IRQ_MASK_B]; + + case IRQ_MASK_B: + return m_regs[IRQ_MASK_B]; + + case FIQ_STATUS: + // x--- ---- Always 1 (force FIQ) + // -x-- ---- IL0 line + // --xx x--- C5, C4 and C3 lines + // ---- -x-- IF line + // ---- --xx FH0 and FH1 lines + + return m_regs[FIQ_STATUS]; + + case FIQ_REQUEST: + return m_regs[FIQ_STATUS] & m_regs[FIQ_MASK]; + + case FIQ_MASK: + return m_regs[FIQ_MASK]; + + case T0_LATCH_LO: + return m_timerout[0] & 0xff; + + case T0_LATCH_HI: + return (m_timerout[0] >> 8) & 0xff; + + case T1_LATCH_LO: + return m_timerout[1] & 0xff; + + case T1_LATCH_HI: + return (m_timerout[1] >> 8) & 0xff; + + case T2_LATCH_LO: + return m_timerout[2] & 0xff; + + case T2_LATCH_HI: + return (m_timerout[2] >> 8) & 0xff; + + case T3_LATCH_LO: + return m_timerout[3] & 0xff; + + case T3_LATCH_HI: + return (m_timerout[3] >> 8) & 0xff; + + default: + return m_regs[offset & 0x1f]; + } +} + +void acorn_ioc_device::registers_w(offs_t offset, uint32_t data, uint32_t mem_mask) +{ + LOG("%s: IOC W %02x = %02x\n", machine().describe_context(), offset, data); + + // IOC uses the data bus lines D16-D23 as inputs, this also works with byte store (STRB) + // because the ARM CPU repeats the byte four times across the data bus. + if (ACCESSING_BITS_16_31) + data >>= 16; + + switch (offset & 0x1f) + { + case CONTROL: + for (int i = 0; i < 6; i++) + m_giop_w[i](BIT(data, i)); + break; + + case KART: + change_interrupt(IRQ_STATUS_B, 0x40, CLEAR_LINE); + m_regs[KART] = data; + transmit_register_setup(data); + break; + + case IRQ_REQUEST_A: + m_regs[IRQ_STATUS_A] &= ~(data & 0x7c); + update_interrups(); // check pending irqs + break; + + case IRQ_MASK_A: + m_regs[IRQ_MASK_A] = data; + update_interrups(); + break; + + case IRQ_MASK_B: + m_regs[IRQ_MASK_B] = data; + update_interrups(); + break; + + case FIQ_MASK: + m_regs[FIQ_MASK] = data; + update_interrups(); + break; + + case T0_LATCH_LO: case T0_LATCH_HI: + case T1_LATCH_LO: case T1_LATCH_HI: + case T2_LATCH_LO: case T2_LATCH_HI: + case T3_LATCH_LO: case T3_LATCH_HI: + m_regs[offset] = data; + break; + + case T0_LATCH: // Timer 0 latch + latch_timer_cnt(0); + break; + + case T1_LATCH: // Timer 1 latch + latch_timer_cnt(1); + break; + + case T2_LATCH: // Timer 2 latch + latch_timer_cnt(2); + break; + + case T3_LATCH: // Timer 3 latch + latch_timer_cnt(3); + break; + + case T0_GO: // Timer 0 start + m_timercnt[0] = m_regs[T0_LATCH_HI] << 8 | m_regs[T0_LATCH_LO]; + set_timer(0); + break; + + case T1_GO: // Timer 1 start + m_timercnt[1] = m_regs[T1_LATCH_HI] << 8 | m_regs[T1_LATCH_LO]; + set_timer(1); + break; + + case T2_GO: // Timer 2 start + m_timercnt[2] = m_regs[T2_LATCH_HI] << 8 | m_regs[T2_LATCH_LO]; + set_timer(2); + break; + + case T3_GO: // Timer 3 start + m_timercnt[3] = m_regs[T3_LATCH_HI] << 8 | m_regs[T3_LATCH_LO]; + set_timer(3); + break; + + default: + m_regs[offset & 0x1f] = data; + break; + } +} |