2019-05-06 01:55:34 +00:00
|
|
|
//
|
|
|
|
// IWM.cpp
|
|
|
|
// Clock Signal
|
|
|
|
//
|
|
|
|
// Created by Thomas Harte on 05/05/2019.
|
|
|
|
// Copyright © 2019 Thomas Harte. All rights reserved.
|
|
|
|
//
|
|
|
|
|
|
|
|
#include "IWM.hpp"
|
|
|
|
|
2019-05-07 21:16:22 +00:00
|
|
|
#include <cstdio>
|
|
|
|
|
2019-05-06 01:55:34 +00:00
|
|
|
using namespace Apple;
|
|
|
|
|
2019-05-31 02:17:49 +00:00
|
|
|
namespace {
|
|
|
|
const int CA0 = 1 << 0;
|
|
|
|
const int CA1 = 1 << 1;
|
|
|
|
const int CA2 = 1 << 2;
|
|
|
|
const int LSTRB = 1 << 3;
|
|
|
|
const int ENABLE = 1 << 4;
|
|
|
|
const int DRIVESEL = 1 << 5; /* This means drive select, like on the original Disk II. */
|
|
|
|
const int Q6 = 1 << 6;
|
|
|
|
const int Q7 = 1 << 7;
|
|
|
|
const int SEL = 1 << 8; /* This is an additional input, not available on a Disk II, with a confusingly-similar name to SELECT but a distinct purpose. */
|
2019-05-06 01:55:34 +00:00
|
|
|
}
|
|
|
|
|
2019-06-01 22:43:47 +00:00
|
|
|
IWM::IWM(int clock_rate) :
|
|
|
|
clock_rate_(clock_rate),
|
|
|
|
drives_{
|
|
|
|
{static_cast<unsigned int>(clock_rate), 300, 2},
|
|
|
|
{static_cast<unsigned int>(clock_rate), 300, 2}
|
|
|
|
} {}
|
2019-05-31 02:17:49 +00:00
|
|
|
|
|
|
|
// MARK: - Bus accessors
|
|
|
|
|
2019-05-06 01:55:34 +00:00
|
|
|
uint8_t IWM::read(int address) {
|
|
|
|
access(address);
|
|
|
|
|
2019-05-31 02:17:49 +00:00
|
|
|
// Per Inside Macintosh:
|
|
|
|
//
|
|
|
|
// "Before you can read from any of the disk registers you must set up the state of the IWM so that it
|
|
|
|
// can pass the data through to the MC68000's address space where you'll be able to read it. To do that,
|
|
|
|
// you must first turn off Q7 by reading or writing dBase+q7L. Then turn on Q6 by accessing dBase+q6H.
|
|
|
|
// After that, the IWM will be able to pass data from the disk's RD/SENSE line through to you."
|
|
|
|
//
|
2019-06-01 18:39:40 +00:00
|
|
|
// My understanding:
|
|
|
|
//
|
|
|
|
// Q6 = 1, Q7 = 0 reads the status register. The meaning of the top 'SENSE' bit is then determined by
|
|
|
|
// the CA0,1,2 and SEL switches as described in Inside Macintosh, summarised above as RD/SENSE.
|
2019-05-06 01:55:34 +00:00
|
|
|
|
2019-06-01 18:39:40 +00:00
|
|
|
if(address&1) {
|
|
|
|
return 0xff;
|
|
|
|
}
|
2019-05-06 01:55:34 +00:00
|
|
|
|
2019-05-31 02:17:49 +00:00
|
|
|
switch(state_ & (Q6 | Q7 | ENABLE)) {
|
|
|
|
default:
|
|
|
|
printf("Invalid read\n");
|
|
|
|
return 0xff;
|
|
|
|
|
|
|
|
// "Read all 1s".
|
|
|
|
case 0:
|
|
|
|
printf("Reading all 1s\n");
|
|
|
|
return 0xff;
|
|
|
|
|
|
|
|
case ENABLE: /* Read data register. */
|
|
|
|
printf("Reading data register\n");
|
2019-06-01 22:43:47 +00:00
|
|
|
return 0xff;
|
2019-05-31 02:17:49 +00:00
|
|
|
|
|
|
|
case Q6: case Q6|ENABLE: {
|
|
|
|
/*
|
|
|
|
[If A = 0], Read status register:
|
|
|
|
|
2019-06-01 22:43:47 +00:00
|
|
|
bits 0-4: same as mode register.
|
2019-05-31 02:17:49 +00:00
|
|
|
bit 5: 1 = either /ENBL1 or /ENBL2 is currently low.
|
|
|
|
bit 6: 1 = MZ (reserved for future compatibility; should always be read as 0).
|
|
|
|
bit 7: 1 = SENSE input high; 0 = SENSE input low.
|
|
|
|
|
|
|
|
(/ENBL1 is low when the first drive's motor is on; /ENBL2 is low when the second drive's motor is on.
|
|
|
|
If the 1-second timer is enabled, motors remain on for one second after being programmatically disabled.)
|
|
|
|
*/
|
2019-06-01 22:43:47 +00:00
|
|
|
printf("Reading status (including ");
|
2019-05-31 02:17:49 +00:00
|
|
|
|
|
|
|
// Determine the SENSE input.
|
2019-06-01 22:43:47 +00:00
|
|
|
uint8_t sense = 0x00;
|
2019-05-31 02:17:49 +00:00
|
|
|
switch(state_ & (CA2 | CA1 | CA0 | SEL)) {
|
|
|
|
default:
|
|
|
|
printf("unknown)\n");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0: // Head step direction.
|
|
|
|
printf("head step direction)\n");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SEL: // Disk in place.
|
|
|
|
printf("disk in place)\n");
|
2019-06-01 22:43:47 +00:00
|
|
|
sense = drives_[active_drive_].has_disk() ? 0x00 : 0x80;
|
2019-05-31 02:17:49 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case CA0: // Disk head stepping.
|
|
|
|
printf("head stepping)\n");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case CA0|SEL: // Disk locked (i.e. write-protect tab).
|
|
|
|
printf("disk locked)\n");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case CA1: // Disk motor running.
|
|
|
|
printf("disk motor running)\n");
|
2019-06-01 22:43:47 +00:00
|
|
|
sense = drives_[active_drive_].get_motor_on() ? 0x00 : 0x80;
|
2019-05-31 02:17:49 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case CA1|SEL: // Head at track 0.
|
|
|
|
printf("head at track 0)\n");
|
2019-06-01 22:43:47 +00:00
|
|
|
sense = drives_[active_drive_].get_is_track_zero() ? 0x00 : 0x80;
|
2019-05-31 02:17:49 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case CA1|CA0|SEL: // Tachometer (?)
|
|
|
|
printf("tachometer)\n");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case CA2: // Read data, lower head.
|
|
|
|
printf("data, lower head)\n");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case CA2|SEL: // Read data, upper head.
|
|
|
|
printf("data, upper head)\n");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case CA2|CA1: // Single- or double-sided drive.
|
|
|
|
printf("single- or double-sided drive)\n");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case CA2|CA1|CA0|SEL: // Drive installed.
|
|
|
|
printf("drive installed)\n");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2019-06-01 22:43:47 +00:00
|
|
|
return
|
|
|
|
(mode_&0x1f) |
|
|
|
|
(drives_[active_drive_].get_motor_on() ? 0x20 : 0x00) |
|
|
|
|
sense;
|
2019-05-31 02:17:49 +00:00
|
|
|
} break;
|
|
|
|
|
|
|
|
case Q7: case Q7|ENABLE:
|
|
|
|
/*
|
|
|
|
Read write-handshake register:
|
|
|
|
|
|
|
|
bits 0-5: reserved for future use (currently read as 1).
|
|
|
|
bit 6: 1 = write state (cleared to 0 if a write underrun occurs).
|
|
|
|
bit 7: 1 = write data buffer ready for data.
|
|
|
|
*/
|
|
|
|
printf("Reading write handshake\n");
|
|
|
|
return 0x1f;
|
2019-05-06 01:55:34 +00:00
|
|
|
}
|
2019-05-31 02:17:49 +00:00
|
|
|
|
|
|
|
return 0xff;
|
2019-05-06 01:55:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void IWM::write(int address, uint8_t input) {
|
|
|
|
access(address);
|
|
|
|
|
2019-05-31 02:17:49 +00:00
|
|
|
switch(state_ & (Q6 | Q7 | ENABLE)) {
|
|
|
|
default: break;
|
|
|
|
|
|
|
|
case Q7|Q6:
|
|
|
|
/*
|
|
|
|
Write mode register:
|
|
|
|
|
|
|
|
bit 0: 1 = latch mode (should be set in asynchronous mode).
|
|
|
|
bit 1: 0 = synchronous handshake protocol; 1 = asynchronous.
|
|
|
|
bit 2: 0 = 1-second on-board timer enable; 1 = timer disable.
|
|
|
|
bit 3: 0 = slow mode; 1 = fast mode.
|
|
|
|
bit 4: 0 = 7Mhz; 1 = 8Mhz (7 or 8 mHz clock descriptor).
|
|
|
|
bit 5: 1 = test mode; 0 = normal operation.
|
|
|
|
bit 6: 1 = MZ-reset.
|
|
|
|
bit 7: reserved for future expansion.
|
|
|
|
*/
|
2019-06-01 23:08:29 +00:00
|
|
|
|
2019-05-06 01:55:34 +00:00
|
|
|
mode_ = input;
|
2019-06-01 23:08:29 +00:00
|
|
|
|
|
|
|
switch(mode_ & 0x18) {
|
|
|
|
case 0x00: bit_length_ = Cycles(24); break; // slow mode, 7Mhz
|
|
|
|
case 0x08: bit_length_ = Cycles(12); break; // fast mode, 7Mhz
|
|
|
|
case 0x10: bit_length_ = Cycles(32); break; // slow mode, 8Mhz
|
|
|
|
case 0x18: bit_length_ = Cycles(16); break; // fast mode, 8Mhz
|
|
|
|
}
|
2019-05-31 02:17:49 +00:00
|
|
|
printf("IWM mode is now %02x\n", mode_);
|
2019-05-06 01:55:34 +00:00
|
|
|
break;
|
|
|
|
|
2019-05-31 02:17:49 +00:00
|
|
|
case Q7|Q6|ENABLE: // Write data register.
|
|
|
|
printf("Data register write\n");
|
2019-05-06 01:55:34 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-31 02:17:49 +00:00
|
|
|
// MARK: - Switch access
|
2019-05-06 01:55:34 +00:00
|
|
|
|
2019-05-31 02:17:49 +00:00
|
|
|
void IWM::access(int address) {
|
|
|
|
// Keep a record of switch state; bits in state_
|
|
|
|
// should correlate with the anonymous namespace constants
|
|
|
|
// defined at the top of this file — CA0, CA1, etc.
|
|
|
|
address &= 0xf;
|
|
|
|
const auto mask = 1 << (address >> 1);
|
|
|
|
|
|
|
|
if(address & 1) {
|
|
|
|
state_ |= mask;
|
|
|
|
} else {
|
|
|
|
state_ &= ~mask;
|
2019-05-06 01:55:34 +00:00
|
|
|
}
|
2019-06-01 22:43:47 +00:00
|
|
|
|
|
|
|
// React appropriately to motor requests.
|
|
|
|
switch(address >> 1) {
|
|
|
|
default: break;
|
|
|
|
|
|
|
|
case 4:
|
|
|
|
if(address & 1) {
|
|
|
|
drives_[active_drive_].set_motor_on(true);
|
|
|
|
} else {
|
|
|
|
// If the 1-second delay is enabled, set up a timer for that.
|
|
|
|
if(!(mode_ & 4)) {
|
|
|
|
cycles_until_motor_off_ = Cycles(clock_rate_);
|
|
|
|
} else {
|
|
|
|
drives_[active_drive_].set_motor_on(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 5: {
|
|
|
|
const int new_drive = address & 1;
|
|
|
|
if(new_drive != active_drive_) {
|
|
|
|
drives_[new_drive].set_motor_on(drives_[active_drive_].get_motor_on());
|
|
|
|
drives_[active_drive_].set_motor_on(false);
|
|
|
|
active_drive_ = new_drive;
|
|
|
|
}
|
|
|
|
} break;
|
|
|
|
}
|
2019-05-06 01:55:34 +00:00
|
|
|
}
|
|
|
|
|
2019-05-31 02:17:49 +00:00
|
|
|
void IWM::set_select(bool enabled) {
|
|
|
|
// Augment switch state with the value of the SEL line;
|
|
|
|
// it's active low, which is implicitly inverted here for
|
|
|
|
// consistency in the meaning of state_ bits.
|
|
|
|
if(!enabled) state_ |= 0x100;
|
|
|
|
else state_ &= ~0x100;
|
2019-05-06 01:55:34 +00:00
|
|
|
}
|
2019-05-30 16:08:00 +00:00
|
|
|
|
2019-05-31 02:17:49 +00:00
|
|
|
// MARK: - Active logic
|
|
|
|
|
|
|
|
void IWM::run_for(const Cycles cycles) {
|
2019-06-01 22:43:47 +00:00
|
|
|
if(cycles_until_motor_off_ > Cycles(0)) {
|
|
|
|
cycles_until_motor_off_ -= cycles;
|
|
|
|
if(cycles_until_motor_off_ <= Cycles(0)) {
|
|
|
|
drives_[active_drive_].set_motor_on(false);
|
|
|
|
}
|
|
|
|
}
|
2019-05-30 16:08:00 +00:00
|
|
|
}
|