mirror of
				https://github.com/TomHarte/CLK.git
				synced 2025-10-31 05:16:08 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			182 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			182 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| //
 | |
| //  MacintoshDoubleDensityDrive.cpp
 | |
| //  Clock Signal
 | |
| //
 | |
| //  Created by Thomas Harte on 10/07/2019.
 | |
| //  Copyright © 2019 Thomas Harte. All rights reserved.
 | |
| //
 | |
| 
 | |
| #include "MacintoshDoubleDensityDrive.hpp"
 | |
| 
 | |
| /*
 | |
| 	Sources used pervasively:
 | |
| 
 | |
| 	http://members.iinet.net.au/~kalandi/apple/AUG/1991/11%20NOV.DEC/DISK.STUFF.html
 | |
| 	Apple Guide to the Macintosh Family Hardware
 | |
| 	Inside Macintosh III
 | |
| */
 | |
| 
 | |
| using namespace Apple::Macintosh;
 | |
| 
 | |
| DoubleDensityDrive::DoubleDensityDrive(int input_clock_rate, bool is_800k) :
 | |
| 	IWMDrive(input_clock_rate, is_800k ? 2 : 1),	// Only 800kb drives are double sided.
 | |
| 	is_800k_(is_800k) {
 | |
| 	// Start with a valid rotation speed.
 | |
| 	if(is_800k) {
 | |
| 		Drive::set_rotation_speed(393.3807f);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // MARK: - Speed Selection
 | |
| 
 | |
| void DoubleDensityDrive::did_step(Storage::Disk::HeadPosition to_position) {
 | |
| 	// The 800kb drive automatically selects rotation speed as a function of
 | |
| 	// head position; the 400kb drive doesn't do so.
 | |
| 	if(is_800k_) {
 | |
| 		/*
 | |
| 			Numbers below cribbed from the Kryoflux forums; specifically:
 | |
| 			https://forum.kryoflux.com/viewtopic.php?t=1090
 | |
| 
 | |
| 			They can almost be worked out algorithmically, since the point is to
 | |
| 			produce an almost-constant value for speed*(number of sectors), and:
 | |
| 
 | |
| 			393.3807 * 12 = 4720.5684
 | |
| 			429.1723 * 11 = 4720.895421
 | |
| 			472.1435 * 10 = 4721.435
 | |
| 			524.5672 * 9 = 4721.1048
 | |
| 			590.1098 * 8 = 4720.8784
 | |
| 
 | |
| 			So 4721 / (number of sectors per track in zone) would give essentially
 | |
| 			the same results.
 | |
| 		*/
 | |
| 		const int zone = to_position.as_int() >> 4;
 | |
| 		switch(zone) {
 | |
| 			case 0:		Drive::set_rotation_speed(393.3807f);	break;
 | |
| 			case 1:		Drive::set_rotation_speed(429.1723f);	break;
 | |
| 			case 2:		Drive::set_rotation_speed(472.1435f);	break;
 | |
| 			case 3:		Drive::set_rotation_speed(524.5672f);	break;
 | |
| 			default:	Drive::set_rotation_speed(590.1098f);	break;
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void DoubleDensityDrive::set_rotation_speed(float revolutions_per_minute) {
 | |
| 	if(!is_800k_) {
 | |
| 		// Don't allow drive speeds to drop below 10 RPM, as a temporary sop
 | |
| 		// to sanity.
 | |
| 		Drive::set_rotation_speed(std::max(10.0f, revolutions_per_minute));
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // MARK: - Control input/output.
 | |
| 
 | |
| void DoubleDensityDrive::set_enabled(bool enabled) {
 | |
| 	// Disabling a drive also stops its motor.
 | |
| 	if(!enabled) set_motor_on(false);
 | |
| }
 | |
| 
 | |
| void DoubleDensityDrive::set_control_lines(int lines) {
 | |
| 	const auto old_state = control_state_;
 | |
| 	control_state_ = lines;
 | |
| 
 | |
| 	// Catch low-to-high LSTRB transitions.
 | |
| 	if((old_state ^ control_state_) & control_state_ & Line::LSTRB) {
 | |
| 		switch(control_state_ & (Line::CA2 | Line::CA1 | Line::CA0 | Line::SEL)) {
 | |
| 			default:
 | |
| 			break;
 | |
| 
 | |
| 			case 0:						// Set step direction — CA2 set => step outward.
 | |
| 			case Line::CA2:
 | |
| 				step_direction_ = (control_state_ & Line::CA2) ? -1 : 1;
 | |
| 			break;
 | |
| 
 | |
| 			case Line::CA1:				// Set drive motor — CA2 set => motor off.
 | |
| 			case Line::CA1|Line::CA2:
 | |
| 				set_motor_on(!(control_state_ & Line::CA2));
 | |
| 			break;
 | |
| 
 | |
| 			case Line::CA0:				// Initiate a step.
 | |
| 				step(Storage::Disk::HeadPosition(step_direction_));
 | |
| 			break;
 | |
| 
 | |
| 			case Line::SEL|Line::CA2:	// Reset new disk flag.
 | |
| 				has_new_disk_ = false;
 | |
| 			break;
 | |
| 
 | |
| 			case Line::CA2 | Line::CA1 | Line::CA0:	// Eject the disk.
 | |
| 				set_disk(nullptr);
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| bool DoubleDensityDrive::read() {
 | |
| 	switch(control_state_ & (CA2 | CA1 | CA0 | SEL)) {
 | |
| 		default:
 | |
| 		return false;
 | |
| 
 | |
| 		case 0:					// Head step direction.
 | |
| 								// (0 = inward)
 | |
| 		return step_direction_ <= 0;
 | |
| 
 | |
| 		case SEL:				// Disk in place.
 | |
| 								// (0 = disk present)
 | |
| 		return !has_disk();
 | |
| 
 | |
| 		case CA0:				// Disk head step completed.
 | |
| 								// (0 = still stepping)
 | |
| 		return true;	// TODO: stepping delay. But at the main Drive level.
 | |
| 
 | |
| 		case CA0|SEL:			// Disk locked.
 | |
| 								// (0 = write protected)
 | |
| 		return !get_is_read_only();
 | |
| 
 | |
| 		case CA1:				// Disk motor running.
 | |
| 								// (0 = motor on)
 | |
| 		return !get_motor_on();
 | |
| 
 | |
| 		case CA1|SEL:			// Head at track 0.
 | |
| 								// (0 = at track 0)
 | |
| 								// "This bit becomes valid beginning 12 msec after the step that places the head at track 0."
 | |
| 		return !get_is_track_zero();
 | |
| 
 | |
| 		case CA1|CA0:			// Disk has been ejected.
 | |
| 								// (1 = user has ejected disk)
 | |
| 								//
 | |
| 								// TODO: does this really mean _user_ has ejected disk? If so then I should avoid
 | |
| 								// changing the flag upon a programmatic eject.
 | |
| 		return has_new_disk_;
 | |
| 
 | |
| 		case CA1|CA0|SEL:		// Tachometer.
 | |
| 								// (arbitrary)
 | |
| 		return get_tachometer();
 | |
| 
 | |
| 		case CA2:				// Read data, lower head.
 | |
| 			set_head(0);
 | |
| 		return false;
 | |
| 
 | |
| 		case CA2|SEL:			// Read data, upper head.
 | |
| 			set_head(1);
 | |
| 		return false;
 | |
| 
 | |
| 		case CA2|CA1:			// Single- or double-sided drive.
 | |
| 								// (0 = single sided)
 | |
| 		return get_head_count() != 1;
 | |
| 
 | |
| 		case CA2|CA1|CA0:		// "Present/HD" (per the Mac Plus ROM)
 | |
| 								// (0 = ??HD??)
 | |
| 								//
 | |
| 								// Alternative explanation: "Disk ready for reading?"
 | |
| 								// (0 = ready)
 | |
| 		return false;
 | |
| 
 | |
| 		case CA2|CA1|CA0|SEL:	// Drive installed.
 | |
| 								// (0 = present, 1 = missing)
 | |
| 		return false;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void DoubleDensityDrive::did_set_disk(bool did_replace) {
 | |
| 	has_new_disk_ = did_replace;
 | |
| }
 |