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 cdr.RetryDetected = msfiEq(cdr.SetSector, set_loc)
839 && !cdr.SetlocPending;
840 //cdr.RetryDetected |= msfiEq(cdr.Param, cdr.Transfer);
841 memcpy(cdr.SetSector, set_loc, 3);
842 cdr.SetSector[3] = 0;
843 cdr.SetlocPending = 1;
844 cdr.errorRetryhack = 0;
853 cdr.FastBackward = 0;
857 // - Pause player, hit Track 01/02/../xx (Setloc issued!!)
859 if (ParamC != 0 && cdr.Param[0] != 0) {
860 int track = btoi( cdr.Param[0] );
862 if (track <= cdr.ResultTN[1])
863 cdr.CurTrack = track;
865 CDR_LOG("PLAY track %d\n", cdr.CurTrack);
867 if (CDR_getTD((u8)cdr.CurTrack, cdr.ResultTD) != -1) {
868 for (i = 0; i < 3; i++)
869 set_loc[i] = cdr.ResultTD[2 - i];
870 seekTime = cdrSeekTime(set_loc);
871 memcpy(cdr.SetSectorPlay, set_loc, 3);
874 else if (cdr.SetlocPending) {
875 seekTime = cdrSeekTime(cdr.SetSector);
876 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
879 CDR_LOG("PLAY Resume @ %d:%d:%d\n",
880 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2]);
882 cdr.SetlocPending = 0;
885 Rayman: detect track changes
888 Twisted Metal 2: skip PREGAP + starting accurate SubQ
889 - plays tracks without retry play
891 Wild 9: skip PREGAP + starting accurate SubQ
892 - plays tracks without retry play
894 Find_CurTrack(cdr.SetSectorPlay);
895 generate_subq(cdr.SetSectorPlay);
896 cdr.LocL[0] = LOCL_INVALID;
897 cdr.SubqForwardSectors = 1;
898 cdr.TrackChanged = FALSE;
899 cdr.FileChannelSelected = 0;
901 cdr.ReportDelay = 60;
905 CDR_play(cdr.SetSectorPlay);
907 SetPlaySeekRead(cdr.StatP, STATUS_SEEK | STATUS_ROTATING);
909 // BIOS player - set flag again
911 cdr.DriveState = DRIVESTATE_PLAY_READ;
913 CDRPLAYREAD_INT(cdReadTime + seekTime, 1);
918 // TODO: error 80 if stopped
921 // GameShark CD Player: Calls 2x + Play 2x
923 cdr.FastBackward = 0;
929 // GameShark CD Player: Calls 2x + Play 2x
930 cdr.FastBackward = 1;
935 if (cdr.DriveState != DRIVESTATE_STOPPED) {
936 error = ERROR_BAD_ARGNUM;
939 cdr.DriveState = DRIVESTATE_STANDBY;
940 second_resp_time = cdReadTime * 125 / 2;
944 case CdlStandby + CMD_PART2:
950 // grab time for current track
951 CDR_getTD((u8)(cdr.CurTrack), cdr.ResultTD);
953 cdr.SetSectorPlay[0] = cdr.ResultTD[2];
954 cdr.SetSectorPlay[1] = cdr.ResultTD[1];
955 cdr.SetSectorPlay[2] = cdr.ResultTD[0];
960 SetPlaySeekRead(cdr.StatP, 0);
961 cdr.StatP &= ~STATUS_ROTATING;
962 cdr.LocL[0] = LOCL_INVALID;
964 second_resp_time = 0x800;
965 if (cdr.DriveState != DRIVESTATE_STOPPED)
966 second_resp_time = cdReadTime * 30 / 2;
968 cdr.DriveState = DRIVESTATE_STOPPED;
971 case CdlStop + CMD_PART2:
976 if (cdr.AdpcmActive) {
979 SPU_playADPCMchannel(&cdr.Xa, psxRegs.cycle, 1); // flush adpcm
984 // how the drive maintains the position while paused is quite
985 // complicated, this is the minimum to make "Bedlam" happy
986 msfiSub(cdr.SetSectorPlay, MIN_VALUE(cdr.sectorsRead, 4));
990 Gundam Battle Assault 2
992 InuYasha - Feudal Fairy Tale
993 Dance Dance Revolution Konamix
996 if (!(cdr.StatP & (STATUS_PLAY | STATUS_READ)))
998 second_resp_time = 7000;
1002 second_resp_time = 2 * 1097107;
1004 SetPlaySeekRead(cdr.StatP, 0);
1005 DriveStateOld = cdr.DriveState;
1006 cdr.DriveState = DRIVESTATE_PAUSED;
1007 if (DriveStateOld == DRIVESTATE_SEEK) {
1008 // According to Duckstation this fails, but the
1009 // exact conditions and effects are not clear.
1010 // Moto Racer World Tour seems to rely on this.
1011 // For now assume pause works anyway, just errors out.
1012 error = ERROR_NOTREADY;
1017 case CdlPause + CMD_PART2:
1022 case CdlReset + CMD_WHILE_NOT_READY:
1023 // note: nocash and Duckstation calls this 'Init', but
1024 // the official SDK calls it 'Reset', and so do we
1028 second_resp_time = not_ready ? 70000 : 4100000;
1032 case CdlReset + CMD_PART2:
1033 case CdlReset + CMD_PART2 + CMD_WHILE_NOT_READY:
1039 SPU_setCDvol(0, 0, 0, 0, psxRegs.cycle);
1044 SPU_setCDvol(cdr.AttenuatorLeftToLeft, cdr.AttenuatorLeftToRight,
1045 cdr.AttenuatorRightToLeft, cdr.AttenuatorRightToRight, psxRegs.cycle);
1049 cdr.FilterFile = cdr.Param[0];
1050 cdr.FilterChannel = cdr.Param[1];
1051 cdr.FileChannelSelected = 0;
1055 case CdlSetmode + CMD_WHILE_NOT_READY:
1056 CDR_LOG("cdrWrite1() Log: Setmode %x\n", cdr.Param[0]);
1057 cdr.Mode = cdr.Param[0];
1061 case CdlGetparam + CMD_WHILE_NOT_READY:
1062 /* Gameblabla : According to mednafen, Result size should be 5 and done this way. */
1064 cdr.Result[1] = cdr.Mode;
1066 cdr.Result[3] = cdr.FilterFile;
1067 cdr.Result[4] = cdr.FilterChannel;
1071 if (cdr.LocL[0] == LOCL_INVALID) {
1076 memcpy(cdr.Result, cdr.LocL, 8);
1081 memcpy(&cdr.Result, &cdr.subq, 8);
1084 case CdlReadT: // SetSession?
1086 second_resp_time = cdReadTime * 290 / 4;
1090 case CdlReadT + CMD_PART2:
1095 if (CDR_getTN(cdr.ResultTN) == -1) {
1099 cdr.Result[1] = itob(cdr.ResultTN[0]);
1100 cdr.Result[2] = itob(cdr.ResultTN[1]);
1104 cdr.Track = btoi(cdr.Param[0]);
1105 if (CDR_getTD(cdr.Track, cdr.ResultTD) == -1) {
1106 error = ERROR_BAD_ARGVAL;
1110 cdr.Result[1] = itob(cdr.ResultTD[2]);
1111 cdr.Result[2] = itob(cdr.ResultTD[1]);
1113 //cdr.Result[3] = itob(cdr.ResultTD[0]);
1120 SetPlaySeekRead(cdr.StatP, STATUS_SEEK | STATUS_ROTATING);
1123 seekTime = cdrSeekTime(cdr.SetSector);
1124 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
1125 cdr.DriveState = DRIVESTATE_SEEK;
1126 CDR_prefetch(cdr.SetSectorPlay[0], cdr.SetSectorPlay[1],
1127 cdr.SetSectorPlay[2]);
1129 Crusaders of Might and Magic = 0.5x-4x
1130 - fix cutscene speech start
1132 Eggs of Steel = 2x-?
1136 - fix cutscene speech
1141 second_resp_time = cdReadTime + seekTime;
1145 case CdlSeekL + CMD_PART2:
1146 case CdlSeekP + CMD_PART2:
1147 SetPlaySeekRead(cdr.StatP, 0);
1148 cdr.Result[0] = cdr.StatP;
1151 Find_CurTrack(cdr.SetSectorPlay);
1152 read_ok = ReadTrack(cdr.SetSectorPlay);
1153 if (read_ok && (buf = CDR_getBuffer()))
1154 memcpy(cdr.LocL, buf, 8);
1155 UpdateSubq(cdr.SetSectorPlay);
1156 cdr.DriveState = DRIVESTATE_STANDBY;
1157 cdr.TrackChanged = FALSE;
1158 cdr.LastReadSeekCycles = psxRegs.cycle;
1162 case CdlTest + CMD_WHILE_NOT_READY:
1163 switch (cdr.Param[0]) {
1164 case 0x20: // System Controller ROM Version
1166 memcpy(cdr.Result, Test20, 4);
1170 memcpy(cdr.Result, Test22, 4);
1172 case 0x23: case 0x24:
1174 memcpy(cdr.Result, Test23, 4);
1180 second_resp_time = 20480;
1183 case CdlID + CMD_PART2:
1185 cdr.Result[0] = cdr.StatP;
1190 // 0x10 - audio | 0x40 - disk missing | 0x80 - unlicensed
1191 if (CDR_getStatus(&cdr_stat) == -1 || cdr_stat.Type == 0 || cdr_stat.Type == 0xff) {
1192 cdr.Result[1] = 0xc0;
1195 if (cdr_stat.Type == 2)
1196 cdr.Result[1] |= 0x10;
1197 if (CdromId[0] == '\0')
1198 cdr.Result[1] |= 0x80;
1200 cdr.Result[0] |= (cdr.Result[1] >> 4) & 0x08;
1201 CDR_LOG_I("CdlID: %02x %02x %02x %02x\n", cdr.Result[0],
1202 cdr.Result[1], cdr.Result[2], cdr.Result[3]);
1204 /* This adds the string "PCSX" in Playstation bios boot screen */
1205 memcpy((char *)&cdr.Result[4], "PCSX", 4);
1210 case CdlInit + CMD_WHILE_NOT_READY:
1213 SetPlaySeekRead(cdr.StatP, 0);
1214 // yes, it really sets STATUS_SHELLOPEN
1215 cdr.StatP |= STATUS_SHELLOPEN;
1216 cdr.DriveState = DRIVESTATE_RESCAN_CD;
1217 set_event(PSXINT_CDRLID, 20480);
1222 case CdlGetQ + CMD_WHILE_NOT_READY:
1226 case CdlReadToc + CMD_WHILE_NOT_READY:
1227 cdr.LocL[0] = LOCL_INVALID;
1228 second_resp_time = cdReadTime * 180 / 4;
1232 case CdlReadToc + CMD_PART2:
1233 case CdlReadToc + CMD_PART2 + CMD_WHILE_NOT_READY:
1239 if (cdr.Reading && !cdr.SetlocPending)
1242 Find_CurTrack(cdr.SetlocPending ? cdr.SetSector : cdr.SetSectorPlay);
1244 if ((cdr.Mode & MODE_CDDA) && cdr.CurTrack > 1)
1245 // Read* acts as play for cdda tracks in cdda mode
1249 if (cdr.SetlocPending) {
1250 seekTime = cdrSeekTime(cdr.SetSector);
1251 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
1252 cdr.SetlocPending = 0;
1255 cdr.FileChannelSelected = 0;
1256 cdr.AdpcmActive = 0;
1258 // Fighting Force 2 - update subq time immediately
1260 UpdateSubq(cdr.SetSectorPlay);
1261 cdr.LocL[0] = LOCL_INVALID;
1262 cdr.SubqForwardSectors = 1;
1263 cdr.sectorsRead = 0;
1264 cdr.DriveState = DRIVESTATE_SEEK;
1265 CDR_prefetch(cdr.SetSectorPlay[0], cdr.SetSectorPlay[1],
1266 cdr.SetSectorPlay[2]);
1268 cycles = (cdr.Mode & MODE_SPEED) ? cdReadTime : cdReadTime * 2;
1270 if (Config.hacks.cdr_read_timing)
1271 cycles = cdrAlignTimingHack(cycles);
1272 else if (canDoTurbo())
1273 cycles = cdReadTime / 2;
1274 CDRPLAYREAD_INT(cycles, 1);
1276 SetPlaySeekRead(cdr.StatP, STATUS_SEEK);
1282 error = ERROR_INVALIDCMD;
1287 cdr.Result[0] = cdr.StatP | STATUS_ERROR;
1288 cdr.Result[1] = not_ready ? ERROR_NOTREADY : error;
1289 IrqStat = DiskError;
1290 CDR_LOG_I("cmd %02x error %02x\n", Cmd, cdr.Result[1]);
1294 if (cdr.DriveState == DRIVESTATE_STOPPED && start_rotating) {
1295 cdr.DriveState = DRIVESTATE_STANDBY;
1296 cdr.StatP |= STATUS_ROTATING;
1299 if (second_resp_time) {
1300 cdr.CmdInProgress = Cmd | 0x100;
1301 set_event(PSXINT_CDR, second_resp_time);
1303 else if (cdr.Cmd && cdr.Cmd != (Cmd & 0xff)) {
1304 cdr.CmdInProgress = cdr.Cmd;
1305 CDR_LOG_I("cmd %02x came before %02x finished\n", cdr.Cmd, Cmd);
1308 setIrq(IrqStat, Cmd);
1312 #define ssat32_to_16(v) \
1313 asm("ssat %0,#16,%1" : "=r" (v) : "r" (v))
1315 #define ssat32_to_16(v) do { \
1316 if (v < -32768) v = -32768; \
1317 else if (v > 32767) v = 32767; \
1321 static void cdrPrepCdda(s16 *buf, int samples)
1323 #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
1325 for (i = 0; i < samples; i++) {
1326 buf[i * 2 + 0] = SWAP16(buf[i * 2 + 0]);
1327 buf[i * 2 + 1] = SWAP16(buf[i * 2 + 1]);
1332 static void cdrReadInterruptSetResult(unsigned char result)
1335 CDR_LOG_I("%d:%02d:%02d irq miss, cmd=%02x irqstat=%02x\n",
1336 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2],
1337 cdr.CmdInProgress, cdr.IrqStat);
1338 cdr.Irq1Pending = result;
1342 cdr.Result[0] = result;
1343 setIrq((result & STATUS_ERROR) ? DiskError : DataReady, 0x1004);
1346 static void cdrUpdateTransferBuf(const u8 *buf)
1350 memcpy(cdr.Transfer, buf, DATA_SIZE);
1351 CheckPPFCache(cdr.Transfer, cdr.Prev[0], cdr.Prev[1], cdr.Prev[2]);
1352 CDR_LOG("cdr.Transfer %02x:%02x:%02x\n",
1353 cdr.Transfer[0], cdr.Transfer[1], cdr.Transfer[2]);
1354 if (cdr.FifoOffset < 2048 + 12)
1355 CDR_LOG("FifoOffset(1) %d/%d\n", cdr.FifoOffset, cdr.FifoSize);
1358 static void cdrReadInterrupt(void)
1360 const struct { u8 file, chan, mode, coding; } *subhdr;
1361 const u8 *buf = NULL;
1362 int deliver_data = 1;
1367 memcpy(subqPos, cdr.SetSectorPlay, sizeof(subqPos));
1368 msfiAdd(subqPos, cdr.SubqForwardSectors);
1369 UpdateSubq(subqPos);
1370 if (cdr.SubqForwardSectors < SUBQ_FORWARD_SECTORS) {
1371 cdr.SubqForwardSectors++;
1372 CDRPLAYREAD_INT((cdr.Mode & MODE_SPEED) ? (cdReadTime / 2) : cdReadTime, 0);
1376 // note: CdlGetlocL should work as soon as STATUS_READ is indicated
1377 SetPlaySeekRead(cdr.StatP, STATUS_READ | STATUS_ROTATING);
1378 cdr.DriveState = DRIVESTATE_PLAY_READ;
1381 read_ok = ReadTrack(cdr.SetSectorPlay);
1383 buf = CDR_getBuffer();
1388 CDR_LOG_I("cdrReadInterrupt() Log: err\n");
1389 cdrReadInterruptSetResult(cdr.StatP | STATUS_ERROR);
1390 cdr.DriveState = DRIVESTATE_PAUSED; // ?
1393 memcpy(cdr.LocL, buf, 8);
1395 if (!cdr.IrqStat && !cdr.Irq1Pending)
1396 cdrUpdateTransferBuf(buf);
1398 subhdr = (void *)(buf + 4);
1400 // try to process as adpcm
1401 if (!(cdr.Mode & MODE_STRSND))
1403 if (buf[3] != 2 || (subhdr->mode & 0x44) != 0x44) // or 0x64?
1405 CDR_LOG("f=%d m=%d %d,%3d | %d,%2d | %d,%2d\n", !!(cdr.Mode & MODE_SF), cdr.Muted,
1406 subhdr->file, subhdr->chan, cdr.CurFile, cdr.CurChannel, cdr.FilterFile, cdr.FilterChannel);
1407 if ((cdr.Mode & MODE_SF) && (subhdr->file != cdr.FilterFile || subhdr->chan != cdr.FilterChannel))
1409 if (subhdr->chan & 0x80) { // ?
1410 if (subhdr->chan != 0xff)
1411 log_unhandled("adpcm %d:%d\n", subhdr->file, subhdr->chan);
1414 if (!cdr.FileChannelSelected) {
1415 cdr.CurFile = subhdr->file;
1416 cdr.CurChannel = subhdr->chan;
1417 cdr.FileChannelSelected = 1;
1419 else if (subhdr->file != cdr.CurFile || subhdr->chan != cdr.CurChannel)
1422 // accepted as adpcm
1427 is_start = !cdr.AdpcmActive;
1428 cdr.AdpcmActive = !xa_decode_sector(&cdr.Xa, buf + 4, is_start);
1429 if (cdr.AdpcmActive)
1430 SPU_playADPCMchannel(&cdr.Xa, psxRegs.cycle, is_start);
1433 if ((cdr.Mode & MODE_SF) && (subhdr->mode & 0x44) == 0x44) // according to nocash
1437 Croc 2: $40 - only FORM1 (*)
1438 Judge Dredd: $C8 - only FORM1 (*)
1439 Sim Theme Park - no adpcm at all (zero)
1443 cdrReadInterruptSetResult(cdr.StatP);
1445 msfiAdd(cdr.SetSectorPlay, 1);
1446 CDR_prefetch(cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2]);
1448 CDRPLAYREAD_INT((cdr.Mode & MODE_SPEED) ? (cdReadTime / 2) : cdReadTime, 0);
1454 bit 2 - adpcm active
1455 bit 5 - 1 result ready
1457 bit 7 - 1 command being processed
1460 unsigned char cdrRead0(void) {
1462 cdr.Ctrl |= cdr.AdpcmActive << 2;
1463 cdr.Ctrl |= cdr.ResultReady << 5;
1465 //cdr.Ctrl &= ~0x40;
1466 //if (cdr.FifoOffset != DATA_SIZE)
1467 cdr.Ctrl |= 0x40; // data fifo not empty
1469 // What means the 0x10 and the 0x08 bits? I only saw it used by the bios
1472 CDR_LOG_IO("cdr r0.sta: %02x\n", cdr.Ctrl);
1474 return psxHu8(0x1800) = cdr.Ctrl;
1477 void cdrWrite0(unsigned char rt) {
1478 CDR_LOG_IO("cdr w0.x.idx: %02x\n", rt);
1480 cdr.Ctrl = (rt & 3) | (cdr.Ctrl & ~3);
1483 unsigned char cdrRead1(void) {
1484 if ((cdr.ResultP & 0xf) < cdr.ResultC)
1485 psxHu8(0x1801) = cdr.Result[cdr.ResultP & 0xf];
1489 if (cdr.ResultP == cdr.ResultC)
1490 cdr.ResultReady = 0;
1492 CDR_LOG_IO("cdr r1.x.rsp: %02x #%u\n", psxHu8(0x1801), cdr.ResultP - 1);
1494 return psxHu8(0x1801);
1497 void cdrWrite1(unsigned char rt) {
1498 const char *rnames[] = { "0.cmd", "1.smd", "2.smc", "3.arr" }; (void)rnames;
1499 CDR_LOG_IO("cdr w1.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1501 switch (cdr.Ctrl & 3) {
1505 cdr.AttenuatorRightToRightT = rt;
1511 #ifdef CDR_LOG_CMD_IRQ
1512 CDR_LOG_I("CD1 write: %x (%s)", rt, CmdName[rt]);
1515 SysPrintf(" Param[%d] = {", cdr.ParamC);
1516 for (i = 0; i < cdr.ParamC; i++)
1517 SysPrintf(" %x,", cdr.Param[i]);
1520 SysPrintf(" @%08x\n", psxRegs.pc);
1523 cdr.ResultReady = 0;
1526 if (!cdr.CmdInProgress) {
1527 cdr.CmdInProgress = rt;
1528 // should be something like 12k + controller delays
1529 set_event(PSXINT_CDR, 5000);
1532 CDR_LOG_I("cmd while busy: %02x, prev %02x, busy %02x\n",
1533 rt, cdr.Cmd, cdr.CmdInProgress);
1534 if (cdr.CmdInProgress < 0x100) // no pending 2nd response
1535 cdr.CmdInProgress = rt;
1541 unsigned char cdrRead2(void) {
1542 unsigned char ret = cdr.Transfer[0x920];
1544 if (cdr.FifoOffset < cdr.FifoSize)
1545 ret = cdr.Transfer[cdr.FifoOffset++];
1547 CDR_LOG_I("read empty fifo (%d)\n", cdr.FifoSize);
1549 CDR_LOG_IO("cdr r2.x.dat: %02x\n", ret);
1553 void cdrWrite2(unsigned char rt) {
1554 const char *rnames[] = { "0.prm", "1.ien", "2.all", "3.arl" }; (void)rnames;
1555 CDR_LOG_IO("cdr w2.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1557 switch (cdr.Ctrl & 3) {
1559 if (cdr.ParamC < 8) // FIXME: size and wrapping
1560 cdr.Param[cdr.ParamC++] = rt;
1564 setIrq(cdr.IrqStat, 0x1005);
1567 cdr.AttenuatorLeftToLeftT = rt;
1570 cdr.AttenuatorRightToLeftT = rt;
1575 unsigned char cdrRead3(void) {
1577 psxHu8(0x1803) = cdr.IrqStat | 0xE0;
1579 psxHu8(0x1803) = cdr.IrqMask | 0xE0;
1581 CDR_LOG_IO("cdr r3.%d.%s: %02x\n", cdr.Ctrl & 3,
1582 (cdr.Ctrl & 1) ? "ifl" : "ien", psxHu8(0x1803));
1583 return psxHu8(0x1803);
1586 void cdrWrite3(unsigned char rt) {
1587 const char *rnames[] = { "0.req", "1.ifl", "2.alr", "3.ava" }; (void)rnames;
1589 CDR_LOG_IO("cdr w3.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1591 switch (cdr.Ctrl & 3) {
1595 if (cdr.IrqStat & rt) {
1596 u32 nextCycle = psxRegs.intCycle[PSXINT_CDR].sCycle
1597 + psxRegs.intCycle[PSXINT_CDR].cycle;
1598 int pending = psxRegs.interrupt & (1 << PSXINT_CDR);
1599 #ifdef CDR_LOG_CMD_IRQ
1600 CDR_LOG_I("ack %02x (w=%02x p=%d,%x,%x,%d)\n",
1601 cdr.IrqStat & rt, rt, !!pending, cdr.CmdInProgress,
1602 cdr.Irq1Pending, nextCycle - psxRegs.cycle);
1604 // note: Croc, Shadow Tower (more) vs Discworld Noir (<993)
1605 if (!pending && (cdr.CmdInProgress || cdr.Irq1Pending))
1608 if (cdr.CmdInProgress) {
1609 c = 2048 - (psxRegs.cycle - nextCycle);
1610 c = MAX_VALUE(c, 512);
1612 set_event(PSXINT_CDR, c);
1621 cdr.AttenuatorLeftToRightT = rt;
1625 log_unhandled("Mute ADPCM?\n");
1627 ll = cdr.AttenuatorLeftToLeftT; lr = cdr.AttenuatorLeftToRightT;
1628 rl = cdr.AttenuatorRightToLeftT; rr = cdr.AttenuatorRightToRightT;
1629 if (ll == cdr.AttenuatorLeftToLeft &&
1630 lr == cdr.AttenuatorLeftToRight &&
1631 rl == cdr.AttenuatorRightToLeft &&
1632 rr == cdr.AttenuatorRightToRight)
1634 cdr.AttenuatorLeftToLeft = ll; cdr.AttenuatorLeftToRight = lr;
1635 cdr.AttenuatorRightToLeft = rl; cdr.AttenuatorRightToRight = rr;
1636 CDR_LOG_I("CD-XA Volume: %02x %02x | %02x %02x\n", ll, lr, rl, rr);
1637 SPU_setCDvol(ll, lr, rl, rr, psxRegs.cycle);
1643 if ((rt & 0x80) && cdr.FifoOffset < cdr.FifoSize) {
1644 CDR_LOG("cdrom: FifoOffset(2) %d/%d\n", cdr.FifoOffset, cdr.FifoSize);
1646 else if (rt & 0x80) {
1647 switch (cdr.Mode & (MODE_SIZE_2328|MODE_SIZE_2340)) {
1648 case MODE_SIZE_2328:
1650 cdr.FifoOffset = 12;
1651 cdr.FifoSize = 2048 + 12;
1654 case MODE_SIZE_2340:
1657 cdr.FifoSize = 2340;
1661 else if (!(rt & 0xc0))
1662 cdr.FifoOffset = DATA_SIZE; // fifo empty
1665 void psxDma3(u32 madr, u32 bcr, u32 chcr) {
1666 u32 cdsize, max_words, cycles;
1671 CDR_LOG_I("psxDma3() Log: *** DMA 3 *** %x addr = %x size = %x", chcr, madr, bcr);
1672 if (cdr.FifoOffset == 0) {
1674 SysPrintf(" %02x:%02x:%02x", ptr[0], ptr[1], ptr[2]);
1679 switch (chcr & 0x71000000) {
1682 ptr = getDmaRam(madr, &max_words);
1683 if (ptr == INVALID_PTR) {
1684 CDR_LOG_I("psxDma3() Log: *** DMA 3 *** NULL Pointer!\n");
1688 cdsize = (((bcr - 1) & 0xffff) + 1) * 4;
1691 GS CDX: Enhancement CD crash
1694 - Spams DMA3 and gets buffer overrun
1696 size = DATA_SIZE - cdr.FifoOffset;
1699 if (size > max_words * 4)
1700 size = max_words * 4;
1703 memcpy(ptr, cdr.Transfer + cdr.FifoOffset, size);
1704 cdr.FifoOffset += size;
1706 if (size < cdsize) {
1707 CDR_LOG_I("cdrom: dma3 %d/%d\n", size, cdsize);
1708 memset(ptr + size, cdr.Transfer[0x920], cdsize - size);
1710 psxCpu->Clear(madr, cdsize / 4);
1712 cycles = (cdsize / 4) * 24;
1713 set_event(PSXINT_CDRDMA, cycles);
1715 HW_DMA3_CHCR &= SWAPu32(~0x10000000);
1717 HW_DMA3_MADR = SWAPu32(madr + cdsize);
1718 HW_DMA3_BCR &= SWAPu32(0xffff0000);
1722 psxRegs.cycle += cycles - 20;
1724 if (canDoTurbo() && cdr.Reading && cdr.FifoOffset >= 2048)
1725 CDRPLAYREAD_INT(cycles + 4096, 1);
1729 CDR_LOG_I("psxDma3() Log: Unknown cddma %x\n", chcr);
1733 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1737 void cdrDmaInterrupt(void)
1739 if (HW_DMA3_CHCR & SWAP32(0x01000000))
1741 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1746 static void getCdInfo(void)
1750 CDR_getTN(cdr.ResultTN);
1751 CDR_getTD(0, cdr.SetSectorEnd);
1752 tmp = cdr.SetSectorEnd[0];
1753 cdr.SetSectorEnd[0] = cdr.SetSectorEnd[2];
1754 cdr.SetSectorEnd[2] = tmp;
1758 memset(&cdr, 0, sizeof(cdr));
1761 cdr.FilterChannel = 0;
1763 cdr.IrqStat = NoIntr;
1765 // BIOS player - default values
1766 cdr.AttenuatorLeftToLeft = 0x80;
1767 cdr.AttenuatorLeftToRight = 0x00;
1768 cdr.AttenuatorRightToLeft = 0x00;
1769 cdr.AttenuatorRightToRight = 0x80;
1775 int cdrFreeze(void *f, int Mode) {
1779 if (Mode == 0 && !Config.Cdda)
1782 cdr.freeze_ver = 0x63647202;
1783 gzfreeze(&cdr, sizeof(cdr));
1786 cdr.ParamP = cdr.ParamC;
1787 tmp = cdr.FifoOffset;
1790 gzfreeze(&tmp, sizeof(tmp));
1793 u8 ll = 0, lr = 0, rl = 0, rr = 0;
1796 cdr.FifoOffset = tmp < DATA_SIZE ? tmp : DATA_SIZE;
1797 cdr.FifoSize = (cdr.Mode & MODE_SIZE_2340) ? 2340 : 2048 + 12;
1798 if (cdr.SubqForwardSectors > SUBQ_FORWARD_SECTORS)
1799 cdr.SubqForwardSectors = SUBQ_FORWARD_SECTORS;
1801 // read right sub data
1802 tmpp[0] = btoi(cdr.Prev[0]);
1803 tmpp[1] = btoi(cdr.Prev[1]);
1804 tmpp[2] = btoi(cdr.Prev[2]);
1809 if (cdr.freeze_ver < 0x63647202)
1810 memcpy(cdr.SetSectorPlay, cdr.SetSector, 3);
1812 Find_CurTrack(cdr.SetSectorPlay);
1814 CDR_play(cdr.SetSectorPlay);
1817 ll = cdr.AttenuatorLeftToLeft, lr = cdr.AttenuatorLeftToLeft,
1818 rl = cdr.AttenuatorRightToLeft, rr = cdr.AttenuatorRightToRight;
1819 SPU_setCDvol(ll, lr, rl, rr, psxRegs.cycle);
1825 void LidInterrupt(void) {
1827 cdrLidSeekInterrupt();