1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-07-03 11:30:02 +00:00
CLK/Machines/AtariST/DMAController.cpp

169 lines
4.7 KiB
C++
Raw Normal View History

2019-10-27 01:33:57 +00:00
//
// DMAController.cpp
// Clock Signal
//
// Created by Thomas Harte on 26/10/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#include "DMAController.hpp"
#include <cstdio>
using namespace Atari::ST;
2019-11-03 22:24:36 +00:00
namespace {
enum Control: uint16_t {
Direction = 0x100,
DRQSource = 0x80,
SectorCountSelect = 0x10,
CPUTarget = 0x08
};
}
2019-10-27 02:39:11 +00:00
DMAController::DMAController() {
fdc_.set_delegate(this);
fdc_.set_clocking_hint_observer(this);
2019-10-27 01:33:57 +00:00
}
uint16_t DMAController::read(int address) {
switch(address & 7) {
// Reserved.
default: break;
// Disk controller or sector count.
case 2:
2019-11-03 22:24:36 +00:00
if(control_ & Control::SectorCountSelect) {
return uint16_t((byte_count_ + 511) >> 9); // Assumed here: the count is of sectors remaining, i.e. it decrements
// only when a sector is complete.
2019-10-27 01:33:57 +00:00
} else {
2019-11-03 22:24:36 +00:00
if(control_ & Control::CPUTarget) {
return 0xffff;
} else {
return 0xff00 | fdc_.get_register(control_ >> 1);
}
2019-10-27 01:33:57 +00:00
}
break;
// DMA status.
case 3:
2019-11-03 22:24:36 +00:00
// TODO: should DRQ come from the HDC if that mode is selected?
return 0xfff8 | (error_ ? 0 : 1) | (byte_count_ ? 2 : 0) | (fdc_.get_data_request_line() ? 4 : 0);
2019-10-27 01:33:57 +00:00
// DMA addressing.
2019-11-03 22:24:36 +00:00
case 4: return uint16_t(0xff00 | ((address_ >> 16) & 0xff));
case 5: return uint16_t(0xff00 | ((address_ >> 8) & 0xff));
case 6: return uint16_t(0xff00 | ((address_ >> 0) & 0xff));
2019-10-27 01:33:57 +00:00
}
return 0xffff;
}
void DMAController::write(int address, uint16_t value) {
switch(address & 7) {
// Reserved.
default: break;
// Disk controller or sector count.
case 2:
2019-11-03 22:24:36 +00:00
if(control_ & Control::SectorCountSelect) {
byte_count_ = (value & 0xff) << 9; // The computer provides a sector count; that times 512 is a byte count.
// TODO: if this is a write-mode DMA operation, try to fill both buffers, ASAP.
2019-10-27 01:33:57 +00:00
} else {
2019-11-03 22:24:36 +00:00
if(control_ & Control::CPUTarget) {
// TODO: HDC.
} else {
fdc_.set_register(control_ >> 1, uint8_t(value));
}
2019-10-27 01:33:57 +00:00
}
break;
// DMA control; meaning is:
//
2019-11-03 22:24:36 +00:00
// b0: unused
2019-10-27 01:33:57 +00:00
// b1, b2 = address lines for FDC access.
2019-11-03 22:24:36 +00:00
// b3 = 1 => CPU HDC access; 0 => CPU FDC access.
// b4 = 1 => sector count access; 0 => [F/H]DC access.
// b5: unused.
// b6 = officially, 1 => DMA off; 0 => DMA on. Ignored in real hardware.
// b7 = 1 => FDC DRQs being observed; 0 => HDC access DRQs being observed.
// b8 = 1 => DMA is writing to [F/H]DC; 0 => DMA is reading. Changing value resets DMA state.
2019-10-27 01:33:57 +00:00
//
// All other bits: undefined.
case 3:
2019-11-03 22:24:36 +00:00
// Check for a DMA state reset.
if((control_^value) & Control::Direction) {
bytes_received_ = active_buffer_ = 0;
error_ = false;
byte_count_ = 0;
}
2019-10-27 01:33:57 +00:00
control_ = value;
break;
// DMA addressing.
2019-11-03 22:24:36 +00:00
case 4: address_ = int((address_ & 0x00ffff) | ((value & 0xff) << 16)); break;
case 5: address_ = int((address_ & 0xff00ff) | ((value & 0xff) << 8)); break;
case 6: address_ = int((address_ & 0xffff00) | ((value & 0xff) << 0)); break;
2019-10-27 01:33:57 +00:00
}
}
void DMAController::set_floppy_drive_selection(bool drive1, bool drive2, bool side2) {
fdc_.set_floppy_drive_selection(drive1, drive2, side2);
}
void DMAController::set_floppy_disk(std::shared_ptr<Storage::Disk::Disk> disk, size_t drive) {
fdc_.drives_[drive]->set_disk(disk);
}
2019-10-27 01:33:57 +00:00
void DMAController::run_for(HalfCycles duration) {
running_time_ += duration;
fdc_.run_for(duration.flush<Cycles>());
}
void DMAController::wd1770_did_change_output(WD::WD1770 *) {
// Check for a change in interrupt state.
const bool old_interrupt_line = interrupt_line_;
interrupt_line_ = fdc_.get_interrupt_request_line();
if(interrupt_delegate_ && interrupt_line_ != old_interrupt_line) {
interrupt_delegate_->dma_controller_did_change_interrupt_status(this);
}
2019-11-03 22:24:36 +00:00
// Check for a change in DRQ state, if it's the FDC that is currently being watched.
if(fdc_.get_data_request_line()) {
2019-11-03 22:24:36 +00:00
if(!(control_ & Control::DRQSource)) return;
if(control_ & Control::Direction) {
// TODO: DMA is supposed to be helping with a write.
} else {
// DMA is enabling a read.
// Read from the data register into the active buffer.
buffer_[active_buffer_][bytes_received_] = fdc_.get_register(3);
++bytes_received_;
if(bytes_received_ == 16) {
// TODO: BusReq and eventual deposit into RAM?
active_buffer_ ^= 1;
bytes_received_ = 0;
}
}
}
}
void DMAController::set_interrupt_delegate(InterruptDelegate *delegate) {
interrupt_delegate_ = delegate;
}
bool DMAController::get_interrupt_line() {
return interrupt_line_;
}
void DMAController::set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference) {
update_clocking_observer();
}
ClockingHint::Preference DMAController::preferred_clocking() {
return (fdc_.preferred_clocking() == ClockingHint::Preference::None) ? ClockingHint::Preference::None : ClockingHint::Preference::RealTime;
}