From 635e18a50d2cbaee0ee6279930f19aebc23b40f6 Mon Sep 17 00:00:00 2001
From: Thomas Harte <thomas.harte@gmail.com>
Date: Wed, 30 Oct 2019 22:42:06 -0400
Subject: [PATCH] Ensures the MFP requests and receives real-time clocking when
 needed.

---
 Components/68901/MFP68901.cpp | 77 ++++++++++++++++++++---------------
 Components/68901/MFP68901.hpp |  8 +++-
 Machines/AtariST/AtariST.cpp  | 26 +++++++++---
 3 files changed, 71 insertions(+), 40 deletions(-)

diff --git a/Components/68901/MFP68901.cpp b/Components/68901/MFP68901.cpp
index fd5bf1133..6c9f211e8 100644
--- a/Components/68901/MFP68901.cpp
+++ b/Components/68901/MFP68901.cpp
@@ -16,6 +16,17 @@
 
 using namespace Motorola::MFP68901;
 
+ClockingHint::Preference MFP68901::preferred_clocking() {
+	// Rule applied: if any timer is actively running and permitted to produce an
+	// interrupt, request real-time running.
+	return
+		(timers_[0].mode >= TimerMode::Delay && interrupt_enable_&Interrupt::TimerA) ||
+		(timers_[1].mode >= TimerMode::Delay && interrupt_enable_&Interrupt::TimerB) ||
+		(timers_[2].mode >= TimerMode::Delay && interrupt_enable_&Interrupt::TimerC) ||
+		(timers_[3].mode >= TimerMode::Delay && interrupt_enable_&Interrupt::TimerD)
+			? ClockingHint::Preference::RealTime : ClockingHint::Preference::JustInTime;
+}
+
 uint8_t MFP68901::read(int address) {
 	address &= 0x1f;
 
@@ -94,9 +105,14 @@ void MFP68901::write(int address, uint8_t value) {
 
 		// Whatever just happened may have affected the state of the interrupt line.
 		update_interrupts();
+		update_clocking_observer();
 		return;
 	}
 
+	const int timer_prescales[] = {
+		1, 4, 10, 16, 50, 64, 100, 200
+	};
+
 	switch(address) {
 		// GPIP block: output and configuration of active edge and direction values.
 		case 0x00:
@@ -141,26 +157,8 @@ void MFP68901::write(int address, uint8_t value) {
 		} break;
 		case 0x0e:
 			timer_cd_control_ = value;
-			switch(value & 7) {
-				case 0:	set_timer_mode(3, TimerMode::Stopped, 1, false);	break;
-				case 1:	set_timer_mode(3, TimerMode::Delay, 4, false);		break;
-				case 2:	set_timer_mode(3, TimerMode::Delay, 10, false);		break;
-				case 3:	set_timer_mode(3, TimerMode::Delay, 16, false);		break;
-				case 4:	set_timer_mode(3, TimerMode::Delay, 50, false);		break;
-				case 5:	set_timer_mode(3, TimerMode::Delay, 64, false);		break;
-				case 6:	set_timer_mode(3, TimerMode::Delay, 100, false);	break;
-				case 7:	set_timer_mode(3, TimerMode::Delay, 200, false);	break;
-			}
-			switch((value >> 4) & 7) {
-				case 0:	set_timer_mode(2, TimerMode::Stopped, 1, false);	break;
-				case 1:	set_timer_mode(2, TimerMode::Delay, 4, false);		break;
-				case 2:	set_timer_mode(2, TimerMode::Delay, 10, false);		break;
-				case 3:	set_timer_mode(2, TimerMode::Delay, 16, false);		break;
-				case 4:	set_timer_mode(2, TimerMode::Delay, 50, false);		break;
-				case 5:	set_timer_mode(2, TimerMode::Delay, 64, false);		break;
-				case 6:	set_timer_mode(2, TimerMode::Delay, 100, false);	break;
-				case 7:	set_timer_mode(2, TimerMode::Delay, 200, false);	break;
-			}
+			set_timer_mode(3, (value & 7) ? TimerMode::Delay : TimerMode::Stopped, timer_prescales[value & 7], false);
+			set_timer_mode(2, ((value >> 4) & 7) ? TimerMode::Delay : TimerMode::Stopped, timer_prescales[(value >> 4) & 7], false);
 		break;
 		case 0x0f:	case 0x10:	case 0x11:	case 0x12:
 			set_timer_data(address - 0xf, value);
@@ -173,6 +171,8 @@ void MFP68901::write(int address, uint8_t value) {
 		case 0x16:		LOG("Write: transmitter status");		break;
 		case 0x17:		LOG("Write: USART data");				break;
 	}
+
+	update_clocking_observer();
 }
 
 void MFP68901::run_for(HalfCycles time) {
@@ -186,11 +186,20 @@ void MFP68901::run_for(HalfCycles time) {
 				--timers_[c].divisor;
 				if(!timers_[c].divisor) {
 					timers_[c].divisor = timers_[c].prescale;
-					decrement_timer(c);
+					decrement_timer(c, 1);
 				}
 			}
 		}
 	}
