1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-12-12 08:30:05 +00:00
CLK/Storage/Disk/Controller/DiskController.cpp
2024-04-30 22:18:17 -04:00

142 lines
3.8 KiB
C++

//
// DiskController.cpp
// Clock Signal
//
// Created by Thomas Harte on 14/07/2016.
// Copyright 2016 Thomas Harte. All rights reserved.
//
#include "DiskController.hpp"
using namespace Storage::Disk;
Controller::Controller(Cycles clock_rate) :
clock_rate_multiplier_(128000000 / clock_rate.as_integral()),
clock_rate_(clock_rate.as_integral() * clock_rate_multiplier_),
pll_(100, *this),
empty_drive_(int(clock_rate.as_integral()), 1, 1),
drive_(&empty_drive_) {
empty_drive_.set_clocking_hint_observer(this);
set_expected_bit_length(Time(1));
}
void Controller::set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference) {
update_clocking_observer();
}
ClockingHint::Preference Controller::preferred_clocking() const {
// Nominate RealTime clocking if any drive currently wants any clocking whatsoever.
// Otherwise, ::None will do.
for(auto &drive: drives_) {
const auto preferred_clocking = drive->preferred_clocking();
if(preferred_clocking != ClockingHint::Preference::None) {
return ClockingHint::Preference::RealTime;
}
}
if(empty_drive_.preferred_clocking() != ClockingHint::Preference::None) {
return ClockingHint::Preference::RealTime;
}
return ClockingHint::Preference::None;
}
void Controller::run_for(const Cycles cycles) {
for(auto &drive: drives_) {
drive->run_for(cycles);
}
empty_drive_.run_for(cycles);
}
Drive &Controller::get_drive() {
return *drive_;
}
const Drive &Controller::get_drive() const {
return *drive_;
}
// MARK: - Drive::EventDelegate
void Controller::process_event(const Drive::Event &event) {
switch(event.type) {
case Track::Event::FluxTransition: pll_.add_pulse(); break;
case Track::Event::IndexHole: process_index_hole(); break;
}
}
void Controller::advance(const Cycles cycles) {
if(is_reading_) pll_.run_for(Cycles(cycles.as_integral() * clock_rate_multiplier_));
}
void Controller::process_write_completed() {
// Provided for subclasses to override.
}
// MARK: - PLL control and delegate
void Controller::set_expected_bit_length(Time bit_length) {
bit_length_ = bit_length;
bit_length_.simplify();
Time cycles_per_bit = Storage::Time(int(clock_rate_)) * bit_length;
cycles_per_bit.simplify();
// this conversion doesn't need to be exact because there's a lot of variation to be taken
// account of in rotation speed, air turbulence, etc, so a direct conversion will do
const int clocks_per_bit = cycles_per_bit.get<int>();
pll_.set_clocks_per_bit(clocks_per_bit);
}
void Controller::digital_phase_locked_loop_output_bit(int value) {
if(is_reading_) process_input_bit(value);
}
void Controller::set_drive(int index_mask) {
if(drive_selection_mask_ == index_mask) {
return;
}
const ClockingHint::Preference former_preference = preferred_clocking();
// Stop receiving events from the current drive.
get_drive().set_event_delegate(nullptr);
// TODO: a transfer of writing state, if writing?
if(!index_mask) {
drive_ = &empty_drive_;
} else {
// TEMPORARY FIX: connect up only the first selected drive.
// TODO: at least merge events if multiple drives are selected. Some computers have
// controllers that allow this, with usually meaningless results as far as I can
// imagine. But the limit of an emulator shouldn't be the author's imagination.
size_t index = 0;
while(!(index_mask&1)) {
index_mask >>= 1;
++index;
}
drive_ = drives_[index].get();
}
get_drive().set_event_delegate(this);
if(preferred_clocking() != former_preference) {
update_clocking_observer();
}
}
void Controller::begin_writing(bool clamp_to_index_hole) {
is_reading_ = false;
get_drive().begin_writing(bit_length_, clamp_to_index_hole);
}
void Controller::end_writing() {
if(!is_reading_) {
is_reading_ = true;
get_drive().end_writing();
}
}
bool Controller::is_reading() {
return is_reading_;
}