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 "RawTrack.hh"
9 #include "Clock.hh"
10 #include "CliComm.hh"
11 #include "MSXException.hh"
12 #include "serialize.hh"
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(RawTrack::STANDARD_SIZE);
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(unsigned trackLength)
154 {
155  delayTime.setFreq(trackLength * 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 drive[driveSelect]->readTrackByte(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  auto* drv = drive[driveSelect];
208  byte result = drv->readTrackByte(dataCurrent++);
209  crc.update(result);
210  --dataAvailable;
211  delayTime += 1; // time when next byte will be available
212  mainStatus &= ~STM_RQM;
213  if (delayTime.before(time)) {
214  // lost data
215  status0 |= ST0_IC0;
216  status1 |= ST1_OR;
217  resultPhase();
218  } else if (!dataAvailable) {
219  // check crc error
220  word diskCrc = 256 * drv->readTrackByte(dataCurrent++);
221  diskCrc += drv->readTrackByte(dataCurrent++);
222  if (diskCrc != crc.getValue()) {
223  status0 |= ST0_IC0;
224  status1 |= ST1_DE;
225  status2 |= ST2_DD;
226  }
227  resultPhase();
228  }
229  return result;
230  }
231  default:
232  return 0xff;
233  }
234 }
235 
236 byte TC8566AF::resultsPhasePeek() const
237 {
238  switch (command) {
239  case CMD_READ_DATA:
240  case CMD_WRITE_DATA:
241  case CMD_FORMAT:
242  switch (phaseStep) {
243  case 0:
244  return status0;
245  case 1:
246  return status1;
247  case 2:
248  return status2;
249  case 3:
250  return cylinderNumber;
251  case 4:
252  return headNumber;
253  case 5:
254  return sectorNumber;
255  case 6:
256  return number;
257  }
258  break;
259 
261  switch (phaseStep) {
262  case 0:
263  return status0;
264  case 1:
265  return currentTrack;
266  }
267  break;
268 
270  switch (phaseStep) {
271  case 0:
272  return status3;
273  }
274  break;
275  default:
276  // nothing
277  break;
278  }
279  return 0xff;
280 }
281 
282 byte TC8566AF::resultsPhaseRead(EmuTime::param time)
283 {
284  byte result = resultsPhasePeek();
285  switch (command) {
286  case CMD_READ_DATA:
287  case CMD_WRITE_DATA:
288  case CMD_FORMAT:
289  switch (phaseStep++) {
290  case 6:
291  endCommand(time);
292  break;
293  }
294  break;
295 
297  switch (phaseStep++) {
298  case 1:
299  endCommand(time);
300  break;
301  }
302  break;
303 
305  switch (phaseStep++) {
306  case 0:
307  endCommand(time);
308  break;
309  }
310  break;
311  default:
312  // nothing
313  break;
314  }
315  return result;
316 }
317 
319 {
320  switch (reg) {
321  case 2: // control register 0
322  drive[3]->setMotor((data & 0x80) != 0, time);
323  drive[2]->setMotor((data & 0x40) != 0, time);
324  drive[1]->setMotor((data & 0x20) != 0, time);
325  drive[0]->setMotor((data & 0x10) != 0, time);
326  //enableIntDma = data & 0x08;
327  //notReset = data & 0x04;
328  driveSelect = data & 0x03;
329  break;
330 
331  //case 3: // control register 1
332  // controlReg1 = data;
333  // break;
334 
335  case 5: // data port
336  writeDataPort(data, time);
337  break;
338  }
339 }
340 
341 void TC8566AF::writeDataPort(byte value, EmuTime::param time)
342 {
343  switch (phase) {
344  case PHASE_IDLE:
345  idlePhaseWrite(value, time);
346  break;
347 
348  case PHASE_COMMAND:
349  commandPhaseWrite(value, time);
350  break;
351 
352  case PHASE_DATATRANSFER:
353  executionPhaseWrite(value, time);
354  break;
355  default:
356  // nothing
357  break;
358  }
359 }
360 
361 void TC8566AF::idlePhaseWrite(byte value, EmuTime::param time)
362 {
363  command = CMD_UNKNOWN;
364  commandCode = value;
365  if ((commandCode & 0x1f) == 0x06) command = CMD_READ_DATA;
366  if ((commandCode & 0x3f) == 0x05) command = CMD_WRITE_DATA;
367  if ((commandCode & 0x3f) == 0x09) command = CMD_WRITE_DELETED_DATA;
368  if ((commandCode & 0x1f) == 0x0c) command = CMD_READ_DELETED_DATA;
369  if ((commandCode & 0xbf) == 0x02) command = CMD_READ_DIAGNOSTIC;
370  if ((commandCode & 0xbf) == 0x0a) command = CMD_READ_ID;
371  if ((commandCode & 0xbf) == 0x0d) command = CMD_FORMAT;
372  if ((commandCode & 0x1f) == 0x11) command = CMD_SCAN_EQUAL;
373  if ((commandCode & 0x1f) == 0x19) command = CMD_SCAN_LOW_OR_EQUAL;
374  if ((commandCode & 0x1f) == 0x1d) command = CMD_SCAN_HIGH_OR_EQUAL;
375  if ((commandCode & 0xff) == 0x0f) command = CMD_SEEK;
376  if ((commandCode & 0xff) == 0x07) command = CMD_RECALIBRATE;
377  if ((commandCode & 0xff) == 0x08) command = CMD_SENSE_INTERRUPT_STATUS;
378  if ((commandCode & 0xff) == 0x03) command = CMD_SPECIFY;
379  if ((commandCode & 0xff) == 0x04) command = CMD_SENSE_DEVICE_STATUS;
380 
381  phase = PHASE_COMMAND;
382  phaseStep = 0;
383  mainStatus |= STM_CB;
384 
385  switch (command) {
386  case CMD_READ_DATA:
387  case CMD_WRITE_DATA:
388  case CMD_FORMAT:
389  status0 &= ~(ST0_IC0 | ST0_IC1);
390  status1 = 0;
391  status2 = 0;
392  //MT = value & 0x80;
393  //MFM = value & 0x40;
394  //SK = value & 0x20;
395  break;
396 
397  case CMD_RECALIBRATE:
398  status0 &= ~ST0_SE;
399  break;
400 
402  resultPhase();
403  break;
404 
405  case CMD_SEEK:
406  case CMD_SPECIFY:
408  break;
409 
410  default:
411  endCommand(time);
412  }
413 }
414 
415 void TC8566AF::commandPhase1(byte value)
416 {
417  drive[driveSelect]->setSide((value & 0x04) != 0);
418  status0 &= ~(ST0_DS0 | ST0_DS1 | ST0_IC0 | ST0_IC1);
419  status0 |= //(drive[driveSelect]->isDiskInserted() ? 0 : ST0_DS0) |
420  (value & (ST0_DS0 | ST0_DS1)) |
421  (drive[driveSelect]->isDummyDrive() ? ST0_IC1 : 0);
422  status3 = (value & (ST3_DS0 | ST3_DS1)) |
423  (drive[driveSelect]->isTrack00() ? ST3_TK0 : 0) |
424  (drive[driveSelect]->isDoubleSided() ? ST3_HD : 0) |
425  (drive[driveSelect]->isWriteProtected() ? ST3_WP : 0) |
426  (drive[driveSelect]->isDiskInserted() ? ST3_RDY : 0);
427 }
428 
429 EmuTime TC8566AF::locateSector(EmuTime::param time)
430 {
431  RawTrack::Sector sectorInfo;
432  int lastIdx = -1;
433  EmuTime next = time;
434  while (true) {
435  try {
436  auto* drv = drive[driveSelect];
437  setDrqRate(drv->getTrackLength());
438  next = drv->getNextSector(next, sectorInfo);
439  } catch (MSXException& /*e*/) {
440  return EmuTime::infinity;
441  }
442  if ((next == EmuTime::infinity) ||
443  (sectorInfo.addrIdx == lastIdx)) {
444  // no sectors on track or sector already seen
445  return EmuTime::infinity;
446  }
447  if (lastIdx == -1) lastIdx = sectorInfo.addrIdx;
448  if (sectorInfo.addrCrcErr) continue;
449  if (sectorInfo.track != cylinderNumber) continue;
450  if (sectorInfo.head != headNumber) continue;
451  if (sectorInfo.sector != sectorNumber) continue;
452  if (sectorInfo.dataIdx == -1) continue;
453  break;
454  }
455  // TODO does TC8566AF look at lower 3 bits?
456  dataAvailable = 128 << (sectorInfo.sizeCode & 7);
457  dataCurrent = sectorInfo.dataIdx;
458  return next;
459 }
460 
461 void TC8566AF::commandPhaseWrite(byte value, EmuTime::param time)
462 {
463  switch (command) {
464  case CMD_READ_DATA:
465  case CMD_WRITE_DATA:
466  switch (phaseStep++) {
467  case 0:
468  commandPhase1(value);
469  break;
470  case 1:
471  cylinderNumber = value;
472  break;
473  case 2:
474  headNumber = value;
475  break;
476  case 3:
477  sectorNumber = value;
478  break;
479  case 4:
480  number = value;
481  break;
482  case 5: // End Of Track
483  break;
484  case 6: // Gap Length
485  break;
486  case 7: // Data length
487  phase = PHASE_DATATRANSFER;
488  phaseStep = 0;
489  //interrupt = true;
490 
491  // load drive head, if not already loaded
492  EmuTime ready = time;
493  if (!isHeadLoaded(time)) {
494  ready += getHeadLoadDelay();
495  // set 'head is loaded'
496  headUnloadTime = EmuTime::infinity;
497  }
498 
499  // actually read sector: fills in
500  // dataAvailable and dataCurrent
501  ready = locateSector(ready);
502  if (ready == EmuTime::infinity) {
503  status0 |= ST0_IC0;
504  status1 |= ST1_ND;
505  resultPhase();
506  return;
507  }
508  if (command == CMD_READ_DATA) {
509  mainStatus |= STM_DIO;
510  } else {
511  mainStatus &= ~STM_DIO;
512  }
513  // Initialize crc
514  // TODO 0xFB vs 0xF8 depends on deleted vs normal data
515  crc.init<0xA1, 0xA1, 0xA1, 0xFB>();
516 
517  // first byte is available when it's rotated below the
518  // drive-head
519  delayTime.reset(ready);
520  mainStatus &= ~STM_RQM;
521  break;
522  }
523  break;
524 
525  case CMD_FORMAT:
526  switch (phaseStep++) {
527  case 0:
528  commandPhase1(value);
529  break;
530  case 1:
531  number = value;
532  break;
533  case 2:
534  sectorsPerCylinder = value;
535  sectorNumber = value;
536  break;
537  case 3:
538  gapLength = value;
539  break;
540  case 4:
541  fillerByte = value;
542  mainStatus &= ~STM_DIO;
543  phase = PHASE_DATATRANSFER;
544  phaseStep = 0;
545  //interrupt = true;
546  initTrackHeader(time);
547  break;
548  }
549  break;
550 
551  case CMD_SEEK:
552  switch (phaseStep++) {
553  case 0:
554  commandPhase1(value);
555  break;
556  case 1:
557  seekValue = value; // target track
558  doSeek(time);
559  break;
560  }
561  break;
562 
563  case CMD_RECALIBRATE:
564  switch (phaseStep++) {
565  case 0:
566  commandPhase1(value);
567  seekValue = 255; // max try 255 steps
568  doSeek(time);
569  break;
570  }
571  break;
572 
573  case CMD_SPECIFY:
574  specifyData[phaseStep] = value;
575  switch (phaseStep++) {
576  case 1:
577  endCommand(time);
578  break;
579  }
580  break;
581 
583  switch (phaseStep++) {
584  case 0:
585  commandPhase1(value);
586  resultPhase();
587  break;
588  }
589  break;
590  default:
591  // nothing
592  break;
593  }
594 }
595 
596 void TC8566AF::initTrackHeader(EmuTime::param time)
597 {
598  try {
599  auto* drv = drive[driveSelect];
600  auto trackLength = drv->getTrackLength();
601  setDrqRate(trackLength);
602  dataCurrent = 0;
603  dataAvailable = trackLength;
604 
605  for (int i = 0; i < 80; ++i) drv->writeTrackByte(dataCurrent++, 0x4E); // gap4a
606  for (int i = 0; i < 12; ++i) drv->writeTrackByte(dataCurrent++, 0x00); // sync
607  for (int i = 0; i < 3; ++i) drv->writeTrackByte(dataCurrent++, 0xC2); // index mark
608  for (int i = 0; i < 1; ++i) drv->writeTrackByte(dataCurrent++, 0xFC); // " "
609  for (int i = 0; i < 50; ++i) drv->writeTrackByte(dataCurrent++, 0x4E); // gap1
610  } catch (MSXException& /*e*/) {
611  endCommand(time);
612  }
613 }
614 
615 void TC8566AF::formatSector()
616 {
617  auto* drv = drive[driveSelect];
618  for (int i = 0; i < 12; ++i) drv->writeTrackByte(dataCurrent++, 0x00); // sync
619 
620  for (int i = 0; i < 3; ++i) drv->writeTrackByte(dataCurrent++, 0xA1); // addr mark
621  drv->writeTrackByte(dataCurrent++, 0xFE, true); // addr mark + add idam
622  crc.init<0xA1, 0xA1, 0xA1, 0xFE>();
623  drv->writeTrackByte(dataCurrent++, currentTrack); // C: Cylinder number
624  crc.update(currentTrack);
625  drv->writeTrackByte(dataCurrent++, headNumber); // H: Head Address
626  crc.update(headNumber);
627  drv->writeTrackByte(dataCurrent++, sectorNumber); // R: Record
628  crc.update(sectorNumber);
629  drv->writeTrackByte(dataCurrent++, number); // N: Length of sector
630  crc.update(number);
631  drv->writeTrackByte(dataCurrent++, crc.getValue() >> 8); // CRC (high byte)
632  drv->writeTrackByte(dataCurrent++, crc.getValue() & 0xff); // (low byte)
633 
634  for (int i = 0; i < 22; ++i) drv->writeTrackByte(dataCurrent++, 0x4E); // gap2
635  for (int i = 0; i < 12; ++i) drv->writeTrackByte(dataCurrent++, 0x00); // sync
636 
637  for (int i = 0; i < 3; ++i) drv->writeTrackByte(dataCurrent++, 0xA1); // data mark
638  for (int i = 0; i < 1; ++i) drv->writeTrackByte(dataCurrent++, 0xFB); // " "
639  crc.init<0xA1, 0xA1, 0xA1, 0xFB>();
640  for (int i = 0; i < (128 << (number & 7)); ++i) {
641  drv->writeTrackByte(dataCurrent++, fillerByte);
642  crc.update(fillerByte);
643  }
644  drv->writeTrackByte(dataCurrent++, crc.getValue() >> 8); // CRC (high byte)
645  drv->writeTrackByte(dataCurrent++, crc.getValue() & 0xff); // (low byte)
646 
647  for (int i = 0; i < gapLength; ++i) drv->writeTrackByte(dataCurrent++, 0x4E); // gap3
648 }
649 
650 void TC8566AF::doSeek(EmuTime::param time)
651 {
652  DiskDrive& currentDrive = *drive[driveSelect];
653 
654  bool direction = false; // initialize to avoid warning
655  switch (command) {
656  case CMD_SEEK:
657  if (seekValue > currentTrack) {
658  ++currentTrack;
659  direction = true;
660  } else if (seekValue < currentTrack) {
661  --currentTrack;
662  direction = false;
663  } else {
664  assert(seekValue == currentTrack);
665  status0 |= ST0_SE;
666  endCommand(time);
667  return;
668  }
669  break;
670  case CMD_RECALIBRATE:
671  if (currentDrive.isTrack00() || (seekValue == 0)) {
672  if (seekValue == 0) {
673  status0 |= ST0_EC;
674  }
675  currentTrack = 0;
676  status0 |= ST0_SE;
677  endCommand(time);
678  return;
679  }
680  direction = false;
681  --seekValue;
682  break;
683  default:
684  UNREACHABLE;
685  }
686 
687  currentDrive.step(direction, time);
688 
689  setSyncPoint(time + getSeekDelay());
690 }
691 
692 void TC8566AF::executeUntil(EmuTime::param time)
693 {
694  if ((command == CMD_SEEK) || (command == CMD_RECALIBRATE)) {
695  doSeek(time);
696  }
697 }
698 
699 void TC8566AF::writeSector()
700 {
701  // write 2 CRC bytes (big endian)
702  auto* drv = drive[driveSelect];
703  drv->writeTrackByte(dataCurrent++, crc.getValue() >> 8);
704  drv->writeTrackByte(dataCurrent++, crc.getValue() & 0xFF);
705  drv->flushTrack();
706 }
707 
708 void TC8566AF::executionPhaseWrite(byte value, EmuTime::param time)
709 {
710  auto* drv = drive[driveSelect];
711  switch (command) {
712  case CMD_WRITE_DATA:
713  assert(dataAvailable);
714  drv->writeTrackByte(dataCurrent++, value);
715  crc.update(value);
716  --dataAvailable;
717  delayTime += 1; // time when next byte can be written
718  mainStatus &= ~STM_RQM;
719  if (delayTime.before(time)) {
720  // lost data
721  status0 |= ST0_IC0;
722  status1 |= ST1_OR;
723  resultPhase();
724  } else if (!dataAvailable) {
725  try {
726  writeSector();
727  } catch (MSXException&) {
728  status0 |= ST0_IC0;
729  status1 |= ST1_NW;
730  }
731  resultPhase();
732  }
733  break;
734 
735  case CMD_FORMAT:
736  delayTime += 1; // correct?
737  mainStatus &= ~STM_RQM;
738  switch (phaseStep & 3) {
739  case 0:
740  currentTrack = value;
741  break;
742  case 1:
743  headNumber = value;
744  break;
745  case 2:
746  sectorNumber = value;
747  break;
748  case 3:
749  number = value;
750  formatSector();
751  break;
752  }
753  ++phaseStep;
754 
755  if (phaseStep == 4 * sectorsPerCylinder) {
756  // data for all sectors was written, now write track
757  try {
758  drv->flushTrack();
759  } catch (MSXException&) {
760  status0 |= ST0_IC0;
761  status1 |= ST1_NW;
762  }
763  resultPhase();
764  }
765  break;
766  default:
767  // nothing
768  break;
769  }
770 }
771 
772 void TC8566AF::resultPhase()
773 {
774  mainStatus |= STM_DIO | STM_RQM;
775  phase = PHASE_RESULT;
776  phaseStep = 0;
777  //interrupt = true;
778 }
779 
780 void TC8566AF::endCommand(EmuTime::param time)
781 {
782  phase = PHASE_IDLE;
783  mainStatus &= ~(STM_CB | STM_DIO);
784  delayTime.reset(time); // set STM_RQM
785  if (headUnloadTime == EmuTime::infinity) {
786  headUnloadTime = time + getHeadUnloadDelay();
787  }
788 }
789 
790 bool TC8566AF::diskChanged(unsigned driveNum)
791 {
792  assert(driveNum < 4);
793  return drive[driveNum]->diskChanged();
794 }
795 
796 bool TC8566AF::peekDiskChanged(unsigned driveNum) const
797 {
798  assert(driveNum < 4);
799  return drive[driveNum]->peekDiskChanged();
800 }
801 
802 
803 bool TC8566AF::isHeadLoaded(EmuTime::param time) const
804 {
805  return time < headUnloadTime;
806 }
807 EmuDuration TC8566AF::getHeadLoadDelay() const
808 {
809  return EmuDuration::msec(2 * (specifyData[1] >> 1)); // 2ms per unit
810 }
811 EmuDuration TC8566AF::getHeadUnloadDelay() const
812 {
813  return EmuDuration::msec(16 * (specifyData[0] & 0x0F)); // 16ms per unit
814 }
815 
816 EmuDuration TC8566AF::getSeekDelay() const
817 {
818  return EmuDuration::msec(16 - (specifyData[0] >> 4)); // 1ms per unit
819 }
820 
821 
822 static std::initializer_list<enum_string<TC8566AF::Command>> commandInfo = {
823  { "UNKNOWN", TC8566AF::CMD_UNKNOWN },
824  { "READ_DATA", TC8566AF::CMD_READ_DATA },
825  { "WRITE_DATA", TC8566AF::CMD_WRITE_DATA },
826  { "WRITE_DELETED_DATA", TC8566AF::CMD_WRITE_DELETED_DATA },
827  { "READ_DELETED_DATA", TC8566AF::CMD_READ_DELETED_DATA },
828  { "READ_DIAGNOSTIC", TC8566AF::CMD_READ_DIAGNOSTIC },
829  { "READ_ID", TC8566AF::CMD_READ_ID },
830  { "FORMAT", TC8566AF::CMD_FORMAT },
831  { "SCAN_EQUAL", TC8566AF::CMD_SCAN_EQUAL },
832  { "SCAN_LOW_OR_EQUAL", TC8566AF::CMD_SCAN_LOW_OR_EQUAL },
833  { "SCAN_HIGH_OR_EQUAL", TC8566AF::CMD_SCAN_HIGH_OR_EQUAL },
834  { "SEEK", TC8566AF::CMD_SEEK },
835  { "RECALIBRATE", TC8566AF::CMD_RECALIBRATE },
836  { "SENSE_INTERRUPT_STATUS", TC8566AF::CMD_SENSE_INTERRUPT_STATUS },
837  { "SPECIFY", TC8566AF::CMD_SPECIFY },
838  { "SENSE_DEVICE_STATUS", TC8566AF::CMD_SENSE_DEVICE_STATUS }
839 };
840 SERIALIZE_ENUM(TC8566AF::Command, commandInfo);
841 
842 static std::initializer_list<enum_string<TC8566AF::Phase>> phaseInfo = {
843  { "IDLE", TC8566AF::PHASE_IDLE },
844  { "COMMAND", TC8566AF::PHASE_COMMAND },
845  { "DATATRANSFER", TC8566AF::PHASE_DATATRANSFER },
846  { "RESULT", TC8566AF::PHASE_RESULT }
847 };
848 SERIALIZE_ENUM(TC8566AF::Phase, phaseInfo);
849 
850 // version 1: initial version
851 // version 2: added specifyData, headUnloadTime, seekValue
852 // inherit from Schedulable
853 // version 3: Replaced 'sectorSize', 'sectorOffset', 'sectorBuf'
854 // with 'dataAvailable', 'dataCurrent', .trackData'.
855 // Not 100% backwardscompatible, see also comments in WD2793.
856 // Added 'crc' and 'gapLength'.
857 // version 4: changed type of delayTime from Clock to DynamicClock
858 // version 5: removed trackData
859 template<typename Archive>
860 void TC8566AF::serialize(Archive& ar, unsigned version)
861 {
862  if (ar.versionAtLeast(version, 4)) {
863  ar.serialize("delayTime", delayTime);
864  } else {
865  assert(ar.isLoader());
867  ar.serialize("delayTime", c);
868  delayTime.reset(c.getTime());
869  delayTime.setFreq(6250 * 5);
870  }
871  ar.serialize("command", command);
872  ar.serialize("phase", phase);
873  ar.serialize("phaseStep", phaseStep);
874  ar.serialize("driveSelect", driveSelect);
875  ar.serialize("mainStatus", mainStatus);
876  ar.serialize("status0", status0);
877  ar.serialize("status1", status1);
878  ar.serialize("status2", status2);
879  ar.serialize("status3", status3);
880  ar.serialize("commandCode", commandCode);
881  ar.serialize("cylinderNumber", cylinderNumber);
882  ar.serialize("headNumber", headNumber);
883  ar.serialize("sectorNumber", sectorNumber);
884  ar.serialize("number", number);
885  ar.serialize("currentTrack", currentTrack);
886  ar.serialize("sectorsPerCylinder", sectorsPerCylinder);
887  ar.serialize("fillerByte", fillerByte);
888  if (ar.versionAtLeast(version, 2)) {
889  ar.template serializeBase<Schedulable>(*this);
890  ar.serialize("specifyData", specifyData);
891  ar.serialize("headUnloadTime", headUnloadTime);
892  ar.serialize("seekValue", seekValue);
893  } else {
894  assert(ar.isLoader());
895  specifyData[0] = 0xDF; // values normally set by TurboR disk rom
896  specifyData[1] = 0x03;
897  headUnloadTime = EmuTime::zero;
898  seekValue = 0;
899  }
900  if (ar.versionAtLeast(version, 3)) {
901  ar.serialize("dataAvailable", dataAvailable);
902  ar.serialize("dataCurrent", dataCurrent);
903  ar.serialize("gapLength", gapLength);
904  word crcVal = crc.getValue();
905  ar.serialize("crc", crcVal);
906  crc.init(crcVal);
907  }
908  if (ar.versionBelow(version, 5)) {
909  // Version 4->5: 'trackData' moved from FDC to RealDrive.
910  if (phase != PHASE_IDLE) {
911  cliComm.printWarning(
912  "Loading an old savestate that has an "
913  "in-progress TC8566AF command. This is not "
914  "fully backwards-compatible and can cause "
915  "wrong emulation behavior.");
916  }
917  }
918 };
920 
921 } // namespace openmsx
virtual void writeTrackByte(int idx, byte val, bool addIdam=false)=0
SERIALIZE_ENUM(CassettePlayer::State, stateInfo)
static const unsigned ROTATIONS_PER_SECOND
Definition: DiskDrive.hh:15
byte peekReg(int reg, EmuTime::param time) const
Definition: TC8566AF.cc:116
void init(uint16_t initialCRC)
(Re)initialize the current value
Definition: CRC16.hh:25
static EmuDuration msec(unsigned x)
Definition: EmuDuration.hh:38
static param dummy()
Definition: EmuTime.hh:21
virtual byte readTrackByte(int idx)=0
void printWarning(string_ref message)
Definition: CliComm.cc:20
uint8_t byte
8 bit unsigned integer
Definition: openmsx.hh:26
virtual void setSide(bool side)=0
Side select.
virtual unsigned getTrackLength()=0
Represents a clock with a fixed frequency.
Definition: Clock.hh:18
uint16_t getValue() const
Get current CRC value.
Definition: CRC16.hh:110
byte readReg(int reg, EmuTime::param time)
Definition: TC8566AF.cc:127
Every class that wants to get scheduled at some point must inherit from this class.
Definition: Schedulable.hh:33
virtual bool isTrack00() const =0
Head above track 0.
static const EmuTime infinity
Definition: EmuTime.hh:58
void serialize(Archive &ar, unsigned version)
Definition: TC8566AF.cc:860
void setFreq(unsigned freq)
Change the frequency at which this clock ticks.
Definition: DynamicClock.hh:86
Thanks to enen for testing this on a real cartridge:
Definition: Autofire.cc:5
void reset(EmuTime::param time)
Definition: TC8566AF.cc:81
virtual void setMotor(bool status, EmuTime::param time)=0
Set motor on/off.
TC8566AF(Scheduler &scheduler, DiskDrive *drive[4], CliComm &cliComm, EmuTime::param time)
Definition: TC8566AF.cc:62
virtual bool isDummyDrive() const =0
Is there a dummy (unconncted) drive?
static const EmuTime zero
Definition: EmuTime.hh:57
uint16_t word
16 bit unsigned integer
Definition: openmsx.hh:29
static const unsigned STANDARD_SIZE
Definition: RawTrack.hh:71
void update(uint8_t value)
Update CRC with one byte.
Definition: CRC16.hh:66
bool peekDiskChanged(unsigned driveNum) const
Definition: TC8566AF.cc:796
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:840
virtual bool diskChanged()=0
Is disk changed?
bool diskChanged(unsigned driveNum)
Definition: TC8566AF.cc:790
uint8_t * data()
virtual bool peekDiskChanged() const =0
This (abstract) class defines the DiskDrive interface.
Definition: DiskDrive.hh:12
bool before(EmuTime::param e) const
Checks whether this clock&#39;s last tick is or is not before the given time stamp.
Definition: DynamicClock.hh:44
void reset(EmuTime::param e)
Reset the clock to start ticking at the given time.
void setSyncPoint(EmuTime::param timestamp)
Definition: Schedulable.cc:23
virtual void step(bool direction, EmuTime::param time)=0
Step head.
void writeReg(int reg, byte data, EmuTime::param time)
Definition: TC8566AF.cc:318
#define UNREACHABLE
Definition: unreachable.hh:35