2024-03-20 18:25:20 +00:00
|
|
|
|
//
|
|
|
|
|
// InputOutputController.h
|
|
|
|
|
// Clock Signal
|
|
|
|
|
//
|
|
|
|
|
// Created by Thomas Harte on 20/03/2024.
|
|
|
|
|
// Copyright © 2024 Thomas Harte. All rights reserved.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
#pragma once
|
|
|
|
|
|
|
|
|
|
#include "CMOSRAM.hpp"
|
|
|
|
|
#include "Keyboard.hpp"
|
|
|
|
|
#include "Sound.hpp"
|
2024-03-21 14:02:56 +00:00
|
|
|
|
#include "Video.hpp"
|
|
|
|
|
|
2024-03-25 19:03:54 +00:00
|
|
|
|
#include "../../../Outputs/Log.hpp"
|
|
|
|
|
#include "../../../Activity/Observer.hpp"
|
2024-03-20 18:25:20 +00:00
|
|
|
|
|
|
|
|
|
namespace Archimedes {
|
|
|
|
|
|
|
|
|
|
// IRQ A flags
|
|
|
|
|
namespace IRQA {
|
|
|
|
|
// The first four of these are taken from the A500 documentation and may be inaccurate.
|
|
|
|
|
static constexpr uint8_t PrinterBusy = 0x01;
|
|
|
|
|
static constexpr uint8_t SerialRinging = 0x02;
|
|
|
|
|
static constexpr uint8_t PrinterAcknowledge = 0x04;
|
|
|
|
|
static constexpr uint8_t VerticalFlyback = 0x08;
|
|
|
|
|
static constexpr uint8_t PowerOnReset = 0x10;
|
|
|
|
|
static constexpr uint8_t Timer0 = 0x20;
|
|
|
|
|
static constexpr uint8_t Timer1 = 0x40;
|
|
|
|
|
static constexpr uint8_t SetAlways = 0x80;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// IRQ B flags
|
|
|
|
|
namespace IRQB {
|
|
|
|
|
// These are taken from the A3010 documentation.
|
|
|
|
|
static constexpr uint8_t PoduleFIQRequest = 0x01;
|
|
|
|
|
static constexpr uint8_t SoundBufferPointerUsed = 0x02;
|
|
|
|
|
static constexpr uint8_t SerialLine = 0x04;
|
|
|
|
|
static constexpr uint8_t IDE = 0x08;
|
|
|
|
|
static constexpr uint8_t FloppyDiscInterrupt = 0x10;
|
|
|
|
|
static constexpr uint8_t PoduleIRQRequest = 0x20;
|
|
|
|
|
static constexpr uint8_t KeyboardTransmitEmpty = 0x40;
|
|
|
|
|
static constexpr uint8_t KeyboardReceiveFull = 0x80;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// FIQ flags
|
|
|
|
|
namespace FIQ {
|
|
|
|
|
// These are taken from the A3010 documentation.
|
|
|
|
|
static constexpr uint8_t FloppyDiscData = 0x01;
|
|
|
|
|
static constexpr uint8_t SerialLine = 0x10;
|
|
|
|
|
static constexpr uint8_t PoduleFIQRequest = 0x40;
|
|
|
|
|
static constexpr uint8_t SetAlways = 0x80;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
namespace InterruptRequests {
|
|
|
|
|
static constexpr int IRQ = 0x01;
|
|
|
|
|
static constexpr int FIQ = 0x02;
|
|
|
|
|
};
|
|
|
|
|
|
2024-03-22 14:01:34 +00:00
|
|
|
|
template <typename InterruptObserverT, typename ClockRateObserverT>
|
2024-03-20 18:25:20 +00:00
|
|
|
|
struct InputOutputController {
|
|
|
|
|
int interrupt_mask() const {
|
|
|
|
|
return
|
|
|
|
|
((irq_a_.request() | irq_b_.request()) ? InterruptRequests::IRQ : 0) |
|
|
|
|
|
(fiq_.request() ? InterruptRequests::FIQ : 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <int c>
|
|
|
|
|
bool tick_timer() {
|
|
|
|
|
if(!counters_[c].value && !counters_[c].reload) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
--counters_[c].value;
|
|
|
|
|
if(!counters_[c].value) {
|
|
|
|
|
counters_[c].value = counters_[c].reload;
|
|
|
|
|
|
|
|
|
|
switch(c) {
|
|
|
|
|
case 0: return irq_a_.set(IRQA::Timer0);
|
|
|
|
|
case 1: return irq_a_.set(IRQA::Timer1);
|
|
|
|
|
case 3: {
|
|
|
|
|
serial_.shift();
|
|
|
|
|
keyboard_.update();
|
|
|
|
|
|
|
|
|
|
const uint8_t events = serial_.events(IOCParty);
|
|
|
|
|
bool did_interrupt = false;
|
|
|
|
|
if(events & HalfDuplexSerial::Receive) {
|
|
|
|
|
did_interrupt |= irq_b_.set(IRQB::KeyboardReceiveFull);
|
|
|
|
|
}
|
|
|
|
|
if(events & HalfDuplexSerial::Transmit) {
|
|
|
|
|
did_interrupt |= irq_b_.set(IRQB::KeyboardTransmitEmpty);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return did_interrupt;
|
|
|
|
|
}
|
|
|
|
|
default: break;
|
|
|
|
|
}
|
|
|
|
|
// TODO: events for timers 2 (baud).
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void tick_timers() {
|
|
|
|
|
bool did_change_interrupts = false;
|
|
|
|
|
did_change_interrupts |= tick_timer<0>();
|
|
|
|
|
did_change_interrupts |= tick_timer<1>();
|
|
|
|
|
did_change_interrupts |= tick_timer<2>();
|
|
|
|
|
did_change_interrupts |= tick_timer<3>();
|
|
|
|
|
if(did_change_interrupts) {
|
|
|
|
|
observer_.update_interrupts();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-25 14:16:36 +00:00
|
|
|
|
/// Decomposes an Archimedes bus address into bank, offset and type.
|
|
|
|
|
struct Address {
|
|
|
|
|
constexpr Address(uint32_t bus_address) noexcept {
|
|
|
|
|
bank = (bus_address >> 16) & 0b111;
|
|
|
|
|
type = Type((bus_address >> 19) & 0b11);
|
|
|
|
|
offset = bus_address & 0b1111100;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A value from 0 to 7 indicating the device being addressed.
|
|
|
|
|
uint32_t bank;
|
|
|
|
|
/// A seven-bit value which is a multiple of 4, indicating the address within the bank.
|
|
|
|
|
uint32_t offset;
|
|
|
|
|
/// Access type.
|
|
|
|
|
enum class Type {
|
|
|
|
|
Sync = 0b00,
|
2024-03-25 15:07:44 +00:00
|
|
|
|
Fast = 0b10,
|
|
|
|
|
Medium = 0b01,
|
2024-03-25 14:16:36 +00:00
|
|
|
|
Slow = 0b11
|
|
|
|
|
} type;
|
|
|
|
|
};
|
2024-03-20 18:25:20 +00:00
|
|
|
|
|
2024-03-25 18:54:30 +00:00
|
|
|
|
// Peripheral addresses on the A500:
|
|
|
|
|
//
|
|
|
|
|
// fast/1 = FDC
|
|
|
|
|
// sync/2 = econet
|
|
|
|
|
// sync/3 = serial line
|
|
|
|
|
//
|
|
|
|
|
// bank 4 = podules
|
|
|
|
|
//
|
|
|
|
|
// fast/5
|
|
|
|
|
|
2024-03-25 15:07:44 +00:00
|
|
|
|
template <typename IntT>
|
2024-03-29 02:10:49 +00:00
|
|
|
|
bool read(uint32_t address, IntT &destination) {
|
2024-03-25 14:16:36 +00:00
|
|
|
|
const Address target(address);
|
2024-03-29 02:10:49 +00:00
|
|
|
|
|
|
|
|
|
const auto set_byte = [&](uint8_t value) {
|
|
|
|
|
if constexpr (std::is_same_v<IntT, uint32_t>) {
|
2024-03-30 00:54:07 +00:00
|
|
|
|
destination = static_cast<uint32_t>(value << 16) | 0xff'00'ff'ff;
|
2024-03-29 02:10:49 +00:00
|
|
|
|
} else {
|
|
|
|
|
destination = value;
|
|
|
|
|
}
|
|
|
|
|
};
|
2024-03-25 14:16:36 +00:00
|
|
|
|
|
2024-03-25 18:54:30 +00:00
|
|
|
|
// TODO: flatten the switch below, and the equivalent in `write`.
|
|
|
|
|
|
2024-03-25 14:16:36 +00:00
|
|
|
|
switch(target.bank) {
|
2024-03-20 18:25:20 +00:00
|
|
|
|
default:
|
2024-03-25 14:16:36 +00:00
|
|
|
|
logger.error().append("Unrecognised IOC read from %08x i.e. bank %d / type %d", address, target.bank, target.type);
|
2024-03-31 01:49:21 +00:00
|
|
|
|
destination = IntT(~0);
|
2024-03-20 18:25:20 +00:00
|
|
|
|
break;
|
|
|
|
|
|
2024-03-25 14:16:36 +00:00
|
|
|
|
// Bank 0: internal registers.
|
|
|
|
|
case 0:
|
|
|
|
|
switch(target.offset) {
|
|
|
|
|
default:
|
|
|
|
|
logger.error().append("Unrecognised IOC bank 0 read; offset %02x", target.offset);
|
|
|
|
|
break;
|
|
|
|
|
|
2024-03-29 02:10:49 +00:00
|
|
|
|
case 0x00: {
|
|
|
|
|
uint8_t value = control_ | 0xc0;
|
2024-03-25 14:16:36 +00:00
|
|
|
|
value &= ~(i2c_.clock() ? 2 : 0);
|
|
|
|
|
value &= ~(i2c_.data() ? 1 : 0);
|
2024-03-29 02:10:49 +00:00
|
|
|
|
set_byte(value);
|
2024-03-26 01:31:59 +00:00
|
|
|
|
// logger.error().append("IOC control read: C:%d D:%d", !(value & 2), !(value & 1));
|
2024-03-29 02:10:49 +00:00
|
|
|
|
} break;
|
2024-03-25 14:16:36 +00:00
|
|
|
|
|
|
|
|
|
case 0x04:
|
2024-03-29 02:10:49 +00:00
|
|
|
|
set_byte(serial_.input(IOCParty));
|
2024-03-25 14:16:36 +00:00
|
|
|
|
irq_b_.clear(IRQB::KeyboardReceiveFull);
|
|
|
|
|
observer_.update_interrupts();
|
|
|
|
|
// logger.error().append("IOC keyboard receive: %02x", value);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
// IRQ A.
|
|
|
|
|
case 0x10:
|
2024-03-29 02:10:49 +00:00
|
|
|
|
set_byte(irq_a_.status);
|
2024-03-25 14:16:36 +00:00
|
|
|
|
// logger.error().append("IRQ A status is %02x", value);
|
|
|
|
|
break;
|
|
|
|
|
case 0x14:
|
2024-03-29 02:10:49 +00:00
|
|
|
|
set_byte(irq_a_.request());
|
2024-03-25 14:16:36 +00:00
|
|
|
|
// logger.error().append("IRQ A request is %02x", value);
|
|
|
|
|
break;
|
|
|
|
|
case 0x18:
|
2024-03-29 02:10:49 +00:00
|
|
|
|
set_byte(irq_a_.mask);
|
2024-03-25 14:16:36 +00:00
|
|
|
|
// logger.error().append("IRQ A mask is %02x", value);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
// IRQ B.
|
|
|
|
|
case 0x20:
|
2024-03-29 02:10:49 +00:00
|
|
|
|
set_byte(irq_b_.status);
|
2024-03-25 14:16:36 +00:00
|
|
|
|
// logger.error().append("IRQ B status is %02x", value);
|
|
|
|
|
break;
|
|
|
|
|
case 0x24:
|
2024-03-29 02:10:49 +00:00
|
|
|
|
set_byte(irq_b_.request());
|
2024-03-25 14:16:36 +00:00
|
|
|
|
// logger.error().append("IRQ B request is %02x", value);
|
|
|
|
|
break;
|
|
|
|
|
case 0x28:
|
2024-03-29 02:10:49 +00:00
|
|
|
|
set_byte(irq_b_.mask);
|
2024-03-25 14:16:36 +00:00
|
|
|
|
// logger.error().append("IRQ B mask is %02x", value);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
// FIQ.
|
|
|
|
|
case 0x30:
|
2024-03-29 02:10:49 +00:00
|
|
|
|
set_byte(fiq_.status);
|
2024-03-30 00:54:07 +00:00
|
|
|
|
logger.error().append("FIQ status is %02x", fiq_.status);
|
2024-03-25 14:16:36 +00:00
|
|
|
|
break;
|
|
|
|
|
case 0x34:
|
2024-03-29 02:10:49 +00:00
|
|
|
|
set_byte(fiq_.request());
|
2024-03-30 00:54:07 +00:00
|
|
|
|
logger.error().append("FIQ request is %02x", fiq_.request());
|
2024-03-25 14:16:36 +00:00
|
|
|
|
break;
|
|
|
|
|
case 0x38:
|
2024-03-29 02:10:49 +00:00
|
|
|
|
set_byte(fiq_.mask);
|
2024-03-30 00:54:07 +00:00
|
|
|
|
logger.error().append("FIQ mask is %02x", fiq_.mask);
|
2024-03-25 14:16:36 +00:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
// Counters.
|
|
|
|
|
case 0x40: case 0x50: case 0x60: case 0x70:
|
2024-03-29 02:10:49 +00:00
|
|
|
|
set_byte(counters_[(target.offset >> 4) - 0x4].output & 0xff);
|
2024-03-25 14:16:36 +00:00
|
|
|
|
// logger.error().append("%02x: Counter %d low is %02x", target, (target >> 4) - 0x4, value);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 0x44: case 0x54: case 0x64: case 0x74:
|
2024-03-29 02:10:49 +00:00
|
|
|
|
set_byte(counters_[(target.offset >> 4) - 0x4].output >> 8);
|
2024-03-25 14:16:36 +00:00
|
|
|
|
// logger.error().append("%02x: Counter %d high is %02x", target, (target >> 4) - 0x4, value);
|
|
|
|
|
break;
|
|
|
|
|
}
|
2024-03-25 15:07:44 +00:00
|
|
|
|
break;
|
2024-03-20 18:25:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-25 15:07:44 +00:00
|
|
|
|
template <typename IntT>
|
2024-03-25 19:03:54 +00:00
|
|
|
|
bool write(uint32_t address, IntT bus_value) {
|
2024-03-25 14:16:36 +00:00
|
|
|
|
const Address target(address);
|
2024-03-25 18:54:30 +00:00
|
|
|
|
|
|
|
|
|
// Empirically, RISC OS 3.19:
|
|
|
|
|
// * at 03801e88 and 03801e8c loads R8 and R9 with 0xbe0000 and 0xff0000 respectively; and
|
|
|
|
|
// * subsequently uses 32-bit strs (e.g. at 03801eac) to write those values to latch A.
|
|
|
|
|
//
|
|
|
|
|
// Given that 8-bit ARM writes duplicate the 8-bit value four times across the data bus,
|
|
|
|
|
// my conclusion is that the IOC is probably connected to data lines 15–23.
|
|
|
|
|
//
|
|
|
|
|
// Hence: use @c byte to get a current 8-bit value.
|
|
|
|
|
const auto byte = [](IntT original) -> uint8_t {
|
|
|
|
|
if constexpr (std::is_same_v<IntT, uint32_t>) {
|
|
|
|
|
return static_cast<uint8_t>(original >> 16);
|
|
|
|
|
} else {
|
|
|
|
|
return original;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2024-03-25 14:16:36 +00:00
|
|
|
|
switch(target.bank) {
|
2024-03-20 18:25:20 +00:00
|
|
|
|
default:
|
2024-03-25 19:03:54 +00:00
|
|
|
|
logger.error().append("Unrecognised IOC write of %02x to %08x i.e. bank %d / type %d", bus_value, address, target.bank, target.type);
|
2024-03-20 18:25:20 +00:00
|
|
|
|
break;
|
|
|
|
|
|
2024-03-25 14:16:36 +00:00
|
|
|
|
// Bank 0: internal registers.
|
|
|
|
|
case 0:
|
|
|
|
|
switch(target.offset) {
|
|
|
|
|
default:
|
2024-03-25 19:03:54 +00:00
|
|
|
|
logger.error().append("Unrecognised IOC bank 0 write; %02x to offset %02x", bus_value, target.offset);
|
2024-03-25 14:16:36 +00:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 0x00:
|
2024-03-25 19:03:54 +00:00
|
|
|
|
control_ = byte(bus_value);
|
|
|
|
|
i2c_.set_clock_data(!(bus_value & 2), !(bus_value & 1));
|
2024-03-25 15:07:44 +00:00
|
|
|
|
|
|
|
|
|
// Per the A500 documentation:
|
|
|
|
|
// b7: vertical sync/test input bit, so should be programmed high;
|
|
|
|
|
// b6: input for printer acknowledgement, so should be programmed high;
|
|
|
|
|
// b5: speaker mute; 1 = muted;
|
|
|
|
|
// b4: "Available on the auxiliary I/O connector"
|
|
|
|
|
// b3: "Programmed HIGH, unless Reset Mask is required."
|
|
|
|
|
// b2: Used as the floppy disk (READY) input and must be programmed high;
|
|
|
|
|
// b1 and b0: I2C connections as above.
|
2024-03-25 14:16:36 +00:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 0x04:
|
2024-03-25 19:03:54 +00:00
|
|
|
|
serial_.output(IOCParty, byte(bus_value));
|
2024-03-25 14:16:36 +00:00
|
|
|
|
irq_b_.clear(IRQB::KeyboardTransmitEmpty);
|
|
|
|
|
observer_.update_interrupts();
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 0x14:
|
|
|
|
|
// b2: clear IF.
|
|
|
|
|
// b3: clear IR.
|
|
|
|
|
// b4: clear POR.
|
|
|
|
|
// b5: clear TM[0].
|
|
|
|
|
// b6: clear TM[1].
|
2024-03-25 19:03:54 +00:00
|
|
|
|
irq_a_.clear(byte(bus_value) & 0x7c);
|
2024-03-25 14:16:36 +00:00
|
|
|
|
observer_.update_interrupts();
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
// Interrupts.
|
2024-03-29 02:10:49 +00:00
|
|
|
|
case 0x18:
|
|
|
|
|
irq_a_.mask = byte(bus_value);
|
|
|
|
|
// logger.error().append("IRQ A mask set to %02x", byte(bus_value));
|
|
|
|
|
break;
|
|
|
|
|
case 0x28:
|
|
|
|
|
irq_b_.mask = byte(bus_value);
|
|
|
|
|
// logger.error().append("IRQ B mask set to %02x", byte(bus_value));
|
|
|
|
|
break;
|
|
|
|
|
case 0x38:
|
|
|
|
|
fiq_.mask = byte(bus_value);
|
|
|
|
|
// logger.error().append("FIQ mask set to %02x", byte(bus_value));
|
|
|
|
|
break;
|
2024-03-25 14:16:36 +00:00
|
|
|
|
|
|
|
|
|
// Counters.
|
|
|
|
|
case 0x40: case 0x50: case 0x60: case 0x70:
|
|
|
|
|
counters_[(target.offset >> 4) - 0x4].reload = uint16_t(
|
2024-03-25 19:03:54 +00:00
|
|
|
|
(counters_[(target.offset >> 4) - 0x4].reload & 0xff00) | byte(bus_value)
|
2024-03-25 14:16:36 +00:00
|
|
|
|
);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 0x44: case 0x54: case 0x64: case 0x74:
|
|
|
|
|
counters_[(target.offset >> 4) - 0x4].reload = uint16_t(
|
2024-03-25 19:03:54 +00:00
|
|
|
|
(counters_[(target.offset >> 4) - 0x4].reload & 0x00ff) | (byte(bus_value) << 8)
|
2024-03-25 14:16:36 +00:00
|
|
|
|
);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 0x48: case 0x58: case 0x68: case 0x78:
|
|
|
|
|
counters_[(target.offset >> 4) - 0x4].value = counters_[(target.offset >> 4) - 0x4].reload;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 0x4c: case 0x5c: case 0x6c: case 0x7c:
|
|
|
|
|
counters_[(target.offset >> 4) - 0x4].output = counters_[(target.offset >> 4) - 0x4].value;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2024-03-25 15:07:44 +00:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
// Bank 5: both the hard disk and the latches, depending on type.
|
|
|
|
|
case 5:
|
|
|
|
|
switch(target.type) {
|
|
|
|
|
default:
|
2024-03-25 19:03:54 +00:00
|
|
|
|
logger.error().append("Unrecognised IOC bank 5 type %d write; %02x to offset %02x", target.type, bus_value, target.offset);
|
2024-03-25 15:07:44 +00:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case Address::Type::Fast:
|
|
|
|
|
switch(target.offset) {
|
|
|
|
|
default:
|
2024-03-25 19:03:54 +00:00
|
|
|
|
logger.error().append("Unrecognised IOC fast bank 5 write; %02x to offset %02x", bus_value, target.offset);
|
2024-03-25 15:07:44 +00:00
|
|
|
|
break;
|
2024-03-25 18:54:30 +00:00
|
|
|
|
|
|
|
|
|
case 0x00:
|
2024-03-25 19:03:54 +00:00
|
|
|
|
logger.error().append("TODO: printer data write; %02x", byte(bus_value));
|
2024-03-25 18:54:30 +00:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 0x18:
|
2024-03-26 01:31:59 +00:00
|
|
|
|
// TODO, per the A500 documentation:
|
|
|
|
|
//
|
|
|
|
|
// Latch B:
|
|
|
|
|
// b0: ?
|
|
|
|
|
// b1: double/single density; 0 = double.
|
|
|
|
|
// b2: ?
|
|
|
|
|
// b3: floppy drive reset; 0 = reset.
|
|
|
|
|
// b4: printer strobe
|
|
|
|
|
// b5: ?
|
|
|
|
|
// b6: ?
|
|
|
|
|
// b7: HS3?
|
|
|
|
|
|
2024-03-25 19:03:54 +00:00
|
|
|
|
logger.error().append("TODO: latch B write; %02x", byte(bus_value));
|
2024-03-25 18:54:30 +00:00
|
|
|
|
break;
|
|
|
|
|
|
2024-03-25 19:03:54 +00:00
|
|
|
|
case 0x40: {
|
2024-03-26 01:31:59 +00:00
|
|
|
|
// TODO, per the A500 documentation:
|
|
|
|
|
//
|
|
|
|
|
// Latch A:
|
|
|
|
|
// b0, b1, b2, b3 = drive selects;
|
|
|
|
|
// b4 = side select;
|
|
|
|
|
// b5 = motor on/off
|
|
|
|
|
// b6 = floppy in use (i.e. LED?);
|
|
|
|
|
// b7 = "Not used."
|
|
|
|
|
|
2024-03-25 19:03:54 +00:00
|
|
|
|
const uint8_t value = byte(bus_value);
|
2024-03-26 01:31:59 +00:00
|
|
|
|
// logger.error().append("TODO: latch A write; %02x", value);
|
2024-03-25 19:03:54 +00:00
|
|
|
|
|
|
|
|
|
// Set the floppy indicator on if any drive is selected,
|
|
|
|
|
// because this emulator is compressing them all into a
|
|
|
|
|
// single LED, and the machine has indicated 'in use'.
|
|
|
|
|
if(activity_observer_) {
|
|
|
|
|
activity_observer_->set_led_status(FloppyActivityLED,
|
|
|
|
|
!(value & 0x40) && ((value & 0xf) != 0xf)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
} break;
|
2024-03-25 18:54:30 +00:00
|
|
|
|
|
|
|
|
|
case 0x48:
|
2024-03-26 01:31:59 +00:00
|
|
|
|
// TODO, per the A500 documentation:
|
|
|
|
|
//
|
|
|
|
|
// Latch C:
|
|
|
|
|
// (probably not present on earlier machines?)
|
|
|
|
|
// b2/b3: sync polarity [b3 = V polarity, b2 = H?]
|
|
|
|
|
// b0/b1: VIDC master clock; 00 = 24Mhz, 01 = 25.175Mhz; 10 = 36Mhz; 11 = reserved.
|
|
|
|
|
|
2024-03-25 19:03:54 +00:00
|
|
|
|
logger.error().append("TODO: latch C write; %02x", byte(bus_value));
|
2024-03-25 18:54:30 +00:00
|
|
|
|
break;
|
2024-03-25 15:07:44 +00:00
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
break;
|
2024-03-20 18:25:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-03-25 14:16:36 +00:00
|
|
|
|
// case 0x327'0000 & AddressMask: // Bank 7
|
|
|
|
|
// logger.error().append("TODO: exteded external podule space");
|
|
|
|
|
// return true;
|
|
|
|
|
//
|
|
|
|
|
// case 0x331'0000 & AddressMask:
|
|
|
|
|
// logger.error().append("TODO: 1772 / disk write");
|
|
|
|
|
// return true;
|
|
|
|
|
//
|
|
|
|
|
// case 0x336'0000 & AddressMask:
|
|
|
|
|
// logger.error().append("TODO: podule interrupt request");
|
|
|
|
|
// return true;
|
|
|
|
|
//
|
|
|
|
|
// case 0x336'0004 & AddressMask:
|
|
|
|
|
// logger.error().append("TODO: podule interrupt mask");
|
|
|
|
|
// return true;
|
|
|
|
|
//
|
|
|
|
|
// case 0x33a'0000 & AddressMask:
|
|
|
|
|
// logger.error().append("TODO: 6854 / econet write");
|
|
|
|
|
// return true;
|
|
|
|
|
//
|
|
|
|
|
// case 0x33b'0000 & AddressMask:
|
|
|
|
|
// logger.error().append("TODO: 6551 / serial line write");
|
|
|
|
|
// return true;
|
2024-03-20 18:25:20 +00:00
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-22 14:01:34 +00:00
|
|
|
|
InputOutputController(InterruptObserverT &observer, ClockRateObserverT &clock_observer, const uint8_t *ram) :
|
2024-03-20 18:25:20 +00:00
|
|
|
|
observer_(observer),
|
|
|
|
|
keyboard_(serial_),
|
2024-03-22 00:22:20 +00:00
|
|
|
|
sound_(*this, ram),
|
2024-03-22 14:01:34 +00:00
|
|
|
|
video_(*this, clock_observer, sound_, ram)
|
2024-03-20 18:25:20 +00:00
|
|
|
|
{
|
|
|
|
|
irq_a_.status = IRQA::SetAlways | IRQA::PowerOnReset;
|
|
|
|
|
irq_b_.status = 0x00;
|
2024-03-29 02:15:27 +00:00
|
|
|
|
fiq_.status = FIQ::SetAlways;
|
2024-03-20 18:25:20 +00:00
|
|
|
|
|
|
|
|
|
i2c_.add_peripheral(&cmos_, 0xa0);
|
2024-03-21 14:02:56 +00:00
|
|
|
|
update_interrupts();
|
2024-03-20 18:25:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-03-23 19:43:04 +00:00
|
|
|
|
auto &sound() { return sound_; }
|
|
|
|
|
const auto &sound() const { return sound_; }
|
|
|
|
|
auto &video() { return video_; }
|
|
|
|
|
const auto &video() const { return video_; }
|
|
|
|
|
auto &keyboard() { return keyboard_; }
|
|
|
|
|
const auto &keyboard() const { return keyboard_; }
|
2024-03-21 14:02:56 +00:00
|
|
|
|
|
|
|
|
|
void update_interrupts() {
|
2024-03-20 18:25:20 +00:00
|
|
|
|
if(sound_.interrupt()) {
|
|
|
|
|
irq_b_.set(IRQB::SoundBufferPointerUsed);
|
|
|
|
|
} else {
|
|
|
|
|
irq_b_.clear(IRQB::SoundBufferPointerUsed);
|
|
|
|
|
}
|
2024-03-21 14:02:56 +00:00
|
|
|
|
|
|
|
|
|
if(video_.interrupt()) {
|
|
|
|
|
irq_a_.set(IRQA::VerticalFlyback);
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-20 18:25:20 +00:00
|
|
|
|
observer_.update_interrupts();
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-25 19:03:54 +00:00
|
|
|
|
void set_activity_observer(Activity::Observer *observer) {
|
|
|
|
|
activity_observer_ = observer;
|
|
|
|
|
if(activity_observer_) {
|
|
|
|
|
activity_observer_->register_led(FloppyActivityLED);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-20 18:25:20 +00:00
|
|
|
|
private:
|
|
|
|
|
Log::Logger<Log::Source::ARMIOC> logger;
|
|
|
|
|
InterruptObserverT &observer_;
|
2024-03-25 19:03:54 +00:00
|
|
|
|
Activity::Observer *activity_observer_ = nullptr;
|
|
|
|
|
static inline const std::string FloppyActivityLED = "Drive";
|
2024-03-20 18:25:20 +00:00
|
|
|
|
|
|
|
|
|
// IRQA, IRQB and FIQ states.
|
|
|
|
|
struct Interrupt {
|
|
|
|
|
uint8_t status, mask;
|
|
|
|
|
uint8_t request() const {
|
|
|
|
|
return status & mask;
|
|
|
|
|
}
|
|
|
|
|
bool set(uint8_t value) {
|
|
|
|
|
status |= value;
|
|
|
|
|
return status & mask;
|
|
|
|
|
}
|
|
|
|
|
void clear(uint8_t bits) {
|
|
|
|
|
status &= ~bits;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
Interrupt irq_a_, irq_b_, fiq_;
|
|
|
|
|
|
|
|
|
|
// The IOCs four counters.
|
|
|
|
|
struct Counter {
|
|
|
|
|
uint16_t value;
|
|
|
|
|
uint16_t reload;
|
|
|
|
|
uint16_t output;
|
|
|
|
|
};
|
|
|
|
|
Counter counters_[4];
|
|
|
|
|
|
|
|
|
|
// The KART and keyboard beyond it.
|
|
|
|
|
HalfDuplexSerial serial_;
|
|
|
|
|
Keyboard keyboard_;
|
|
|
|
|
|
|
|
|
|
// The control register.
|
|
|
|
|
uint8_t control_ = 0xff;
|
|
|
|
|
|
|
|
|
|
// The I2C bus.
|
|
|
|
|
I2C::Bus i2c_;
|
|
|
|
|
CMOSRAM cmos_;
|
|
|
|
|
|
2024-03-21 14:02:56 +00:00
|
|
|
|
// Audio and video.
|
2024-03-20 18:25:20 +00:00
|
|
|
|
Sound<InputOutputController> sound_;
|
2024-03-22 14:01:34 +00:00
|
|
|
|
Video<InputOutputController, ClockRateObserverT, Sound<InputOutputController>> video_;
|
2024-03-20 18:25:20 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|