mirror of
https://github.com/TomHarte/CLK.git
synced 2024-12-13 00:29:14 +00:00
263 lines
6.8 KiB
C++
263 lines
6.8 KiB
C++
//
|
|
// Disk.cpp
|
|
// Clock Signal
|
|
//
|
|
// Created by Thomas Harte on 02/11/2021.
|
|
// Copyright © 2021 Thomas Harte. All rights reserved.
|
|
//
|
|
|
|
#include "Chipset.hpp"
|
|
|
|
#include "../../Outputs/Log.hpp"
|
|
|
|
namespace {
|
|
Log::Logger<Log::Source::AmigaDisk> logger;
|
|
}
|
|
|
|
using namespace Amiga;
|
|
|
|
// MARK: - Disk DMA.
|
|
|
|
void Chipset::DiskDMA::enqueue(uint16_t value, bool matches_sync) {
|
|
if(matches_sync && state_ == State::WaitingForSync) {
|
|
state_ = State::Reading;
|
|
return;
|
|
}
|
|
|
|
if(state_ == State::Reading) {
|
|
buffer_[buffer_write_ & 3] = value;
|
|
if(buffer_write_ == buffer_read_ + 4) {
|
|
++buffer_read_;
|
|
}
|
|
++buffer_write_;
|
|
}
|
|
}
|
|
|
|
void Chipset::DiskDMA::set_control(uint16_t control) {
|
|
sync_with_word_ = control & 0x400;
|
|
}
|
|
|
|
void Chipset::DiskDMA::set_length(uint16_t value) {
|
|
if(value == last_set_length_) {
|
|
dma_enable_ = value & 0x8000;
|
|
write_ = value & 0x4000;
|
|
length_ = value & 0x3fff;
|
|
buffer_read_ = buffer_write_ = 0;
|
|
|
|
if(dma_enable_) {
|
|
logger.info().append("Disk DMA %s of %d to %08x", write_ ? "write" : "read", length_, pointer_[0]);
|
|
}
|
|
|
|
state_ = sync_with_word_ ? State::WaitingForSync : State::Reading;
|
|
}
|
|
|
|
last_set_length_ = value;
|
|
}
|
|
|
|
bool Chipset::DiskDMA::advance_dma() {
|
|
if(!dma_enable_) return false;
|
|
|
|
if(!write_) {
|
|
if(length_ && buffer_read_ != buffer_write_) {
|
|
ram_[pointer_[0] & ram_mask_] = buffer_[buffer_read_ & 3];
|
|
++pointer_[0];
|
|
++buffer_read_;
|
|
--length_;
|
|
|
|
if(!length_) {
|
|
chipset_.posit_interrupt(InterruptFlag::DiskBlock);
|
|
state_ = State::Inactive;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// MARK: - Disk Controller.
|
|
|
|
Chipset::DiskController::DiskController(Cycles clock_rate, Chipset &chipset, DiskDMA &disk_dma, CIAB &cia) :
|
|
Storage::Disk::Controller(clock_rate),
|
|
chipset_(chipset),
|
|
disk_dma_(disk_dma),
|
|
cia_(cia) {
|
|
|
|
// Add four drives.
|
|
for(int c = 0; c < 4; c++) {
|
|
emplace_drive(clock_rate.as<int>(), 300, 2, Storage::Disk::Drive::ReadyType::IBMRDY);
|
|
}
|
|
}
|
|
|
|
void Chipset::DiskController::process_input_bit(int value) {
|
|
data_ = uint16_t((data_ << 1) | value);
|
|
++bit_count_;
|
|
|
|
const bool sync_matches = data_ == sync_word_;
|
|
if(sync_matches) {
|
|
chipset_.posit_interrupt(InterruptFlag::DiskSyncMatch);
|
|
|
|
if(sync_with_word_) {
|
|
bit_count_ = 0;
|
|
}
|
|
}
|
|
|
|
if(!(bit_count_ & 15)) {
|
|
disk_dma_.enqueue(data_, sync_matches);
|
|
}
|
|
}
|
|
|
|
void Chipset::DiskController::set_sync_word(uint16_t value) {
|
|
logger.info().append("Set disk sync word to %04x", value);
|
|
sync_word_ = value;
|
|
}
|
|
|
|
void Chipset::DiskController::set_control(uint16_t control) {
|
|
// b13 and b14: precompensation length specifier
|
|
// b12: 0 => GCR precompensation; 1 => MFM.
|
|
// b10: 1 => enable use of word sync; 0 => disable.
|
|
// b9: 1 => sync on MSB (Disk II style, presumably?); 0 => don't.
|
|
// b8: 1 => 2µs per bit; 0 => 4µs.
|
|
|
|
sync_with_word_ = control & 0x400;
|
|
|
|
Storage::Time bit_length;
|
|
bit_length.length = 1;
|
|
bit_length.clock_rate = (control & 0x100) ? 500000 : 250000;
|
|
set_expected_bit_length(bit_length);
|
|
|
|
logger.info().append("%s sync with word; bit length is %s", sync_with_word_ ? "Will" : "Won't", (control & 0x100) ? "short" : "long");
|
|
}
|
|
|
|
void Chipset::DiskController::process_index_hole() {
|
|
// Pulse the CIA flag input.
|
|
//
|
|
// TODO: rectify once drives do an actual index pulse, with length.
|
|
cia_.set_flag_input(true);
|
|
cia_.set_flag_input(false);
|
|
|
|
// Resync word output. Experimental!!
|
|
bit_count_ = 0;
|
|
}
|
|
|
|
void Chipset::DiskController::set_mtr_sel_side_dir_step(uint8_t value) {
|
|
// b7: /MTR
|
|
// b6: /SEL3
|
|
// b5: /SEL2
|
|
// b4: /SEL1
|
|
// b3: /SEL0
|
|
// b2: /SIDE
|
|
// b1: DIR
|
|
// b0: /STEP
|
|
|
|
// Select active drive.
|
|
set_drive(((value >> 3) & 0x0f) ^ 0x0f);
|
|
|
|
// "[The MTR] signal is nonstandard on the Amiga system.
|
|
// Each drive will latch the motor signal at the time its
|
|
// select signal turns on." — The Hardware Reference Manual.
|
|
const auto difference = int(previous_select_ ^ value);
|
|
previous_select_ = value;
|
|
|
|
// Check for changes in the SEL line per drive.
|
|
const bool motor_on = !(value & 0x80);
|
|
const int side = (value & 0x04) ? 0 : 1;
|
|
const bool did_step = difference & value & 0x01;
|
|
const auto direction = Storage::Disk::HeadPosition(
|
|
(value & 0x02) ? -1 : 1
|
|
);
|
|
|
|
for(int c = 0; c < 4; c++) {
|
|
auto &drive = get_drive(size_t(c));
|
|
const int select_mask = 0x08 << c;
|
|
const bool is_selected = !(value & select_mask);
|
|
|
|
// Both the motor state and the ID shifter are affected upon
|
|
// the trailing edge of changes in drive selection only.
|
|
if(difference & select_mask & ~value) {
|
|
// If transitioning to inactive, shift the drive ID value;
|
|
// if transitioning to active, possibly reset the drive
|
|
// ID and definitely latch the new motor state.
|
|
if(!is_selected) {
|
|
drive_ids_[c] <<= 1;
|
|
logger.info().append("Shifted drive ID shift register for drive %d to %08x", c, drive_ids_[c]);
|
|
} else {
|
|
// Motor transition on -> off => reload register.
|
|
if(!motor_on && drive.get_motor_on()) {
|
|
// NB:
|
|
// 0xffff'ffff = 3.5" drive;
|
|
// 0x5555'5555 = 5.25" drive;
|
|
// 0x0000'0000 = no drive.
|
|
drive_ids_[c] = 0xffff'ffff;
|
|
logger.info().append("Reloaded drive ID shift register for drive %d", c);
|
|
}
|
|
|
|
// Also latch the new motor state.
|
|
drive.set_motor_on(motor_on);
|
|
}
|
|
}
|
|
|
|
// Set the new side.
|
|
drive.set_head(side);
|
|
|
|
// Possibly step.
|
|
if(did_step && is_selected) {
|
|
logger.info().append("Stepped drive %d by %d", c, direction.as_int());
|
|
drive.step(direction);
|
|
}
|
|
}
|
|
}
|
|
|
|
uint8_t Chipset::DiskController::get_rdy_trk0_wpro_chng() {
|
|
// b5: /RDY
|
|
// b4: /TRK0
|
|
// b3: /WPRO
|
|
// b2: /CHNG
|
|
|
|
// My interpretation:
|
|
//
|
|
// RDY isn't RDY, it's a shift value as described above, combined with the motor state.
|
|
// CHNG is what is normally RDY.
|
|
|
|
const uint32_t combined_id =
|
|
((previous_select_ & 0x40) ? 0 : drive_ids_[3]) |
|
|
((previous_select_ & 0x20) ? 0 : drive_ids_[2]) |
|
|
((previous_select_ & 0x10) ? 0 : drive_ids_[1]) |
|
|
((previous_select_ & 0x08) ? 0 : drive_ids_[0]);
|
|
|
|
auto &drive = get_drive();
|
|
const uint8_t active_high =
|
|
((combined_id & 0x8000) >> 10) |
|
|
(drive.get_motor_on() ? 0x20 : 0x00) |
|
|
(drive.get_is_ready() ? 0x00 : 0x04) |
|
|
(drive.get_is_track_zero() ? 0x10 : 0x00) |
|
|
(drive.get_is_read_only() ? 0x08 : 0x00);
|
|
|
|
return ~active_high;
|
|
}
|
|
|
|
void Chipset::DiskController::set_activity_observer(Activity::Observer *observer) {
|
|
for_all_drives([observer] (Storage::Disk::Drive &drive, size_t index) {
|
|
drive.set_activity_observer(observer, "Drive " + std::to_string(index+1), true);
|
|
});
|
|
}
|
|
|
|
bool Chipset::DiskController::insert(const std::shared_ptr<Storage::Disk::Disk> &disk, size_t drive) {
|
|
if(drive >= 4) return false;
|
|
get_drive(drive).set_disk(disk);
|
|
return true;
|
|
}
|
|
|
|
bool Chipset::insert(const std::vector<std::shared_ptr<Storage::Disk::Disk>> &disks) {
|
|
bool inserted = false;
|
|
|
|
size_t target = 0;
|
|
for(const auto &disk: disks) {
|
|
inserted |= disk_controller_.insert(disk, target);
|
|
++target;
|
|
}
|
|
|
|
return inserted;
|
|
}
|