Epple-II/src/diskcontroller.cpp

157 lines
5.0 KiB
C++

/*
epple2
Copyright (C) 2008 by Christopher A. Mosher <cmosher01@gmail.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY, without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "diskcontroller.h"
DiskController::DiskController(ScreenImage& gui, int slot, bool lss13):
gui(gui),
slot(slot),
currentDrive(&this->drive1),
load(false),
write(false),
ioStepped(false),
lssp6rom(lss13),
seq(0x20), // gotta start somewhere
t(0) {
}
DiskController::~DiskController() {
}
unsigned char DiskController::io(const unsigned short addr, const unsigned char d, const bool writing) {
this->dataBusReadOnlyCopy = d;
const unsigned char q = (addr & 0x000E) >> 1;
const bool on = (addr & 0x0001);
// printf("Q%d<--%s\n", q, on?"ON":"OFF");
switch (q) {
case 0:
case 1: // TODO if phase-1 is on, it also acts as write-protect (UA2, 9-8)
case 2:
case 3:
this->currentDrive->set_phase(q,on);
this->gui.setTrack(this->slot, getCurrentDriveNumber(), getTrack());
break;
case 4:
this->motor.power(on);
break;
case 5:
this->gui.clearCurrentDrive(this->slot, getCurrentDriveNumber());
this->currentDrive = (on ? &this->drive2 : &this->drive1);
this->gui.setCurrentDrive(this->slot, getCurrentDriveNumber(),getTrack(), this->motor.isOn());
break;
case 6:
this->load = on;
break;
case 7:
this->write = on;
break;
}
if (this->write && !this->load) {
this->gui.setDirty(this->slot,getCurrentDriveNumber(),true);
}
// UA2, 9-23, Figure 9.12
// 2 LSS cycles need to happen AFTER setting the Qx switch, and
// BEFORE reading LSS's update of the data register
this->ioStepped = false;
tick();
this->ioStepped = true; // flag that we ran it already
return on ? d : this->dataRegister;
}
/*
* Get a timing cycle here, based on the MPU clock (1 MHz).
* In the real Apple we don't get really get this here. But...
* We need a 2MHz clock for the LSS; and
* we need to rotate the floppy @ 1 bit per 4 microseconds.
* (When the motor is on, that is.)
*/
void DiskController::tick() {
this->drive1.tick();
this->drive2.tick();
if (this->ioStepped) { // if we already ran it, above in io(), skip here
this->ioStepped = false;
return;
}
this->gui.setIO(this->slot, getCurrentDriveNumber(), this->motor.isOn());
if (!this->motor.isOn()) {
this->ioStepped = false;
return;
}
this->motor.tick(); // only need to send tick when motor is powered on
/*
* TODO
* Every CPU clock, add 8 to your bit timing clock. If your bit timing clock is >= optimal bit timing,
* then inject the next bit and subtract the optimal bit timing from your bit timing clock.
* That will give you 125ns resolution on your bits being fed to the sequencer.
*/
rotateCurrentDisk();
// run two LSS cycles = 2MHz
stepLss();
// pulse lasts only 500 nanoseconds (1 LSS clock cycle), so clear it now:
this->currentDrive->clearPulse();
stepLss();
}
void DiskController::rotateCurrentDisk() {
++this->t;
if (4 <= this->t) { // 4us interval between bits
this->currentDrive->rotateDiskOneBit(); // (will also generate a read-pulse when it reads a 1-bit)
this->t = 0;
}
}
void DiskController::stepLss() {
std::uint8_t adr = this->write<<3 | this->load<<2 | (this->dataRegister>>7)<<1 | this->currentDrive->readPulse();
std::uint8_t cmd = this->lssp6rom.read(this->seq|adr);
this->seq = cmd & 0xF0u;
// LSS command functions (UA2, 9-15, Table 9.3)
if (cmd & 8u) {
if (cmd & 3u) {
if (this->write) {
const bool one = (this->seq&0x80u) != (this->prev_seq&0x80u);
this->prev_seq = this->seq;
this->currentDrive->writeBit(one);
}
}
switch (cmd & 3u) {
case 3:
this->dataRegister = this->dataBusReadOnlyCopy;
break;
case 2:
this->dataRegister >>= 1;
this->dataRegister |= (isWriteProtected() << 7);
break;
case 1:
this->dataRegister <<= 1;
this->dataRegister |= ((cmd & 4u) >> 2);
break;
}
} else {
this->dataRegister = 0;
}
}