frontend: update libpicofe, fix missed callbacks
[pcsx_rearmed.git] / libpcsxcore / cdrom.c
... / ...
CommitLineData
1/***************************************************************************
2 * Copyright (C) 2007 Ryan Schultz, PCSX-df Team, PCSX team *
3 * *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
8 * *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, write to the *
16 * Free Software Foundation, Inc., *
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *
18 ***************************************************************************/
19
20/*
21* Handles all CD-ROM registers and functions.
22*/
23
24#include <stdalign.h>
25#include <assert.h>
26#include "cdrom.h"
27#include "cdrom-async.h"
28#include "misc.h"
29#include "ppf.h"
30#include "psxdma.h"
31#include "psxevents.h"
32#include "arm_features.h"
33
34/* logging */
35#if 0
36#define CDR_LOG SysPrintf
37#else
38#define CDR_LOG(...)
39#endif
40#if 0
41#define CDR_LOG_I SysPrintf
42#else
43#define CDR_LOG_I(fmt, ...) \
44 log_unhandled("%u cdrom: " fmt, psxRegs.cycle, ##__VA_ARGS__)
45#endif
46#if 0
47#define CDR_LOG_IO SysPrintf
48#else
49#define CDR_LOG_IO(...)
50#endif
51//#define CDR_LOG_CMD_IRQ
52
53static struct {
54 // unused members maintain savesate compatibility
55 unsigned char unused0;
56 unsigned char unused1;
57 unsigned char IrqMask;
58 unsigned char unused2;
59 unsigned char Ctrl;
60 unsigned char IrqStat;
61
62 unsigned char StatP;
63
64 unsigned char Transfer[DATA_SIZE];
65 struct {
66 unsigned char Track;
67 unsigned char Index;
68 unsigned char Relative[3];
69 unsigned char Absolute[3];
70 } subq;
71 unsigned char TrackChanged;
72 unsigned char ReportDelay;
73 unsigned char PhysCdPropagations;
74 unsigned short sectorsRead;
75 unsigned int freeze_ver;
76
77 unsigned char Prev[4];
78 unsigned char Param[8];
79 unsigned char Result[16];
80
81 unsigned char ParamC;
82 unsigned char ParamP;
83 unsigned char ResultC;
84 unsigned char ResultP;
85 unsigned char ResultReady;
86 unsigned char Cmd;
87 unsigned char SubqForwardSectors;
88 unsigned char SetlocPending;
89 u32 Reading;
90
91 unsigned char ResultTN[6];
92 unsigned char ResultTD[4];
93 unsigned char SetSectorPlay[4];
94 unsigned char SetSectorEnd[4];
95 unsigned char SetSector[4];
96 unsigned char Track;
97 boolean Play, Muted;
98 int CurTrack;
99 unsigned char Mode;
100 unsigned char FileChannelSelected;
101 unsigned char CurFile, CurChannel;
102 int FilterFile, FilterChannel;
103 unsigned char LocL[8];
104 int unused4;
105
106 xa_decode_t Xa;
107
108 u16 FifoOffset;
109 u16 FifoSize;
110
111 u16 CmdInProgress;
112 u8 Irq1Pending;
113 u8 AdpcmActive;
114 u32 LastReadSeekCycles;
115
116 u8 RetryDetected;
117
118 u8 DriveState; // enum drive_state
119 u8 FastForward;
120 u8 FastBackward;
121 u8 errorRetryhack;
122
123 u8 AttenuatorLeftToLeft, AttenuatorLeftToRight;
124 u8 AttenuatorRightToRight, AttenuatorRightToLeft;
125 u8 AttenuatorLeftToLeftT, AttenuatorLeftToRightT;
126 u8 AttenuatorRightToRightT, AttenuatorRightToLeftT;
127} cdr;
128alignas(64) static s16 read_buf[CD_FRAMESIZE_RAW_ALIGNED / 2];
129
130struct SubQ {
131 char res0[12];
132 unsigned char ControlAndADR;
133 unsigned char TrackNumber;
134 unsigned char IndexNumber;
135 unsigned char TrackRelativeAddress[3];
136 unsigned char Filler;
137 unsigned char AbsoluteAddress[3];
138 unsigned char CRC[2];
139 char res1[72];
140};
141
142/* CD-ROM magic numbers */
143#define CdlSync 0 /* nocash documentation : "Uh, actually, returns error code 40h = Invalid Command...?" */
144#define CdlNop 1
145#define CdlSetloc 2
146#define CdlPlay 3
147#define CdlForward 4
148#define CdlBackward 5
149#define CdlReadN 6
150#define CdlStandby 7
151#define CdlStop 8
152#define CdlPause 9
153#define CdlReset 10
154#define CdlMute 11
155#define CdlDemute 12
156#define CdlSetfilter 13
157#define CdlSetmode 14
158#define CdlGetparam 15
159#define CdlGetlocL 16
160#define CdlGetlocP 17
161#define CdlReadT 18
162#define CdlGetTN 19
163#define CdlGetTD 20
164#define CdlSeekL 21
165#define CdlSeekP 22
166#define CdlSetclock 23
167#define CdlGetclock 24
168#define CdlTest 25
169#define CdlID 26
170#define CdlReadS 27
171#define CdlInit 28
172#define CdlGetQ 29
173#define CdlReadToc 30
174
175#ifdef CDR_LOG_CMD_IRQ
176static const char * const CmdName[0x100] = {
177 "CdlSync", "CdlNop", "CdlSetloc", "CdlPlay",
178 "CdlForward", "CdlBackward", "CdlReadN", "CdlStandby",
179 "CdlStop", "CdlPause", "CdlReset", "CdlMute",
180 "CdlDemute", "CdlSetfilter", "CdlSetmode", "CdlGetparam",
181 "CdlGetlocL", "CdlGetlocP", "CdlReadT", "CdlGetTN",
182 "CdlGetTD", "CdlSeekL", "CdlSeekP", "CdlSetclock",
183 "CdlGetclock", "CdlTest", "CdlID", "CdlReadS",
184 "CdlInit", NULL, "CDlReadToc", NULL
185};
186#endif
187
188unsigned char Test04[] = { 0 };
189unsigned char Test05[] = { 0 };
190unsigned char Test20[] = { 0x98, 0x06, 0x10, 0xC3 };
191unsigned char Test22[] = { 0x66, 0x6F, 0x72, 0x20, 0x45, 0x75, 0x72, 0x6F };
192unsigned char Test23[] = { 0x43, 0x58, 0x44, 0x32, 0x39 ,0x34, 0x30, 0x51 };
193
194// cdr.IrqStat:
195#define NoIntr 0
196#define DataReady 1
197#define Complete 2
198#define Acknowledge 3
199#define DataEnd 4
200#define DiskError 5
201
202/* Modes flags */
203#define MODE_SPEED (1<<7) // 0x80
204#define MODE_STRSND (1<<6) // 0x40 ADPCM on/off
205#define MODE_SIZE_2340 (1<<5) // 0x20
206#define MODE_SIZE_2328 (1<<4) // 0x10
207#define MODE_SIZE_2048 (0<<4) // 0x00
208#define MODE_SF (1<<3) // 0x08 channel on/off
209#define MODE_REPORT (1<<2) // 0x04
210#define MODE_AUTOPAUSE (1<<1) // 0x02
211#define MODE_CDDA (1<<0) // 0x01
212
213/* Status flags */
214#define STATUS_PLAY (1<<7) // 0x80
215#define STATUS_SEEK (1<<6) // 0x40
216#define STATUS_READ (1<<5) // 0x20
217#define STATUS_SHELLOPEN (1<<4) // 0x10
218#define STATUS_UNKNOWN3 (1<<3) // 0x08
219#define STATUS_SEEKERROR (1<<2) // 0x04
220#define STATUS_ROTATING (1<<1) // 0x02
221#define STATUS_ERROR (1<<0) // 0x01
222
223/* Errors */
224#define ERROR_NOTREADY (1<<7) // 0x80
225#define ERROR_INVALIDCMD (1<<6) // 0x40
226#define ERROR_BAD_ARGNUM (1<<5) // 0x20
227#define ERROR_BAD_ARGVAL (1<<4) // 0x10
228#define ERROR_SHELLOPEN (1<<3) // 0x08
229
230// 1x = 75 sectors per second
231// PSXCLK = 1 sec in the ps
232// so (PSXCLK / 75) = cdr read time (linuzappz)
233#define cdReadTime (PSXCLK / 75)
234
235#define LOCL_INVALID 0xff
236#define SUBQ_FORWARD_SECTORS 2u
237
238enum drive_state {
239 DRIVESTATE_STANDBY = 0, // different from paused
240 DRIVESTATE_LID_OPEN,
241 DRIVESTATE_RESCAN_CD,
242 DRIVESTATE_PREPARE_CD,
243 DRIVESTATE_STOPPED,
244 DRIVESTATE_PAUSED,
245 DRIVESTATE_PLAY_READ,
246 DRIVESTATE_SEEK,
247};
248
249static struct CdrStat cdr_stat;
250
251static unsigned int msf2sec(const u8 *msf) {
252 return ((msf[0] * 60 + msf[1]) * 75) + msf[2];
253}
254
255// cdrPlayReadInterrupt
256#define CDRPLAYREAD_INT(eCycle, isFirst) { \
257 u32 e_ = eCycle; \
258 psxRegs.interrupt |= (1 << PSXINT_CDREAD); \
259 if (isFirst) \
260 psxRegs.intCycle[PSXINT_CDREAD].sCycle = psxRegs.cycle; \
261 else \
262 psxRegs.intCycle[PSXINT_CDREAD].sCycle += psxRegs.intCycle[PSXINT_CDREAD].cycle; \
263 psxRegs.intCycle[PSXINT_CDREAD].cycle = e_; \
264 set_event_raw_abs(PSXINT_CDREAD, psxRegs.intCycle[PSXINT_CDREAD].sCycle + e_); \
265}
266
267#define StopReading() { \
268 cdr.Reading = 0; \
269 psxRegs.interrupt &= ~(1 << PSXINT_CDREAD); \
270}
271
272#define StopCdda() { \
273 cdr.Play = FALSE; \
274 cdr.FastForward = 0; \
275 cdr.FastBackward = 0; \
276}
277
278#define SetPlaySeekRead(x, f) { \
279 x &= ~(STATUS_PLAY | STATUS_SEEK | STATUS_READ); \
280 x |= f; \
281}
282
283#define SetResultSize_(size) { \
284 cdr.ResultP = 0; \
285 cdr.ResultC = size; \
286 cdr.ResultReady = 1; \
287}
288
289#define SetResultSize(size) { \
290 if (cdr.ResultP < cdr.ResultC) \
291 CDR_LOG_I("overwriting result, len=%u\n", cdr.ResultC); \
292 SetResultSize_(size); \
293}
294
295static void setIrq(u8 irq, int log_cmd)
296{
297 u8 old = cdr.IrqStat & cdr.IrqMask ? 1 : 0;
298 u8 new_ = irq & cdr.IrqMask ? 1 : 0;
299
300 cdr.IrqStat = irq;
301 if ((old ^ new_) & new_)
302 psxHu32ref(0x1070) |= SWAP32((u32)0x4);
303
304#ifdef CDR_LOG_CMD_IRQ
305 if (cdr.IrqStat)
306 {
307 int i;
308 CDR_LOG_I("CDR IRQ=%d cmd %02x irqstat %02x: ",
309 !!(cdr.IrqStat & cdr.IrqMask), log_cmd, cdr.IrqStat);
310 for (i = 0; i < cdr.ResultC; i++)
311 SysPrintf("%02x ", cdr.Result[i]);
312 SysPrintf("\n");
313 }
314#endif
315}
316
317// timing used in this function was taken from tests on real hardware
318// (yes it's slow, but you probably don't want to modify it)
319void cdrLidSeekInterrupt(void)
320{
321 CDR_LOG_I("%s cdr.DriveState=%d\n", __func__, cdr.DriveState);
322
323 switch (cdr.DriveState) {
324 default:
325 case DRIVESTATE_STANDBY:
326 StopCdda();
327 //StopReading();
328 SetPlaySeekRead(cdr.StatP, 0);
329
330 if (cdra_getStatus(&cdr_stat) == -1)
331 return;
332
333 if (cdr_stat.Status & STATUS_SHELLOPEN)
334 {
335 memset(cdr.Prev, 0xff, sizeof(cdr.Prev));
336 cdr.DriveState = DRIVESTATE_LID_OPEN;
337 set_event(PSXINT_CDRLID, 0x800);
338 }
339 break;
340
341 case DRIVESTATE_LID_OPEN:
342 if (cdra_getStatus(&cdr_stat) != 0)
343 cdr_stat.Status &= ~STATUS_SHELLOPEN;
344
345 // 02, 12, 10
346 if (!(cdr.StatP & STATUS_SHELLOPEN)) {
347 int was_reading = cdr.Reading;
348 StopReading();
349 SetPlaySeekRead(cdr.StatP, 0);
350 cdr.StatP |= STATUS_SHELLOPEN;
351 memset(cdr.Prev, 0xff, sizeof(cdr.Prev));
352
353 // IIRC this sometimes doesn't happen on real hw
354 // (when lots of commands are sent?)
355 SetResultSize(2);
356 cdr.Result[0] = cdr.StatP | STATUS_SEEKERROR;
357 cdr.Result[1] = ERROR_SHELLOPEN;
358 if (cdr.CmdInProgress || was_reading) {
359 psxRegs.interrupt &= ~(1 << PSXINT_CDR);
360 cdr.CmdInProgress = 0;
361 cdr.Result[0] = cdr.StatP | STATUS_ERROR;
362 cdr.Result[1] = ERROR_NOTREADY;
363 }
364 setIrq(DiskError, 0x1006);
365
366 set_event(PSXINT_CDRLID, cdReadTime * 30);
367 break;
368 }
369 else if (cdr.StatP & STATUS_ROTATING) {
370 cdr.StatP &= ~STATUS_ROTATING;
371 }
372 else if (!(cdr_stat.Status & STATUS_SHELLOPEN)) {
373 // closed now
374 CheckCdrom();
375
376 // cdr.StatP STATUS_SHELLOPEN is "sticky"
377 // and is only cleared by CdlNop
378
379 cdr.DriveState = DRIVESTATE_RESCAN_CD;
380 set_event(PSXINT_CDRLID, cdReadTime * 105);
381 break;
382 }
383
384 // recheck for close
385 set_event(PSXINT_CDRLID, cdReadTime * 3);
386 break;
387
388 case DRIVESTATE_RESCAN_CD:
389 cdr.StatP |= STATUS_ROTATING;
390 cdr.DriveState = DRIVESTATE_PREPARE_CD;
391
392 // this is very long on real hardware, over 6 seconds
393 // make it a bit faster here...
394 set_event(PSXINT_CDRLID, cdReadTime * 150);
395 break;
396
397 case DRIVESTATE_PREPARE_CD:
398 if (cdr.StatP & STATUS_SEEK) {
399 SetPlaySeekRead(cdr.StatP, 0);
400 cdr.DriveState = DRIVESTATE_STANDBY;
401 }
402 else {
403 SetPlaySeekRead(cdr.StatP, STATUS_SEEK);
404 set_event(PSXINT_CDRLID, cdReadTime * 26);
405 }
406 break;
407 }
408}
409
410static void Find_CurTrack(const u8 *time)
411{
412 int current, sect;
413
414 current = msf2sec(time);
415
416 for (cdr.CurTrack = 1; cdr.CurTrack < cdr.ResultTN[1]; cdr.CurTrack++) {
417 cdra_getTD(cdr.CurTrack + 1, cdr.ResultTD);
418 sect = msf2sec(cdr.ResultTD);
419 if (sect - current >= 150)
420 break;
421 }
422}
423
424static void generate_subq(const u8 *time)
425{
426 unsigned char start[3], next[3];
427 unsigned int this_s, start_s, next_s, pregap;
428 int relative_s;
429
430 cdra_getTD(cdr.CurTrack, start);
431 if (cdr.CurTrack + 1 <= cdr.ResultTN[1]) {
432 pregap = 150;
433 cdra_getTD(cdr.CurTrack + 1, next);
434 }
435 else {
436 // last track - cd size
437 pregap = 0;
438 memcpy(next, cdr.SetSectorEnd, 3);
439 }
440
441 this_s = msf2sec(time);
442 start_s = msf2sec(start);
443 next_s = msf2sec(next);
444
445 cdr.TrackChanged = FALSE;
446
447 if (next_s - this_s < pregap) {
448 cdr.TrackChanged = TRUE;
449 cdr.CurTrack++;
450 start_s = next_s;
451 }
452
453 cdr.subq.Index = 1;
454
455 relative_s = this_s - start_s;
456 if (relative_s < 0) {
457 cdr.subq.Index = 0;
458 relative_s = -relative_s;
459 }
460 lba2msf(relative_s, &cdr.subq.Relative[0],
461 &cdr.subq.Relative[1], &cdr.subq.Relative[2]);
462
463 cdr.subq.Track = itob(cdr.CurTrack);
464 cdr.subq.Relative[0] = itob(cdr.subq.Relative[0]);
465 cdr.subq.Relative[1] = itob(cdr.subq.Relative[1]);
466 cdr.subq.Relative[2] = itob(cdr.subq.Relative[2]);
467 cdr.subq.Absolute[0] = itob(time[0]);
468 cdr.subq.Absolute[1] = itob(time[1]);
469 cdr.subq.Absolute[2] = itob(time[2]);
470}
471
472static int ReadTrack(const u8 *time)
473{
474 int ret;
475
476 CDR_LOG("ReadTrack *** %02d:%02d:%02d\n", tmp[0], tmp[1], tmp[2]);
477
478 if (memcmp(cdr.Prev, time, 3) == 0)
479 return 1;
480
481 ret = cdra_readTrack(time);
482 if (ret == 0)
483 memcpy(cdr.Prev, time, 3);
484 return ret == 0;
485}
486
487static void UpdateSubq(const u8 *time)
488{
489 int ret = -1, s = MSF2SECT(time[0], time[1], time[2]);
490 struct SubQ subq;
491 u16 crc;
492
493 if (CheckSBI(s))
494 return;
495
496 if (cdr.CurTrack == 1)
497 ret = cdra_readSub(time, &subq);
498 if (ret == 0) {
499 crc = calcCrc((u8 *)&subq + 12, 10);
500 if (crc == (((u16)subq.CRC[0] << 8) | subq.CRC[1])) {
501 cdr.subq.Track = subq.TrackNumber;
502 cdr.subq.Index = subq.IndexNumber;
503 memcpy(cdr.subq.Relative, subq.TrackRelativeAddress, 3);
504 memcpy(cdr.subq.Absolute, subq.AbsoluteAddress, 3);
505 }
506 else {
507 CDR_LOG_I("subq bad crc @%02d:%02d:%02d\n",
508 time[0], time[1], time[2]);
509 }
510 }
511 else {
512 generate_subq(time);
513 }
514
515 CDR_LOG(" -> %02x,%02x %02x:%02x:%02x %02x:%02x:%02x\n",
516 cdr.subq.Track, cdr.subq.Index,
517 cdr.subq.Relative[0], cdr.subq.Relative[1], cdr.subq.Relative[2],
518 cdr.subq.Absolute[0], cdr.subq.Absolute[1], cdr.subq.Absolute[2]);
519}
520
521static void cdrPlayInterrupt_Autopause()
522{
523 u32 abs_lev_max = 0;
524 boolean abs_lev_chselect;
525 u32 i;
526
527 if ((cdr.Mode & MODE_AUTOPAUSE) && cdr.TrackChanged) {
528 CDR_LOG_I("autopause\n");
529
530 SetResultSize(1);
531 cdr.Result[0] = cdr.StatP;
532 setIrq(DataEnd, 0x1000); // 0x1000 just for logging purposes
533
534 StopCdda();
535 SetPlaySeekRead(cdr.StatP, 0);
536 cdr.DriveState = DRIVESTATE_PAUSED;
537 }
538 else if ((cdr.Mode & MODE_REPORT) && !cdr.ReportDelay &&
539 ((cdr.subq.Absolute[2] & 0x0f) == 0 || cdr.FastForward || cdr.FastBackward))
540 {
541 SetResultSize(8);
542 cdr.Result[0] = cdr.StatP;
543 cdr.Result[1] = cdr.subq.Track;
544 cdr.Result[2] = cdr.subq.Index;
545
546 abs_lev_chselect = cdr.subq.Absolute[1] & 0x01;
547
548 /* 8 is a hack. For accuracy, it should be 588. */
549 for (i = 0; i < 8; i++)
550 {
551 abs_lev_max = MAX_VALUE(abs_lev_max, abs(read_buf[i * 2 + abs_lev_chselect]));
552 }
553 abs_lev_max = MIN_VALUE(abs_lev_max, 32767);
554 abs_lev_max |= abs_lev_chselect << 15;
555
556 if (cdr.subq.Absolute[2] & 0x10) {
557 cdr.Result[3] = cdr.subq.Relative[0];
558 cdr.Result[4] = cdr.subq.Relative[1] | 0x80;
559 cdr.Result[5] = cdr.subq.Relative[2];
560 }
561 else {
562 cdr.Result[3] = cdr.subq.Absolute[0];
563 cdr.Result[4] = cdr.subq.Absolute[1];
564 cdr.Result[5] = cdr.subq.Absolute[2];
565 }
566 cdr.Result[6] = abs_lev_max >> 0;
567 cdr.Result[7] = abs_lev_max >> 8;
568
569 setIrq(DataReady, 0x1001);
570 }
571
572 if (cdr.ReportDelay)
573 cdr.ReportDelay--;
574}
575
576static boolean canDoTurbo(void)
577{
578 u32 c = psxRegs.cycle;
579 return Config.TurboCD && !cdr.RetryDetected && !cdr.AdpcmActive
580 //&& c - psxRegs.intCycle[PSXINT_SPUDMA].sCycle > (u32)cdReadTime * 2
581 && c - psxRegs.intCycle[PSXINT_MDECOUTDMA].sCycle > (u32)cdReadTime * 16;
582}
583
584static int cdrSeekTime(unsigned char *target)
585{
586 int diff = abs((int)msf2sec(cdr.SetSectorPlay) - (int)msf2sec(target));
587 int seekTime = diff * (cdReadTime / 2000);
588 int cyclesSinceRS = psxRegs.cycle - cdr.LastReadSeekCycles;
589 seekTime = MAX_VALUE(seekTime, 20000);
590
591 // sled seek?
592 if (diff >= 7200)
593 seekTime = PSXCLK / 7 + diff * 64;
594 // add *something* as rotation time until the target sector
595 if (cyclesSinceRS >= cdReadTime)
596 seekTime += (8 - ((cyclesSinceRS >> 18) & 7)) * (cdReadTime / 2);
597
598 // Transformers Beast Wars Transmetals does Setloc(x),SeekL,Setloc(x),ReadN
599 // and then wants some slack time
600 if (cdr.DriveState == DRIVESTATE_PAUSED || cyclesSinceRS < cdReadTime *3/2)
601 seekTime += cdReadTime;
602
603 //seekTime = MIN_VALUE(seekTime, PSXCLK * 2 / 3);
604 CDR_LOG("seek: %02d:%02d:%02d %.2f %.2f (%.2f) st %d di %d\n",
605 target[0], target[1], target[2], (float)seekTime / PSXCLK,
606 (float)seekTime / cdReadTime, (float)cyclesSinceRS / cdReadTime,
607 cdr.DriveState, diff);
608 return seekTime;
609}
610
611static u32 cdrAlignTimingHack(u32 cycles)
612{
613 /*
614 * timing hack for T'ai Fu - Wrath of the Tiger:
615 * The game has a bug where it issues some cdc commands from a low priority
616 * vint handler, however there is a higher priority default bios handler
617 * that acks the vint irq and returns, so game's handler is not reached
618 * (see bios irq handler chains at e004 and the game's irq handling func
619 * at 80036810). For the game to work, vint has to arrive after the bios
620 * vint handler rejects some other irq (of which only cd and rcnt2 are
621 * active), but before the game's handler loop reads I_STAT. The time
622 * window for this is quite small (~1k cycles of so). Apparently this
623 * somehow happens naturally on the real hardware.
624 *
625 * Note: always enforcing this breaks other games like Crash PAL version
626 * (inputs get dropped because bios handler doesn't see interrupts).
627 */
628 u32 vint_rel = rcnts[3].cycleStart + 63000 - psxRegs.cycle;
629 while ((s32)(vint_rel - cycles) < 0)
630 vint_rel += PSXCLK / 60;
631 return vint_rel;
632}
633
634static void cdrUpdateTransferBuf(const u8 *buf);
635static void cdrReadInterrupt(void);
636static void cdrPrepCdda(s16 *buf, int samples);
637
638static void msfiAdd(u8 *msfi, u32 count)
639{
640 assert(count < 75);
641 msfi[2] += count;
642 if (msfi[2] >= 75) {
643 msfi[2] -= 75;
644 msfi[1]++;
645 if (msfi[1] == 60) {
646 msfi[1] = 0;
647 msfi[0]++;
648 }
649 }
650}
651
652static void msfiSub(u8 *msfi, u32 count)
653{
654 assert(count < 75);
655 msfi[2] -= count;
656 if ((s8)msfi[2] < 0) {
657 msfi[2] += 75;
658 msfi[1]--;
659 if ((s8)msfi[1] < 0) {
660 msfi[1] = 60;
661 msfi[0]--;
662 }
663 }
664}
665
666static int msfiEq(const u8 *a, const u8 *b)
667{
668 return a[0] == b[0] && a[1] == b[1] && a[2] == b[2];
669}
670
671void cdrPlayReadInterrupt(void)
672{
673 // this works but causes instability for timing sensitive games
674#if 0
675 int hit = cdra_prefetch(cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2]);
676 if (!hit && cdr.PhysCdPropagations < 75/2) {
677 // this propagates the real cdrom delays to the emulated game
678 CDRPLAYREAD_INT(cdReadTime / 2, 0);
679 cdr.PhysCdPropagations++;
680 return;
681 }
682#endif
683 cdr.LastReadSeekCycles = psxRegs.cycle;
684
685 if (cdr.Reading) {
686 cdrReadInterrupt();
687 return;
688 }
689
690 if (!cdr.Play) return;
691
692 CDR_LOG("CDDA - %02d:%02d:%02d m %02x\n",
693 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2], cdr.Mode);
694
695 cdr.DriveState = DRIVESTATE_PLAY_READ;
696 SetPlaySeekRead(cdr.StatP, STATUS_PLAY);
697 if (memcmp(cdr.SetSectorPlay, cdr.SetSectorEnd, 3) == 0) {
698 CDR_LOG_I("end stop\n");
699 StopCdda();
700 SetPlaySeekRead(cdr.StatP, 0);
701 cdr.TrackChanged = TRUE;
702 cdr.DriveState = DRIVESTATE_PAUSED;
703 }
704 else {
705 cdra_readCDDA(cdr.SetSectorPlay, read_buf);
706 }
707
708 if (!cdr.IrqStat && (cdr.Mode & (MODE_AUTOPAUSE|MODE_REPORT)))
709 cdrPlayInterrupt_Autopause();
710
711 if (cdr.Play && !Config.Cdda) {
712 cdrPrepCdda(read_buf, CD_FRAMESIZE_RAW / 4);
713 SPU_playCDDAchannel(read_buf, CD_FRAMESIZE_RAW, psxRegs.cycle, 0);
714 }
715
716 msfiAdd(cdr.SetSectorPlay, 1);
717 cdra_prefetch(cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2]);
718
719 // update for CdlGetlocP/autopause
720 generate_subq(cdr.SetSectorPlay);
721
722 CDRPLAYREAD_INT(cdReadTime, 0);
723
724 // stop propagation since it breaks streaming
725 cdr.PhysCdPropagations = 0xff;
726}
727
728static void softReset(void)
729{
730 cdra_getStatus(&cdr_stat);
731 if (cdr_stat.Status & STATUS_SHELLOPEN) {
732 cdr.DriveState = DRIVESTATE_LID_OPEN;
733 cdr.StatP = STATUS_SHELLOPEN;
734 }
735 else if (CdromId[0] == '\0') {
736 cdr.DriveState = DRIVESTATE_STOPPED;
737 cdr.StatP = 0;
738 }
739 else {
740 cdr.DriveState = DRIVESTATE_STANDBY;
741 cdr.StatP = STATUS_ROTATING;
742 }
743
744 cdr.FifoOffset = DATA_SIZE; // fifo empty
745 cdr.LocL[0] = LOCL_INVALID;
746 cdr.Mode = MODE_SIZE_2340;
747 cdr.Muted = FALSE;
748 SPU_setCDvol(cdr.AttenuatorLeftToLeft, cdr.AttenuatorLeftToRight,
749 cdr.AttenuatorRightToLeft, cdr.AttenuatorRightToRight, psxRegs.cycle);
750}
751
752#define CMD_PART2 0x100
753#define CMD_WHILE_NOT_READY 0x200
754
755void cdrInterrupt(void) {
756 int start_rotating = 0;
757 int error = 0;
758 u32 cycles, seekTime = 0;
759 u32 second_resp_time = 0;
760 const void *buf;
761 u8 ParamC;
762 int read_ok;
763 u16 not_ready = 0;
764 u8 IrqStat = Acknowledge;
765 u8 DriveStateOld;
766 u16 Cmd;
767 int i;
768
769 if (cdr.IrqStat) {
770 CDR_LOG_I("cmd %02x with irqstat %x\n",
771 cdr.CmdInProgress, cdr.IrqStat);
772 return;
773 }
774 if (cdr.Irq1Pending) {
775 // hand out the "newest" sector, according to nocash
776 cdrUpdateTransferBuf(cdra_getBuffer());
777 CDR_LOG_I("%x:%02x:%02x loaded on ack, cmd=%02x res=%02x\n",
778 cdr.Transfer[0], cdr.Transfer[1], cdr.Transfer[2],
779 cdr.CmdInProgress, cdr.Irq1Pending);
780 SetResultSize(1);
781 cdr.Result[0] = cdr.Irq1Pending;
782 cdr.Irq1Pending = 0;
783 setIrq((cdr.Irq1Pending & STATUS_ERROR) ? DiskError : DataReady, 0x1003);
784 return;
785 }
786
787 // default response
788 SetResultSize(1);
789 cdr.Result[0] = cdr.StatP;
790
791 Cmd = cdr.CmdInProgress;
792 cdr.CmdInProgress = 0;
793 ParamC = cdr.ParamC;
794
795 if (Cmd < 0x100) {
796 cdr.Ctrl &= ~0x80;
797 cdr.ParamC = 0;
798 cdr.Cmd = 0;
799 }
800
801 switch (cdr.DriveState) {
802 case DRIVESTATE_PREPARE_CD:
803 if (Cmd > 2) {
804 // Syphon filter 2 expects commands to work shortly after it sees
805 // STATUS_ROTATING, so give up trying to emulate the startup seq
806 cdr.DriveState = DRIVESTATE_STANDBY;
807 cdr.StatP &= ~STATUS_SEEK;
808 psxRegs.interrupt &= ~(1 << PSXINT_CDRLID);
809 break;
810 }
811 // fallthrough
812 case DRIVESTATE_LID_OPEN:
813 case DRIVESTATE_RESCAN_CD:
814 // no disk or busy with the initial scan, allowed cmds are limited
815 not_ready = CMD_WHILE_NOT_READY;
816 break;
817 }
818
819 switch (Cmd | not_ready) {
820 case CdlNop:
821 case CdlNop + CMD_WHILE_NOT_READY:
822 if (cdr.DriveState != DRIVESTATE_LID_OPEN)
823 cdr.StatP &= ~STATUS_SHELLOPEN;
824 break;
825
826 case CdlSetloc:
827 case CdlSetloc + CMD_WHILE_NOT_READY: // apparently?
828 if (cdr.StatP & STATUS_SHELLOPEN)
829 // wrong? Driver2 vs Amerzone
830 goto set_error;
831
832 // MM must be BCD, SS must be BCD and <0x60, FF must be BCD and <0x75
833 if (((cdr.Param[0] & 0x0F) > 0x09) || (cdr.Param[0] > 0x99) || ((cdr.Param[1] & 0x0F) > 0x09) || (cdr.Param[1] >= 0x60) || ((cdr.Param[2] & 0x0F) > 0x09) || (cdr.Param[2] >= 0x75))
834 {
835 CDR_LOG_I("Invalid/out of range seek to %02X:%02X:%02X\n", cdr.Param[0], cdr.Param[1], cdr.Param[2]);
836 if (++cdr.errorRetryhack > 100)
837 break;
838 error = ERROR_BAD_ARGNUM;
839 goto set_error;
840 }
841 else
842 {
843 u8 set_loc[3];
844 for (i = 0; i < 3; i++)
845 set_loc[i] = btoi(cdr.Param[i]);
846 if ((msfiEq(cdr.SetSector, set_loc)) //|| msfiEq(cdr.Param, cdr.Transfer))
847 && !cdr.SetlocPending)
848 cdr.RetryDetected++;
849 else
850 cdr.RetryDetected = 0;
851 memcpy(cdr.SetSector, set_loc, 3);
852 cdr.SetSector[3] = 0;
853 cdr.SetlocPending = 1;
854 cdr.errorRetryhack = 0;
855 }
856 break;
857
858 do_CdlPlay:
859 case CdlPlay:
860 StopCdda();
861 StopReading();
862
863 cdr.FastBackward = 0;
864 cdr.FastForward = 0;
865
866 // BIOS CD Player
867 // - Pause player, hit Track 01/02/../xx (Setloc issued!!)
868
869 if (ParamC != 0 && cdr.Param[0] != 0) {
870 int track = btoi( cdr.Param[0] );
871
872 if (track <= cdr.ResultTN[1])
873 cdr.CurTrack = track;
874
875 CDR_LOG("PLAY track %d\n", cdr.CurTrack);
876
877 if (cdra_getTD(cdr.CurTrack, cdr.ResultTD) != -1) {
878 seekTime = cdrSeekTime(cdr.ResultTD);
879 memcpy(cdr.SetSectorPlay, cdr.ResultTD, 3);
880 }
881 }
882 else if (cdr.SetlocPending) {
883 seekTime = cdrSeekTime(cdr.SetSector);
884 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
885 }
886 else {
887 CDR_LOG("PLAY Resume @ %d:%d:%d\n",
888 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2]);
889 }
890 cdr.SetlocPending = 0;
891
892 /*
893 Rayman: detect track changes
894 - fixes logo freeze
895
896 Twisted Metal 2: skip PREGAP + starting accurate SubQ
897 - plays tracks without retry play
898
899 Wild 9: skip PREGAP + starting accurate SubQ
900 - plays tracks without retry play
901 */
902 Find_CurTrack(cdr.SetSectorPlay);
903 generate_subq(cdr.SetSectorPlay);
904 cdr.LocL[0] = LOCL_INVALID;
905 cdr.SubqForwardSectors = 1;
906 cdr.TrackChanged = FALSE;
907 cdr.FileChannelSelected = 0;
908 cdr.AdpcmActive = 0;
909 cdr.ReportDelay = 60;
910 cdr.sectorsRead = 0;
911
912 SetPlaySeekRead(cdr.StatP, STATUS_SEEK | STATUS_ROTATING);
913
914 // BIOS player - set flag again
915 cdr.Play = TRUE;
916 cdr.DriveState = DRIVESTATE_PLAY_READ;
917 cdr.PhysCdPropagations = 0;
918
919 CDRPLAYREAD_INT(cdReadTime + seekTime, 1);
920 start_rotating = 1;
921 break;
922
923 case CdlForward:
924 // TODO: error 80 if stopped
925 IrqStat = Complete;
926
927 // GameShark CD Player: Calls 2x + Play 2x
928 cdr.FastForward = 1;
929 cdr.FastBackward = 0;
930 break;
931
932 case CdlBackward:
933 IrqStat = Complete;
934
935 // GameShark CD Player: Calls 2x + Play 2x
936 cdr.FastBackward = 1;
937 cdr.FastForward = 0;
938 break;
939
940 case CdlStandby:
941 if (cdr.DriveState != DRIVESTATE_STOPPED) {
942 error = ERROR_BAD_ARGNUM;
943 goto set_error;
944 }
945 second_resp_time = cdReadTime * 125 / 2;
946 start_rotating = 1;
947 break;
948
949 case CdlStandby + CMD_PART2:
950 IrqStat = Complete;
951 break;
952
953 case CdlStop:
954 if (cdr.Play) {
955 // grab time for current track
956 cdra_getTD(cdr.CurTrack, cdr.ResultTD);
957 memcpy(cdr.SetSectorPlay, cdr.ResultTD, 3);
958 }
959
960 StopCdda();
961 StopReading();
962 SetPlaySeekRead(cdr.StatP, 0);
963 cdr.StatP &= ~STATUS_ROTATING;
964 cdr.LocL[0] = LOCL_INVALID;
965
966 second_resp_time = 0x800;
967 if (cdr.DriveState != DRIVESTATE_STOPPED)
968 second_resp_time = cdReadTime * 30 / 2;
969
970 cdr.DriveState = DRIVESTATE_STOPPED;
971 break;
972
973 case CdlStop + CMD_PART2:
974 IrqStat = Complete;
975 break;
976
977 case CdlPause:
978 if (cdr.AdpcmActive) {
979 cdr.AdpcmActive = 0;
980 cdr.Xa.nsamples = 0;
981 SPU_playADPCMchannel(&cdr.Xa, psxRegs.cycle, 1); // flush adpcm
982 }
983 StopCdda();
984 StopReading();
985
986 // how the drive maintains the position while paused is quite
987 // complicated, this is the minimum to make "Bedlam" happy
988 msfiSub(cdr.SetSectorPlay, MIN_VALUE(cdr.sectorsRead, 4));
989 cdr.sectorsRead = 0;
990
991 /*
992 Gundam Battle Assault 2
993 Hokuto no Ken 2
994 InuYasha - Feudal Fairy Tale
995 Dance Dance Revolution Konamix
996 Digimon Rumble Arena
997 ...
998 */
999 if (!(cdr.StatP & (STATUS_PLAY | STATUS_READ)))
1000 {
1001 second_resp_time = 7000;
1002 }
1003 else
1004 {
1005 second_resp_time = 2100011;
1006 // a hack to try to avoid weird cmd vs irq1 races causing games to retry
1007 second_resp_time += (cdr.RetryDetected & 15) * 100001;
1008 }
1009 SetPlaySeekRead(cdr.StatP, 0);
1010 DriveStateOld = cdr.DriveState;
1011 cdr.DriveState = DRIVESTATE_PAUSED;
1012 if (DriveStateOld == DRIVESTATE_SEEK) {
1013 // According to Duckstation this fails, but the
1014 // exact conditions and effects are not clear.
1015 // Moto Racer World Tour seems to rely on this.
1016 // For now assume pause works anyway, just errors out.
1017 error = ERROR_NOTREADY;
1018 goto set_error;
1019 }
1020 break;
1021
1022 case CdlPause + CMD_PART2:
1023 IrqStat = Complete;
1024 break;
1025
1026 case CdlReset:
1027 case CdlReset + CMD_WHILE_NOT_READY:
1028 // note: nocash and Duckstation calls this 'Init', but
1029 // the official SDK calls it 'Reset', and so do we
1030 StopCdda();
1031 StopReading();
1032 softReset();
1033 second_resp_time = not_ready ? 70000 : 4100000;
1034 start_rotating = 1;
1035 break;
1036
1037 case CdlReset + CMD_PART2:
1038 case CdlReset + CMD_PART2 + CMD_WHILE_NOT_READY:
1039 IrqStat = Complete;
1040 break;
1041
1042 case CdlMute:
1043 cdr.Muted = TRUE;
1044 SPU_setCDvol(0, 0, 0, 0, psxRegs.cycle);
1045 break;
1046
1047 case CdlDemute:
1048 cdr.Muted = FALSE;
1049 SPU_setCDvol(cdr.AttenuatorLeftToLeft, cdr.AttenuatorLeftToRight,
1050 cdr.AttenuatorRightToLeft, cdr.AttenuatorRightToRight, psxRegs.cycle);
1051 break;
1052
1053 case CdlSetfilter:
1054 cdr.FilterFile = cdr.Param[0];
1055 cdr.FilterChannel = cdr.Param[1];
1056 cdr.FileChannelSelected = 0;
1057 break;
1058
1059 case CdlSetmode:
1060 case CdlSetmode + CMD_WHILE_NOT_READY:
1061 CDR_LOG("cdrWrite1() Log: Setmode %x\n", cdr.Param[0]);
1062 cdr.Mode = cdr.Param[0];
1063 break;
1064
1065 case CdlGetparam:
1066 case CdlGetparam + CMD_WHILE_NOT_READY:
1067 /* Gameblabla : According to mednafen, Result size should be 5 and done this way. */
1068 SetResultSize_(5);
1069 cdr.Result[1] = cdr.Mode;
1070 cdr.Result[2] = 0;
1071 cdr.Result[3] = cdr.FilterFile;
1072 cdr.Result[4] = cdr.FilterChannel;
1073 break;
1074
1075 case CdlGetlocL:
1076 if (cdr.LocL[0] == LOCL_INVALID) {
1077 error = 0x80;
1078 goto set_error;
1079 }
1080 SetResultSize_(8);
1081 memcpy(cdr.Result, cdr.LocL, 8);
1082 break;
1083
1084 case CdlGetlocP:
1085 SetResultSize_(8);
1086 memcpy(&cdr.Result, &cdr.subq, 8);
1087 break;
1088
1089 case CdlReadT: // SetSession?
1090 // really long
1091 second_resp_time = cdReadTime * 290 / 4;
1092 start_rotating = 1;
1093 break;
1094
1095 case CdlReadT + CMD_PART2:
1096 IrqStat = Complete;
1097 break;
1098
1099 case CdlGetTN:
1100 if (cdra_getTN(cdr.ResultTN) != 0) {
1101 assert(0);
1102 }
1103 SetResultSize_(3);
1104 cdr.Result[1] = itob(cdr.ResultTN[0]);
1105 cdr.Result[2] = itob(cdr.ResultTN[1]);
1106 break;
1107
1108 case CdlGetTD:
1109 cdr.Track = btoi(cdr.Param[0]);
1110 if (cdra_getTD(cdr.Track, cdr.ResultTD) != 0) {
1111 error = ERROR_BAD_ARGVAL;
1112 goto set_error;
1113 }
1114 SetResultSize_(3);
1115 cdr.Result[1] = itob(cdr.ResultTD[0]);
1116 cdr.Result[2] = itob(cdr.ResultTD[1]);
1117 // no sector number
1118 //cdr.Result[3] = itob(cdr.ResultTD[2]);
1119 break;
1120
1121 case CdlSeekL:
1122 case CdlSeekP:
1123 StopCdda();
1124 StopReading();
1125 SetPlaySeekRead(cdr.StatP, STATUS_SEEK | STATUS_ROTATING);
1126
1127 if (!canDoTurbo())
1128 seekTime = cdrSeekTime(cdr.SetSector);
1129 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
1130 cdr.DriveState = DRIVESTATE_SEEK;
1131 cdra_prefetch(cdr.SetSectorPlay[0], cdr.SetSectorPlay[1],
1132 cdr.SetSectorPlay[2]);
1133 /*
1134 Crusaders of Might and Magic = 0.5x-4x
1135 - fix cutscene speech start
1136
1137 Eggs of Steel = 2x-?
1138 - fix new game
1139
1140 Medievil = ?-4x
1141 - fix cutscene speech
1142
1143 Rockman X5 = 0.5-4x
1144 - fix capcom logo
1145 */
1146 second_resp_time = cdReadTime + seekTime;
1147 start_rotating = 1;
1148 break;
1149
1150 case CdlSeekL + CMD_PART2:
1151 case CdlSeekP + CMD_PART2:
1152 SetPlaySeekRead(cdr.StatP, 0);
1153 cdr.Result[0] = cdr.StatP;
1154 IrqStat = Complete;
1155
1156 Find_CurTrack(cdr.SetSectorPlay);
1157 read_ok = ReadTrack(cdr.SetSectorPlay);
1158 if (read_ok && (buf = cdra_getBuffer()))
1159 memcpy(cdr.LocL, buf, 8);
1160 UpdateSubq(cdr.SetSectorPlay);
1161 cdr.DriveState = DRIVESTATE_STANDBY;
1162 cdr.TrackChanged = FALSE;
1163 cdr.LastReadSeekCycles = psxRegs.cycle;
1164 break;
1165
1166 case CdlTest:
1167 case CdlTest + CMD_WHILE_NOT_READY:
1168 switch (cdr.Param[0]) {
1169 case 0x20: // System Controller ROM Version
1170 SetResultSize_(4);
1171 memcpy(cdr.Result, Test20, 4);
1172 break;
1173 case 0x22:
1174 SetResultSize_(8);
1175 memcpy(cdr.Result, Test22, 4);
1176 break;
1177 case 0x23: case 0x24:
1178 SetResultSize_(8);
1179 memcpy(cdr.Result, Test23, 4);
1180 break;
1181 }
1182 break;
1183
1184 case CdlID:
1185 second_resp_time = 20480;
1186 break;
1187
1188 case CdlID + CMD_PART2:
1189 SetResultSize_(8);
1190 cdr.Result[0] = cdr.StatP;
1191 cdr.Result[1] = 0;
1192 cdr.Result[2] = 0;
1193 cdr.Result[3] = 0;
1194
1195 // 0x10 - audio | 0x40 - disk missing | 0x80 - unlicensed
1196 if (cdra_getStatus(&cdr_stat) != 0 || cdr_stat.Type == 0 || cdr_stat.Type == 0xff) {
1197 cdr.Result[1] = 0xc0;
1198 }
1199 else {
1200 if (cdr_stat.Type == 2)
1201 cdr.Result[1] |= 0x10;
1202 if (CdromId[0] == '\0')
1203 cdr.Result[1] |= 0x80;
1204 }
1205 cdr.Result[0] |= (cdr.Result[1] >> 4) & 0x08;
1206 CDR_LOG_I("CdlID: %02x %02x %02x %02x\n", cdr.Result[0],
1207 cdr.Result[1], cdr.Result[2], cdr.Result[3]);
1208
1209 /* This adds the string "PCSX" in Playstation bios boot screen */
1210 memcpy((char *)&cdr.Result[4], "PCSX", 4);
1211 IrqStat = Complete;
1212 break;
1213
1214 case CdlInit:
1215 case CdlInit + CMD_WHILE_NOT_READY:
1216 StopCdda();
1217 StopReading();
1218 SetPlaySeekRead(cdr.StatP, 0);
1219 // yes, it really sets STATUS_SHELLOPEN
1220 cdr.StatP |= STATUS_SHELLOPEN;
1221 cdr.DriveState = DRIVESTATE_RESCAN_CD;
1222 set_event(PSXINT_CDRLID, 20480);
1223 start_rotating = 1;
1224 break;
1225
1226 case CdlGetQ:
1227 case CdlGetQ + CMD_WHILE_NOT_READY:
1228 break;
1229
1230 case CdlReadToc:
1231 case CdlReadToc + CMD_WHILE_NOT_READY:
1232 cdr.LocL[0] = LOCL_INVALID;
1233 second_resp_time = cdReadTime * 180 / 4;
1234 start_rotating = 1;
1235 break;
1236
1237 case CdlReadToc + CMD_PART2:
1238 case CdlReadToc + CMD_PART2 + CMD_WHILE_NOT_READY:
1239 IrqStat = Complete;
1240 break;
1241
1242 case CdlReadN:
1243 case CdlReadS:
1244 if (cdr.Reading && !cdr.SetlocPending)
1245 break;
1246
1247 Find_CurTrack(cdr.SetlocPending ? cdr.SetSector : cdr.SetSectorPlay);
1248
1249 if ((cdr.Mode & MODE_CDDA) && cdr.CurTrack > 1)
1250 // Read* acts as play for cdda tracks in cdda mode
1251 goto do_CdlPlay;
1252
1253 StopCdda();
1254 if (cdr.SetlocPending) {
1255 seekTime = cdrSeekTime(cdr.SetSector);
1256 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
1257 cdr.SetlocPending = 0;
1258 }
1259 cdr.Reading = 1;
1260 cdr.FileChannelSelected = 0;
1261 cdr.AdpcmActive = 0;
1262
1263 // Fighting Force 2 - update subq time immediately
1264 // - fixes new game
1265 UpdateSubq(cdr.SetSectorPlay);
1266 cdr.LocL[0] = LOCL_INVALID;
1267 cdr.SubqForwardSectors = 1;
1268 cdr.sectorsRead = 0;
1269 cdr.DriveState = DRIVESTATE_SEEK;
1270 cdr.PhysCdPropagations = 0;
1271 cdra_prefetch(cdr.SetSectorPlay[0], cdr.SetSectorPlay[1],
1272 cdr.SetSectorPlay[2]);
1273
1274 cycles = (cdr.Mode & MODE_SPEED) ? cdReadTime : cdReadTime * 2;
1275 cycles += seekTime;
1276 if (Config.hacks.cdr_read_timing)
1277 cycles = cdrAlignTimingHack(cycles);
1278 else if (canDoTurbo())
1279 cycles = cdReadTime / 2;
1280 CDRPLAYREAD_INT(cycles, 1);
1281
1282 SetPlaySeekRead(cdr.StatP, STATUS_SEEK);
1283 start_rotating = 1;
1284 break;
1285
1286 case CdlSync:
1287 default:
1288 error = ERROR_INVALIDCMD;
1289 // FALLTHROUGH
1290
1291 set_error:
1292 SetResultSize_(2);
1293 cdr.Result[0] = cdr.StatP | STATUS_ERROR;
1294 cdr.Result[1] = not_ready ? ERROR_NOTREADY : error;
1295 IrqStat = DiskError;
1296 CDR_LOG_I("cmd %02x error %02x\n", Cmd, cdr.Result[1]);
1297 break;
1298 }
1299
1300 if (cdr.DriveState == DRIVESTATE_STOPPED && start_rotating) {
1301 cdr.DriveState = DRIVESTATE_STANDBY;
1302 cdr.StatP |= STATUS_ROTATING;
1303 }
1304
1305 if (second_resp_time) {
1306 cdr.CmdInProgress = Cmd | 0x100;
1307 set_event(PSXINT_CDR, second_resp_time);
1308 }
1309 else if (cdr.Cmd && cdr.Cmd != (Cmd & 0xff)) {
1310 cdr.CmdInProgress = cdr.Cmd;
1311 CDR_LOG_I("cmd %02x came before %02x finished\n", cdr.Cmd, Cmd);
1312 }
1313
1314 setIrq(IrqStat, Cmd);
1315}
1316
1317static void cdrPrepCdda(s16 *buf, int samples)
1318{
1319#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
1320 int i;
1321 for (i = 0; i < samples; i++) {
1322 buf[i * 2 + 0] = SWAP16(buf[i * 2 + 0]);
1323 buf[i * 2 + 1] = SWAP16(buf[i * 2 + 1]);
1324 }
1325#endif
1326}
1327
1328static void cdrReadInterruptSetResult(unsigned char result)
1329{
1330 if (cdr.IrqStat) {
1331 CDR_LOG_I("%d:%02d:%02d irq miss, cmd=%02x irqstat=%02x\n",
1332 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2],
1333 cdr.CmdInProgress, cdr.IrqStat);
1334 cdr.Irq1Pending = result;
1335 // F1 2000 timing hack :(
1336 // compensate for some csum func @80014380 taking too long
1337 if (!cdr.AdpcmActive)
1338 psxRegs.intCycle[PSXINT_CDREAD].sCycle += cdReadTime / 10;
1339 return;
1340 }
1341 SetResultSize(1);
1342 cdr.Result[0] = result;
1343 setIrq((result & STATUS_ERROR) ? DiskError : DataReady, 0x1004);
1344}
1345
1346static void cdrUpdateTransferBuf(const u8 *buf)
1347{
1348 if (!buf)
1349 return;
1350 memcpy(cdr.Transfer, buf, DATA_SIZE);
1351 CheckPPFCache(cdr.Transfer, cdr.Prev[0], cdr.Prev[1], cdr.Prev[2]);
1352 CDR_LOG("cdr.Transfer %02x:%02x:%02x\n",
1353 cdr.Transfer[0], cdr.Transfer[1], cdr.Transfer[2]);
1354 if (cdr.FifoOffset < 2048 + 12)
1355 CDR_LOG("FifoOffset(1) %d/%d\n", cdr.FifoOffset, cdr.FifoSize);
1356}
1357
1358static void cdrReadInterrupt(void)
1359{
1360 const struct { u8 file, chan, mode, coding; } *subhdr;
1361 const u8 *buf = NULL;
1362 int deliver_data = 1;
1363 u8 subqPos[3];
1364 int read_ok;
1365 int is_start;
1366
1367 memcpy(subqPos, cdr.SetSectorPlay, sizeof(subqPos));
1368 msfiAdd(subqPos, cdr.SubqForwardSectors);
1369 UpdateSubq(subqPos);
1370 if (cdr.SubqForwardSectors < SUBQ_FORWARD_SECTORS) {
1371 cdr.SubqForwardSectors++;
1372 CDRPLAYREAD_INT((cdr.Mode & MODE_SPEED) ? (cdReadTime / 2) : cdReadTime, 0);
1373 return;
1374 }
1375
1376 // note: CdlGetlocL should work as soon as STATUS_READ is indicated
1377 SetPlaySeekRead(cdr.StatP, STATUS_READ | STATUS_ROTATING);
1378 cdr.DriveState = DRIVESTATE_PLAY_READ;
1379 cdr.sectorsRead++;
1380
1381 read_ok = ReadTrack(cdr.SetSectorPlay);
1382 if (read_ok)
1383 buf = cdra_getBuffer();
1384 if (buf == NULL)
1385 read_ok = 0;
1386
1387 if (!read_ok) {
1388 CDR_LOG_I("cdrReadInterrupt() Log: err\n");
1389 cdrReadInterruptSetResult(cdr.StatP | STATUS_ERROR);
1390 cdr.DriveState = DRIVESTATE_PAUSED; // ?
1391 return;
1392 }
1393 memcpy(cdr.LocL, buf, 8);
1394
1395 if (!cdr.IrqStat && !cdr.Irq1Pending)
1396 cdrUpdateTransferBuf(buf);
1397
1398 subhdr = (void *)(buf + 4);
1399 do {
1400 // try to process as adpcm
1401 if (!(cdr.Mode & MODE_STRSND))
1402 break;
1403 if (buf[3] != 2 || (subhdr->mode & 0x44) != 0x44) // or 0x64?
1404 break;
1405 CDR_LOG("f=%d m=%d %d,%3d | %d,%2d | %d,%2d\n", !!(cdr.Mode & MODE_SF), cdr.Muted,
1406 subhdr->file, subhdr->chan, cdr.CurFile, cdr.CurChannel, cdr.FilterFile, cdr.FilterChannel);
1407 if ((cdr.Mode & MODE_SF) && (subhdr->file != cdr.FilterFile || subhdr->chan != cdr.FilterChannel))
1408 break;
1409 if (subhdr->chan & 0x80) { // ?
1410 if (subhdr->chan != 0xff)
1411 log_unhandled("adpcm %d:%d\n", subhdr->file, subhdr->chan);
1412 break;
1413 }
1414 if (!cdr.FileChannelSelected) {
1415 cdr.CurFile = subhdr->file;
1416 cdr.CurChannel = subhdr->chan;
1417 cdr.FileChannelSelected = 1;
1418 }
1419 else if (subhdr->file != cdr.CurFile || subhdr->chan != cdr.CurChannel)
1420 break;
1421
1422 // accepted as adpcm
1423 deliver_data = 0;
1424
1425 if (Config.Xa)
1426 break;
1427 is_start = !cdr.AdpcmActive;
1428 cdr.AdpcmActive = !xa_decode_sector(&cdr.Xa, buf + 4, is_start);
1429 if (cdr.AdpcmActive)
1430 SPU_playADPCMchannel(&cdr.Xa, psxRegs.cycle, is_start);
1431 } while (0);
1432
1433 if ((cdr.Mode & MODE_SF) && (subhdr->mode & 0x44) == 0x44) // according to nocash
1434 deliver_data = 0;
1435 if (buf[3] != 1 && buf[3] != 2) { // according to duckstation
1436 deliver_data = 0;
1437 CDR_LOG_I("%x:%02x:%02x mode %02x ignored\n",
1438 buf[0], buf[1], buf[2], buf[3]);
1439 }
1440
1441 /*
1442 Croc 2: $40 - only FORM1 (*)
1443 Judge Dredd: $C8 - only FORM1 (*)
1444 Sim Theme Park - no adpcm at all (zero)
1445 */
1446
1447 if (deliver_data)
1448 cdrReadInterruptSetResult(cdr.StatP);
1449
1450 msfiAdd(cdr.SetSectorPlay, 1);
1451 cdra_prefetch(cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2]);
1452
1453 CDRPLAYREAD_INT((cdr.Mode & MODE_SPEED) ? (cdReadTime / 2) : cdReadTime, 0);
1454}
1455
1456/*
1457cdrRead0:
1458 bit 0,1 - reg index
1459 bit 2 - adpcm active
1460 bit 3 - 1 parameter fifo empty
1461 bit 4 - 1 parameter fifo not full
1462 bit 5 - 1 response fifo not empty
1463 bit 6 - 1 data fifo not empty
1464 bit 7 - 1 command being processed
1465*/
1466
1467unsigned char cdrRead0(void) {
1468 cdr.Ctrl &= ~0x64;
1469 cdr.Ctrl |= cdr.AdpcmActive << 2;
1470 cdr.Ctrl |= cdr.ResultReady << 5;
1471 cdr.Ctrl |= ((signed int)(cdr.FifoOffset - cdr.FifoSize) >> 31) & 0x40;
1472
1473 cdr.Ctrl |= 0x18;
1474
1475 CDR_LOG_IO("cdr r0.sta: %02x\n", cdr.Ctrl);
1476
1477 return psxHu8(0x1800) = cdr.Ctrl;
1478}
1479
1480void cdrWrite0(unsigned char rt) {
1481 CDR_LOG_IO("cdr w0.x.idx: %02x\n", rt);
1482
1483 cdr.Ctrl = (rt & 3) | (cdr.Ctrl & ~3);
1484}
1485
1486unsigned char cdrRead1(void) {
1487 if ((cdr.ResultP & 0xf) < cdr.ResultC)
1488 psxHu8(0x1801) = cdr.Result[cdr.ResultP & 0xf];
1489 else
1490 psxHu8(0x1801) = 0;
1491 cdr.ResultP++;
1492 if (cdr.ResultP == cdr.ResultC)
1493 cdr.ResultReady = 0;
1494
1495 CDR_LOG_IO("cdr r1.x.rsp: %02x #%u\n", psxHu8(0x1801), cdr.ResultP - 1);
1496
1497 return psxHu8(0x1801);
1498}
1499
1500void cdrWrite1(unsigned char rt) {
1501 const char *rnames[] = { "0.cmd", "1.smd", "2.smc", "3.arr" }; (void)rnames;
1502 CDR_LOG_IO("cdr w1.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1503
1504 switch (cdr.Ctrl & 3) {
1505 case 0:
1506 break;
1507 case 3:
1508 cdr.AttenuatorRightToRightT = rt;
1509 return;
1510 default:
1511 return;
1512 }
1513
1514#ifdef CDR_LOG_CMD_IRQ
1515 CDR_LOG_I("CD1 write: %x (%s)", rt, CmdName[rt]);
1516 if (cdr.ParamC) {
1517 int i;
1518 SysPrintf(" Param[%d] = {", cdr.ParamC);
1519 for (i = 0; i < cdr.ParamC; i++)
1520 SysPrintf(" %x,", cdr.Param[i]);
1521 SysPrintf("}");
1522 }
1523 SysPrintf(" @%08x\n", psxRegs.pc);
1524#endif
1525
1526 cdr.ResultReady = 0;
1527 cdr.Ctrl |= 0x80;
1528
1529 if (!cdr.CmdInProgress) {
1530 cdr.CmdInProgress = rt;
1531 // should be something like 12k + controller delays
1532 set_event(PSXINT_CDR, 5000);
1533 }
1534 else {
1535 CDR_LOG_I("cmd while busy: %02x, prev %02x, busy %02x\n",
1536 rt, cdr.Cmd, cdr.CmdInProgress);
1537 if (cdr.CmdInProgress < 0x100) // no pending 2nd response
1538 cdr.CmdInProgress = rt;
1539 }
1540
1541 cdr.Cmd = rt;
1542}
1543
1544unsigned char cdrRead2(void) {
1545 unsigned char ret = cdr.Transfer[0x920];
1546
1547 if (cdr.FifoOffset < cdr.FifoSize)
1548 ret = cdr.Transfer[cdr.FifoOffset++];
1549 else
1550 CDR_LOG_I("read empty fifo (%d)\n", cdr.FifoSize);
1551
1552 CDR_LOG_IO("cdr r2.x.dat: %02x\n", ret);
1553 return ret;
1554}
1555
1556void cdrWrite2(unsigned char rt) {
1557 const char *rnames[] = { "0.prm", "1.ien", "2.all", "3.arl" }; (void)rnames;
1558 CDR_LOG_IO("cdr w2.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1559
1560 switch (cdr.Ctrl & 3) {
1561 case 0:
1562 if (cdr.ParamC < 8) // FIXME: size and wrapping
1563 cdr.Param[cdr.ParamC++] = rt;
1564 return;
1565 case 1:
1566 cdr.IrqMask = rt;
1567 setIrq(cdr.IrqStat, 0x1005);
1568 return;
1569 case 2:
1570 cdr.AttenuatorLeftToLeftT = rt;
1571 return;
1572 case 3:
1573 cdr.AttenuatorRightToLeftT = rt;
1574 return;
1575 }
1576}
1577
1578unsigned char cdrRead3(void) {
1579 if (cdr.Ctrl & 0x1)
1580 psxHu8(0x1803) = cdr.IrqStat | 0xE0;
1581 else
1582 psxHu8(0x1803) = cdr.IrqMask | 0xE0;
1583
1584 CDR_LOG_IO("cdr r3.%d.%s: %02x\n", cdr.Ctrl & 3,
1585 (cdr.Ctrl & 1) ? "ifl" : "ien", psxHu8(0x1803));
1586 return psxHu8(0x1803);
1587}
1588
1589void cdrWrite3(unsigned char rt) {
1590 const char *rnames[] = { "0.req", "1.ifl", "2.alr", "3.ava" }; (void)rnames;
1591 u8 ll, lr, rl, rr;
1592 CDR_LOG_IO("cdr w3.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1593
1594 switch (cdr.Ctrl & 3) {
1595 case 0:
1596 break; // transfer
1597 case 1:
1598 if (cdr.IrqStat & rt) {
1599 u32 nextCycle = psxRegs.intCycle[PSXINT_CDR].sCycle
1600 + psxRegs.intCycle[PSXINT_CDR].cycle;
1601 int pending = psxRegs.interrupt & (1 << PSXINT_CDR);
1602#ifdef CDR_LOG_CMD_IRQ
1603 CDR_LOG_I("ack %02x (w=%02x p=%d,%x,%x,%d)\n",
1604 cdr.IrqStat & rt, rt, !!pending, cdr.CmdInProgress,
1605 cdr.Irq1Pending, nextCycle - psxRegs.cycle);
1606#endif
1607 // note: Croc, Shadow Tower (more) vs Discworld Noir (<993)
1608 if (!pending && (cdr.CmdInProgress || cdr.Irq1Pending))
1609 {
1610 s32 c = 2048;
1611 if (cdr.CmdInProgress) {
1612 c = 2048 - (psxRegs.cycle - nextCycle);
1613 c = MAX_VALUE(c, 512);
1614 }
1615 set_event(PSXINT_CDR, c);
1616 }
1617 }
1618 cdr.IrqStat &= ~rt;
1619
1620 if (rt & 0x40)
1621 cdr.ParamC = 0;
1622 return;
1623 case 2:
1624 cdr.AttenuatorLeftToRightT = rt;
1625 return;
1626 case 3:
1627 if (rt & 0x01)
1628 log_unhandled("Mute ADPCM?\n");
1629 if (rt & 0x20) {
1630 ll = cdr.AttenuatorLeftToLeftT; lr = cdr.AttenuatorLeftToRightT;
1631 rl = cdr.AttenuatorRightToLeftT; rr = cdr.AttenuatorRightToRightT;
1632 if (ll == cdr.AttenuatorLeftToLeft &&
1633 lr == cdr.AttenuatorLeftToRight &&
1634 rl == cdr.AttenuatorRightToLeft &&
1635 rr == cdr.AttenuatorRightToRight)
1636 return;
1637 cdr.AttenuatorLeftToLeft = ll; cdr.AttenuatorLeftToRight = lr;
1638 cdr.AttenuatorRightToLeft = rl; cdr.AttenuatorRightToRight = rr;
1639 CDR_LOG_I("CD-XA Volume: %02x %02x | %02x %02x\n", ll, lr, rl, rr);
1640 SPU_setCDvol(ll, lr, rl, rr, psxRegs.cycle);
1641 }
1642 return;
1643 }
1644
1645 // test: Viewpoint
1646 if ((rt & 0x80) && cdr.FifoOffset < cdr.FifoSize) {
1647 CDR_LOG("cdrom: FifoOffset(2) %d/%d\n", cdr.FifoOffset, cdr.FifoSize);
1648 }
1649 else if (rt & 0x80) {
1650 switch (cdr.Mode & (MODE_SIZE_2328|MODE_SIZE_2340)) {
1651 case MODE_SIZE_2328:
1652 case 0x00:
1653 cdr.FifoOffset = 12;
1654 cdr.FifoSize = 2048 + 12;
1655 break;
1656
1657 case MODE_SIZE_2340:
1658 default:
1659 cdr.FifoOffset = 0;
1660 cdr.FifoSize = 2340;
1661 break;
1662 }
1663 }
1664 else if (!(rt & 0xc0))
1665 cdr.FifoOffset = DATA_SIZE; // fifo empty
1666}
1667
1668void psxDma3(u32 madr, u32 bcr, u32 chcr) {
1669 u32 cdsize, max_words, cycles;
1670 int size;
1671 u8 *ptr;
1672
1673#if 0
1674 CDR_LOG_I("psxDma3() Log: *** DMA 3 *** %x addr = %x size = %x", chcr, madr, bcr);
1675 if (cdr.FifoOffset == 0) {
1676 ptr = cdr.Transfer;
1677 SysPrintf(" %02x:%02x:%02x", ptr[0], ptr[1], ptr[2]);
1678 }
1679 SysPrintf("\n");
1680#endif
1681
1682 switch (chcr & 0x71000000) {
1683 case 0x11000000:
1684 madr &= ~3;
1685 ptr = getDmaRam(madr, &max_words);
1686 if (ptr == INVALID_PTR) {
1687 CDR_LOG_I("psxDma3() Log: *** DMA 3 *** NULL Pointer!\n");
1688 break;
1689 }
1690
1691 cdsize = (((bcr - 1) & 0xffff) + 1) * 4;
1692
1693 /*
1694 GS CDX: Enhancement CD crash
1695 - Setloc 0:0:0
1696 - CdlPlay
1697 - Spams DMA3 and gets buffer overrun
1698 */
1699 size = DATA_SIZE - cdr.FifoOffset;
1700 if (size > cdsize)
1701 size = cdsize;
1702 if (size > max_words * 4)
1703 size = max_words * 4;
1704 if (size > 0)
1705 {
1706 memcpy(ptr, cdr.Transfer + cdr.FifoOffset, size);
1707 cdr.FifoOffset += size;
1708 }
1709 if (size < cdsize) {
1710 CDR_LOG_I("cdrom: dma3 %d/%d\n", size, cdsize);
1711 memset(ptr + size, cdr.Transfer[0x920], cdsize - size);
1712 }
1713 psxCpu->Clear(madr, cdsize / 4);
1714
1715 cycles = (cdsize / 4) * 24;
1716 set_event(PSXINT_CDRDMA, cycles);
1717
1718 HW_DMA3_CHCR &= SWAPu32(~0x10000000);
1719 if (chcr & 0x100) {
1720 HW_DMA3_MADR = SWAPu32(madr + cdsize);
1721 HW_DMA3_BCR &= SWAPu32(0xffff0000);
1722 }
1723 else {
1724 // halted
1725 psxRegs.cycle += cycles - 20;
1726 }
1727 if (canDoTurbo() && cdr.Reading && cdr.FifoOffset >= 2048)
1728 CDRPLAYREAD_INT(cycles + 4096, 1);
1729 return;
1730
1731 default:
1732 CDR_LOG_I("psxDma3() Log: Unknown cddma %x\n", chcr);
1733 break;
1734 }
1735
1736 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1737 DMA_INTERRUPT(3);
1738}
1739
1740void cdrDmaInterrupt(void)
1741{
1742 if (HW_DMA3_CHCR & SWAP32(0x01000000))
1743 {
1744 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1745 DMA_INTERRUPT(3);
1746 }
1747}
1748
1749static void getCdInfo(void)
1750{
1751 cdra_getTN(cdr.ResultTN);
1752 cdra_getTD(0, cdr.SetSectorEnd);
1753}
1754
1755void cdrReset() {
1756 memset(&cdr, 0, sizeof(cdr));
1757 cdr.CurTrack = 1;
1758 cdr.FilterFile = 0;
1759 cdr.FilterChannel = 0;
1760 cdr.IrqMask = 0x1f;
1761 cdr.IrqStat = NoIntr;
1762
1763 // BIOS player - default values
1764 cdr.AttenuatorLeftToLeft = 0x80;
1765 cdr.AttenuatorLeftToRight = 0x00;
1766 cdr.AttenuatorRightToLeft = 0x00;
1767 cdr.AttenuatorRightToRight = 0x80;
1768
1769 softReset();
1770 getCdInfo();
1771}
1772
1773int cdrFreeze(void *f, int Mode) {
1774 u32 tmp;
1775 u8 tmpp[3];
1776
1777 cdr.freeze_ver = 0x63647203;
1778 gzfreeze(&cdr, sizeof(cdr));
1779
1780 if (Mode == 1) {
1781 cdr.ParamP = cdr.ParamC;
1782 tmp = cdr.FifoOffset;
1783 }
1784
1785 gzfreeze(&tmp, sizeof(tmp));
1786
1787 if (Mode == 0) {
1788 u8 ll = 0, lr = 0, rl = 0, rr = 0;
1789 getCdInfo();
1790
1791 cdr.FifoOffset = tmp < DATA_SIZE ? tmp : DATA_SIZE;
1792 cdr.FifoSize = (cdr.Mode & MODE_SIZE_2340) ? 2340 : 2048 + 12;
1793 if (cdr.SubqForwardSectors > SUBQ_FORWARD_SECTORS)
1794 cdr.SubqForwardSectors = SUBQ_FORWARD_SECTORS;
1795
1796 // read right sub data
1797 memcpy(tmpp, cdr.Prev, sizeof(tmpp));
1798 if (cdr.freeze_ver < 0x63647203) {
1799 tmpp[0] = btoi(tmpp[0]);
1800 tmpp[1] = btoi(tmpp[1]);
1801 tmpp[2] = btoi(tmpp[2]);
1802 }
1803 cdr.Prev[0] = 0xff;
1804 if (tmpp[0] != 0xff)
1805 ReadTrack(tmpp);
1806
1807 if (cdr.Play) {
1808 if (cdr.freeze_ver < 0x63647202)
1809 memcpy(cdr.SetSectorPlay, cdr.SetSector, 3);
1810
1811 Find_CurTrack(cdr.SetSectorPlay);
1812 }
1813 if (!cdr.Muted)
1814 ll = cdr.AttenuatorLeftToLeft, lr = cdr.AttenuatorLeftToRight,
1815 rl = cdr.AttenuatorRightToLeft, rr = cdr.AttenuatorRightToRight;
1816 SPU_setCDvol(ll, lr, rl, rr, psxRegs.cycle);
1817 }
1818
1819 return 0;
1820}
1821
1822void LidInterrupt(void) {
1823 getCdInfo();
1824 cdrLidSeekInterrupt();
1825}