1 /***************************************************************************
2 * Copyright (C) 2007 Ryan Schultz, PCSX-df Team, PCSX team *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, write to the *
16 * Free Software Foundation, Inc., *
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *
18 ***************************************************************************/
21 * Handles all CD-ROM registers and functions.
27 #include "arm_features.h"
31 #define CDR_LOG SysPrintf
36 #define CDR_LOG_I SysPrintf
38 #define CDR_LOG_I log_unhandled
41 #define CDR_LOG_IO SysPrintf
43 #define CDR_LOG_IO(...)
45 //#define CDR_LOG_CMD_IRQ
48 // unused members maintain savesate compatibility
49 unsigned char unused0;
50 unsigned char unused1;
52 unsigned char unused2;
58 unsigned char Transfer[DATA_SIZE];
62 unsigned char Relative[3];
63 unsigned char Absolute[3];
65 unsigned char TrackChanged;
66 unsigned char unused3[3];
67 unsigned int freeze_ver;
69 unsigned char Prev[4];
70 unsigned char Param[8];
71 unsigned char Result[16];
75 unsigned char ResultC;
76 unsigned char ResultP;
77 unsigned char ResultReady;
79 unsigned char unused4;
80 unsigned char SetlocPending;
83 unsigned char ResultTN[6];
84 unsigned char ResultTD[4];
85 unsigned char SetSectorPlay[4];
86 unsigned char SetSectorEnd[4];
87 unsigned char SetSector[4];
91 int Mode, File, Channel;
113 u8 AttenuatorLeftToLeft, AttenuatorLeftToRight;
114 u8 AttenuatorRightToRight, AttenuatorRightToLeft;
115 u8 AttenuatorLeftToLeftT, AttenuatorLeftToRightT;
116 u8 AttenuatorRightToRightT, AttenuatorRightToLeftT;
118 static s16 read_buf[CD_FRAMESIZE_RAW/2];
120 /* CD-ROM magic numbers */
121 #define CdlSync 0 /* nocash documentation : "Uh, actually, returns error code 40h = Invalid Command...?" */
126 #define CdlBackward 5
134 #define CdlSetfilter 13
135 #define CdlSetmode 14
136 #define CdlGetparam 15
137 #define CdlGetlocL 16
138 #define CdlGetlocP 17
144 #define CdlSetclock 23
145 #define CdlGetclock 24
151 #define CdlReadToc 30
153 #ifdef CDR_LOG_CMD_IRQ
154 static const char * const CmdName[0x100] = {
155 "CdlSync", "CdlNop", "CdlSetloc", "CdlPlay",
156 "CdlForward", "CdlBackward", "CdlReadN", "CdlStandby",
157 "CdlStop", "CdlPause", "CdlReset", "CdlMute",
158 "CdlDemute", "CdlSetfilter", "CdlSetmode", "CdlGetparam",
159 "CdlGetlocL", "CdlGetlocP", "CdlReadT", "CdlGetTN",
160 "CdlGetTD", "CdlSeekL", "CdlSeekP", "CdlSetclock",
161 "CdlGetclock", "CdlTest", "CdlID", "CdlReadS",
162 "CdlInit", NULL, "CDlReadToc", NULL
166 unsigned char Test04[] = { 0 };
167 unsigned char Test05[] = { 0 };
168 unsigned char Test20[] = { 0x98, 0x06, 0x10, 0xC3 };
169 unsigned char Test22[] = { 0x66, 0x6F, 0x72, 0x20, 0x45, 0x75, 0x72, 0x6F };
170 unsigned char Test23[] = { 0x43, 0x58, 0x44, 0x32, 0x39 ,0x34, 0x30, 0x51 };
176 #define Acknowledge 3
181 #define MODE_SPEED (1<<7) // 0x80
182 #define MODE_STRSND (1<<6) // 0x40 ADPCM on/off
183 #define MODE_SIZE_2340 (1<<5) // 0x20
184 #define MODE_SIZE_2328 (1<<4) // 0x10
185 #define MODE_SIZE_2048 (0<<4) // 0x00
186 #define MODE_SF (1<<3) // 0x08 channel on/off
187 #define MODE_REPORT (1<<2) // 0x04
188 #define MODE_AUTOPAUSE (1<<1) // 0x02
189 #define MODE_CDDA (1<<0) // 0x01
192 #define STATUS_PLAY (1<<7) // 0x80
193 #define STATUS_SEEK (1<<6) // 0x40
194 #define STATUS_READ (1<<5) // 0x20
195 #define STATUS_SHELLOPEN (1<<4) // 0x10
196 #define STATUS_UNKNOWN3 (1<<3) // 0x08
197 #define STATUS_UNKNOWN2 (1<<2) // 0x04
198 #define STATUS_ROTATING (1<<1) // 0x02
199 #define STATUS_ERROR (1<<0) // 0x01
202 #define ERROR_NOTREADY (1<<7) // 0x80
203 #define ERROR_INVALIDCMD (1<<6) // 0x40
204 #define ERROR_INVALIDARG (1<<5) // 0x20
206 // 1x = 75 sectors per second
207 // PSXCLK = 1 sec in the ps
208 // so (PSXCLK / 75) = cdr read time (linuzappz)
209 #define cdReadTime (PSXCLK / 75)
212 DRIVESTATE_STANDBY = 0, // pause, play, read
214 DRIVESTATE_RESCAN_CD,
215 DRIVESTATE_PREPARE_CD,
219 static struct CdrStat stat;
221 static unsigned int msf2sec(const u8 *msf) {
222 return ((msf[0] * 60 + msf[1]) * 75) + msf[2];
225 // for that weird psemu API..
226 static unsigned int fsm2sec(const u8 *msf) {
227 return ((msf[2] * 60 + msf[1]) * 75) + msf[0];
230 static void sec2msf(unsigned int s, u8 *msf) {
231 msf[0] = s / 75 / 60;
232 s = s - msf[0] * 75 * 60;
239 #define CDR_INT(eCycle) { \
240 psxRegs.interrupt |= (1 << PSXINT_CDR); \
241 psxRegs.intCycle[PSXINT_CDR].cycle = eCycle; \
242 psxRegs.intCycle[PSXINT_CDR].sCycle = psxRegs.cycle; \
243 new_dyna_set_event(PSXINT_CDR, eCycle); \
246 // cdrPlaySeekReadInterrupt
247 #define CDRPLAYSEEKREAD_INT(eCycle, isFirst) { \
249 psxRegs.interrupt |= (1 << PSXINT_CDREAD); \
251 psxRegs.intCycle[PSXINT_CDREAD].sCycle = psxRegs.cycle; \
253 psxRegs.intCycle[PSXINT_CDREAD].sCycle += psxRegs.intCycle[PSXINT_CDREAD].cycle; \
254 psxRegs.intCycle[PSXINT_CDREAD].cycle = e_; \
255 new_dyna_set_event_abs(PSXINT_CDREAD, psxRegs.intCycle[PSXINT_CDREAD].sCycle + e_); \
258 // cdrLidSeekInterrupt
259 #define CDRLID_INT(eCycle) { \
260 psxRegs.interrupt |= (1 << PSXINT_CDRLID); \
261 psxRegs.intCycle[PSXINT_CDRLID].cycle = eCycle; \
262 psxRegs.intCycle[PSXINT_CDRLID].sCycle = psxRegs.cycle; \
263 new_dyna_set_event(PSXINT_CDRLID, eCycle); \
266 #define StopReading() { \
268 psxRegs.interrupt &= ~(1 << PSXINT_CDREAD); \
271 #define StopCdda() { \
272 if (cdr.Play && !Config.Cdda) CDR_stop(); \
274 cdr.FastForward = 0; \
275 cdr.FastBackward = 0; \
278 #define SetPlaySeekRead(x, f) { \
279 x &= ~(STATUS_PLAY | STATUS_SEEK | STATUS_READ); \
283 #define SetResultSize(size) { \
285 cdr.ResultC = size; \
286 cdr.ResultReady = 1; \
289 static void setIrq(int log_cmd)
291 if (cdr.Stat & cdr.Reg2)
292 psxHu32ref(0x1070) |= SWAP32((u32)0x4);
294 #ifdef CDR_LOG_CMD_IRQ
298 SysPrintf("CDR IRQ=%d cmd %02x stat %02x: ",
299 !!(cdr.Stat & cdr.Reg2), log_cmd, cdr.Stat);
300 for (i = 0; i < cdr.ResultC; i++)
301 SysPrintf("%02x ", cdr.Result[i]);
307 // timing used in this function was taken from tests on real hardware
308 // (yes it's slow, but you probably don't want to modify it)
309 void cdrLidSeekInterrupt(void)
311 switch (cdr.DriveState) {
313 case DRIVESTATE_STANDBY:
316 SetPlaySeekRead(cdr.StatP, 0);
318 if (CDR_getStatus(&stat) == -1)
321 if (stat.Status & STATUS_SHELLOPEN)
323 cdr.DriveState = DRIVESTATE_LID_OPEN;
328 case DRIVESTATE_LID_OPEN:
329 if (CDR_getStatus(&stat) == -1)
330 stat.Status &= ~STATUS_SHELLOPEN;
333 if (!(cdr.StatP & STATUS_SHELLOPEN)) {
334 cdr.StatP |= STATUS_SHELLOPEN;
336 // could generate error irq here, but real hardware
337 // only sometimes does that
338 // (not done when lots of commands are sent?)
340 CDRLID_INT(cdReadTime * 30);
343 else if (cdr.StatP & STATUS_ROTATING) {
344 cdr.StatP &= ~STATUS_ROTATING;
346 else if (!(stat.Status & STATUS_SHELLOPEN)) {
350 // cdr.StatP STATUS_SHELLOPEN is "sticky"
351 // and is only cleared by CdlNop
353 cdr.DriveState = DRIVESTATE_RESCAN_CD;
354 CDRLID_INT(cdReadTime * 105);
359 CDRLID_INT(cdReadTime * 3);
362 case DRIVESTATE_RESCAN_CD:
363 cdr.StatP |= STATUS_ROTATING;
364 cdr.DriveState = DRIVESTATE_PREPARE_CD;
366 // this is very long on real hardware, over 6 seconds
367 // make it a bit faster here...
368 CDRLID_INT(cdReadTime * 150);
371 case DRIVESTATE_PREPARE_CD:
372 cdr.StatP |= STATUS_SEEK;
374 cdr.DriveState = DRIVESTATE_STANDBY;
375 CDRLID_INT(cdReadTime * 26);
380 static void Find_CurTrack(const u8 *time)
384 current = msf2sec(time);
386 for (cdr.CurTrack = 1; cdr.CurTrack < cdr.ResultTN[1]; cdr.CurTrack++) {
387 CDR_getTD(cdr.CurTrack + 1, cdr.ResultTD);
388 sect = fsm2sec(cdr.ResultTD);
389 if (sect - current >= 150)
394 static void generate_subq(const u8 *time)
396 unsigned char start[3], next[3];
397 unsigned int this_s, start_s, next_s, pregap;
400 CDR_getTD(cdr.CurTrack, start);
401 if (cdr.CurTrack + 1 <= cdr.ResultTN[1]) {
403 CDR_getTD(cdr.CurTrack + 1, next);
406 // last track - cd size
408 next[0] = cdr.SetSectorEnd[2];
409 next[1] = cdr.SetSectorEnd[1];
410 next[2] = cdr.SetSectorEnd[0];
413 this_s = msf2sec(time);
414 start_s = fsm2sec(start);
415 next_s = fsm2sec(next);
417 cdr.TrackChanged = FALSE;
419 if (next_s - this_s < pregap) {
420 cdr.TrackChanged = TRUE;
427 relative_s = this_s - start_s;
428 if (relative_s < 0) {
430 relative_s = -relative_s;
432 sec2msf(relative_s, cdr.subq.Relative);
434 cdr.subq.Track = itob(cdr.CurTrack);
435 cdr.subq.Relative[0] = itob(cdr.subq.Relative[0]);
436 cdr.subq.Relative[1] = itob(cdr.subq.Relative[1]);
437 cdr.subq.Relative[2] = itob(cdr.subq.Relative[2]);
438 cdr.subq.Absolute[0] = itob(time[0]);
439 cdr.subq.Absolute[1] = itob(time[1]);
440 cdr.subq.Absolute[2] = itob(time[2]);
443 static void ReadTrack(const u8 *time) {
444 unsigned char tmp[3];
448 tmp[0] = itob(time[0]);
449 tmp[1] = itob(time[1]);
450 tmp[2] = itob(time[2]);
452 if (memcmp(cdr.Prev, tmp, 3) == 0)
455 CDR_LOG("ReadTrack *** %02x:%02x:%02x\n", tmp[0], tmp[1], tmp[2]);
457 cdr.NoErr = CDR_readTrack(tmp);
458 memcpy(cdr.Prev, tmp, 3);
463 subq = (struct SubQ *)CDR_getBufferSub();
464 if (subq != NULL && cdr.CurTrack == 1) {
465 crc = calcCrc((u8 *)subq + 12, 10);
466 if (crc == (((u16)subq->CRC[0] << 8) | subq->CRC[1])) {
467 cdr.subq.Track = subq->TrackNumber;
468 cdr.subq.Index = subq->IndexNumber;
469 memcpy(cdr.subq.Relative, subq->TrackRelativeAddress, 3);
470 memcpy(cdr.subq.Absolute, subq->AbsoluteAddress, 3);
473 CDR_LOG_I("subq bad crc @%02x:%02x:%02x\n",
474 tmp[0], tmp[1], tmp[2]);
481 CDR_LOG(" -> %02x,%02x %02x:%02x:%02x %02x:%02x:%02x\n",
482 cdr.subq.Track, cdr.subq.Index,
483 cdr.subq.Relative[0], cdr.subq.Relative[1], cdr.subq.Relative[2],
484 cdr.subq.Absolute[0], cdr.subq.Absolute[1], cdr.subq.Absolute[2]);
487 static void cdrPlayInterrupt_Autopause()
490 boolean abs_lev_chselect;
493 if ((cdr.Mode & MODE_AUTOPAUSE) && cdr.TrackChanged) {
494 CDR_LOG( "CDDA STOP\n" );
496 // Magic the Gathering
497 // - looping territory cdda
500 //cdr.ResultReady = 1;
501 //cdr.Stat = DataReady;
506 SetPlaySeekRead(cdr.StatP, 0);
508 else if (((cdr.Mode & MODE_REPORT) || cdr.FastForward || cdr.FastBackward)) {
509 cdr.Result[0] = cdr.StatP;
510 cdr.Result[1] = cdr.subq.Track;
511 cdr.Result[2] = cdr.subq.Index;
513 abs_lev_chselect = cdr.subq.Absolute[1] & 0x01;
515 /* 8 is a hack. For accuracy, it should be 588. */
516 for (i = 0; i < 8; i++)
518 abs_lev_max = MAX_VALUE(abs_lev_max, abs(read_buf[i * 2 + abs_lev_chselect]));
520 abs_lev_max = MIN_VALUE(abs_lev_max, 32767);
521 abs_lev_max |= abs_lev_chselect << 15;
523 if (cdr.subq.Absolute[2] & 0x10) {
524 cdr.Result[3] = cdr.subq.Relative[0];
525 cdr.Result[4] = cdr.subq.Relative[1] | 0x80;
526 cdr.Result[5] = cdr.subq.Relative[2];
529 cdr.Result[3] = cdr.subq.Absolute[0];
530 cdr.Result[4] = cdr.subq.Absolute[1];
531 cdr.Result[5] = cdr.subq.Absolute[2];
534 cdr.Result[6] = abs_lev_max >> 0;
535 cdr.Result[7] = abs_lev_max >> 8;
537 // Rayman: Logo freeze (resultready + dataready)
539 cdr.Stat = DataReady;
546 static int cdrSeekTime(unsigned char *target)
548 int seekTime = abs(msf2sec(cdr.SetSectorPlay) - msf2sec(target)) * (cdReadTime / 200);
551 * It was originally set to 1000000 for Driver, however it is not high enough for Worms Pinball
552 * and was unreliable for that game.
553 * I also tested it against Mednafen and Driver's titlescreen music starts 25 frames later, not immediatly.
555 * Obviously, this isn't perfect but right now, it should be a bit better.
556 * Games to test this against if you change that setting :
557 * - Driver (titlescreen music delay and retry mission)
558 * - Worms Pinball (Will either not boot or crash in the memory card screen)
559 * - Viewpoint (short pauses if the delay in the ingame music is too long)
561 * It seems that 3386880 * 5 is too much for Driver's titlescreen and it starts skipping.
562 * However, 1000000 is not enough for Worms Pinball to reliably boot.
564 if(seekTime > 3386880 * 2) seekTime = 3386880 * 2;
565 CDR_LOG("seek: %.2f %.2f\n", (float)seekTime / PSXCLK, (float)seekTime / cdReadTime);
569 static void cdrReadInterrupt(void);
571 void cdrPlaySeekReadInterrupt(void)
578 if (!cdr.Play && (cdr.StatP & STATUS_SEEK)) {
580 CDR_LOG_I("cdrom: seek stat hack\n");
581 CDRPLAYSEEKREAD_INT(0x1000, 1);
585 cdr.StatP |= STATUS_ROTATING;
586 SetPlaySeekRead(cdr.StatP, 0);
587 cdr.Result[0] = cdr.StatP;
591 Find_CurTrack(cdr.SetSectorPlay);
592 ReadTrack(cdr.SetSectorPlay);
593 cdr.TrackChanged = FALSE;
597 if (!cdr.Play) return;
599 CDR_LOG( "CDDA - %d:%d:%d\n",
600 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2] );
602 SetPlaySeekRead(cdr.StatP, STATUS_PLAY);
603 if (memcmp(cdr.SetSectorPlay, cdr.SetSectorEnd, 3) == 0) {
605 SetPlaySeekRead(cdr.StatP, 0);
606 cdr.TrackChanged = TRUE;
609 CDR_readCDDA(cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2], (u8 *)read_buf);
612 if (!cdr.Stat && (cdr.Mode & (MODE_AUTOPAUSE|MODE_REPORT)))
613 cdrPlayInterrupt_Autopause();
615 if (!cdr.Muted && !Config.Cdda) {
616 cdrAttenuate(read_buf, CD_FRAMESIZE_RAW / 4, 1);
617 SPU_playCDDAchannel(read_buf, CD_FRAMESIZE_RAW, psxRegs.cycle, cdr.FirstSector);
621 cdr.SetSectorPlay[2]++;
622 if (cdr.SetSectorPlay[2] == 75) {
623 cdr.SetSectorPlay[2] = 0;
624 cdr.SetSectorPlay[1]++;
625 if (cdr.SetSectorPlay[1] == 60) {
626 cdr.SetSectorPlay[1] = 0;
627 cdr.SetSectorPlay[0]++;
631 // update for CdlGetlocP/autopause
632 generate_subq(cdr.SetSectorPlay);
634 CDRPLAYSEEKREAD_INT(cdReadTime, 0);
637 void cdrInterrupt(void) {
638 int no_busy_error = 0;
639 int start_rotating = 0;
641 unsigned int seekTime = 0;
642 u32 second_resp_time = 0;
649 CDR_LOG_I("cdrom: cmd %02x with irqstat %x\n", cdr.CmdInProgress, cdr.Stat);
657 cdr.Result[0] = cdr.StatP;
658 cdr.Stat = Acknowledge;
660 Cmd = cdr.CmdInProgress;
661 cdr.CmdInProgress = 0;
671 if (cdr.DriveState != DRIVESTATE_LID_OPEN)
672 cdr.StatP &= ~STATUS_SHELLOPEN;
677 CDR_LOG("CDROM setloc command (%02X, %02X, %02X)\n", cdr.Param[0], cdr.Param[1], cdr.Param[2]);
679 // MM must be BCD, SS must be BCD and <0x60, FF must be BCD and <0x75
680 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))
682 CDR_LOG("Invalid/out of range seek to %02X:%02X:%02X\n", cdr.Param[0], cdr.Param[1], cdr.Param[2]);
683 error = ERROR_INVALIDARG;
688 for (i = 0; i < 3; i++)
689 set_loc[i] = btoi(cdr.Param[i]);
690 memcpy(cdr.SetSector, set_loc, 3);
691 cdr.SetSector[3] = 0;
692 cdr.SetlocPending = 1;
701 cdr.FastBackward = 0;
705 // - Pause player, hit Track 01/02/../xx (Setloc issued!!)
707 if (ParamC != 0 && cdr.Param[0] != 0) {
708 int track = btoi( cdr.Param[0] );
710 if (track <= cdr.ResultTN[1])
711 cdr.CurTrack = track;
713 CDR_LOG("PLAY track %d\n", cdr.CurTrack);
715 if (CDR_getTD((u8)cdr.CurTrack, cdr.ResultTD) != -1) {
716 for (i = 0; i < 3; i++)
717 set_loc[i] = cdr.ResultTD[2 - i];
718 seekTime = cdrSeekTime(set_loc);
719 memcpy(cdr.SetSectorPlay, set_loc, 3);
722 else if (cdr.SetlocPending) {
723 seekTime = cdrSeekTime(cdr.SetSector);
724 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
727 CDR_LOG("PLAY Resume @ %d:%d:%d\n",
728 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2]);
730 cdr.SetlocPending = 0;
733 Rayman: detect track changes
736 Twisted Metal 2: skip PREGAP + starting accurate SubQ
737 - plays tracks without retry play
739 Wild 9: skip PREGAP + starting accurate SubQ
740 - plays tracks without retry play
742 Find_CurTrack(cdr.SetSectorPlay);
743 ReadTrack(cdr.SetSectorPlay);
744 cdr.TrackChanged = FALSE;
748 CDR_play(cdr.SetSectorPlay);
750 SetPlaySeekRead(cdr.StatP, STATUS_SEEK | STATUS_ROTATING);
752 // BIOS player - set flag again
755 CDRPLAYSEEKREAD_INT(cdReadTime + seekTime, 1);
760 // TODO: error 80 if stopped
763 // GameShark CD Player: Calls 2x + Play 2x
765 cdr.FastBackward = 0;
771 // GameShark CD Player: Calls 2x + Play 2x
772 cdr.FastBackward = 1;
777 if (cdr.DriveState != DRIVESTATE_STOPPED) {
778 error = ERROR_INVALIDARG;
781 second_resp_time = cdReadTime * 125 / 2;
785 case CdlStandby + 0x100:
791 // grab time for current track
792 CDR_getTD((u8)(cdr.CurTrack), cdr.ResultTD);
794 cdr.SetSectorPlay[0] = cdr.ResultTD[2];
795 cdr.SetSectorPlay[1] = cdr.ResultTD[1];
796 cdr.SetSectorPlay[2] = cdr.ResultTD[0];
801 SetPlaySeekRead(cdr.StatP, 0);
802 cdr.StatP &= ~STATUS_ROTATING;
804 second_resp_time = 0x800;
805 if (cdr.DriveState == DRIVESTATE_STANDBY)
806 second_resp_time = cdReadTime * 30 / 2;
808 cdr.DriveState = DRIVESTATE_STOPPED;
811 case CdlStop + 0x100:
819 Gundam Battle Assault 2: much slower (*)
820 - Fixes boot, gameplay
822 Hokuto no Ken 2: slower
823 - Fixes intro + subtitles
825 InuYasha - Feudal Fairy Tale: slower
828 /* Gameblabla - Tightening the timings (as taken from Duckstation).
829 * The timings from Duckstation are based upon hardware tests.
830 * Mednafen's timing don't work for Gundam Battle Assault 2 in PAL/50hz mode,
831 * seems to be timing sensitive as it can depend on the CPU's clock speed.
833 if (!(cdr.StatP & (STATUS_PLAY | STATUS_READ)))
835 second_resp_time = 7000;
839 second_resp_time = (((cdr.Mode & MODE_SPEED) ? 2 : 1) * 1000000);
841 SetPlaySeekRead(cdr.StatP, 0);
845 case CdlPause + 0x100:
852 SetPlaySeekRead(cdr.StatP, 0);
854 cdr.Mode = 0x20; /* This fixes This is Football 2, Pooh's Party lockups */
855 second_resp_time = 4100000;
860 case CdlReset + 0x100:
873 cdr.File = cdr.Param[0];
874 cdr.Channel = cdr.Param[1];
878 CDR_LOG("cdrWrite1() Log: Setmode %x\n", cdr.Param[0]);
879 cdr.Mode = cdr.Param[0];
884 /* Gameblabla : According to mednafen, Result size should be 5 and done this way. */
886 cdr.Result[1] = cdr.Mode;
888 cdr.Result[3] = cdr.File;
889 cdr.Result[4] = cdr.Channel;
895 memcpy(cdr.Result, cdr.Transfer, 8);
900 memcpy(&cdr.Result, &cdr.subq, 8);
903 case CdlReadT: // SetSession?
905 second_resp_time = cdReadTime * 290 / 4;
909 case CdlReadT + 0x100:
915 if (CDR_getTN(cdr.ResultTN) == -1) {
916 cdr.Stat = DiskError;
917 cdr.Result[0] |= STATUS_ERROR;
919 cdr.Stat = Acknowledge;
920 cdr.Result[1] = itob(cdr.ResultTN[0]);
921 cdr.Result[2] = itob(cdr.ResultTN[1]);
926 cdr.Track = btoi(cdr.Param[0]);
928 if (CDR_getTD(cdr.Track, cdr.ResultTD) == -1) {
929 cdr.Stat = DiskError;
930 cdr.Result[0] |= STATUS_ERROR;
932 cdr.Stat = Acknowledge;
933 cdr.Result[0] = cdr.StatP;
934 cdr.Result[1] = itob(cdr.ResultTD[2]);
935 cdr.Result[2] = itob(cdr.ResultTD[1]);
936 /* According to Nocash's documentation, the function doesn't care about ff.
937 * This can be seen also in Mednafen's implementation. */
938 //cdr.Result[3] = itob(cdr.ResultTD[0]);
946 SetPlaySeekRead(cdr.StatP, STATUS_SEEK);
948 seekTime = cdrSeekTime(cdr.SetSector);
949 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
951 Crusaders of Might and Magic = 0.5x-4x
952 - fix cutscene speech start
958 - fix cutscene speech
963 CDRPLAYSEEKREAD_INT(cdReadTime + seekTime, 1);
968 switch (cdr.Param[0]) {
969 case 0x20: // System Controller ROM Version
971 memcpy(cdr.Result, Test20, 4);
975 memcpy(cdr.Result, Test22, 4);
977 case 0x23: case 0x24:
979 memcpy(cdr.Result, Test23, 4);
986 second_resp_time = 20480;
991 cdr.Result[0] = cdr.StatP;
996 // 0x10 - audio | 0x40 - disk missing | 0x80 - unlicensed
997 if (CDR_getStatus(&stat) == -1 || stat.Type == 0 || stat.Type == 0xff) {
998 cdr.Result[1] = 0xc0;
1002 cdr.Result[1] |= 0x10;
1003 if (CdromId[0] == '\0')
1004 cdr.Result[1] |= 0x80;
1006 cdr.Result[0] |= (cdr.Result[1] >> 4) & 0x08;
1008 /* This adds the string "PCSX" in Playstation bios boot screen */
1009 memcpy((char *)&cdr.Result[4], "PCSX", 4);
1010 cdr.Stat = Complete;
1016 SetPlaySeekRead(cdr.StatP, 0);
1017 // yes, it really sets STATUS_SHELLOPEN
1018 cdr.StatP |= STATUS_SHELLOPEN;
1019 cdr.DriveState = DRIVESTATE_RESCAN_CD;
1030 second_resp_time = cdReadTime * 180 / 4;
1035 case CdlReadToc + 0x100:
1036 cdr.Stat = Complete;
1042 Find_CurTrack(cdr.SetlocPending ? cdr.SetSector : cdr.SetSectorPlay);
1044 if ((cdr.Mode & MODE_CDDA) && cdr.CurTrack > 1)
1045 // Read* acts as play for cdda tracks in cdda mode
1049 if (cdr.SetlocPending) {
1050 seekTime = cdrSeekTime(cdr.SetSector);
1051 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
1052 cdr.SetlocPending = 0;
1055 cdr.FirstSector = 1;
1057 // Fighting Force 2 - update subq time immediately
1059 ReadTrack(cdr.SetSectorPlay);
1061 CDRPLAYSEEKREAD_INT(((cdr.Mode & 0x80) ? (cdReadTime) : cdReadTime * 2) + seekTime, 1);
1063 SetPlaySeekRead(cdr.StatP, STATUS_SEEK);
1068 CDR_LOG_I("Invalid command: %02x\n", Cmd);
1069 error = ERROR_INVALIDCMD;
1074 cdr.Result[0] = cdr.StatP | STATUS_ERROR;
1075 cdr.Result[1] = error;
1076 cdr.Stat = DiskError;
1080 if (cdr.DriveState == DRIVESTATE_STOPPED && start_rotating) {
1081 cdr.DriveState = DRIVESTATE_STANDBY;
1082 cdr.StatP |= STATUS_ROTATING;
1085 if (!no_busy_error) {
1086 switch (cdr.DriveState) {
1087 case DRIVESTATE_LID_OPEN:
1088 case DRIVESTATE_RESCAN_CD:
1089 case DRIVESTATE_PREPARE_CD:
1091 cdr.Result[0] = cdr.StatP | STATUS_ERROR;
1092 cdr.Result[1] = ERROR_NOTREADY;
1093 cdr.Stat = DiskError;
1098 if (second_resp_time) {
1099 cdr.CmdInProgress = Cmd | 0x100;
1100 CDR_INT(second_resp_time);
1102 else if (cdr.Cmd && cdr.Cmd != (Cmd & 0xff)) {
1103 cdr.CmdInProgress = cdr.Cmd;
1104 CDR_LOG_I("cdrom: cmd %02x came before %02x finished\n", cdr.Cmd, Cmd);
1111 #define ssat32_to_16(v) \
1112 asm("ssat %0,#16,%1" : "=r" (v) : "r" (v))
1114 #define ssat32_to_16(v) do { \
1115 if (v < -32768) v = -32768; \
1116 else if (v > 32767) v = 32767; \
1120 void cdrAttenuate(s16 *buf, int samples, int stereo)
1123 int ll = cdr.AttenuatorLeftToLeft;
1124 int lr = cdr.AttenuatorLeftToRight;
1125 int rl = cdr.AttenuatorRightToLeft;
1126 int rr = cdr.AttenuatorRightToRight;
1128 if (lr == 0 && rl == 0 && 0x78 <= ll && ll <= 0x88 && 0x78 <= rr && rr <= 0x88)
1131 if (!stereo && ll == 0x40 && lr == 0x40 && rl == 0x40 && rr == 0x40)
1135 for (i = 0; i < samples; i++) {
1138 l = (l * ll + r * rl) >> 7;
1139 r = (r * rr + l * lr) >> 7;
1147 for (i = 0; i < samples; i++) {
1149 l = l * (ll + rl) >> 7;
1150 //r = r * (rr + lr) >> 7;
1158 static void cdrReadInterruptSetResult(unsigned char result)
1161 CDR_LOG_I("cdrom: %d:%02d:%02d irq miss, cmd=%02x irqstat=%02x\n",
1162 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2],
1163 cdr.CmdInProgress, cdr.Stat);
1164 cdr.Irq1Pending = result;
1168 cdr.Result[0] = result;
1169 cdr.Stat = (result & STATUS_ERROR) ? DiskError : DataReady;
1173 static void cdrUpdateTransferBuf(const u8 *buf)
1177 memcpy(cdr.Transfer, buf, DATA_SIZE);
1178 CheckPPFCache(cdr.Transfer, cdr.Prev[0], cdr.Prev[1], cdr.Prev[2]);
1179 CDR_LOG("cdr.Transfer %x:%x:%x\n", cdr.Transfer[0], cdr.Transfer[1], cdr.Transfer[2]);
1180 if (cdr.FifoOffset < 2048 + 12)
1181 CDR_LOG("cdrom: FifoOffset(1) %d/%d\n", cdr.FifoOffset, cdr.FifoSize);
1184 static void cdrReadInterrupt(void)
1186 u8 *buf = NULL, *hdr;
1188 SetPlaySeekRead(cdr.StatP, STATUS_READ | STATUS_ROTATING);
1190 ReadTrack(cdr.SetSectorPlay);
1192 buf = CDR_getBuffer();
1197 CDR_LOG_I("cdrReadInterrupt() Log: err\n");
1198 memset(cdr.Transfer, 0, DATA_SIZE);
1199 cdrReadInterruptSetResult(cdr.StatP | STATUS_ERROR);
1203 if (!cdr.Irq1Pending)
1204 cdrUpdateTransferBuf(buf);
1206 if ((!cdr.Muted) && (cdr.Mode & MODE_STRSND) && (!Config.Xa) && (cdr.FirstSector != -1)) { // CD-XA
1208 // Firemen 2: Multi-XA files - briefings, cutscenes
1209 if( cdr.FirstSector == 1 && (cdr.Mode & MODE_SF)==0 ) {
1211 cdr.Channel = hdr[1];
1215 * Skips playing on channel 255.
1216 * Fixes missing audio in Blue's Clues : Blue's Big Musical. (Should also fix Taxi 2)
1217 * TODO : Check if this is the proper behaviour.
1219 if ((hdr[2] & 0x4) && hdr[0] == cdr.File && hdr[1] == cdr.Channel && cdr.Channel != 255) {
1220 int ret = xa_decode_sector(&cdr.Xa, buf + 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;
1231 Croc 2: $40 - only FORM1 (*)
1232 Judge Dredd: $C8 - only FORM1 (*)
1233 Sim Theme Park - no adpcm at all (zero)
1236 if (!(cdr.Mode & MODE_STRSND) || !(buf[4+2] & 0x4))
1237 cdrReadInterruptSetResult(cdr.StatP);
1239 cdr.SetSectorPlay[2]++;
1240 if (cdr.SetSectorPlay[2] == 75) {
1241 cdr.SetSectorPlay[2] = 0;
1242 cdr.SetSectorPlay[1]++;
1243 if (cdr.SetSectorPlay[1] == 60) {
1244 cdr.SetSectorPlay[1] = 0;
1245 cdr.SetSectorPlay[0]++;
1249 if (!cdr.Irq1Pending) {
1250 // update for CdlGetlocP
1251 ReadTrack(cdr.SetSectorPlay);
1254 CDRPLAYSEEKREAD_INT((cdr.Mode & MODE_SPEED) ? (cdReadTime / 2) : cdReadTime, 0);
1257 static void doMissedIrqs(void)
1259 if (cdr.Irq1Pending)
1261 // hand out the "newest" sector, according to nocash
1262 cdrUpdateTransferBuf(CDR_getBuffer());
1263 CDR_LOG_I("cdrom: %x:%02x:%02x loaded on ack\n",
1264 cdr.Transfer[0], cdr.Transfer[1], cdr.Transfer[2]);
1266 cdr.Result[0] = cdr.Irq1Pending;
1267 cdr.Stat = (cdr.Irq1Pending & STATUS_ERROR) ? DiskError : DataReady;
1268 cdr.Irq1Pending = 0;
1272 if (!(psxRegs.interrupt & (1 << PSXINT_CDR)) && cdr.CmdInProgress)
1282 bit 5 - 1 result ready
1284 bit 7 - 1 command being processed
1287 unsigned char cdrRead0(void) {
1288 if (cdr.ResultReady)
1293 cdr.Ctrl |= 0x40; // data fifo not empty
1295 // What means the 0x10 and the 0x08 bits? I only saw it used by the bios
1298 CDR_LOG_IO("cdr r0.sta: %02x\n", cdr.Ctrl);
1300 return psxHu8(0x1800) = cdr.Ctrl;
1303 void cdrWrite0(unsigned char rt) {
1304 CDR_LOG_IO("cdr w0.idx: %02x\n", rt);
1306 cdr.Ctrl = (rt & 3) | (cdr.Ctrl & ~3);
1309 unsigned char cdrRead1(void) {
1310 if ((cdr.ResultP & 0xf) < cdr.ResultC)
1311 psxHu8(0x1801) = cdr.Result[cdr.ResultP & 0xf];
1315 if (cdr.ResultP == cdr.ResultC)
1316 cdr.ResultReady = 0;
1318 CDR_LOG_IO("cdr r1.rsp: %02x #%u\n", psxHu8(0x1801), cdr.ResultP - 1);
1320 return psxHu8(0x1801);
1323 void cdrWrite1(unsigned char rt) {
1324 const char *rnames[] = { "cmd", "smd", "smc", "arr" }; (void)rnames;
1325 CDR_LOG_IO("cdr w1.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1327 switch (cdr.Ctrl & 3) {
1331 cdr.AttenuatorRightToRightT = rt;
1337 #ifdef CDR_LOG_CMD_IRQ
1338 SysPrintf("CD1 write: %x (%s)", rt, CmdName[rt]);
1341 SysPrintf(" Param[%d] = {", cdr.ParamC);
1342 for (i = 0; i < cdr.ParamC; i++)
1343 SysPrintf(" %x,", cdr.Param[i]);
1350 cdr.ResultReady = 0;
1353 if (!cdr.CmdInProgress) {
1354 cdr.CmdInProgress = rt;
1355 // should be something like 12k + controller delays
1359 CDR_LOG_I("cdr: cmd while busy: %02x, prev %02x, busy %02x\n",
1360 rt, cdr.Cmd, cdr.CmdInProgress);
1361 if (cdr.CmdInProgress < 0x100) // no pending 2nd response
1362 cdr.CmdInProgress = rt;
1368 unsigned char cdrRead2(void) {
1369 unsigned char ret = 0;
1371 if (cdr.FifoOffset < cdr.FifoSize)
1372 ret = cdr.Transfer[cdr.FifoOffset++];
1374 CDR_LOG_I("cdrom: read empty fifo (%d)\n", cdr.FifoSize);
1376 CDR_LOG_IO("cdr r2.dat: %02x\n", ret);
1380 void cdrWrite2(unsigned char rt) {
1381 const char *rnames[] = { "prm", "ien", "all", "arl" }; (void)rnames;
1382 CDR_LOG_IO("cdr w2.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1384 switch (cdr.Ctrl & 3) {
1386 if (cdr.ParamC < 8) // FIXME: size and wrapping
1387 cdr.Param[cdr.ParamC++] = rt;
1394 cdr.AttenuatorLeftToLeftT = rt;
1397 cdr.AttenuatorRightToLeftT = rt;
1402 unsigned char cdrRead3(void) {
1404 psxHu8(0x1803) = cdr.Stat | 0xE0;
1406 psxHu8(0x1803) = cdr.Reg2 | 0xE0;
1408 CDR_LOG_IO("cdr r3.%s: %02x\n", (cdr.Ctrl & 1) ? "ifl" : "ien", psxHu8(0x1803));
1409 return psxHu8(0x1803);
1412 void cdrWrite3(unsigned char rt) {
1413 const char *rnames[] = { "req", "ifl", "alr", "ava" }; (void)rnames;
1414 CDR_LOG_IO("cdr w3.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1416 switch (cdr.Ctrl & 3) {
1420 #ifdef CDR_LOG_CMD_IRQ
1422 SysPrintf("ack %02x\n", cdr.Stat & rt);
1431 cdr.AttenuatorLeftToRightT = rt;
1435 memcpy(&cdr.AttenuatorLeftToLeft, &cdr.AttenuatorLeftToLeftT, 4);
1436 CDR_LOG("CD-XA Volume: %02x %02x | %02x %02x\n",
1437 cdr.AttenuatorLeftToLeft, cdr.AttenuatorLeftToRight,
1438 cdr.AttenuatorRightToLeft, cdr.AttenuatorRightToRight);
1444 if ((rt & 0x80) && cdr.FifoOffset < cdr.FifoSize) {
1445 CDR_LOG("cdrom: FifoOffset(2) %d/%d\n", cdr.FifoOffset, cdr.FifoSize);
1447 else if (rt & 0x80) {
1448 switch (cdr.Mode & 0x30) {
1449 case MODE_SIZE_2328:
1451 cdr.FifoOffset = 12;
1452 cdr.FifoSize = 2048 + 12;
1455 case MODE_SIZE_2340:
1458 cdr.FifoSize = 2340;
1462 else if (!(rt & 0xc0))
1463 cdr.FifoOffset = DATA_SIZE; // fifo empty
1466 void psxDma3(u32 madr, u32 bcr, u32 chcr) {
1471 CDR_LOG("psxDma3() Log: *** DMA 3 *** %x addr = %x size = %x\n", chcr, madr, bcr);
1473 switch (chcr & 0x71000000) {
1475 ptr = (u8 *)PSXM(madr);
1476 if (ptr == INVALID_PTR) {
1477 CDR_LOG_I("psxDma3() Log: *** DMA 3 *** NULL Pointer!\n");
1481 cdsize = (((bcr - 1) & 0xffff) + 1) * 4;
1484 GS CDX: Enhancement CD crash
1487 - Spams DMA3 and gets buffer overrun
1489 size = DATA_SIZE - cdr.FifoOffset;
1494 memcpy(ptr, cdr.Transfer + cdr.FifoOffset, size);
1495 cdr.FifoOffset += size;
1496 psxCpu->Clear(madr, size / 4);
1499 CDR_LOG_I("cdrom: dma3 %d/%d\n", size, cdsize);
1501 CDRDMA_INT((cdsize/4) * 24);
1503 HW_DMA3_CHCR &= SWAPu32(~0x10000000);
1505 HW_DMA3_MADR = SWAPu32(madr + cdsize);
1506 HW_DMA3_BCR &= SWAPu32(0xffff0000);
1510 psxRegs.cycle += (cdsize/4) * 24 - 20;
1515 CDR_LOG_I("psxDma3() Log: Unknown cddma %x\n", chcr);
1519 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1523 void cdrDmaInterrupt(void)
1525 if (HW_DMA3_CHCR & SWAP32(0x01000000))
1527 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1532 static void getCdInfo(void)
1536 CDR_getTN(cdr.ResultTN);
1537 CDR_getTD(0, cdr.SetSectorEnd);
1538 tmp = cdr.SetSectorEnd[0];
1539 cdr.SetSectorEnd[0] = cdr.SetSectorEnd[2];
1540 cdr.SetSectorEnd[2] = tmp;
1544 memset(&cdr, 0, sizeof(cdr));
1550 cdr.DriveState = DRIVESTATE_STANDBY;
1551 cdr.StatP = STATUS_ROTATING;
1552 cdr.FifoOffset = DATA_SIZE; // fifo empty
1554 // BIOS player - default values
1555 cdr.AttenuatorLeftToLeft = 0x80;
1556 cdr.AttenuatorLeftToRight = 0x00;
1557 cdr.AttenuatorRightToLeft = 0x00;
1558 cdr.AttenuatorRightToRight = 0x80;
1563 int cdrFreeze(void *f, int Mode) {
1567 if (Mode == 0 && !Config.Cdda)
1570 cdr.freeze_ver = 0x63647202;
1571 gzfreeze(&cdr, sizeof(cdr));
1574 cdr.ParamP = cdr.ParamC;
1575 tmp = cdr.FifoOffset;
1578 gzfreeze(&tmp, sizeof(tmp));
1583 cdr.FifoOffset = tmp;
1584 cdr.FifoSize = (cdr.Mode & 0x20) ? 2340 : 2048 + 12;
1586 // read right sub data
1587 tmpp[0] = btoi(cdr.Prev[0]);
1588 tmpp[1] = btoi(cdr.Prev[1]);
1589 tmpp[2] = btoi(cdr.Prev[2]);
1594 if (cdr.freeze_ver < 0x63647202)
1595 memcpy(cdr.SetSectorPlay, cdr.SetSector, 3);
1597 Find_CurTrack(cdr.SetSectorPlay);
1599 CDR_play(cdr.SetSectorPlay);
1600 if (psxRegs.interrupt & (1 << PSXINT_CDRPLAY_OLD))
1601 CDRPLAYSEEKREAD_INT((cdr.Mode & 0x80) ? (cdReadTime / 2) : cdReadTime, 1);
1604 if ((cdr.freeze_ver & 0xffffff00) != 0x63647200) {
1605 // old versions did not latch Reg2, have to fixup..
1606 if (cdr.Reg2 == 0) {
1607 SysPrintf("cdrom: fixing up old savestate\n");
1610 // also did not save Attenuator..
1611 if ((cdr.AttenuatorLeftToLeft | cdr.AttenuatorLeftToRight
1612 | cdr.AttenuatorRightToLeft | cdr.AttenuatorRightToRight) == 0)
1614 cdr.AttenuatorLeftToLeft = cdr.AttenuatorRightToRight = 0x80;
1622 void LidInterrupt(void) {
1624 cdrLidSeekInterrupt();