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  break;
453  }
454  // TODO does TC8566AF look at lower 3 bits?
455  dataAvailable = 128 << (sectorInfo.sizeCode & 7);
456  dataCurrent = sectorInfo.dataIdx;
457  return next;
458 }
459 
460 void TC8566AF::commandPhaseWrite(byte value, EmuTime::param time)
461 {
462  switch (command) {
463  case CMD_READ_DATA:
464  case CMD_WRITE_DATA:
465  switch (phaseStep++) {
466  case 0:
467  commandPhase1(value);
468  break;
469  case 1:
470  cylinderNumber = value;
471  break;
472  case 2:
473  headNumber = value;
474  break;
475  case 3:
476  sectorNumber = value;
477  break;
478  case 4:
479  number = value;
480  break;
481  case 5: // End Of Track
482  break;
483  case 6: // Gap Length
484  break;
485  case 7: // Data length
486  phase = PHASE_DATATRANSFER;
487  phaseStep = 0;
488  //interrupt = true;
489 
490  // load drive head, if not already loaded
491  EmuTime ready = time;
492  if (!isHeadLoaded(time)) {
493  ready += getHeadLoadDelay();
494  // set 'head is loaded'
495  headUnloadTime = EmuTime::infinity;
496  }
497 
498  // actually read sector: fills in
499  // dataAvailable and dataCurrent
500  ready = locateSector(ready);
501  if (ready == EmuTime::infinity) {
502  status0 |= ST0_IC0;
503  status1 |= ST1_ND;
504  resultPhase();
505  return;
506  }
507  if (command == CMD_READ_DATA) {
508  mainStatus |= STM_DIO;
509  } else {
510  mainStatus &= ~STM_DIO;
511  }
512  // Initialize crc
513  // TODO 0xFB vs 0xF8 depends on deleted vs normal data
514  crc.init<0xA1, 0xA1, 0xA1, 0xFB>();
515 
516  // first byte is available when it's rotated below the
517  // drive-head
518  delayTime.reset(ready);
519  mainStatus &= ~STM_RQM;
520  break;
521  }
522  break;
523 
524  case CMD_FORMAT:
525  switch (phaseStep++) {
526  case 0:
527  commandPhase1(value);
528  break;
529  case 1:
530  number = value;
531  break;
532  case 2:
533  sectorsPerCylinder = value;
534  sectorNumber = value;
535  break;
536  case 3:
537  gapLength = value;
538  break;
539  case 4:
540  fillerByte = value;
541  mainStatus &= ~STM_DIO;
542  phase = PHASE_DATATRANSFER;
543  phaseStep = 0;
544  //interrupt = true;
545  initTrackHeader(time);
546  break;
547  }
548  break;
549 
550  case CMD_SEEK:
551  switch (phaseStep++) {
552  case 0:
553  commandPhase1(value);
554  break;
555  case 1:
556  seekValue = value; // target track
557  doSeek(time);
558  break;
559  }
560  break;
561 
562  case CMD_RECALIBRATE:
563  switch (phaseStep++) {
564  case 0:
565  commandPhase1(value);
566  seekValue = 255; // max try 255 steps
567  doSeek(time);
568  break;
569  }
570  break;
571 
572  case CMD_SPECIFY:
573  specifyData[phaseStep] = value;
574  switch (phaseStep++) {
575  case 1:
576  endCommand(time);
577  break;
578  }
579  break;
580 
582  switch (phaseStep++) {
583  case 0:
584  commandPhase1(value);
585  resultPhase();
586  break;
587  }
588  break;
589  default:
590  // nothing
591  break;
592  }
593 }
594 
595 void TC8566AF::initTrackHeader(EmuTime::param time)
596 {
597  try {
598  auto* drv = drive[driveSelect];
599  auto trackLength = drv->getTrackLength();
600  setDrqRate(trackLength);
601  dataCurrent = 0;
602  dataAvailable = trackLength;
603 
604  for (int i = 0; i < 80; ++i) drv->writeTrackByte(dataCurrent++, 0x4E); // gap4a
605  for (int i = 0; i < 12; ++i) drv->writeTrackByte(dataCurrent++, 0x00); // sync
606  for (int i = 0; i < 3; ++i) drv->writeTrackByte(dataCurrent++, 0xC2); // index mark
607  for (int i = 0; i < 1; ++i) drv->writeTrackByte(dataCurrent++, 0xFC); // " "
608  for (int i = 0; i < 50; ++i) drv->writeTrackByte(dataCurrent++, 0x4E); // gap1
609  } catch (MSXException& /*e*/) {
610  endCommand(time);
611  }
612 }
613 
614 void TC8566AF::formatSector()
615 {
616  auto* drv = drive[driveSelect];
617  for (int i = 0; i < 12; ++i) drv->writeTrackByte(dataCurrent++, 0x00); // sync
618 
619  for (int i = 0; i < 3; ++i) drv->writeTrackByte(dataCurrent++, 0xA1); // addr mark
620  drv->writeTrackByte(dataCurrent++, 0xFE, true); // addr mark + add idam
621  crc.init<0xA1, 0xA1, 0xA1, 0xFE>();
622  drv->writeTrackByte(dataCurrent++, currentTrack); // C: Cylinder number
623  crc.update(currentTrack);
624  drv->writeTrackByte(dataCurrent++, headNumber); // H: Head Address
625  crc.update(headNumber);
626  drv->writeTrackByte(dataCurrent++, sectorNumber); // R: Record
627  crc.update(sectorNumber);
628  drv->writeTrackByte(dataCurrent++, number); // N: Length of sector
629  crc.update(number);
630  drv->writeTrackByte(dataCurrent++, crc.getValue() >> 8); // CRC (high byte)
631  drv->writeTrackByte(dataCurrent++, crc.getValue() & 0xff); // (low byte)
632 
633  for (int i = 0; i < 22; ++i) drv->writeTrackByte(dataCurrent++, 0x4E); // gap2
634  for (int i = 0; i < 12; ++i) drv->writeTrackByte(dataCurrent++, 0x00); // sync
635 
636  for (int i = 0; i < 3; ++i) drv->writeTrackByte(dataCurrent++, 0xA1); // data mark
637  for (int i = 0; i < 1; ++i) drv->writeTrackByte(dataCurrent++, 0xFB); // " "
638  crc.init<0xA1, 0xA1, 0xA1, 0xFB>();
639  for (int i = 0; i < (128 << (number & 7)); ++i) {
640  drv->writeTrackByte(dataCurrent++, fillerByte);
641  crc.update(fillerByte);
642  }
643  drv->writeTrackByte(dataCurrent++, crc.getValue() >> 8); // CRC (high byte)
644  drv->writeTrackByte(dataCurrent++, crc.getValue() & 0xff); // (low byte)
645 
646  for (int i = 0; i < gapLength; ++i) drv->writeTrackByte(dataCurrent++, 0x4E); // gap3
647 }
648 
649 void TC8566AF::doSeek(EmuTime::param time)
650 {
651  DiskDrive& currentDrive = *drive[driveSelect];
652 
653  bool direction = false; // initialize to avoid warning
654  switch (command) {
655  case CMD_SEEK:
656  if (seekValue > currentTrack) {
657  ++currentTrack;
658  direction = true;
659  } else if (seekValue < currentTrack) {
660  --currentTrack;
661  direction = false;
662  } else {
663  assert(seekValue == currentTrack);
664  status0 |= ST0_SE;
665  endCommand(time);
666  return;
667  }
668  break;
669  case CMD_RECALIBRATE:
670  if (currentDrive.isTrack00() || (seekValue == 0)) {
671  if (seekValue == 0) {
672  status0 |= ST0_EC;
673  }
674  currentTrack = 0;
675  status0 |= ST0_SE;
676  endCommand(time);
677  return;
678  }
679  direction = false;
680  --seekValue;
681  break;
682  default:
683  UNREACHABLE;
684  }
685 
686  currentDrive.step(direction, time);
687 
688  setSyncPoint(time + getSeekDelay());
689 }
690 
691 void TC8566AF::executeUntil(EmuTime::param time)
692 {
693  if ((command == CMD_SEEK) || (command == CMD_RECALIBRATE)) {
694  doSeek(time);
695  }
696 }
697 
698 void TC8566AF::writeSector()
699 {
700  // write 2 CRC bytes (big endian)
701  auto* drv = drive[driveSelect];
702  drv->writeTrackByte(dataCurrent++, crc.getValue() >> 8);
703  drv->writeTrackByte(dataCurrent++, crc.getValue() & 0xFF);
704  drv->flushTrack();
705 }
706 
707 void TC8566AF::executionPhaseWrite(byte value, EmuTime::param time)
708 {
709  auto* drv = drive[driveSelect];
710  switch (command) {
711  case CMD_WRITE_DATA:
712  assert(dataAvailable);
713  drv->writeTrackByte(dataCurrent++, value);
714  crc.update(value);
715  --dataAvailable;
716  delayTime += 1; // time when next byte can be written
717  mainStatus &= ~STM_RQM;
718  if (delayTime.before(time)) {
719  // lost data
720  status0 |= ST0_IC0;
721  status1 |= ST1_OR;
722  resultPhase();
723  } else if (!dataAvailable) {
724  try {
725  writeSector();
726  } catch (MSXException&) {
727  status0 |= ST0_IC0;
728  status1 |= ST1_NW;
729  }
730  resultPhase();
731  }
732  break;
733 
734  case CMD_FORMAT:
735  delayTime += 1; // correct?
736  mainStatus &= ~STM_RQM;
737  switch (phaseStep & 3) {
738  case 0:
739  currentTrack = value;
740  break;
741  case 1:
742  headNumber = value;
743  break;
744  case 2:
745  sectorNumber = value;
746  break;
747  case 3:
748  number = value;
749  formatSector();
750  break;
751  }
752  ++phaseStep;
753 
754  if (phaseStep == 4 * sectorsPerCylinder) {
755  // data for all sectors was written, now write track
756  try {
757  drv->flushTrack();
758  } catch (MSXException&) {
759  status0 |= ST0_IC0;
760  status1 |= ST1_NW;
761  }
762  resultPhase();
763  }
764  break;
765  default:
766  // nothing
767  break;
768  }
769 }
770 
771 void TC8566AF::resultPhase()
772 {
773  mainStatus |= STM_DIO | STM_RQM;
774  phase = PHASE_RESULT;
775  phaseStep = 0;
776  //interrupt = true;
777 }
778 
779 void TC8566AF::endCommand(EmuTime::param time)
780 {
781  phase = PHASE_IDLE;
782  mainStatus &= ~(STM_CB | STM_DIO);
783  delayTime.reset(time); // set STM_RQM
784  if (headUnloadTime == EmuTime::infinity) {
785  headUnloadTime = time + getHeadUnloadDelay();
786  }
787 }
788 
789 bool TC8566AF::diskChanged(unsigned driveNum)
790 {
791  assert(driveNum < 4);
792  return drive[driveNum]->diskChanged();
793 }
794 
795 bool TC8566AF::peekDiskChanged(unsigned driveNum) const
796 {
797  assert(driveNum < 4);
798  return drive[driveNum]->peekDiskChanged();
799 }
800 
801 
802 bool TC8566AF::isHeadLoaded(EmuTime::param time) const
803 {
804  return time < headUnloadTime;
805 }
806 EmuDuration TC8566AF::getHeadLoadDelay() const
807 {
808  return EmuDuration::msec(2 * (specifyData[1] >> 1)); // 2ms per unit
809 }
810 EmuDuration TC8566AF::getHeadUnloadDelay() const
811 {
812  return EmuDuration::msec(16 * (specifyData[0] & 0x0F)); // 16ms per unit
813 }
814 
815 EmuDuration TC8566AF::getSeekDelay() const
816 {
817  return EmuDuration::msec(16 - (specifyData[0] >> 4)); // 1ms per unit
818 }
819 
820 
821 static std::initializer_list<enum_string<TC8566AF::Command>> commandInfo = {
822  { "UNKNOWN", TC8566AF::CMD_UNKNOWN },
823  { "READ_DATA", TC8566AF::CMD_READ_DATA },
824  { "WRITE_DATA", TC8566AF::CMD_WRITE_DATA },
825  { "WRITE_DELETED_DATA", TC8566AF::CMD_WRITE_DELETED_DATA },
826  { "READ_DELETED_DATA", TC8566AF::CMD_READ_DELETED_DATA },
827  { "READ_DIAGNOSTIC", TC8566AF::CMD_READ_DIAGNOSTIC },
828  { "READ_ID", TC8566AF::CMD_READ_ID },
829  { "FORMAT", TC8566AF::CMD_FORMAT },
830  { "SCAN_EQUAL", TC8566AF::CMD_SCAN_EQUAL },
831  { "SCAN_LOW_OR_EQUAL", TC8566AF::CMD_SCAN_LOW_OR_EQUAL },
832  { "SCAN_HIGH_OR_EQUAL", TC8566AF::CMD_SCAN_HIGH_OR_EQUAL },
833  { "SEEK", TC8566AF::CMD_SEEK },
834  { "RECALIBRATE", TC8566AF::CMD_RECALIBRATE },
835  { "SENSE_INTERRUPT_STATUS", TC8566AF::CMD_SENSE_INTERRUPT_STATUS },
836  { "SPECIFY", TC8566AF::CMD_SPECIFY },
837  { "SENSE_DEVICE_STATUS", TC8566AF::CMD_SENSE_DEVICE_STATUS }
838 };
839 SERIALIZE_ENUM(TC8566AF::Command, commandInfo);
840 
841 static std::initializer_list<enum_string<TC8566AF::Phase>> phaseInfo = {
842  { "IDLE", TC8566AF::PHASE_IDLE },
843  { "COMMAND", TC8566AF::PHASE_COMMAND },
844  { "DATATRANSFER", TC8566AF::PHASE_DATATRANSFER },
845  { "RESULT", TC8566AF::PHASE_RESULT }
846 };
847 SERIALIZE_ENUM(TC8566AF::Phase, phaseInfo);
848 
849 // version 1: initial version
850 // version 2: added specifyData, headUnloadTime, seekValue
851 // inherit from Schedulable
852 // version 3: Replaced 'sectorSize', 'sectorOffset', 'sectorBuf'
853 // with 'dataAvailable', 'dataCurrent', .trackData'.
854 // Not 100% backwardscompatible, see also comments in WD2793.
855 // Added 'crc' and 'gapLength'.
856 // version 4: changed type of delayTime from Clock to DynamicClock
857 // version 5: removed trackData
858 template<typename Archive>
859 void TC8566AF::serialize(Archive& ar, unsigned version)
860 {
861  if (ar.versionAtLeast(version, 4)) {
862  ar.serialize("delayTime", delayTime);
863  } else {
864  assert(ar.isLoader());
866  ar.serialize("delayTime", c);
867  delayTime.reset(c.getTime());
868  delayTime.setFreq(6250 * 5);
869  }
870  ar.serialize("command", command);
871  ar.serialize("phase", phase);
872  ar.serialize("phaseStep", phaseStep);
873  ar.serialize("driveSelect", driveSelect);
874  ar.serialize("mainStatus", mainStatus);
875  ar.serialize("status0", status0);
876  ar.serialize("status1", status1);
877  ar.serialize("status2", status2);
878  ar.serialize("status3", status3);
879  ar.serialize("commandCode", commandCode);
880  ar.serialize("cylinderNumber", cylinderNumber);
881  ar.serialize("headNumber", headNumber);
882  ar.serialize("sectorNumber", sectorNumber);
883  ar.serialize("number", number);
884  ar.serialize("currentTrack", currentTrack);
885  ar.serialize("sectorsPerCylinder", sectorsPerCylinder);
886  ar.serialize("fillerByte", fillerByte);
887  if (ar.versionAtLeast(version, 2)) {
888  ar.template serializeBase<Schedulable>(*this);
889  ar.serialize("specifyData", specifyData);
890  ar.serialize("headUnloadTime", headUnloadTime);
891  ar.serialize("seekValue", seekValue);
892  } else {
893  assert(ar.isLoader());
894  specifyData[0] = 0xDF; // values normally set by TurboR disk rom
895  specifyData[1] = 0x03;
896  headUnloadTime = EmuTime::zero;
897  seekValue = 0;
898  }
899  if (ar.versionAtLeast(version, 3)) {
900  ar.serialize("dataAvailable", dataAvailable);
901  ar.serialize("dataCurrent", dataCurrent);
902  ar.serialize("gapLength", gapLength);
903  word crcVal = crc.getValue();
904  ar.serialize("crc", crcVal);
905  crc.init(crcVal);
906  }
907  if (ar.versionBelow(version, 5)) {
908  // Version 4->5: 'trackData' moved from FDC to RealDrive.
909  if (phase != PHASE_IDLE) {
910  cliComm.printWarning(
911  "Loading an old savestate that has an "
912  "in-progress TC8566AF command. This is not "
913  "fully backwards-compatible and can cause "
914  "wrong emulation behavior.");
915  }
916  }
917 };
919 
920 } // 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:859
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:795
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:840
virtual bool diskChanged()=0
Is disk changed?
bool diskChanged(unsigned driveNum)
Definition: TC8566AF.cc:789
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