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);
636 static void cdrAttenuate(s16 *buf, int samples, int stereo);
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 void cdrPlayReadInterrupt(void)
668 cdr.LastReadSeekCycles = psxRegs.cycle;
675 if (!cdr.Play) return;
677 CDR_LOG("CDDA - %02d:%02d:%02d m %02x\n",
678 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2], cdr.Mode);
680 cdr.DriveState = DRIVESTATE_PLAY_READ;
681 SetPlaySeekRead(cdr.StatP, STATUS_PLAY);
682 if (memcmp(cdr.SetSectorPlay, cdr.SetSectorEnd, 3) == 0) {
683 CDR_LOG_I("end stop\n");
685 SetPlaySeekRead(cdr.StatP, 0);
686 cdr.TrackChanged = TRUE;
687 cdr.DriveState = DRIVESTATE_PAUSED;
690 CDR_readCDDA(cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2], (u8 *)read_buf);
693 if (!cdr.IrqStat && (cdr.Mode & (MODE_AUTOPAUSE|MODE_REPORT)))
694 cdrPlayInterrupt_Autopause();
696 if (!cdr.Muted && cdr.Play && !Config.Cdda) {
697 cdrPrepCdda(read_buf, CD_FRAMESIZE_RAW / 4);
698 cdrAttenuate(read_buf, CD_FRAMESIZE_RAW / 4, 1);
699 SPU_playCDDAchannel(read_buf, CD_FRAMESIZE_RAW, psxRegs.cycle, 0);
702 msfiAdd(cdr.SetSectorPlay, 1);
704 // update for CdlGetlocP/autopause
705 generate_subq(cdr.SetSectorPlay);
707 CDRPLAYREAD_INT(cdReadTime, 0);
710 #define CMD_PART2 0x100
711 #define CMD_WHILE_NOT_READY 0x200
713 void cdrInterrupt(void) {
714 int start_rotating = 0;
716 u32 cycles, seekTime = 0;
717 u32 second_resp_time = 0;
723 u8 IrqStat = Acknowledge;
728 CDR_LOG_I("cmd %02x with irqstat %x\n",
729 cdr.CmdInProgress, cdr.IrqStat);
732 if (cdr.Irq1Pending) {
733 // hand out the "newest" sector, according to nocash
734 cdrUpdateTransferBuf(CDR_getBuffer());
735 CDR_LOG_I("%x:%02x:%02x loaded on ack, cmd=%02x res=%02x\n",
736 cdr.Transfer[0], cdr.Transfer[1], cdr.Transfer[2],
737 cdr.CmdInProgress, cdr.Irq1Pending);
739 cdr.Result[0] = cdr.Irq1Pending;
741 setIrq((cdr.Irq1Pending & STATUS_ERROR) ? DiskError : DataReady, 0x1003);
747 cdr.Result[0] = cdr.StatP;
749 Cmd = cdr.CmdInProgress;
750 cdr.CmdInProgress = 0;
759 switch (cdr.DriveState) {
760 case DRIVESTATE_PREPARE_CD:
762 // Syphon filter 2 expects commands to work shortly after it sees
763 // STATUS_ROTATING, so give up trying to emulate the startup seq
764 cdr.DriveState = DRIVESTATE_STANDBY;
765 cdr.StatP &= ~STATUS_SEEK;
766 psxRegs.interrupt &= ~(1 << PSXINT_CDRLID);
770 case DRIVESTATE_LID_OPEN:
771 case DRIVESTATE_RESCAN_CD:
772 // no disk or busy with the initial scan, allowed cmds are limited
773 not_ready = CMD_WHILE_NOT_READY;
777 switch (Cmd | not_ready) {
779 case CdlNop + CMD_WHILE_NOT_READY:
780 if (cdr.DriveState != DRIVESTATE_LID_OPEN)
781 cdr.StatP &= ~STATUS_SHELLOPEN;
785 // case CdlSetloc + CMD_WHILE_NOT_READY: // or is it?
786 CDR_LOG("CDROM setloc command (%02X, %02X, %02X)\n", cdr.Param[0], cdr.Param[1], cdr.Param[2]);
788 // MM must be BCD, SS must be BCD and <0x60, FF must be BCD and <0x75
789 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))
791 CDR_LOG_I("Invalid/out of range seek to %02X:%02X:%02X\n", cdr.Param[0], cdr.Param[1], cdr.Param[2]);
792 if (++cdr.errorRetryhack > 100)
794 error = ERROR_BAD_ARGNUM;
799 for (i = 0; i < 3; i++)
800 set_loc[i] = btoi(cdr.Param[i]);
801 memcpy(cdr.SetSector, set_loc, 3);
802 cdr.SetSector[3] = 0;
803 cdr.SetlocPending = 1;
804 cdr.errorRetryhack = 0;
813 cdr.FastBackward = 0;
817 // - Pause player, hit Track 01/02/../xx (Setloc issued!!)
819 if (ParamC != 0 && cdr.Param[0] != 0) {
820 int track = btoi( cdr.Param[0] );
822 if (track <= cdr.ResultTN[1])
823 cdr.CurTrack = track;
825 CDR_LOG("PLAY track %d\n", cdr.CurTrack);
827 if (CDR_getTD((u8)cdr.CurTrack, cdr.ResultTD) != -1) {
828 for (i = 0; i < 3; i++)
829 set_loc[i] = cdr.ResultTD[2 - i];
830 seekTime = cdrSeekTime(set_loc);
831 memcpy(cdr.SetSectorPlay, set_loc, 3);
834 else if (cdr.SetlocPending) {
835 seekTime = cdrSeekTime(cdr.SetSector);
836 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
839 CDR_LOG("PLAY Resume @ %d:%d:%d\n",
840 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2]);
842 cdr.SetlocPending = 0;
845 Rayman: detect track changes
848 Twisted Metal 2: skip PREGAP + starting accurate SubQ
849 - plays tracks without retry play
851 Wild 9: skip PREGAP + starting accurate SubQ
852 - plays tracks without retry play
854 Find_CurTrack(cdr.SetSectorPlay);
855 generate_subq(cdr.SetSectorPlay);
856 cdr.LocL[0] = LOCL_INVALID;
857 cdr.SubqForwardSectors = 1;
858 cdr.TrackChanged = FALSE;
859 cdr.FileChannelSelected = 0;
861 cdr.ReportDelay = 60;
865 CDR_play(cdr.SetSectorPlay);
867 SetPlaySeekRead(cdr.StatP, STATUS_SEEK | STATUS_ROTATING);
869 // BIOS player - set flag again
871 cdr.DriveState = DRIVESTATE_PLAY_READ;
873 CDRPLAYREAD_INT(cdReadTime + seekTime, 1);
878 // TODO: error 80 if stopped
881 // GameShark CD Player: Calls 2x + Play 2x
883 cdr.FastBackward = 0;
889 // GameShark CD Player: Calls 2x + Play 2x
890 cdr.FastBackward = 1;
895 if (cdr.DriveState != DRIVESTATE_STOPPED) {
896 error = ERROR_BAD_ARGNUM;
899 cdr.DriveState = DRIVESTATE_STANDBY;
900 second_resp_time = cdReadTime * 125 / 2;
904 case CdlStandby + CMD_PART2:
910 // grab time for current track
911 CDR_getTD((u8)(cdr.CurTrack), cdr.ResultTD);
913 cdr.SetSectorPlay[0] = cdr.ResultTD[2];
914 cdr.SetSectorPlay[1] = cdr.ResultTD[1];
915 cdr.SetSectorPlay[2] = cdr.ResultTD[0];
920 SetPlaySeekRead(cdr.StatP, 0);
921 cdr.StatP &= ~STATUS_ROTATING;
922 cdr.LocL[0] = LOCL_INVALID;
924 second_resp_time = 0x800;
925 if (cdr.DriveState != DRIVESTATE_STOPPED)
926 second_resp_time = cdReadTime * 30 / 2;
928 cdr.DriveState = DRIVESTATE_STOPPED;
931 case CdlStop + CMD_PART2:
939 // how the drive maintains the position while paused is quite
940 // complicated, this is the minimum to make "Bedlam" happy
941 msfiSub(cdr.SetSectorPlay, MIN_VALUE(cdr.sectorsRead, 4));
945 Gundam Battle Assault 2: much slower (*)
946 - Fixes boot, gameplay
948 Hokuto no Ken 2: slower
949 - Fixes intro + subtitles
951 InuYasha - Feudal Fairy Tale: slower
954 /* Gameblabla - Tightening the timings (as taken from Duckstation).
955 * The timings from Duckstation are based upon hardware tests.
956 * Mednafen's timing don't work for Gundam Battle Assault 2 in PAL/50hz mode,
957 * seems to be timing sensitive as it can depend on the CPU's clock speed.
959 if (!(cdr.StatP & (STATUS_PLAY | STATUS_READ)))
961 second_resp_time = 7000;
965 second_resp_time = (((cdr.Mode & MODE_SPEED) ? 1 : 2) * 1097107);
967 SetPlaySeekRead(cdr.StatP, 0);
968 cdr.DriveState = DRIVESTATE_PAUSED;
971 case CdlPause + CMD_PART2:
976 case CdlReset + CMD_WHILE_NOT_READY:
979 SetPlaySeekRead(cdr.StatP, 0);
980 cdr.LocL[0] = LOCL_INVALID;
982 cdr.Mode = MODE_SIZE_2340; /* This fixes This is Football 2, Pooh's Party lockups */
983 cdr.DriveState = DRIVESTATE_PAUSED;
984 second_resp_time = not_ready ? 70000 : 4100000;
988 case CdlReset + CMD_PART2:
989 case CdlReset + CMD_PART2 + CMD_WHILE_NOT_READY:
1002 cdr.FilterFile = cdr.Param[0];
1003 cdr.FilterChannel = cdr.Param[1];
1004 cdr.FileChannelSelected = 0;
1008 case CdlSetmode + CMD_WHILE_NOT_READY:
1009 CDR_LOG("cdrWrite1() Log: Setmode %x\n", cdr.Param[0]);
1010 cdr.Mode = cdr.Param[0];
1014 case CdlGetparam + CMD_WHILE_NOT_READY:
1015 /* Gameblabla : According to mednafen, Result size should be 5 and done this way. */
1017 cdr.Result[1] = cdr.Mode;
1019 cdr.Result[3] = cdr.FilterFile;
1020 cdr.Result[4] = cdr.FilterChannel;
1024 if (cdr.LocL[0] == LOCL_INVALID) {
1029 memcpy(cdr.Result, cdr.LocL, 8);
1034 memcpy(&cdr.Result, &cdr.subq, 8);
1037 case CdlReadT: // SetSession?
1039 second_resp_time = cdReadTime * 290 / 4;
1043 case CdlReadT + CMD_PART2:
1048 if (CDR_getTN(cdr.ResultTN) == -1) {
1052 cdr.Result[1] = itob(cdr.ResultTN[0]);
1053 cdr.Result[2] = itob(cdr.ResultTN[1]);
1057 cdr.Track = btoi(cdr.Param[0]);
1058 if (CDR_getTD(cdr.Track, cdr.ResultTD) == -1) {
1059 error = ERROR_BAD_ARGVAL;
1063 cdr.Result[1] = itob(cdr.ResultTD[2]);
1064 cdr.Result[2] = itob(cdr.ResultTD[1]);
1066 //cdr.Result[3] = itob(cdr.ResultTD[0]);
1073 SetPlaySeekRead(cdr.StatP, STATUS_SEEK | STATUS_ROTATING);
1075 seekTime = cdrSeekTime(cdr.SetSector);
1076 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
1077 cdr.DriveState = DRIVESTATE_SEEK;
1079 Crusaders of Might and Magic = 0.5x-4x
1080 - fix cutscene speech start
1082 Eggs of Steel = 2x-?
1086 - fix cutscene speech
1091 second_resp_time = cdReadTime + seekTime;
1095 case CdlSeekL + CMD_PART2:
1096 case CdlSeekP + CMD_PART2:
1097 SetPlaySeekRead(cdr.StatP, 0);
1098 cdr.Result[0] = cdr.StatP;
1101 Find_CurTrack(cdr.SetSectorPlay);
1102 read_ok = ReadTrack(cdr.SetSectorPlay);
1103 if (read_ok && (buf = CDR_getBuffer()))
1104 memcpy(cdr.LocL, buf, 8);
1105 UpdateSubq(cdr.SetSectorPlay);
1106 cdr.DriveState = DRIVESTATE_STANDBY;
1107 cdr.TrackChanged = FALSE;
1108 cdr.LastReadSeekCycles = psxRegs.cycle;
1112 case CdlTest + CMD_WHILE_NOT_READY:
1113 switch (cdr.Param[0]) {
1114 case 0x20: // System Controller ROM Version
1116 memcpy(cdr.Result, Test20, 4);
1120 memcpy(cdr.Result, Test22, 4);
1122 case 0x23: case 0x24:
1124 memcpy(cdr.Result, Test23, 4);
1130 second_resp_time = 20480;
1133 case CdlID + CMD_PART2:
1135 cdr.Result[0] = cdr.StatP;
1140 // 0x10 - audio | 0x40 - disk missing | 0x80 - unlicensed
1141 if (CDR_getStatus(&stat) == -1 || stat.Type == 0 || stat.Type == 0xff) {
1142 cdr.Result[1] = 0xc0;
1146 cdr.Result[1] |= 0x10;
1147 if (CdromId[0] == '\0')
1148 cdr.Result[1] |= 0x80;
1150 cdr.Result[0] |= (cdr.Result[1] >> 4) & 0x08;
1151 CDR_LOG_I("CdlID: %02x %02x %02x %02x\n", cdr.Result[0],
1152 cdr.Result[1], cdr.Result[2], cdr.Result[3]);
1154 /* This adds the string "PCSX" in Playstation bios boot screen */
1155 memcpy((char *)&cdr.Result[4], "PCSX", 4);
1160 case CdlInit + CMD_WHILE_NOT_READY:
1163 SetPlaySeekRead(cdr.StatP, 0);
1164 // yes, it really sets STATUS_SHELLOPEN
1165 cdr.StatP |= STATUS_SHELLOPEN;
1166 cdr.DriveState = DRIVESTATE_RESCAN_CD;
1167 set_event(PSXINT_CDRLID, 20480);
1172 case CdlGetQ + CMD_WHILE_NOT_READY:
1176 case CdlReadToc + CMD_WHILE_NOT_READY:
1177 cdr.LocL[0] = LOCL_INVALID;
1178 second_resp_time = cdReadTime * 180 / 4;
1182 case CdlReadToc + CMD_PART2:
1183 case CdlReadToc + CMD_PART2 + CMD_WHILE_NOT_READY:
1189 if (cdr.Reading && !cdr.SetlocPending)
1192 Find_CurTrack(cdr.SetlocPending ? cdr.SetSector : cdr.SetSectorPlay);
1194 if ((cdr.Mode & MODE_CDDA) && cdr.CurTrack > 1)
1195 // Read* acts as play for cdda tracks in cdda mode
1199 if (cdr.SetlocPending) {
1200 seekTime = cdrSeekTime(cdr.SetSector);
1201 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
1202 cdr.SetlocPending = 0;
1205 cdr.FileChannelSelected = 0;
1206 cdr.AdpcmActive = 0;
1208 // Fighting Force 2 - update subq time immediately
1210 UpdateSubq(cdr.SetSectorPlay);
1211 cdr.LocL[0] = LOCL_INVALID;
1212 cdr.SubqForwardSectors = 1;
1213 cdr.sectorsRead = 0;
1214 cdr.DriveState = DRIVESTATE_SEEK;
1216 cycles = (cdr.Mode & MODE_SPEED) ? cdReadTime : cdReadTime * 2;
1218 if (Config.hacks.cdr_read_timing)
1219 cycles = cdrAlignTimingHack(cycles);
1220 CDRPLAYREAD_INT(cycles, 1);
1222 SetPlaySeekRead(cdr.StatP, STATUS_SEEK);
1228 error = ERROR_INVALIDCMD;
1233 cdr.Result[0] = cdr.StatP | STATUS_ERROR;
1234 cdr.Result[1] = not_ready ? ERROR_NOTREADY : error;
1235 IrqStat = DiskError;
1236 CDR_LOG_I("cmd %02x error %02x\n", Cmd, cdr.Result[1]);
1240 if (cdr.DriveState == DRIVESTATE_STOPPED && start_rotating) {
1241 cdr.DriveState = DRIVESTATE_STANDBY;
1242 cdr.StatP |= STATUS_ROTATING;
1245 if (second_resp_time) {
1246 cdr.CmdInProgress = Cmd | 0x100;
1247 set_event(PSXINT_CDR, second_resp_time);
1249 else if (cdr.Cmd && cdr.Cmd != (Cmd & 0xff)) {
1250 cdr.CmdInProgress = cdr.Cmd;
1251 CDR_LOG_I("cmd %02x came before %02x finished\n", cdr.Cmd, Cmd);
1254 setIrq(IrqStat, Cmd);
1258 #define ssat32_to_16(v) \
1259 asm("ssat %0,#16,%1" : "=r" (v) : "r" (v))
1261 #define ssat32_to_16(v) do { \
1262 if (v < -32768) v = -32768; \
1263 else if (v > 32767) v = 32767; \
1267 static void cdrPrepCdda(s16 *buf, int samples)
1269 #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
1271 for (i = 0; i < samples; i++) {
1272 buf[i * 2 + 0] = SWAP16(buf[i * 2 + 0]);
1273 buf[i * 2 + 1] = SWAP16(buf[i * 2 + 1]);
1278 static void cdrAttenuate(s16 *buf, int samples, int stereo)
1281 int ll = cdr.AttenuatorLeftToLeft;
1282 int lr = cdr.AttenuatorLeftToRight;
1283 int rl = cdr.AttenuatorRightToLeft;
1284 int rr = cdr.AttenuatorRightToRight;
1286 if (lr == 0 && rl == 0 && 0x78 <= ll && ll <= 0x88 && 0x78 <= rr && rr <= 0x88)
1289 if (!stereo && ll == 0x40 && lr == 0x40 && rl == 0x40 && rr == 0x40)
1293 for (i = 0; i < samples; i++) {
1296 l = (l * ll + r * rl) >> 7;
1297 r = (r * rr + l * lr) >> 7;
1305 for (i = 0; i < samples; i++) {
1307 l = l * (ll + rl) >> 7;
1308 //r = r * (rr + lr) >> 7;
1316 static void cdrReadInterruptSetResult(unsigned char result)
1319 CDR_LOG_I("%d:%02d:%02d irq miss, cmd=%02x irqstat=%02x\n",
1320 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2],
1321 cdr.CmdInProgress, cdr.IrqStat);
1322 cdr.Irq1Pending = result;
1326 cdr.Result[0] = result;
1327 setIrq((result & STATUS_ERROR) ? DiskError : DataReady, 0x1004);
1330 static void cdrUpdateTransferBuf(const u8 *buf)
1334 memcpy(cdr.Transfer, buf, DATA_SIZE);
1335 CheckPPFCache(cdr.Transfer, cdr.Prev[0], cdr.Prev[1], cdr.Prev[2]);
1336 CDR_LOG("cdr.Transfer %02x:%02x:%02x\n",
1337 cdr.Transfer[0], cdr.Transfer[1], cdr.Transfer[2]);
1338 if (cdr.FifoOffset < 2048 + 12)
1339 CDR_LOG("FifoOffset(1) %d/%d\n", cdr.FifoOffset, cdr.FifoSize);
1342 static void cdrReadInterrupt(void)
1344 const struct { u8 file, chan, mode, coding; } *subhdr;
1345 const u8 *buf = NULL;
1346 int deliver_data = 1;
1350 memcpy(subqPos, cdr.SetSectorPlay, sizeof(subqPos));
1351 msfiAdd(subqPos, cdr.SubqForwardSectors);
1352 UpdateSubq(subqPos);
1353 if (cdr.SubqForwardSectors < SUBQ_FORWARD_SECTORS) {
1354 cdr.SubqForwardSectors++;
1355 CDRPLAYREAD_INT((cdr.Mode & MODE_SPEED) ? (cdReadTime / 2) : cdReadTime, 0);
1359 // note: CdlGetlocL should work as soon as STATUS_READ is indicated
1360 SetPlaySeekRead(cdr.StatP, STATUS_READ | STATUS_ROTATING);
1361 cdr.DriveState = DRIVESTATE_PLAY_READ;
1364 read_ok = ReadTrack(cdr.SetSectorPlay);
1366 buf = CDR_getBuffer();
1371 CDR_LOG_I("cdrReadInterrupt() Log: err\n");
1372 cdrReadInterruptSetResult(cdr.StatP | STATUS_ERROR);
1373 cdr.DriveState = DRIVESTATE_PAUSED; // ?
1376 memcpy(cdr.LocL, buf, 8);
1378 if (!cdr.IrqStat && !cdr.Irq1Pending)
1379 cdrUpdateTransferBuf(buf);
1381 subhdr = (void *)(buf + 4);
1383 // try to process as adpcm
1384 if (!(cdr.Mode & MODE_STRSND))
1386 if (buf[3] != 2 || (subhdr->mode & 0x44) != 0x44) // or 0x64?
1388 CDR_LOG("f=%d m=%d %d,%3d | %d,%2d | %d,%2d\n", !!(cdr.Mode & MODE_SF), cdr.Muted,
1389 subhdr->file, subhdr->chan, cdr.CurFile, cdr.CurChannel, cdr.FilterFile, cdr.FilterChannel);
1390 if ((cdr.Mode & MODE_SF) && (subhdr->file != cdr.FilterFile || subhdr->chan != cdr.FilterChannel))
1392 if (subhdr->chan & 0xe0) { // ?
1393 if (subhdr->chan != 0xff)
1394 log_unhandled("adpcm %d:%d\n", subhdr->file, subhdr->chan);
1397 if (!cdr.FileChannelSelected) {
1398 cdr.CurFile = subhdr->file;
1399 cdr.CurChannel = subhdr->chan;
1400 cdr.FileChannelSelected = 1;
1402 else if (subhdr->file != cdr.CurFile || subhdr->chan != cdr.CurChannel)
1405 // accepted as adpcm
1410 if (!cdr.Muted && cdr.AdpcmActive) {
1411 cdrAttenuate(cdr.Xa.pcm, cdr.Xa.nsamples, cdr.Xa.stereo);
1412 SPU_playADPCMchannel(&cdr.Xa, psxRegs.cycle, 0);
1415 cdr.AdpcmActive = !xa_decode_sector(&cdr.Xa, buf + 4, !cdr.AdpcmActive);
1418 if ((cdr.Mode & MODE_SF) && (subhdr->mode & 0x44) == 0x44) // according to nocash
1422 Croc 2: $40 - only FORM1 (*)
1423 Judge Dredd: $C8 - only FORM1 (*)
1424 Sim Theme Park - no adpcm at all (zero)
1428 cdrReadInterruptSetResult(cdr.StatP);
1430 msfiAdd(cdr.SetSectorPlay, 1);
1432 CDRPLAYREAD_INT((cdr.Mode & MODE_SPEED) ? (cdReadTime / 2) : cdReadTime, 0);
1438 bit 2 - adpcm active
1439 bit 5 - 1 result ready
1441 bit 7 - 1 command being processed
1444 unsigned char cdrRead0(void) {
1446 cdr.Ctrl |= cdr.AdpcmActive << 2;
1447 cdr.Ctrl |= cdr.ResultReady << 5;
1449 cdr.Ctrl |= 0x40; // data fifo not empty
1451 // What means the 0x10 and the 0x08 bits? I only saw it used by the bios
1454 CDR_LOG_IO("cdr r0.sta: %02x\n", cdr.Ctrl);
1456 return psxHu8(0x1800) = cdr.Ctrl;
1459 void cdrWrite0(unsigned char rt) {
1460 CDR_LOG_IO("cdr w0.idx: %02x\n", rt);
1462 cdr.Ctrl = (rt & 3) | (cdr.Ctrl & ~3);
1465 unsigned char cdrRead1(void) {
1466 if ((cdr.ResultP & 0xf) < cdr.ResultC)
1467 psxHu8(0x1801) = cdr.Result[cdr.ResultP & 0xf];
1471 if (cdr.ResultP == cdr.ResultC)
1472 cdr.ResultReady = 0;
1474 CDR_LOG_IO("cdr r1.rsp: %02x #%u\n", psxHu8(0x1801), cdr.ResultP - 1);
1476 return psxHu8(0x1801);
1479 void cdrWrite1(unsigned char rt) {
1480 const char *rnames[] = { "cmd", "smd", "smc", "arr" }; (void)rnames;
1481 CDR_LOG_IO("cdr w1.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1483 switch (cdr.Ctrl & 3) {
1487 cdr.AttenuatorRightToRightT = rt;
1493 #ifdef CDR_LOG_CMD_IRQ
1494 CDR_LOG_I("CD1 write: %x (%s)", rt, CmdName[rt]);
1497 SysPrintf(" Param[%d] = {", cdr.ParamC);
1498 for (i = 0; i < cdr.ParamC; i++)
1499 SysPrintf(" %x,", cdr.Param[i]);
1506 cdr.ResultReady = 0;
1509 if (!cdr.CmdInProgress) {
1510 cdr.CmdInProgress = rt;
1511 // should be something like 12k + controller delays
1512 set_event(PSXINT_CDR, 5000);
1515 CDR_LOG_I("cmd while busy: %02x, prev %02x, busy %02x\n",
1516 rt, cdr.Cmd, cdr.CmdInProgress);
1517 if (cdr.CmdInProgress < 0x100) // no pending 2nd response
1518 cdr.CmdInProgress = rt;
1524 unsigned char cdrRead2(void) {
1525 unsigned char ret = cdr.Transfer[0x920];
1527 if (cdr.FifoOffset < cdr.FifoSize)
1528 ret = cdr.Transfer[cdr.FifoOffset++];
1530 CDR_LOG_I("read empty fifo (%d)\n", cdr.FifoSize);
1532 CDR_LOG_IO("cdr r2.dat: %02x\n", ret);
1536 void cdrWrite2(unsigned char rt) {
1537 const char *rnames[] = { "prm", "ien", "all", "arl" }; (void)rnames;
1538 CDR_LOG_IO("cdr w2.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1540 switch (cdr.Ctrl & 3) {
1542 if (cdr.ParamC < 8) // FIXME: size and wrapping
1543 cdr.Param[cdr.ParamC++] = rt;
1547 setIrq(cdr.IrqStat, 0x1005);
1550 cdr.AttenuatorLeftToLeftT = rt;
1553 cdr.AttenuatorRightToLeftT = rt;
1558 unsigned char cdrRead3(void) {
1560 psxHu8(0x1803) = cdr.IrqStat | 0xE0;
1562 psxHu8(0x1803) = cdr.IrqMask | 0xE0;
1564 CDR_LOG_IO("cdr r3.%s: %02x\n", (cdr.Ctrl & 1) ? "ifl" : "ien", psxHu8(0x1803));
1565 return psxHu8(0x1803);
1568 void cdrWrite3(unsigned char rt) {
1569 const char *rnames[] = { "req", "ifl", "alr", "ava" }; (void)rnames;
1570 CDR_LOG_IO("cdr w3.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1572 switch (cdr.Ctrl & 3) {
1576 if (cdr.IrqStat & rt) {
1577 u32 nextCycle = psxRegs.intCycle[PSXINT_CDR].sCycle
1578 + psxRegs.intCycle[PSXINT_CDR].cycle;
1579 int pending = psxRegs.interrupt & (1 << PSXINT_CDR);
1580 #ifdef CDR_LOG_CMD_IRQ
1581 CDR_LOG_I("ack %02x (w=%02x p=%d,%x,%x,%d)\n",
1582 cdr.IrqStat & rt, rt, !!pending, cdr.CmdInProgress,
1583 cdr.Irq1Pending, nextCycle - psxRegs.cycle);
1585 // note: Croc, Shadow Tower (more) vs Discworld Noir (<993)
1586 if (!pending && (cdr.CmdInProgress || cdr.Irq1Pending))
1589 if (cdr.CmdInProgress) {
1590 c = 2048 - (psxRegs.cycle - nextCycle);
1591 c = MAX_VALUE(c, 512);
1593 set_event(PSXINT_CDR, c);
1602 cdr.AttenuatorLeftToRightT = rt;
1606 memcpy(&cdr.AttenuatorLeftToLeft, &cdr.AttenuatorLeftToLeftT, 4);
1607 CDR_LOG("CD-XA Volume: %02x %02x | %02x %02x\n",
1608 cdr.AttenuatorLeftToLeft, cdr.AttenuatorLeftToRight,
1609 cdr.AttenuatorRightToLeft, cdr.AttenuatorRightToRight);
1615 if ((rt & 0x80) && cdr.FifoOffset < cdr.FifoSize) {
1616 CDR_LOG("cdrom: FifoOffset(2) %d/%d\n", cdr.FifoOffset, cdr.FifoSize);
1618 else if (rt & 0x80) {
1619 switch (cdr.Mode & (MODE_SIZE_2328|MODE_SIZE_2340)) {
1620 case MODE_SIZE_2328:
1622 cdr.FifoOffset = 12;
1623 cdr.FifoSize = 2048 + 12;
1626 case MODE_SIZE_2340:
1629 cdr.FifoSize = 2340;
1633 else if (!(rt & 0xc0))
1634 cdr.FifoOffset = DATA_SIZE; // fifo empty
1637 void psxDma3(u32 madr, u32 bcr, u32 chcr) {
1638 u32 cdsize, max_words;
1643 CDR_LOG_I("psxDma3() Log: *** DMA 3 *** %x addr = %x size = %x", chcr, madr, bcr);
1644 if (cdr.FifoOffset == 0) {
1646 SysPrintf(" %02x:%02x:%02x", ptr[0], ptr[1], ptr[2]);
1651 switch (chcr & 0x71000000) {
1653 ptr = getDmaRam(madr, &max_words);
1654 if (ptr == INVALID_PTR) {
1655 CDR_LOG_I("psxDma3() Log: *** DMA 3 *** NULL Pointer!\n");
1659 cdsize = (((bcr - 1) & 0xffff) + 1) * 4;
1662 GS CDX: Enhancement CD crash
1665 - Spams DMA3 and gets buffer overrun
1667 size = DATA_SIZE - cdr.FifoOffset;
1670 if (size > max_words * 4)
1671 size = max_words * 4;
1674 memcpy(ptr, cdr.Transfer + cdr.FifoOffset, size);
1675 cdr.FifoOffset += size;
1677 if (size < cdsize) {
1678 CDR_LOG_I("cdrom: dma3 %d/%d\n", size, cdsize);
1679 memset(ptr + size, cdr.Transfer[0x920], cdsize - size);
1681 psxCpu->Clear(madr, cdsize / 4);
1683 set_event(PSXINT_CDRDMA, (cdsize / 4) * 24);
1685 HW_DMA3_CHCR &= SWAPu32(~0x10000000);
1687 HW_DMA3_MADR = SWAPu32(madr + cdsize);
1688 HW_DMA3_BCR &= SWAPu32(0xffff0000);
1692 psxRegs.cycle += (cdsize/4) * 24 - 20;
1697 CDR_LOG_I("psxDma3() Log: Unknown cddma %x\n", chcr);
1701 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1705 void cdrDmaInterrupt(void)
1707 if (HW_DMA3_CHCR & SWAP32(0x01000000))
1709 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1714 static void getCdInfo(void)
1718 CDR_getTN(cdr.ResultTN);
1719 CDR_getTD(0, cdr.SetSectorEnd);
1720 tmp = cdr.SetSectorEnd[0];
1721 cdr.SetSectorEnd[0] = cdr.SetSectorEnd[2];
1722 cdr.SetSectorEnd[2] = tmp;
1726 memset(&cdr, 0, sizeof(cdr));
1729 cdr.FilterChannel = 0;
1731 cdr.IrqStat = NoIntr;
1732 cdr.FifoOffset = DATA_SIZE; // fifo empty
1734 CDR_getStatus(&stat);
1735 if (stat.Status & STATUS_SHELLOPEN) {
1736 cdr.DriveState = DRIVESTATE_LID_OPEN;
1737 cdr.StatP = STATUS_SHELLOPEN;
1739 else if (CdromId[0] == '\0') {
1740 cdr.DriveState = DRIVESTATE_STOPPED;
1744 cdr.DriveState = DRIVESTATE_STANDBY;
1745 cdr.StatP = STATUS_ROTATING;
1748 // BIOS player - default values
1749 cdr.AttenuatorLeftToLeft = 0x80;
1750 cdr.AttenuatorLeftToRight = 0x00;
1751 cdr.AttenuatorRightToLeft = 0x00;
1752 cdr.AttenuatorRightToRight = 0x80;
1757 int cdrFreeze(void *f, int Mode) {
1761 if (Mode == 0 && !Config.Cdda)
1764 cdr.freeze_ver = 0x63647202;
1765 gzfreeze(&cdr, sizeof(cdr));
1768 cdr.ParamP = cdr.ParamC;
1769 tmp = cdr.FifoOffset;
1772 gzfreeze(&tmp, sizeof(tmp));
1777 cdr.FifoOffset = tmp < DATA_SIZE ? tmp : DATA_SIZE;
1778 cdr.FifoSize = (cdr.Mode & MODE_SIZE_2340) ? 2340 : 2048 + 12;
1779 if (cdr.SubqForwardSectors > SUBQ_FORWARD_SECTORS)
1780 cdr.SubqForwardSectors = SUBQ_FORWARD_SECTORS;
1782 // read right sub data
1783 tmpp[0] = btoi(cdr.Prev[0]);
1784 tmpp[1] = btoi(cdr.Prev[1]);
1785 tmpp[2] = btoi(cdr.Prev[2]);
1790 if (cdr.freeze_ver < 0x63647202)
1791 memcpy(cdr.SetSectorPlay, cdr.SetSector, 3);
1793 Find_CurTrack(cdr.SetSectorPlay);
1795 CDR_play(cdr.SetSectorPlay);
1802 void LidInterrupt(void) {
1804 cdrLidSeekInterrupt();