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
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;
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;
111 u8 AttenuatorLeftToLeft, AttenuatorLeftToRight;
112 u8 AttenuatorRightToRight, AttenuatorRightToLeft;
113 u8 AttenuatorLeftToLeftT, AttenuatorLeftToRightT;
114 u8 AttenuatorRightToRightT, AttenuatorRightToLeftT;
116 static unsigned char *pTransfer;
117 static s16 read_buf[CD_FRAMESIZE_RAW/2];
119 /* CD-ROM magic numbers */
120 #define CdlSync 0 /* nocash documentation : "Uh, actually, returns error code 40h = Invalid Command...?" */
125 #define CdlBackward 5
133 #define CdlSetfilter 13
134 #define CdlSetmode 14
135 #define CdlGetparam 15
136 #define CdlGetlocL 16
137 #define CdlGetlocP 17
143 #define CdlSetclock 23
144 #define CdlGetclock 24
150 #define CdlReadToc 30
152 #ifdef CDR_LOG_CMD_IRQ
153 static const char * const CmdName[0x100] = {
154 "CdlSync", "CdlNop", "CdlSetloc", "CdlPlay",
155 "CdlForward", "CdlBackward", "CdlReadN", "CdlStandby",
156 "CdlStop", "CdlPause", "CdlReset", "CdlMute",
157 "CdlDemute", "CdlSetfilter", "CdlSetmode", "CdlGetparam",
158 "CdlGetlocL", "CdlGetlocP", "CdlReadT", "CdlGetTN",
159 "CdlGetTD", "CdlSeekL", "CdlSeekP", "CdlSetclock",
160 "CdlGetclock", "CdlTest", "CdlID", "CdlReadS",
161 "CdlInit", NULL, "CDlReadToc", NULL
165 unsigned char Test04[] = { 0 };
166 unsigned char Test05[] = { 0 };
167 unsigned char Test20[] = { 0x98, 0x06, 0x10, 0xC3 };
168 unsigned char Test22[] = { 0x66, 0x6F, 0x72, 0x20, 0x45, 0x75, 0x72, 0x6F };
169 unsigned char Test23[] = { 0x43, 0x58, 0x44, 0x32, 0x39 ,0x34, 0x30, 0x51 };
175 #define Acknowledge 3
180 #define MODE_SPEED (1<<7) // 0x80
181 #define MODE_STRSND (1<<6) // 0x40 ADPCM on/off
182 #define MODE_SIZE_2340 (1<<5) // 0x20
183 #define MODE_SIZE_2328 (1<<4) // 0x10
184 #define MODE_SIZE_2048 (0<<4) // 0x00
185 #define MODE_SF (1<<3) // 0x08 channel on/off
186 #define MODE_REPORT (1<<2) // 0x04
187 #define MODE_AUTOPAUSE (1<<1) // 0x02
188 #define MODE_CDDA (1<<0) // 0x01
191 #define STATUS_PLAY (1<<7) // 0x80
192 #define STATUS_SEEK (1<<6) // 0x40
193 #define STATUS_READ (1<<5) // 0x20
194 #define STATUS_SHELLOPEN (1<<4) // 0x10
195 #define STATUS_UNKNOWN3 (1<<3) // 0x08
196 #define STATUS_UNKNOWN2 (1<<2) // 0x04
197 #define STATUS_ROTATING (1<<1) // 0x02
198 #define STATUS_ERROR (1<<0) // 0x01
201 #define ERROR_NOTREADY (1<<7) // 0x80
202 #define ERROR_INVALIDCMD (1<<6) // 0x40
203 #define ERROR_INVALIDARG (1<<5) // 0x20
205 // 1x = 75 sectors per second
206 // PSXCLK = 1 sec in the ps
207 // so (PSXCLK / 75) = cdr read time (linuzappz)
208 #define cdReadTime (PSXCLK / 75)
211 DRIVESTATE_STANDBY = 0, // pause, play, read
213 DRIVESTATE_RESCAN_CD,
214 DRIVESTATE_PREPARE_CD,
218 static struct CdrStat stat;
220 static unsigned int msf2sec(const u8 *msf) {
221 return ((msf[0] * 60 + msf[1]) * 75) + msf[2];
224 // for that weird psemu API..
225 static unsigned int fsm2sec(const u8 *msf) {
226 return ((msf[2] * 60 + msf[1]) * 75) + msf[0];
229 static void sec2msf(unsigned int s, u8 *msf) {
230 msf[0] = s / 75 / 60;
231 s = s - msf[0] * 75 * 60;
238 #define CDR_INT(eCycle) { \
239 psxRegs.interrupt |= (1 << PSXINT_CDR); \
240 psxRegs.intCycle[PSXINT_CDR].cycle = eCycle; \
241 psxRegs.intCycle[PSXINT_CDR].sCycle = psxRegs.cycle; \
242 new_dyna_set_event(PSXINT_CDR, eCycle); \
245 // cdrPlaySeekReadInterrupt
246 #define CDRPLAYSEEKREAD_INT(eCycle, isFirst) { \
248 psxRegs.interrupt |= (1 << PSXINT_CDREAD); \
250 psxRegs.intCycle[PSXINT_CDREAD].sCycle = psxRegs.cycle; \
252 psxRegs.intCycle[PSXINT_CDREAD].sCycle += psxRegs.intCycle[PSXINT_CDREAD].cycle; \
253 psxRegs.intCycle[PSXINT_CDREAD].cycle = e_; \
254 new_dyna_set_event_abs(PSXINT_CDREAD, psxRegs.intCycle[PSXINT_CDREAD].sCycle + e_); \
257 // cdrLidSeekInterrupt
258 #define CDRLID_INT(eCycle) { \
259 psxRegs.interrupt |= (1 << PSXINT_CDRLID); \
260 psxRegs.intCycle[PSXINT_CDRLID].cycle = eCycle; \
261 psxRegs.intCycle[PSXINT_CDRLID].sCycle = psxRegs.cycle; \
262 new_dyna_set_event(PSXINT_CDRLID, eCycle); \
265 #define StopReading() { \
267 psxRegs.interrupt &= ~(1 << PSXINT_CDREAD); \
270 #define StopCdda() { \
271 if (cdr.Play && !Config.Cdda) CDR_stop(); \
273 cdr.FastForward = 0; \
274 cdr.FastBackward = 0; \
277 #define SetPlaySeekRead(x, f) { \
278 x &= ~(STATUS_PLAY | STATUS_SEEK | STATUS_READ); \
282 #define SetResultSize(size) { \
284 cdr.ResultC = size; \
285 cdr.ResultReady = 1; \
288 static void setIrq(int log_cmd)
290 if (cdr.Stat & cdr.Reg2)
291 psxHu32ref(0x1070) |= SWAP32((u32)0x4);
293 #ifdef CDR_LOG_CMD_IRQ
297 SysPrintf("CDR IRQ=%d cmd %02x stat %02x: ",
298 !!(cdr.Stat & cdr.Reg2), log_cmd, cdr.Stat);
299 for (i = 0; i < cdr.ResultC; i++)
300 SysPrintf("%02x ", cdr.Result[i]);
306 // timing used in this function was taken from tests on real hardware
307 // (yes it's slow, but you probably don't want to modify it)
308 void cdrLidSeekInterrupt(void)
310 switch (cdr.DriveState) {
312 case DRIVESTATE_STANDBY:
315 SetPlaySeekRead(cdr.StatP, 0);
317 if (CDR_getStatus(&stat) == -1)
320 if (stat.Status & STATUS_SHELLOPEN)
322 cdr.DriveState = DRIVESTATE_LID_OPEN;
327 case DRIVESTATE_LID_OPEN:
328 if (CDR_getStatus(&stat) == -1)
329 stat.Status &= ~STATUS_SHELLOPEN;
332 if (!(cdr.StatP & STATUS_SHELLOPEN)) {
333 cdr.StatP |= STATUS_SHELLOPEN;
335 // could generate error irq here, but real hardware
336 // only sometimes does that
337 // (not done when lots of commands are sent?)
339 CDRLID_INT(cdReadTime * 30);
342 else if (cdr.StatP & STATUS_ROTATING) {
343 cdr.StatP &= ~STATUS_ROTATING;
345 else if (!(stat.Status & STATUS_SHELLOPEN)) {
349 // cdr.StatP STATUS_SHELLOPEN is "sticky"
350 // and is only cleared by CdlNop
352 cdr.DriveState = DRIVESTATE_RESCAN_CD;
353 CDRLID_INT(cdReadTime * 105);
358 CDRLID_INT(cdReadTime * 3);
361 case DRIVESTATE_RESCAN_CD:
362 cdr.StatP |= STATUS_ROTATING;
363 cdr.DriveState = DRIVESTATE_PREPARE_CD;
365 // this is very long on real hardware, over 6 seconds
366 // make it a bit faster here...
367 CDRLID_INT(cdReadTime * 150);
370 case DRIVESTATE_PREPARE_CD:
371 cdr.StatP |= STATUS_SEEK;
373 cdr.DriveState = DRIVESTATE_STANDBY;
374 CDRLID_INT(cdReadTime * 26);
379 static void Find_CurTrack(const u8 *time)
383 current = msf2sec(time);
385 for (cdr.CurTrack = 1; cdr.CurTrack < cdr.ResultTN[1]; cdr.CurTrack++) {
386 CDR_getTD(cdr.CurTrack + 1, cdr.ResultTD);
387 sect = fsm2sec(cdr.ResultTD);
388 if (sect - current >= 150)
393 static void generate_subq(const u8 *time)
395 unsigned char start[3], next[3];
396 unsigned int this_s, start_s, next_s, pregap;
399 CDR_getTD(cdr.CurTrack, start);
400 if (cdr.CurTrack + 1 <= cdr.ResultTN[1]) {
402 CDR_getTD(cdr.CurTrack + 1, next);
405 // last track - cd size
407 next[0] = cdr.SetSectorEnd[2];
408 next[1] = cdr.SetSectorEnd[1];
409 next[2] = cdr.SetSectorEnd[0];
412 this_s = msf2sec(time);
413 start_s = fsm2sec(start);
414 next_s = fsm2sec(next);
416 cdr.TrackChanged = FALSE;
418 if (next_s - this_s < pregap) {
419 cdr.TrackChanged = TRUE;
426 relative_s = this_s - start_s;
427 if (relative_s < 0) {
429 relative_s = -relative_s;
431 sec2msf(relative_s, cdr.subq.Relative);
433 cdr.subq.Track = itob(cdr.CurTrack);
434 cdr.subq.Relative[0] = itob(cdr.subq.Relative[0]);
435 cdr.subq.Relative[1] = itob(cdr.subq.Relative[1]);
436 cdr.subq.Relative[2] = itob(cdr.subq.Relative[2]);
437 cdr.subq.Absolute[0] = itob(time[0]);
438 cdr.subq.Absolute[1] = itob(time[1]);
439 cdr.subq.Absolute[2] = itob(time[2]);
442 static void ReadTrack(const u8 *time) {
443 unsigned char tmp[3];
447 tmp[0] = itob(time[0]);
448 tmp[1] = itob(time[1]);
449 tmp[2] = itob(time[2]);
451 if (memcmp(cdr.Prev, tmp, 3) == 0)
454 CDR_LOG("ReadTrack *** %02x:%02x:%02x\n", tmp[0], tmp[1], tmp[2]);
456 cdr.NoErr = CDR_readTrack(tmp);
457 memcpy(cdr.Prev, tmp, 3);
462 subq = (struct SubQ *)CDR_getBufferSub();
463 if (subq != NULL && cdr.CurTrack == 1) {
464 crc = calcCrc((u8 *)subq + 12, 10);
465 if (crc == (((u16)subq->CRC[0] << 8) | subq->CRC[1])) {
466 cdr.subq.Track = subq->TrackNumber;
467 cdr.subq.Index = subq->IndexNumber;
468 memcpy(cdr.subq.Relative, subq->TrackRelativeAddress, 3);
469 memcpy(cdr.subq.Absolute, subq->AbsoluteAddress, 3);
472 CDR_LOG_I("subq bad crc @%02x:%02x:%02x\n",
473 tmp[0], tmp[1], tmp[2]);
480 CDR_LOG(" -> %02x,%02x %02x:%02x:%02x %02x:%02x:%02x\n",
481 cdr.subq.Track, cdr.subq.Index,
482 cdr.subq.Relative[0], cdr.subq.Relative[1], cdr.subq.Relative[2],
483 cdr.subq.Absolute[0], cdr.subq.Absolute[1], cdr.subq.Absolute[2]);
486 static void cdrPlayInterrupt_Autopause()
489 boolean abs_lev_chselect;
492 if ((cdr.Mode & MODE_AUTOPAUSE) && cdr.TrackChanged) {
493 CDR_LOG( "CDDA STOP\n" );
495 // Magic the Gathering
496 // - looping territory cdda
499 //cdr.ResultReady = 1;
500 //cdr.Stat = DataReady;
505 SetPlaySeekRead(cdr.StatP, 0);
507 else if (((cdr.Mode & MODE_REPORT) || cdr.FastForward || cdr.FastBackward)) {
508 cdr.Result[0] = cdr.StatP;
509 cdr.Result[1] = cdr.subq.Track;
510 cdr.Result[2] = cdr.subq.Index;
512 abs_lev_chselect = cdr.subq.Absolute[1] & 0x01;
514 /* 8 is a hack. For accuracy, it should be 588. */
515 for (i = 0; i < 8; i++)
517 abs_lev_max = MAX_VALUE(abs_lev_max, abs(read_buf[i * 2 + abs_lev_chselect]));
519 abs_lev_max = MIN_VALUE(abs_lev_max, 32767);
520 abs_lev_max |= abs_lev_chselect << 15;
522 if (cdr.subq.Absolute[2] & 0x10) {
523 cdr.Result[3] = cdr.subq.Relative[0];
524 cdr.Result[4] = cdr.subq.Relative[1] | 0x80;
525 cdr.Result[5] = cdr.subq.Relative[2];
528 cdr.Result[3] = cdr.subq.Absolute[0];
529 cdr.Result[4] = cdr.subq.Absolute[1];
530 cdr.Result[5] = cdr.subq.Absolute[2];
533 cdr.Result[6] = abs_lev_max >> 0;
534 cdr.Result[7] = abs_lev_max >> 8;
536 // Rayman: Logo freeze (resultready + dataready)
538 cdr.Stat = DataReady;
545 static int cdrSeekTime(unsigned char *target)
547 int seekTime = abs(msf2sec(cdr.SetSectorPlay) - msf2sec(target)) * (cdReadTime / 200);
550 * It was originally set to 1000000 for Driver, however it is not high enough for Worms Pinball
551 * and was unreliable for that game.
552 * I also tested it against Mednafen and Driver's titlescreen music starts 25 frames later, not immediatly.
554 * Obviously, this isn't perfect but right now, it should be a bit better.
555 * Games to test this against if you change that setting :
556 * - Driver (titlescreen music delay and retry mission)
557 * - Worms Pinball (Will either not boot or crash in the memory card screen)
558 * - Viewpoint (short pauses if the delay in the ingame music is too long)
560 * It seems that 3386880 * 5 is too much for Driver's titlescreen and it starts skipping.
561 * However, 1000000 is not enough for Worms Pinball to reliably boot.
563 if(seekTime > 3386880 * 2) seekTime = 3386880 * 2;
564 CDR_LOG("seek: %.2f %.2f\n", (float)seekTime / PSXCLK, (float)seekTime / cdReadTime);
568 static void cdrReadInterrupt(void);
570 void cdrPlaySeekReadInterrupt(void)
577 if (!cdr.Play && (cdr.StatP & STATUS_SEEK)) {
579 CDR_LOG_I("cdrom: seek stat hack\n");
580 CDRPLAYSEEKREAD_INT(0x1000, 1);
584 cdr.StatP |= STATUS_ROTATING;
585 SetPlaySeekRead(cdr.StatP, 0);
586 cdr.Result[0] = cdr.StatP;
592 Find_CurTrack(cdr.SetSectorPlay);
593 ReadTrack(cdr.SetSectorPlay);
594 cdr.TrackChanged = FALSE;
598 if (!cdr.Play) return;
600 CDR_LOG( "CDDA - %d:%d:%d\n",
601 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2] );
603 SetPlaySeekRead(cdr.StatP, STATUS_PLAY);
604 if (memcmp(cdr.SetSectorPlay, cdr.SetSectorEnd, 3) == 0) {
606 SetPlaySeekRead(cdr.StatP, 0);
607 cdr.TrackChanged = TRUE;
610 CDR_readCDDA(cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2], (u8 *)read_buf);
613 if (!cdr.Stat && (cdr.Mode & (MODE_AUTOPAUSE|MODE_REPORT)))
614 cdrPlayInterrupt_Autopause();
616 if (!cdr.Muted && !Config.Cdda) {
617 cdrAttenuate(read_buf, CD_FRAMESIZE_RAW / 4, 1);
618 SPU_playCDDAchannel(read_buf, CD_FRAMESIZE_RAW, psxRegs.cycle, cdr.FirstSector);
622 cdr.SetSectorPlay[2]++;
623 if (cdr.SetSectorPlay[2] == 75) {
624 cdr.SetSectorPlay[2] = 0;
625 cdr.SetSectorPlay[1]++;
626 if (cdr.SetSectorPlay[1] == 60) {
627 cdr.SetSectorPlay[1] = 0;
628 cdr.SetSectorPlay[0]++;
632 CDRPLAYSEEKREAD_INT(cdReadTime, 0);
634 // update for CdlGetlocP/autopause
635 generate_subq(cdr.SetSectorPlay);
638 void cdrInterrupt(void) {
639 int no_busy_error = 0;
640 int start_rotating = 0;
642 unsigned int seekTime = 0;
643 u32 second_resp_time = 0;
651 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);
1115 #define ssat32_to_16(v) \
1116 asm("ssat %0,#16,%1" : "=r" (v) : "r" (v))
1118 #define ssat32_to_16(v) do { \
1119 if (v < -32768) v = -32768; \
1120 else if (v > 32767) v = 32767; \
1124 void cdrAttenuate(s16 *buf, int samples, int stereo)
1127 int ll = cdr.AttenuatorLeftToLeft;
1128 int lr = cdr.AttenuatorLeftToRight;
1129 int rl = cdr.AttenuatorRightToLeft;
1130 int rr = cdr.AttenuatorRightToRight;
1132 if (lr == 0 && rl == 0 && 0x78 <= ll && ll <= 0x88 && 0x78 <= rr && rr <= 0x88)
1135 if (!stereo && ll == 0x40 && lr == 0x40 && rl == 0x40 && rr == 0x40)
1139 for (i = 0; i < samples; i++) {
1142 l = (l * ll + r * rl) >> 7;
1143 r = (r * rr + l * lr) >> 7;
1151 for (i = 0; i < samples; i++) {
1153 l = l * (ll + rl) >> 7;
1154 //r = r * (rr + lr) >> 7;
1162 static void cdrReadInterrupt(void)
1167 CDR_LOG_I("cdrom: read stat hack %02x %02x\n", cdr.Cmd, cdr.Stat);
1168 CDRPLAYSEEKREAD_INT(2048, 1);
1173 SetPlaySeekRead(cdr.StatP, STATUS_READ | STATUS_ROTATING);
1174 cdr.Result[0] = cdr.StatP;
1176 ReadTrack(cdr.SetSectorPlay);
1178 buf = CDR_getBuffer();
1183 CDR_LOG_I("cdrReadInterrupt() Log: err\n");
1184 memset(cdr.Transfer, 0, DATA_SIZE);
1185 cdr.Stat = DiskError;
1186 cdr.Result[0] |= STATUS_ERROR;
1191 memcpy(cdr.Transfer, buf, DATA_SIZE);
1192 CheckPPFCache(cdr.Transfer, cdr.Prev[0], cdr.Prev[1], cdr.Prev[2]);
1195 CDR_LOG("cdrReadInterrupt() Log: cdr.Transfer %x:%x:%x\n", cdr.Transfer[0], cdr.Transfer[1], cdr.Transfer[2]);
1197 if ((!cdr.Muted) && (cdr.Mode & MODE_STRSND) && (!Config.Xa) && (cdr.FirstSector != -1)) { // CD-XA
1198 // Firemen 2: Multi-XA files - briefings, cutscenes
1199 if( cdr.FirstSector == 1 && (cdr.Mode & MODE_SF)==0 ) {
1200 cdr.File = cdr.Transfer[4 + 0];
1201 cdr.Channel = cdr.Transfer[4 + 1];
1205 * Skips playing on channel 255.
1206 * Fixes missing audio in Blue's Clues : Blue's Big Musical. (Should also fix Taxi 2)
1207 * TODO : Check if this is the proper behaviour.
1209 if((cdr.Transfer[4 + 2] & 0x4) &&
1210 (cdr.Transfer[4 + 1] == cdr.Channel) &&
1211 (cdr.Transfer[4 + 0] == cdr.File) && cdr.Channel != 255) {
1212 int ret = xa_decode_sector(&cdr.Xa, cdr.Transfer+4, cdr.FirstSector);
1214 cdrAttenuate(cdr.Xa.pcm, cdr.Xa.nsamples, cdr.Xa.stereo);
1215 SPU_playADPCMchannel(&cdr.Xa, psxRegs.cycle, cdr.FirstSector);
1216 cdr.FirstSector = 0;
1218 else cdr.FirstSector = -1;
1222 cdr.SetSectorPlay[2]++;
1223 if (cdr.SetSectorPlay[2] == 75) {
1224 cdr.SetSectorPlay[2] = 0;
1225 cdr.SetSectorPlay[1]++;
1226 if (cdr.SetSectorPlay[1] == 60) {
1227 cdr.SetSectorPlay[1] = 0;
1228 cdr.SetSectorPlay[0]++;
1234 CDRPLAYSEEKREAD_INT((cdr.Mode & MODE_SPEED) ? (cdReadTime / 2) : cdReadTime, 0);
1237 Croc 2: $40 - only FORM1 (*)
1238 Judge Dredd: $C8 - only FORM1 (*)
1239 Sim Theme Park - no adpcm at all (zero)
1242 if (!(cdr.Mode & MODE_STRSND) || !(cdr.Transfer[4+2] & 0x4)) {
1243 cdr.Stat = DataReady;
1247 // update for CdlGetlocP
1248 ReadTrack(cdr.SetSectorPlay);
1257 bit 5 - 1 result ready
1259 bit 7 - 1 command being processed
1262 unsigned char cdrRead0(void) {
1263 if (cdr.ResultReady)
1268 cdr.Ctrl |= 0x40; // data fifo not empty
1270 // What means the 0x10 and the 0x08 bits? I only saw it used by the bios
1273 CDR_LOG_IO("cdr r0.sta: %02x\n", cdr.Ctrl);
1275 return psxHu8(0x1800) = cdr.Ctrl;
1278 void cdrWrite0(unsigned char rt) {
1279 CDR_LOG_IO("cdr w0.idx: %02x\n", rt);
1281 cdr.Ctrl = (rt & 3) | (cdr.Ctrl & ~3);
1284 unsigned char cdrRead1(void) {
1285 if ((cdr.ResultP & 0xf) < cdr.ResultC)
1286 psxHu8(0x1801) = cdr.Result[cdr.ResultP & 0xf];
1290 if (cdr.ResultP == cdr.ResultC)
1291 cdr.ResultReady = 0;
1293 CDR_LOG_IO("cdr r1.rsp: %02x #%u\n", psxHu8(0x1801), cdr.ResultP - 1);
1295 return psxHu8(0x1801);
1298 void cdrWrite1(unsigned char rt) {
1299 const char *rnames[] = { "cmd", "smd", "smc", "arr" }; (void)rnames;
1300 CDR_LOG_IO("cdr w1.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1302 switch (cdr.Ctrl & 3) {
1306 cdr.AttenuatorRightToRightT = rt;
1312 #ifdef CDR_LOG_CMD_IRQ
1313 SysPrintf("CD1 write: %x (%s)", rt, CmdName[rt]);
1316 SysPrintf(" Param[%d] = {", cdr.ParamC);
1317 for (i = 0; i < cdr.ParamC; i++)
1318 SysPrintf(" %x,", cdr.Param[i]);
1325 cdr.ResultReady = 0;
1328 if (!cdr.CmdInProgress) {
1329 cdr.CmdInProgress = rt;
1330 // should be something like 12k + controller delays
1334 CDR_LOG_I("cdr: cmd while busy: %02x, prev %02x, busy %02x\n",
1335 rt, cdr.Cmd, cdr.CmdInProgress);
1336 if (cdr.CmdInProgress < 0x100) // no pending 2nd response
1337 cdr.CmdInProgress = rt;
1343 unsigned char cdrRead2(void) {
1346 if (cdr.Readed == 0) {
1352 CDR_LOG_IO("cdr r2.dat: %02x\n", ret);
1356 void cdrWrite2(unsigned char rt) {
1357 const char *rnames[] = { "prm", "ien", "all", "arl" }; (void)rnames;
1358 CDR_LOG_IO("cdr w2.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1360 switch (cdr.Ctrl & 3) {
1362 if (cdr.ParamC < 8) // FIXME: size and wrapping
1363 cdr.Param[cdr.ParamC++] = rt;
1370 cdr.AttenuatorLeftToLeftT = rt;
1373 cdr.AttenuatorRightToLeftT = rt;
1378 unsigned char cdrRead3(void) {
1380 psxHu8(0x1803) = cdr.Stat | 0xE0;
1382 psxHu8(0x1803) = cdr.Reg2 | 0xE0;
1384 CDR_LOG_IO("cdr r3.%s: %02x\n", (cdr.Ctrl & 1) ? "ifl" : "ien", psxHu8(0x1803));
1385 return psxHu8(0x1803);
1388 void cdrWrite3(unsigned char rt) {
1389 const char *rnames[] = { "req", "ifl", "alr", "ava" }; (void)rnames;
1390 CDR_LOG_IO("cdr w3.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1392 switch (cdr.Ctrl & 3) {
1396 #ifdef CDR_LOG_CMD_IRQ
1398 SysPrintf("ack %02x\n", cdr.Stat & rt);
1406 cdr.AttenuatorLeftToRightT = rt;
1410 memcpy(&cdr.AttenuatorLeftToLeft, &cdr.AttenuatorLeftToLeftT, 4);
1411 CDR_LOG("CD-XA Volume: %02x %02x | %02x %02x\n",
1412 cdr.AttenuatorLeftToLeft, cdr.AttenuatorLeftToRight,
1413 cdr.AttenuatorRightToLeft, cdr.AttenuatorRightToRight);
1418 if ((rt & 0x80) && cdr.Readed == 0) {
1420 pTransfer = cdr.Transfer;
1422 switch (cdr.Mode & 0x30) {
1423 case MODE_SIZE_2328:
1428 case MODE_SIZE_2340:
1438 void psxDma3(u32 madr, u32 bcr, u32 chcr) {
1443 CDR_LOG("psxDma3() Log: *** DMA 3 *** %x addr = %x size = %x\n", chcr, madr, bcr);
1448 if (cdr.Readed == 0) {
1449 CDR_LOG("psxDma3() Log: *** DMA 3 *** NOT READY\n");
1453 cdsize = (bcr & 0xffff) * 4;
1455 // Ape Escape: bcr = 0001 / 0000
1459 switch (cdr.Mode & (MODE_SIZE_2340|MODE_SIZE_2328)) {
1460 case MODE_SIZE_2340: cdsize = 2340; break;
1461 case MODE_SIZE_2328: cdsize = 2328; break;
1463 case MODE_SIZE_2048: cdsize = 2048; break;
1468 ptr = (u8 *)PSXM(madr);
1469 if (ptr == INVALID_PTR) {
1470 CDR_LOG("psxDma3() Log: *** DMA 3 *** NULL Pointer!\n");
1475 GS CDX: Enhancement CD crash
1478 - Spams DMA3 and gets buffer overrun
1480 size = CD_FRAMESIZE_RAW - (pTransfer - cdr.Transfer);
1485 memcpy(ptr, pTransfer, size);
1488 psxCpu->Clear(madr, cdsize / 4);
1489 pTransfer += cdsize;
1491 if( chcr == 0x11400100 ) {
1492 HW_DMA3_MADR = SWAPu32(madr + cdsize);
1493 CDRDMA_INT( (cdsize/4) / 4 );
1495 else if( chcr == 0x11000000 ) {
1496 // CDRDMA_INT( (cdsize/4) * 1 );
1498 psxRegs.cycle += (cdsize/4) * 24/2;
1504 CDR_LOG("psxDma3() Log: Unknown cddma %x\n", chcr);
1508 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1512 void cdrDmaInterrupt(void)
1514 if (HW_DMA3_CHCR & SWAP32(0x01000000))
1516 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1521 static void getCdInfo(void)
1525 CDR_getTN(cdr.ResultTN);
1526 CDR_getTD(0, cdr.SetSectorEnd);
1527 tmp = cdr.SetSectorEnd[0];
1528 cdr.SetSectorEnd[0] = cdr.SetSectorEnd[2];
1529 cdr.SetSectorEnd[2] = tmp;
1533 memset(&cdr, 0, sizeof(cdr));
1539 cdr.DriveState = DRIVESTATE_STANDBY;
1540 cdr.StatP = STATUS_ROTATING;
1541 pTransfer = cdr.Transfer;
1543 // BIOS player - default values
1544 cdr.AttenuatorLeftToLeft = 0x80;
1545 cdr.AttenuatorLeftToRight = 0x00;
1546 cdr.AttenuatorRightToLeft = 0x00;
1547 cdr.AttenuatorRightToRight = 0x80;
1552 int cdrFreeze(void *f, int Mode) {
1556 if (Mode == 0 && !Config.Cdda)
1559 cdr.freeze_ver = 0x63647202;
1560 gzfreeze(&cdr, sizeof(cdr));
1563 cdr.ParamP = cdr.ParamC;
1564 tmp = pTransfer - cdr.Transfer;
1567 gzfreeze(&tmp, sizeof(tmp));
1572 pTransfer = cdr.Transfer + tmp;
1574 // read right sub data
1575 tmpp[0] = btoi(cdr.Prev[0]);
1576 tmpp[1] = btoi(cdr.Prev[1]);
1577 tmpp[2] = btoi(cdr.Prev[2]);
1582 if (cdr.freeze_ver < 0x63647202)
1583 memcpy(cdr.SetSectorPlay, cdr.SetSector, 3);
1585 Find_CurTrack(cdr.SetSectorPlay);
1587 CDR_play(cdr.SetSectorPlay);
1588 if (psxRegs.interrupt & (1 << PSXINT_CDRPLAY_OLD))
1589 CDRPLAYSEEKREAD_INT((cdr.Mode & 0x80) ? (cdReadTime / 2) : cdReadTime, 1);
1592 if ((cdr.freeze_ver & 0xffffff00) != 0x63647200) {
1593 // old versions did not latch Reg2, have to fixup..
1594 if (cdr.Reg2 == 0) {
1595 SysPrintf("cdrom: fixing up old savestate\n");
1598 // also did not save Attenuator..
1599 if ((cdr.AttenuatorLeftToLeft | cdr.AttenuatorLeftToRight
1600 | cdr.AttenuatorRightToLeft | cdr.AttenuatorRightToRight) == 0)
1602 cdr.AttenuatorLeftToLeft = cdr.AttenuatorRightToRight = 0x80;
1610 void LidInterrupt(void) {
1612 cdrLidSeekInterrupt();