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 PhysCdPropagations;
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)) {
348 SetPlaySeekRead(cdr.StatP, 0);
349 cdr.StatP |= STATUS_SHELLOPEN;
351 // IIRC this sometimes doesn't happen on real hw
352 // (when lots of commands are sent?)
354 cdr.Result[0] = cdr.StatP | STATUS_SEEKERROR;
355 cdr.Result[1] = ERROR_SHELLOPEN;
356 if (cdr.CmdInProgress) {
357 psxRegs.interrupt &= ~(1 << PSXINT_CDR);
358 cdr.CmdInProgress = 0;
359 cdr.Result[0] = cdr.StatP | STATUS_ERROR;
360 cdr.Result[1] = ERROR_NOTREADY;
362 setIrq(DiskError, 0x1006);
364 set_event(PSXINT_CDRLID, cdReadTime * 30);
367 else if (cdr.StatP & STATUS_ROTATING) {
368 cdr.StatP &= ~STATUS_ROTATING;
370 else if (!(stat.Status & STATUS_SHELLOPEN)) {
374 // cdr.StatP STATUS_SHELLOPEN is "sticky"
375 // and is only cleared by CdlNop
377 cdr.DriveState = DRIVESTATE_RESCAN_CD;
378 set_event(PSXINT_CDRLID, cdReadTime * 105);
383 set_event(PSXINT_CDRLID, cdReadTime * 3);
386 case DRIVESTATE_RESCAN_CD:
387 cdr.StatP |= STATUS_ROTATING;
388 cdr.DriveState = DRIVESTATE_PREPARE_CD;
390 // this is very long on real hardware, over 6 seconds
391 // make it a bit faster here...
392 set_event(PSXINT_CDRLID, cdReadTime * 150);
395 case DRIVESTATE_PREPARE_CD:
396 if (cdr.StatP & STATUS_SEEK) {
397 SetPlaySeekRead(cdr.StatP, 0);
398 cdr.DriveState = DRIVESTATE_STANDBY;
401 SetPlaySeekRead(cdr.StatP, STATUS_SEEK);
402 set_event(PSXINT_CDRLID, cdReadTime * 26);
408 static void Find_CurTrack(const u8 *time)
412 current = msf2sec(time);
414 for (cdr.CurTrack = 1; cdr.CurTrack < cdr.ResultTN[1]; cdr.CurTrack++) {
415 CDR_getTD(cdr.CurTrack + 1, cdr.ResultTD);
416 sect = fsm2sec(cdr.ResultTD);
417 if (sect - current >= 150)
422 static void generate_subq(const u8 *time)
424 unsigned char start[3], next[3];
425 unsigned int this_s, start_s, next_s, pregap;
428 CDR_getTD(cdr.CurTrack, start);
429 if (cdr.CurTrack + 1 <= cdr.ResultTN[1]) {
431 CDR_getTD(cdr.CurTrack + 1, next);
434 // last track - cd size
436 next[0] = cdr.SetSectorEnd[2];
437 next[1] = cdr.SetSectorEnd[1];
438 next[2] = cdr.SetSectorEnd[0];
441 this_s = msf2sec(time);
442 start_s = fsm2sec(start);
443 next_s = fsm2sec(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 sec2msf(relative_s, cdr.subq.Relative);
462 cdr.subq.Track = itob(cdr.CurTrack);
463 cdr.subq.Relative[0] = itob(cdr.subq.Relative[0]);
464 cdr.subq.Relative[1] = itob(cdr.subq.Relative[1]);
465 cdr.subq.Relative[2] = itob(cdr.subq.Relative[2]);
466 cdr.subq.Absolute[0] = itob(time[0]);
467 cdr.subq.Absolute[1] = itob(time[1]);
468 cdr.subq.Absolute[2] = itob(time[2]);
471 static int ReadTrack(const u8 *time)
473 unsigned char tmp[3];
476 tmp[0] = itob(time[0]);
477 tmp[1] = itob(time[1]);
478 tmp[2] = itob(time[2]);
480 CDR_LOG("ReadTrack *** %02x:%02x:%02x\n", tmp[0], tmp[1], tmp[2]);
482 if (memcmp(cdr.Prev, tmp, 3) == 0)
485 read_ok = CDR_readTrack(tmp);
487 memcpy(cdr.Prev, tmp, 3);
491 static void UpdateSubq(const u8 *time)
493 const struct SubQ *subq;
494 int s = MSF2SECT(time[0], time[1], time[2]);
500 subq = (struct SubQ *)CDR_getBufferSub(s);
501 if (subq != NULL && cdr.CurTrack == 1) {
502 crc = calcCrc((u8 *)subq + 12, 10);
503 if (crc == (((u16)subq->CRC[0] << 8) | subq->CRC[1])) {
504 cdr.subq.Track = subq->TrackNumber;
505 cdr.subq.Index = subq->IndexNumber;
506 memcpy(cdr.subq.Relative, subq->TrackRelativeAddress, 3);
507 memcpy(cdr.subq.Absolute, subq->AbsoluteAddress, 3);
510 CDR_LOG_I("subq bad crc @%02d:%02d:%02d\n",
511 time[0], time[1], time[2]);
518 CDR_LOG(" -> %02x,%02x %02x:%02x:%02x %02x:%02x:%02x\n",
519 cdr.subq.Track, cdr.subq.Index,
520 cdr.subq.Relative[0], cdr.subq.Relative[1], cdr.subq.Relative[2],
521 cdr.subq.Absolute[0], cdr.subq.Absolute[1], cdr.subq.Absolute[2]);
524 static void cdrPlayInterrupt_Autopause()
527 boolean abs_lev_chselect;
530 if ((cdr.Mode & MODE_AUTOPAUSE) && cdr.TrackChanged) {
531 CDR_LOG_I("autopause\n");
534 cdr.Result[0] = cdr.StatP;
535 setIrq(DataEnd, 0x1000); // 0x1000 just for logging purposes
538 SetPlaySeekRead(cdr.StatP, 0);
539 cdr.DriveState = DRIVESTATE_PAUSED;
541 else if ((cdr.Mode & MODE_REPORT) && !cdr.ReportDelay &&
542 ((cdr.subq.Absolute[2] & 0x0f) == 0 || cdr.FastForward || cdr.FastBackward))
545 cdr.Result[0] = cdr.StatP;
546 cdr.Result[1] = cdr.subq.Track;
547 cdr.Result[2] = cdr.subq.Index;
549 abs_lev_chselect = cdr.subq.Absolute[1] & 0x01;
551 /* 8 is a hack. For accuracy, it should be 588. */
552 for (i = 0; i < 8; i++)
554 abs_lev_max = MAX_VALUE(abs_lev_max, abs(read_buf[i * 2 + abs_lev_chselect]));
556 abs_lev_max = MIN_VALUE(abs_lev_max, 32767);
557 abs_lev_max |= abs_lev_chselect << 15;
559 if (cdr.subq.Absolute[2] & 0x10) {
560 cdr.Result[3] = cdr.subq.Relative[0];
561 cdr.Result[4] = cdr.subq.Relative[1] | 0x80;
562 cdr.Result[5] = cdr.subq.Relative[2];
565 cdr.Result[3] = cdr.subq.Absolute[0];
566 cdr.Result[4] = cdr.subq.Absolute[1];
567 cdr.Result[5] = cdr.subq.Absolute[2];
569 cdr.Result[6] = abs_lev_max >> 0;
570 cdr.Result[7] = abs_lev_max >> 8;
572 setIrq(DataReady, 0x1001);
579 static int cdrSeekTime(unsigned char *target)
581 int diff = msf2sec(cdr.SetSectorPlay) - msf2sec(target);
582 int seekTime = abs(diff) * (cdReadTime / 2000);
583 int cyclesSinceRS = psxRegs.cycle - cdr.LastReadSeekCycles;
584 seekTime = MAX_VALUE(seekTime, 20000);
586 // need this stupidly long penalty or else Spyro2 intro desyncs
587 // note: if misapplied this breaks MGS cutscenes among other things
588 if (cdr.DriveState == DRIVESTATE_PAUSED && cyclesSinceRS > cdReadTime * 50)
589 seekTime += cdReadTime * 25;
590 // Transformers Beast Wars Transmetals does Setloc(x),SeekL,Setloc(x),ReadN
591 // and then wants some slack time
592 else if (cdr.DriveState == DRIVESTATE_PAUSED || cyclesSinceRS < cdReadTime *3/2)
593 seekTime += cdReadTime;
595 seekTime = MIN_VALUE(seekTime, PSXCLK * 2 / 3);
596 CDR_LOG("seek: %.2f %.2f (%.2f) st %d\n", (float)seekTime / PSXCLK,
597 (float)seekTime / cdReadTime, (float)cyclesSinceRS / cdReadTime,
602 static u32 cdrAlignTimingHack(u32 cycles)
605 * timing hack for T'ai Fu - Wrath of the Tiger:
606 * The game has a bug where it issues some cdc commands from a low priority
607 * vint handler, however there is a higher priority default bios handler
608 * that acks the vint irq and returns, so game's handler is not reached
609 * (see bios irq handler chains at e004 and the game's irq handling func
610 * at 80036810). For the game to work, vint has to arrive after the bios
611 * vint handler rejects some other irq (of which only cd and rcnt2 are
612 * active), but before the game's handler loop reads I_STAT. The time
613 * window for this is quite small (~1k cycles of so). Apparently this
614 * somehow happens naturally on the real hardware.
616 * Note: always enforcing this breaks other games like Crash PAL version
617 * (inputs get dropped because bios handler doesn't see interrupts).
620 if (psxRegs.cycle - rcnts[3].cycleStart > 250000)
622 vint_rel = rcnts[3].cycleStart + 63000 - psxRegs.cycle;
623 vint_rel += PSXCLK / 60;
624 while ((s32)(vint_rel - cycles) < 0)
625 vint_rel += PSXCLK / 60;
629 static void cdrUpdateTransferBuf(const u8 *buf);
630 static void cdrReadInterrupt(void);
631 static void cdrPrepCdda(s16 *buf, int samples);
633 static void msfiAdd(u8 *msfi, u32 count)
647 static void msfiSub(u8 *msfi, u32 count)
651 if ((s8)msfi[2] < 0) {
654 if ((s8)msfi[1] < 0) {
661 void cdrPlayReadInterrupt(void)
663 int hit = CDR_prefetch(cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2]);
664 if (!hit && cdr.PhysCdPropagations++ < 222) {
665 // this propagates real cdrom delays to the emulated game
666 CDRPLAYREAD_INT(cdReadTime / 2, 0);
669 cdr.PhysCdPropagations = 0;
671 cdr.LastReadSeekCycles = psxRegs.cycle;
678 if (!cdr.Play) return;
680 CDR_LOG("CDDA - %02d:%02d:%02d m %02x\n",
681 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2], cdr.Mode);
683 cdr.DriveState = DRIVESTATE_PLAY_READ;
684 SetPlaySeekRead(cdr.StatP, STATUS_PLAY);
685 if (memcmp(cdr.SetSectorPlay, cdr.SetSectorEnd, 3) == 0) {
686 CDR_LOG_I("end stop\n");
688 SetPlaySeekRead(cdr.StatP, 0);
689 cdr.TrackChanged = TRUE;
690 cdr.DriveState = DRIVESTATE_PAUSED;
693 CDR_readCDDA(cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2], (u8 *)read_buf);
696 if (!cdr.IrqStat && (cdr.Mode & (MODE_AUTOPAUSE|MODE_REPORT)))
697 cdrPlayInterrupt_Autopause();
699 if (cdr.Play && !Config.Cdda) {
700 cdrPrepCdda(read_buf, CD_FRAMESIZE_RAW / 4);
701 SPU_playCDDAchannel(read_buf, CD_FRAMESIZE_RAW, psxRegs.cycle, 0);
704 msfiAdd(cdr.SetSectorPlay, 1);
705 CDR_prefetch(cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2]);
707 // update for CdlGetlocP/autopause
708 generate_subq(cdr.SetSectorPlay);
710 CDRPLAYREAD_INT(cdReadTime, 0);
713 static void softReset(void)
715 CDR_getStatus(&stat);
716 if (stat.Status & STATUS_SHELLOPEN) {
717 cdr.DriveState = DRIVESTATE_LID_OPEN;
718 cdr.StatP = STATUS_SHELLOPEN;
720 else if (CdromId[0] == '\0') {
721 cdr.DriveState = DRIVESTATE_STOPPED;
725 cdr.DriveState = DRIVESTATE_STANDBY;
726 cdr.StatP = STATUS_ROTATING;
729 cdr.FifoOffset = DATA_SIZE; // fifo empty
730 cdr.LocL[0] = LOCL_INVALID;
731 cdr.Mode = MODE_SIZE_2340;
733 SPU_setCDvol(cdr.AttenuatorLeftToLeft, cdr.AttenuatorLeftToRight,
734 cdr.AttenuatorRightToLeft, cdr.AttenuatorRightToRight, psxRegs.cycle);
737 #define CMD_PART2 0x100
738 #define CMD_WHILE_NOT_READY 0x200
740 void cdrInterrupt(void) {
741 int start_rotating = 0;
743 u32 cycles, seekTime = 0;
744 u32 second_resp_time = 0;
750 u8 IrqStat = Acknowledge;
756 CDR_LOG_I("cmd %02x with irqstat %x\n",
757 cdr.CmdInProgress, cdr.IrqStat);
760 if (cdr.Irq1Pending) {
761 // hand out the "newest" sector, according to nocash
762 cdrUpdateTransferBuf(CDR_getBuffer());
763 CDR_LOG_I("%x:%02x:%02x loaded on ack, cmd=%02x res=%02x\n",
764 cdr.Transfer[0], cdr.Transfer[1], cdr.Transfer[2],
765 cdr.CmdInProgress, cdr.Irq1Pending);
767 cdr.Result[0] = cdr.Irq1Pending;
769 setIrq((cdr.Irq1Pending & STATUS_ERROR) ? DiskError : DataReady, 0x1003);
775 cdr.Result[0] = cdr.StatP;
777 Cmd = cdr.CmdInProgress;
778 cdr.CmdInProgress = 0;
787 switch (cdr.DriveState) {
788 case DRIVESTATE_PREPARE_CD:
790 // Syphon filter 2 expects commands to work shortly after it sees
791 // STATUS_ROTATING, so give up trying to emulate the startup seq
792 cdr.DriveState = DRIVESTATE_STANDBY;
793 cdr.StatP &= ~STATUS_SEEK;
794 psxRegs.interrupt &= ~(1 << PSXINT_CDRLID);
798 case DRIVESTATE_LID_OPEN:
799 case DRIVESTATE_RESCAN_CD:
800 // no disk or busy with the initial scan, allowed cmds are limited
801 not_ready = CMD_WHILE_NOT_READY;
805 switch (Cmd | not_ready) {
807 case CdlNop + CMD_WHILE_NOT_READY:
808 if (cdr.DriveState != DRIVESTATE_LID_OPEN)
809 cdr.StatP &= ~STATUS_SHELLOPEN;
813 // case CdlSetloc + CMD_WHILE_NOT_READY: // or is it?
814 CDR_LOG("CDROM setloc command (%02X, %02X, %02X)\n", cdr.Param[0], cdr.Param[1], cdr.Param[2]);
816 // MM must be BCD, SS must be BCD and <0x60, FF must be BCD and <0x75
817 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))
819 CDR_LOG_I("Invalid/out of range seek to %02X:%02X:%02X\n", cdr.Param[0], cdr.Param[1], cdr.Param[2]);
820 if (++cdr.errorRetryhack > 100)
822 error = ERROR_BAD_ARGNUM;
827 for (i = 0; i < 3; i++)
828 set_loc[i] = btoi(cdr.Param[i]);
829 memcpy(cdr.SetSector, set_loc, 3);
830 cdr.SetSector[3] = 0;
831 cdr.SetlocPending = 1;
832 cdr.errorRetryhack = 0;
841 cdr.FastBackward = 0;
845 // - Pause player, hit Track 01/02/../xx (Setloc issued!!)
847 if (ParamC != 0 && cdr.Param[0] != 0) {
848 int track = btoi( cdr.Param[0] );
850 if (track <= cdr.ResultTN[1])
851 cdr.CurTrack = track;
853 CDR_LOG("PLAY track %d\n", cdr.CurTrack);
855 if (CDR_getTD((u8)cdr.CurTrack, cdr.ResultTD) != -1) {
856 for (i = 0; i < 3; i++)
857 set_loc[i] = cdr.ResultTD[2 - i];
858 seekTime = cdrSeekTime(set_loc);
859 memcpy(cdr.SetSectorPlay, set_loc, 3);
862 else if (cdr.SetlocPending) {
863 seekTime = cdrSeekTime(cdr.SetSector);
864 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
867 CDR_LOG("PLAY Resume @ %d:%d:%d\n",
868 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2]);
870 cdr.SetlocPending = 0;
873 Rayman: detect track changes
876 Twisted Metal 2: skip PREGAP + starting accurate SubQ
877 - plays tracks without retry play
879 Wild 9: skip PREGAP + starting accurate SubQ
880 - plays tracks without retry play
882 Find_CurTrack(cdr.SetSectorPlay);
883 generate_subq(cdr.SetSectorPlay);
884 cdr.LocL[0] = LOCL_INVALID;
885 cdr.SubqForwardSectors = 1;
886 cdr.TrackChanged = FALSE;
887 cdr.FileChannelSelected = 0;
889 cdr.ReportDelay = 60;
893 CDR_play(cdr.SetSectorPlay);
895 SetPlaySeekRead(cdr.StatP, STATUS_SEEK | STATUS_ROTATING);
897 // BIOS player - set flag again
899 cdr.DriveState = DRIVESTATE_PLAY_READ;
901 CDRPLAYREAD_INT(cdReadTime + seekTime, 1);
906 // TODO: error 80 if stopped
909 // GameShark CD Player: Calls 2x + Play 2x
911 cdr.FastBackward = 0;
917 // GameShark CD Player: Calls 2x + Play 2x
918 cdr.FastBackward = 1;
923 if (cdr.DriveState != DRIVESTATE_STOPPED) {
924 error = ERROR_BAD_ARGNUM;
927 cdr.DriveState = DRIVESTATE_STANDBY;
928 second_resp_time = cdReadTime * 125 / 2;
932 case CdlStandby + CMD_PART2:
938 // grab time for current track
939 CDR_getTD((u8)(cdr.CurTrack), cdr.ResultTD);
941 cdr.SetSectorPlay[0] = cdr.ResultTD[2];
942 cdr.SetSectorPlay[1] = cdr.ResultTD[1];
943 cdr.SetSectorPlay[2] = cdr.ResultTD[0];
948 SetPlaySeekRead(cdr.StatP, 0);
949 cdr.StatP &= ~STATUS_ROTATING;
950 cdr.LocL[0] = LOCL_INVALID;
952 second_resp_time = 0x800;
953 if (cdr.DriveState != DRIVESTATE_STOPPED)
954 second_resp_time = cdReadTime * 30 / 2;
956 cdr.DriveState = DRIVESTATE_STOPPED;
959 case CdlStop + CMD_PART2:
964 if (cdr.AdpcmActive) {
967 SPU_playADPCMchannel(&cdr.Xa, psxRegs.cycle, 1); // flush adpcm
972 // how the drive maintains the position while paused is quite
973 // complicated, this is the minimum to make "Bedlam" happy
974 msfiSub(cdr.SetSectorPlay, MIN_VALUE(cdr.sectorsRead, 4));
978 Gundam Battle Assault 2: much slower (*)
979 - Fixes boot, gameplay
981 Hokuto no Ken 2: slower
982 - Fixes intro + subtitles
984 InuYasha - Feudal Fairy Tale: slower
987 /* Gameblabla - Tightening the timings (as taken from Duckstation).
988 * The timings from Duckstation are based upon hardware tests.
989 * Mednafen's timing don't work for Gundam Battle Assault 2 in PAL/50hz mode,
990 * seems to be timing sensitive as it can depend on the CPU's clock speed.
992 if (!(cdr.StatP & (STATUS_PLAY | STATUS_READ)))
994 second_resp_time = 7000;
998 second_resp_time = (((cdr.Mode & MODE_SPEED) ? 1 : 2) * 1097107);
1000 SetPlaySeekRead(cdr.StatP, 0);
1001 DriveStateOld = cdr.DriveState;
1002 cdr.DriveState = DRIVESTATE_PAUSED;
1003 if (DriveStateOld == DRIVESTATE_SEEK) {
1004 // According to Duckstation this fails, but the
1005 // exact conditions and effects are not clear.
1006 // Moto Racer World Tour seems to rely on this.
1007 // For now assume pause works anyway, just errors out.
1008 error = ERROR_NOTREADY;
1013 case CdlPause + CMD_PART2:
1018 case CdlReset + CMD_WHILE_NOT_READY:
1019 // note: nocash and Duckstation calls this 'Init', but
1020 // the official SDK calls it 'Reset', and so do we
1024 second_resp_time = not_ready ? 70000 : 4100000;
1028 case CdlReset + CMD_PART2:
1029 case CdlReset + CMD_PART2 + CMD_WHILE_NOT_READY:
1035 SPU_setCDvol(0, 0, 0, 0, psxRegs.cycle);
1040 SPU_setCDvol(cdr.AttenuatorLeftToLeft, cdr.AttenuatorLeftToRight,
1041 cdr.AttenuatorRightToLeft, cdr.AttenuatorRightToRight, psxRegs.cycle);
1045 cdr.FilterFile = cdr.Param[0];
1046 cdr.FilterChannel = cdr.Param[1];
1047 cdr.FileChannelSelected = 0;
1051 case CdlSetmode + CMD_WHILE_NOT_READY:
1052 CDR_LOG("cdrWrite1() Log: Setmode %x\n", cdr.Param[0]);
1053 cdr.Mode = cdr.Param[0];
1057 case CdlGetparam + CMD_WHILE_NOT_READY:
1058 /* Gameblabla : According to mednafen, Result size should be 5 and done this way. */
1060 cdr.Result[1] = cdr.Mode;
1062 cdr.Result[3] = cdr.FilterFile;
1063 cdr.Result[4] = cdr.FilterChannel;
1067 if (cdr.LocL[0] == LOCL_INVALID) {
1072 memcpy(cdr.Result, cdr.LocL, 8);
1077 memcpy(&cdr.Result, &cdr.subq, 8);
1080 case CdlReadT: // SetSession?
1082 second_resp_time = cdReadTime * 290 / 4;
1086 case CdlReadT + CMD_PART2:
1091 if (CDR_getTN(cdr.ResultTN) == -1) {
1095 cdr.Result[1] = itob(cdr.ResultTN[0]);
1096 cdr.Result[2] = itob(cdr.ResultTN[1]);
1100 cdr.Track = btoi(cdr.Param[0]);
1101 if (CDR_getTD(cdr.Track, cdr.ResultTD) == -1) {
1102 error = ERROR_BAD_ARGVAL;
1106 cdr.Result[1] = itob(cdr.ResultTD[2]);
1107 cdr.Result[2] = itob(cdr.ResultTD[1]);
1109 //cdr.Result[3] = itob(cdr.ResultTD[0]);
1116 SetPlaySeekRead(cdr.StatP, STATUS_SEEK | STATUS_ROTATING);
1118 seekTime = cdrSeekTime(cdr.SetSector);
1119 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
1120 cdr.DriveState = DRIVESTATE_SEEK;
1121 CDR_prefetch(cdr.SetSectorPlay[0], cdr.SetSectorPlay[1],
1122 cdr.SetSectorPlay[2]);
1124 Crusaders of Might and Magic = 0.5x-4x
1125 - fix cutscene speech start
1127 Eggs of Steel = 2x-?
1131 - fix cutscene speech
1136 second_resp_time = cdReadTime + seekTime;
1140 case CdlSeekL + CMD_PART2:
1141 case CdlSeekP + CMD_PART2:
1142 SetPlaySeekRead(cdr.StatP, 0);
1143 cdr.Result[0] = cdr.StatP;
1146 Find_CurTrack(cdr.SetSectorPlay);
1147 read_ok = ReadTrack(cdr.SetSectorPlay);
1148 if (read_ok && (buf = CDR_getBuffer()))
1149 memcpy(cdr.LocL, buf, 8);
1150 UpdateSubq(cdr.SetSectorPlay);
1151 cdr.DriveState = DRIVESTATE_STANDBY;
1152 cdr.TrackChanged = FALSE;
1153 cdr.LastReadSeekCycles = psxRegs.cycle;
1157 case CdlTest + CMD_WHILE_NOT_READY:
1158 switch (cdr.Param[0]) {
1159 case 0x20: // System Controller ROM Version
1161 memcpy(cdr.Result, Test20, 4);
1165 memcpy(cdr.Result, Test22, 4);
1167 case 0x23: case 0x24:
1169 memcpy(cdr.Result, Test23, 4);
1175 second_resp_time = 20480;
1178 case CdlID + CMD_PART2:
1180 cdr.Result[0] = cdr.StatP;
1185 // 0x10 - audio | 0x40 - disk missing | 0x80 - unlicensed
1186 if (CDR_getStatus(&stat) == -1 || stat.Type == 0 || stat.Type == 0xff) {
1187 cdr.Result[1] = 0xc0;
1191 cdr.Result[1] |= 0x10;
1192 if (CdromId[0] == '\0')
1193 cdr.Result[1] |= 0x80;
1195 cdr.Result[0] |= (cdr.Result[1] >> 4) & 0x08;
1196 CDR_LOG_I("CdlID: %02x %02x %02x %02x\n", cdr.Result[0],
1197 cdr.Result[1], cdr.Result[2], cdr.Result[3]);
1199 /* This adds the string "PCSX" in Playstation bios boot screen */
1200 memcpy((char *)&cdr.Result[4], "PCSX", 4);
1205 case CdlInit + CMD_WHILE_NOT_READY:
1208 SetPlaySeekRead(cdr.StatP, 0);
1209 // yes, it really sets STATUS_SHELLOPEN
1210 cdr.StatP |= STATUS_SHELLOPEN;
1211 cdr.DriveState = DRIVESTATE_RESCAN_CD;
1212 set_event(PSXINT_CDRLID, 20480);
1217 case CdlGetQ + CMD_WHILE_NOT_READY:
1221 case CdlReadToc + CMD_WHILE_NOT_READY:
1222 cdr.LocL[0] = LOCL_INVALID;
1223 second_resp_time = cdReadTime * 180 / 4;
1227 case CdlReadToc + CMD_PART2:
1228 case CdlReadToc + CMD_PART2 + CMD_WHILE_NOT_READY:
1234 if (cdr.Reading && !cdr.SetlocPending)
1237 Find_CurTrack(cdr.SetlocPending ? cdr.SetSector : cdr.SetSectorPlay);
1239 if ((cdr.Mode & MODE_CDDA) && cdr.CurTrack > 1)
1240 // Read* acts as play for cdda tracks in cdda mode
1244 if (cdr.SetlocPending) {
1245 seekTime = cdrSeekTime(cdr.SetSector);
1246 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
1247 cdr.SetlocPending = 0;
1250 cdr.FileChannelSelected = 0;
1251 cdr.AdpcmActive = 0;
1253 // Fighting Force 2 - update subq time immediately
1255 UpdateSubq(cdr.SetSectorPlay);
1256 cdr.LocL[0] = LOCL_INVALID;
1257 cdr.SubqForwardSectors = 1;
1258 cdr.sectorsRead = 0;
1259 cdr.DriveState = DRIVESTATE_SEEK;
1260 CDR_prefetch(cdr.SetSectorPlay[0], cdr.SetSectorPlay[1],
1261 cdr.SetSectorPlay[2]);
1263 cycles = (cdr.Mode & MODE_SPEED) ? cdReadTime : cdReadTime * 2;
1265 if (Config.hacks.cdr_read_timing)
1266 cycles = cdrAlignTimingHack(cycles);
1267 CDRPLAYREAD_INT(cycles, 1);
1269 SetPlaySeekRead(cdr.StatP, STATUS_SEEK);
1275 error = ERROR_INVALIDCMD;
1280 cdr.Result[0] = cdr.StatP | STATUS_ERROR;
1281 cdr.Result[1] = not_ready ? ERROR_NOTREADY : error;
1282 IrqStat = DiskError;
1283 CDR_LOG_I("cmd %02x error %02x\n", Cmd, cdr.Result[1]);
1287 if (cdr.DriveState == DRIVESTATE_STOPPED && start_rotating) {
1288 cdr.DriveState = DRIVESTATE_STANDBY;
1289 cdr.StatP |= STATUS_ROTATING;
1292 if (second_resp_time) {
1293 cdr.CmdInProgress = Cmd | 0x100;
1294 set_event(PSXINT_CDR, second_resp_time);
1296 else if (cdr.Cmd && cdr.Cmd != (Cmd & 0xff)) {
1297 cdr.CmdInProgress = cdr.Cmd;
1298 CDR_LOG_I("cmd %02x came before %02x finished\n", cdr.Cmd, Cmd);
1301 setIrq(IrqStat, Cmd);
1305 #define ssat32_to_16(v) \
1306 asm("ssat %0,#16,%1" : "=r" (v) : "r" (v))
1308 #define ssat32_to_16(v) do { \
1309 if (v < -32768) v = -32768; \
1310 else if (v > 32767) v = 32767; \
1314 static void cdrPrepCdda(s16 *buf, int samples)
1316 #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
1318 for (i = 0; i < samples; i++) {
1319 buf[i * 2 + 0] = SWAP16(buf[i * 2 + 0]);
1320 buf[i * 2 + 1] = SWAP16(buf[i * 2 + 1]);
1325 static void cdrReadInterruptSetResult(unsigned char result)
1328 CDR_LOG_I("%d:%02d:%02d irq miss, cmd=%02x irqstat=%02x\n",
1329 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2],
1330 cdr.CmdInProgress, cdr.IrqStat);
1331 cdr.Irq1Pending = result;
1335 cdr.Result[0] = result;
1336 setIrq((result & STATUS_ERROR) ? DiskError : DataReady, 0x1004);
1339 static void cdrUpdateTransferBuf(const u8 *buf)
1343 memcpy(cdr.Transfer, buf, DATA_SIZE);
1344 CheckPPFCache(cdr.Transfer, cdr.Prev[0], cdr.Prev[1], cdr.Prev[2]);
1345 CDR_LOG("cdr.Transfer %02x:%02x:%02x\n",
1346 cdr.Transfer[0], cdr.Transfer[1], cdr.Transfer[2]);
1347 if (cdr.FifoOffset < 2048 + 12)
1348 CDR_LOG("FifoOffset(1) %d/%d\n", cdr.FifoOffset, cdr.FifoSize);
1351 static void cdrReadInterrupt(void)
1353 const struct { u8 file, chan, mode, coding; } *subhdr;
1354 const u8 *buf = NULL;
1355 int deliver_data = 1;
1360 memcpy(subqPos, cdr.SetSectorPlay, sizeof(subqPos));
1361 msfiAdd(subqPos, cdr.SubqForwardSectors);
1362 UpdateSubq(subqPos);
1363 if (cdr.SubqForwardSectors < SUBQ_FORWARD_SECTORS) {
1364 cdr.SubqForwardSectors++;
1365 CDRPLAYREAD_INT((cdr.Mode & MODE_SPEED) ? (cdReadTime / 2) : cdReadTime, 0);
1369 // note: CdlGetlocL should work as soon as STATUS_READ is indicated
1370 SetPlaySeekRead(cdr.StatP, STATUS_READ | STATUS_ROTATING);
1371 cdr.DriveState = DRIVESTATE_PLAY_READ;
1374 read_ok = ReadTrack(cdr.SetSectorPlay);
1376 buf = CDR_getBuffer();
1381 CDR_LOG_I("cdrReadInterrupt() Log: err\n");
1382 cdrReadInterruptSetResult(cdr.StatP | STATUS_ERROR);
1383 cdr.DriveState = DRIVESTATE_PAUSED; // ?
1386 memcpy(cdr.LocL, buf, 8);
1388 if (!cdr.IrqStat && !cdr.Irq1Pending)
1389 cdrUpdateTransferBuf(buf);
1391 subhdr = (void *)(buf + 4);
1393 // try to process as adpcm
1394 if (!(cdr.Mode & MODE_STRSND))
1396 if (buf[3] != 2 || (subhdr->mode & 0x44) != 0x44) // or 0x64?
1398 CDR_LOG("f=%d m=%d %d,%3d | %d,%2d | %d,%2d\n", !!(cdr.Mode & MODE_SF), cdr.Muted,
1399 subhdr->file, subhdr->chan, cdr.CurFile, cdr.CurChannel, cdr.FilterFile, cdr.FilterChannel);
1400 if ((cdr.Mode & MODE_SF) && (subhdr->file != cdr.FilterFile || subhdr->chan != cdr.FilterChannel))
1402 if (subhdr->chan & 0xe0) { // ?
1403 if (subhdr->chan != 0xff)
1404 log_unhandled("adpcm %d:%d\n", subhdr->file, subhdr->chan);
1407 if (!cdr.FileChannelSelected) {
1408 cdr.CurFile = subhdr->file;
1409 cdr.CurChannel = subhdr->chan;
1410 cdr.FileChannelSelected = 1;
1412 else if (subhdr->file != cdr.CurFile || subhdr->chan != cdr.CurChannel)
1415 // accepted as adpcm
1420 is_start = !cdr.AdpcmActive;
1421 cdr.AdpcmActive = !xa_decode_sector(&cdr.Xa, buf + 4, is_start);
1422 if (cdr.AdpcmActive)
1423 SPU_playADPCMchannel(&cdr.Xa, psxRegs.cycle, is_start);
1426 if ((cdr.Mode & MODE_SF) && (subhdr->mode & 0x44) == 0x44) // according to nocash
1430 Croc 2: $40 - only FORM1 (*)
1431 Judge Dredd: $C8 - only FORM1 (*)
1432 Sim Theme Park - no adpcm at all (zero)
1436 cdrReadInterruptSetResult(cdr.StatP);
1438 msfiAdd(cdr.SetSectorPlay, 1);
1439 CDR_prefetch(cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2]);
1441 CDRPLAYREAD_INT((cdr.Mode & MODE_SPEED) ? (cdReadTime / 2) : cdReadTime, 0);
1447 bit 2 - adpcm active
1448 bit 5 - 1 result ready
1450 bit 7 - 1 command being processed
1453 unsigned char cdrRead0(void) {
1455 cdr.Ctrl |= cdr.AdpcmActive << 2;
1456 cdr.Ctrl |= cdr.ResultReady << 5;
1458 cdr.Ctrl |= 0x40; // data fifo not empty
1460 // What means the 0x10 and the 0x08 bits? I only saw it used by the bios
1463 CDR_LOG_IO("cdr r0.sta: %02x\n", cdr.Ctrl);
1465 return psxHu8(0x1800) = cdr.Ctrl;
1468 void cdrWrite0(unsigned char rt) {
1469 CDR_LOG_IO("cdr w0.x.idx: %02x\n", rt);
1471 cdr.Ctrl = (rt & 3) | (cdr.Ctrl & ~3);
1474 unsigned char cdrRead1(void) {
1475 if ((cdr.ResultP & 0xf) < cdr.ResultC)
1476 psxHu8(0x1801) = cdr.Result[cdr.ResultP & 0xf];
1480 if (cdr.ResultP == cdr.ResultC)
1481 cdr.ResultReady = 0;
1483 CDR_LOG_IO("cdr r1.x.rsp: %02x #%u\n", psxHu8(0x1801), cdr.ResultP - 1);
1485 return psxHu8(0x1801);
1488 void cdrWrite1(unsigned char rt) {
1489 const char *rnames[] = { "0.cmd", "1.smd", "2.smc", "3.arr" }; (void)rnames;
1490 CDR_LOG_IO("cdr w1.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1492 switch (cdr.Ctrl & 3) {
1496 cdr.AttenuatorRightToRightT = rt;
1502 #ifdef CDR_LOG_CMD_IRQ
1503 CDR_LOG_I("CD1 write: %x (%s)", rt, CmdName[rt]);
1506 SysPrintf(" Param[%d] = {", cdr.ParamC);
1507 for (i = 0; i < cdr.ParamC; i++)
1508 SysPrintf(" %x,", cdr.Param[i]);
1511 SysPrintf(" @%08x\n", psxRegs.pc);
1514 cdr.ResultReady = 0;
1517 if (!cdr.CmdInProgress) {
1518 cdr.CmdInProgress = rt;
1519 // should be something like 12k + controller delays
1520 set_event(PSXINT_CDR, 5000);
1523 CDR_LOG_I("cmd while busy: %02x, prev %02x, busy %02x\n",
1524 rt, cdr.Cmd, cdr.CmdInProgress);
1525 if (cdr.CmdInProgress < 0x100) // no pending 2nd response
1526 cdr.CmdInProgress = rt;
1532 unsigned char cdrRead2(void) {
1533 unsigned char ret = cdr.Transfer[0x920];
1535 if (cdr.FifoOffset < cdr.FifoSize)
1536 ret = cdr.Transfer[cdr.FifoOffset++];
1538 CDR_LOG_I("read empty fifo (%d)\n", cdr.FifoSize);
1540 CDR_LOG_IO("cdr r2.x.dat: %02x\n", ret);
1544 void cdrWrite2(unsigned char rt) {
1545 const char *rnames[] = { "0.prm", "1.ien", "2.all", "3.arl" }; (void)rnames;
1546 CDR_LOG_IO("cdr w2.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1548 switch (cdr.Ctrl & 3) {
1550 if (cdr.ParamC < 8) // FIXME: size and wrapping
1551 cdr.Param[cdr.ParamC++] = rt;
1555 setIrq(cdr.IrqStat, 0x1005);
1558 cdr.AttenuatorLeftToLeftT = rt;
1561 cdr.AttenuatorRightToLeftT = rt;
1566 unsigned char cdrRead3(void) {
1568 psxHu8(0x1803) = cdr.IrqStat | 0xE0;
1570 psxHu8(0x1803) = cdr.IrqMask | 0xE0;
1572 CDR_LOG_IO("cdr r3.%d.%s: %02x\n", cdr.Ctrl & 3,
1573 (cdr.Ctrl & 1) ? "ifl" : "ien", psxHu8(0x1803));
1574 return psxHu8(0x1803);
1577 void cdrWrite3(unsigned char rt) {
1578 const char *rnames[] = { "0.req", "1.ifl", "2.alr", "3.ava" }; (void)rnames;
1580 CDR_LOG_IO("cdr w3.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1582 switch (cdr.Ctrl & 3) {
1586 if (cdr.IrqStat & rt) {
1587 u32 nextCycle = psxRegs.intCycle[PSXINT_CDR].sCycle
1588 + psxRegs.intCycle[PSXINT_CDR].cycle;
1589 int pending = psxRegs.interrupt & (1 << PSXINT_CDR);
1590 #ifdef CDR_LOG_CMD_IRQ
1591 CDR_LOG_I("ack %02x (w=%02x p=%d,%x,%x,%d)\n",
1592 cdr.IrqStat & rt, rt, !!pending, cdr.CmdInProgress,
1593 cdr.Irq1Pending, nextCycle - psxRegs.cycle);
1595 // note: Croc, Shadow Tower (more) vs Discworld Noir (<993)
1596 if (!pending && (cdr.CmdInProgress || cdr.Irq1Pending))
1599 if (cdr.CmdInProgress) {
1600 c = 2048 - (psxRegs.cycle - nextCycle);
1601 c = MAX_VALUE(c, 512);
1603 set_event(PSXINT_CDR, c);
1612 cdr.AttenuatorLeftToRightT = rt;
1616 log_unhandled("Mute ADPCM?\n");
1618 ll = cdr.AttenuatorLeftToLeftT; lr = cdr.AttenuatorLeftToRightT;
1619 rl = cdr.AttenuatorRightToLeftT; rr = cdr.AttenuatorRightToRightT;
1620 if (ll == cdr.AttenuatorLeftToLeft &&
1621 lr == cdr.AttenuatorLeftToRight &&
1622 rl == cdr.AttenuatorRightToLeft &&
1623 rr == cdr.AttenuatorRightToRight)
1625 cdr.AttenuatorLeftToLeft = ll; cdr.AttenuatorLeftToRight = lr;
1626 cdr.AttenuatorRightToLeft = rl; cdr.AttenuatorRightToRight = rr;
1627 CDR_LOG_I("CD-XA Volume: %02x %02x | %02x %02x\n", ll, lr, rl, rr);
1628 SPU_setCDvol(ll, lr, rl, rr, psxRegs.cycle);
1634 if ((rt & 0x80) && cdr.FifoOffset < cdr.FifoSize) {
1635 CDR_LOG("cdrom: FifoOffset(2) %d/%d\n", cdr.FifoOffset, cdr.FifoSize);
1637 else if (rt & 0x80) {
1638 switch (cdr.Mode & (MODE_SIZE_2328|MODE_SIZE_2340)) {
1639 case MODE_SIZE_2328:
1641 cdr.FifoOffset = 12;
1642 cdr.FifoSize = 2048 + 12;
1645 case MODE_SIZE_2340:
1648 cdr.FifoSize = 2340;
1652 else if (!(rt & 0xc0))
1653 cdr.FifoOffset = DATA_SIZE; // fifo empty
1656 void psxDma3(u32 madr, u32 bcr, u32 chcr) {
1657 u32 cdsize, max_words;
1662 CDR_LOG_I("psxDma3() Log: *** DMA 3 *** %x addr = %x size = %x", chcr, madr, bcr);
1663 if (cdr.FifoOffset == 0) {
1665 SysPrintf(" %02x:%02x:%02x", ptr[0], ptr[1], ptr[2]);
1670 switch (chcr & 0x71000000) {
1673 ptr = getDmaRam(madr, &max_words);
1674 if (ptr == INVALID_PTR) {
1675 CDR_LOG_I("psxDma3() Log: *** DMA 3 *** NULL Pointer!\n");
1679 cdsize = (((bcr - 1) & 0xffff) + 1) * 4;
1682 GS CDX: Enhancement CD crash
1685 - Spams DMA3 and gets buffer overrun
1687 size = DATA_SIZE - cdr.FifoOffset;
1690 if (size > max_words * 4)
1691 size = max_words * 4;
1694 memcpy(ptr, cdr.Transfer + cdr.FifoOffset, size);
1695 cdr.FifoOffset += size;
1697 if (size < cdsize) {
1698 CDR_LOG_I("cdrom: dma3 %d/%d\n", size, cdsize);
1699 memset(ptr + size, cdr.Transfer[0x920], cdsize - size);
1701 psxCpu->Clear(madr, cdsize / 4);
1703 set_event(PSXINT_CDRDMA, (cdsize / 4) * 24);
1705 HW_DMA3_CHCR &= SWAPu32(~0x10000000);
1707 HW_DMA3_MADR = SWAPu32(madr + cdsize);
1708 HW_DMA3_BCR &= SWAPu32(0xffff0000);
1712 psxRegs.cycle += (cdsize/4) * 24 - 20;
1717 CDR_LOG_I("psxDma3() Log: Unknown cddma %x\n", chcr);
1721 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1725 void cdrDmaInterrupt(void)
1727 if (HW_DMA3_CHCR & SWAP32(0x01000000))
1729 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1734 static void getCdInfo(void)
1738 CDR_getTN(cdr.ResultTN);
1739 CDR_getTD(0, cdr.SetSectorEnd);
1740 tmp = cdr.SetSectorEnd[0];
1741 cdr.SetSectorEnd[0] = cdr.SetSectorEnd[2];
1742 cdr.SetSectorEnd[2] = tmp;
1746 memset(&cdr, 0, sizeof(cdr));
1749 cdr.FilterChannel = 0;
1751 cdr.IrqStat = NoIntr;
1753 // BIOS player - default values
1754 cdr.AttenuatorLeftToLeft = 0x80;
1755 cdr.AttenuatorLeftToRight = 0x00;
1756 cdr.AttenuatorRightToLeft = 0x00;
1757 cdr.AttenuatorRightToRight = 0x80;
1763 int cdrFreeze(void *f, int Mode) {
1767 if (Mode == 0 && !Config.Cdda)
1770 cdr.freeze_ver = 0x63647202;
1771 gzfreeze(&cdr, sizeof(cdr));
1774 cdr.ParamP = cdr.ParamC;
1775 tmp = cdr.FifoOffset;
1778 gzfreeze(&tmp, sizeof(tmp));
1781 u8 ll = 0, lr = 0, rl = 0, rr = 0;
1784 cdr.FifoOffset = tmp < DATA_SIZE ? tmp : DATA_SIZE;
1785 cdr.FifoSize = (cdr.Mode & MODE_SIZE_2340) ? 2340 : 2048 + 12;
1786 if (cdr.SubqForwardSectors > SUBQ_FORWARD_SECTORS)
1787 cdr.SubqForwardSectors = SUBQ_FORWARD_SECTORS;
1789 // read right sub data
1790 tmpp[0] = btoi(cdr.Prev[0]);
1791 tmpp[1] = btoi(cdr.Prev[1]);
1792 tmpp[2] = btoi(cdr.Prev[2]);
1797 if (cdr.freeze_ver < 0x63647202)
1798 memcpy(cdr.SetSectorPlay, cdr.SetSector, 3);
1800 Find_CurTrack(cdr.SetSectorPlay);
1802 CDR_play(cdr.SetSectorPlay);
1805 ll = cdr.AttenuatorLeftToLeft, lr = cdr.AttenuatorLeftToLeft,
1806 rl = cdr.AttenuatorRightToLeft, rr = cdr.AttenuatorRightToRight;
1807 SPU_setCDvol(ll, lr, rl, rr, psxRegs.cycle);
1813 void LidInterrupt(void) {
1815 cdrLidSeekInterrupt();