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 cdr_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(&cdr_stat) == -1)
333 if (cdr_stat.Status & STATUS_SHELLOPEN)
335 memset(cdr.Prev, 0xff, sizeof(cdr.Prev));
336 cdr.DriveState = DRIVESTATE_LID_OPEN;
337 set_event(PSXINT_CDRLID, 0x800);
341 case DRIVESTATE_LID_OPEN:
342 if (CDR_getStatus(&cdr_stat) == -1)
343 cdr_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 (!(cdr_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 boolean canDoTurbo(void)
581 u32 c = psxRegs.cycle;
582 return Config.TurboCD && !cdr.RetryDetected && !cdr.AdpcmActive
583 //&& c - psxRegs.intCycle[PSXINT_SPUDMA].sCycle > (u32)cdReadTime * 2
584 && c - psxRegs.intCycle[PSXINT_MDECOUTDMA].sCycle > (u32)cdReadTime * 16;
587 static int cdrSeekTime(unsigned char *target)
589 int diff = msf2sec(cdr.SetSectorPlay) - msf2sec(target);
590 int seekTime = abs(diff) * (cdReadTime / 2000);
591 int cyclesSinceRS = psxRegs.cycle - cdr.LastReadSeekCycles;
592 seekTime = MAX_VALUE(seekTime, 20000);
594 // Transformers Beast Wars Transmetals does Setloc(x),SeekL,Setloc(x),ReadN
595 // and then wants some slack time
596 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 di %d\n", (float)seekTime / PSXCLK,
601 (float)seekTime / cdReadTime, (float)cyclesSinceRS / cdReadTime,
602 cdr.DriveState, diff);
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 static int msfiEq(const u8 *a, const u8 *b)
667 return a[0] == b[0] && a[1] == b[1] && a[2] == b[2];
670 void cdrPlayReadInterrupt(void)
672 int hit = CDR_prefetch(cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2]);
673 if (!hit && cdr.PhysCdPropagations++ < 222) {
674 // this propagates real cdrom delays to the emulated game
675 CDRPLAYREAD_INT(cdReadTime / 2, 0);
678 cdr.PhysCdPropagations = 0;
680 cdr.LastReadSeekCycles = psxRegs.cycle;
687 if (!cdr.Play) return;
689 CDR_LOG("CDDA - %02d:%02d:%02d m %02x\n",
690 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2], cdr.Mode);
692 cdr.DriveState = DRIVESTATE_PLAY_READ;
693 SetPlaySeekRead(cdr.StatP, STATUS_PLAY);
694 if (memcmp(cdr.SetSectorPlay, cdr.SetSectorEnd, 3) == 0) {
695 CDR_LOG_I("end stop\n");
697 SetPlaySeekRead(cdr.StatP, 0);
698 cdr.TrackChanged = TRUE;
699 cdr.DriveState = DRIVESTATE_PAUSED;
702 CDR_readCDDA(cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2], (u8 *)read_buf);
705 if (!cdr.IrqStat && (cdr.Mode & (MODE_AUTOPAUSE|MODE_REPORT)))
706 cdrPlayInterrupt_Autopause();
708 if (cdr.Play && !Config.Cdda) {
709 cdrPrepCdda(read_buf, CD_FRAMESIZE_RAW / 4);
710 SPU_playCDDAchannel(read_buf, CD_FRAMESIZE_RAW, psxRegs.cycle, 0);
713 msfiAdd(cdr.SetSectorPlay, 1);
714 CDR_prefetch(cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2]);
716 // update for CdlGetlocP/autopause
717 generate_subq(cdr.SetSectorPlay);
719 CDRPLAYREAD_INT(cdReadTime, 0);
722 static void softReset(void)
724 CDR_getStatus(&cdr_stat);
725 if (cdr_stat.Status & STATUS_SHELLOPEN) {
726 cdr.DriveState = DRIVESTATE_LID_OPEN;
727 cdr.StatP = STATUS_SHELLOPEN;
729 else if (CdromId[0] == '\0') {
730 cdr.DriveState = DRIVESTATE_STOPPED;
734 cdr.DriveState = DRIVESTATE_STANDBY;
735 cdr.StatP = STATUS_ROTATING;
738 cdr.FifoOffset = DATA_SIZE; // fifo empty
739 cdr.LocL[0] = LOCL_INVALID;
740 cdr.Mode = MODE_SIZE_2340;
742 SPU_setCDvol(cdr.AttenuatorLeftToLeft, cdr.AttenuatorLeftToRight,
743 cdr.AttenuatorRightToLeft, cdr.AttenuatorRightToRight, psxRegs.cycle);
746 #define CMD_PART2 0x100
747 #define CMD_WHILE_NOT_READY 0x200
749 void cdrInterrupt(void) {
750 int start_rotating = 0;
752 u32 cycles, seekTime = 0;
753 u32 second_resp_time = 0;
759 u8 IrqStat = Acknowledge;
765 CDR_LOG_I("cmd %02x with irqstat %x\n",
766 cdr.CmdInProgress, cdr.IrqStat);
769 if (cdr.Irq1Pending) {
770 // hand out the "newest" sector, according to nocash
771 cdrUpdateTransferBuf(CDR_getBuffer());
772 CDR_LOG_I("%x:%02x:%02x loaded on ack, cmd=%02x res=%02x\n",
773 cdr.Transfer[0], cdr.Transfer[1], cdr.Transfer[2],
774 cdr.CmdInProgress, cdr.Irq1Pending);
776 cdr.Result[0] = cdr.Irq1Pending;
778 setIrq((cdr.Irq1Pending & STATUS_ERROR) ? DiskError : DataReady, 0x1003);
784 cdr.Result[0] = cdr.StatP;
786 Cmd = cdr.CmdInProgress;
787 cdr.CmdInProgress = 0;
796 switch (cdr.DriveState) {
797 case DRIVESTATE_PREPARE_CD:
799 // Syphon filter 2 expects commands to work shortly after it sees
800 // STATUS_ROTATING, so give up trying to emulate the startup seq
801 cdr.DriveState = DRIVESTATE_STANDBY;
802 cdr.StatP &= ~STATUS_SEEK;
803 psxRegs.interrupt &= ~(1 << PSXINT_CDRLID);
807 case DRIVESTATE_LID_OPEN:
808 case DRIVESTATE_RESCAN_CD:
809 // no disk or busy with the initial scan, allowed cmds are limited
810 not_ready = CMD_WHILE_NOT_READY;
814 switch (Cmd | not_ready) {
816 case CdlNop + CMD_WHILE_NOT_READY:
817 if (cdr.DriveState != DRIVESTATE_LID_OPEN)
818 cdr.StatP &= ~STATUS_SHELLOPEN;
822 // case CdlSetloc + CMD_WHILE_NOT_READY: // or is it?
823 CDR_LOG("CDROM setloc command (%02X, %02X, %02X)\n", cdr.Param[0], cdr.Param[1], cdr.Param[2]);
825 // MM must be BCD, SS must be BCD and <0x60, FF must be BCD and <0x75
826 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))
828 CDR_LOG_I("Invalid/out of range seek to %02X:%02X:%02X\n", cdr.Param[0], cdr.Param[1], cdr.Param[2]);
829 if (++cdr.errorRetryhack > 100)
831 error = ERROR_BAD_ARGNUM;
836 for (i = 0; i < 3; i++)
837 set_loc[i] = btoi(cdr.Param[i]);
838 if ((msfiEq(cdr.SetSector, set_loc)) //|| msfiEq(cdr.Param, cdr.Transfer))
839 && !cdr.SetlocPending)
842 cdr.RetryDetected = 0;
843 memcpy(cdr.SetSector, set_loc, 3);
844 cdr.SetSector[3] = 0;
845 cdr.SetlocPending = 1;
846 cdr.errorRetryhack = 0;
855 cdr.FastBackward = 0;
859 // - Pause player, hit Track 01/02/../xx (Setloc issued!!)
861 if (ParamC != 0 && cdr.Param[0] != 0) {
862 int track = btoi( cdr.Param[0] );
864 if (track <= cdr.ResultTN[1])
865 cdr.CurTrack = track;
867 CDR_LOG("PLAY track %d\n", cdr.CurTrack);
869 if (CDR_getTD((u8)cdr.CurTrack, cdr.ResultTD) != -1) {
870 for (i = 0; i < 3; i++)
871 set_loc[i] = cdr.ResultTD[2 - i];
872 seekTime = cdrSeekTime(set_loc);
873 memcpy(cdr.SetSectorPlay, set_loc, 3);
876 else if (cdr.SetlocPending) {
877 seekTime = cdrSeekTime(cdr.SetSector);
878 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
881 CDR_LOG("PLAY Resume @ %d:%d:%d\n",
882 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2]);
884 cdr.SetlocPending = 0;
887 Rayman: detect track changes
890 Twisted Metal 2: skip PREGAP + starting accurate SubQ
891 - plays tracks without retry play
893 Wild 9: skip PREGAP + starting accurate SubQ
894 - plays tracks without retry play
896 Find_CurTrack(cdr.SetSectorPlay);
897 generate_subq(cdr.SetSectorPlay);
898 cdr.LocL[0] = LOCL_INVALID;
899 cdr.SubqForwardSectors = 1;
900 cdr.TrackChanged = FALSE;
901 cdr.FileChannelSelected = 0;
903 cdr.ReportDelay = 60;
907 CDR_play(cdr.SetSectorPlay);
909 SetPlaySeekRead(cdr.StatP, STATUS_SEEK | STATUS_ROTATING);
911 // BIOS player - set flag again
913 cdr.DriveState = DRIVESTATE_PLAY_READ;
915 CDRPLAYREAD_INT(cdReadTime + seekTime, 1);
920 // TODO: error 80 if stopped
923 // GameShark CD Player: Calls 2x + Play 2x
925 cdr.FastBackward = 0;
931 // GameShark CD Player: Calls 2x + Play 2x
932 cdr.FastBackward = 1;
937 if (cdr.DriveState != DRIVESTATE_STOPPED) {
938 error = ERROR_BAD_ARGNUM;
941 cdr.DriveState = DRIVESTATE_STANDBY;
942 second_resp_time = cdReadTime * 125 / 2;
946 case CdlStandby + CMD_PART2:
952 // grab time for current track
953 CDR_getTD((u8)(cdr.CurTrack), cdr.ResultTD);
955 cdr.SetSectorPlay[0] = cdr.ResultTD[2];
956 cdr.SetSectorPlay[1] = cdr.ResultTD[1];
957 cdr.SetSectorPlay[2] = cdr.ResultTD[0];
962 SetPlaySeekRead(cdr.StatP, 0);
963 cdr.StatP &= ~STATUS_ROTATING;
964 cdr.LocL[0] = LOCL_INVALID;
966 second_resp_time = 0x800;
967 if (cdr.DriveState != DRIVESTATE_STOPPED)
968 second_resp_time = cdReadTime * 30 / 2;
970 cdr.DriveState = DRIVESTATE_STOPPED;
973 case CdlStop + CMD_PART2:
978 if (cdr.AdpcmActive) {
981 SPU_playADPCMchannel(&cdr.Xa, psxRegs.cycle, 1); // flush adpcm
986 // how the drive maintains the position while paused is quite
987 // complicated, this is the minimum to make "Bedlam" happy
988 msfiSub(cdr.SetSectorPlay, MIN_VALUE(cdr.sectorsRead, 4));
992 Gundam Battle Assault 2
994 InuYasha - Feudal Fairy Tale
995 Dance Dance Revolution Konamix
999 if (!(cdr.StatP & (STATUS_PLAY | STATUS_READ)))
1001 second_resp_time = 7000;
1005 second_resp_time = 2100011;
1006 // a hack to try to avoid weird cmd vs irq1 races causing games to retry
1007 second_resp_time += (cdr.RetryDetected & 15) * 100001;
1009 SetPlaySeekRead(cdr.StatP, 0);
1010 DriveStateOld = cdr.DriveState;
1011 cdr.DriveState = DRIVESTATE_PAUSED;
1012 if (DriveStateOld == DRIVESTATE_SEEK) {
1013 // According to Duckstation this fails, but the
1014 // exact conditions and effects are not clear.
1015 // Moto Racer World Tour seems to rely on this.
1016 // For now assume pause works anyway, just errors out.
1017 error = ERROR_NOTREADY;
1022 case CdlPause + CMD_PART2:
1027 case CdlReset + CMD_WHILE_NOT_READY:
1028 // note: nocash and Duckstation calls this 'Init', but
1029 // the official SDK calls it 'Reset', and so do we
1033 second_resp_time = not_ready ? 70000 : 4100000;
1037 case CdlReset + CMD_PART2:
1038 case CdlReset + CMD_PART2 + CMD_WHILE_NOT_READY:
1044 SPU_setCDvol(0, 0, 0, 0, psxRegs.cycle);
1049 SPU_setCDvol(cdr.AttenuatorLeftToLeft, cdr.AttenuatorLeftToRight,
1050 cdr.AttenuatorRightToLeft, cdr.AttenuatorRightToRight, psxRegs.cycle);
1054 cdr.FilterFile = cdr.Param[0];
1055 cdr.FilterChannel = cdr.Param[1];
1056 cdr.FileChannelSelected = 0;
1060 case CdlSetmode + CMD_WHILE_NOT_READY:
1061 CDR_LOG("cdrWrite1() Log: Setmode %x\n", cdr.Param[0]);
1062 cdr.Mode = cdr.Param[0];
1066 case CdlGetparam + CMD_WHILE_NOT_READY:
1067 /* Gameblabla : According to mednafen, Result size should be 5 and done this way. */
1069 cdr.Result[1] = cdr.Mode;
1071 cdr.Result[3] = cdr.FilterFile;
1072 cdr.Result[4] = cdr.FilterChannel;
1076 if (cdr.LocL[0] == LOCL_INVALID) {
1081 memcpy(cdr.Result, cdr.LocL, 8);
1086 memcpy(&cdr.Result, &cdr.subq, 8);
1089 case CdlReadT: // SetSession?
1091 second_resp_time = cdReadTime * 290 / 4;
1095 case CdlReadT + CMD_PART2:
1100 if (CDR_getTN(cdr.ResultTN) == -1) {
1104 cdr.Result[1] = itob(cdr.ResultTN[0]);
1105 cdr.Result[2] = itob(cdr.ResultTN[1]);
1109 cdr.Track = btoi(cdr.Param[0]);
1110 if (CDR_getTD(cdr.Track, cdr.ResultTD) == -1) {
1111 error = ERROR_BAD_ARGVAL;
1115 cdr.Result[1] = itob(cdr.ResultTD[2]);
1116 cdr.Result[2] = itob(cdr.ResultTD[1]);
1118 //cdr.Result[3] = itob(cdr.ResultTD[0]);
1125 SetPlaySeekRead(cdr.StatP, STATUS_SEEK | STATUS_ROTATING);
1128 seekTime = cdrSeekTime(cdr.SetSector);
1129 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
1130 cdr.DriveState = DRIVESTATE_SEEK;
1131 CDR_prefetch(cdr.SetSectorPlay[0], cdr.SetSectorPlay[1],
1132 cdr.SetSectorPlay[2]);
1134 Crusaders of Might and Magic = 0.5x-4x
1135 - fix cutscene speech start
1137 Eggs of Steel = 2x-?
1141 - fix cutscene speech
1146 second_resp_time = cdReadTime + seekTime;
1150 case CdlSeekL + CMD_PART2:
1151 case CdlSeekP + CMD_PART2:
1152 SetPlaySeekRead(cdr.StatP, 0);
1153 cdr.Result[0] = cdr.StatP;
1156 Find_CurTrack(cdr.SetSectorPlay);
1157 read_ok = ReadTrack(cdr.SetSectorPlay);
1158 if (read_ok && (buf = CDR_getBuffer()))
1159 memcpy(cdr.LocL, buf, 8);
1160 UpdateSubq(cdr.SetSectorPlay);
1161 cdr.DriveState = DRIVESTATE_STANDBY;
1162 cdr.TrackChanged = FALSE;
1163 cdr.LastReadSeekCycles = psxRegs.cycle;
1167 case CdlTest + CMD_WHILE_NOT_READY:
1168 switch (cdr.Param[0]) {
1169 case 0x20: // System Controller ROM Version
1171 memcpy(cdr.Result, Test20, 4);
1175 memcpy(cdr.Result, Test22, 4);
1177 case 0x23: case 0x24:
1179 memcpy(cdr.Result, Test23, 4);
1185 second_resp_time = 20480;
1188 case CdlID + CMD_PART2:
1190 cdr.Result[0] = cdr.StatP;
1195 // 0x10 - audio | 0x40 - disk missing | 0x80 - unlicensed
1196 if (CDR_getStatus(&cdr_stat) == -1 || cdr_stat.Type == 0 || cdr_stat.Type == 0xff) {
1197 cdr.Result[1] = 0xc0;
1200 if (cdr_stat.Type == 2)
1201 cdr.Result[1] |= 0x10;
1202 if (CdromId[0] == '\0')
1203 cdr.Result[1] |= 0x80;
1205 cdr.Result[0] |= (cdr.Result[1] >> 4) & 0x08;
1206 CDR_LOG_I("CdlID: %02x %02x %02x %02x\n", cdr.Result[0],
1207 cdr.Result[1], cdr.Result[2], cdr.Result[3]);
1209 /* This adds the string "PCSX" in Playstation bios boot screen */
1210 memcpy((char *)&cdr.Result[4], "PCSX", 4);
1215 case CdlInit + CMD_WHILE_NOT_READY:
1218 SetPlaySeekRead(cdr.StatP, 0);
1219 // yes, it really sets STATUS_SHELLOPEN
1220 cdr.StatP |= STATUS_SHELLOPEN;
1221 cdr.DriveState = DRIVESTATE_RESCAN_CD;
1222 set_event(PSXINT_CDRLID, 20480);
1227 case CdlGetQ + CMD_WHILE_NOT_READY:
1231 case CdlReadToc + CMD_WHILE_NOT_READY:
1232 cdr.LocL[0] = LOCL_INVALID;
1233 second_resp_time = cdReadTime * 180 / 4;
1237 case CdlReadToc + CMD_PART2:
1238 case CdlReadToc + CMD_PART2 + CMD_WHILE_NOT_READY:
1244 if (cdr.Reading && !cdr.SetlocPending)
1247 Find_CurTrack(cdr.SetlocPending ? cdr.SetSector : cdr.SetSectorPlay);
1249 if ((cdr.Mode & MODE_CDDA) && cdr.CurTrack > 1)
1250 // Read* acts as play for cdda tracks in cdda mode
1254 if (cdr.SetlocPending) {
1255 seekTime = cdrSeekTime(cdr.SetSector);
1256 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
1257 cdr.SetlocPending = 0;
1260 cdr.FileChannelSelected = 0;
1261 cdr.AdpcmActive = 0;
1263 // Fighting Force 2 - update subq time immediately
1265 UpdateSubq(cdr.SetSectorPlay);
1266 cdr.LocL[0] = LOCL_INVALID;
1267 cdr.SubqForwardSectors = 1;
1268 cdr.sectorsRead = 0;
1269 cdr.DriveState = DRIVESTATE_SEEK;
1270 CDR_prefetch(cdr.SetSectorPlay[0], cdr.SetSectorPlay[1],
1271 cdr.SetSectorPlay[2]);
1273 cycles = (cdr.Mode & MODE_SPEED) ? cdReadTime : cdReadTime * 2;
1275 if (Config.hacks.cdr_read_timing)
1276 cycles = cdrAlignTimingHack(cycles);
1277 else if (canDoTurbo())
1278 cycles = cdReadTime / 2;
1279 CDRPLAYREAD_INT(cycles, 1);
1281 SetPlaySeekRead(cdr.StatP, STATUS_SEEK);
1287 error = ERROR_INVALIDCMD;
1292 cdr.Result[0] = cdr.StatP | STATUS_ERROR;
1293 cdr.Result[1] = not_ready ? ERROR_NOTREADY : error;
1294 IrqStat = DiskError;
1295 CDR_LOG_I("cmd %02x error %02x\n", Cmd, cdr.Result[1]);
1299 if (cdr.DriveState == DRIVESTATE_STOPPED && start_rotating) {
1300 cdr.DriveState = DRIVESTATE_STANDBY;
1301 cdr.StatP |= STATUS_ROTATING;
1304 if (second_resp_time) {
1305 cdr.CmdInProgress = Cmd | 0x100;
1306 set_event(PSXINT_CDR, second_resp_time);
1308 else if (cdr.Cmd && cdr.Cmd != (Cmd & 0xff)) {
1309 cdr.CmdInProgress = cdr.Cmd;
1310 CDR_LOG_I("cmd %02x came before %02x finished\n", cdr.Cmd, Cmd);
1313 setIrq(IrqStat, Cmd);
1317 #define ssat32_to_16(v) \
1318 asm("ssat %0,#16,%1" : "=r" (v) : "r" (v))
1320 #define ssat32_to_16(v) do { \
1321 if (v < -32768) v = -32768; \
1322 else if (v > 32767) v = 32767; \
1326 static void cdrPrepCdda(s16 *buf, int samples)
1328 #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
1330 for (i = 0; i < samples; i++) {
1331 buf[i * 2 + 0] = SWAP16(buf[i * 2 + 0]);
1332 buf[i * 2 + 1] = SWAP16(buf[i * 2 + 1]);
1337 static void cdrReadInterruptSetResult(unsigned char result)
1340 CDR_LOG_I("%d:%02d:%02d irq miss, cmd=%02x irqstat=%02x\n",
1341 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2],
1342 cdr.CmdInProgress, cdr.IrqStat);
1343 cdr.Irq1Pending = result;
1347 cdr.Result[0] = result;
1348 setIrq((result & STATUS_ERROR) ? DiskError : DataReady, 0x1004);
1351 static void cdrUpdateTransferBuf(const u8 *buf)
1355 memcpy(cdr.Transfer, buf, DATA_SIZE);
1356 CheckPPFCache(cdr.Transfer, cdr.Prev[0], cdr.Prev[1], cdr.Prev[2]);
1357 CDR_LOG("cdr.Transfer %02x:%02x:%02x\n",
1358 cdr.Transfer[0], cdr.Transfer[1], cdr.Transfer[2]);
1359 if (cdr.FifoOffset < 2048 + 12)
1360 CDR_LOG("FifoOffset(1) %d/%d\n", cdr.FifoOffset, cdr.FifoSize);
1363 static void cdrReadInterrupt(void)
1365 const struct { u8 file, chan, mode, coding; } *subhdr;
1366 const u8 *buf = NULL;
1367 int deliver_data = 1;
1372 memcpy(subqPos, cdr.SetSectorPlay, sizeof(subqPos));
1373 msfiAdd(subqPos, cdr.SubqForwardSectors);
1374 UpdateSubq(subqPos);
1375 if (cdr.SubqForwardSectors < SUBQ_FORWARD_SECTORS) {
1376 cdr.SubqForwardSectors++;
1377 CDRPLAYREAD_INT((cdr.Mode & MODE_SPEED) ? (cdReadTime / 2) : cdReadTime, 0);
1381 // note: CdlGetlocL should work as soon as STATUS_READ is indicated
1382 SetPlaySeekRead(cdr.StatP, STATUS_READ | STATUS_ROTATING);
1383 cdr.DriveState = DRIVESTATE_PLAY_READ;
1386 read_ok = ReadTrack(cdr.SetSectorPlay);
1388 buf = CDR_getBuffer();
1393 CDR_LOG_I("cdrReadInterrupt() Log: err\n");
1394 cdrReadInterruptSetResult(cdr.StatP | STATUS_ERROR);
1395 cdr.DriveState = DRIVESTATE_PAUSED; // ?
1398 memcpy(cdr.LocL, buf, 8);
1400 if (!cdr.IrqStat && !cdr.Irq1Pending)
1401 cdrUpdateTransferBuf(buf);
1403 subhdr = (void *)(buf + 4);
1405 // try to process as adpcm
1406 if (!(cdr.Mode & MODE_STRSND))
1408 if (buf[3] != 2 || (subhdr->mode & 0x44) != 0x44) // or 0x64?
1410 CDR_LOG("f=%d m=%d %d,%3d | %d,%2d | %d,%2d\n", !!(cdr.Mode & MODE_SF), cdr.Muted,
1411 subhdr->file, subhdr->chan, cdr.CurFile, cdr.CurChannel, cdr.FilterFile, cdr.FilterChannel);
1412 if ((cdr.Mode & MODE_SF) && (subhdr->file != cdr.FilterFile || subhdr->chan != cdr.FilterChannel))
1414 if (subhdr->chan & 0x80) { // ?
1415 if (subhdr->chan != 0xff)
1416 log_unhandled("adpcm %d:%d\n", subhdr->file, subhdr->chan);
1419 if (!cdr.FileChannelSelected) {
1420 cdr.CurFile = subhdr->file;
1421 cdr.CurChannel = subhdr->chan;
1422 cdr.FileChannelSelected = 1;
1424 else if (subhdr->file != cdr.CurFile || subhdr->chan != cdr.CurChannel)
1427 // accepted as adpcm
1432 is_start = !cdr.AdpcmActive;
1433 cdr.AdpcmActive = !xa_decode_sector(&cdr.Xa, buf + 4, is_start);
1434 if (cdr.AdpcmActive)
1435 SPU_playADPCMchannel(&cdr.Xa, psxRegs.cycle, is_start);
1438 if ((cdr.Mode & MODE_SF) && (subhdr->mode & 0x44) == 0x44) // according to nocash
1442 Croc 2: $40 - only FORM1 (*)
1443 Judge Dredd: $C8 - only FORM1 (*)
1444 Sim Theme Park - no adpcm at all (zero)
1448 cdrReadInterruptSetResult(cdr.StatP);
1450 msfiAdd(cdr.SetSectorPlay, 1);
1451 CDR_prefetch(cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2]);
1453 CDRPLAYREAD_INT((cdr.Mode & MODE_SPEED) ? (cdReadTime / 2) : cdReadTime, 0);
1459 bit 2 - adpcm active
1460 bit 5 - 1 result ready
1462 bit 7 - 1 command being processed
1465 unsigned char cdrRead0(void) {
1467 cdr.Ctrl |= cdr.AdpcmActive << 2;
1468 cdr.Ctrl |= cdr.ResultReady << 5;
1470 //cdr.Ctrl &= ~0x40;
1471 //if (cdr.FifoOffset != DATA_SIZE)
1472 cdr.Ctrl |= 0x40; // data fifo not empty
1474 // What means the 0x10 and the 0x08 bits? I only saw it used by the bios
1477 CDR_LOG_IO("cdr r0.sta: %02x\n", cdr.Ctrl);
1479 return psxHu8(0x1800) = cdr.Ctrl;
1482 void cdrWrite0(unsigned char rt) {
1483 CDR_LOG_IO("cdr w0.x.idx: %02x\n", rt);
1485 cdr.Ctrl = (rt & 3) | (cdr.Ctrl & ~3);
1488 unsigned char cdrRead1(void) {
1489 if ((cdr.ResultP & 0xf) < cdr.ResultC)
1490 psxHu8(0x1801) = cdr.Result[cdr.ResultP & 0xf];
1494 if (cdr.ResultP == cdr.ResultC)
1495 cdr.ResultReady = 0;
1497 CDR_LOG_IO("cdr r1.x.rsp: %02x #%u\n", psxHu8(0x1801), cdr.ResultP - 1);
1499 return psxHu8(0x1801);
1502 void cdrWrite1(unsigned char rt) {
1503 const char *rnames[] = { "0.cmd", "1.smd", "2.smc", "3.arr" }; (void)rnames;
1504 CDR_LOG_IO("cdr w1.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1506 switch (cdr.Ctrl & 3) {
1510 cdr.AttenuatorRightToRightT = rt;
1516 #ifdef CDR_LOG_CMD_IRQ
1517 CDR_LOG_I("CD1 write: %x (%s)", rt, CmdName[rt]);
1520 SysPrintf(" Param[%d] = {", cdr.ParamC);
1521 for (i = 0; i < cdr.ParamC; i++)
1522 SysPrintf(" %x,", cdr.Param[i]);
1525 SysPrintf(" @%08x\n", psxRegs.pc);
1528 cdr.ResultReady = 0;
1531 if (!cdr.CmdInProgress) {
1532 cdr.CmdInProgress = rt;
1533 // should be something like 12k + controller delays
1534 set_event(PSXINT_CDR, 5000);
1537 CDR_LOG_I("cmd while busy: %02x, prev %02x, busy %02x\n",
1538 rt, cdr.Cmd, cdr.CmdInProgress);
1539 if (cdr.CmdInProgress < 0x100) // no pending 2nd response
1540 cdr.CmdInProgress = rt;
1546 unsigned char cdrRead2(void) {
1547 unsigned char ret = cdr.Transfer[0x920];
1549 if (cdr.FifoOffset < cdr.FifoSize)
1550 ret = cdr.Transfer[cdr.FifoOffset++];
1552 CDR_LOG_I("read empty fifo (%d)\n", cdr.FifoSize);
1554 CDR_LOG_IO("cdr r2.x.dat: %02x\n", ret);
1558 void cdrWrite2(unsigned char rt) {
1559 const char *rnames[] = { "0.prm", "1.ien", "2.all", "3.arl" }; (void)rnames;
1560 CDR_LOG_IO("cdr w2.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1562 switch (cdr.Ctrl & 3) {
1564 if (cdr.ParamC < 8) // FIXME: size and wrapping
1565 cdr.Param[cdr.ParamC++] = rt;
1569 setIrq(cdr.IrqStat, 0x1005);
1572 cdr.AttenuatorLeftToLeftT = rt;
1575 cdr.AttenuatorRightToLeftT = rt;
1580 unsigned char cdrRead3(void) {
1582 psxHu8(0x1803) = cdr.IrqStat | 0xE0;
1584 psxHu8(0x1803) = cdr.IrqMask | 0xE0;
1586 CDR_LOG_IO("cdr r3.%d.%s: %02x\n", cdr.Ctrl & 3,
1587 (cdr.Ctrl & 1) ? "ifl" : "ien", psxHu8(0x1803));
1588 return psxHu8(0x1803);
1591 void cdrWrite3(unsigned char rt) {
1592 const char *rnames[] = { "0.req", "1.ifl", "2.alr", "3.ava" }; (void)rnames;
1594 CDR_LOG_IO("cdr w3.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1596 switch (cdr.Ctrl & 3) {
1600 if (cdr.IrqStat & rt) {
1601 u32 nextCycle = psxRegs.intCycle[PSXINT_CDR].sCycle
1602 + psxRegs.intCycle[PSXINT_CDR].cycle;
1603 int pending = psxRegs.interrupt & (1 << PSXINT_CDR);
1604 #ifdef CDR_LOG_CMD_IRQ
1605 CDR_LOG_I("ack %02x (w=%02x p=%d,%x,%x,%d)\n",
1606 cdr.IrqStat & rt, rt, !!pending, cdr.CmdInProgress,
1607 cdr.Irq1Pending, nextCycle - psxRegs.cycle);
1609 // note: Croc, Shadow Tower (more) vs Discworld Noir (<993)
1610 if (!pending && (cdr.CmdInProgress || cdr.Irq1Pending))
1613 if (cdr.CmdInProgress) {
1614 c = 2048 - (psxRegs.cycle - nextCycle);
1615 c = MAX_VALUE(c, 512);
1617 set_event(PSXINT_CDR, c);
1626 cdr.AttenuatorLeftToRightT = rt;
1630 log_unhandled("Mute ADPCM?\n");
1632 ll = cdr.AttenuatorLeftToLeftT; lr = cdr.AttenuatorLeftToRightT;
1633 rl = cdr.AttenuatorRightToLeftT; rr = cdr.AttenuatorRightToRightT;
1634 if (ll == cdr.AttenuatorLeftToLeft &&
1635 lr == cdr.AttenuatorLeftToRight &&
1636 rl == cdr.AttenuatorRightToLeft &&
1637 rr == cdr.AttenuatorRightToRight)
1639 cdr.AttenuatorLeftToLeft = ll; cdr.AttenuatorLeftToRight = lr;
1640 cdr.AttenuatorRightToLeft = rl; cdr.AttenuatorRightToRight = rr;
1641 CDR_LOG_I("CD-XA Volume: %02x %02x | %02x %02x\n", ll, lr, rl, rr);
1642 SPU_setCDvol(ll, lr, rl, rr, psxRegs.cycle);
1648 if ((rt & 0x80) && cdr.FifoOffset < cdr.FifoSize) {
1649 CDR_LOG("cdrom: FifoOffset(2) %d/%d\n", cdr.FifoOffset, cdr.FifoSize);
1651 else if (rt & 0x80) {
1652 switch (cdr.Mode & (MODE_SIZE_2328|MODE_SIZE_2340)) {
1653 case MODE_SIZE_2328:
1655 cdr.FifoOffset = 12;
1656 cdr.FifoSize = 2048 + 12;
1659 case MODE_SIZE_2340:
1662 cdr.FifoSize = 2340;
1666 else if (!(rt & 0xc0))
1667 cdr.FifoOffset = DATA_SIZE; // fifo empty
1670 void psxDma3(u32 madr, u32 bcr, u32 chcr) {
1671 u32 cdsize, max_words, cycles;
1676 CDR_LOG_I("psxDma3() Log: *** DMA 3 *** %x addr = %x size = %x", chcr, madr, bcr);
1677 if (cdr.FifoOffset == 0) {
1679 SysPrintf(" %02x:%02x:%02x", ptr[0], ptr[1], ptr[2]);
1684 switch (chcr & 0x71000000) {
1687 ptr = getDmaRam(madr, &max_words);
1688 if (ptr == INVALID_PTR) {
1689 CDR_LOG_I("psxDma3() Log: *** DMA 3 *** NULL Pointer!\n");
1693 cdsize = (((bcr - 1) & 0xffff) + 1) * 4;
1696 GS CDX: Enhancement CD crash
1699 - Spams DMA3 and gets buffer overrun
1701 size = DATA_SIZE - cdr.FifoOffset;
1704 if (size > max_words * 4)
1705 size = max_words * 4;
1708 memcpy(ptr, cdr.Transfer + cdr.FifoOffset, size);
1709 cdr.FifoOffset += size;
1711 if (size < cdsize) {
1712 CDR_LOG_I("cdrom: dma3 %d/%d\n", size, cdsize);
1713 memset(ptr + size, cdr.Transfer[0x920], cdsize - size);
1715 psxCpu->Clear(madr, cdsize / 4);
1717 cycles = (cdsize / 4) * 24;
1718 set_event(PSXINT_CDRDMA, cycles);
1720 HW_DMA3_CHCR &= SWAPu32(~0x10000000);
1722 HW_DMA3_MADR = SWAPu32(madr + cdsize);
1723 HW_DMA3_BCR &= SWAPu32(0xffff0000);
1727 psxRegs.cycle += cycles - 20;
1729 if (canDoTurbo() && cdr.Reading && cdr.FifoOffset >= 2048)
1730 CDRPLAYREAD_INT(cycles + 4096, 1);
1734 CDR_LOG_I("psxDma3() Log: Unknown cddma %x\n", chcr);
1738 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1742 void cdrDmaInterrupt(void)
1744 if (HW_DMA3_CHCR & SWAP32(0x01000000))
1746 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1751 static void getCdInfo(void)
1755 CDR_getTN(cdr.ResultTN);
1756 CDR_getTD(0, cdr.SetSectorEnd);
1757 tmp = cdr.SetSectorEnd[0];
1758 cdr.SetSectorEnd[0] = cdr.SetSectorEnd[2];
1759 cdr.SetSectorEnd[2] = tmp;
1763 memset(&cdr, 0, sizeof(cdr));
1766 cdr.FilterChannel = 0;
1768 cdr.IrqStat = NoIntr;
1770 // BIOS player - default values
1771 cdr.AttenuatorLeftToLeft = 0x80;
1772 cdr.AttenuatorLeftToRight = 0x00;
1773 cdr.AttenuatorRightToLeft = 0x00;
1774 cdr.AttenuatorRightToRight = 0x80;
1780 int cdrFreeze(void *f, int Mode) {
1784 if (Mode == 0 && !Config.Cdda)
1787 cdr.freeze_ver = 0x63647202;
1788 gzfreeze(&cdr, sizeof(cdr));
1791 cdr.ParamP = cdr.ParamC;
1792 tmp = cdr.FifoOffset;
1795 gzfreeze(&tmp, sizeof(tmp));
1798 u8 ll = 0, lr = 0, rl = 0, rr = 0;
1801 cdr.FifoOffset = tmp < DATA_SIZE ? tmp : DATA_SIZE;
1802 cdr.FifoSize = (cdr.Mode & MODE_SIZE_2340) ? 2340 : 2048 + 12;
1803 if (cdr.SubqForwardSectors > SUBQ_FORWARD_SECTORS)
1804 cdr.SubqForwardSectors = SUBQ_FORWARD_SECTORS;
1806 // read right sub data
1807 tmpp[0] = btoi(cdr.Prev[0]);
1808 tmpp[1] = btoi(cdr.Prev[1]);
1809 tmpp[2] = btoi(cdr.Prev[2]);
1814 if (cdr.freeze_ver < 0x63647202)
1815 memcpy(cdr.SetSectorPlay, cdr.SetSector, 3);
1817 Find_CurTrack(cdr.SetSectorPlay);
1819 CDR_play(cdr.SetSectorPlay);
1822 ll = cdr.AttenuatorLeftToLeft, lr = cdr.AttenuatorLeftToLeft,
1823 rl = cdr.AttenuatorRightToLeft, rr = cdr.AttenuatorRightToRight;
1824 SPU_setCDvol(ll, lr, rl, rr, psxRegs.cycle);
1830 void LidInterrupt(void) {
1832 cdrLidSeekInterrupt();