+//	const int cycles = int(cycles_left_.flush<Cycles>().as_integral());
+//	for(int c = 0; c < 4; ++c) {
+//		if(timers_[c].mode >= TimerMode::Delay) {
+//			const int dividend = (cycles + timers_[c].prescale - timers_[c].divisor);
+//			const int decrements = dividend / timers_[c].prescale;
+//			timers_[c].divisor = timers_[c].prescale - (dividend % timers_[c].prescale);
+//			if(decrements) decrement_timer(c, decrements);
+//		}
+//	}
 }
 
 HalfCycles MFP68901::get_next_sequence_point() {
@@ -224,21 +233,23 @@ void MFP68901::set_timer_event_input(int channel, bool value) {
 
 	timers_[channel].event_input = value;
 	if(timers_[channel].mode == TimerMode::EventCount && !value) {	/* TODO: which edge is counted? "as defined by the associated Interrupt Channel’s edge bit"?  */
-		decrement_timer(channel);
+		decrement_timer(channel, 1);
 	}
 }
 
-void MFP68901::decrement_timer(int timer) {
-	--timers_[timer].value;
-	if(!timers_[timer].value) {
-		switch(timer) {
-			case 0: begin_interrupts(Interrupt::TimerA);	break;
-			case 1: begin_interrupts(Interrupt::TimerB);	break;
-			case 2: begin_interrupts(Interrupt::TimerC);	break;
-			case 3: begin_interrupts(Interrupt::TimerD);	break;
-		}
-		if(timers_[timer].mode == TimerMode::Delay) {
-			timers_[timer].value = timers_[timer].reload_value;
+void MFP68901::decrement_timer(int timer, int amount) {
+	while(amount--) {
+		--timers_[timer].value;
+		if(timers_[timer].value < 1) {
+			switch(timer) {
+				case 0: begin_interrupts(Interrupt::TimerA);	break;
+				case 1: begin_interrupts(Interrupt::TimerB);	break;
+				case 2: begin_interrupts(Interrupt::TimerC);	break;
+				case 3: begin_interrupts(Interrupt::TimerD);	break;
+			}
+			if(timers_[timer].mode == TimerMode::Delay) {
+				timers_[timer].value += timers_[timer].reload_value;	// TODO: properly.
+			}
 		}
 	}
 }
diff --git a/Components/68901/MFP68901.hpp b/Components/68901/MFP68901.hpp
index 88b423afa..a9d4325ff 100644
--- a/Components/68901/MFP68901.hpp
+++ b/Components/68901/MFP68901.hpp
@@ -11,6 +11,7 @@
 
 #include <cstdint>
 #include "../../ClockReceiver/ClockReceiver.hpp"
+#include "../../ClockReceiver/ClockingHintSource.hpp"
 
 namespace Motorola {
 namespace MFP68901 {
@@ -20,7 +21,7 @@ class PortHandler {
 		// TODO: announce changes in output.
 };
 
-class MFP68901 {
+class MFP68901: public ClockingHint::Source {
 	public:
 		uint8_t read(int address);
 		void write(int address, uint8_t value);
@@ -42,6 +43,9 @@ class MFP68901 {
 		};
 		void set_interrupt_delegate(InterruptDelegate *delegate);
 
+		// ClockingHint::Source.
+		ClockingHint::Preference preferred_clocking() final;
+
 	private:
 		// MARK: - Timers
 		enum class TimerMode {
@@ -50,7 +54,7 @@ class MFP68901 {
 		void set_timer_mode(int timer, TimerMode, int prescale, bool reset_timer);
 		void set_timer_data(int timer, uint8_t);
 		uint8_t get_timer_data(int timer);
-		void decrement_timer(int timer);
+		void decrement_timer(int timer, int amount);
 
 		struct Timer {
 			TimerMode mode = TimerMode::Stopped;
diff --git a/Machines/AtariST/AtariST.cpp b/Machines/AtariST/AtariST.cpp
index 047b504cf..338ea7283 100644
--- a/Machines/AtariST/AtariST.cpp
+++ b/Machines/AtariST/AtariST.cpp
@@ -278,6 +278,7 @@ class ConcreteMachine:
 			midi_acia_->set_clocking_hint_observer(this);
 			keyboard_acia_->set_clocking_hint_observer(this);
 			ikbd_.set_clocking_hint_observer(this);
+			mfp_->set_clocking_hint_observer(this);
 
 			mfp_->set_interrupt_delegate(this);
 			dma_->set_interrupt_delegate(this);
@@ -573,22 +574,34 @@ class ConcreteMachine:
 
 	private:
 		forceinline void advance_time(HalfCycles length) {
+			// Advance the relevant counters.
 			cycles_since_audio_update_ += length;
 			mfp_ += length;
 			dma_ += length;
-
 			keyboard_acia_ += length;
 			midi_acia_ += length;
-			if(!may_defer_acias_) {
-				keyboard_acia_.flush();
-				midi_acia_.flush();
-			}
 
+			// Don't even count time for the keyboard unless it has requested it.
 			if(keyboard_needs_clock_) {
 				cycles_since_ikbd_update_ += length;
 				ikbd_.run_for(cycles_since_ikbd_update_.divide(HalfCycles(512)));
 			}
 
+			// Flush anything that needs real-time updating.
+			if(!may_defer_acias_) {
+				keyboard_acia_.flush();
+				midi_acia_.flush();
+			}
+
+			if(mfp_is_realtime_) {
+				mfp_.flush();
+			}
+
+			if(dma_is_realtime_) {
+				dma_.flush();
+			}
+
+			// Update the video output, checking whether a sequence point has been hit.
 			while(length >= cycles_until_video_event_) {
 				length -= cycles_until_video_event_;
 				video_ += cycles_until_video_event_;
@@ -635,6 +648,8 @@ class ConcreteMachine:
 		// MARK: - Clocking Management.
 		bool may_defer_acias_ = true;
 		bool keyboard_needs_clock_ = false;
+		bool mfp_is_realtime_ = false;
+		bool dma_is_realtime_ = false;
 		void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) final {
 			// This is being called by one of the components; avoid any time flushing here as that's
 			// already dealt with (and, just to be absolutely sure, to avoid recursive mania).
@@ -642,6 +657,7 @@ class ConcreteMachine:
 				(keyboard_acia_.last_valid()->preferred_clocking() != ClockingHint::Preference::RealTime) &&
 				(midi_acia_.last_valid()->preferred_clocking() != ClockingHint::Preference::RealTime);
 			keyboard_needs_clock_ = ikbd_.preferred_clocking() != ClockingHint::Preference::None;
+			mfp_is_realtime_ = mfp_.last_valid()->preferred_clocking() == ClockingHint::Preference::RealTime;
 		}
 
 		// MARK: - GPIP input.