2020-10-30 01:03:02 +00:00
|
|
|
|
//
|
|
|
|
|
// RealTimeClock.hpp
|
|
|
|
|
// Clock Signal
|
|
|
|
|
//
|
|
|
|
|
// Created by Thomas Harte on 07/05/2019.
|
|
|
|
|
// Copyright © 2019 Thomas Harte. All rights reserved.
|
|
|
|
|
//
|
|
|
|
|
|
2024-01-17 04:34:46 +00:00
|
|
|
|
#pragma once
|
2020-10-30 01:03:02 +00:00
|
|
|
|
|
2021-09-10 23:56:20 +00:00
|
|
|
|
#include <array>
|
|
|
|
|
|
2023-05-10 21:02:18 +00:00
|
|
|
|
namespace Apple::Clock {
|
2020-10-30 01:03:02 +00:00
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
Models Apple's real-time clocks, as contained in the Macintosh and IIgs.
|
|
|
|
|
|
|
|
|
|
Since tracking of time is pushed to this class, it is assumed
|
|
|
|
|
that whomever is translating real time into emulated time
|
|
|
|
|
will also signal interrupts — this is just the storage and time counting.
|
|
|
|
|
*/
|
|
|
|
|
class ClockStorage {
|
|
|
|
|
public:
|
|
|
|
|
/*!
|
|
|
|
|
Advances the clock by 1 second.
|
|
|
|
|
|
2021-09-10 23:56:20 +00:00
|
|
|
|
The caller should also signal an interrupt if applicable.
|
2020-10-30 01:03:02 +00:00
|
|
|
|
*/
|
|
|
|
|
void update() {
|
2021-09-10 23:56:20 +00:00
|
|
|
|
for(size_t c = 0; c < 4; ++c) {
|
2020-10-30 01:03:02 +00:00
|
|
|
|
++seconds_[c];
|
|
|
|
|
if(seconds_[c]) break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-10 23:56:20 +00:00
|
|
|
|
/*!
|
|
|
|
|
Sets the current [P/B]RAM contents.
|
|
|
|
|
*/
|
|
|
|
|
template <typename CollectionT> void set_data(const CollectionT &collection) {
|
|
|
|
|
set_data(collection.begin(), collection.end());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <typename IteratorT> void set_data(IteratorT begin, const IteratorT end) {
|
|
|
|
|
size_t c = 0;
|
|
|
|
|
while(begin != end && c < 256) {
|
|
|
|
|
data_[c] = *begin;
|
|
|
|
|
++begin;
|
|
|
|
|
++c;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-30 01:03:02 +00:00
|
|
|
|
protected:
|
|
|
|
|
static constexpr uint16_t NoResult = 0x100;
|
2020-10-30 01:38:36 +00:00
|
|
|
|
static constexpr uint16_t DidComplete = 0x101;
|
2020-10-30 01:03:02 +00:00
|
|
|
|
uint16_t perform(uint8_t command) {
|
|
|
|
|
/*
|
|
|
|
|
Documented commands:
|
|
|
|
|
|
|
|
|
|
z0000001 Seconds register 0 (lowest order byte)
|
|
|
|
|
z0000101 Seconds register 1
|
|
|
|
|
z0001001 Seconds register 2
|
|
|
|
|
z0001101 Seconds register 3
|
|
|
|
|
00110001 Test register (write only)
|
|
|
|
|
00110101 Write-protect register (write only)
|
|
|
|
|
z010aa01 RAM addresses 0x10 - 0x13
|
|
|
|
|
z1aaaa01 RAM addresses 0x00 – 0x0f
|
|
|
|
|
|
|
|
|
|
z0111abc, followed by 0defgh00
|
|
|
|
|
RAM address abcdefgh
|
|
|
|
|
|
|
|
|
|
z = 1 => a read; z = 0 => a write.
|
|
|
|
|
|
|
|
|
|
The top bit of the write-protect register enables (0) or disables (1)
|
|
|
|
|
writes to other locations.
|
|
|
|
|
|
|
|
|
|
All the documentation says about the test register is to set the top
|
|
|
|
|
two bits to 0 for normal operation. Abnormal operation is undefined.
|
|
|
|
|
*/
|
2020-10-30 01:38:36 +00:00
|
|
|
|
switch(phase_) {
|
|
|
|
|
case Phase::Command:
|
|
|
|
|
// Decode an address.
|
|
|
|
|
switch(command & 0x70) {
|
|
|
|
|
default:
|
|
|
|
|
if(command & 0x40) {
|
|
|
|
|
// RAM addresses 0x00 – 0x0f.
|
|
|
|
|
address_ = (command >> 2) & 0xf;
|
|
|
|
|
} else return DidComplete; // Unrecognised.
|
|
|
|
|
break;
|
2020-10-30 01:03:02 +00:00
|
|
|
|
|
2020-10-30 01:38:36 +00:00
|
|
|
|
case 0x00:
|
|
|
|
|
// A time access.
|
|
|
|
|
address_ = SecondsBuffer + ((command >> 2)&3);
|
|
|
|
|
break;
|
|
|
|
|
case 0x30:
|
|
|
|
|
// Either a register access or an extended instruction.
|
|
|
|
|
if(command & 0x08) {
|
2021-09-10 23:56:20 +00:00
|
|
|
|
address_ = unsigned((command & 0x7) << 5);
|
2020-10-31 02:17:55 +00:00
|
|
|
|
phase_ = (command & 0x80) ? Phase::SecondAddressByteRead : Phase::SecondAddressByteWrite;
|
2020-10-30 01:38:36 +00:00
|
|
|
|
return NoResult;
|
|
|
|
|
} else {
|
|
|
|
|
address_ = (command & 4) ? RegisterWriteProtect : RegisterTest;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 0x20:
|
|
|
|
|
// RAM addresses 0x10 – 0x13.
|
|
|
|
|
address_ = 0x10 + ((command >> 2) & 0x3);
|
|
|
|
|
break;
|
|
|
|
|
}
|
2020-10-30 01:03:02 +00:00
|
|
|
|
|
2020-10-30 01:38:36 +00:00
|
|
|
|
// If this is a read, return a result; otherwise prepare to write.
|
|
|
|
|
if(command & 0x80) {
|
|
|
|
|
// The two registers are write-only.
|
|
|
|
|
if(address_ == RegisterTest || address_ == RegisterWriteProtect) {
|
|
|
|
|
return DidComplete;
|
|
|
|
|
}
|
|
|
|
|
return (address_ >= SecondsBuffer) ? seconds_[address_ & 0xff] : data_[address_];
|
|
|
|
|
}
|
|
|
|
|
phase_ = Phase::WriteData;
|
2020-10-30 01:03:02 +00:00
|
|
|
|
return NoResult;
|
|
|
|
|
|
2020-10-30 01:38:36 +00:00
|
|
|
|
case Phase::SecondAddressByteRead:
|
|
|
|
|
case Phase::SecondAddressByteWrite:
|
|
|
|
|
if(command & 0x83) {
|
|
|
|
|
return DidComplete;
|
|
|
|
|
}
|
|
|
|
|
address_ |= command >> 2;
|
|
|
|
|
|
|
|
|
|
if(phase_ == Phase::SecondAddressByteRead) {
|
|
|
|
|
phase_ = Phase::Command;
|
|
|
|
|
return data_[address_]; // Only RAM accesses can get this far.
|
2020-10-30 01:03:02 +00:00
|
|
|
|
} else {
|
2020-10-30 01:38:36 +00:00
|
|
|
|
phase_ = Phase::WriteData;
|
|
|
|
|
}
|
|
|
|
|
return NoResult;
|
|
|
|
|
|
|
|
|
|
case Phase::WriteData:
|
|
|
|
|
// First test: is this to the write-protect register?
|
|
|
|
|
if(address_ == RegisterWriteProtect) {
|
|
|
|
|
write_protect_ = command;
|
|
|
|
|
return DidComplete;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(address_ == RegisterTest) {
|
|
|
|
|
// No documentation here.
|
|
|
|
|
return DidComplete;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// No other writing is permitted if the write protect
|
|
|
|
|
// register won't allow it.
|
|
|
|
|
if(!(write_protect_ & 0x80)) {
|
|
|
|
|
if(address_ >= SecondsBuffer) {
|
|
|
|
|
seconds_[address_ & 0xff] = command;
|
|
|
|
|
} else {
|
|
|
|
|
data_[address_] = command;
|
|
|
|
|
}
|
2020-10-30 01:03:02 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-10-30 01:38:36 +00:00
|
|
|
|
phase_ = Phase::Command;
|
|
|
|
|
return DidComplete;
|
2020-10-30 01:03:02 +00:00
|
|
|
|
}
|
2020-11-23 02:43:56 +00:00
|
|
|
|
|
|
|
|
|
return NoResult;
|
2020-10-30 01:03:02 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private:
|
2021-09-10 23:56:20 +00:00
|
|
|
|
std::array<uint8_t, 256> data_{0xff};
|
|
|
|
|
std::array<uint8_t, 4> seconds_{};
|
2021-09-10 03:08:13 +00:00
|
|
|
|
uint8_t write_protect_ = 0;
|
2021-09-10 23:56:20 +00:00
|
|
|
|
unsigned int address_ = 0;
|
2020-10-30 01:38:36 +00:00
|
|
|
|
|
|
|
|
|
static constexpr int SecondsBuffer = 0x100;
|
|
|
|
|
static constexpr int RegisterTest = 0x200;
|
|
|
|
|
static constexpr int RegisterWriteProtect = 0x201;
|
|
|
|
|
|
|
|
|
|
enum class Phase {
|
|
|
|
|
Command,
|
|
|
|
|
SecondAddressByteRead,
|
|
|
|
|
SecondAddressByteWrite,
|
|
|
|
|
WriteData
|
|
|
|
|
};
|
|
|
|
|
Phase phase_ = Phase::Command;
|
|
|
|
|
|
2020-10-30 01:03:02 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
Provides the serial interface implemented by the Macintosh.
|
|
|
|
|
*/
|
|
|
|
|
class SerialClock: public ClockStorage {
|
|
|
|
|
public:
|
|
|
|
|
/*!
|
|
|
|
|
Sets the current clock and data inputs to the clock.
|
|
|
|
|
*/
|
|
|
|
|
void set_input(bool clock, bool data) {
|
2023-05-12 18:14:45 +00:00
|
|
|
|
// The data line is valid when the clock transitions to level 0.
|
2020-10-30 01:03:02 +00:00
|
|
|
|
if(clock && !previous_clock_) {
|
|
|
|
|
// Shift into the command_ register, no matter what.
|
|
|
|
|
command_ = uint16_t((command_ << 1) | (data ? 1 : 0));
|
|
|
|
|
result_ <<= 1;
|
|
|
|
|
|
|
|
|
|
// Increment phase.
|
|
|
|
|
++phase_;
|
|
|
|
|
|
|
|
|
|
// If a whole byte has been collected, push it onwards.
|
|
|
|
|
if(!(phase_&7)) {
|
|
|
|
|
// Begin pessimistically.
|
|
|
|
|
const auto effect = perform(uint8_t(command_));
|
|
|
|
|
|
|
|
|
|
switch(effect) {
|
|
|
|
|
case ClockStorage::NoResult:
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
result_ = uint8_t(effect);
|
|
|
|
|
break;
|
2020-10-30 01:38:36 +00:00
|
|
|
|
case ClockStorage::DidComplete:
|
2020-10-30 01:03:02 +00:00
|
|
|
|
abort();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
previous_clock_ = clock;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
Reads the current data output level from the clock.
|
|
|
|
|
*/
|
|
|
|
|
bool get_data() {
|
|
|
|
|
return !!(result_ & 0x80);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
Announces that a serial command has been aborted.
|
|
|
|
|
*/
|
|
|
|
|
void abort() {
|
|
|
|
|
result_ = 0;
|
|
|
|
|
phase_ = 0;
|
|
|
|
|
command_ = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
int phase_ = 0;
|
|
|
|
|
uint16_t command_;
|
|
|
|
|
uint8_t result_ = 0;
|
|
|
|
|
|
|
|
|
|
bool previous_clock_ = false;
|
|
|
|
|
};
|
|
|
|
|
|
2020-10-30 01:38:36 +00:00
|
|
|
|
/*!
|
|
|
|
|
Provides the parallel interface implemented by the IIgs.
|
|
|
|
|
*/
|
|
|
|
|
class ParallelClock: public ClockStorage {
|
|
|
|
|
public:
|
|
|
|
|
void set_control(uint8_t control) {
|
|
|
|
|
if(!(control&0x80)) return;
|
|
|
|
|
|
|
|
|
|
if(control & 0x40) {
|
|
|
|
|
// Read from the RTC.
|
|
|
|
|
// A no-op for now.
|
|
|
|
|
} else {
|
|
|
|
|
// Write to the RTC. Which in this implementation also sets up a future read.
|
2021-09-10 02:07:03 +00:00
|
|
|
|
const auto result = perform(data_);
|
2021-09-10 03:08:13 +00:00
|
|
|
|
if(result < 0x100) {
|
2021-09-10 02:07:03 +00:00
|
|
|
|
data_ = uint8_t(result);
|
|
|
|
|
}
|
2020-10-30 01:38:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MAGIC! The transaction took 0 seconds.
|
|
|
|
|
// TODO: no magic.
|
|
|
|
|
control_ = control & 0x7f;
|
|
|
|
|
|
|
|
|
|
// Bit 5 is also meant to be 1 or 0 to indicate the final byte.
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint8_t get_control() {
|
|
|
|
|
return control_;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void set_data(uint8_t data) {
|
|
|
|
|
data_ = data;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint8_t get_data() {
|
|
|
|
|
return data_;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
uint8_t data_;
|
|
|
|
|
uint8_t control_;
|
|
|
|
|
};
|
|
|
|
|
|
2020-10-30 01:03:02 +00:00
|
|
|
|
}
|