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.
29 #include "psxevents.h"
30 #include "arm_features.h"
34 #define CDR_LOG SysPrintf
39 #define CDR_LOG_I SysPrintf
41 #define CDR_LOG_I(fmt, ...) \
42 log_unhandled("%u cdrom: " fmt, psxRegs.cycle, ##__VA_ARGS__)
45 #define CDR_LOG_IO SysPrintf
47 #define CDR_LOG_IO(...)
49 //#define CDR_LOG_CMD_IRQ
52 // unused members maintain savesate compatibility
53 unsigned char unused0;
54 unsigned char unused1;
55 unsigned char IrqMask;
56 unsigned char unused2;
58 unsigned char IrqStat;
62 unsigned char Transfer[DATA_SIZE];
66 unsigned char Relative[3];
67 unsigned char Absolute[3];
69 unsigned char TrackChanged;
70 unsigned char ReportDelay;
71 unsigned char unused3;
72 unsigned short sectorsRead;
73 unsigned int freeze_ver;
75 unsigned char Prev[4];
76 unsigned char Param[8];
77 unsigned char Result[16];
81 unsigned char ResultC;
82 unsigned char ResultP;
83 unsigned char ResultReady;
85 unsigned char SubqForwardSectors;
86 unsigned char SetlocPending;
89 unsigned char ResultTN[6];
90 unsigned char ResultTD[4];
91 unsigned char SetSectorPlay[4];
92 unsigned char SetSectorEnd[4];
93 unsigned char SetSector[4];
98 unsigned char FileChannelSelected;
99 unsigned char CurFile, CurChannel;
100 int FilterFile, FilterChannel;
101 unsigned char LocL[8];
112 u32 LastReadSeekCycles;
116 u8 DriveState; // enum drive_state
121 u8 AttenuatorLeftToLeft, AttenuatorLeftToRight;
122 u8 AttenuatorRightToRight, AttenuatorRightToLeft;
123 u8 AttenuatorLeftToLeftT, AttenuatorLeftToRightT;
124 u8 AttenuatorRightToRightT, AttenuatorRightToLeftT;
126 static s16 read_buf[CD_FRAMESIZE_RAW/2];
128 /* CD-ROM magic numbers */
129 #define CdlSync 0 /* nocash documentation : "Uh, actually, returns error code 40h = Invalid Command...?" */
134 #define CdlBackward 5
142 #define CdlSetfilter 13
143 #define CdlSetmode 14
144 #define CdlGetparam 15
145 #define CdlGetlocL 16
146 #define CdlGetlocP 17
152 #define CdlSetclock 23
153 #define CdlGetclock 24
159 #define CdlReadToc 30
161 #ifdef CDR_LOG_CMD_IRQ
162 static const char * const CmdName[0x100] = {
163 "CdlSync", "CdlNop", "CdlSetloc", "CdlPlay",
164 "CdlForward", "CdlBackward", "CdlReadN", "CdlStandby",
165 "CdlStop", "CdlPause", "CdlReset", "CdlMute",
166 "CdlDemute", "CdlSetfilter", "CdlSetmode", "CdlGetparam",
167 "CdlGetlocL", "CdlGetlocP", "CdlReadT", "CdlGetTN",
168 "CdlGetTD", "CdlSeekL", "CdlSeekP", "CdlSetclock",
169 "CdlGetclock", "CdlTest", "CdlID", "CdlReadS",
170 "CdlInit", NULL, "CDlReadToc", NULL
174 unsigned char Test04[] = { 0 };
175 unsigned char Test05[] = { 0 };
176 unsigned char Test20[] = { 0x98, 0x06, 0x10, 0xC3 };
177 unsigned char Test22[] = { 0x66, 0x6F, 0x72, 0x20, 0x45, 0x75, 0x72, 0x6F };
178 unsigned char Test23[] = { 0x43, 0x58, 0x44, 0x32, 0x39 ,0x34, 0x30, 0x51 };
184 #define Acknowledge 3
189 #define MODE_SPEED (1<<7) // 0x80
190 #define MODE_STRSND (1<<6) // 0x40 ADPCM on/off
191 #define MODE_SIZE_2340 (1<<5) // 0x20
192 #define MODE_SIZE_2328 (1<<4) // 0x10
193 #define MODE_SIZE_2048 (0<<4) // 0x00
194 #define MODE_SF (1<<3) // 0x08 channel on/off
195 #define MODE_REPORT (1<<2) // 0x04
196 #define MODE_AUTOPAUSE (1<<1) // 0x02
197 #define MODE_CDDA (1<<0) // 0x01
200 #define STATUS_PLAY (1<<7) // 0x80
201 #define STATUS_SEEK (1<<6) // 0x40
202 #define STATUS_READ (1<<5) // 0x20
203 #define STATUS_SHELLOPEN (1<<4) // 0x10
204 #define STATUS_UNKNOWN3 (1<<3) // 0x08
205 #define STATUS_SEEKERROR (1<<2) // 0x04
206 #define STATUS_ROTATING (1<<1) // 0x02
207 #define STATUS_ERROR (1<<0) // 0x01
210 #define ERROR_NOTREADY (1<<7) // 0x80
211 #define ERROR_INVALIDCMD (1<<6) // 0x40
212 #define ERROR_BAD_ARGNUM (1<<5) // 0x20
213 #define ERROR_BAD_ARGVAL (1<<4) // 0x10
214 #define ERROR_SHELLOPEN (1<<3) // 0x08
216 // 1x = 75 sectors per second
217 // PSXCLK = 1 sec in the ps
218 // so (PSXCLK / 75) = cdr read time (linuzappz)
219 #define cdReadTime (PSXCLK / 75)
221 #define LOCL_INVALID 0xff
222 #define SUBQ_FORWARD_SECTORS 2u
225 DRIVESTATE_STANDBY = 0, // different from paused
227 DRIVESTATE_RESCAN_CD,
228 DRIVESTATE_PREPARE_CD,
231 DRIVESTATE_PLAY_READ,
235 static struct CdrStat stat;
237 static unsigned int msf2sec(const u8 *msf) {
238 return ((msf[0] * 60 + msf[1]) * 75) + msf[2];
241 // for that weird psemu API..
242 static unsigned int fsm2sec(const u8 *msf) {
243 return ((msf[2] * 60 + msf[1]) * 75) + msf[0];
246 static void sec2msf(unsigned int s, u8 *msf) {
247 msf[0] = s / 75 / 60;
248 s = s - msf[0] * 75 * 60;
254 // cdrPlayReadInterrupt
255 #define CDRPLAYREAD_INT(eCycle, isFirst) { \
257 psxRegs.interrupt |= (1 << PSXINT_CDREAD); \
259 psxRegs.intCycle[PSXINT_CDREAD].sCycle = psxRegs.cycle; \
261 psxRegs.intCycle[PSXINT_CDREAD].sCycle += psxRegs.intCycle[PSXINT_CDREAD].cycle; \
262 psxRegs.intCycle[PSXINT_CDREAD].cycle = e_; \
263 set_event_raw_abs(PSXINT_CDREAD, psxRegs.intCycle[PSXINT_CDREAD].sCycle + e_); \
266 #define StopReading() { \
268 psxRegs.interrupt &= ~(1 << PSXINT_CDREAD); \
271 #define StopCdda() { \
272 if (cdr.Play && !Config.Cdda) CDR_stop(); \
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 (CDR_getStatus(&stat) == -1)
333 if (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 (CDR_getStatus(&stat) == -1)
343 stat.Status &= ~STATUS_SHELLOPEN;
346 if (!(cdr.StatP & STATUS_SHELLOPEN)) {
347 SetPlaySeekRead(cdr.StatP, 0);
348 cdr.StatP |= STATUS_SHELLOPEN;
350 // IIRC this sometimes doesn't happen on real hw
351 // (when lots of commands are sent?)
355 cdr.Result[0] = cdr.StatP | STATUS_SEEKERROR;
356 cdr.Result[1] = ERROR_SHELLOPEN;
357 setIrq(DiskError, 0x1006);
359 if (cdr.CmdInProgress) {
360 psxRegs.interrupt &= ~(1 << PSXINT_CDR);
361 cdr.CmdInProgress = 0;
363 cdr.Result[0] = cdr.StatP | STATUS_ERROR;
364 cdr.Result[1] = ERROR_NOTREADY;
365 setIrq(DiskError, 0x1007);
368 set_event(PSXINT_CDRLID, cdReadTime * 30);
371 else if (cdr.StatP & STATUS_ROTATING) {
372 cdr.StatP &= ~STATUS_ROTATING;
374 else if (!(stat.Status & STATUS_SHELLOPEN)) {
378 // cdr.StatP STATUS_SHELLOPEN is "sticky"
379 // and is only cleared by CdlNop
381 cdr.DriveState = DRIVESTATE_RESCAN_CD;
382 set_event(PSXINT_CDRLID, cdReadTime * 105);
387 set_event(PSXINT_CDRLID, cdReadTime * 3);
390 case DRIVESTATE_RESCAN_CD:
391 cdr.StatP |= STATUS_ROTATING;
392 cdr.DriveState = DRIVESTATE_PREPARE_CD;
394 // this is very long on real hardware, over 6 seconds
395 // make it a bit faster here...
396 set_event(PSXINT_CDRLID, cdReadTime * 150);
399 case DRIVESTATE_PREPARE_CD:
400 if (cdr.StatP & STATUS_SEEK) {
401 SetPlaySeekRead(cdr.StatP, 0);
402 cdr.DriveState = DRIVESTATE_STANDBY;
405 SetPlaySeekRead(cdr.StatP, STATUS_SEEK);
406 set_event(PSXINT_CDRLID, cdReadTime * 26);
412 static void Find_CurTrack(const u8 *time)
416 current = msf2sec(time);
418 for (cdr.CurTrack = 1; cdr.CurTrack < cdr.ResultTN[1]; cdr.CurTrack++) {
419 CDR_getTD(cdr.CurTrack + 1, cdr.ResultTD);
420 sect = fsm2sec(cdr.ResultTD);
421 if (sect - current >= 150)
426 static void generate_subq(const u8 *time)
428 unsigned char start[3], next[3];
429 unsigned int this_s, start_s, next_s, pregap;
432 CDR_getTD(cdr.CurTrack, start);
433 if (cdr.CurTrack + 1 <= cdr.ResultTN[1]) {
435 CDR_getTD(cdr.CurTrack + 1, next);
438 // last track - cd size
440 next[0] = cdr.SetSectorEnd[2];
441 next[1] = cdr.SetSectorEnd[1];
442 next[2] = cdr.SetSectorEnd[0];
445 this_s = msf2sec(time);
446 start_s = fsm2sec(start);
447 next_s = fsm2sec(next);
449 cdr.TrackChanged = FALSE;
451 if (next_s - this_s < pregap) {
452 cdr.TrackChanged = TRUE;
459 relative_s = this_s - start_s;
460 if (relative_s < 0) {
462 relative_s = -relative_s;
464 sec2msf(relative_s, cdr.subq.Relative);
466 cdr.subq.Track = itob(cdr.CurTrack);
467 cdr.subq.Relative[0] = itob(cdr.subq.Relative[0]);
468 cdr.subq.Relative[1] = itob(cdr.subq.Relative[1]);
469 cdr.subq.Relative[2] = itob(cdr.subq.Relative[2]);
470 cdr.subq.Absolute[0] = itob(time[0]);
471 cdr.subq.Absolute[1] = itob(time[1]);
472 cdr.subq.Absolute[2] = itob(time[2]);
475 static int ReadTrack(const u8 *time)
477 unsigned char tmp[3];
480 tmp[0] = itob(time[0]);
481 tmp[1] = itob(time[1]);
482 tmp[2] = itob(time[2]);
484 CDR_LOG("ReadTrack *** %02x:%02x:%02x\n", tmp[0], tmp[1], tmp[2]);
486 if (memcmp(cdr.Prev, tmp, 3) == 0)
489 read_ok = CDR_readTrack(tmp);
491 memcpy(cdr.Prev, tmp, 3);
495 static void UpdateSubq(const u8 *time)
497 const struct SubQ *subq;
498 int s = MSF2SECT(time[0], time[1], time[2]);
504 subq = (struct SubQ *)CDR_getBufferSub(s);
505 if (subq != NULL && cdr.CurTrack == 1) {
506 crc = calcCrc((u8 *)subq + 12, 10);
507 if (crc == (((u16)subq->CRC[0] << 8) | subq->CRC[1])) {
508 cdr.subq.Track = subq->TrackNumber;
509 cdr.subq.Index = subq->IndexNumber;
510 memcpy(cdr.subq.Relative, subq->TrackRelativeAddress, 3);
511 memcpy(cdr.subq.Absolute, subq->AbsoluteAddress, 3);
514 CDR_LOG_I("subq bad crc @%02d:%02d:%02d\n",
515 time[0], time[1], time[2]);
522 CDR_LOG(" -> %02x,%02x %02x:%02x:%02x %02x:%02x:%02x\n",
523 cdr.subq.Track, cdr.subq.Index,
524 cdr.subq.Relative[0], cdr.subq.Relative[1], cdr.subq.Relative[2],
525 cdr.subq.Absolute[0], cdr.subq.Absolute[1], cdr.subq.Absolute[2]);
528 static void cdrPlayInterrupt_Autopause()
531 boolean abs_lev_chselect;
534 if ((cdr.Mode & MODE_AUTOPAUSE) && cdr.TrackChanged) {
535 CDR_LOG_I("autopause\n");
538 cdr.Result[0] = cdr.StatP;
539 setIrq(DataEnd, 0x1000); // 0x1000 just for logging purposes
542 SetPlaySeekRead(cdr.StatP, 0);
543 cdr.DriveState = DRIVESTATE_PAUSED;
545 else if ((cdr.Mode & MODE_REPORT) && !cdr.ReportDelay &&
546 ((cdr.subq.Absolute[2] & 0x0f) == 0 || cdr.FastForward || cdr.FastBackward))
549 cdr.Result[0] = cdr.StatP;
550 cdr.Result[1] = cdr.subq.Track;
551 cdr.Result[2] = cdr.subq.Index;
553 abs_lev_chselect = cdr.subq.Absolute[1] & 0x01;
555 /* 8 is a hack. For accuracy, it should be 588. */
556 for (i = 0; i < 8; i++)
558 abs_lev_max = MAX_VALUE(abs_lev_max, abs(read_buf[i * 2 + abs_lev_chselect]));
560 abs_lev_max = MIN_VALUE(abs_lev_max, 32767);
561 abs_lev_max |= abs_lev_chselect << 15;
563 if (cdr.subq.Absolute[2] & 0x10) {
564 cdr.Result[3] = cdr.subq.Relative[0];
565 cdr.Result[4] = cdr.subq.Relative[1] | 0x80;
566 cdr.Result[5] = cdr.subq.Relative[2];
569 cdr.Result[3] = cdr.subq.Absolute[0];
570 cdr.Result[4] = cdr.subq.Absolute[1];
571 cdr.Result[5] = cdr.subq.Absolute[2];
573 cdr.Result[6] = abs_lev_max >> 0;
574 cdr.Result[7] = abs_lev_max >> 8;
576 setIrq(DataReady, 0x1001);
583 static int cdrSeekTime(unsigned char *target)
585 int diff = msf2sec(cdr.SetSectorPlay) - msf2sec(target);
586 int seekTime = abs(diff) * (cdReadTime / 2000);
587 int cyclesSinceRS = psxRegs.cycle - cdr.LastReadSeekCycles;
588 seekTime = MAX_VALUE(seekTime, 20000);
590 // need this stupidly long penalty or else Spyro2 intro desyncs
591 // note: if misapplied this breaks MGS cutscenes among other things
592 if (cdr.DriveState == DRIVESTATE_PAUSED && cyclesSinceRS > cdReadTime * 50)
593 seekTime += cdReadTime * 25;
594 // Transformers Beast Wars Transmetals does Setloc(x),SeekL,Setloc(x),ReadN
595 // and then wants some slack time
596 else if (cdr.DriveState == DRIVESTATE_PAUSED || cyclesSinceRS < cdReadTime *3/2)
597 seekTime += cdReadTime;
599 seekTime = MIN_VALUE(seekTime, PSXCLK * 2 / 3);
600 CDR_LOG("seek: %.2f %.2f (%.2f) st %d\n", (float)seekTime / PSXCLK,
601 (float)seekTime / cdReadTime, (float)cyclesSinceRS / cdReadTime,
606 static u32 cdrAlignTimingHack(u32 cycles)
609 * timing hack for T'ai Fu - Wrath of the Tiger:
610 * The game has a bug where it issues some cdc commands from a low priority
611 * vint handler, however there is a higher priority default bios handler
612 * that acks the vint irq and returns, so game's handler is not reached
613 * (see bios irq handler chains at e004 and the game's irq handling func
614 * at 80036810). For the game to work, vint has to arrive after the bios
615 * vint handler rejects some other irq (of which only cd and rcnt2 are
616 * active), but before the game's handler loop reads I_STAT. The time
617 * window for this is quite small (~1k cycles of so). Apparently this
618 * somehow happens naturally on the real hardware.
620 * Note: always enforcing this breaks other games like Crash PAL version
621 * (inputs get dropped because bios handler doesn't see interrupts).
624 if (psxRegs.cycle - rcnts[3].cycleStart > 250000)
626 vint_rel = rcnts[3].cycleStart + 63000 - psxRegs.cycle;
627 vint_rel += PSXCLK / 60;
628 while ((s32)(vint_rel - cycles) < 0)
629 vint_rel += PSXCLK / 60;
633 static void cdrUpdateTransferBuf(const u8 *buf);
634 static void cdrReadInterrupt(void);
635 static void cdrPrepCdda(s16 *buf, int samples);
637 static void msfiAdd(u8 *msfi, u32 count)
651 static void msfiSub(u8 *msfi, u32 count)
655 if ((s8)msfi[2] < 0) {
658 if ((s8)msfi[1] < 0) {
665 void cdrPlayReadInterrupt(void)
667 cdr.LastReadSeekCycles = psxRegs.cycle;
674 if (!cdr.Play) return;
676 CDR_LOG("CDDA - %02d:%02d:%02d m %02x\n",
677 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2], cdr.Mode);
679 cdr.DriveState = DRIVESTATE_PLAY_READ;
680 SetPlaySeekRead(cdr.StatP, STATUS_PLAY);
681 if (memcmp(cdr.SetSectorPlay, cdr.SetSectorEnd, 3) == 0) {
682 CDR_LOG_I("end stop\n");
684 SetPlaySeekRead(cdr.StatP, 0);
685 cdr.TrackChanged = TRUE;
686 cdr.DriveState = DRIVESTATE_PAUSED;
689 CDR_readCDDA(cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2], (u8 *)read_buf);
692 if (!cdr.IrqStat && (cdr.Mode & (MODE_AUTOPAUSE|MODE_REPORT)))
693 cdrPlayInterrupt_Autopause();
695 if (cdr.Play && !Config.Cdda) {
696 cdrPrepCdda(read_buf, CD_FRAMESIZE_RAW / 4);
697 SPU_playCDDAchannel(read_buf, CD_FRAMESIZE_RAW, psxRegs.cycle, 0);
700 msfiAdd(cdr.SetSectorPlay, 1);
702 // update for CdlGetlocP/autopause
703 generate_subq(cdr.SetSectorPlay);
705 CDRPLAYREAD_INT(cdReadTime, 0);
708 #define CMD_PART2 0x100
709 #define CMD_WHILE_NOT_READY 0x200
711 void cdrInterrupt(void) {
712 int start_rotating = 0;
714 u32 cycles, seekTime = 0;
715 u32 second_resp_time = 0;
721 u8 IrqStat = Acknowledge;
726 CDR_LOG_I("cmd %02x with irqstat %x\n",
727 cdr.CmdInProgress, cdr.IrqStat);
730 if (cdr.Irq1Pending) {
731 // hand out the "newest" sector, according to nocash
732 cdrUpdateTransferBuf(CDR_getBuffer());
733 CDR_LOG_I("%x:%02x:%02x loaded on ack, cmd=%02x res=%02x\n",
734 cdr.Transfer[0], cdr.Transfer[1], cdr.Transfer[2],
735 cdr.CmdInProgress, cdr.Irq1Pending);
737 cdr.Result[0] = cdr.Irq1Pending;
739 setIrq((cdr.Irq1Pending & STATUS_ERROR) ? DiskError : DataReady, 0x1003);
745 cdr.Result[0] = cdr.StatP;
747 Cmd = cdr.CmdInProgress;
748 cdr.CmdInProgress = 0;
757 switch (cdr.DriveState) {
758 case DRIVESTATE_PREPARE_CD:
760 // Syphon filter 2 expects commands to work shortly after it sees
761 // STATUS_ROTATING, so give up trying to emulate the startup seq
762 cdr.DriveState = DRIVESTATE_STANDBY;
763 cdr.StatP &= ~STATUS_SEEK;
764 psxRegs.interrupt &= ~(1 << PSXINT_CDRLID);
768 case DRIVESTATE_LID_OPEN:
769 case DRIVESTATE_RESCAN_CD:
770 // no disk or busy with the initial scan, allowed cmds are limited
771 not_ready = CMD_WHILE_NOT_READY;
775 switch (Cmd | not_ready) {
777 case CdlNop + CMD_WHILE_NOT_READY:
778 if (cdr.DriveState != DRIVESTATE_LID_OPEN)
779 cdr.StatP &= ~STATUS_SHELLOPEN;
783 // case CdlSetloc + CMD_WHILE_NOT_READY: // or is it?
784 CDR_LOG("CDROM setloc command (%02X, %02X, %02X)\n", cdr.Param[0], cdr.Param[1], cdr.Param[2]);
786 // MM must be BCD, SS must be BCD and <0x60, FF must be BCD and <0x75
787 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))
789 CDR_LOG_I("Invalid/out of range seek to %02X:%02X:%02X\n", cdr.Param[0], cdr.Param[1], cdr.Param[2]);
790 if (++cdr.errorRetryhack > 100)
792 error = ERROR_BAD_ARGNUM;
797 for (i = 0; i < 3; i++)
798 set_loc[i] = btoi(cdr.Param[i]);
799 memcpy(cdr.SetSector, set_loc, 3);
800 cdr.SetSector[3] = 0;
801 cdr.SetlocPending = 1;
802 cdr.errorRetryhack = 0;
811 cdr.FastBackward = 0;
815 // - Pause player, hit Track 01/02/../xx (Setloc issued!!)
817 if (ParamC != 0 && cdr.Param[0] != 0) {
818 int track = btoi( cdr.Param[0] );
820 if (track <= cdr.ResultTN[1])
821 cdr.CurTrack = track;
823 CDR_LOG("PLAY track %d\n", cdr.CurTrack);
825 if (CDR_getTD((u8)cdr.CurTrack, cdr.ResultTD) != -1) {
826 for (i = 0; i < 3; i++)
827 set_loc[i] = cdr.ResultTD[2 - i];
828 seekTime = cdrSeekTime(set_loc);
829 memcpy(cdr.SetSectorPlay, set_loc, 3);
832 else if (cdr.SetlocPending) {
833 seekTime = cdrSeekTime(cdr.SetSector);
834 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
837 CDR_LOG("PLAY Resume @ %d:%d:%d\n",
838 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2]);
840 cdr.SetlocPending = 0;
843 Rayman: detect track changes
846 Twisted Metal 2: skip PREGAP + starting accurate SubQ
847 - plays tracks without retry play
849 Wild 9: skip PREGAP + starting accurate SubQ
850 - plays tracks without retry play
852 Find_CurTrack(cdr.SetSectorPlay);
853 generate_subq(cdr.SetSectorPlay);
854 cdr.LocL[0] = LOCL_INVALID;
855 cdr.SubqForwardSectors = 1;
856 cdr.TrackChanged = FALSE;
857 cdr.FileChannelSelected = 0;
859 cdr.ReportDelay = 60;
863 CDR_play(cdr.SetSectorPlay);
865 SetPlaySeekRead(cdr.StatP, STATUS_SEEK | STATUS_ROTATING);
867 // BIOS player - set flag again
869 cdr.DriveState = DRIVESTATE_PLAY_READ;
871 CDRPLAYREAD_INT(cdReadTime + seekTime, 1);
876 // TODO: error 80 if stopped
879 // GameShark CD Player: Calls 2x + Play 2x
881 cdr.FastBackward = 0;
887 // GameShark CD Player: Calls 2x + Play 2x
888 cdr.FastBackward = 1;
893 if (cdr.DriveState != DRIVESTATE_STOPPED) {
894 error = ERROR_BAD_ARGNUM;
897 cdr.DriveState = DRIVESTATE_STANDBY;
898 second_resp_time = cdReadTime * 125 / 2;
902 case CdlStandby + CMD_PART2:
908 // grab time for current track
909 CDR_getTD((u8)(cdr.CurTrack), cdr.ResultTD);
911 cdr.SetSectorPlay[0] = cdr.ResultTD[2];
912 cdr.SetSectorPlay[1] = cdr.ResultTD[1];
913 cdr.SetSectorPlay[2] = cdr.ResultTD[0];
918 SetPlaySeekRead(cdr.StatP, 0);
919 cdr.StatP &= ~STATUS_ROTATING;
920 cdr.LocL[0] = LOCL_INVALID;
922 second_resp_time = 0x800;
923 if (cdr.DriveState != DRIVESTATE_STOPPED)
924 second_resp_time = cdReadTime * 30 / 2;
926 cdr.DriveState = DRIVESTATE_STOPPED;
929 case CdlStop + CMD_PART2:
934 if (cdr.AdpcmActive) {
937 SPU_playADPCMchannel(&cdr.Xa, psxRegs.cycle, 1); // flush adpcm
942 // how the drive maintains the position while paused is quite
943 // complicated, this is the minimum to make "Bedlam" happy
944 msfiSub(cdr.SetSectorPlay, MIN_VALUE(cdr.sectorsRead, 4));
948 Gundam Battle Assault 2: much slower (*)
949 - Fixes boot, gameplay
951 Hokuto no Ken 2: slower
952 - Fixes intro + subtitles
954 InuYasha - Feudal Fairy Tale: slower
957 /* Gameblabla - Tightening the timings (as taken from Duckstation).
958 * The timings from Duckstation are based upon hardware tests.
959 * Mednafen's timing don't work for Gundam Battle Assault 2 in PAL/50hz mode,
960 * seems to be timing sensitive as it can depend on the CPU's clock speed.
962 if (!(cdr.StatP & (STATUS_PLAY | STATUS_READ)))
964 second_resp_time = 7000;
968 second_resp_time = (((cdr.Mode & MODE_SPEED) ? 1 : 2) * 1097107);
970 SetPlaySeekRead(cdr.StatP, 0);
971 cdr.DriveState = DRIVESTATE_PAUSED;
974 case CdlPause + CMD_PART2:
979 case CdlReset + CMD_WHILE_NOT_READY:
982 SetPlaySeekRead(cdr.StatP, 0);
983 cdr.LocL[0] = LOCL_INVALID;
984 cdr.Mode = MODE_SIZE_2340; /* This fixes This is Football 2, Pooh's Party lockups */
985 cdr.DriveState = DRIVESTATE_PAUSED;
987 SPU_setCDvol(cdr.AttenuatorLeftToLeft, cdr.AttenuatorLeftToRight,
988 cdr.AttenuatorRightToLeft, cdr.AttenuatorRightToRight, psxRegs.cycle);
989 second_resp_time = not_ready ? 70000 : 4100000;
993 case CdlReset + CMD_PART2:
994 case CdlReset + CMD_PART2 + CMD_WHILE_NOT_READY:
1000 SPU_setCDvol(0, 0, 0, 0, psxRegs.cycle);
1005 SPU_setCDvol(cdr.AttenuatorLeftToLeft, cdr.AttenuatorLeftToRight,
1006 cdr.AttenuatorRightToLeft, cdr.AttenuatorRightToRight, psxRegs.cycle);
1010 cdr.FilterFile = cdr.Param[0];
1011 cdr.FilterChannel = cdr.Param[1];
1012 cdr.FileChannelSelected = 0;
1016 case CdlSetmode + CMD_WHILE_NOT_READY:
1017 CDR_LOG("cdrWrite1() Log: Setmode %x\n", cdr.Param[0]);
1018 cdr.Mode = cdr.Param[0];
1022 case CdlGetparam + CMD_WHILE_NOT_READY:
1023 /* Gameblabla : According to mednafen, Result size should be 5 and done this way. */
1025 cdr.Result[1] = cdr.Mode;
1027 cdr.Result[3] = cdr.FilterFile;
1028 cdr.Result[4] = cdr.FilterChannel;
1032 if (cdr.LocL[0] == LOCL_INVALID) {
1037 memcpy(cdr.Result, cdr.LocL, 8);
1042 memcpy(&cdr.Result, &cdr.subq, 8);
1045 case CdlReadT: // SetSession?
1047 second_resp_time = cdReadTime * 290 / 4;
1051 case CdlReadT + CMD_PART2:
1056 if (CDR_getTN(cdr.ResultTN) == -1) {
1060 cdr.Result[1] = itob(cdr.ResultTN[0]);
1061 cdr.Result[2] = itob(cdr.ResultTN[1]);
1065 cdr.Track = btoi(cdr.Param[0]);
1066 if (CDR_getTD(cdr.Track, cdr.ResultTD) == -1) {
1067 error = ERROR_BAD_ARGVAL;
1071 cdr.Result[1] = itob(cdr.ResultTD[2]);
1072 cdr.Result[2] = itob(cdr.ResultTD[1]);
1074 //cdr.Result[3] = itob(cdr.ResultTD[0]);
1081 SetPlaySeekRead(cdr.StatP, STATUS_SEEK | STATUS_ROTATING);
1083 seekTime = cdrSeekTime(cdr.SetSector);
1084 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
1085 cdr.DriveState = DRIVESTATE_SEEK;
1087 Crusaders of Might and Magic = 0.5x-4x
1088 - fix cutscene speech start
1090 Eggs of Steel = 2x-?
1094 - fix cutscene speech
1099 second_resp_time = cdReadTime + seekTime;
1103 case CdlSeekL + CMD_PART2:
1104 case CdlSeekP + CMD_PART2:
1105 SetPlaySeekRead(cdr.StatP, 0);
1106 cdr.Result[0] = cdr.StatP;
1109 Find_CurTrack(cdr.SetSectorPlay);
1110 read_ok = ReadTrack(cdr.SetSectorPlay);
1111 if (read_ok && (buf = CDR_getBuffer()))
1112 memcpy(cdr.LocL, buf, 8);
1113 UpdateSubq(cdr.SetSectorPlay);
1114 cdr.DriveState = DRIVESTATE_STANDBY;
1115 cdr.TrackChanged = FALSE;
1116 cdr.LastReadSeekCycles = psxRegs.cycle;
1120 case CdlTest + CMD_WHILE_NOT_READY:
1121 switch (cdr.Param[0]) {
1122 case 0x20: // System Controller ROM Version
1124 memcpy(cdr.Result, Test20, 4);
1128 memcpy(cdr.Result, Test22, 4);
1130 case 0x23: case 0x24:
1132 memcpy(cdr.Result, Test23, 4);
1138 second_resp_time = 20480;
1141 case CdlID + CMD_PART2:
1143 cdr.Result[0] = cdr.StatP;
1148 // 0x10 - audio | 0x40 - disk missing | 0x80 - unlicensed
1149 if (CDR_getStatus(&stat) == -1 || stat.Type == 0 || stat.Type == 0xff) {
1150 cdr.Result[1] = 0xc0;
1154 cdr.Result[1] |= 0x10;
1155 if (CdromId[0] == '\0')
1156 cdr.Result[1] |= 0x80;
1158 cdr.Result[0] |= (cdr.Result[1] >> 4) & 0x08;
1159 CDR_LOG_I("CdlID: %02x %02x %02x %02x\n", cdr.Result[0],
1160 cdr.Result[1], cdr.Result[2], cdr.Result[3]);
1162 /* This adds the string "PCSX" in Playstation bios boot screen */
1163 memcpy((char *)&cdr.Result[4], "PCSX", 4);
1168 case CdlInit + CMD_WHILE_NOT_READY:
1171 SetPlaySeekRead(cdr.StatP, 0);
1172 // yes, it really sets STATUS_SHELLOPEN
1173 cdr.StatP |= STATUS_SHELLOPEN;
1174 cdr.DriveState = DRIVESTATE_RESCAN_CD;
1175 set_event(PSXINT_CDRLID, 20480);
1180 case CdlGetQ + CMD_WHILE_NOT_READY:
1184 case CdlReadToc + CMD_WHILE_NOT_READY:
1185 cdr.LocL[0] = LOCL_INVALID;
1186 second_resp_time = cdReadTime * 180 / 4;
1190 case CdlReadToc + CMD_PART2:
1191 case CdlReadToc + CMD_PART2 + CMD_WHILE_NOT_READY:
1197 if (cdr.Reading && !cdr.SetlocPending)
1200 Find_CurTrack(cdr.SetlocPending ? cdr.SetSector : cdr.SetSectorPlay);
1202 if ((cdr.Mode & MODE_CDDA) && cdr.CurTrack > 1)
1203 // Read* acts as play for cdda tracks in cdda mode
1207 if (cdr.SetlocPending) {
1208 seekTime = cdrSeekTime(cdr.SetSector);
1209 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
1210 cdr.SetlocPending = 0;
1213 cdr.FileChannelSelected = 0;
1214 cdr.AdpcmActive = 0;
1216 // Fighting Force 2 - update subq time immediately
1218 UpdateSubq(cdr.SetSectorPlay);
1219 cdr.LocL[0] = LOCL_INVALID;
1220 cdr.SubqForwardSectors = 1;
1221 cdr.sectorsRead = 0;
1222 cdr.DriveState = DRIVESTATE_SEEK;
1224 cycles = (cdr.Mode & MODE_SPEED) ? cdReadTime : cdReadTime * 2;
1226 if (Config.hacks.cdr_read_timing)
1227 cycles = cdrAlignTimingHack(cycles);
1228 CDRPLAYREAD_INT(cycles, 1);
1230 SetPlaySeekRead(cdr.StatP, STATUS_SEEK);
1236 error = ERROR_INVALIDCMD;
1241 cdr.Result[0] = cdr.StatP | STATUS_ERROR;
1242 cdr.Result[1] = not_ready ? ERROR_NOTREADY : error;
1243 IrqStat = DiskError;
1244 CDR_LOG_I("cmd %02x error %02x\n", Cmd, cdr.Result[1]);
1248 if (cdr.DriveState == DRIVESTATE_STOPPED && start_rotating) {
1249 cdr.DriveState = DRIVESTATE_STANDBY;
1250 cdr.StatP |= STATUS_ROTATING;
1253 if (second_resp_time) {
1254 cdr.CmdInProgress = Cmd | 0x100;
1255 set_event(PSXINT_CDR, second_resp_time);
1257 else if (cdr.Cmd && cdr.Cmd != (Cmd & 0xff)) {
1258 cdr.CmdInProgress = cdr.Cmd;
1259 CDR_LOG_I("cmd %02x came before %02x finished\n", cdr.Cmd, Cmd);
1262 setIrq(IrqStat, Cmd);
1266 #define ssat32_to_16(v) \
1267 asm("ssat %0,#16,%1" : "=r" (v) : "r" (v))
1269 #define ssat32_to_16(v) do { \
1270 if (v < -32768) v = -32768; \
1271 else if (v > 32767) v = 32767; \
1275 static void cdrPrepCdda(s16 *buf, int samples)
1277 #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
1279 for (i = 0; i < samples; i++) {
1280 buf[i * 2 + 0] = SWAP16(buf[i * 2 + 0]);
1281 buf[i * 2 + 1] = SWAP16(buf[i * 2 + 1]);
1286 static void cdrReadInterruptSetResult(unsigned char result)
1289 CDR_LOG_I("%d:%02d:%02d irq miss, cmd=%02x irqstat=%02x\n",
1290 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2],
1291 cdr.CmdInProgress, cdr.IrqStat);
1292 cdr.Irq1Pending = result;
1296 cdr.Result[0] = result;
1297 setIrq((result & STATUS_ERROR) ? DiskError : DataReady, 0x1004);
1300 static void cdrUpdateTransferBuf(const u8 *buf)
1304 memcpy(cdr.Transfer, buf, DATA_SIZE);
1305 CheckPPFCache(cdr.Transfer, cdr.Prev[0], cdr.Prev[1], cdr.Prev[2]);
1306 CDR_LOG("cdr.Transfer %02x:%02x:%02x\n",
1307 cdr.Transfer[0], cdr.Transfer[1], cdr.Transfer[2]);
1308 if (cdr.FifoOffset < 2048 + 12)
1309 CDR_LOG("FifoOffset(1) %d/%d\n", cdr.FifoOffset, cdr.FifoSize);
1312 static void cdrReadInterrupt(void)
1314 const struct { u8 file, chan, mode, coding; } *subhdr;
1315 const u8 *buf = NULL;
1316 int deliver_data = 1;
1321 memcpy(subqPos, cdr.SetSectorPlay, sizeof(subqPos));
1322 msfiAdd(subqPos, cdr.SubqForwardSectors);
1323 UpdateSubq(subqPos);
1324 if (cdr.SubqForwardSectors < SUBQ_FORWARD_SECTORS) {
1325 cdr.SubqForwardSectors++;
1326 CDRPLAYREAD_INT((cdr.Mode & MODE_SPEED) ? (cdReadTime / 2) : cdReadTime, 0);
1330 // note: CdlGetlocL should work as soon as STATUS_READ is indicated
1331 SetPlaySeekRead(cdr.StatP, STATUS_READ | STATUS_ROTATING);
1332 cdr.DriveState = DRIVESTATE_PLAY_READ;
1335 read_ok = ReadTrack(cdr.SetSectorPlay);
1337 buf = CDR_getBuffer();
1342 CDR_LOG_I("cdrReadInterrupt() Log: err\n");
1343 cdrReadInterruptSetResult(cdr.StatP | STATUS_ERROR);
1344 cdr.DriveState = DRIVESTATE_PAUSED; // ?
1347 memcpy(cdr.LocL, buf, 8);
1349 if (!cdr.IrqStat && !cdr.Irq1Pending)
1350 cdrUpdateTransferBuf(buf);
1352 subhdr = (void *)(buf + 4);
1354 // try to process as adpcm
1355 if (!(cdr.Mode & MODE_STRSND))
1357 if (buf[3] != 2 || (subhdr->mode & 0x44) != 0x44) // or 0x64?
1359 CDR_LOG("f=%d m=%d %d,%3d | %d,%2d | %d,%2d\n", !!(cdr.Mode & MODE_SF), cdr.Muted,
1360 subhdr->file, subhdr->chan, cdr.CurFile, cdr.CurChannel, cdr.FilterFile, cdr.FilterChannel);
1361 if ((cdr.Mode & MODE_SF) && (subhdr->file != cdr.FilterFile || subhdr->chan != cdr.FilterChannel))
1363 if (subhdr->chan & 0xe0) { // ?
1364 if (subhdr->chan != 0xff)
1365 log_unhandled("adpcm %d:%d\n", subhdr->file, subhdr->chan);
1368 if (!cdr.FileChannelSelected) {
1369 cdr.CurFile = subhdr->file;
1370 cdr.CurChannel = subhdr->chan;
1371 cdr.FileChannelSelected = 1;
1373 else if (subhdr->file != cdr.CurFile || subhdr->chan != cdr.CurChannel)
1376 // accepted as adpcm
1381 is_start = !cdr.AdpcmActive;
1382 cdr.AdpcmActive = !xa_decode_sector(&cdr.Xa, buf + 4, is_start);
1383 if (cdr.AdpcmActive)
1384 SPU_playADPCMchannel(&cdr.Xa, psxRegs.cycle, is_start);
1387 if ((cdr.Mode & MODE_SF) && (subhdr->mode & 0x44) == 0x44) // according to nocash
1391 Croc 2: $40 - only FORM1 (*)
1392 Judge Dredd: $C8 - only FORM1 (*)
1393 Sim Theme Park - no adpcm at all (zero)
1397 cdrReadInterruptSetResult(cdr.StatP);
1399 msfiAdd(cdr.SetSectorPlay, 1);
1401 CDRPLAYREAD_INT((cdr.Mode & MODE_SPEED) ? (cdReadTime / 2) : cdReadTime, 0);
1407 bit 2 - adpcm active
1408 bit 5 - 1 result ready
1410 bit 7 - 1 command being processed
1413 unsigned char cdrRead0(void) {
1415 cdr.Ctrl |= cdr.AdpcmActive << 2;
1416 cdr.Ctrl |= cdr.ResultReady << 5;
1418 cdr.Ctrl |= 0x40; // data fifo not empty
1420 // What means the 0x10 and the 0x08 bits? I only saw it used by the bios
1423 CDR_LOG_IO("cdr r0.sta: %02x\n", cdr.Ctrl);
1425 return psxHu8(0x1800) = cdr.Ctrl;
1428 void cdrWrite0(unsigned char rt) {
1429 CDR_LOG_IO("cdr w0.idx: %02x\n", rt);
1431 cdr.Ctrl = (rt & 3) | (cdr.Ctrl & ~3);
1434 unsigned char cdrRead1(void) {
1435 if ((cdr.ResultP & 0xf) < cdr.ResultC)
1436 psxHu8(0x1801) = cdr.Result[cdr.ResultP & 0xf];
1440 if (cdr.ResultP == cdr.ResultC)
1441 cdr.ResultReady = 0;
1443 CDR_LOG_IO("cdr r1.rsp: %02x #%u\n", psxHu8(0x1801), cdr.ResultP - 1);
1445 return psxHu8(0x1801);
1448 void cdrWrite1(unsigned char rt) {
1449 const char *rnames[] = { "cmd", "smd", "smc", "arr" }; (void)rnames;
1450 CDR_LOG_IO("cdr w1.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1452 switch (cdr.Ctrl & 3) {
1456 cdr.AttenuatorRightToRightT = rt;
1462 #ifdef CDR_LOG_CMD_IRQ
1463 CDR_LOG_I("CD1 write: %x (%s)", rt, CmdName[rt]);
1466 SysPrintf(" Param[%d] = {", cdr.ParamC);
1467 for (i = 0; i < cdr.ParamC; i++)
1468 SysPrintf(" %x,", cdr.Param[i]);
1475 cdr.ResultReady = 0;
1478 if (!cdr.CmdInProgress) {
1479 cdr.CmdInProgress = rt;
1480 // should be something like 12k + controller delays
1481 set_event(PSXINT_CDR, 5000);
1484 CDR_LOG_I("cmd while busy: %02x, prev %02x, busy %02x\n",
1485 rt, cdr.Cmd, cdr.CmdInProgress);
1486 if (cdr.CmdInProgress < 0x100) // no pending 2nd response
1487 cdr.CmdInProgress = rt;
1493 unsigned char cdrRead2(void) {
1494 unsigned char ret = cdr.Transfer[0x920];
1496 if (cdr.FifoOffset < cdr.FifoSize)
1497 ret = cdr.Transfer[cdr.FifoOffset++];
1499 CDR_LOG_I("read empty fifo (%d)\n", cdr.FifoSize);
1501 CDR_LOG_IO("cdr r2.dat: %02x\n", ret);
1505 void cdrWrite2(unsigned char rt) {
1506 const char *rnames[] = { "prm", "ien", "all", "arl" }; (void)rnames;
1507 CDR_LOG_IO("cdr w2.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1509 switch (cdr.Ctrl & 3) {
1511 if (cdr.ParamC < 8) // FIXME: size and wrapping
1512 cdr.Param[cdr.ParamC++] = rt;
1516 setIrq(cdr.IrqStat, 0x1005);
1519 cdr.AttenuatorLeftToLeftT = rt;
1522 cdr.AttenuatorRightToLeftT = rt;
1527 unsigned char cdrRead3(void) {
1529 psxHu8(0x1803) = cdr.IrqStat | 0xE0;
1531 psxHu8(0x1803) = cdr.IrqMask | 0xE0;
1533 CDR_LOG_IO("cdr r3.%s: %02x\n", (cdr.Ctrl & 1) ? "ifl" : "ien", psxHu8(0x1803));
1534 return psxHu8(0x1803);
1537 void cdrWrite3(unsigned char rt) {
1538 const char *rnames[] = { "req", "ifl", "alr", "ava" }; (void)rnames;
1540 CDR_LOG_IO("cdr w3.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1542 switch (cdr.Ctrl & 3) {
1546 if (cdr.IrqStat & rt) {
1547 u32 nextCycle = psxRegs.intCycle[PSXINT_CDR].sCycle
1548 + psxRegs.intCycle[PSXINT_CDR].cycle;
1549 int pending = psxRegs.interrupt & (1 << PSXINT_CDR);
1550 #ifdef CDR_LOG_CMD_IRQ
1551 CDR_LOG_I("ack %02x (w=%02x p=%d,%x,%x,%d)\n",
1552 cdr.IrqStat & rt, rt, !!pending, cdr.CmdInProgress,
1553 cdr.Irq1Pending, nextCycle - psxRegs.cycle);
1555 // note: Croc, Shadow Tower (more) vs Discworld Noir (<993)
1556 if (!pending && (cdr.CmdInProgress || cdr.Irq1Pending))
1559 if (cdr.CmdInProgress) {
1560 c = 2048 - (psxRegs.cycle - nextCycle);
1561 c = MAX_VALUE(c, 512);
1563 set_event(PSXINT_CDR, c);
1572 cdr.AttenuatorLeftToRightT = rt;
1576 log_unhandled("Mute ADPCM?\n");
1578 ll = cdr.AttenuatorLeftToLeftT; lr = cdr.AttenuatorLeftToRightT;
1579 rl = cdr.AttenuatorRightToLeftT; rr = cdr.AttenuatorRightToRightT;
1580 if (ll == cdr.AttenuatorLeftToLeft &&
1581 lr == cdr.AttenuatorLeftToRight &&
1582 rl == cdr.AttenuatorRightToLeft &&
1583 rr == cdr.AttenuatorRightToRight)
1585 cdr.AttenuatorLeftToLeftT = ll; cdr.AttenuatorLeftToRightT = lr;
1586 cdr.AttenuatorRightToLeftT = rl; cdr.AttenuatorRightToRightT = rr;
1587 CDR_LOG_I("CD-XA Volume: %02x %02x | %02x %02x\n", ll, lr, rl, rr);
1588 SPU_setCDvol(ll, lr, rl, rr, psxRegs.cycle);
1594 if ((rt & 0x80) && cdr.FifoOffset < cdr.FifoSize) {
1595 CDR_LOG("cdrom: FifoOffset(2) %d/%d\n", cdr.FifoOffset, cdr.FifoSize);
1597 else if (rt & 0x80) {
1598 switch (cdr.Mode & (MODE_SIZE_2328|MODE_SIZE_2340)) {
1599 case MODE_SIZE_2328:
1601 cdr.FifoOffset = 12;
1602 cdr.FifoSize = 2048 + 12;
1605 case MODE_SIZE_2340:
1608 cdr.FifoSize = 2340;
1612 else if (!(rt & 0xc0))
1613 cdr.FifoOffset = DATA_SIZE; // fifo empty
1616 void psxDma3(u32 madr, u32 bcr, u32 chcr) {
1617 u32 cdsize, max_words;
1622 CDR_LOG_I("psxDma3() Log: *** DMA 3 *** %x addr = %x size = %x", chcr, madr, bcr);
1623 if (cdr.FifoOffset == 0) {
1625 SysPrintf(" %02x:%02x:%02x", ptr[0], ptr[1], ptr[2]);
1630 switch (chcr & 0x71000000) {
1632 ptr = getDmaRam(madr, &max_words);
1633 if (ptr == INVALID_PTR) {
1634 CDR_LOG_I("psxDma3() Log: *** DMA 3 *** NULL Pointer!\n");
1638 cdsize = (((bcr - 1) & 0xffff) + 1) * 4;
1641 GS CDX: Enhancement CD crash
1644 - Spams DMA3 and gets buffer overrun
1646 size = DATA_SIZE - cdr.FifoOffset;
1649 if (size > max_words * 4)
1650 size = max_words * 4;
1653 memcpy(ptr, cdr.Transfer + cdr.FifoOffset, size);
1654 cdr.FifoOffset += size;
1656 if (size < cdsize) {
1657 CDR_LOG_I("cdrom: dma3 %d/%d\n", size, cdsize);
1658 memset(ptr + size, cdr.Transfer[0x920], cdsize - size);
1660 psxCpu->Clear(madr, cdsize / 4);
1662 set_event(PSXINT_CDRDMA, (cdsize / 4) * 24);
1664 HW_DMA3_CHCR &= SWAPu32(~0x10000000);
1666 HW_DMA3_MADR = SWAPu32(madr + cdsize);
1667 HW_DMA3_BCR &= SWAPu32(0xffff0000);
1671 psxRegs.cycle += (cdsize/4) * 24 - 20;
1676 CDR_LOG_I("psxDma3() Log: Unknown cddma %x\n", chcr);
1680 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1684 void cdrDmaInterrupt(void)
1686 if (HW_DMA3_CHCR & SWAP32(0x01000000))
1688 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1693 static void getCdInfo(void)
1697 CDR_getTN(cdr.ResultTN);
1698 CDR_getTD(0, cdr.SetSectorEnd);
1699 tmp = cdr.SetSectorEnd[0];
1700 cdr.SetSectorEnd[0] = cdr.SetSectorEnd[2];
1701 cdr.SetSectorEnd[2] = tmp;
1705 memset(&cdr, 0, sizeof(cdr));
1708 cdr.FilterChannel = 0;
1710 cdr.IrqStat = NoIntr;
1711 cdr.FifoOffset = DATA_SIZE; // fifo empty
1713 CDR_getStatus(&stat);
1714 if (stat.Status & STATUS_SHELLOPEN) {
1715 cdr.DriveState = DRIVESTATE_LID_OPEN;
1716 cdr.StatP = STATUS_SHELLOPEN;
1718 else if (CdromId[0] == '\0') {
1719 cdr.DriveState = DRIVESTATE_STOPPED;
1723 cdr.DriveState = DRIVESTATE_STANDBY;
1724 cdr.StatP = STATUS_ROTATING;
1727 // BIOS player - default values
1728 cdr.AttenuatorLeftToLeft = 0x80;
1729 cdr.AttenuatorLeftToRight = 0x00;
1730 cdr.AttenuatorRightToLeft = 0x00;
1731 cdr.AttenuatorRightToRight = 0x80;
1732 SPU_setCDvol(cdr.AttenuatorLeftToLeft, cdr.AttenuatorLeftToRight,
1733 cdr.AttenuatorRightToLeft, cdr.AttenuatorRightToRight, psxRegs.cycle);
1738 int cdrFreeze(void *f, int Mode) {
1742 if (Mode == 0 && !Config.Cdda)
1745 cdr.freeze_ver = 0x63647202;
1746 gzfreeze(&cdr, sizeof(cdr));
1749 cdr.ParamP = cdr.ParamC;
1750 tmp = cdr.FifoOffset;
1753 gzfreeze(&tmp, sizeof(tmp));
1756 u8 ll = 0, lr = 0, rl = 0, rr = 0;
1759 cdr.FifoOffset = tmp < DATA_SIZE ? tmp : DATA_SIZE;
1760 cdr.FifoSize = (cdr.Mode & MODE_SIZE_2340) ? 2340 : 2048 + 12;
1761 if (cdr.SubqForwardSectors > SUBQ_FORWARD_SECTORS)
1762 cdr.SubqForwardSectors = SUBQ_FORWARD_SECTORS;
1764 // read right sub data
1765 tmpp[0] = btoi(cdr.Prev[0]);
1766 tmpp[1] = btoi(cdr.Prev[1]);
1767 tmpp[2] = btoi(cdr.Prev[2]);
1772 if (cdr.freeze_ver < 0x63647202)
1773 memcpy(cdr.SetSectorPlay, cdr.SetSector, 3);
1775 Find_CurTrack(cdr.SetSectorPlay);
1777 CDR_play(cdr.SetSectorPlay);
1780 ll = cdr.AttenuatorLeftToLeft, lr = cdr.AttenuatorLeftToLeft,
1781 rl = cdr.AttenuatorRightToLeft, rr = cdr.AttenuatorRightToRight;
1782 SPU_setCDvol(ll, lr, rl, rr, psxRegs.cycle);
1788 void LidInterrupt(void) {
1790 cdrLidSeekInterrupt();