openMSX
TC8566AF.cc
Go to the documentation of this file.
1 /*
2  * Based on code from NLMSX written by Frits Hilderink
3  * and blueMSX written by Daniel Vik
4  */
5 
6 #include "TC8566AF.hh"
7 #include "DiskDrive.hh"
8 #include "Clock.hh"
9 #include "CliComm.hh"
10 #include "MSXException.hh"
11 #include "serialize.hh"
12 #include <cstring>
13 
14 namespace openmsx {
15 
16 static const byte STM_DB0 = 0x01; // FDD 0 Busy
17 static const byte STM_DB1 = 0x02; // FDD 1 Busy
18 static const byte STM_DB2 = 0x04; // FDD 2 Busy
19 static const byte STM_DB3 = 0x08; // FDD 3 Busy
20 static const byte STM_CB = 0x10; // FDC Busy
21 static const byte STM_NDM = 0x20; // Non-DMA mode
22 static const byte STM_DIO = 0x40; // Data Input/Output
23 static const byte STM_RQM = 0x80; // Request for Master
24 
25 static const byte ST0_DS0 = 0x01; // Drive Select 0,1
26 static const byte ST0_DS1 = 0x02; //
27 static const byte ST0_HD = 0x04; // Head Address
28 static const byte ST0_NR = 0x08; // Not Ready
29 static const byte ST0_EC = 0x10; // Equipment Check
30 static const byte ST0_SE = 0x20; // Seek End
31 static const byte ST0_IC0 = 0x40; // Interrupt Code
32 static const byte ST0_IC1 = 0x80; //
33 
34 static const byte ST1_MA = 0x01; // Missing Address Mark
35 static const byte ST1_NW = 0x02; // Not Writable
36 static const byte ST1_ND = 0x04; // No Data
37 // = 0x08; // -
38 static const byte ST1_OR = 0x10; // Over Run
39 static const byte ST1_DE = 0x20; // Data Error
40 // = 0x40; // -
41 static const byte ST1_EN = 0x80; // End of Cylinder
42 
43 static const byte ST2_MD = 0x01; // Missing Address Mark in Data Field
44 static const byte ST2_BC = 0x02; // Bad Cylinder
45 static const byte ST2_SN = 0x04; // Scan Not Satisfied
46 static const byte ST2_SH = 0x08; // Scan Equal Satisfied
47 static const byte ST2_NC = 0x10; // No cylinder
48 static const byte ST2_DD = 0x20; // Data Error in Data Field
49 static const byte ST2_CM = 0x40; // Control Mark
50 // = 0x80; // -
51 
52 static const byte ST3_DS0 = 0x01; // Drive Select 0
53 static const byte ST3_DS1 = 0x02; // Drive Select 1
54 static const byte ST3_HD = 0x04; // Head Address
55 static const byte ST3_2S = 0x08; // Two Side
56 static const byte ST3_TK0 = 0x10; // Track 0
57 static const byte ST3_RDY = 0x20; // Ready
58 static const byte ST3_WP = 0x40; // Write Protect
59 static const byte ST3_FLT = 0x80; // Fault
60 
61 
62 TC8566AF::TC8566AF(Scheduler& scheduler, DiskDrive* drv[4], CliComm& cliComm_,
63  EmuTime::param time)
64  : Schedulable(scheduler)
65  , cliComm(cliComm_)
66  , delayTime(EmuTime::zero)
67  , headUnloadTime(EmuTime::zero) // head not loaded
68 {
69  // avoid UMR (on savestate)
70  dataAvailable = 0;
71  dataCurrent = 0;
72  setDrqRate();
73 
74  drive[0] = drv[0];
75  drive[1] = drv[1];
76  drive[2] = drv[2];
77  drive[3] = drv[3];
78  reset(time);
79 }
80 
82 {
83  drive[0]->setMotor(false, time);
84  drive[1]->setMotor(false, time);
85  drive[2]->setMotor(false, time);
86  drive[3]->setMotor(false, time);
87  //enableIntDma = 0;
88  //notReset = 1;
89  driveSelect = 0;
90 
91  status0 = 0;
92  status1 = 0;
93  status2 = 0;
94  status3 = 0;
95  commandCode = 0;
96  command = CMD_UNKNOWN;
97  phase = PHASE_IDLE;
98  phaseStep = 0;
99  cylinderNumber = 0;
100  headNumber = 0;
101  sectorNumber = 0;
102  number = 0;
103  currentTrack = 0;
104  sectorsPerCylinder = 0;
105  fillerByte = 0;
106  gapLength = 0;
107  specifyData[0] = 0; // TODO check
108  specifyData[1] = 0; // TODO check
109  seekValue = 0;
110  headUnloadTime = EmuTime::zero; // head not loaded
111 
112  mainStatus = STM_RQM;
113  //interrupt = false;
114 }
115 
117 {
118  switch (reg) {
119  case 4: // Main Status Register
120  return peekStatus();
121  case 5: // data port
122  return peekDataPort(time);
123  }
124  return 0xff;
125 }
126 
128 {
129  switch (reg) {
130  case 4: // Main Status Register
131  return readStatus(time);
132  case 5: // data port
133  return readDataPort(time);
134  }
135  return 0xff;
136 }
137 
138 byte TC8566AF::peekStatus() const
139 {
140  bool nonDMAMode = specifyData[1] & 1;
141  bool dma = nonDMAMode && (phase == PHASE_DATATRANSFER);
142  return mainStatus | (dma ? STM_NDM : 0);
143 }
144 
145 byte TC8566AF::readStatus(EmuTime::param time)
146 {
147  if (delayTime.before(time)) {
148  mainStatus |= STM_RQM;
149  }
150  return peekStatus();
151 }
152 
153 void TC8566AF::setDrqRate()
154 {
155  delayTime.setFreq(trackData.getLength() * DiskDrive::ROTATIONS_PER_SECOND);
156 }
157 
158 byte TC8566AF::peekDataPort(EmuTime::param time) const
159 {
160  switch (phase) {
161  case PHASE_DATATRANSFER:
162  return executionPhasePeek(time);
163  case PHASE_RESULT:
164  return resultsPhasePeek();
165  default:
166  return 0xff;
167  }
168 }
169 
170 byte TC8566AF::readDataPort(EmuTime::param time)
171 {
172  //interrupt = false;
173  switch (phase) {
174  case PHASE_DATATRANSFER:
175  if (delayTime.before(time)) {
176  return executionPhaseRead(time);
177  } else {
178  return 0xff; // TODO check this
179  }
180  case PHASE_RESULT:
181  return resultsPhaseRead(time);
182  default:
183  return 0xff;
184  }
185 }
186 
187 byte TC8566AF::executionPhasePeek(EmuTime::param time) const
188 {
189  switch (command) {
190  case CMD_READ_DATA:
191  if (delayTime.before(time)) {
192  assert(dataAvailable);
193  return trackData.read(dataCurrent);
194  } else {
195  return 0xff; // TODO check this
196  }
197  default:
198  return 0xff;
199  }
200 }
201 
202 byte TC8566AF::executionPhaseRead(EmuTime::param time)
203 {
204  switch (command) {
205  case CMD_READ_DATA: {
206  assert(dataAvailable);
207  byte result = trackData.read(dataCurrent++);
208  crc.update(result);
209  --dataAvailable;
210  delayTime += 1; // time when next byte will be available
211  mainStatus &= ~STM_RQM;
212  if (delayTime.before(time)) {
213  // lost data
214  status0 |= ST0_IC0;
215  status1 |= ST1_OR;
216  resultPhase();
217  } else if (!dataAvailable) {
218  // check crc error
219  word diskCrc = 256 * trackData.read(dataCurrent++);
220  diskCrc += trackData.read(dataCurrent++);
221  if (diskCrc != crc.getValue()) {
222  status0 |= ST0_IC0;
223  status1 |= ST1_DE;
224  status2 |= ST2_DD;
225  }
226  resultPhase();
227  }
228  return result;
229  }
230  default:
231  return 0xff;
232  }
233 }
234 
235 byte TC8566AF::resultsPhasePeek() const
236 {
237  switch (command) {
238  case CMD_READ_DATA:
239  case CMD_WRITE_DATA:
240  case CMD_FORMAT:
241  switch (phaseStep) {
242  case 0:
243  return status0;
244  case 1:
245  return status1;
246  case 2:
247  return status2;
248  case 3:
249  return cylinderNumber;
250  case 4:
251  return headNumber;
252  case 5:
253  return sectorNumber;
254  case 6:
255  return number;
256  }
257  break;
258 
260  switch (phaseStep) {
261  case 0:
262  return status0;
263  case 1:
264  return currentTrack;
265  }
266  break;
267 
269  switch (phaseStep) {
270  case 0:
271  return status3;
272  }
273  break;
274  default:
275  // nothing
276  break;
277  }
278  return 0xff;
279 }
280 
281 byte TC8566AF::resultsPhaseRead(EmuTime::param time)
282 {
283  byte result = resultsPhasePeek();
284  switch (command) {
285  case CMD_READ_DATA:
286  case CMD_WRITE_DATA:
287  case CMD_FORMAT:
288  switch (phaseStep++) {
289  case 6:
290  endCommand(time);
291  break;
292  }
293  break;
294 
296  switch (phaseStep++) {
297  case 1:
298  endCommand(time);
299  break;
300  }
301  break;
302 
304  switch (phaseStep++) {
305  case 0:
306  endCommand(time);
307  break;
308  }
309  break;
310  default:
311  // nothing
312  break;
313  }
314  return result;
315 }
316 
317 void TC8566AF::writeReg(int reg, byte data, EmuTime::param time)
318 {
319  switch (reg) {
320  case 2: // control register 0
321  drive[3]->setMotor((data & 0x80) != 0, time);
322  drive[2]->setMotor((data & 0x40) != 0, time);
323  drive[1]->setMotor((data & 0x20) != 0, time);
324  drive[0]->setMotor((data & 0x10) != 0, time);
325  //enableIntDma = data & 0x08;
326  //notReset = data & 0x04;
327  driveSelect = data & 0x03;
328  break;
329 
330  //case 3: // control register 1
331  // controlReg1 = data;
332  // break;
333 
334  case 5: // data port
335  writeDataPort(data, time);
336  break;
337  }
338 }
339 
340 void TC8566AF::writeDataPort(byte value, EmuTime::param time)
341 {
342  switch (phase) {
343  case PHASE_IDLE:
344  idlePhaseWrite(value, time);
345  break;
346 
347  case PHASE_COMMAND:
348  commandPhaseWrite(value, time);
349  break;
350 
351  case PHASE_DATATRANSFER:
352  executionPhaseWrite(value, time);
353  break;
354  default:
355  // nothing
356  break;
357  }
358 }
359 
360 void TC8566AF::idlePhaseWrite(byte value, EmuTime::param time)
361 {
362  command = CMD_UNKNOWN;
363  commandCode = value;
364  if ((commandCode & 0x1f) == 0x06) command = CMD_READ_DATA;
365  if ((commandCode & 0x3f) == 0x05) command = CMD_WRITE_DATA;
366  if ((commandCode & 0x3f) == 0x09) command = CMD_WRITE_DELETED_DATA;
367  if ((commandCode & 0x1f) == 0x0c) command = CMD_READ_DELETED_DATA;
368  if ((commandCode & 0xbf) == 0x02) command = CMD_READ_DIAGNOSTIC;
369  if ((commandCode & 0xbf) == 0x0a) command = CMD_READ_ID;
370  if ((commandCode & 0xbf) == 0x0d) command = CMD_FORMAT;
371  if ((commandCode & 0x1f) == 0x11) command = CMD_SCAN_EQUAL;
372  if ((commandCode & 0x1f) == 0x19) command = CMD_SCAN_LOW_OR_EQUAL;
373  if ((commandCode & 0x1f) == 0x1d) command = CMD_SCAN_HIGH_OR_EQUAL;
374  if ((commandCode & 0xff) == 0x0f) command = CMD_SEEK;
375  if ((commandCode & 0xff) == 0x07) command = CMD_RECALIBRATE;
376  if ((commandCode & 0xff) == 0x08) command = CMD_SENSE_INTERRUPT_STATUS;
377  if ((commandCode & 0xff) == 0x03) command = CMD_SPECIFY;
378  if ((commandCode & 0xff) == 0x04) command = CMD_SENSE_DEVICE_STATUS;
379 
380  phase = PHASE_COMMAND;
381  phaseStep = 0;
382  mainStatus |= STM_CB;
383 
384  switch (command) {
385  case CMD_READ_DATA:
386  case CMD_WRITE_DATA:
387  case CMD_FORMAT:
388  status0 &= ~(ST0_IC0 | ST0_IC1);
389  status1 = 0;
390  status2 = 0;
391  //MT = value & 0x80;
392  //MFM = value & 0x40;
393  //SK = value & 0x20;
394  break;
395 
396  case CMD_RECALIBRATE:
397  status0 &= ~ST0_SE;
398  break;
399 
401  resultPhase();
402  break;
403 
404  case CMD_SEEK:
405  case CMD_SPECIFY:
407  break;
408 
409  default:
410  endCommand(time);
411  }
412 }
413 
414 void TC8566AF::commandPhase1(byte value)
415 {
416  drive[driveSelect]->setSide((value & 0x04) != 0);
417  status0 &= ~(ST0_DS0 | ST0_DS1 | ST0_IC0 | ST0_IC1);
418  status0 |= //(drive[driveSelect]->isDiskInserted() ? 0 : ST0_DS0) |
419  (value & (ST0_DS0 | ST0_DS1)) |
420  (drive[driveSelect]->isDummyDrive() ? ST0_IC1 : 0);
421  status3 = (value & (ST3_DS0 | ST3_DS1)) |
422  (drive[driveSelect]->isTrack00() ? ST3_TK0 : 0) |
423  (drive[driveSelect]->isDoubleSided() ? ST3_HD : 0) |
424  (drive[driveSelect]->isWriteProtected() ? ST3_WP : 0) |
425  (drive[driveSelect]->isDiskInserted() ? ST3_RDY : 0);
426 }
427 
428 EmuTime TC8566AF::locateSector(EmuTime::param time)
429 {
430  RawTrack::Sector sectorInfo;
431  int lastIdx = -1;
432  EmuTime next = time;
433  while (true) {
434  try {
435  next = drive[driveSelect]->getNextSector(
436  next, trackData, sectorInfo);
437  setDrqRate();
438  } catch (MSXException& /*e*/) {
439  return EmuTime::infinity;
440  }
441  if ((next == EmuTime::infinity) ||
442  (sectorInfo.addrIdx == lastIdx)) {
443  // no sectors on track or sector already seen
444  return EmuTime::infinity;
445  }
446  if (lastIdx == -1) lastIdx = sectorInfo.addrIdx;
447  if (sectorInfo.addrCrcErr) continue;
448  if (sectorInfo.track != cylinderNumber) continue;
449  if (sectorInfo.head != headNumber) continue;
450  if (sectorInfo.sector != sectorNumber) continue;
451  break;
452  }
453  // TODO does TC8566AF look at lower 3 bits?
454  dataAvailable = 128 << (sectorInfo.sizeCode & 7);
455  dataCurrent = sectorInfo.dataIdx;
456  return next;
457 }
458 
459 void TC8566AF::commandPhaseWrite(byte value, EmuTime::param time)
460 {
461  switch (command) {
462  case CMD_READ_DATA:
463  case CMD_WRITE_DATA:
464  switch (phaseStep++) {
465  case 0:
466  commandPhase1(value);
467  break;
468  case 1:
469  cylinderNumber = value;
470  break;
471  case 2:
472  headNumber = value;
473  break;
474  case 3:
475  sectorNumber = value;
476  break;
477  case 4:
478  number = value;
479  break;
480  case 5: // End Of Track
481  break;
482  case 6: // Gap Length
483  break;
484  case 7: // Data length
485  phase = PHASE_DATATRANSFER;
486  phaseStep = 0;
487  //interrupt = true;
488 
489  // load drive head, if not already loaded
490  EmuTime ready = time;
491  if (!isHeadLoaded(time)) {
492  ready += getHeadLoadDelay();
493  // set 'head is loaded'
494  headUnloadTime = EmuTime::infinity;
495  }
496 
497  // actually read sector: fills in
498  // trackData, dataAvailable and dataCurrent
499  ready = locateSector(ready);
500  if (ready == EmuTime::infinity) {
501  status0 |= ST0_IC0;
502  status1 |= ST1_ND;
503  resultPhase();
504  return;
505  }
506  if (command == CMD_READ_DATA) {
507  mainStatus |= STM_DIO;
508  } else {
509  mainStatus &= ~STM_DIO;
510  }
511  // Initialize crc
512  // TODO 0xFB vs 0xF8 depends on deleted vs normal data
513  crc.init<0xA1, 0xA1, 0xA1, 0xFB>();
514 
515  // first byte is available when it's rotated below the
516  // drive-head
517  delayTime.reset(ready);
518  mainStatus &= ~STM_RQM;
519  break;
520  }
521  break;
522 
523  case CMD_FORMAT:
524  switch (phaseStep++) {
525  case 0:
526  commandPhase1(value);
527  break;
528  case 1:
529  number = value;
530  break;
531  case 2:
532  sectorsPerCylinder = value;
533  sectorNumber = value;
534  break;
535  case 3:
536  gapLength = value;
537  break;
538  case 4:
539  fillerByte = value;
540  mainStatus &= ~STM_DIO;
541  phase = PHASE_DATATRANSFER;
542  phaseStep = 0;
543  //interrupt = true;
544  initTrackHeader(time);
545  break;
546  }
547  break;
548 
549  case CMD_SEEK:
550  switch (phaseStep++) {
551  case 0:
552  commandPhase1(value);
553  break;
554  case 1:
555  seekValue = value; // target track
556  doSeek(time);
557  break;
558  }
559  break;
560 
561  case CMD_RECALIBRATE:
562  switch (phaseStep++) {
563  case 0:
564  commandPhase1(value);
565  seekValue = 255; // max try 255 steps
566  doSeek(time);
567  break;
568  }
569  break;
570 
571  case CMD_SPECIFY:
572  specifyData[phaseStep] = value;
573  switch (phaseStep++) {
574  case 1:
575  endCommand(time);
576  break;
577  }
578  break;
579 
581  switch (phaseStep++) {
582  case 0:
583  commandPhase1(value);
584  resultPhase();
585  break;
586  }
587  break;
588  default:
589  // nothing
590  break;
591  }
592 }
593 
594 void TC8566AF::initTrackHeader(EmuTime::param time)
595 {
596  try {
597  // get track length, see comment in WD2793 for details.
598  drive[driveSelect]->readTrack(trackData);
599  } catch (MSXException& /*e*/) {
600  endCommand(time);
601  }
602  trackData.clear(trackData.getLength());
603  setDrqRate();
604  dataCurrent = 0;
605  dataAvailable = trackData.getLength();
606 
607  for (int i = 0; i < 80; ++i) trackData.write(dataCurrent++, 0x4E); // gap4a
608  for (int i = 0; i < 12; ++i) trackData.write(dataCurrent++, 0x00); // sync
609  for (int i = 0; i < 3; ++i) trackData.write(dataCurrent++, 0xC2); // index mark
610  for (int i = 0; i < 1; ++i) trackData.write(dataCurrent++, 0xFC); // " "
611  for (int i = 0; i < 50; ++i) trackData.write(dataCurrent++, 0x4E); // gap1
612 }
613 
614 void TC8566AF::formatSector()
615 {
616  for (int i = 0; i < 12; ++i) trackData.write(dataCurrent++, 0x00); // sync
617 
618  for (int i = 0; i < 3; ++i) trackData.write(dataCurrent++, 0xA1); // addr mark
619  trackData.addIdam(dataCurrent);
620  for (int i = 0; i < 1; ++i) trackData.write(dataCurrent++, 0xFE); // " "
621  trackData.write(dataCurrent++, currentTrack); // C: Cylinder number
622  trackData.write(dataCurrent++, headNumber); // H: Head Address
623  trackData.write(dataCurrent++, sectorNumber); // R: Record
624  trackData.write(dataCurrent++, number); // N: Length of sector
625  word addrCrc = trackData.calcCrc(dataCurrent - 8, 8);
626  trackData.write(dataCurrent++, addrCrc >> 8); // CRC (high byte)
627  trackData.write(dataCurrent++, addrCrc & 0xff); // (low byte)
628 
629  for (int i = 0; i < 22; ++i) trackData.write(dataCurrent++, 0x4E); // gap2
630  for (int i = 0; i < 12; ++i) trackData.write(dataCurrent++, 0x00); // sync
631 
632  for (int i = 0; i < 3; ++i) trackData.write(dataCurrent++, 0xA1); // data mark
633  for (int i = 0; i < 1; ++i) trackData.write(dataCurrent++, 0xFB); // " "
634 
635  int sectorSize = 128 << (number & 7); // 2 -> 512bytes
636  for (int i = 0; i < sectorSize; ++i) trackData.write(dataCurrent++, fillerByte);
637 
638  word dataCrc = trackData.calcCrc(dataCurrent - (sectorSize + 4), sectorSize + 4);
639  trackData.write(dataCurrent++, dataCrc >> 8); // CRC (high byte)
640  trackData.write(dataCurrent++, dataCrc & 0xff); // (low byte)
641 
642  for (int i = 0; i < gapLength; ++i) trackData.write(dataCurrent++, 0x4E); // gap3
643 }
644 
645 void TC8566AF::doSeek(EmuTime::param time)
646 {
647  DiskDrive& currentDrive = *drive[driveSelect];
648 
649  bool direction = false; // initialize to avoid warning
650  switch (command) {
651  case CMD_SEEK:
652  if (seekValue > currentTrack) {
653  ++currentTrack;
654  direction = true;
655  } else if (seekValue < currentTrack) {
656  --currentTrack;
657  direction = false;
658  } else {
659  assert(seekValue == currentTrack);
660  status0 |= ST0_SE;
661  endCommand(time);
662  return;
663  }
664  break;
665  case CMD_RECALIBRATE:
666  if (currentDrive.isTrack00() || (seekValue == 0)) {
667  if (seekValue == 0) {
668  status0 |= ST0_EC;
669  }
670  currentTrack = 0;
671  status0 |= ST0_SE;
672  endCommand(time);
673  return;
674  }
675  direction = false;
676  --seekValue;
677  break;
678  default:
679  UNREACHABLE;
680  }
681 
682  currentDrive.step(direction, time);
683 
684  setSyncPoint(time + getSeekDelay());
685 }
686 
687 void TC8566AF::executeUntil(EmuTime::param time, int /*userData*/)
688 {
689  if ((command == CMD_SEEK) ||
690  (command == CMD_RECALIBRATE)) {
691  doSeek(time);
692  }
693 }
694 
695 void TC8566AF::writeSector()
696 {
697  // write 2 CRC bytes (big endian)
698  trackData.write(dataCurrent++, crc.getValue() >> 8);
699  trackData.write(dataCurrent++, crc.getValue() & 0xFF);
700  drive[driveSelect]->writeTrack(trackData);
701 }
702 
703 void TC8566AF::executionPhaseWrite(byte value, EmuTime::param time)
704 {
705  switch (command) {
706  case CMD_WRITE_DATA:
707  assert(dataAvailable);
708  trackData.write(dataCurrent++, value);
709  crc.update(value);
710  --dataAvailable;
711  delayTime += 1; // time when next byte can be written
712  mainStatus &= ~STM_RQM;
713  if (delayTime.before(time)) {
714  // lost data
715  status0 |= ST0_IC0;
716  status1 |= ST1_OR;
717  resultPhase();
718  } else if (!dataAvailable) {
719  try {
720  writeSector();
721  } catch (MSXException&) {
722  status0 |= ST0_IC0;
723  status1 |= ST1_NW;
724  }
725  resultPhase();
726  }
727  break;
728 
729  case CMD_FORMAT:
730  delayTime += 1; // correct?
731  mainStatus &= ~STM_RQM;
732  switch (phaseStep & 3) {
733  case 0:
734  currentTrack = value;
735  break;
736  case 1:
737  headNumber = value;
738  break;
739  case 2:
740  sectorNumber = value;
741  break;
742  case 3:
743  number = value;
744  formatSector();
745  break;
746  }
747  ++phaseStep;
748 
749  if (phaseStep == 4 * sectorsPerCylinder) {
750  // data for all sectors was written, now write track
751  try {
752  drive[driveSelect]->writeTrack(trackData);
753  } catch (MSXException&) {
754  status0 |= ST0_IC0;
755  status1 |= ST1_NW;
756  }
757  resultPhase();
758  }
759  break;
760  default:
761  // nothing
762  break;
763  }
764 }
765 
766 void TC8566AF::resultPhase()
767 {
768  mainStatus |= STM_DIO | STM_RQM;
769  phase = PHASE_RESULT;
770  phaseStep = 0;
771  //interrupt = true;
772 }
773 
774 void TC8566AF::endCommand(EmuTime::param time)
775 {
776  phase = PHASE_IDLE;
777  mainStatus &= ~(STM_CB | STM_DIO);
778  delayTime.reset(time); // set STM_RQM
779  if (headUnloadTime == EmuTime::infinity) {
780  headUnloadTime = time + getHeadUnloadDelay();
781  }
782 }
783 
784 bool TC8566AF::diskChanged(unsigned driveNum)
785 {
786  assert(driveNum < 4);
787  return drive[driveNum]->diskChanged();
788 }
789 
790 bool TC8566AF::peekDiskChanged(unsigned driveNum) const
791 {
792  assert(driveNum < 4);
793  return drive[driveNum]->peekDiskChanged();
794 }
795 
796 
797 bool TC8566AF::isHeadLoaded(EmuTime::param time) const
798 {
799  return time < headUnloadTime;
800 }
801 EmuDuration TC8566AF::getHeadLoadDelay() const
802 {
803  return EmuDuration::msec(2 * (specifyData[1] >> 1)); // 2ms per unit
804 }
805 EmuDuration TC8566AF::getHeadUnloadDelay() const
806 {
807  return EmuDuration::msec(16 * (specifyData[0] & 0x0F)); // 16ms per unit
808 }
809 
810 EmuDuration TC8566AF::getSeekDelay() const
811 {
812  return EmuDuration::msec(16 - (specifyData[0] >> 4)); // 1ms per unit
813 }
814 
815 
816 static enum_string<TC8566AF::Command> commandInfo[] = {
817  { "UNKNOWN", TC8566AF::CMD_UNKNOWN },
818  { "READ_DATA", TC8566AF::CMD_READ_DATA },
819  { "WRITE_DATA", TC8566AF::CMD_WRITE_DATA },
820  { "WRITE_DELETED_DATA", TC8566AF::CMD_WRITE_DELETED_DATA },
821  { "READ_DELETED_DATA", TC8566AF::CMD_READ_DELETED_DATA },
822  { "READ_DIAGNOSTIC", TC8566AF::CMD_READ_DIAGNOSTIC },
823  { "READ_ID", TC8566AF::CMD_READ_ID },
824  { "FORMAT", TC8566AF::CMD_FORMAT },
825  { "SCAN_EQUAL", TC8566AF::CMD_SCAN_EQUAL },
826  { "SCAN_LOW_OR_EQUAL", TC8566AF::CMD_SCAN_LOW_OR_EQUAL },
827  { "SCAN_HIGH_OR_EQUAL", TC8566AF::CMD_SCAN_HIGH_OR_EQUAL },
828  { "SEEK", TC8566AF::CMD_SEEK },
829  { "RECALIBRATE", TC8566AF::CMD_RECALIBRATE },
830  { "SENSE_INTERRUPT_STATUS", TC8566AF::CMD_SENSE_INTERRUPT_STATUS },
831  { "SPECIFY", TC8566AF::CMD_SPECIFY },
832  { "SENSE_DEVICE_STATUS", TC8566AF::CMD_SENSE_DEVICE_STATUS }
833 };
834 SERIALIZE_ENUM(TC8566AF::Command, commandInfo);
835 
836 static enum_string<TC8566AF::Phase> phaseInfo[] = {
837  { "IDLE", TC8566AF::PHASE_IDLE },
838  { "COMMAND", TC8566AF::PHASE_COMMAND },
839  { "DATATRANSFER", TC8566AF::PHASE_DATATRANSFER },
840  { "RESULT", TC8566AF::PHASE_RESULT }
841 };
842 SERIALIZE_ENUM(TC8566AF::Phase, phaseInfo);
843 
844 // version 1: initial version
845 // version 2: added specifyData, headUnloadTime, seekValue
846 // inherit from Schedulable
847 // version 3: Replaced 'sectorSize', 'sectorOffset', 'sectorBuf'
848 // with 'dataAvailable', 'dataCurrent', .trackData'.
849 // Not 100% backwardscompatible, see also comments in WD2793.
850 // Added 'crc' and 'gapLength'.
851 // version 4: changed type of delayTime from Clock to DynamicClock
852 template<typename Archive>
853 void TC8566AF::serialize(Archive& ar, unsigned version)
854 {
855  if (ar.versionAtLeast(version, 4)) {
856  ar.serialize("delayTime", delayTime);
857  } else {
858  assert(ar.isLoader());
860  ar.serialize("delayTime", c);
861  delayTime.reset(c.getTime());
862  delayTime.setFreq(6250 * 5);
863  }
864  ar.serialize("command", command);
865  ar.serialize("phase", phase);
866  ar.serialize("phaseStep", phaseStep);
867  ar.serialize("driveSelect", driveSelect);
868  ar.serialize("mainStatus", mainStatus);
869  ar.serialize("status0", status0);
870  ar.serialize("status1", status1);
871  ar.serialize("status2", status2);
872  ar.serialize("status3", status3);
873  ar.serialize("commandCode", commandCode);
874  ar.serialize("cylinderNumber", cylinderNumber);
875  ar.serialize("headNumber", headNumber);
876  ar.serialize("sectorNumber", sectorNumber);
877  ar.serialize("number", number);
878  ar.serialize("currentTrack", currentTrack);
879  ar.serialize("sectorsPerCylinder", sectorsPerCylinder);
880  ar.serialize("fillerByte", fillerByte);
881  if (ar.versionAtLeast(version, 2)) {
882  ar.template serializeBase<Schedulable>(*this);
883  ar.serialize("specifyData", specifyData);
884  ar.serialize("headUnloadTime", headUnloadTime);
885  ar.serialize("seekValue", seekValue);
886  } else {
887  assert(ar.isLoader());
888  specifyData[0] = 0xDF; // values normally set by TurboR disk rom
889  specifyData[1] = 0x03;
890  headUnloadTime = EmuTime::zero;
891  seekValue = 0;
892  }
893  if (ar.versionAtLeast(version, 3)) {
894  ar.serialize("dataAvailable", dataAvailable);
895  ar.serialize("dataCurrent", dataCurrent);
896  ar.serialize("trackData", trackData);
897  ar.serialize("gapLength", gapLength);
898  word crcVal = crc.getValue();
899  ar.serialize("crc", crcVal);
900  crc.init(crcVal);
901  } else {
902  // Compared to previous versions the buffer managment worked
903  // differently (was sector oriented instead of track oriented).
904  // Converting the old state to the new state is not that easy,
905  // we only give a warning when an old savestate that was in
906  // the middle of a read/write operation is loaded.
907  //ar.serialize("sectorSize", sectorSize);
908  //ar.serialize("sectorOffset", sectorOffset);
909  //ar.serialize_blob("sectorBuf", sectorBuf, sizeof(sectorBuf));
910  //TODO wrning
911  if ((phase == PHASE_DATATRANSFER) &&
912  ((command == CMD_READ_DATA) ||
913  (command == CMD_WRITE_DATA) ||
914  (command == CMD_FORMAT))) {
915  cliComm.printWarning(
916  "Loading an old savestate that had an "
917  "in-progress TC8566AF data-transfer command. "
918  "This is not fully backwards-compatible and "
919  "could cause wrong emulation behavior.");
920  }
921  }
922 };
924 
925 } // namespace openmsx