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.
27 #include "arm_features.h"
31 #define CDR_LOG SysPrintf
36 #define CDR_LOG_I SysPrintf
38 #define CDR_LOG_I log_unhandled
41 #define CDR_LOG_IO SysPrintf
43 #define CDR_LOG_IO(...)
45 //#define CDR_LOG_CMD_IRQ
48 // unused members maintain savesate compatibility
49 unsigned char unused0;
50 unsigned char unused1;
52 unsigned char unused2;
58 unsigned char Transfer[DATA_SIZE];
62 unsigned char Relative[3];
63 unsigned char Absolute[3];
65 unsigned char TrackChanged;
66 unsigned char unused3[3];
67 unsigned int freeze_ver;
69 unsigned char Prev[4];
70 unsigned char Param[8];
71 unsigned char Result[16];
75 unsigned char ResultC;
76 unsigned char ResultP;
77 unsigned char ResultReady;
79 unsigned char unused4;
80 unsigned char SetlocPending;
83 unsigned char ResultTN[6];
84 unsigned char ResultTD[4];
85 unsigned char SetSectorPlay[4];
86 unsigned char SetSectorEnd[4];
87 unsigned char SetSector[4];
91 int Mode, File, Channel;
113 u8 AttenuatorLeftToLeft, AttenuatorLeftToRight;
114 u8 AttenuatorRightToRight, AttenuatorRightToLeft;
115 u8 AttenuatorLeftToLeftT, AttenuatorLeftToRightT;
116 u8 AttenuatorRightToRightT, AttenuatorRightToLeftT;
118 static s16 read_buf[CD_FRAMESIZE_RAW/2];
120 /* CD-ROM magic numbers */
121 #define CdlSync 0 /* nocash documentation : "Uh, actually, returns error code 40h = Invalid Command...?" */
126 #define CdlBackward 5
134 #define CdlSetfilter 13
135 #define CdlSetmode 14
136 #define CdlGetparam 15
137 #define CdlGetlocL 16
138 #define CdlGetlocP 17
144 #define CdlSetclock 23
145 #define CdlGetclock 24
151 #define CdlReadToc 30
153 #ifdef CDR_LOG_CMD_IRQ
154 static const char * const CmdName[0x100] = {
155 "CdlSync", "CdlNop", "CdlSetloc", "CdlPlay",
156 "CdlForward", "CdlBackward", "CdlReadN", "CdlStandby",
157 "CdlStop", "CdlPause", "CdlReset", "CdlMute",
158 "CdlDemute", "CdlSetfilter", "CdlSetmode", "CdlGetparam",
159 "CdlGetlocL", "CdlGetlocP", "CdlReadT", "CdlGetTN",
160 "CdlGetTD", "CdlSeekL", "CdlSeekP", "CdlSetclock",
161 "CdlGetclock", "CdlTest", "CdlID", "CdlReadS",
162 "CdlInit", NULL, "CDlReadToc", NULL
166 unsigned char Test04[] = { 0 };
167 unsigned char Test05[] = { 0 };
168 unsigned char Test20[] = { 0x98, 0x06, 0x10, 0xC3 };
169 unsigned char Test22[] = { 0x66, 0x6F, 0x72, 0x20, 0x45, 0x75, 0x72, 0x6F };
170 unsigned char Test23[] = { 0x43, 0x58, 0x44, 0x32, 0x39 ,0x34, 0x30, 0x51 };
176 #define Acknowledge 3
181 #define MODE_SPEED (1<<7) // 0x80
182 #define MODE_STRSND (1<<6) // 0x40 ADPCM on/off
183 #define MODE_SIZE_2340 (1<<5) // 0x20
184 #define MODE_SIZE_2328 (1<<4) // 0x10
185 #define MODE_SIZE_2048 (0<<4) // 0x00
186 #define MODE_SF (1<<3) // 0x08 channel on/off
187 #define MODE_REPORT (1<<2) // 0x04
188 #define MODE_AUTOPAUSE (1<<1) // 0x02
189 #define MODE_CDDA (1<<0) // 0x01
192 #define STATUS_PLAY (1<<7) // 0x80
193 #define STATUS_SEEK (1<<6) // 0x40
194 #define STATUS_READ (1<<5) // 0x20
195 #define STATUS_SHELLOPEN (1<<4) // 0x10
196 #define STATUS_UNKNOWN3 (1<<3) // 0x08
197 #define STATUS_UNKNOWN2 (1<<2) // 0x04
198 #define STATUS_ROTATING (1<<1) // 0x02
199 #define STATUS_ERROR (1<<0) // 0x01
202 #define ERROR_NOTREADY (1<<7) // 0x80
203 #define ERROR_INVALIDCMD (1<<6) // 0x40
204 #define ERROR_INVALIDARG (1<<5) // 0x20
206 // 1x = 75 sectors per second
207 // PSXCLK = 1 sec in the ps
208 // so (PSXCLK / 75) = cdr read time (linuzappz)
209 #define cdReadTime (PSXCLK / 75)
212 DRIVESTATE_STANDBY = 0, // pause, play, read
214 DRIVESTATE_RESCAN_CD,
215 DRIVESTATE_PREPARE_CD,
219 static struct CdrStat stat;
221 static unsigned int msf2sec(const u8 *msf) {
222 return ((msf[0] * 60 + msf[1]) * 75) + msf[2];
225 // for that weird psemu API..
226 static unsigned int fsm2sec(const u8 *msf) {
227 return ((msf[2] * 60 + msf[1]) * 75) + msf[0];
230 static void sec2msf(unsigned int s, u8 *msf) {
231 msf[0] = s / 75 / 60;
232 s = s - msf[0] * 75 * 60;
239 #define CDR_INT(eCycle) { \
240 psxRegs.interrupt |= (1 << PSXINT_CDR); \
241 psxRegs.intCycle[PSXINT_CDR].cycle = eCycle; \
242 psxRegs.intCycle[PSXINT_CDR].sCycle = psxRegs.cycle; \
243 new_dyna_set_event(PSXINT_CDR, eCycle); \
246 // cdrPlaySeekReadInterrupt
247 #define CDRPLAYSEEKREAD_INT(eCycle, isFirst) { \
249 psxRegs.interrupt |= (1 << PSXINT_CDREAD); \
251 psxRegs.intCycle[PSXINT_CDREAD].sCycle = psxRegs.cycle; \
253 psxRegs.intCycle[PSXINT_CDREAD].sCycle += psxRegs.intCycle[PSXINT_CDREAD].cycle; \
254 psxRegs.intCycle[PSXINT_CDREAD].cycle = e_; \
255 new_dyna_set_event_abs(PSXINT_CDREAD, psxRegs.intCycle[PSXINT_CDREAD].sCycle + e_); \
258 // cdrLidSeekInterrupt
259 #define CDRLID_INT(eCycle) { \
260 psxRegs.interrupt |= (1 << PSXINT_CDRLID); \
261 psxRegs.intCycle[PSXINT_CDRLID].cycle = eCycle; \
262 psxRegs.intCycle[PSXINT_CDRLID].sCycle = psxRegs.cycle; \
263 new_dyna_set_event(PSXINT_CDRLID, eCycle); \
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 static void setIrq(int log_cmd)
291 if (cdr.Stat & cdr.Reg2)
292 psxHu32ref(0x1070) |= SWAP32((u32)0x4);
294 #ifdef CDR_LOG_CMD_IRQ
298 SysPrintf("CDR IRQ=%d cmd %02x stat %02x: ",
299 !!(cdr.Stat & cdr.Reg2), log_cmd, cdr.Stat);
300 for (i = 0; i < cdr.ResultC; i++)
301 SysPrintf("%02x ", cdr.Result[i]);
307 // timing used in this function was taken from tests on real hardware
308 // (yes it's slow, but you probably don't want to modify it)
309 void cdrLidSeekInterrupt(void)
311 switch (cdr.DriveState) {
313 case DRIVESTATE_STANDBY:
316 SetPlaySeekRead(cdr.StatP, 0);
318 if (CDR_getStatus(&stat) == -1)
321 if (stat.Status & STATUS_SHELLOPEN)
323 cdr.DriveState = DRIVESTATE_LID_OPEN;
328 case DRIVESTATE_LID_OPEN:
329 if (CDR_getStatus(&stat) == -1)
330 stat.Status &= ~STATUS_SHELLOPEN;
333 if (!(cdr.StatP & STATUS_SHELLOPEN)) {
334 cdr.StatP |= STATUS_SHELLOPEN;
336 // could generate error irq here, but real hardware
337 // only sometimes does that
338 // (not done when lots of commands are sent?)
340 CDRLID_INT(cdReadTime * 30);
343 else if (cdr.StatP & STATUS_ROTATING) {
344 cdr.StatP &= ~STATUS_ROTATING;
346 else if (!(stat.Status & STATUS_SHELLOPEN)) {
350 // cdr.StatP STATUS_SHELLOPEN is "sticky"
351 // and is only cleared by CdlNop
353 cdr.DriveState = DRIVESTATE_RESCAN_CD;
354 CDRLID_INT(cdReadTime * 105);
359 CDRLID_INT(cdReadTime * 3);
362 case DRIVESTATE_RESCAN_CD:
363 cdr.StatP |= STATUS_ROTATING;
364 cdr.DriveState = DRIVESTATE_PREPARE_CD;
366 // this is very long on real hardware, over 6 seconds
367 // make it a bit faster here...
368 CDRLID_INT(cdReadTime * 150);
371 case DRIVESTATE_PREPARE_CD:
372 cdr.StatP |= STATUS_SEEK;
374 cdr.DriveState = DRIVESTATE_STANDBY;
375 CDRLID_INT(cdReadTime * 26);
380 static void Find_CurTrack(const u8 *time)
384 current = msf2sec(time);
386 for (cdr.CurTrack = 1; cdr.CurTrack < cdr.ResultTN[1]; cdr.CurTrack++) {
387 CDR_getTD(cdr.CurTrack + 1, cdr.ResultTD);
388 sect = fsm2sec(cdr.ResultTD);
389 if (sect - current >= 150)
394 static void generate_subq(const u8 *time)
396 unsigned char start[3], next[3];
397 unsigned int this_s, start_s, next_s, pregap;
400 CDR_getTD(cdr.CurTrack, start);
401 if (cdr.CurTrack + 1 <= cdr.ResultTN[1]) {
403 CDR_getTD(cdr.CurTrack + 1, next);
406 // last track - cd size
408 next[0] = cdr.SetSectorEnd[2];
409 next[1] = cdr.SetSectorEnd[1];
410 next[2] = cdr.SetSectorEnd[0];
413 this_s = msf2sec(time);
414 start_s = fsm2sec(start);
415 next_s = fsm2sec(next);
417 cdr.TrackChanged = FALSE;
419 if (next_s - this_s < pregap) {
420 cdr.TrackChanged = TRUE;
427 relative_s = this_s - start_s;
428 if (relative_s < 0) {
430 relative_s = -relative_s;
432 sec2msf(relative_s, cdr.subq.Relative);
434 cdr.subq.Track = itob(cdr.CurTrack);
435 cdr.subq.Relative[0] = itob(cdr.subq.Relative[0]);
436 cdr.subq.Relative[1] = itob(cdr.subq.Relative[1]);
437 cdr.subq.Relative[2] = itob(cdr.subq.Relative[2]);
438 cdr.subq.Absolute[0] = itob(time[0]);
439 cdr.subq.Absolute[1] = itob(time[1]);
440 cdr.subq.Absolute[2] = itob(time[2]);
443 static void ReadTrack(const u8 *time) {
444 unsigned char tmp[3];
448 tmp[0] = itob(time[0]);
449 tmp[1] = itob(time[1]);
450 tmp[2] = itob(time[2]);
452 if (memcmp(cdr.Prev, tmp, 3) == 0)
455 CDR_LOG("ReadTrack *** %02x:%02x:%02x\n", tmp[0], tmp[1], tmp[2]);
457 cdr.NoErr = CDR_readTrack(tmp);
458 memcpy(cdr.Prev, tmp, 3);
463 subq = (struct SubQ *)CDR_getBufferSub();
464 if (subq != NULL && cdr.CurTrack == 1) {
465 crc = calcCrc((u8 *)subq + 12, 10);
466 if (crc == (((u16)subq->CRC[0] << 8) | subq->CRC[1])) {
467 cdr.subq.Track = subq->TrackNumber;
468 cdr.subq.Index = subq->IndexNumber;
469 memcpy(cdr.subq.Relative, subq->TrackRelativeAddress, 3);
470 memcpy(cdr.subq.Absolute, subq->AbsoluteAddress, 3);
473 CDR_LOG_I("subq bad crc @%02x:%02x:%02x\n",
474 tmp[0], tmp[1], tmp[2]);
481 CDR_LOG(" -> %02x,%02x %02x:%02x:%02x %02x:%02x:%02x\n",
482 cdr.subq.Track, cdr.subq.Index,
483 cdr.subq.Relative[0], cdr.subq.Relative[1], cdr.subq.Relative[2],
484 cdr.subq.Absolute[0], cdr.subq.Absolute[1], cdr.subq.Absolute[2]);
487 static void cdrPlayInterrupt_Autopause()
490 boolean abs_lev_chselect;
493 if ((cdr.Mode & MODE_AUTOPAUSE) && cdr.TrackChanged) {
494 CDR_LOG( "CDDA STOP\n" );
496 // Magic the Gathering
497 // - looping territory cdda
500 //cdr.ResultReady = 1;
501 //cdr.Stat = DataReady;
506 SetPlaySeekRead(cdr.StatP, 0);
508 else if (((cdr.Mode & MODE_REPORT) || cdr.FastForward || cdr.FastBackward)) {
509 cdr.Result[0] = cdr.StatP;
510 cdr.Result[1] = cdr.subq.Track;
511 cdr.Result[2] = cdr.subq.Index;
513 abs_lev_chselect = cdr.subq.Absolute[1] & 0x01;
515 /* 8 is a hack. For accuracy, it should be 588. */
516 for (i = 0; i < 8; i++)
518 abs_lev_max = MAX_VALUE(abs_lev_max, abs(read_buf[i * 2 + abs_lev_chselect]));
520 abs_lev_max = MIN_VALUE(abs_lev_max, 32767);
521 abs_lev_max |= abs_lev_chselect << 15;
523 if (cdr.subq.Absolute[2] & 0x10) {
524 cdr.Result[3] = cdr.subq.Relative[0];
525 cdr.Result[4] = cdr.subq.Relative[1] | 0x80;
526 cdr.Result[5] = cdr.subq.Relative[2];
529 cdr.Result[3] = cdr.subq.Absolute[0];
530 cdr.Result[4] = cdr.subq.Absolute[1];
531 cdr.Result[5] = cdr.subq.Absolute[2];
534 cdr.Result[6] = abs_lev_max >> 0;
535 cdr.Result[7] = abs_lev_max >> 8;
537 // Rayman: Logo freeze (resultready + dataready)
539 cdr.Stat = DataReady;
546 static int cdrSeekTime(unsigned char *target)
548 int seekTime = abs(msf2sec(cdr.SetSectorPlay) - msf2sec(target)) * (cdReadTime / 200);
551 * It was originally set to 1000000 for Driver, however it is not high enough for Worms Pinball
552 * and was unreliable for that game.
553 * I also tested it against Mednafen and Driver's titlescreen music starts 25 frames later, not immediatly.
555 * Obviously, this isn't perfect but right now, it should be a bit better.
556 * Games to test this against if you change that setting :
557 * - Driver (titlescreen music delay and retry mission)
558 * - Worms Pinball (Will either not boot or crash in the memory card screen)
559 * - Viewpoint (short pauses if the delay in the ingame music is too long)
561 * It seems that 3386880 * 5 is too much for Driver's titlescreen and it starts skipping.
562 * However, 1000000 is not enough for Worms Pinball to reliably boot.
564 if(seekTime > 3386880 * 2) seekTime = 3386880 * 2;
565 CDR_LOG("seek: %.2f %.2f\n", (float)seekTime / PSXCLK, (float)seekTime / cdReadTime);
569 static void cdrReadInterrupt(void);
570 static void cdrPrepCdda(s16 *buf, int samples);
571 static void cdrAttenuate(s16 *buf, int samples, int stereo);
573 void cdrPlaySeekReadInterrupt(void)
580 if (!cdr.Play && (cdr.StatP & STATUS_SEEK)) {
582 CDR_LOG_I("cdrom: seek stat hack\n");
583 CDRPLAYSEEKREAD_INT(0x1000, 1);
587 cdr.StatP |= STATUS_ROTATING;
588 SetPlaySeekRead(cdr.StatP, 0);
589 cdr.Result[0] = cdr.StatP;
593 Find_CurTrack(cdr.SetSectorPlay);
594 ReadTrack(cdr.SetSectorPlay);
595 cdr.TrackChanged = FALSE;
599 if (!cdr.Play) return;
601 CDR_LOG( "CDDA - %d:%d:%d\n",
602 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2] );
604 SetPlaySeekRead(cdr.StatP, STATUS_PLAY);
605 if (memcmp(cdr.SetSectorPlay, cdr.SetSectorEnd, 3) == 0) {
607 SetPlaySeekRead(cdr.StatP, 0);
608 cdr.TrackChanged = TRUE;
611 CDR_readCDDA(cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2], (u8 *)read_buf);
614 if (!cdr.Stat && (cdr.Mode & (MODE_AUTOPAUSE|MODE_REPORT)))
615 cdrPlayInterrupt_Autopause();
617 if (!cdr.Muted && !Config.Cdda) {
618 cdrPrepCdda(read_buf, CD_FRAMESIZE_RAW / 4);
619 cdrAttenuate(read_buf, CD_FRAMESIZE_RAW / 4, 1);
620 SPU_playCDDAchannel(read_buf, CD_FRAMESIZE_RAW, psxRegs.cycle, cdr.FirstSector);
624 cdr.SetSectorPlay[2]++;
625 if (cdr.SetSectorPlay[2] == 75) {
626 cdr.SetSectorPlay[2] = 0;
627 cdr.SetSectorPlay[1]++;
628 if (cdr.SetSectorPlay[1] == 60) {
629 cdr.SetSectorPlay[1] = 0;
630 cdr.SetSectorPlay[0]++;
634 // update for CdlGetlocP/autopause
635 generate_subq(cdr.SetSectorPlay);
637 CDRPLAYSEEKREAD_INT(cdReadTime, 0);
640 void cdrInterrupt(void) {
641 int no_busy_error = 0;
642 int start_rotating = 0;
644 unsigned int seekTime = 0;
645 u32 second_resp_time = 0;
652 CDR_LOG_I("cdrom: cmd %02x with irqstat %x\n", cdr.CmdInProgress, cdr.Stat);
660 cdr.Result[0] = cdr.StatP;
661 cdr.Stat = Acknowledge;
663 Cmd = cdr.CmdInProgress;
664 cdr.CmdInProgress = 0;
674 if (cdr.DriveState != DRIVESTATE_LID_OPEN)
675 cdr.StatP &= ~STATUS_SHELLOPEN;
680 CDR_LOG("CDROM setloc command (%02X, %02X, %02X)\n", cdr.Param[0], cdr.Param[1], cdr.Param[2]);
682 // MM must be BCD, SS must be BCD and <0x60, FF must be BCD and <0x75
683 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))
685 CDR_LOG("Invalid/out of range seek to %02X:%02X:%02X\n", cdr.Param[0], cdr.Param[1], cdr.Param[2]);
686 error = ERROR_INVALIDARG;
691 for (i = 0; i < 3; i++)
692 set_loc[i] = btoi(cdr.Param[i]);
693 memcpy(cdr.SetSector, set_loc, 3);
694 cdr.SetSector[3] = 0;
695 cdr.SetlocPending = 1;
704 cdr.FastBackward = 0;
708 // - Pause player, hit Track 01/02/../xx (Setloc issued!!)
710 if (ParamC != 0 && cdr.Param[0] != 0) {
711 int track = btoi( cdr.Param[0] );
713 if (track <= cdr.ResultTN[1])
714 cdr.CurTrack = track;
716 CDR_LOG("PLAY track %d\n", cdr.CurTrack);
718 if (CDR_getTD((u8)cdr.CurTrack, cdr.ResultTD) != -1) {
719 for (i = 0; i < 3; i++)
720 set_loc[i] = cdr.ResultTD[2 - i];
721 seekTime = cdrSeekTime(set_loc);
722 memcpy(cdr.SetSectorPlay, set_loc, 3);
725 else if (cdr.SetlocPending) {
726 seekTime = cdrSeekTime(cdr.SetSector);
727 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
730 CDR_LOG("PLAY Resume @ %d:%d:%d\n",
731 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2]);
733 cdr.SetlocPending = 0;
736 Rayman: detect track changes
739 Twisted Metal 2: skip PREGAP + starting accurate SubQ
740 - plays tracks without retry play
742 Wild 9: skip PREGAP + starting accurate SubQ
743 - plays tracks without retry play
745 Find_CurTrack(cdr.SetSectorPlay);
746 ReadTrack(cdr.SetSectorPlay);
747 cdr.TrackChanged = FALSE;
751 CDR_play(cdr.SetSectorPlay);
753 SetPlaySeekRead(cdr.StatP, STATUS_SEEK | STATUS_ROTATING);
755 // BIOS player - set flag again
758 CDRPLAYSEEKREAD_INT(cdReadTime + seekTime, 1);
763 // TODO: error 80 if stopped
766 // GameShark CD Player: Calls 2x + Play 2x
768 cdr.FastBackward = 0;
774 // GameShark CD Player: Calls 2x + Play 2x
775 cdr.FastBackward = 1;
780 if (cdr.DriveState != DRIVESTATE_STOPPED) {
781 error = ERROR_INVALIDARG;
784 second_resp_time = cdReadTime * 125 / 2;
788 case CdlStandby + 0x100:
794 // grab time for current track
795 CDR_getTD((u8)(cdr.CurTrack), cdr.ResultTD);
797 cdr.SetSectorPlay[0] = cdr.ResultTD[2];
798 cdr.SetSectorPlay[1] = cdr.ResultTD[1];
799 cdr.SetSectorPlay[2] = cdr.ResultTD[0];
804 SetPlaySeekRead(cdr.StatP, 0);
805 cdr.StatP &= ~STATUS_ROTATING;
807 second_resp_time = 0x800;
808 if (cdr.DriveState == DRIVESTATE_STANDBY)
809 second_resp_time = cdReadTime * 30 / 2;
811 cdr.DriveState = DRIVESTATE_STOPPED;
814 case CdlStop + 0x100:
822 Gundam Battle Assault 2: much slower (*)
823 - Fixes boot, gameplay
825 Hokuto no Ken 2: slower
826 - Fixes intro + subtitles
828 InuYasha - Feudal Fairy Tale: slower
831 /* Gameblabla - Tightening the timings (as taken from Duckstation).
832 * The timings from Duckstation are based upon hardware tests.
833 * Mednafen's timing don't work for Gundam Battle Assault 2 in PAL/50hz mode,
834 * seems to be timing sensitive as it can depend on the CPU's clock speed.
836 if (!(cdr.StatP & (STATUS_PLAY | STATUS_READ)))
838 second_resp_time = 7000;
842 second_resp_time = (((cdr.Mode & MODE_SPEED) ? 2 : 1) * 1000000);
844 SetPlaySeekRead(cdr.StatP, 0);
848 case CdlPause + 0x100:
855 SetPlaySeekRead(cdr.StatP, 0);
857 cdr.Mode = 0x20; /* This fixes This is Football 2, Pooh's Party lockups */
858 second_resp_time = 4100000;
863 case CdlReset + 0x100:
876 cdr.File = cdr.Param[0];
877 cdr.Channel = cdr.Param[1];
881 CDR_LOG("cdrWrite1() Log: Setmode %x\n", cdr.Param[0]);
882 cdr.Mode = cdr.Param[0];
887 /* Gameblabla : According to mednafen, Result size should be 5 and done this way. */
889 cdr.Result[1] = cdr.Mode;
891 cdr.Result[3] = cdr.File;
892 cdr.Result[4] = cdr.Channel;
898 memcpy(cdr.Result, cdr.Transfer, 8);
903 memcpy(&cdr.Result, &cdr.subq, 8);
906 case CdlReadT: // SetSession?
908 second_resp_time = cdReadTime * 290 / 4;
912 case CdlReadT + 0x100:
918 if (CDR_getTN(cdr.ResultTN) == -1) {
919 cdr.Stat = DiskError;
920 cdr.Result[0] |= STATUS_ERROR;
922 cdr.Stat = Acknowledge;
923 cdr.Result[1] = itob(cdr.ResultTN[0]);
924 cdr.Result[2] = itob(cdr.ResultTN[1]);
929 cdr.Track = btoi(cdr.Param[0]);
931 if (CDR_getTD(cdr.Track, cdr.ResultTD) == -1) {
932 cdr.Stat = DiskError;
933 cdr.Result[0] |= STATUS_ERROR;
935 cdr.Stat = Acknowledge;
936 cdr.Result[0] = cdr.StatP;
937 cdr.Result[1] = itob(cdr.ResultTD[2]);
938 cdr.Result[2] = itob(cdr.ResultTD[1]);
939 /* According to Nocash's documentation, the function doesn't care about ff.
940 * This can be seen also in Mednafen's implementation. */
941 //cdr.Result[3] = itob(cdr.ResultTD[0]);
949 SetPlaySeekRead(cdr.StatP, STATUS_SEEK);
951 seekTime = cdrSeekTime(cdr.SetSector);
952 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
954 Crusaders of Might and Magic = 0.5x-4x
955 - fix cutscene speech start
961 - fix cutscene speech
966 CDRPLAYSEEKREAD_INT(cdReadTime + seekTime, 1);
971 switch (cdr.Param[0]) {
972 case 0x20: // System Controller ROM Version
974 memcpy(cdr.Result, Test20, 4);
978 memcpy(cdr.Result, Test22, 4);
980 case 0x23: case 0x24:
982 memcpy(cdr.Result, Test23, 4);
989 second_resp_time = 20480;
994 cdr.Result[0] = cdr.StatP;
999 // 0x10 - audio | 0x40 - disk missing | 0x80 - unlicensed
1000 if (CDR_getStatus(&stat) == -1 || stat.Type == 0 || stat.Type == 0xff) {
1001 cdr.Result[1] = 0xc0;
1005 cdr.Result[1] |= 0x10;
1006 if (CdromId[0] == '\0')
1007 cdr.Result[1] |= 0x80;
1009 cdr.Result[0] |= (cdr.Result[1] >> 4) & 0x08;
1011 /* This adds the string "PCSX" in Playstation bios boot screen */
1012 memcpy((char *)&cdr.Result[4], "PCSX", 4);
1013 cdr.Stat = Complete;
1019 SetPlaySeekRead(cdr.StatP, 0);
1020 // yes, it really sets STATUS_SHELLOPEN
1021 cdr.StatP |= STATUS_SHELLOPEN;
1022 cdr.DriveState = DRIVESTATE_RESCAN_CD;
1033 second_resp_time = cdReadTime * 180 / 4;
1038 case CdlReadToc + 0x100:
1039 cdr.Stat = Complete;
1045 Find_CurTrack(cdr.SetlocPending ? cdr.SetSector : cdr.SetSectorPlay);
1047 if ((cdr.Mode & MODE_CDDA) && cdr.CurTrack > 1)
1048 // Read* acts as play for cdda tracks in cdda mode
1052 if (cdr.SetlocPending) {
1053 seekTime = cdrSeekTime(cdr.SetSector);
1054 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
1055 cdr.SetlocPending = 0;
1058 cdr.FirstSector = 1;
1060 // Fighting Force 2 - update subq time immediately
1062 ReadTrack(cdr.SetSectorPlay);
1064 CDRPLAYSEEKREAD_INT(((cdr.Mode & 0x80) ? (cdReadTime) : cdReadTime * 2) + seekTime, 1);
1066 SetPlaySeekRead(cdr.StatP, STATUS_SEEK);
1071 CDR_LOG_I("Invalid command: %02x\n", Cmd);
1072 error = ERROR_INVALIDCMD;
1077 cdr.Result[0] = cdr.StatP | STATUS_ERROR;
1078 cdr.Result[1] = error;
1079 cdr.Stat = DiskError;
1083 if (cdr.DriveState == DRIVESTATE_STOPPED && start_rotating) {
1084 cdr.DriveState = DRIVESTATE_STANDBY;
1085 cdr.StatP |= STATUS_ROTATING;
1088 if (!no_busy_error) {
1089 switch (cdr.DriveState) {
1090 case DRIVESTATE_LID_OPEN:
1091 case DRIVESTATE_RESCAN_CD:
1092 case DRIVESTATE_PREPARE_CD:
1094 cdr.Result[0] = cdr.StatP | STATUS_ERROR;
1095 cdr.Result[1] = ERROR_NOTREADY;
1096 cdr.Stat = DiskError;
1101 if (second_resp_time) {
1102 cdr.CmdInProgress = Cmd | 0x100;
1103 CDR_INT(second_resp_time);
1105 else if (cdr.Cmd && cdr.Cmd != (Cmd & 0xff)) {
1106 cdr.CmdInProgress = cdr.Cmd;
1107 CDR_LOG_I("cdrom: cmd %02x came before %02x finished\n", cdr.Cmd, Cmd);
1114 #define ssat32_to_16(v) \
1115 asm("ssat %0,#16,%1" : "=r" (v) : "r" (v))
1117 #define ssat32_to_16(v) do { \
1118 if (v < -32768) v = -32768; \
1119 else if (v > 32767) v = 32767; \
1123 static void cdrPrepCdda(s16 *buf, int samples)
1125 #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
1127 for (i = 0; i < samples; i++) {
1128 buf[i * 2 + 0] = SWAP16(buf[i * 2 + 0]);
1129 buf[i * 2 + 1] = SWAP16(buf[i * 2 + 1]);
1134 static void cdrAttenuate(s16 *buf, int samples, int stereo)
1137 int ll = cdr.AttenuatorLeftToLeft;
1138 int lr = cdr.AttenuatorLeftToRight;
1139 int rl = cdr.AttenuatorRightToLeft;
1140 int rr = cdr.AttenuatorRightToRight;
1142 if (lr == 0 && rl == 0 && 0x78 <= ll && ll <= 0x88 && 0x78 <= rr && rr <= 0x88)
1145 if (!stereo && ll == 0x40 && lr == 0x40 && rl == 0x40 && rr == 0x40)
1149 for (i = 0; i < samples; i++) {
1152 l = (l * ll + r * rl) >> 7;
1153 r = (r * rr + l * lr) >> 7;
1161 for (i = 0; i < samples; i++) {
1163 l = l * (ll + rl) >> 7;
1164 //r = r * (rr + lr) >> 7;
1172 static void cdrReadInterruptSetResult(unsigned char result)
1175 CDR_LOG_I("cdrom: %d:%02d:%02d irq miss, cmd=%02x irqstat=%02x\n",
1176 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2],
1177 cdr.CmdInProgress, cdr.Stat);
1178 cdr.Irq1Pending = result;
1182 cdr.Result[0] = result;
1183 cdr.Stat = (result & STATUS_ERROR) ? DiskError : DataReady;
1187 static void cdrUpdateTransferBuf(const u8 *buf)
1191 memcpy(cdr.Transfer, buf, DATA_SIZE);
1192 CheckPPFCache(cdr.Transfer, cdr.Prev[0], cdr.Prev[1], cdr.Prev[2]);
1193 CDR_LOG("cdr.Transfer %x:%x:%x\n", cdr.Transfer[0], cdr.Transfer[1], cdr.Transfer[2]);
1194 if (cdr.FifoOffset < 2048 + 12)
1195 CDR_LOG("cdrom: FifoOffset(1) %d/%d\n", cdr.FifoOffset, cdr.FifoSize);
1198 static void cdrReadInterrupt(void)
1200 u8 *buf = NULL, *hdr;
1202 SetPlaySeekRead(cdr.StatP, STATUS_READ | STATUS_ROTATING);
1204 ReadTrack(cdr.SetSectorPlay);
1206 buf = CDR_getBuffer();
1211 CDR_LOG_I("cdrReadInterrupt() Log: err\n");
1212 memset(cdr.Transfer, 0, DATA_SIZE);
1213 cdrReadInterruptSetResult(cdr.StatP | STATUS_ERROR);
1217 if (!cdr.Irq1Pending)
1218 cdrUpdateTransferBuf(buf);
1220 if ((!cdr.Muted) && (cdr.Mode & MODE_STRSND) && (!Config.Xa) && (cdr.FirstSector != -1)) { // CD-XA
1222 // Firemen 2: Multi-XA files - briefings, cutscenes
1223 if( cdr.FirstSector == 1 && (cdr.Mode & MODE_SF)==0 ) {
1225 cdr.Channel = hdr[1];
1229 * Skips playing on channel 255.
1230 * Fixes missing audio in Blue's Clues : Blue's Big Musical. (Should also fix Taxi 2)
1231 * TODO : Check if this is the proper behaviour.
1233 if ((hdr[2] & 0x4) && hdr[0] == cdr.File && hdr[1] == cdr.Channel && cdr.Channel != 255) {
1234 int ret = xa_decode_sector(&cdr.Xa, buf + 4, cdr.FirstSector);
1236 cdrAttenuate(cdr.Xa.pcm, cdr.Xa.nsamples, cdr.Xa.stereo);
1237 SPU_playADPCMchannel(&cdr.Xa, psxRegs.cycle, cdr.FirstSector);
1238 cdr.FirstSector = 0;
1240 else cdr.FirstSector = -1;
1245 Croc 2: $40 - only FORM1 (*)
1246 Judge Dredd: $C8 - only FORM1 (*)
1247 Sim Theme Park - no adpcm at all (zero)
1250 if (!(cdr.Mode & MODE_STRSND) || !(buf[4+2] & 0x4))
1251 cdrReadInterruptSetResult(cdr.StatP);
1253 cdr.SetSectorPlay[2]++;
1254 if (cdr.SetSectorPlay[2] == 75) {
1255 cdr.SetSectorPlay[2] = 0;
1256 cdr.SetSectorPlay[1]++;
1257 if (cdr.SetSectorPlay[1] == 60) {
1258 cdr.SetSectorPlay[1] = 0;
1259 cdr.SetSectorPlay[0]++;
1263 if (!cdr.Irq1Pending) {
1264 // update for CdlGetlocP
1265 ReadTrack(cdr.SetSectorPlay);
1268 CDRPLAYSEEKREAD_INT((cdr.Mode & MODE_SPEED) ? (cdReadTime / 2) : cdReadTime, 0);
1271 static void doMissedIrqs(void)
1273 if (cdr.Irq1Pending)
1275 // hand out the "newest" sector, according to nocash
1276 cdrUpdateTransferBuf(CDR_getBuffer());
1277 CDR_LOG_I("cdrom: %x:%02x:%02x loaded on ack\n",
1278 cdr.Transfer[0], cdr.Transfer[1], cdr.Transfer[2]);
1280 cdr.Result[0] = cdr.Irq1Pending;
1281 cdr.Stat = (cdr.Irq1Pending & STATUS_ERROR) ? DiskError : DataReady;
1282 cdr.Irq1Pending = 0;
1286 if (!(psxRegs.interrupt & (1 << PSXINT_CDR)) && cdr.CmdInProgress)
1296 bit 5 - 1 result ready
1298 bit 7 - 1 command being processed
1301 unsigned char cdrRead0(void) {
1302 if (cdr.ResultReady)
1307 cdr.Ctrl |= 0x40; // data fifo not empty
1309 // What means the 0x10 and the 0x08 bits? I only saw it used by the bios
1312 CDR_LOG_IO("cdr r0.sta: %02x\n", cdr.Ctrl);
1314 return psxHu8(0x1800) = cdr.Ctrl;
1317 void cdrWrite0(unsigned char rt) {
1318 CDR_LOG_IO("cdr w0.idx: %02x\n", rt);
1320 cdr.Ctrl = (rt & 3) | (cdr.Ctrl & ~3);
1323 unsigned char cdrRead1(void) {
1324 if ((cdr.ResultP & 0xf) < cdr.ResultC)
1325 psxHu8(0x1801) = cdr.Result[cdr.ResultP & 0xf];
1329 if (cdr.ResultP == cdr.ResultC)
1330 cdr.ResultReady = 0;
1332 CDR_LOG_IO("cdr r1.rsp: %02x #%u\n", psxHu8(0x1801), cdr.ResultP - 1);
1334 return psxHu8(0x1801);
1337 void cdrWrite1(unsigned char rt) {
1338 const char *rnames[] = { "cmd", "smd", "smc", "arr" }; (void)rnames;
1339 CDR_LOG_IO("cdr w1.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1341 switch (cdr.Ctrl & 3) {
1345 cdr.AttenuatorRightToRightT = rt;
1351 #ifdef CDR_LOG_CMD_IRQ
1352 SysPrintf("CD1 write: %x (%s)", rt, CmdName[rt]);
1355 SysPrintf(" Param[%d] = {", cdr.ParamC);
1356 for (i = 0; i < cdr.ParamC; i++)
1357 SysPrintf(" %x,", cdr.Param[i]);
1364 cdr.ResultReady = 0;
1367 if (!cdr.CmdInProgress) {
1368 cdr.CmdInProgress = rt;
1369 // should be something like 12k + controller delays
1373 CDR_LOG_I("cdr: cmd while busy: %02x, prev %02x, busy %02x\n",
1374 rt, cdr.Cmd, cdr.CmdInProgress);
1375 if (cdr.CmdInProgress < 0x100) // no pending 2nd response
1376 cdr.CmdInProgress = rt;
1382 unsigned char cdrRead2(void) {
1383 unsigned char ret = 0;
1385 if (cdr.FifoOffset < cdr.FifoSize)
1386 ret = cdr.Transfer[cdr.FifoOffset++];
1388 CDR_LOG_I("cdrom: read empty fifo (%d)\n", cdr.FifoSize);
1390 CDR_LOG_IO("cdr r2.dat: %02x\n", ret);
1394 void cdrWrite2(unsigned char rt) {
1395 const char *rnames[] = { "prm", "ien", "all", "arl" }; (void)rnames;
1396 CDR_LOG_IO("cdr w2.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1398 switch (cdr.Ctrl & 3) {
1400 if (cdr.ParamC < 8) // FIXME: size and wrapping
1401 cdr.Param[cdr.ParamC++] = rt;
1408 cdr.AttenuatorLeftToLeftT = rt;
1411 cdr.AttenuatorRightToLeftT = rt;
1416 unsigned char cdrRead3(void) {
1418 psxHu8(0x1803) = cdr.Stat | 0xE0;
1420 psxHu8(0x1803) = cdr.Reg2 | 0xE0;
1422 CDR_LOG_IO("cdr r3.%s: %02x\n", (cdr.Ctrl & 1) ? "ifl" : "ien", psxHu8(0x1803));
1423 return psxHu8(0x1803);
1426 void cdrWrite3(unsigned char rt) {
1427 const char *rnames[] = { "req", "ifl", "alr", "ava" }; (void)rnames;
1428 CDR_LOG_IO("cdr w3.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1430 switch (cdr.Ctrl & 3) {
1434 #ifdef CDR_LOG_CMD_IRQ
1436 SysPrintf("ack %02x\n", cdr.Stat & rt);
1445 cdr.AttenuatorLeftToRightT = rt;
1449 memcpy(&cdr.AttenuatorLeftToLeft, &cdr.AttenuatorLeftToLeftT, 4);
1450 CDR_LOG("CD-XA Volume: %02x %02x | %02x %02x\n",
1451 cdr.AttenuatorLeftToLeft, cdr.AttenuatorLeftToRight,
1452 cdr.AttenuatorRightToLeft, cdr.AttenuatorRightToRight);
1458 if ((rt & 0x80) && cdr.FifoOffset < cdr.FifoSize) {
1459 CDR_LOG("cdrom: FifoOffset(2) %d/%d\n", cdr.FifoOffset, cdr.FifoSize);
1461 else if (rt & 0x80) {
1462 switch (cdr.Mode & 0x30) {
1463 case MODE_SIZE_2328:
1465 cdr.FifoOffset = 12;
1466 cdr.FifoSize = 2048 + 12;
1469 case MODE_SIZE_2340:
1472 cdr.FifoSize = 2340;
1476 else if (!(rt & 0xc0))
1477 cdr.FifoOffset = DATA_SIZE; // fifo empty
1480 void psxDma3(u32 madr, u32 bcr, u32 chcr) {
1485 CDR_LOG("psxDma3() Log: *** DMA 3 *** %x addr = %x size = %x\n", chcr, madr, bcr);
1487 switch (chcr & 0x71000000) {
1489 ptr = (u8 *)PSXM(madr);
1490 if (ptr == INVALID_PTR) {
1491 CDR_LOG_I("psxDma3() Log: *** DMA 3 *** NULL Pointer!\n");
1495 cdsize = (((bcr - 1) & 0xffff) + 1) * 4;
1498 GS CDX: Enhancement CD crash
1501 - Spams DMA3 and gets buffer overrun
1503 size = DATA_SIZE - cdr.FifoOffset;
1508 memcpy(ptr, cdr.Transfer + cdr.FifoOffset, size);
1509 cdr.FifoOffset += size;
1510 psxCpu->Clear(madr, size / 4);
1513 CDR_LOG_I("cdrom: dma3 %d/%d\n", size, cdsize);
1515 CDRDMA_INT((cdsize/4) * 24);
1517 HW_DMA3_CHCR &= SWAPu32(~0x10000000);
1519 HW_DMA3_MADR = SWAPu32(madr + cdsize);
1520 HW_DMA3_BCR &= SWAPu32(0xffff0000);
1524 psxRegs.cycle += (cdsize/4) * 24 - 20;
1529 CDR_LOG_I("psxDma3() Log: Unknown cddma %x\n", chcr);
1533 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1537 void cdrDmaInterrupt(void)
1539 if (HW_DMA3_CHCR & SWAP32(0x01000000))
1541 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1546 static void getCdInfo(void)
1550 CDR_getTN(cdr.ResultTN);
1551 CDR_getTD(0, cdr.SetSectorEnd);
1552 tmp = cdr.SetSectorEnd[0];
1553 cdr.SetSectorEnd[0] = cdr.SetSectorEnd[2];
1554 cdr.SetSectorEnd[2] = tmp;
1558 memset(&cdr, 0, sizeof(cdr));
1564 cdr.DriveState = DRIVESTATE_STANDBY;
1565 cdr.StatP = STATUS_ROTATING;
1566 cdr.FifoOffset = DATA_SIZE; // fifo empty
1568 // BIOS player - default values
1569 cdr.AttenuatorLeftToLeft = 0x80;
1570 cdr.AttenuatorLeftToRight = 0x00;
1571 cdr.AttenuatorRightToLeft = 0x00;
1572 cdr.AttenuatorRightToRight = 0x80;
1577 int cdrFreeze(void *f, int Mode) {
1581 if (Mode == 0 && !Config.Cdda)
1584 cdr.freeze_ver = 0x63647202;
1585 gzfreeze(&cdr, sizeof(cdr));
1588 cdr.ParamP = cdr.ParamC;
1589 tmp = cdr.FifoOffset;
1592 gzfreeze(&tmp, sizeof(tmp));
1597 cdr.FifoOffset = tmp;
1598 cdr.FifoSize = (cdr.Mode & 0x20) ? 2340 : 2048 + 12;
1600 // read right sub data
1601 tmpp[0] = btoi(cdr.Prev[0]);
1602 tmpp[1] = btoi(cdr.Prev[1]);
1603 tmpp[2] = btoi(cdr.Prev[2]);
1608 if (cdr.freeze_ver < 0x63647202)
1609 memcpy(cdr.SetSectorPlay, cdr.SetSector, 3);
1611 Find_CurTrack(cdr.SetSectorPlay);
1613 CDR_play(cdr.SetSectorPlay);
1614 if (psxRegs.interrupt & (1 << PSXINT_CDRPLAY_OLD))
1615 CDRPLAYSEEKREAD_INT((cdr.Mode & 0x80) ? (cdReadTime / 2) : cdReadTime, 1);
1618 if ((cdr.freeze_ver & 0xffffff00) != 0x63647200) {
1619 // old versions did not latch Reg2, have to fixup..
1620 if (cdr.Reg2 == 0) {
1621 SysPrintf("cdrom: fixing up old savestate\n");
1624 // also did not save Attenuator..
1625 if ((cdr.AttenuatorLeftToLeft | cdr.AttenuatorLeftToRight
1626 | cdr.AttenuatorRightToLeft | cdr.AttenuatorRightToRight) == 0)
1628 cdr.AttenuatorLeftToLeft = cdr.AttenuatorRightToRight = 0x80;
1636 void LidInterrupt(void) {
1638 cdrLidSeekInterrupt();