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(...)
41 #define CDR_LOG_IO SysPrintf
43 #define CDR_LOG_IO(...)
45 //#define CDR_LOG_CMD_IRQ
49 unsigned char Reg1Mode;
51 unsigned char CmdProcess;
57 unsigned char Transfer[DATA_SIZE];
61 unsigned char Relative[3];
62 unsigned char Absolute[3];
64 unsigned char TrackChanged;
65 unsigned char pad1[3];
66 unsigned int freeze_ver;
68 unsigned char Prev[4];
69 unsigned char Param[8];
70 unsigned char Result[16];
74 unsigned char ResultC;
75 unsigned char ResultP;
76 unsigned char ResultReady;
79 unsigned char SetlocPending;
82 unsigned char ResultTN[6];
83 unsigned char ResultTD[4];
84 unsigned char SetSectorPlay[4];
85 unsigned char SetSectorEnd[4];
86 unsigned char SetSector[4];
90 int Mode, File, Channel;
110 u8 AttenuatorLeftToLeft, AttenuatorLeftToRight;
111 u8 AttenuatorRightToRight, AttenuatorRightToLeft;
112 u8 AttenuatorLeftToLeftT, AttenuatorLeftToRightT;
113 u8 AttenuatorRightToRightT, AttenuatorRightToLeftT;
115 static unsigned char *pTransfer;
116 static s16 read_buf[CD_FRAMESIZE_RAW/2];
118 /* CD-ROM magic numbers */
119 #define CdlSync 0 /* nocash documentation : "Uh, actually, returns error code 40h = Invalid Command...?" */
124 #define CdlBackward 5
132 #define CdlSetfilter 13
133 #define CdlSetmode 14
134 #define CdlGetparam 15
135 #define CdlGetlocL 16
136 #define CdlGetlocP 17
142 #define CdlSetclock 23
143 #define CdlGetclock 24
149 #define CdlReadToc 30
151 #ifdef CDR_LOG_CMD_IRQ
152 static const char * const CmdName[0x100] = {
153 "CdlSync", "CdlNop", "CdlSetloc", "CdlPlay",
154 "CdlForward", "CdlBackward", "CdlReadN", "CdlStandby",
155 "CdlStop", "CdlPause", "CdlReset", "CdlMute",
156 "CdlDemute", "CdlSetfilter", "CdlSetmode", "CdlGetparam",
157 "CdlGetlocL", "CdlGetlocP", "CdlReadT", "CdlGetTN",
158 "CdlGetTD", "CdlSeekL", "CdlSeekP", "CdlSetclock",
159 "CdlGetclock", "CdlTest", "CdlID", "CdlReadS",
160 "CdlInit", NULL, "CDlReadToc", NULL
164 unsigned char Test04[] = { 0 };
165 unsigned char Test05[] = { 0 };
166 unsigned char Test20[] = { 0x98, 0x06, 0x10, 0xC3 };
167 unsigned char Test22[] = { 0x66, 0x6F, 0x72, 0x20, 0x45, 0x75, 0x72, 0x6F };
168 unsigned char Test23[] = { 0x43, 0x58, 0x44, 0x32, 0x39 ,0x34, 0x30, 0x51 };
174 #define Acknowledge 3
179 #define MODE_SPEED (1<<7) // 0x80
180 #define MODE_STRSND (1<<6) // 0x40 ADPCM on/off
181 #define MODE_SIZE_2340 (1<<5) // 0x20
182 #define MODE_SIZE_2328 (1<<4) // 0x10
183 #define MODE_SIZE_2048 (0<<4) // 0x00
184 #define MODE_SF (1<<3) // 0x08 channel on/off
185 #define MODE_REPORT (1<<2) // 0x04
186 #define MODE_AUTOPAUSE (1<<1) // 0x02
187 #define MODE_CDDA (1<<0) // 0x01
190 #define STATUS_PLAY (1<<7) // 0x80
191 #define STATUS_SEEK (1<<6) // 0x40
192 #define STATUS_READ (1<<5) // 0x20
193 #define STATUS_SHELLOPEN (1<<4) // 0x10
194 #define STATUS_UNKNOWN3 (1<<3) // 0x08
195 #define STATUS_UNKNOWN2 (1<<2) // 0x04
196 #define STATUS_ROTATING (1<<1) // 0x02
197 #define STATUS_ERROR (1<<0) // 0x01
200 #define ERROR_NOTREADY (1<<7) // 0x80
201 #define ERROR_INVALIDCMD (1<<6) // 0x40
202 #define ERROR_INVALIDARG (1<<5) // 0x20
204 // 1x = 75 sectors per second
205 // PSXCLK = 1 sec in the ps
206 // so (PSXCLK / 75) = cdr read time (linuzappz)
207 #define cdReadTime (PSXCLK / 75)
210 DRIVESTATE_STANDBY = 0, // pause, play, read
212 DRIVESTATE_RESCAN_CD,
213 DRIVESTATE_PREPARE_CD,
217 static struct CdrStat stat;
219 static unsigned int msf2sec(const u8 *msf) {
220 return ((msf[0] * 60 + msf[1]) * 75) + msf[2];
223 // for that weird psemu API..
224 static unsigned int fsm2sec(const u8 *msf) {
225 return ((msf[2] * 60 + msf[1]) * 75) + msf[0];
228 static void sec2msf(unsigned int s, u8 *msf) {
229 msf[0] = s / 75 / 60;
230 s = s - msf[0] * 75 * 60;
237 #define CDR_INT(eCycle) { \
238 psxRegs.interrupt |= (1 << PSXINT_CDR); \
239 psxRegs.intCycle[PSXINT_CDR].cycle = eCycle; \
240 psxRegs.intCycle[PSXINT_CDR].sCycle = psxRegs.cycle; \
241 new_dyna_set_event(PSXINT_CDR, eCycle); \
245 #define CDREAD_INT(eCycle) { \
246 psxRegs.interrupt |= (1 << PSXINT_CDREAD); \
247 psxRegs.intCycle[PSXINT_CDREAD].cycle = eCycle; \
248 psxRegs.intCycle[PSXINT_CDREAD].sCycle = psxRegs.cycle; \
249 new_dyna_set_event(PSXINT_CDREAD, eCycle); \
252 // cdrLidSeekInterrupt
253 #define CDRLID_INT(eCycle) { \
254 psxRegs.interrupt |= (1 << PSXINT_CDRLID); \
255 psxRegs.intCycle[PSXINT_CDRLID].cycle = eCycle; \
256 psxRegs.intCycle[PSXINT_CDRLID].sCycle = psxRegs.cycle; \
257 new_dyna_set_event(PSXINT_CDRLID, eCycle); \
261 #define CDRSEEKPLAY_INT(eCycle) { \
262 psxRegs.interrupt |= (1 << PSXINT_CDRPLAY); \
263 psxRegs.intCycle[PSXINT_CDRPLAY].cycle = eCycle; \
264 psxRegs.intCycle[PSXINT_CDRPLAY].sCycle = psxRegs.cycle; \
265 new_dyna_set_event(PSXINT_CDRPLAY, eCycle); \
268 #define StopReading() { \
270 psxRegs.interrupt &= ~(1 << PSXINT_CDREAD); \
273 #define StopCdda() { \
274 if (cdr.Play && !Config.Cdda) CDR_stop(); \
276 cdr.FastForward = 0; \
277 cdr.FastBackward = 0; \
280 #define SetPlaySeekRead(x, f) { \
281 x &= ~(STATUS_PLAY | STATUS_SEEK | STATUS_READ); \
285 #define SetResultSize(size) { \
287 cdr.ResultC = size; \
288 cdr.ResultReady = 1; \
291 static void setIrq(int log_cmd)
293 if (cdr.Stat & cdr.Reg2)
294 psxHu32ref(0x1070) |= SWAP32((u32)0x4);
296 #ifdef CDR_LOG_CMD_IRQ
299 SysPrintf("CDR IRQ=%d cmd %02x stat %02x: ",
300 !!(cdr.Stat & cdr.Reg2), log_cmd, cdr.Stat);
301 for (i = 0; i < cdr.ResultC; i++)
302 SysPrintf("%02x ", cdr.Result[i]);
308 // timing used in this function was taken from tests on real hardware
309 // (yes it's slow, but you probably don't want to modify it)
310 void cdrLidSeekInterrupt()
312 switch (cdr.DriveState) {
314 case DRIVESTATE_STANDBY:
317 SetPlaySeekRead(cdr.StatP, 0);
319 if (CDR_getStatus(&stat) == -1)
322 if (stat.Status & STATUS_SHELLOPEN)
324 cdr.DriveState = DRIVESTATE_LID_OPEN;
329 case DRIVESTATE_LID_OPEN:
330 if (CDR_getStatus(&stat) == -1)
331 stat.Status &= ~STATUS_SHELLOPEN;
334 if (!(cdr.StatP & STATUS_SHELLOPEN)) {
335 cdr.StatP |= STATUS_SHELLOPEN;
337 // could generate error irq here, but real hardware
338 // only sometimes does that
339 // (not done when lots of commands are sent?)
341 CDRLID_INT(cdReadTime * 30);
344 else if (cdr.StatP & STATUS_ROTATING) {
345 cdr.StatP &= ~STATUS_ROTATING;
347 else if (!(stat.Status & STATUS_SHELLOPEN)) {
351 // cdr.StatP STATUS_SHELLOPEN is "sticky"
352 // and is only cleared by CdlNop
354 cdr.DriveState = DRIVESTATE_RESCAN_CD;
355 CDRLID_INT(cdReadTime * 105);
360 CDRLID_INT(cdReadTime * 3);
363 case DRIVESTATE_RESCAN_CD:
364 cdr.StatP |= STATUS_ROTATING;
365 cdr.DriveState = DRIVESTATE_PREPARE_CD;
367 // this is very long on real hardware, over 6 seconds
368 // make it a bit faster here...
369 CDRLID_INT(cdReadTime * 150);
372 case DRIVESTATE_PREPARE_CD:
373 cdr.StatP |= STATUS_SEEK;
375 cdr.DriveState = DRIVESTATE_STANDBY;
376 CDRLID_INT(cdReadTime * 26);
381 static void Find_CurTrack(const u8 *time)
385 current = msf2sec(time);
387 for (cdr.CurTrack = 1; cdr.CurTrack < cdr.ResultTN[1]; cdr.CurTrack++) {
388 CDR_getTD(cdr.CurTrack + 1, cdr.ResultTD);
389 sect = fsm2sec(cdr.ResultTD);
390 if (sect - current >= 150)
395 static void generate_subq(const u8 *time)
397 unsigned char start[3], next[3];
398 unsigned int this_s, start_s, next_s, pregap;
401 CDR_getTD(cdr.CurTrack, start);
402 if (cdr.CurTrack + 1 <= cdr.ResultTN[1]) {
404 CDR_getTD(cdr.CurTrack + 1, next);
407 // last track - cd size
409 next[0] = cdr.SetSectorEnd[2];
410 next[1] = cdr.SetSectorEnd[1];
411 next[2] = cdr.SetSectorEnd[0];
414 this_s = msf2sec(time);
415 start_s = fsm2sec(start);
416 next_s = fsm2sec(next);
418 cdr.TrackChanged = FALSE;
420 if (next_s - this_s < pregap) {
421 cdr.TrackChanged = TRUE;
428 relative_s = this_s - start_s;
429 if (relative_s < 0) {
431 relative_s = -relative_s;
433 sec2msf(relative_s, cdr.subq.Relative);
435 cdr.subq.Track = itob(cdr.CurTrack);
436 cdr.subq.Relative[0] = itob(cdr.subq.Relative[0]);
437 cdr.subq.Relative[1] = itob(cdr.subq.Relative[1]);
438 cdr.subq.Relative[2] = itob(cdr.subq.Relative[2]);
439 cdr.subq.Absolute[0] = itob(time[0]);
440 cdr.subq.Absolute[1] = itob(time[1]);
441 cdr.subq.Absolute[2] = itob(time[2]);
444 static void ReadTrack(const u8 *time) {
445 unsigned char tmp[3];
449 tmp[0] = itob(time[0]);
450 tmp[1] = itob(time[1]);
451 tmp[2] = itob(time[2]);
453 if (memcmp(cdr.Prev, tmp, 3) == 0)
456 CDR_LOG("ReadTrack *** %02x:%02x:%02x\n", tmp[0], tmp[1], tmp[2]);
458 cdr.NoErr = CDR_readTrack(tmp);
459 memcpy(cdr.Prev, tmp, 3);
464 subq = (struct SubQ *)CDR_getBufferSub();
465 if (subq != NULL && cdr.CurTrack == 1) {
466 crc = calcCrc((u8 *)subq + 12, 10);
467 if (crc == (((u16)subq->CRC[0] << 8) | subq->CRC[1])) {
468 cdr.subq.Track = subq->TrackNumber;
469 cdr.subq.Index = subq->IndexNumber;
470 memcpy(cdr.subq.Relative, subq->TrackRelativeAddress, 3);
471 memcpy(cdr.subq.Absolute, subq->AbsoluteAddress, 3);
474 CDR_LOG_I("subq bad crc @%02x:%02x:%02x\n",
475 tmp[0], tmp[1], tmp[2]);
482 CDR_LOG(" -> %02x,%02x %02x:%02x:%02x %02x:%02x:%02x\n",
483 cdr.subq.Track, cdr.subq.Index,
484 cdr.subq.Relative[0], cdr.subq.Relative[1], cdr.subq.Relative[2],
485 cdr.subq.Absolute[0], cdr.subq.Absolute[1], cdr.subq.Absolute[2]);
488 static void AddIrqQueue(unsigned short irq, unsigned long ecycle) {
490 if (irq == cdr.Irq || irq + 0x100 == cdr.Irq) {
496 CDR_LOG_I("cdr: override cmd %02x -> %02x\n", cdr.Irq, irq);
505 static void cdrPlayInterrupt_Autopause()
508 boolean abs_lev_chselect;
511 if ((cdr.Mode & MODE_AUTOPAUSE) && cdr.TrackChanged) {
512 CDR_LOG( "CDDA STOP\n" );
514 // Magic the Gathering
515 // - looping territory cdda
518 //cdr.ResultReady = 1;
519 //cdr.Stat = DataReady;
524 SetPlaySeekRead(cdr.StatP, 0);
526 else if (((cdr.Mode & MODE_REPORT) || cdr.FastForward || cdr.FastBackward)) {
527 cdr.Result[0] = cdr.StatP;
528 cdr.Result[1] = cdr.subq.Track;
529 cdr.Result[2] = cdr.subq.Index;
531 abs_lev_chselect = cdr.subq.Absolute[1] & 0x01;
533 /* 8 is a hack. For accuracy, it should be 588. */
534 for (i = 0; i < 8; i++)
536 abs_lev_max = MAX_VALUE(abs_lev_max, abs(read_buf[i * 2 + abs_lev_chselect]));
538 abs_lev_max = MIN_VALUE(abs_lev_max, 32767);
539 abs_lev_max |= abs_lev_chselect << 15;
541 if (cdr.subq.Absolute[2] & 0x10) {
542 cdr.Result[3] = cdr.subq.Relative[0];
543 cdr.Result[4] = cdr.subq.Relative[1] | 0x80;
544 cdr.Result[5] = cdr.subq.Relative[2];
547 cdr.Result[3] = cdr.subq.Absolute[0];
548 cdr.Result[4] = cdr.subq.Absolute[1];
549 cdr.Result[5] = cdr.subq.Absolute[2];
552 cdr.Result[6] = abs_lev_max >> 0;
553 cdr.Result[7] = abs_lev_max >> 8;
555 // Rayman: Logo freeze (resultready + dataready)
557 cdr.Stat = DataReady;
564 static int cdrSeekTime(unsigned char *target)
566 int seekTime = abs(msf2sec(cdr.SetSectorPlay) - msf2sec(target)) * (cdReadTime / 200);
569 * It was originally set to 1000000 for Driver, however it is not high enough for Worms Pinball
570 * and was unreliable for that game.
571 * I also tested it against Mednafen and Driver's titlescreen music starts 25 frames later, not immediatly.
573 * Obviously, this isn't perfect but right now, it should be a bit better.
574 * Games to test this against if you change that setting :
575 * - Driver (titlescreen music delay and retry mission)
576 * - Worms Pinball (Will either not boot or crash in the memory card screen)
577 * - Viewpoint (short pauses if the delay in the ingame music is too long)
579 * It seems that 3386880 * 5 is too much for Driver's titlescreen and it starts skipping.
580 * However, 1000000 is not enough for Worms Pinball to reliably boot.
582 if(seekTime > 3386880 * 2) seekTime = 3386880 * 2;
583 CDR_LOG("seek: %.2f %.2f\n", (float)seekTime / PSXCLK, (float)seekTime / cdReadTime);
588 void cdrPlayInterrupt()
590 if (cdr.StatP & STATUS_SEEK) {
592 CDR_LOG_I("cdrom: seek stat hack\n");
593 CDRSEEKPLAY_INT(0x1000);
597 cdr.StatP |= STATUS_ROTATING;
598 SetPlaySeekRead(cdr.StatP, cdr.Play ? STATUS_PLAY : 0);
599 cdr.Result[0] = cdr.StatP;
605 Find_CurTrack(cdr.SetSectorPlay);
606 ReadTrack(cdr.SetSectorPlay);
607 cdr.TrackChanged = FALSE;
610 if (!cdr.Play) return;
612 CDR_LOG( "CDDA - %d:%d:%d\n",
613 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2] );
615 if (memcmp(cdr.SetSectorPlay, cdr.SetSectorEnd, 3) == 0) {
617 SetPlaySeekRead(cdr.StatP, 0);
618 cdr.TrackChanged = TRUE;
621 CDR_readCDDA(cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2], (u8 *)read_buf);
624 if (!cdr.Irq && !cdr.Stat && (cdr.Mode & (MODE_AUTOPAUSE|MODE_REPORT)))
625 cdrPlayInterrupt_Autopause();
627 if (CDR_readCDDA && !cdr.Muted && !Config.Cdda) {
628 cdrAttenuate(read_buf, CD_FRAMESIZE_RAW / 4, 1);
629 if (SPU_playCDDAchannel)
630 SPU_playCDDAchannel(read_buf, CD_FRAMESIZE_RAW);
633 cdr.SetSectorPlay[2]++;
634 if (cdr.SetSectorPlay[2] == 75) {
635 cdr.SetSectorPlay[2] = 0;
636 cdr.SetSectorPlay[1]++;
637 if (cdr.SetSectorPlay[1] == 60) {
638 cdr.SetSectorPlay[1] = 0;
639 cdr.SetSectorPlay[0]++;
643 CDRSEEKPLAY_INT(cdReadTime);
645 // update for CdlGetlocP/autopause
646 generate_subq(cdr.SetSectorPlay);
649 void cdrInterrupt() {
651 int no_busy_error = 0;
652 int start_rotating = 0;
655 unsigned int seekTime = 0;
661 CDR_LOG_I("cdrom: stat hack: %02x %x\n", cdr.Irq, cdr.Stat);
670 cdr.Result[0] = cdr.StatP;
671 cdr.Stat = Acknowledge;
673 if (cdr.IrqRepeated) {
675 if (cdr.eCycle > psxRegs.cycle) {
685 if (cdr.DriveState != DRIVESTATE_LID_OPEN)
686 cdr.StatP &= ~STATUS_SHELLOPEN;
691 CDR_LOG("CDROM setloc command (%02X, %02X, %02X)\n", cdr.Param[0], cdr.Param[1], cdr.Param[2]);
693 // MM must be BCD, SS must be BCD and <0x60, FF must be BCD and <0x75
694 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))
696 CDR_LOG("Invalid/out of range seek to %02X:%02X:%02X\n", cdr.Param[0], cdr.Param[1], cdr.Param[2]);
697 error = ERROR_INVALIDARG;
702 for (i = 0; i < 3; i++)
703 set_loc[i] = btoi(cdr.Param[i]);
704 memcpy(cdr.SetSector, set_loc, 3);
705 cdr.SetSector[3] = 0;
706 cdr.SetlocPending = 1;
715 cdr.FastBackward = 0;
719 // - Pause player, hit Track 01/02/../xx (Setloc issued!!)
721 if (cdr.ParamC != 0 && cdr.Param[0] != 0) {
722 int track = btoi( cdr.Param[0] );
724 if (track <= cdr.ResultTN[1])
725 cdr.CurTrack = track;
727 CDR_LOG("PLAY track %d\n", cdr.CurTrack);
729 if (CDR_getTD((u8)cdr.CurTrack, cdr.ResultTD) != -1) {
730 for (i = 0; i < 3; i++)
731 set_loc[i] = cdr.ResultTD[2 - i];
732 seekTime = cdrSeekTime(set_loc);
733 memcpy(cdr.SetSectorPlay, set_loc, 3);
736 else if (cdr.SetlocPending) {
737 seekTime = cdrSeekTime(cdr.SetSector);
738 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
741 CDR_LOG("PLAY Resume @ %d:%d:%d\n",
742 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2]);
744 cdr.SetlocPending = 0;
747 Rayman: detect track changes
750 Twisted Metal 2: skip PREGAP + starting accurate SubQ
751 - plays tracks without retry play
753 Wild 9: skip PREGAP + starting accurate SubQ
754 - plays tracks without retry play
756 Find_CurTrack(cdr.SetSectorPlay);
757 ReadTrack(cdr.SetSectorPlay);
758 cdr.TrackChanged = FALSE;
761 CDR_play(cdr.SetSectorPlay);
763 SetPlaySeekRead(cdr.StatP, STATUS_SEEK | STATUS_ROTATING);
765 // BIOS player - set flag again
768 CDRSEEKPLAY_INT(cdReadTime + seekTime);
773 // TODO: error 80 if stopped
776 // GameShark CD Player: Calls 2x + Play 2x
778 cdr.FastBackward = 0;
784 // GameShark CD Player: Calls 2x + Play 2x
785 cdr.FastBackward = 1;
790 if (cdr.DriveState != DRIVESTATE_STOPPED) {
791 error = ERROR_INVALIDARG;
794 AddIrqQueue(CdlStandby + 0x100, cdReadTime * 125 / 2);
798 case CdlStandby + 0x100:
804 // grab time for current track
805 CDR_getTD((u8)(cdr.CurTrack), cdr.ResultTD);
807 cdr.SetSectorPlay[0] = cdr.ResultTD[2];
808 cdr.SetSectorPlay[1] = cdr.ResultTD[1];
809 cdr.SetSectorPlay[2] = cdr.ResultTD[0];
814 SetPlaySeekRead(cdr.StatP, 0);
815 cdr.StatP &= ~STATUS_ROTATING;
818 if (cdr.DriveState == DRIVESTATE_STANDBY)
819 delay = cdReadTime * 30 / 2;
821 cdr.DriveState = DRIVESTATE_STOPPED;
822 AddIrqQueue(CdlStop + 0x100, delay);
825 case CdlStop + 0x100:
833 Gundam Battle Assault 2: much slower (*)
834 - Fixes boot, gameplay
836 Hokuto no Ken 2: slower
837 - Fixes intro + subtitles
839 InuYasha - Feudal Fairy Tale: slower
842 /* Gameblabla - Tightening the timings (as taken from Duckstation).
843 * The timings from Duckstation are based upon hardware tests.
844 * Mednafen's timing don't work for Gundam Battle Assault 2 in PAL/50hz mode,
845 * seems to be timing sensitive as it can depend on the CPU's clock speed.
847 if (!(cdr.StatP & (STATUS_PLAY | STATUS_READ)))
853 delay = (((cdr.Mode & MODE_SPEED) ? 2 : 1) * (1000000));
855 AddIrqQueue(CdlPause + 0x100, delay);
856 SetPlaySeekRead(cdr.StatP, 0);
860 case CdlPause + 0x100:
867 SetPlaySeekRead(cdr.StatP, 0);
869 cdr.Mode = 0x20; /* This fixes This is Football 2, Pooh's Party lockups */
870 AddIrqQueue(CdlReset + 0x100, 4100000);
875 case CdlReset + 0x100:
888 cdr.File = cdr.Param[0];
889 cdr.Channel = cdr.Param[1];
897 /* Gameblabla : According to mednafen, Result size should be 5 and done this way. */
899 cdr.Result[1] = cdr.Mode;
901 cdr.Result[3] = cdr.File;
902 cdr.Result[4] = cdr.Channel;
908 memcpy(cdr.Result, cdr.Transfer, 8);
913 memcpy(&cdr.Result, &cdr.subq, 8);
916 case CdlReadT: // SetSession?
918 AddIrqQueue(CdlReadT + 0x100, cdReadTime * 290 / 4);
922 case CdlReadT + 0x100:
928 if (CDR_getTN(cdr.ResultTN) == -1) {
929 cdr.Stat = DiskError;
930 cdr.Result[0] |= STATUS_ERROR;
932 cdr.Stat = Acknowledge;
933 cdr.Result[1] = itob(cdr.ResultTN[0]);
934 cdr.Result[2] = itob(cdr.ResultTN[1]);
939 cdr.Track = btoi(cdr.Param[0]);
941 if (CDR_getTD(cdr.Track, cdr.ResultTD) == -1) {
942 cdr.Stat = DiskError;
943 cdr.Result[0] |= STATUS_ERROR;
945 cdr.Stat = Acknowledge;
946 cdr.Result[0] = cdr.StatP;
947 cdr.Result[1] = itob(cdr.ResultTD[2]);
948 cdr.Result[2] = itob(cdr.ResultTD[1]);
949 /* According to Nocash's documentation, the function doesn't care about ff.
950 * This can be seen also in Mednafen's implementation. */
951 //cdr.Result[3] = itob(cdr.ResultTD[0]);
959 SetPlaySeekRead(cdr.StatP, STATUS_SEEK);
961 seekTime = cdrSeekTime(cdr.SetSector);
962 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
964 Crusaders of Might and Magic = 0.5x-4x
965 - fix cutscene speech start
971 - fix cutscene speech
976 CDRSEEKPLAY_INT(cdReadTime + seekTime);
981 switch (cdr.Param[0]) {
982 case 0x20: // System Controller ROM Version
984 memcpy(cdr.Result, Test20, 4);
988 memcpy(cdr.Result, Test22, 4);
990 case 0x23: case 0x24:
992 memcpy(cdr.Result, Test23, 4);
999 AddIrqQueue(CdlID + 0x100, 20480);
1004 cdr.Result[0] = cdr.StatP;
1009 // 0x10 - audio | 0x40 - disk missing | 0x80 - unlicensed
1010 if (CDR_getStatus(&stat) == -1 || stat.Type == 0 || stat.Type == 0xff) {
1011 cdr.Result[1] = 0xc0;
1015 cdr.Result[1] |= 0x10;
1016 if (CdromId[0] == '\0')
1017 cdr.Result[1] |= 0x80;
1019 cdr.Result[0] |= (cdr.Result[1] >> 4) & 0x08;
1021 /* This adds the string "PCSX" in Playstation bios boot screen */
1022 memcpy((char *)&cdr.Result[4], "PCSX", 4);
1023 cdr.Stat = Complete;
1029 SetPlaySeekRead(cdr.StatP, 0);
1030 // yes, it really sets STATUS_SHELLOPEN
1031 cdr.StatP |= STATUS_SHELLOPEN;
1032 cdr.DriveState = DRIVESTATE_RESCAN_CD;
1043 AddIrqQueue(CdlReadToc + 0x100, cdReadTime * 180 / 4);
1048 case CdlReadToc + 0x100:
1049 cdr.Stat = Complete;
1055 Find_CurTrack(cdr.SetlocPending ? cdr.SetSector : cdr.SetSectorPlay);
1057 if ((cdr.Mode & MODE_CDDA) && cdr.CurTrack > 1)
1058 // Read* acts as play for cdda tracks in cdda mode
1062 if (cdr.SetlocPending) {
1063 seekTime = cdrSeekTime(cdr.SetSector);
1064 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
1065 cdr.SetlocPending = 0;
1068 cdr.FirstSector = 1;
1070 // Fighting Force 2 - update subq time immediately
1072 ReadTrack(cdr.SetSectorPlay);
1075 // Crusaders of Might and Magic - update getlocl now
1076 // - fixes cutscene speech
1078 u8 *buf = CDR_getBuffer();
1080 memcpy(cdr.Transfer, buf, 8);
1084 Duke Nukem: Land of the Babes - seek then delay read for one frame
1086 C-12 - Final Resistance - doesn't like seek
1090 By nicolasnoble from PCSX Redux :
1091 "It LOOKS like this logic is wrong, therefore disabling it with `&& false` for now.
1092 For "PoPoLoCrois Monogatari II", the game logic will soft lock and will never issue GetLocP to detect
1093 the end of its XA streams, as it seems to assume ReadS will not return a status byte with the SEEK
1094 flag set. I think the reasonning is that since it's invalid to call GetLocP while seeking, the game
1095 tries to protect itself against errors by preventing from issuing a GetLocP while it knows the
1096 last status was "seek". But this makes the logic just softlock as it'll never get a notification
1097 about the fact the drive is done seeking and the read actually started.
1099 In other words, this state machine here is probably wrong in assuming the response to ReadS/ReadN is
1100 done right away. It's rather when it's done seeking, and the read has actually started. This probably
1101 requires a bit more work to make sure seek delays are processed properly.
1102 Checked with a few games, this seems to work fine."
1104 Gameblabla additional notes :
1105 This still needs the "+ seekTime" that PCSX Redux doesn't have for the Driver "retry" mission error.
1107 CDREAD_INT(((cdr.Mode & 0x80) ? (cdReadTime) : cdReadTime * 2) + seekTime);
1109 SetPlaySeekRead(cdr.StatP, STATUS_SEEK);
1114 CDR_LOG_I("Invalid command: %02x\n", Irq);
1115 error = ERROR_INVALIDCMD;
1120 cdr.Result[0] = cdr.StatP | STATUS_ERROR;
1121 cdr.Result[1] = error;
1122 cdr.Stat = DiskError;
1126 if (cdr.DriveState == DRIVESTATE_STOPPED && start_rotating) {
1127 cdr.DriveState = DRIVESTATE_STANDBY;
1128 cdr.StatP |= STATUS_ROTATING;
1131 if (!no_busy_error) {
1132 switch (cdr.DriveState) {
1133 case DRIVESTATE_LID_OPEN:
1134 case DRIVESTATE_RESCAN_CD:
1135 case DRIVESTATE_PREPARE_CD:
1137 cdr.Result[0] = cdr.StatP | STATUS_ERROR;
1138 cdr.Result[1] = ERROR_NOTREADY;
1139 cdr.Stat = DiskError;
1150 #define ssat32_to_16(v) \
1151 asm("ssat %0,#16,%1" : "=r" (v) : "r" (v))
1153 #define ssat32_to_16(v) do { \
1154 if (v < -32768) v = -32768; \
1155 else if (v > 32767) v = 32767; \
1159 void cdrAttenuate(s16 *buf, int samples, int stereo)
1162 int ll = cdr.AttenuatorLeftToLeft;
1163 int lr = cdr.AttenuatorLeftToRight;
1164 int rl = cdr.AttenuatorRightToLeft;
1165 int rr = cdr.AttenuatorRightToRight;
1167 if (lr == 0 && rl == 0 && 0x78 <= ll && ll <= 0x88 && 0x78 <= rr && rr <= 0x88)
1170 if (!stereo && ll == 0x40 && lr == 0x40 && rl == 0x40 && rr == 0x40)
1174 for (i = 0; i < samples; i++) {
1177 l = (l * ll + r * rl) >> 7;
1178 r = (r * rr + l * lr) >> 7;
1186 for (i = 0; i < samples; i++) {
1188 l = l * (ll + rl) >> 7;
1189 //r = r * (rr + lr) >> 7;
1197 void cdrReadInterrupt() {
1203 if (cdr.Irq || cdr.Stat) {
1204 CDR_LOG_I("cdrom: read stat hack %02x %x\n", cdr.Irq, cdr.Stat);
1211 SetPlaySeekRead(cdr.StatP, STATUS_READ | STATUS_ROTATING);
1212 cdr.Result[0] = cdr.StatP;
1214 ReadTrack(cdr.SetSectorPlay);
1216 buf = CDR_getBuffer();
1221 CDR_LOG_I("cdrReadInterrupt() Log: err\n");
1222 memset(cdr.Transfer, 0, DATA_SIZE);
1223 cdr.Stat = DiskError;
1224 cdr.Result[0] |= STATUS_ERROR;
1225 CDREAD_INT((cdr.Mode & 0x80) ? (cdReadTime / 2) : cdReadTime);
1229 memcpy(cdr.Transfer, buf, DATA_SIZE);
1230 CheckPPFCache(cdr.Transfer, cdr.Prev[0], cdr.Prev[1], cdr.Prev[2]);
1233 CDR_LOG("cdrReadInterrupt() Log: cdr.Transfer %x:%x:%x\n", cdr.Transfer[0], cdr.Transfer[1], cdr.Transfer[2]);
1235 if ((!cdr.Muted) && (cdr.Mode & MODE_STRSND) && (!Config.Xa) && (cdr.FirstSector != -1)) { // CD-XA
1236 // Firemen 2: Multi-XA files - briefings, cutscenes
1237 if( cdr.FirstSector == 1 && (cdr.Mode & MODE_SF)==0 ) {
1238 cdr.File = cdr.Transfer[4 + 0];
1239 cdr.Channel = cdr.Transfer[4 + 1];
1243 * Skips playing on channel 255.
1244 * Fixes missing audio in Blue's Clues : Blue's Big Musical. (Should also fix Taxi 2)
1245 * TODO : Check if this is the proper behaviour.
1247 if((cdr.Transfer[4 + 2] & 0x4) &&
1248 (cdr.Transfer[4 + 1] == cdr.Channel) &&
1249 (cdr.Transfer[4 + 0] == cdr.File) && cdr.Channel != 255) {
1250 int ret = xa_decode_sector(&cdr.Xa, cdr.Transfer+4, cdr.FirstSector);
1252 cdrAttenuate(cdr.Xa.pcm, cdr.Xa.nsamples, cdr.Xa.stereo);
1253 SPU_playADPCMchannel(&cdr.Xa);
1254 cdr.FirstSector = 0;
1256 else cdr.FirstSector = -1;
1260 cdr.SetSectorPlay[2]++;
1261 if (cdr.SetSectorPlay[2] == 75) {
1262 cdr.SetSectorPlay[2] = 0;
1263 cdr.SetSectorPlay[1]++;
1264 if (cdr.SetSectorPlay[1] == 60) {
1265 cdr.SetSectorPlay[1] = 0;
1266 cdr.SetSectorPlay[0]++;
1272 CDREAD_INT((cdr.Mode & MODE_SPEED) ? (cdReadTime / 2) : cdReadTime);
1275 Croc 2: $40 - only FORM1 (*)
1276 Judge Dredd: $C8 - only FORM1 (*)
1277 Sim Theme Park - no adpcm at all (zero)
1280 if (!(cdr.Mode & MODE_STRSND) || !(cdr.Transfer[4+2] & 0x4)) {
1281 cdr.Stat = DataReady;
1285 // update for CdlGetlocP
1286 ReadTrack(cdr.SetSectorPlay);
1295 bit 5 - 1 result ready
1297 bit 7 - 1 command being processed
1300 unsigned char cdrRead0(void) {
1301 if (cdr.ResultReady)
1309 // cdr.Ctrl &= ~0x40;
1311 // What means the 0x10 and the 0x08 bits? I only saw it used by the bios
1314 CDR_LOG_IO("cdr r0.sta: %02x\n", cdr.Ctrl);
1316 return psxHu8(0x1800) = cdr.Ctrl;
1319 void cdrWrite0(unsigned char rt) {
1320 CDR_LOG_IO("cdr w0.idx: %02x\n", rt);
1322 cdr.Ctrl = (rt & 3) | (cdr.Ctrl & ~3);
1325 unsigned char cdrRead1(void) {
1326 if ((cdr.ResultP & 0xf) < cdr.ResultC)
1327 psxHu8(0x1801) = cdr.Result[cdr.ResultP & 0xf];
1331 if (cdr.ResultP == cdr.ResultC)
1332 cdr.ResultReady = 0;
1334 CDR_LOG_IO("cdr r1.rsp: %02x #%u\n", psxHu8(0x1801), cdr.ResultP - 1);
1336 return psxHu8(0x1801);
1339 void cdrWrite1(unsigned char rt) {
1340 const char *rnames[] = { "cmd", "smd", "smc", "arr" }; (void)rnames;
1341 CDR_LOG_IO("cdr w1.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1343 switch (cdr.Ctrl & 3) {
1347 cdr.AttenuatorRightToRightT = rt;
1356 #ifdef CDR_LOG_CMD_IRQ
1357 SysPrintf("CD1 write: %x (%s)", rt, CmdName[rt]);
1360 SysPrintf(" Param[%d] = {", cdr.ParamC);
1361 for (i = 0; i < cdr.ParamC; i++)
1362 SysPrintf(" %x,", cdr.Param[i]);
1369 cdr.ResultReady = 0;
1371 // cdr.Stat = NoIntr;
1372 AddIrqQueue(cdr.Cmd, 0x800);
1376 CDR_LOG("cdrWrite1() Log: Setmode %x\n", cdr.Param[0]);
1378 cdr.Mode = cdr.Param[0];
1383 unsigned char cdrRead2(void) {
1386 if (cdr.Readed == 0) {
1392 CDR_LOG_IO("cdr r2.dat: %02x\n", ret);
1396 void cdrWrite2(unsigned char rt) {
1397 const char *rnames[] = { "prm", "ien", "all", "arl" }; (void)rnames;
1398 CDR_LOG_IO("cdr w2.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1400 switch (cdr.Ctrl & 3) {
1402 if (cdr.ParamC < 8) // FIXME: size and wrapping
1403 cdr.Param[cdr.ParamC++] = rt;
1410 cdr.AttenuatorLeftToLeftT = rt;
1413 cdr.AttenuatorRightToLeftT = rt;
1418 unsigned char cdrRead3(void) {
1420 psxHu8(0x1803) = cdr.Stat | 0xE0;
1422 psxHu8(0x1803) = cdr.Reg2 | 0xE0;
1424 CDR_LOG_IO("cdr r3.%s: %02x\n", (cdr.Ctrl & 1) ? "ifl" : "ien", psxHu8(0x1803));
1425 return psxHu8(0x1803);
1428 void cdrWrite3(unsigned char rt) {
1429 const char *rnames[] = { "req", "ifl", "alr", "ava" }; (void)rnames;
1430 CDR_LOG_IO("cdr w3.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1432 switch (cdr.Ctrl & 3) {
1442 cdr.AttenuatorLeftToRightT = rt;
1446 memcpy(&cdr.AttenuatorLeftToLeft, &cdr.AttenuatorLeftToLeftT, 4);
1447 CDR_LOG_I("CD-XA Volume: %02x %02x | %02x %02x\n",
1448 cdr.AttenuatorLeftToLeft, cdr.AttenuatorLeftToRight,
1449 cdr.AttenuatorRightToLeft, cdr.AttenuatorRightToRight);
1454 if ((rt & 0x80) && cdr.Readed == 0) {
1456 pTransfer = cdr.Transfer;
1458 switch (cdr.Mode & 0x30) {
1459 case MODE_SIZE_2328:
1464 case MODE_SIZE_2340:
1474 void psxDma3(u32 madr, u32 bcr, u32 chcr) {
1479 CDR_LOG("psxDma3() Log: *** DMA 3 *** %x addr = %x size = %x\n", chcr, madr, bcr);
1484 if (cdr.Readed == 0) {
1485 CDR_LOG("psxDma3() Log: *** DMA 3 *** NOT READY\n");
1489 cdsize = (bcr & 0xffff) * 4;
1491 // Ape Escape: bcr = 0001 / 0000
1495 switch (cdr.Mode & (MODE_SIZE_2340|MODE_SIZE_2328)) {
1496 case MODE_SIZE_2340: cdsize = 2340; break;
1497 case MODE_SIZE_2328: cdsize = 2328; break;
1499 case MODE_SIZE_2048: cdsize = 2048; break;
1504 ptr = (u8 *)PSXM(madr);
1505 if (ptr == INVALID_PTR) {
1506 CDR_LOG("psxDma3() Log: *** DMA 3 *** NULL Pointer!\n");
1511 GS CDX: Enhancement CD crash
1514 - Spams DMA3 and gets buffer overrun
1516 size = CD_FRAMESIZE_RAW - (pTransfer - cdr.Transfer);
1521 memcpy(ptr, pTransfer, size);
1524 psxCpu->Clear(madr, cdsize / 4);
1525 pTransfer += cdsize;
1527 if( chcr == 0x11400100 ) {
1528 HW_DMA3_MADR = SWAPu32(madr + cdsize);
1529 CDRDMA_INT( (cdsize/4) / 4 );
1531 else if( chcr == 0x11000000 ) {
1532 // CDRDMA_INT( (cdsize/4) * 1 );
1534 psxRegs.cycle += (cdsize/4) * 24/2;
1540 CDR_LOG("psxDma3() Log: Unknown cddma %x\n", chcr);
1544 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1548 void cdrDmaInterrupt()
1550 if (HW_DMA3_CHCR & SWAP32(0x01000000))
1552 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1557 static void getCdInfo(void)
1561 CDR_getTN(cdr.ResultTN);
1562 CDR_getTD(0, cdr.SetSectorEnd);
1563 tmp = cdr.SetSectorEnd[0];
1564 cdr.SetSectorEnd[0] = cdr.SetSectorEnd[2];
1565 cdr.SetSectorEnd[2] = tmp;
1569 memset(&cdr, 0, sizeof(cdr));
1575 cdr.DriveState = DRIVESTATE_STANDBY;
1576 cdr.StatP = STATUS_ROTATING;
1577 pTransfer = cdr.Transfer;
1579 // BIOS player - default values
1580 cdr.AttenuatorLeftToLeft = 0x80;
1581 cdr.AttenuatorLeftToRight = 0x00;
1582 cdr.AttenuatorRightToLeft = 0x00;
1583 cdr.AttenuatorRightToRight = 0x80;
1588 int cdrFreeze(void *f, int Mode) {
1592 if (Mode == 0 && !Config.Cdda)
1595 cdr.freeze_ver = 0x63647202;
1596 gzfreeze(&cdr, sizeof(cdr));
1599 cdr.ParamP = cdr.ParamC;
1600 tmp = pTransfer - cdr.Transfer;
1603 gzfreeze(&tmp, sizeof(tmp));
1608 pTransfer = cdr.Transfer + tmp;
1610 // read right sub data
1611 tmpp[0] = btoi(cdr.Prev[0]);
1612 tmpp[1] = btoi(cdr.Prev[1]);
1613 tmpp[2] = btoi(cdr.Prev[2]);
1618 if (cdr.freeze_ver < 0x63647202)
1619 memcpy(cdr.SetSectorPlay, cdr.SetSector, 3);
1621 Find_CurTrack(cdr.SetSectorPlay);
1623 CDR_play(cdr.SetSectorPlay);
1626 if ((cdr.freeze_ver & 0xffffff00) != 0x63647200) {
1627 // old versions did not latch Reg2, have to fixup..
1628 if (cdr.Reg2 == 0) {
1629 SysPrintf("cdrom: fixing up old savestate\n");
1632 // also did not save Attenuator..
1633 if ((cdr.AttenuatorLeftToLeft | cdr.AttenuatorLeftToRight
1634 | cdr.AttenuatorRightToLeft | cdr.AttenuatorRightToRight) == 0)
1636 cdr.AttenuatorLeftToLeft = cdr.AttenuatorRightToRight = 0x80;
1644 void LidInterrupt() {
1646 cdrLidSeekInterrupt();