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); \
244 // cdrPlaySeekReadInterrupt
245 #define CDRPLAYSEEKREAD_INT(eCycle, isFirst) { \
247 psxRegs.interrupt |= (1 << PSXINT_CDREAD); \
249 psxRegs.intCycle[PSXINT_CDREAD].sCycle = psxRegs.cycle; \
251 psxRegs.intCycle[PSXINT_CDREAD].sCycle += psxRegs.intCycle[PSXINT_CDREAD].cycle; \
252 psxRegs.intCycle[PSXINT_CDREAD].cycle = e_; \
253 new_dyna_set_event_abs(PSXINT_CDREAD, psxRegs.intCycle[PSXINT_CDREAD].sCycle + e_); \
256 // cdrLidSeekInterrupt
257 #define CDRLID_INT(eCycle) { \
258 psxRegs.interrupt |= (1 << PSXINT_CDRLID); \
259 psxRegs.intCycle[PSXINT_CDRLID].cycle = eCycle; \
260 psxRegs.intCycle[PSXINT_CDRLID].sCycle = psxRegs.cycle; \
261 new_dyna_set_event(PSXINT_CDRLID, eCycle); \
264 #define StopReading() { \
266 psxRegs.interrupt &= ~(1 << PSXINT_CDREAD); \
269 #define StopCdda() { \
270 if (cdr.Play && !Config.Cdda) CDR_stop(); \
272 cdr.FastForward = 0; \
273 cdr.FastBackward = 0; \
276 #define SetPlaySeekRead(x, f) { \
277 x &= ~(STATUS_PLAY | STATUS_SEEK | STATUS_READ); \
281 #define SetResultSize(size) { \
283 cdr.ResultC = size; \
284 cdr.ResultReady = 1; \
287 static void setIrq(int log_cmd)
289 if (cdr.Stat & cdr.Reg2)
290 psxHu32ref(0x1070) |= SWAP32((u32)0x4);
292 #ifdef CDR_LOG_CMD_IRQ
295 SysPrintf("CDR IRQ=%d cmd %02x stat %02x: ",
296 !!(cdr.Stat & cdr.Reg2), log_cmd, cdr.Stat);
297 for (i = 0; i < cdr.ResultC; i++)
298 SysPrintf("%02x ", cdr.Result[i]);
304 // timing used in this function was taken from tests on real hardware
305 // (yes it's slow, but you probably don't want to modify it)
306 void cdrLidSeekInterrupt(void)
308 switch (cdr.DriveState) {
310 case DRIVESTATE_STANDBY:
313 SetPlaySeekRead(cdr.StatP, 0);
315 if (CDR_getStatus(&stat) == -1)
318 if (stat.Status & STATUS_SHELLOPEN)
320 cdr.DriveState = DRIVESTATE_LID_OPEN;
325 case DRIVESTATE_LID_OPEN:
326 if (CDR_getStatus(&stat) == -1)
327 stat.Status &= ~STATUS_SHELLOPEN;
330 if (!(cdr.StatP & STATUS_SHELLOPEN)) {
331 cdr.StatP |= STATUS_SHELLOPEN;
333 // could generate error irq here, but real hardware
334 // only sometimes does that
335 // (not done when lots of commands are sent?)
337 CDRLID_INT(cdReadTime * 30);
340 else if (cdr.StatP & STATUS_ROTATING) {
341 cdr.StatP &= ~STATUS_ROTATING;
343 else if (!(stat.Status & STATUS_SHELLOPEN)) {
347 // cdr.StatP STATUS_SHELLOPEN is "sticky"
348 // and is only cleared by CdlNop
350 cdr.DriveState = DRIVESTATE_RESCAN_CD;
351 CDRLID_INT(cdReadTime * 105);
356 CDRLID_INT(cdReadTime * 3);
359 case DRIVESTATE_RESCAN_CD:
360 cdr.StatP |= STATUS_ROTATING;
361 cdr.DriveState = DRIVESTATE_PREPARE_CD;
363 // this is very long on real hardware, over 6 seconds
364 // make it a bit faster here...
365 CDRLID_INT(cdReadTime * 150);
368 case DRIVESTATE_PREPARE_CD:
369 cdr.StatP |= STATUS_SEEK;
371 cdr.DriveState = DRIVESTATE_STANDBY;
372 CDRLID_INT(cdReadTime * 26);
377 static void Find_CurTrack(const u8 *time)
381 current = msf2sec(time);
383 for (cdr.CurTrack = 1; cdr.CurTrack < cdr.ResultTN[1]; cdr.CurTrack++) {
384 CDR_getTD(cdr.CurTrack + 1, cdr.ResultTD);
385 sect = fsm2sec(cdr.ResultTD);
386 if (sect - current >= 150)
391 static void generate_subq(const u8 *time)
393 unsigned char start[3], next[3];
394 unsigned int this_s, start_s, next_s, pregap;
397 CDR_getTD(cdr.CurTrack, start);
398 if (cdr.CurTrack + 1 <= cdr.ResultTN[1]) {
400 CDR_getTD(cdr.CurTrack + 1, next);
403 // last track - cd size
405 next[0] = cdr.SetSectorEnd[2];
406 next[1] = cdr.SetSectorEnd[1];
407 next[2] = cdr.SetSectorEnd[0];
410 this_s = msf2sec(time);
411 start_s = fsm2sec(start);
412 next_s = fsm2sec(next);
414 cdr.TrackChanged = FALSE;
416 if (next_s - this_s < pregap) {
417 cdr.TrackChanged = TRUE;
424 relative_s = this_s - start_s;
425 if (relative_s < 0) {
427 relative_s = -relative_s;
429 sec2msf(relative_s, cdr.subq.Relative);
431 cdr.subq.Track = itob(cdr.CurTrack);
432 cdr.subq.Relative[0] = itob(cdr.subq.Relative[0]);
433 cdr.subq.Relative[1] = itob(cdr.subq.Relative[1]);
434 cdr.subq.Relative[2] = itob(cdr.subq.Relative[2]);
435 cdr.subq.Absolute[0] = itob(time[0]);
436 cdr.subq.Absolute[1] = itob(time[1]);
437 cdr.subq.Absolute[2] = itob(time[2]);
440 static void ReadTrack(const u8 *time) {
441 unsigned char tmp[3];
445 tmp[0] = itob(time[0]);
446 tmp[1] = itob(time[1]);
447 tmp[2] = itob(time[2]);
449 if (memcmp(cdr.Prev, tmp, 3) == 0)
452 CDR_LOG("ReadTrack *** %02x:%02x:%02x\n", tmp[0], tmp[1], tmp[2]);
454 cdr.NoErr = CDR_readTrack(tmp);
455 memcpy(cdr.Prev, tmp, 3);
460 subq = (struct SubQ *)CDR_getBufferSub();
461 if (subq != NULL && cdr.CurTrack == 1) {
462 crc = calcCrc((u8 *)subq + 12, 10);
463 if (crc == (((u16)subq->CRC[0] << 8) | subq->CRC[1])) {
464 cdr.subq.Track = subq->TrackNumber;
465 cdr.subq.Index = subq->IndexNumber;
466 memcpy(cdr.subq.Relative, subq->TrackRelativeAddress, 3);
467 memcpy(cdr.subq.Absolute, subq->AbsoluteAddress, 3);
470 CDR_LOG_I("subq bad crc @%02x:%02x:%02x\n",
471 tmp[0], tmp[1], tmp[2]);
478 CDR_LOG(" -> %02x,%02x %02x:%02x:%02x %02x:%02x:%02x\n",
479 cdr.subq.Track, cdr.subq.Index,
480 cdr.subq.Relative[0], cdr.subq.Relative[1], cdr.subq.Relative[2],
481 cdr.subq.Absolute[0], cdr.subq.Absolute[1], cdr.subq.Absolute[2]);
484 static void AddIrqQueue(unsigned short irq, unsigned long ecycle) {
486 if (irq == cdr.Irq || irq + 0x100 == cdr.Irq) {
492 CDR_LOG_I("cdr: override cmd %02x -> %02x\n", cdr.Irq, irq);
501 static void cdrPlayInterrupt_Autopause()
504 boolean abs_lev_chselect;
507 if ((cdr.Mode & MODE_AUTOPAUSE) && cdr.TrackChanged) {
508 CDR_LOG( "CDDA STOP\n" );
510 // Magic the Gathering
511 // - looping territory cdda
514 //cdr.ResultReady = 1;
515 //cdr.Stat = DataReady;
520 SetPlaySeekRead(cdr.StatP, 0);
522 else if (((cdr.Mode & MODE_REPORT) || cdr.FastForward || cdr.FastBackward)) {
523 cdr.Result[0] = cdr.StatP;
524 cdr.Result[1] = cdr.subq.Track;
525 cdr.Result[2] = cdr.subq.Index;
527 abs_lev_chselect = cdr.subq.Absolute[1] & 0x01;
529 /* 8 is a hack. For accuracy, it should be 588. */
530 for (i = 0; i < 8; i++)
532 abs_lev_max = MAX_VALUE(abs_lev_max, abs(read_buf[i * 2 + abs_lev_chselect]));
534 abs_lev_max = MIN_VALUE(abs_lev_max, 32767);
535 abs_lev_max |= abs_lev_chselect << 15;
537 if (cdr.subq.Absolute[2] & 0x10) {
538 cdr.Result[3] = cdr.subq.Relative[0];
539 cdr.Result[4] = cdr.subq.Relative[1] | 0x80;
540 cdr.Result[5] = cdr.subq.Relative[2];
543 cdr.Result[3] = cdr.subq.Absolute[0];
544 cdr.Result[4] = cdr.subq.Absolute[1];
545 cdr.Result[5] = cdr.subq.Absolute[2];
548 cdr.Result[6] = abs_lev_max >> 0;
549 cdr.Result[7] = abs_lev_max >> 8;
551 // Rayman: Logo freeze (resultready + dataready)
553 cdr.Stat = DataReady;
560 static int cdrSeekTime(unsigned char *target)
562 int seekTime = abs(msf2sec(cdr.SetSectorPlay) - msf2sec(target)) * (cdReadTime / 200);
565 * It was originally set to 1000000 for Driver, however it is not high enough for Worms Pinball
566 * and was unreliable for that game.
567 * I also tested it against Mednafen and Driver's titlescreen music starts 25 frames later, not immediatly.
569 * Obviously, this isn't perfect but right now, it should be a bit better.
570 * Games to test this against if you change that setting :
571 * - Driver (titlescreen music delay and retry mission)
572 * - Worms Pinball (Will either not boot or crash in the memory card screen)
573 * - Viewpoint (short pauses if the delay in the ingame music is too long)
575 * It seems that 3386880 * 5 is too much for Driver's titlescreen and it starts skipping.
576 * However, 1000000 is not enough for Worms Pinball to reliably boot.
578 if(seekTime > 3386880 * 2) seekTime = 3386880 * 2;
579 CDR_LOG("seek: %.2f %.2f\n", (float)seekTime / PSXCLK, (float)seekTime / cdReadTime);
583 static void cdrReadInterrupt(void);
585 void cdrPlaySeekReadInterrupt(void)
592 if (!cdr.Play && (cdr.StatP & STATUS_SEEK)) {
594 CDR_LOG_I("cdrom: seek stat hack\n");
595 CDRPLAYSEEKREAD_INT(0x1000, 1);
599 cdr.StatP |= STATUS_ROTATING;
600 SetPlaySeekRead(cdr.StatP, 0);
601 cdr.Result[0] = cdr.StatP;
607 Find_CurTrack(cdr.SetSectorPlay);
608 ReadTrack(cdr.SetSectorPlay);
609 cdr.TrackChanged = FALSE;
613 if (!cdr.Play) return;
615 CDR_LOG( "CDDA - %d:%d:%d\n",
616 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2] );
618 SetPlaySeekRead(cdr.StatP, STATUS_PLAY);
619 if (memcmp(cdr.SetSectorPlay, cdr.SetSectorEnd, 3) == 0) {
621 SetPlaySeekRead(cdr.StatP, 0);
622 cdr.TrackChanged = TRUE;
625 CDR_readCDDA(cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2], (u8 *)read_buf);
628 if (!cdr.Irq && !cdr.Stat && (cdr.Mode & (MODE_AUTOPAUSE|MODE_REPORT)))
629 cdrPlayInterrupt_Autopause();
631 if (!cdr.Muted && !Config.Cdda) {
632 cdrAttenuate(read_buf, CD_FRAMESIZE_RAW / 4, 1);
633 SPU_playCDDAchannel(read_buf, CD_FRAMESIZE_RAW, psxRegs.cycle, cdr.FirstSector);
637 cdr.SetSectorPlay[2]++;
638 if (cdr.SetSectorPlay[2] == 75) {
639 cdr.SetSectorPlay[2] = 0;
640 cdr.SetSectorPlay[1]++;
641 if (cdr.SetSectorPlay[1] == 60) {
642 cdr.SetSectorPlay[1] = 0;
643 cdr.SetSectorPlay[0]++;
647 CDRPLAYSEEKREAD_INT(cdReadTime, 0);
649 // update for CdlGetlocP/autopause
650 generate_subq(cdr.SetSectorPlay);
653 void cdrInterrupt(void) {
655 int no_busy_error = 0;
656 int start_rotating = 0;
659 unsigned int seekTime = 0;
665 CDR_LOG_I("cdrom: stat hack: %02x %x\n", cdr.Irq, cdr.Stat);
674 cdr.Result[0] = cdr.StatP;
675 cdr.Stat = Acknowledge;
677 if (cdr.IrqRepeated) {
679 if (cdr.eCycle > psxRegs.cycle) {
689 if (cdr.DriveState != DRIVESTATE_LID_OPEN)
690 cdr.StatP &= ~STATUS_SHELLOPEN;
695 CDR_LOG("CDROM setloc command (%02X, %02X, %02X)\n", cdr.Param[0], cdr.Param[1], cdr.Param[2]);
697 // MM must be BCD, SS must be BCD and <0x60, FF must be BCD and <0x75
698 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))
700 CDR_LOG("Invalid/out of range seek to %02X:%02X:%02X\n", cdr.Param[0], cdr.Param[1], cdr.Param[2]);
701 error = ERROR_INVALIDARG;
706 for (i = 0; i < 3; i++)
707 set_loc[i] = btoi(cdr.Param[i]);
708 memcpy(cdr.SetSector, set_loc, 3);
709 cdr.SetSector[3] = 0;
710 cdr.SetlocPending = 1;
719 cdr.FastBackward = 0;
723 // - Pause player, hit Track 01/02/../xx (Setloc issued!!)
725 if (cdr.ParamC != 0 && cdr.Param[0] != 0) {
726 int track = btoi( cdr.Param[0] );
728 if (track <= cdr.ResultTN[1])
729 cdr.CurTrack = track;
731 CDR_LOG("PLAY track %d\n", cdr.CurTrack);
733 if (CDR_getTD((u8)cdr.CurTrack, cdr.ResultTD) != -1) {
734 for (i = 0; i < 3; i++)
735 set_loc[i] = cdr.ResultTD[2 - i];
736 seekTime = cdrSeekTime(set_loc);
737 memcpy(cdr.SetSectorPlay, set_loc, 3);
740 else if (cdr.SetlocPending) {
741 seekTime = cdrSeekTime(cdr.SetSector);
742 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
745 CDR_LOG("PLAY Resume @ %d:%d:%d\n",
746 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2]);
748 cdr.SetlocPending = 0;
751 Rayman: detect track changes
754 Twisted Metal 2: skip PREGAP + starting accurate SubQ
755 - plays tracks without retry play
757 Wild 9: skip PREGAP + starting accurate SubQ
758 - plays tracks without retry play
760 Find_CurTrack(cdr.SetSectorPlay);
761 ReadTrack(cdr.SetSectorPlay);
762 cdr.TrackChanged = FALSE;
766 CDR_play(cdr.SetSectorPlay);
768 SetPlaySeekRead(cdr.StatP, STATUS_SEEK | STATUS_ROTATING);
770 // BIOS player - set flag again
773 CDRPLAYSEEKREAD_INT(cdReadTime + seekTime, 1);
778 // TODO: error 80 if stopped
781 // GameShark CD Player: Calls 2x + Play 2x
783 cdr.FastBackward = 0;
789 // GameShark CD Player: Calls 2x + Play 2x
790 cdr.FastBackward = 1;
795 if (cdr.DriveState != DRIVESTATE_STOPPED) {
796 error = ERROR_INVALIDARG;
799 AddIrqQueue(CdlStandby + 0x100, cdReadTime * 125 / 2);
803 case CdlStandby + 0x100:
809 // grab time for current track
810 CDR_getTD((u8)(cdr.CurTrack), cdr.ResultTD);
812 cdr.SetSectorPlay[0] = cdr.ResultTD[2];
813 cdr.SetSectorPlay[1] = cdr.ResultTD[1];
814 cdr.SetSectorPlay[2] = cdr.ResultTD[0];
819 SetPlaySeekRead(cdr.StatP, 0);
820 cdr.StatP &= ~STATUS_ROTATING;
823 if (cdr.DriveState == DRIVESTATE_STANDBY)
824 delay = cdReadTime * 30 / 2;
826 cdr.DriveState = DRIVESTATE_STOPPED;
827 AddIrqQueue(CdlStop + 0x100, delay);
830 case CdlStop + 0x100:
838 Gundam Battle Assault 2: much slower (*)
839 - Fixes boot, gameplay
841 Hokuto no Ken 2: slower
842 - Fixes intro + subtitles
844 InuYasha - Feudal Fairy Tale: slower
847 /* Gameblabla - Tightening the timings (as taken from Duckstation).
848 * The timings from Duckstation are based upon hardware tests.
849 * Mednafen's timing don't work for Gundam Battle Assault 2 in PAL/50hz mode,
850 * seems to be timing sensitive as it can depend on the CPU's clock speed.
852 if (!(cdr.StatP & (STATUS_PLAY | STATUS_READ)))
858 delay = (((cdr.Mode & MODE_SPEED) ? 2 : 1) * (1000000));
860 AddIrqQueue(CdlPause + 0x100, delay);
861 SetPlaySeekRead(cdr.StatP, 0);
865 case CdlPause + 0x100:
872 SetPlaySeekRead(cdr.StatP, 0);
874 cdr.Mode = 0x20; /* This fixes This is Football 2, Pooh's Party lockups */
875 AddIrqQueue(CdlReset + 0x100, 4100000);
880 case CdlReset + 0x100:
893 cdr.File = cdr.Param[0];
894 cdr.Channel = cdr.Param[1];
902 /* Gameblabla : According to mednafen, Result size should be 5 and done this way. */
904 cdr.Result[1] = cdr.Mode;
906 cdr.Result[3] = cdr.File;
907 cdr.Result[4] = cdr.Channel;
913 memcpy(cdr.Result, cdr.Transfer, 8);
918 memcpy(&cdr.Result, &cdr.subq, 8);
921 case CdlReadT: // SetSession?
923 AddIrqQueue(CdlReadT + 0x100, cdReadTime * 290 / 4);
927 case CdlReadT + 0x100:
933 if (CDR_getTN(cdr.ResultTN) == -1) {
934 cdr.Stat = DiskError;
935 cdr.Result[0] |= STATUS_ERROR;
937 cdr.Stat = Acknowledge;
938 cdr.Result[1] = itob(cdr.ResultTN[0]);
939 cdr.Result[2] = itob(cdr.ResultTN[1]);
944 cdr.Track = btoi(cdr.Param[0]);
946 if (CDR_getTD(cdr.Track, cdr.ResultTD) == -1) {
947 cdr.Stat = DiskError;
948 cdr.Result[0] |= STATUS_ERROR;
950 cdr.Stat = Acknowledge;
951 cdr.Result[0] = cdr.StatP;
952 cdr.Result[1] = itob(cdr.ResultTD[2]);
953 cdr.Result[2] = itob(cdr.ResultTD[1]);
954 /* According to Nocash's documentation, the function doesn't care about ff.
955 * This can be seen also in Mednafen's implementation. */
956 //cdr.Result[3] = itob(cdr.ResultTD[0]);
964 SetPlaySeekRead(cdr.StatP, STATUS_SEEK);
966 seekTime = cdrSeekTime(cdr.SetSector);
967 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
969 Crusaders of Might and Magic = 0.5x-4x
970 - fix cutscene speech start
976 - fix cutscene speech
981 CDRPLAYSEEKREAD_INT(cdReadTime + seekTime, 1);
986 switch (cdr.Param[0]) {
987 case 0x20: // System Controller ROM Version
989 memcpy(cdr.Result, Test20, 4);
993 memcpy(cdr.Result, Test22, 4);
995 case 0x23: case 0x24:
997 memcpy(cdr.Result, Test23, 4);
1004 AddIrqQueue(CdlID + 0x100, 20480);
1009 cdr.Result[0] = cdr.StatP;
1014 // 0x10 - audio | 0x40 - disk missing | 0x80 - unlicensed
1015 if (CDR_getStatus(&stat) == -1 || stat.Type == 0 || stat.Type == 0xff) {
1016 cdr.Result[1] = 0xc0;
1020 cdr.Result[1] |= 0x10;
1021 if (CdromId[0] == '\0')
1022 cdr.Result[1] |= 0x80;
1024 cdr.Result[0] |= (cdr.Result[1] >> 4) & 0x08;
1026 /* This adds the string "PCSX" in Playstation bios boot screen */
1027 memcpy((char *)&cdr.Result[4], "PCSX", 4);
1028 cdr.Stat = Complete;
1034 SetPlaySeekRead(cdr.StatP, 0);
1035 // yes, it really sets STATUS_SHELLOPEN
1036 cdr.StatP |= STATUS_SHELLOPEN;
1037 cdr.DriveState = DRIVESTATE_RESCAN_CD;
1048 AddIrqQueue(CdlReadToc + 0x100, cdReadTime * 180 / 4);
1053 case CdlReadToc + 0x100:
1054 cdr.Stat = Complete;
1060 Find_CurTrack(cdr.SetlocPending ? cdr.SetSector : cdr.SetSectorPlay);
1062 if ((cdr.Mode & MODE_CDDA) && cdr.CurTrack > 1)
1063 // Read* acts as play for cdda tracks in cdda mode
1067 if (cdr.SetlocPending) {
1068 seekTime = cdrSeekTime(cdr.SetSector);
1069 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
1070 cdr.SetlocPending = 0;
1073 cdr.FirstSector = 1;
1075 // Fighting Force 2 - update subq time immediately
1077 ReadTrack(cdr.SetSectorPlay);
1079 CDRPLAYSEEKREAD_INT(((cdr.Mode & 0x80) ? (cdReadTime) : cdReadTime * 2) + seekTime, 1);
1081 SetPlaySeekRead(cdr.StatP, STATUS_SEEK);
1086 CDR_LOG_I("Invalid command: %02x\n", Irq);
1087 error = ERROR_INVALIDCMD;
1092 cdr.Result[0] = cdr.StatP | STATUS_ERROR;
1093 cdr.Result[1] = error;
1094 cdr.Stat = DiskError;
1098 if (cdr.DriveState == DRIVESTATE_STOPPED && start_rotating) {
1099 cdr.DriveState = DRIVESTATE_STANDBY;
1100 cdr.StatP |= STATUS_ROTATING;
1103 if (!no_busy_error) {
1104 switch (cdr.DriveState) {
1105 case DRIVESTATE_LID_OPEN:
1106 case DRIVESTATE_RESCAN_CD:
1107 case DRIVESTATE_PREPARE_CD:
1109 cdr.Result[0] = cdr.StatP | STATUS_ERROR;
1110 cdr.Result[1] = ERROR_NOTREADY;
1111 cdr.Stat = DiskError;
1122 #define ssat32_to_16(v) \
1123 asm("ssat %0,#16,%1" : "=r" (v) : "r" (v))
1125 #define ssat32_to_16(v) do { \
1126 if (v < -32768) v = -32768; \
1127 else if (v > 32767) v = 32767; \
1131 void cdrAttenuate(s16 *buf, int samples, int stereo)
1134 int ll = cdr.AttenuatorLeftToLeft;
1135 int lr = cdr.AttenuatorLeftToRight;
1136 int rl = cdr.AttenuatorRightToLeft;
1137 int rr = cdr.AttenuatorRightToRight;
1139 if (lr == 0 && rl == 0 && 0x78 <= ll && ll <= 0x88 && 0x78 <= rr && rr <= 0x88)
1142 if (!stereo && ll == 0x40 && lr == 0x40 && rl == 0x40 && rr == 0x40)
1146 for (i = 0; i < samples; i++) {
1149 l = (l * ll + r * rl) >> 7;
1150 r = (r * rr + l * lr) >> 7;
1158 for (i = 0; i < samples; i++) {
1160 l = l * (ll + rl) >> 7;
1161 //r = r * (rr + lr) >> 7;
1169 static void cdrReadInterrupt(void)
1173 if (cdr.Irq || cdr.Stat) {
1174 CDR_LOG_I("cdrom: read stat hack %02x %x\n", cdr.Irq, cdr.Stat);
1175 CDRPLAYSEEKREAD_INT(2048, 1);
1181 SetPlaySeekRead(cdr.StatP, STATUS_READ | STATUS_ROTATING);
1182 cdr.Result[0] = cdr.StatP;
1184 ReadTrack(cdr.SetSectorPlay);
1186 buf = CDR_getBuffer();
1191 CDR_LOG_I("cdrReadInterrupt() Log: err\n");
1192 memset(cdr.Transfer, 0, DATA_SIZE);
1193 cdr.Stat = DiskError;
1194 cdr.Result[0] |= STATUS_ERROR;
1199 memcpy(cdr.Transfer, buf, DATA_SIZE);
1200 CheckPPFCache(cdr.Transfer, cdr.Prev[0], cdr.Prev[1], cdr.Prev[2]);
1203 CDR_LOG("cdrReadInterrupt() Log: cdr.Transfer %x:%x:%x\n", cdr.Transfer[0], cdr.Transfer[1], cdr.Transfer[2]);
1205 if ((!cdr.Muted) && (cdr.Mode & MODE_STRSND) && (!Config.Xa) && (cdr.FirstSector != -1)) { // CD-XA
1206 // Firemen 2: Multi-XA files - briefings, cutscenes
1207 if( cdr.FirstSector == 1 && (cdr.Mode & MODE_SF)==0 ) {
1208 cdr.File = cdr.Transfer[4 + 0];
1209 cdr.Channel = cdr.Transfer[4 + 1];
1213 * Skips playing on channel 255.
1214 * Fixes missing audio in Blue's Clues : Blue's Big Musical. (Should also fix Taxi 2)
1215 * TODO : Check if this is the proper behaviour.
1217 if((cdr.Transfer[4 + 2] & 0x4) &&
1218 (cdr.Transfer[4 + 1] == cdr.Channel) &&
1219 (cdr.Transfer[4 + 0] == cdr.File) && cdr.Channel != 255) {
1220 int ret = xa_decode_sector(&cdr.Xa, cdr.Transfer+4, cdr.FirstSector);
1222 cdrAttenuate(cdr.Xa.pcm, cdr.Xa.nsamples, cdr.Xa.stereo);
1223 SPU_playADPCMchannel(&cdr.Xa, psxRegs.cycle, cdr.FirstSector);
1224 cdr.FirstSector = 0;
1226 else cdr.FirstSector = -1;
1230 cdr.SetSectorPlay[2]++;
1231 if (cdr.SetSectorPlay[2] == 75) {
1232 cdr.SetSectorPlay[2] = 0;
1233 cdr.SetSectorPlay[1]++;
1234 if (cdr.SetSectorPlay[1] == 60) {
1235 cdr.SetSectorPlay[1] = 0;
1236 cdr.SetSectorPlay[0]++;
1242 CDRPLAYSEEKREAD_INT((cdr.Mode & MODE_SPEED) ? (cdReadTime / 2) : cdReadTime, 0);
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) || !(cdr.Transfer[4+2] & 0x4)) {
1251 cdr.Stat = DataReady;
1255 // update for CdlGetlocP
1256 ReadTrack(cdr.SetSectorPlay);
1265 bit 5 - 1 result ready
1267 bit 7 - 1 command being processed
1270 unsigned char cdrRead0(void) {
1271 if (cdr.ResultReady)
1279 // cdr.Ctrl &= ~0x40;
1281 // What means the 0x10 and the 0x08 bits? I only saw it used by the bios
1284 CDR_LOG_IO("cdr r0.sta: %02x\n", cdr.Ctrl);
1286 return psxHu8(0x1800) = cdr.Ctrl;
1289 void cdrWrite0(unsigned char rt) {
1290 CDR_LOG_IO("cdr w0.idx: %02x\n", rt);
1292 cdr.Ctrl = (rt & 3) | (cdr.Ctrl & ~3);
1295 unsigned char cdrRead1(void) {
1296 if ((cdr.ResultP & 0xf) < cdr.ResultC)
1297 psxHu8(0x1801) = cdr.Result[cdr.ResultP & 0xf];
1301 if (cdr.ResultP == cdr.ResultC)
1302 cdr.ResultReady = 0;
1304 CDR_LOG_IO("cdr r1.rsp: %02x #%u\n", psxHu8(0x1801), cdr.ResultP - 1);
1306 return psxHu8(0x1801);
1309 void cdrWrite1(unsigned char rt) {
1310 const char *rnames[] = { "cmd", "smd", "smc", "arr" }; (void)rnames;
1311 CDR_LOG_IO("cdr w1.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1313 switch (cdr.Ctrl & 3) {
1317 cdr.AttenuatorRightToRightT = rt;
1326 #ifdef CDR_LOG_CMD_IRQ
1327 SysPrintf("CD1 write: %x (%s)", rt, CmdName[rt]);
1330 SysPrintf(" Param[%d] = {", cdr.ParamC);
1331 for (i = 0; i < cdr.ParamC; i++)
1332 SysPrintf(" %x,", cdr.Param[i]);
1339 cdr.ResultReady = 0;
1341 // cdr.Stat = NoIntr;
1342 AddIrqQueue(cdr.Cmd, 0x800);
1346 CDR_LOG("cdrWrite1() Log: Setmode %x\n", cdr.Param[0]);
1348 cdr.Mode = cdr.Param[0];
1353 unsigned char cdrRead2(void) {
1356 if (cdr.Readed == 0) {
1362 CDR_LOG_IO("cdr r2.dat: %02x\n", ret);
1366 void cdrWrite2(unsigned char rt) {
1367 const char *rnames[] = { "prm", "ien", "all", "arl" }; (void)rnames;
1368 CDR_LOG_IO("cdr w2.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1370 switch (cdr.Ctrl & 3) {
1372 if (cdr.ParamC < 8) // FIXME: size and wrapping
1373 cdr.Param[cdr.ParamC++] = rt;
1380 cdr.AttenuatorLeftToLeftT = rt;
1383 cdr.AttenuatorRightToLeftT = rt;
1388 unsigned char cdrRead3(void) {
1390 psxHu8(0x1803) = cdr.Stat | 0xE0;
1392 psxHu8(0x1803) = cdr.Reg2 | 0xE0;
1394 CDR_LOG_IO("cdr r3.%s: %02x\n", (cdr.Ctrl & 1) ? "ifl" : "ien", psxHu8(0x1803));
1395 return psxHu8(0x1803);
1398 void cdrWrite3(unsigned char rt) {
1399 const char *rnames[] = { "req", "ifl", "alr", "ava" }; (void)rnames;
1400 CDR_LOG_IO("cdr w3.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1402 switch (cdr.Ctrl & 3) {
1412 cdr.AttenuatorLeftToRightT = rt;
1416 memcpy(&cdr.AttenuatorLeftToLeft, &cdr.AttenuatorLeftToLeftT, 4);
1417 CDR_LOG_I("CD-XA Volume: %02x %02x | %02x %02x\n",
1418 cdr.AttenuatorLeftToLeft, cdr.AttenuatorLeftToRight,
1419 cdr.AttenuatorRightToLeft, cdr.AttenuatorRightToRight);
1424 if ((rt & 0x80) && cdr.Readed == 0) {
1426 pTransfer = cdr.Transfer;
1428 switch (cdr.Mode & 0x30) {
1429 case MODE_SIZE_2328:
1434 case MODE_SIZE_2340:
1444 void psxDma3(u32 madr, u32 bcr, u32 chcr) {
1449 CDR_LOG("psxDma3() Log: *** DMA 3 *** %x addr = %x size = %x\n", chcr, madr, bcr);
1454 if (cdr.Readed == 0) {
1455 CDR_LOG("psxDma3() Log: *** DMA 3 *** NOT READY\n");
1459 cdsize = (bcr & 0xffff) * 4;
1461 // Ape Escape: bcr = 0001 / 0000
1465 switch (cdr.Mode & (MODE_SIZE_2340|MODE_SIZE_2328)) {
1466 case MODE_SIZE_2340: cdsize = 2340; break;
1467 case MODE_SIZE_2328: cdsize = 2328; break;
1469 case MODE_SIZE_2048: cdsize = 2048; break;
1474 ptr = (u8 *)PSXM(madr);
1476 CDR_LOG("psxDma3() Log: *** DMA 3 *** NULL Pointer!\n");
1481 GS CDX: Enhancement CD crash
1484 - Spams DMA3 and gets buffer overrun
1486 size = CD_FRAMESIZE_RAW - (pTransfer - cdr.Transfer);
1491 memcpy(ptr, pTransfer, size);
1494 psxCpu->Clear(madr, cdsize / 4);
1495 pTransfer += cdsize;
1497 if( chcr == 0x11400100 ) {
1498 HW_DMA3_MADR = SWAPu32(madr + cdsize);
1499 CDRDMA_INT( (cdsize/4) / 4 );
1501 else if( chcr == 0x11000000 ) {
1502 // CDRDMA_INT( (cdsize/4) * 1 );
1504 psxRegs.cycle += (cdsize/4) * 24/2;
1510 CDR_LOG("psxDma3() Log: Unknown cddma %x\n", chcr);
1514 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1518 void cdrDmaInterrupt(void)
1520 if (HW_DMA3_CHCR & SWAP32(0x01000000))
1522 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1527 static void getCdInfo(void)
1531 CDR_getTN(cdr.ResultTN);
1532 CDR_getTD(0, cdr.SetSectorEnd);
1533 tmp = cdr.SetSectorEnd[0];
1534 cdr.SetSectorEnd[0] = cdr.SetSectorEnd[2];
1535 cdr.SetSectorEnd[2] = tmp;
1539 memset(&cdr, 0, sizeof(cdr));
1545 cdr.DriveState = DRIVESTATE_STANDBY;
1546 cdr.StatP = STATUS_ROTATING;
1547 pTransfer = cdr.Transfer;
1549 // BIOS player - default values
1550 cdr.AttenuatorLeftToLeft = 0x80;
1551 cdr.AttenuatorLeftToRight = 0x00;
1552 cdr.AttenuatorRightToLeft = 0x00;
1553 cdr.AttenuatorRightToRight = 0x80;
1558 int cdrFreeze(void *f, int Mode) {
1562 if (Mode == 0 && !Config.Cdda)
1565 cdr.freeze_ver = 0x63647202;
1566 gzfreeze(&cdr, sizeof(cdr));
1569 cdr.ParamP = cdr.ParamC;
1570 tmp = pTransfer - cdr.Transfer;
1573 gzfreeze(&tmp, sizeof(tmp));
1578 pTransfer = cdr.Transfer + tmp;
1580 // read right sub data
1581 tmpp[0] = btoi(cdr.Prev[0]);
1582 tmpp[1] = btoi(cdr.Prev[1]);
1583 tmpp[2] = btoi(cdr.Prev[2]);
1588 if (cdr.freeze_ver < 0x63647202)
1589 memcpy(cdr.SetSectorPlay, cdr.SetSector, 3);
1591 Find_CurTrack(cdr.SetSectorPlay);
1593 CDR_play(cdr.SetSectorPlay);
1594 if (psxRegs.interrupt & (1 << PSXINT_CDRPLAY_OLD))
1595 CDRPLAYSEEKREAD_INT((cdr.Mode & 0x80) ? (cdReadTime / 2) : cdReadTime, 1);
1598 if ((cdr.freeze_ver & 0xffffff00) != 0x63647200) {
1599 // old versions did not latch Reg2, have to fixup..
1600 if (cdr.Reg2 == 0) {
1601 SysPrintf("cdrom: fixing up old savestate\n");
1604 // also did not save Attenuator..
1605 if ((cdr.AttenuatorLeftToLeft | cdr.AttenuatorLeftToRight
1606 | cdr.AttenuatorRightToLeft | cdr.AttenuatorRightToRight) == 0)
1608 cdr.AttenuatorLeftToLeft = cdr.AttenuatorRightToRight = 0x80;
1616 void LidInterrupt(void) {
1618 cdrLidSeekInterrupt();