using System.Collections.Generic; namespace Jellyfish.Virtu { public interface IDiskIIController : IPeripheralCard { bool DriveLight { get; set; } // ReSharper disable once UnusedMemberInSuper.Global DiskIIDrive Drive1 { get; } // ReSharper disable once UnusedMember.Global void Sync(IComponentSerializer ser); } // ReSharper disable once UnusedMember.Global public sealed class DiskIIController : IDiskIIController { private const int Phase1On = 1 << 1; private readonly IVideo _video; private readonly byte[] _romRegionC1C7; private bool _driveLight; private int _latch; private int _phaseStates; private bool _motorOn; private int _driveNumber; private bool _loadMode; private bool _writeMode; private bool _driveSpin; public DiskIIController(IVideo video, byte[] diskIIRom) { _video = video; _romRegionC1C7 = diskIIRom; Drive1 = new DiskIIDrive(this); Drive2 = new DiskIIDrive(this); _phaseStates = 0; SetMotorOn(false); SetDriveNumber(0); _loadMode = false; _writeMode = false; } public DiskIIDrive Drive1 { get; } public DiskIIDrive Drive2 { get; } public bool DriveLight { get => _driveLight; set => _driveLight = value; } public void Sync(IComponentSerializer ser) { ser.Sync(nameof(_driveLight), ref _driveLight); ser.Sync(nameof(_latch), ref _latch); ser.Sync(nameof(_phaseStates), ref _phaseStates); ser.Sync(nameof(_motorOn), ref _motorOn); ser.Sync(nameof(_driveNumber), ref _driveNumber); ser.Sync(nameof(_loadMode), ref _loadMode); ser.Sync(nameof(_writeMode), ref _writeMode); ser.Sync(nameof(_driveSpin), ref _driveSpin); Drive1.Sync(ser); Drive2.Sync(ser); } public IList Drives => new List { Drive1, Drive2 }; public void WriteIoRegionC8CF(int address, int data) => _video.ReadFloatingBus(); public int ReadIoRegionC0C0(int address) { switch (address & 0xF) { case 0x0: case 0x1: case 0x2: case 0x3: case 0x4: case 0x5: case 0x6: case 0x7: SetPhase(address); break; case 0x8: SetMotorOn(false); break; case 0x9: SetMotorOn(true); break; case 0xA: SetDriveNumber(0); break; case 0xB: SetDriveNumber(1); break; case 0xC: _loadMode = false; if (_motorOn) { if (!_writeMode) { return _latch = Drives[_driveNumber].Read(); } WriteLatch(); } break; case 0xD: _loadMode = true; if (_motorOn && !_writeMode) { // write protect is forced if phase 1 is on [F9.7] _latch &= 0x7F; if (Drives[_driveNumber].IsWriteProtected || (_phaseStates & Phase1On) != 0) { _latch |= 0x80; } } break; case 0xE: _writeMode = false; break; case 0xF: _writeMode = true; break; } if ((address & 1) == 0) { // only even addresses return the latch if (_motorOn) { return _latch; } // simple hack to fool DOS SAMESLOT drive spin check (usually at $BD34) _driveSpin = !_driveSpin; return _driveSpin ? 0x7E : 0x7F; } return _video.ReadFloatingBus(); } public int ReadIoRegionC1C7(int address) { return _romRegionC1C7[address & 0xFF]; } public int ReadIoRegionC8CF(int address) => _video.ReadFloatingBus(); public void WriteIoRegionC0C0(int address, int data) { switch (address & 0xF) { case 0x0: case 0x1: case 0x2: case 0x3: case 0x4: case 0x5: case 0x6: case 0x7: SetPhase(address); break; case 0x8: SetMotorOn(false); break; case 0x9: SetMotorOn(true); break; case 0xA: SetDriveNumber(0); break; case 0xB: SetDriveNumber(1); break; case 0xC: _loadMode = false; if (_writeMode) { WriteLatch(); } break; case 0xD: _loadMode = true; break; case 0xE: _writeMode = false; break; case 0xF: _writeMode = true; break; } if (_motorOn && _writeMode) { if (_loadMode) { // any address writes latch for sequencer LD; OE1/2 irrelevant ['323 datasheet] _latch = data; } } } public void WriteIoRegionC1C7(int address, int data) { } private void WriteLatch() { // write protect is forced if phase 1 is on [F9.7] if ((_phaseStates & Phase1On) == 0) { Drives[_driveNumber].Write(_latch); } } private void Flush() { Drives[_driveNumber].FlushTrack(); } private void SetDriveNumber(int driveNumber) { if (_driveNumber != driveNumber) { Flush(); _driveNumber = driveNumber; } } private void SetMotorOn(bool state) { if (_motorOn && !state) { Flush(); } _motorOn = state; } private void SetPhase(int address) { int phase = (address >> 1) & 0x3; int state = address & 1; _phaseStates &= ~(1 << phase); _phaseStates |= (state << phase); if (_motorOn) { Drives[_driveNumber].ApplyPhaseChange(_phaseStates); } } } }