1 /***************************************************************************
2 * Copyright (C) 2007 Ryan Schultz, PCSX-df Team, PCSX team *
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. *
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. *
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 ***************************************************************************/
21 * Handles all CD-ROM registers and functions.
27 #include "cdrom-async.h"
31 #include "psxevents.h"
32 #include "arm_features.h"
36 #define CDR_LOG SysPrintf
41 #define CDR_LOG_I SysPrintf
43 #define CDR_LOG_I(fmt, ...) \
44 log_unhandled("%u cdrom: " fmt, psxRegs.cycle, ##__VA_ARGS__)
47 #define CDR_LOG_IO SysPrintf
49 #define CDR_LOG_IO(...)
51 //#define CDR_LOG_CMD_IRQ
54 // unused members maintain savesate compatibility
55 unsigned char unused0;
56 unsigned char unused1;
57 unsigned char IrqMask;
58 unsigned char unused2;
60 unsigned char IrqStat;
64 unsigned char Transfer[DATA_SIZE];
68 unsigned char Relative[3];
69 unsigned char Absolute[3];
71 unsigned char TrackChanged;
72 unsigned char ReportDelay;
73 unsigned char PhysCdPropagations;
74 unsigned short sectorsRead;
75 unsigned int freeze_ver;
77 unsigned char Prev[4];
78 unsigned char Param[8];
79 unsigned char Result[16];
83 unsigned char ResultC;
84 unsigned char ResultP;
85 unsigned char ResultReady;
87 unsigned char SubqForwardSectors;
88 unsigned char SetlocPending;
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];
100 unsigned char FileChannelSelected;
101 unsigned char CurFile, CurChannel;
102 int FilterFile, FilterChannel;
103 unsigned char LocL[8];
114 u32 LastReadSeekCycles;
118 u8 DriveState; // enum drive_state
123 u8 AttenuatorLeftToLeft, AttenuatorLeftToRight;
124 u8 AttenuatorRightToRight, AttenuatorRightToLeft;
125 u8 AttenuatorLeftToLeftT, AttenuatorLeftToRightT;
126 u8 AttenuatorRightToRightT, AttenuatorRightToLeftT;
128 alignas(64) static s16 read_buf[CD_FRAMESIZE_RAW_ALIGNED / 2];
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];
142 /* CD-ROM magic numbers */
143 #define CdlSync 0 /* nocash documentation : "Uh, actually, returns error code 40h = Invalid Command...?" */
148 #define CdlBackward 5
156 #define CdlSetfilter 13
157 #define CdlSetmode 14
158 #define CdlGetparam 15
159 #define CdlGetlocL 16
160 #define CdlGetlocP 17
166 #define CdlSetclock 23
167 #define CdlGetclock 24
173 #define CdlReadToc 30
175 #ifdef CDR_LOG_CMD_IRQ
176 static 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
188 unsigned char Test04[] = { 0 };
189 unsigned char Test05[] = { 0 };
190 unsigned char Test20[] = { 0x98, 0x06, 0x10, 0xC3 };
191 unsigned char Test22[] = { 0x66, 0x6F, 0x72, 0x20, 0x45, 0x75, 0x72, 0x6F };
192 unsigned char Test23[] = { 0x43, 0x58, 0x44, 0x32, 0x39 ,0x34, 0x30, 0x51 };
198 #define Acknowledge 3
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
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
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
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)
235 #define LOCL_INVALID 0xff
236 #define SUBQ_FORWARD_SECTORS 2u
239 DRIVESTATE_STANDBY = 0, // different from paused
241 DRIVESTATE_RESCAN_CD,
242 DRIVESTATE_PREPARE_CD,
245 DRIVESTATE_PLAY_READ,
249 static struct CdrStat cdr_stat;
251 static unsigned int msf2sec(const u8 *msf) {
252 return ((msf[0] * 60 + msf[1]) * 75) + msf[2];
255 // cdrPlayReadInterrupt
256 #define CDRPLAYREAD_INT(eCycle, isFirst) { \
258 psxRegs.interrupt |= (1 << PSXINT_CDREAD); \
260 psxRegs.intCycle[PSXINT_CDREAD].sCycle = psxRegs.cycle; \
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_); \
267 #define StopReading() { \
269 psxRegs.interrupt &= ~(1 << PSXINT_CDREAD); \
272 #define StopCdda() { \
274 cdr.FastForward = 0; \
275 cdr.FastBackward = 0; \
278 #define SetPlaySeekRead(x, f) { \
279 x &= ~(STATUS_PLAY | STATUS_SEEK | STATUS_READ); \
283 #define SetResultSize_(size) { \
285 cdr.ResultC = size; \
286 cdr.ResultReady = 1; \
289 #define SetResultSize(size) { \
290 if (cdr.ResultP < cdr.ResultC) \
291 CDR_LOG_I("overwriting result, len=%u\n", cdr.ResultC); \
292 SetResultSize_(size); \
295 static void setIrq(u8 irq, int log_cmd)
297 u8 old = cdr.IrqStat & cdr.IrqMask ? 1 : 0;
298 u8 new_ = irq & cdr.IrqMask ? 1 : 0;
301 if ((old ^ new_) & new_)
302 psxHu32ref(0x1070) |= SWAP32((u32)0x4);
304 #ifdef CDR_LOG_CMD_IRQ
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]);
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)
319 void cdrLidSeekInterrupt(void)
321 CDR_LOG_I("%s cdr.DriveState=%d\n", __func__, cdr.DriveState);
323 switch (cdr.DriveState) {
325 case DRIVESTATE_STANDBY:
328 SetPlaySeekRead(cdr.StatP, 0);
330 if (cdra_getStatus(&cdr_stat) == -1)
333 if (cdr_stat.Status & STATUS_SHELLOPEN)
335 memset(cdr.Prev, 0xff, sizeof(cdr.Prev));
336 cdr.DriveState = DRIVESTATE_LID_OPEN;
337 set_event(PSXINT_CDRLID, 0x800);
341 case DRIVESTATE_LID_OPEN:
342 if (cdra_getStatus(&cdr_stat) != 0)
343 cdr_stat.Status &= ~STATUS_SHELLOPEN;
346 if (!(cdr.StatP & STATUS_SHELLOPEN)) {
347 int was_reading = cdr.Reading;
349 SetPlaySeekRead(cdr.StatP, 0);
350 cdr.StatP |= STATUS_SHELLOPEN;
351 memset(cdr.Prev, 0xff, sizeof(cdr.Prev));
353 // IIRC this sometimes doesn't happen on real hw
354 // (when lots of commands are sent?)
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;
364 setIrq(DiskError, 0x1006);
366 set_event(PSXINT_CDRLID, cdReadTime * 30);
369 else if (cdr.StatP & STATUS_ROTATING) {
370 cdr.StatP &= ~STATUS_ROTATING;
372 else if (!(cdr_stat.Status & STATUS_SHELLOPEN)) {
376 // cdr.StatP STATUS_SHELLOPEN is "sticky"
377 // and is only cleared by CdlNop
379 cdr.DriveState = DRIVESTATE_RESCAN_CD;
380 set_event(PSXINT_CDRLID, cdReadTime * 105);
385 set_event(PSXINT_CDRLID, cdReadTime * 3);
388 case DRIVESTATE_RESCAN_CD:
389 cdr.StatP |= STATUS_ROTATING;
390 cdr.DriveState = DRIVESTATE_PREPARE_CD;
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);
397 case DRIVESTATE_PREPARE_CD:
398 if (cdr.StatP & STATUS_SEEK) {
399 SetPlaySeekRead(cdr.StatP, 0);
400 cdr.DriveState = DRIVESTATE_STANDBY;
403 SetPlaySeekRead(cdr.StatP, STATUS_SEEK);
404 set_event(PSXINT_CDRLID, cdReadTime * 26);
410 static void Find_CurTrack(const u8 *time)
414 current = msf2sec(time);
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)
424 static void generate_subq(const u8 *time)
426 unsigned char start[3], next[3];
427 unsigned int this_s, start_s, next_s, pregap;
430 cdra_getTD(cdr.CurTrack, start);
431 if (cdr.CurTrack + 1 <= cdr.ResultTN[1]) {
433 cdra_getTD(cdr.CurTrack + 1, next);
436 // last track - cd size
438 memcpy(next, cdr.SetSectorEnd, 3);
441 this_s = msf2sec(time);
442 start_s = msf2sec(start);
443 next_s = msf2sec(next);
445 cdr.TrackChanged = FALSE;
447 if (next_s - this_s < pregap) {
448 cdr.TrackChanged = TRUE;
455 relative_s = this_s - start_s;
456 if (relative_s < 0) {
458 relative_s = -relative_s;
460 lba2msf(relative_s, &cdr.subq.Relative[0],
461 &cdr.subq.Relative[1], &cdr.subq.Relative[2]);
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]);
472 static int ReadTrack(const u8 *time)
476 CDR_LOG("ReadTrack *** %02d:%02d:%02d\n", tmp[0], tmp[1], tmp[2]);
478 if (memcmp(cdr.Prev, time, 3) == 0)
481 ret = cdra_readTrack(time);
483 memcpy(cdr.Prev, time, 3);
487 static void UpdateSubq(const u8 *time)
489 int ret = -1, s = MSF2SECT(time[0], time[1], time[2]);
496 if (cdr.CurTrack == 1)
497 ret = cdra_readSub(time, &subq);
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);
507 CDR_LOG_I("subq bad crc @%02d:%02d:%02d\n",
508 time[0], time[1], time[2]);
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]);
521 static void cdrPlayInterrupt_Autopause()
524 boolean abs_lev_chselect;
527 if ((cdr.Mode & MODE_AUTOPAUSE) && cdr.TrackChanged) {
528 CDR_LOG_I("autopause\n");
531 cdr.Result[0] = cdr.StatP;
532 setIrq(DataEnd, 0x1000); // 0x1000 just for logging purposes
535 SetPlaySeekRead(cdr.StatP, 0);
536 cdr.DriveState = DRIVESTATE_PAUSED;
538 else if ((cdr.Mode & MODE_REPORT) && !cdr.ReportDelay &&
539 ((cdr.subq.Absolute[2] & 0x0f) == 0 || cdr.FastForward || cdr.FastBackward))
542 cdr.Result[0] = cdr.StatP;
543 cdr.Result[1] = cdr.subq.Track;
544 cdr.Result[2] = cdr.subq.Index;
546 abs_lev_chselect = cdr.subq.Absolute[1] & 0x01;
548 /* 8 is a hack. For accuracy, it should be 588. */
549 for (i = 0; i < 8; i++)
551 abs_lev_max = MAX_VALUE(abs_lev_max, abs(read_buf[i * 2 + abs_lev_chselect]));
553 abs_lev_max = MIN_VALUE(abs_lev_max, 32767);
554 abs_lev_max |= abs_lev_chselect << 15;
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];
562 cdr.Result[3] = cdr.subq.Absolute[0];
563 cdr.Result[4] = cdr.subq.Absolute[1];
564 cdr.Result[5] = cdr.subq.Absolute[2];
566 cdr.Result[6] = abs_lev_max >> 0;
567 cdr.Result[7] = abs_lev_max >> 8;
569 setIrq(DataReady, 0x1001);
576 static boolean canDoTurbo(void)
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;
584 static int cdrSeekTime(unsigned char *target)
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);
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);
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;
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);
611 static u32 cdrAlignTimingHack(u32 cycles)
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.
625 * Note: always enforcing this breaks other games like Crash PAL version
626 * (inputs get dropped because bios handler doesn't see interrupts).
628 u32 vint_rel = rcnts[3].cycleStart + 63000 - psxRegs.cycle;
629 while ((s32)(vint_rel - cycles) < 0)
630 vint_rel += PSXCLK / 60;
634 static void cdrUpdateTransferBuf(const u8 *buf);
635 static void cdrReadInterrupt(void);
636 static void cdrPrepCdda(s16 *buf, int samples);
638 static void msfiAdd(u8 *msfi, u32 count)
652 static void msfiSub(u8 *msfi, u32 count)
656 if ((s8)msfi[2] < 0) {
659 if ((s8)msfi[1] < 0) {
666 static int msfiEq(const u8 *a, const u8 *b)
668 return a[0] == b[0] && a[1] == b[1] && a[2] == b[2];
671 void cdrPlayReadInterrupt(void)
673 // this works but causes instability for timing sensitive games
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++;
683 cdr.LastReadSeekCycles = psxRegs.cycle;
690 if (!cdr.Play) return;
692 CDR_LOG("CDDA - %02d:%02d:%02d m %02x\n",
693 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2], cdr.Mode);
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");
700 SetPlaySeekRead(cdr.StatP, 0);
701 cdr.TrackChanged = TRUE;
702 cdr.DriveState = DRIVESTATE_PAUSED;
705 cdra_readCDDA(cdr.SetSectorPlay, read_buf);
708 if (!cdr.IrqStat && (cdr.Mode & (MODE_AUTOPAUSE|MODE_REPORT)))
709 cdrPlayInterrupt_Autopause();
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);
716 msfiAdd(cdr.SetSectorPlay, 1);
717 cdra_prefetch(cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2]);
719 // update for CdlGetlocP/autopause
720 generate_subq(cdr.SetSectorPlay);
722 CDRPLAYREAD_INT(cdReadTime, 0);
724 // stop propagation since it breaks streaming
725 cdr.PhysCdPropagations = 0xff;
728 static void softReset(void)
730 cdra_getStatus(&cdr_stat);
731 if (cdr_stat.Status & STATUS_SHELLOPEN) {
732 cdr.DriveState = DRIVESTATE_LID_OPEN;
733 cdr.StatP = STATUS_SHELLOPEN;
735 else if (CdromId[0] == '\0') {
736 cdr.DriveState = DRIVESTATE_STOPPED;
740 cdr.DriveState = DRIVESTATE_STANDBY;
741 cdr.StatP = STATUS_ROTATING;
744 cdr.FifoOffset = DATA_SIZE; // fifo empty
745 cdr.LocL[0] = LOCL_INVALID;
746 cdr.Mode = MODE_SIZE_2340;
748 SPU_setCDvol(cdr.AttenuatorLeftToLeft, cdr.AttenuatorLeftToRight,
749 cdr.AttenuatorRightToLeft, cdr.AttenuatorRightToRight, psxRegs.cycle);
752 #define CMD_PART2 0x100
753 #define CMD_WHILE_NOT_READY 0x200
755 void cdrInterrupt(void) {
756 int start_rotating = 0;
758 u32 cycles, seekTime = 0;
759 u32 second_resp_time = 0;
764 u8 IrqStat = Acknowledge;
770 CDR_LOG_I("cmd %02x with irqstat %x\n",
771 cdr.CmdInProgress, cdr.IrqStat);
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);
781 cdr.Result[0] = cdr.Irq1Pending;
783 setIrq((cdr.Irq1Pending & STATUS_ERROR) ? DiskError : DataReady, 0x1003);
789 cdr.Result[0] = cdr.StatP;
791 Cmd = cdr.CmdInProgress;
792 cdr.CmdInProgress = 0;
801 switch (cdr.DriveState) {
802 case DRIVESTATE_PREPARE_CD:
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);
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;
819 switch (Cmd | not_ready) {
821 case CdlNop + CMD_WHILE_NOT_READY:
822 if (cdr.DriveState != DRIVESTATE_LID_OPEN)
823 cdr.StatP &= ~STATUS_SHELLOPEN;
827 case CdlSetloc + CMD_WHILE_NOT_READY: // apparently?
828 if (cdr.StatP & STATUS_SHELLOPEN)
829 // wrong? Driver2 vs Amerzone
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))
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)
838 error = ERROR_BAD_ARGNUM;
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)
850 cdr.RetryDetected = 0;
851 memcpy(cdr.SetSector, set_loc, 3);
852 cdr.SetSector[3] = 0;
853 cdr.SetlocPending = 1;
854 cdr.errorRetryhack = 0;
863 cdr.FastBackward = 0;
867 // - Pause player, hit Track 01/02/../xx (Setloc issued!!)
869 if (ParamC != 0 && cdr.Param[0] != 0) {
870 int track = btoi( cdr.Param[0] );
872 if (track <= cdr.ResultTN[1])
873 cdr.CurTrack = track;
875 CDR_LOG("PLAY track %d\n", cdr.CurTrack);
877 if (cdra_getTD(cdr.CurTrack, cdr.ResultTD) != -1) {
878 seekTime = cdrSeekTime(cdr.ResultTD);
879 memcpy(cdr.SetSectorPlay, cdr.ResultTD, 3);
882 else if (cdr.SetlocPending) {
883 seekTime = cdrSeekTime(cdr.SetSector);
884 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
887 CDR_LOG("PLAY Resume @ %d:%d:%d\n",
888 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2]);
890 cdr.SetlocPending = 0;
893 Rayman: detect track changes
896 Twisted Metal 2: skip PREGAP + starting accurate SubQ
897 - plays tracks without retry play
899 Wild 9: skip PREGAP + starting accurate SubQ
900 - plays tracks without retry play
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;
909 cdr.ReportDelay = 60;
912 SetPlaySeekRead(cdr.StatP, STATUS_SEEK | STATUS_ROTATING);
914 // BIOS player - set flag again
916 cdr.DriveState = DRIVESTATE_PLAY_READ;
917 cdr.PhysCdPropagations = 0;
919 CDRPLAYREAD_INT(cdReadTime + seekTime, 1);
924 // TODO: error 80 if stopped
927 // GameShark CD Player: Calls 2x + Play 2x
929 cdr.FastBackward = 0;
935 // GameShark CD Player: Calls 2x + Play 2x
936 cdr.FastBackward = 1;
941 if (cdr.DriveState != DRIVESTATE_STOPPED) {
942 error = ERROR_BAD_ARGNUM;
945 second_resp_time = cdReadTime * 125 / 2;
949 case CdlStandby + CMD_PART2:
955 // grab time for current track
956 cdra_getTD(cdr.CurTrack, cdr.ResultTD);
957 memcpy(cdr.SetSectorPlay, cdr.ResultTD, 3);
962 SetPlaySeekRead(cdr.StatP, 0);
963 cdr.StatP &= ~STATUS_ROTATING;
964 cdr.LocL[0] = LOCL_INVALID;
966 second_resp_time = 0x800;
967 if (cdr.DriveState != DRIVESTATE_STOPPED)
968 second_resp_time = cdReadTime * 30 / 2;
970 cdr.DriveState = DRIVESTATE_STOPPED;
973 case CdlStop + CMD_PART2:
978 if (cdr.AdpcmActive) {
981 SPU_playADPCMchannel(&cdr.Xa, psxRegs.cycle, 1); // flush adpcm
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));
992 Gundam Battle Assault 2
994 InuYasha - Feudal Fairy Tale
995 Dance Dance Revolution Konamix
999 if (!(cdr.StatP & (STATUS_PLAY | STATUS_READ)))
1001 second_resp_time = 7000;
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;
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;
1022 case CdlPause + CMD_PART2:
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
1033 second_resp_time = not_ready ? 70000 : 4100000;
1037 case CdlReset + CMD_PART2:
1038 case CdlReset + CMD_PART2 + CMD_WHILE_NOT_READY:
1044 SPU_setCDvol(0, 0, 0, 0, psxRegs.cycle);
1049 SPU_setCDvol(cdr.AttenuatorLeftToLeft, cdr.AttenuatorLeftToRight,
1050 cdr.AttenuatorRightToLeft, cdr.AttenuatorRightToRight, psxRegs.cycle);
1054 cdr.FilterFile = cdr.Param[0];
1055 cdr.FilterChannel = cdr.Param[1];
1056 cdr.FileChannelSelected = 0;
1060 case CdlSetmode + CMD_WHILE_NOT_READY:
1061 CDR_LOG("cdrWrite1() Log: Setmode %x\n", cdr.Param[0]);
1062 cdr.Mode = cdr.Param[0];
1066 case CdlGetparam + CMD_WHILE_NOT_READY:
1067 /* Gameblabla : According to mednafen, Result size should be 5 and done this way. */
1069 cdr.Result[1] = cdr.Mode;
1071 cdr.Result[3] = cdr.FilterFile;
1072 cdr.Result[4] = cdr.FilterChannel;
1076 if (cdr.LocL[0] == LOCL_INVALID) {
1081 memcpy(cdr.Result, cdr.LocL, 8);
1086 memcpy(&cdr.Result, &cdr.subq, 8);
1089 case CdlReadT: // SetSession?
1091 second_resp_time = cdReadTime * 290 / 4;
1095 case CdlReadT + CMD_PART2:
1100 if (cdra_getTN(cdr.ResultTN) != 0) {
1104 cdr.Result[1] = itob(cdr.ResultTN[0]);
1105 cdr.Result[2] = itob(cdr.ResultTN[1]);
1109 cdr.Track = btoi(cdr.Param[0]);
1110 if (cdra_getTD(cdr.Track, cdr.ResultTD) != 0) {
1111 error = ERROR_BAD_ARGVAL;
1115 cdr.Result[1] = itob(cdr.ResultTD[0]);
1116 cdr.Result[2] = itob(cdr.ResultTD[1]);
1118 //cdr.Result[3] = itob(cdr.ResultTD[2]);
1125 SetPlaySeekRead(cdr.StatP, STATUS_SEEK | STATUS_ROTATING);
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]);
1134 Crusaders of Might and Magic = 0.5x-4x
1135 - fix cutscene speech start
1137 Eggs of Steel = 2x-?
1141 - fix cutscene speech
1146 second_resp_time = cdReadTime + seekTime;
1150 case CdlSeekL + CMD_PART2:
1151 case CdlSeekP + CMD_PART2:
1152 SetPlaySeekRead(cdr.StatP, 0);
1153 cdr.Result[0] = cdr.StatP;
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;
1167 case CdlTest + CMD_WHILE_NOT_READY:
1168 switch (cdr.Param[0]) {
1169 case 0x20: // System Controller ROM Version
1171 memcpy(cdr.Result, Test20, 4);
1175 memcpy(cdr.Result, Test22, 4);
1177 case 0x23: case 0x24:
1179 memcpy(cdr.Result, Test23, 4);
1185 second_resp_time = 20480;
1188 case CdlID + CMD_PART2:
1190 cdr.Result[0] = cdr.StatP;
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;
1200 if (cdr_stat.Type == 2)
1201 cdr.Result[1] |= 0x10;
1202 if (CdromId[0] == '\0')
1203 cdr.Result[1] |= 0x80;
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]);
1209 /* This adds the string "PCSX" in Playstation bios boot screen */
1210 memcpy((char *)&cdr.Result[4], "PCSX", 4);
1215 case CdlInit + CMD_WHILE_NOT_READY:
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);
1227 case CdlGetQ + CMD_WHILE_NOT_READY:
1231 case CdlReadToc + CMD_WHILE_NOT_READY:
1232 cdr.LocL[0] = LOCL_INVALID;
1233 second_resp_time = cdReadTime * 180 / 4;
1237 case CdlReadToc + CMD_PART2:
1238 case CdlReadToc + CMD_PART2 + CMD_WHILE_NOT_READY:
1244 if (cdr.Reading && !cdr.SetlocPending)
1247 Find_CurTrack(cdr.SetlocPending ? cdr.SetSector : cdr.SetSectorPlay);
1249 if ((cdr.Mode & MODE_CDDA) && cdr.CurTrack > 1)
1250 // Read* acts as play for cdda tracks in cdda mode
1254 if (cdr.SetlocPending) {
1255 seekTime = cdrSeekTime(cdr.SetSector);
1256 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
1257 cdr.SetlocPending = 0;
1260 cdr.FileChannelSelected = 0;
1261 cdr.AdpcmActive = 0;
1263 // Fighting Force 2 - update subq time immediately
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]);
1274 cycles = (cdr.Mode & MODE_SPEED) ? cdReadTime : cdReadTime * 2;
1276 if (Config.hacks.cdr_read_timing)
1277 cycles = cdrAlignTimingHack(cycles);
1278 else if (canDoTurbo())
1279 cycles = cdReadTime / 2;
1280 CDRPLAYREAD_INT(cycles, 1);
1282 SetPlaySeekRead(cdr.StatP, STATUS_SEEK);
1288 error = ERROR_INVALIDCMD;
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]);
1300 if (cdr.DriveState == DRIVESTATE_STOPPED && start_rotating) {
1301 cdr.DriveState = DRIVESTATE_STANDBY;
1302 cdr.StatP |= STATUS_ROTATING;
1305 if (second_resp_time) {
1306 cdr.CmdInProgress = Cmd | 0x100;
1307 set_event(PSXINT_CDR, second_resp_time);
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);
1314 setIrq(IrqStat, Cmd);
1317 static void cdrPrepCdda(s16 *buf, int samples)
1319 #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
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]);
1328 static void cdrReadInterruptSetResult(unsigned char result)
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;
1342 cdr.Result[0] = result;
1343 setIrq((result & STATUS_ERROR) ? DiskError : DataReady, 0x1004);
1346 static void cdrUpdateTransferBuf(const u8 *buf)
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);
1358 static void cdrReadInterrupt(void)
1360 const struct { u8 file, chan, mode, coding; } *subhdr;
1361 const u8 *buf = NULL;
1362 int deliver_data = 1;
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);
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;
1381 read_ok = ReadTrack(cdr.SetSectorPlay);
1383 buf = cdra_getBuffer();
1388 CDR_LOG_I("cdrReadInterrupt() Log: err\n");
1389 cdrReadInterruptSetResult(cdr.StatP | STATUS_ERROR);
1390 cdr.DriveState = DRIVESTATE_PAUSED; // ?
1393 memcpy(cdr.LocL, buf, 8);
1395 if (!cdr.IrqStat && !cdr.Irq1Pending)
1396 cdrUpdateTransferBuf(buf);
1398 subhdr = (void *)(buf + 4);
1400 // try to process as adpcm
1401 if (!(cdr.Mode & MODE_STRSND))
1403 if (buf[3] != 2 || (subhdr->mode & 0x44) != 0x44) // or 0x64?
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))
1409 if (subhdr->chan & 0x80) { // ?
1410 if (subhdr->chan != 0xff)
1411 log_unhandled("adpcm %d:%d\n", subhdr->file, subhdr->chan);
1414 if (!cdr.FileChannelSelected) {
1415 cdr.CurFile = subhdr->file;
1416 cdr.CurChannel = subhdr->chan;
1417 cdr.FileChannelSelected = 1;
1419 else if (subhdr->file != cdr.CurFile || subhdr->chan != cdr.CurChannel)
1422 // accepted as adpcm
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);
1433 if ((cdr.Mode & MODE_SF) && (subhdr->mode & 0x44) == 0x44) // according to nocash
1435 if (buf[3] != 1 && buf[3] != 2) { // according to duckstation
1437 CDR_LOG_I("%x:%02x:%02x mode %02x ignored\n",
1438 buf[0], buf[1], buf[2], buf[3]);
1442 Croc 2: $40 - only FORM1 (*)
1443 Judge Dredd: $C8 - only FORM1 (*)
1444 Sim Theme Park - no adpcm at all (zero)
1448 cdrReadInterruptSetResult(cdr.StatP);
1450 msfiAdd(cdr.SetSectorPlay, 1);
1451 cdra_prefetch(cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2]);
1453 CDRPLAYREAD_INT((cdr.Mode & MODE_SPEED) ? (cdReadTime / 2) : cdReadTime, 0);
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
1467 unsigned char cdrRead0(void) {
1469 cdr.Ctrl |= cdr.AdpcmActive << 2;
1470 cdr.Ctrl |= cdr.ResultReady << 5;
1471 cdr.Ctrl |= ((signed int)(cdr.FifoOffset - cdr.FifoSize) >> 31) & 0x40;
1475 CDR_LOG_IO("cdr r0.sta: %02x\n", cdr.Ctrl);
1477 return psxHu8(0x1800) = cdr.Ctrl;
1480 void cdrWrite0(unsigned char rt) {
1481 CDR_LOG_IO("cdr w0.x.idx: %02x\n", rt);
1483 cdr.Ctrl = (rt & 3) | (cdr.Ctrl & ~3);
1486 unsigned char cdrRead1(void) {
1487 if ((cdr.ResultP & 0xf) < cdr.ResultC)
1488 psxHu8(0x1801) = cdr.Result[cdr.ResultP & 0xf];
1492 if (cdr.ResultP == cdr.ResultC)
1493 cdr.ResultReady = 0;
1495 CDR_LOG_IO("cdr r1.x.rsp: %02x #%u\n", psxHu8(0x1801), cdr.ResultP - 1);
1497 return psxHu8(0x1801);
1500 void 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);
1504 switch (cdr.Ctrl & 3) {
1508 cdr.AttenuatorRightToRightT = rt;
1514 #ifdef CDR_LOG_CMD_IRQ
1515 CDR_LOG_I("CD1 write: %x (%s)", rt, CmdName[rt]);
1518 SysPrintf(" Param[%d] = {", cdr.ParamC);
1519 for (i = 0; i < cdr.ParamC; i++)
1520 SysPrintf(" %x,", cdr.Param[i]);
1523 SysPrintf(" @%08x\n", psxRegs.pc);
1526 cdr.ResultReady = 0;
1529 if (!cdr.CmdInProgress) {
1530 cdr.CmdInProgress = rt;
1531 // should be something like 12k + controller delays
1532 set_event(PSXINT_CDR, 5000);
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;
1544 unsigned char cdrRead2(void) {
1545 unsigned char ret = cdr.Transfer[0x920];
1547 if (cdr.FifoOffset < cdr.FifoSize)
1548 ret = cdr.Transfer[cdr.FifoOffset++];
1550 CDR_LOG_I("read empty fifo (%d)\n", cdr.FifoSize);
1552 CDR_LOG_IO("cdr r2.x.dat: %02x\n", ret);
1556 void 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);
1560 switch (cdr.Ctrl & 3) {
1562 if (cdr.ParamC < 8) // FIXME: size and wrapping
1563 cdr.Param[cdr.ParamC++] = rt;
1567 setIrq(cdr.IrqStat, 0x1005);
1570 cdr.AttenuatorLeftToLeftT = rt;
1573 cdr.AttenuatorRightToLeftT = rt;
1578 unsigned char cdrRead3(void) {
1580 psxHu8(0x1803) = cdr.IrqStat | 0xE0;
1582 psxHu8(0x1803) = cdr.IrqMask | 0xE0;
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);
1589 void cdrWrite3(unsigned char rt) {
1590 const char *rnames[] = { "0.req", "1.ifl", "2.alr", "3.ava" }; (void)rnames;
1592 CDR_LOG_IO("cdr w3.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1594 switch (cdr.Ctrl & 3) {
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);
1607 // note: Croc, Shadow Tower (more) vs Discworld Noir (<993)
1608 if (!pending && (cdr.CmdInProgress || cdr.Irq1Pending))
1611 if (cdr.CmdInProgress) {
1612 c = 2048 - (psxRegs.cycle - nextCycle);
1613 c = MAX_VALUE(c, 512);
1615 set_event(PSXINT_CDR, c);
1624 cdr.AttenuatorLeftToRightT = rt;
1628 log_unhandled("Mute ADPCM?\n");
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)
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);
1646 if ((rt & 0x80) && cdr.FifoOffset < cdr.FifoSize) {
1647 CDR_LOG("cdrom: FifoOffset(2) %d/%d\n", cdr.FifoOffset, cdr.FifoSize);
1649 else if (rt & 0x80) {
1650 switch (cdr.Mode & (MODE_SIZE_2328|MODE_SIZE_2340)) {
1651 case MODE_SIZE_2328:
1653 cdr.FifoOffset = 12;
1654 cdr.FifoSize = 2048 + 12;
1657 case MODE_SIZE_2340:
1660 cdr.FifoSize = 2340;
1664 else if (!(rt & 0xc0))
1665 cdr.FifoOffset = DATA_SIZE; // fifo empty
1668 void psxDma3(u32 madr, u32 bcr, u32 chcr) {
1669 u32 cdsize, max_words, cycles;
1674 CDR_LOG_I("psxDma3() Log: *** DMA 3 *** %x addr = %x size = %x", chcr, madr, bcr);
1675 if (cdr.FifoOffset == 0) {
1677 SysPrintf(" %02x:%02x:%02x", ptr[0], ptr[1], ptr[2]);
1682 switch (chcr & 0x71000000) {
1685 ptr = getDmaRam(madr, &max_words);
1686 if (ptr == INVALID_PTR) {
1687 CDR_LOG_I("psxDma3() Log: *** DMA 3 *** NULL Pointer!\n");
1691 cdsize = (((bcr - 1) & 0xffff) + 1) * 4;
1694 GS CDX: Enhancement CD crash
1697 - Spams DMA3 and gets buffer overrun
1699 size = DATA_SIZE - cdr.FifoOffset;
1702 if (size > max_words * 4)
1703 size = max_words * 4;
1706 memcpy(ptr, cdr.Transfer + cdr.FifoOffset, size);
1707 cdr.FifoOffset += size;
1709 if (size < cdsize) {
1710 CDR_LOG_I("cdrom: dma3 %d/%d\n", size, cdsize);
1711 memset(ptr + size, cdr.Transfer[0x920], cdsize - size);
1713 psxCpu->Clear(madr, cdsize / 4);
1715 cycles = (cdsize / 4) * 24;
1716 set_event(PSXINT_CDRDMA, cycles);
1718 HW_DMA3_CHCR &= SWAPu32(~0x10000000);
1720 HW_DMA3_MADR = SWAPu32(madr + cdsize);
1721 HW_DMA3_BCR &= SWAPu32(0xffff0000);
1725 psxRegs.cycle += cycles - 20;
1727 if (canDoTurbo() && cdr.Reading && cdr.FifoOffset >= 2048)
1728 CDRPLAYREAD_INT(cycles + 4096, 1);
1732 CDR_LOG_I("psxDma3() Log: Unknown cddma %x\n", chcr);
1736 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1740 void cdrDmaInterrupt(void)
1742 if (HW_DMA3_CHCR & SWAP32(0x01000000))
1744 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1749 static void getCdInfo(void)
1751 cdra_getTN(cdr.ResultTN);
1752 cdra_getTD(0, cdr.SetSectorEnd);
1756 memset(&cdr, 0, sizeof(cdr));
1759 cdr.FilterChannel = 0;
1761 cdr.IrqStat = NoIntr;
1763 // BIOS player - default values
1764 cdr.AttenuatorLeftToLeft = 0x80;
1765 cdr.AttenuatorLeftToRight = 0x00;
1766 cdr.AttenuatorRightToLeft = 0x00;
1767 cdr.AttenuatorRightToRight = 0x80;
1773 int cdrFreeze(void *f, int Mode) {
1777 cdr.freeze_ver = 0x63647203;
1778 gzfreeze(&cdr, sizeof(cdr));
1781 cdr.ParamP = cdr.ParamC;
1782 tmp = cdr.FifoOffset;
1785 gzfreeze(&tmp, sizeof(tmp));
1788 u8 ll = 0, lr = 0, rl = 0, rr = 0;
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;
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]);
1804 if (tmpp[0] != 0xff)
1808 if (cdr.freeze_ver < 0x63647202)
1809 memcpy(cdr.SetSectorPlay, cdr.SetSector, 3);
1811 Find_CurTrack(cdr.SetSectorPlay);
1814 ll = cdr.AttenuatorLeftToLeft, lr = cdr.AttenuatorLeftToRight,
1815 rl = cdr.AttenuatorRightToLeft, rr = cdr.AttenuatorRightToRight;
1816 SPU_setCDvol(ll, lr, rl, rr, psxRegs.cycle);
1822 void LidInterrupt(void) {
1824 cdrLidSeekInterrupt();