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 // cdrPlayReadInterrupt
247 #define CDRPLAYREAD_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("%u cdrom: CDR IRQ=%d cmd %02x stat %02x: ",
299 psxRegs.cycle, !!(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 CDR_LOG_I("%u %s cdr.DriveState=%d\n", psxRegs.cycle, __func__, cdr.DriveState);
313 switch (cdr.DriveState) {
315 case DRIVESTATE_STANDBY:
318 SetPlaySeekRead(cdr.StatP, 0);
320 if (CDR_getStatus(&stat) == -1)
323 if (stat.Status & STATUS_SHELLOPEN)
325 cdr.DriveState = DRIVESTATE_LID_OPEN;
330 case DRIVESTATE_LID_OPEN:
331 if (CDR_getStatus(&stat) == -1)
332 stat.Status &= ~STATUS_SHELLOPEN;
335 if (!(cdr.StatP & STATUS_SHELLOPEN)) {
336 cdr.StatP |= STATUS_SHELLOPEN;
338 // could generate error irq here, but real hardware
339 // only sometimes does that
340 // (not done when lots of commands are sent?)
342 CDRLID_INT(cdReadTime * 30);
345 else if (cdr.StatP & STATUS_ROTATING) {
346 cdr.StatP &= ~STATUS_ROTATING;
348 else if (!(stat.Status & STATUS_SHELLOPEN)) {
352 // cdr.StatP STATUS_SHELLOPEN is "sticky"
353 // and is only cleared by CdlNop
355 cdr.DriveState = DRIVESTATE_RESCAN_CD;
356 CDRLID_INT(cdReadTime * 105);
361 CDRLID_INT(cdReadTime * 3);
364 case DRIVESTATE_RESCAN_CD:
365 cdr.StatP |= STATUS_ROTATING;
366 cdr.DriveState = DRIVESTATE_PREPARE_CD;
368 // this is very long on real hardware, over 6 seconds
369 // make it a bit faster here...
370 CDRLID_INT(cdReadTime * 150);
373 case DRIVESTATE_PREPARE_CD:
374 if (cdr.StatP & STATUS_SEEK) {
375 SetPlaySeekRead(cdr.StatP, 0);
376 cdr.DriveState = DRIVESTATE_STANDBY;
379 SetPlaySeekRead(cdr.StatP, STATUS_SEEK);
380 CDRLID_INT(cdReadTime * 26);
386 static void Find_CurTrack(const u8 *time)
390 current = msf2sec(time);
392 for (cdr.CurTrack = 1; cdr.CurTrack < cdr.ResultTN[1]; cdr.CurTrack++) {
393 CDR_getTD(cdr.CurTrack + 1, cdr.ResultTD);
394 sect = fsm2sec(cdr.ResultTD);
395 if (sect - current >= 150)
400 static void generate_subq(const u8 *time)
402 unsigned char start[3], next[3];
403 unsigned int this_s, start_s, next_s, pregap;
406 CDR_getTD(cdr.CurTrack, start);
407 if (cdr.CurTrack + 1 <= cdr.ResultTN[1]) {
409 CDR_getTD(cdr.CurTrack + 1, next);
412 // last track - cd size
414 next[0] = cdr.SetSectorEnd[2];
415 next[1] = cdr.SetSectorEnd[1];
416 next[2] = cdr.SetSectorEnd[0];
419 this_s = msf2sec(time);
420 start_s = fsm2sec(start);
421 next_s = fsm2sec(next);
423 cdr.TrackChanged = FALSE;
425 if (next_s - this_s < pregap) {
426 cdr.TrackChanged = TRUE;
433 relative_s = this_s - start_s;
434 if (relative_s < 0) {
436 relative_s = -relative_s;
438 sec2msf(relative_s, cdr.subq.Relative);
440 cdr.subq.Track = itob(cdr.CurTrack);
441 cdr.subq.Relative[0] = itob(cdr.subq.Relative[0]);
442 cdr.subq.Relative[1] = itob(cdr.subq.Relative[1]);
443 cdr.subq.Relative[2] = itob(cdr.subq.Relative[2]);
444 cdr.subq.Absolute[0] = itob(time[0]);
445 cdr.subq.Absolute[1] = itob(time[1]);
446 cdr.subq.Absolute[2] = itob(time[2]);
449 static void ReadTrack(const u8 *time) {
450 unsigned char tmp[3];
454 tmp[0] = itob(time[0]);
455 tmp[1] = itob(time[1]);
456 tmp[2] = itob(time[2]);
458 if (memcmp(cdr.Prev, tmp, 3) == 0)
461 CDR_LOG("ReadTrack *** %02x:%02x:%02x\n", tmp[0], tmp[1], tmp[2]);
463 cdr.NoErr = CDR_readTrack(tmp);
464 memcpy(cdr.Prev, tmp, 3);
469 subq = (struct SubQ *)CDR_getBufferSub();
470 if (subq != NULL && cdr.CurTrack == 1) {
471 crc = calcCrc((u8 *)subq + 12, 10);
472 if (crc == (((u16)subq->CRC[0] << 8) | subq->CRC[1])) {
473 cdr.subq.Track = subq->TrackNumber;
474 cdr.subq.Index = subq->IndexNumber;
475 memcpy(cdr.subq.Relative, subq->TrackRelativeAddress, 3);
476 memcpy(cdr.subq.Absolute, subq->AbsoluteAddress, 3);
479 CDR_LOG_I("subq bad crc @%02x:%02x:%02x\n",
480 tmp[0], tmp[1], tmp[2]);
487 CDR_LOG(" -> %02x,%02x %02x:%02x:%02x %02x:%02x:%02x\n",
488 cdr.subq.Track, cdr.subq.Index,
489 cdr.subq.Relative[0], cdr.subq.Relative[1], cdr.subq.Relative[2],
490 cdr.subq.Absolute[0], cdr.subq.Absolute[1], cdr.subq.Absolute[2]);
493 static void cdrPlayInterrupt_Autopause()
496 boolean abs_lev_chselect;
499 if ((cdr.Mode & MODE_AUTOPAUSE) && cdr.TrackChanged) {
500 CDR_LOG( "CDDA STOP\n" );
502 // Magic the Gathering
503 // - looping territory cdda
506 //cdr.ResultReady = 1;
507 //cdr.Stat = DataReady;
509 setIrq(0x1000); // 0x1000 just for logging purposes
512 SetPlaySeekRead(cdr.StatP, 0);
514 else if (((cdr.Mode & MODE_REPORT) || cdr.FastForward || cdr.FastBackward)) {
515 cdr.Result[0] = cdr.StatP;
516 cdr.Result[1] = cdr.subq.Track;
517 cdr.Result[2] = cdr.subq.Index;
519 abs_lev_chselect = cdr.subq.Absolute[1] & 0x01;
521 /* 8 is a hack. For accuracy, it should be 588. */
522 for (i = 0; i < 8; i++)
524 abs_lev_max = MAX_VALUE(abs_lev_max, abs(read_buf[i * 2 + abs_lev_chselect]));
526 abs_lev_max = MIN_VALUE(abs_lev_max, 32767);
527 abs_lev_max |= abs_lev_chselect << 15;
529 if (cdr.subq.Absolute[2] & 0x10) {
530 cdr.Result[3] = cdr.subq.Relative[0];
531 cdr.Result[4] = cdr.subq.Relative[1] | 0x80;
532 cdr.Result[5] = cdr.subq.Relative[2];
535 cdr.Result[3] = cdr.subq.Absolute[0];
536 cdr.Result[4] = cdr.subq.Absolute[1];
537 cdr.Result[5] = cdr.subq.Absolute[2];
540 cdr.Result[6] = abs_lev_max >> 0;
541 cdr.Result[7] = abs_lev_max >> 8;
543 // Rayman: Logo freeze (resultready + dataready)
545 cdr.Stat = DataReady;
552 static int cdrSeekTime(unsigned char *target)
554 int diff = msf2sec(cdr.SetSectorPlay) - msf2sec(target);
555 int seekTime = abs(diff) * (cdReadTime / 200);
558 * It was originally set to 1000000 for Driver, however it is not high enough for Worms Pinball
559 * and was unreliable for that game.
560 * I also tested it against Mednafen and Driver's titlescreen music starts 25 frames later, not immediatly.
562 * Obviously, this isn't perfect but right now, it should be a bit better.
563 * Games to test this against if you change that setting :
564 * - Driver (titlescreen music delay and retry mission)
565 * - Worms Pinball (Will either not boot or crash in the memory card screen)
566 * - Viewpoint (short pauses if the delay in the ingame music is too long)
568 * It seems that 3386880 * 5 is too much for Driver's titlescreen and it starts skipping.
569 * However, 1000000 is not enough for Worms Pinball to reliably boot.
571 if(seekTime > 3386880 * 2) seekTime = 3386880 * 2;
572 CDR_LOG("seek: %.2f %.2f\n", (float)seekTime / PSXCLK, (float)seekTime / cdReadTime);
576 static void cdrUpdateTransferBuf(const u8 *buf);
577 static void cdrReadInterrupt(void);
578 static void cdrPrepCdda(s16 *buf, int samples);
579 static void cdrAttenuate(s16 *buf, int samples, int stereo);
581 void cdrPlayReadInterrupt(void)
588 if (!cdr.Play) return;
590 CDR_LOG( "CDDA - %d:%d:%d\n",
591 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2] );
593 SetPlaySeekRead(cdr.StatP, STATUS_PLAY);
594 if (memcmp(cdr.SetSectorPlay, cdr.SetSectorEnd, 3) == 0) {
596 SetPlaySeekRead(cdr.StatP, 0);
597 cdr.TrackChanged = TRUE;
600 CDR_readCDDA(cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2], (u8 *)read_buf);
603 if (!cdr.Stat && (cdr.Mode & (MODE_AUTOPAUSE|MODE_REPORT)))
604 cdrPlayInterrupt_Autopause();
606 if (!cdr.Muted && !Config.Cdda) {
607 cdrPrepCdda(read_buf, CD_FRAMESIZE_RAW / 4);
608 cdrAttenuate(read_buf, CD_FRAMESIZE_RAW / 4, 1);
609 SPU_playCDDAchannel(read_buf, CD_FRAMESIZE_RAW, psxRegs.cycle, cdr.FirstSector);
613 cdr.SetSectorPlay[2]++;
614 if (cdr.SetSectorPlay[2] == 75) {
615 cdr.SetSectorPlay[2] = 0;
616 cdr.SetSectorPlay[1]++;
617 if (cdr.SetSectorPlay[1] == 60) {
618 cdr.SetSectorPlay[1] = 0;
619 cdr.SetSectorPlay[0]++;
623 // update for CdlGetlocP/autopause
624 generate_subq(cdr.SetSectorPlay);
626 CDRPLAYREAD_INT(cdReadTime, 0);
629 #define CMD_PART2 0x100
630 #define CMD_WHILE_NOT_READY 0x200
632 void cdrInterrupt(void) {
633 int start_rotating = 0;
635 unsigned int seekTime = 0;
636 u32 second_resp_time = 0;
644 CDR_LOG_I("%u cdrom: cmd %02x with irqstat %x\n",
645 psxRegs.cycle, cdr.CmdInProgress, cdr.Stat);
648 if (cdr.Irq1Pending) {
649 // hand out the "newest" sector, according to nocash
650 cdrUpdateTransferBuf(CDR_getBuffer());
651 CDR_LOG_I("cdrom: %x:%02x:%02x loaded on ack\n",
652 cdr.Transfer[0], cdr.Transfer[1], cdr.Transfer[2]);
654 cdr.Result[0] = cdr.Irq1Pending;
655 cdr.Stat = (cdr.Irq1Pending & STATUS_ERROR) ? DiskError : DataReady;
663 cdr.Result[0] = cdr.StatP;
664 cdr.Stat = Acknowledge;
666 Cmd = cdr.CmdInProgress;
667 cdr.CmdInProgress = 0;
676 switch (cdr.DriveState) {
677 case DRIVESTATE_PREPARE_CD:
679 // Syphon filter 2 expects commands to work shortly after it sees
680 // STATUS_ROTATING, so give up trying to emulate the startup seq
681 cdr.DriveState = DRIVESTATE_STANDBY;
682 cdr.StatP &= ~STATUS_SEEK;
683 psxRegs.interrupt &= ~(1 << PSXINT_CDRLID);
687 case DRIVESTATE_LID_OPEN:
688 case DRIVESTATE_RESCAN_CD:
689 // no disk or busy with the initial scan, allowed cmds are limited
690 not_ready = CMD_WHILE_NOT_READY;
694 switch (Cmd | not_ready) {
696 case CdlNop + CMD_WHILE_NOT_READY:
697 if (cdr.DriveState != DRIVESTATE_LID_OPEN)
698 cdr.StatP &= ~STATUS_SHELLOPEN;
702 case CdlSetloc + CMD_WHILE_NOT_READY:
703 CDR_LOG("CDROM setloc command (%02X, %02X, %02X)\n", cdr.Param[0], cdr.Param[1], cdr.Param[2]);
705 // MM must be BCD, SS must be BCD and <0x60, FF must be BCD and <0x75
706 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))
708 CDR_LOG("Invalid/out of range seek to %02X:%02X:%02X\n", cdr.Param[0], cdr.Param[1], cdr.Param[2]);
709 error = ERROR_INVALIDARG;
714 for (i = 0; i < 3; i++)
715 set_loc[i] = btoi(cdr.Param[i]);
716 memcpy(cdr.SetSector, set_loc, 3);
717 cdr.SetSector[3] = 0;
718 cdr.SetlocPending = 1;
727 cdr.FastBackward = 0;
731 // - Pause player, hit Track 01/02/../xx (Setloc issued!!)
733 if (ParamC != 0 && cdr.Param[0] != 0) {
734 int track = btoi( cdr.Param[0] );
736 if (track <= cdr.ResultTN[1])
737 cdr.CurTrack = track;
739 CDR_LOG("PLAY track %d\n", cdr.CurTrack);
741 if (CDR_getTD((u8)cdr.CurTrack, cdr.ResultTD) != -1) {
742 for (i = 0; i < 3; i++)
743 set_loc[i] = cdr.ResultTD[2 - i];
744 seekTime = cdrSeekTime(set_loc);
745 memcpy(cdr.SetSectorPlay, set_loc, 3);
748 else if (cdr.SetlocPending) {
749 seekTime = cdrSeekTime(cdr.SetSector);
750 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
753 CDR_LOG("PLAY Resume @ %d:%d:%d\n",
754 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2]);
756 cdr.SetlocPending = 0;
759 Rayman: detect track changes
762 Twisted Metal 2: skip PREGAP + starting accurate SubQ
763 - plays tracks without retry play
765 Wild 9: skip PREGAP + starting accurate SubQ
766 - plays tracks without retry play
768 Find_CurTrack(cdr.SetSectorPlay);
769 ReadTrack(cdr.SetSectorPlay);
770 cdr.TrackChanged = FALSE;
774 CDR_play(cdr.SetSectorPlay);
776 SetPlaySeekRead(cdr.StatP, STATUS_SEEK | STATUS_ROTATING);
778 // BIOS player - set flag again
781 CDRPLAYREAD_INT(cdReadTime + seekTime, 1);
786 // TODO: error 80 if stopped
789 // GameShark CD Player: Calls 2x + Play 2x
791 cdr.FastBackward = 0;
797 // GameShark CD Player: Calls 2x + Play 2x
798 cdr.FastBackward = 1;
803 if (cdr.DriveState != DRIVESTATE_STOPPED) {
804 error = ERROR_INVALIDARG;
807 second_resp_time = cdReadTime * 125 / 2;
811 case CdlStandby + CMD_PART2:
817 // grab time for current track
818 CDR_getTD((u8)(cdr.CurTrack), cdr.ResultTD);
820 cdr.SetSectorPlay[0] = cdr.ResultTD[2];
821 cdr.SetSectorPlay[1] = cdr.ResultTD[1];
822 cdr.SetSectorPlay[2] = cdr.ResultTD[0];
827 SetPlaySeekRead(cdr.StatP, 0);
828 cdr.StatP &= ~STATUS_ROTATING;
830 second_resp_time = 0x800;
831 if (cdr.DriveState == DRIVESTATE_STANDBY)
832 second_resp_time = cdReadTime * 30 / 2;
834 cdr.DriveState = DRIVESTATE_STOPPED;
837 case CdlStop + CMD_PART2:
845 Gundam Battle Assault 2: much slower (*)
846 - Fixes boot, gameplay
848 Hokuto no Ken 2: slower
849 - Fixes intro + subtitles
851 InuYasha - Feudal Fairy Tale: slower
854 /* Gameblabla - Tightening the timings (as taken from Duckstation).
855 * The timings from Duckstation are based upon hardware tests.
856 * Mednafen's timing don't work for Gundam Battle Assault 2 in PAL/50hz mode,
857 * seems to be timing sensitive as it can depend on the CPU's clock speed.
859 if (!(cdr.StatP & (STATUS_PLAY | STATUS_READ)))
861 second_resp_time = 7000;
865 second_resp_time = (((cdr.Mode & MODE_SPEED) ? 2 : 1) * 1000000);
867 SetPlaySeekRead(cdr.StatP, 0);
870 case CdlPause + CMD_PART2:
875 case CdlReset + CMD_WHILE_NOT_READY:
878 SetPlaySeekRead(cdr.StatP, 0);
880 cdr.Mode = 0x20; /* This fixes This is Football 2, Pooh's Party lockups */
881 second_resp_time = not_ready ? 70000 : 4100000;
885 case CdlReset + CMD_PART2:
886 case CdlReset + CMD_PART2 + CMD_WHILE_NOT_READY:
899 cdr.File = cdr.Param[0];
900 cdr.Channel = cdr.Param[1];
904 case CdlSetmode + CMD_WHILE_NOT_READY:
905 CDR_LOG("cdrWrite1() Log: Setmode %x\n", cdr.Param[0]);
906 cdr.Mode = cdr.Param[0];
910 case CdlGetparam + CMD_WHILE_NOT_READY:
911 /* Gameblabla : According to mednafen, Result size should be 5 and done this way. */
913 cdr.Result[1] = cdr.Mode;
915 cdr.Result[3] = cdr.File;
916 cdr.Result[4] = cdr.Channel;
921 memcpy(cdr.Result, cdr.Transfer, 8);
926 memcpy(&cdr.Result, &cdr.subq, 8);
929 case CdlReadT: // SetSession?
931 second_resp_time = cdReadTime * 290 / 4;
935 case CdlReadT + CMD_PART2:
941 if (CDR_getTN(cdr.ResultTN) == -1) {
942 cdr.Stat = DiskError;
943 cdr.Result[0] |= STATUS_ERROR;
945 cdr.Stat = Acknowledge;
946 cdr.Result[1] = itob(cdr.ResultTN[0]);
947 cdr.Result[2] = itob(cdr.ResultTN[1]);
952 cdr.Track = btoi(cdr.Param[0]);
954 if (CDR_getTD(cdr.Track, cdr.ResultTD) == -1) {
955 cdr.Stat = DiskError;
956 cdr.Result[0] |= STATUS_ERROR;
958 cdr.Stat = Acknowledge;
959 cdr.Result[0] = cdr.StatP;
960 cdr.Result[1] = itob(cdr.ResultTD[2]);
961 cdr.Result[2] = itob(cdr.ResultTD[1]);
962 /* According to Nocash's documentation, the function doesn't care about ff.
963 * This can be seen also in Mednafen's implementation. */
964 //cdr.Result[3] = itob(cdr.ResultTD[0]);
972 SetPlaySeekRead(cdr.StatP, STATUS_SEEK | STATUS_ROTATING);
974 seekTime = cdrSeekTime(cdr.SetSector);
975 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
977 Crusaders of Might and Magic = 0.5x-4x
978 - fix cutscene speech start
984 - fix cutscene speech
989 second_resp_time = cdReadTime + seekTime;
993 case CdlSeekL + CMD_PART2:
994 case CdlSeekP + CMD_PART2:
995 SetPlaySeekRead(cdr.StatP, 0);
998 Find_CurTrack(cdr.SetSectorPlay);
999 ReadTrack(cdr.SetSectorPlay);
1000 cdr.TrackChanged = FALSE;
1004 case CdlTest + CMD_WHILE_NOT_READY:
1005 switch (cdr.Param[0]) {
1006 case 0x20: // System Controller ROM Version
1008 memcpy(cdr.Result, Test20, 4);
1012 memcpy(cdr.Result, Test22, 4);
1014 case 0x23: case 0x24:
1016 memcpy(cdr.Result, Test23, 4);
1022 second_resp_time = 20480;
1025 case CdlID + CMD_PART2:
1027 cdr.Result[0] = cdr.StatP;
1032 // 0x10 - audio | 0x40 - disk missing | 0x80 - unlicensed
1033 if (CDR_getStatus(&stat) == -1 || stat.Type == 0 || stat.Type == 0xff) {
1034 cdr.Result[1] = 0xc0;
1038 cdr.Result[1] |= 0x10;
1039 if (CdromId[0] == '\0')
1040 cdr.Result[1] |= 0x80;
1042 cdr.Result[0] |= (cdr.Result[1] >> 4) & 0x08;
1044 /* This adds the string "PCSX" in Playstation bios boot screen */
1045 memcpy((char *)&cdr.Result[4], "PCSX", 4);
1046 cdr.Stat = Complete;
1050 case CdlInit + CMD_WHILE_NOT_READY:
1053 SetPlaySeekRead(cdr.StatP, 0);
1054 // yes, it really sets STATUS_SHELLOPEN
1055 cdr.StatP |= STATUS_SHELLOPEN;
1056 cdr.DriveState = DRIVESTATE_RESCAN_CD;
1062 case CdlGetQ + CMD_WHILE_NOT_READY:
1066 case CdlReadToc + CMD_WHILE_NOT_READY:
1067 second_resp_time = cdReadTime * 180 / 4;
1071 case CdlReadToc + CMD_PART2:
1072 case CdlReadToc + CMD_PART2 + CMD_WHILE_NOT_READY:
1073 cdr.Stat = Complete;
1078 Find_CurTrack(cdr.SetlocPending ? cdr.SetSector : cdr.SetSectorPlay);
1080 if ((cdr.Mode & MODE_CDDA) && cdr.CurTrack > 1)
1081 // Read* acts as play for cdda tracks in cdda mode
1085 if (cdr.SetlocPending) {
1086 seekTime = cdrSeekTime(cdr.SetSector);
1087 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
1088 cdr.SetlocPending = 0;
1091 cdr.FirstSector = 1;
1093 // Fighting Force 2 - update subq time immediately
1095 ReadTrack(cdr.SetSectorPlay);
1097 CDRPLAYREAD_INT(((cdr.Mode & 0x80) ? (cdReadTime) : cdReadTime * 2) + seekTime, 1);
1099 SetPlaySeekRead(cdr.StatP, STATUS_SEEK);
1104 CDR_LOG_I("Invalid command: %02x%s\n",
1105 Cmd, not_ready ? " (not_ready)" : "");
1106 error = ERROR_INVALIDCMD;
1111 cdr.Result[0] = cdr.StatP | STATUS_ERROR;
1112 cdr.Result[1] = not_ready ? ERROR_NOTREADY : error;
1113 cdr.Stat = DiskError;
1117 if (cdr.DriveState == DRIVESTATE_STOPPED && start_rotating) {
1118 printf("cdr.DriveState %d->%d\n", cdr.DriveState, DRIVESTATE_STANDBY);
1119 cdr.DriveState = DRIVESTATE_STANDBY;
1120 cdr.StatP |= STATUS_ROTATING;
1123 if (second_resp_time) {
1124 cdr.CmdInProgress = Cmd | 0x100;
1125 CDR_INT(second_resp_time);
1127 else if (cdr.Cmd && cdr.Cmd != (Cmd & 0xff)) {
1128 cdr.CmdInProgress = cdr.Cmd;
1129 CDR_LOG_I("%u cdrom: cmd %02x came before %02x finished\n",
1130 psxRegs.cycle, cdr.Cmd, Cmd);
1137 #define ssat32_to_16(v) \
1138 asm("ssat %0,#16,%1" : "=r" (v) : "r" (v))
1140 #define ssat32_to_16(v) do { \
1141 if (v < -32768) v = -32768; \
1142 else if (v > 32767) v = 32767; \
1146 static void cdrPrepCdda(s16 *buf, int samples)
1148 #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
1150 for (i = 0; i < samples; i++) {
1151 buf[i * 2 + 0] = SWAP16(buf[i * 2 + 0]);
1152 buf[i * 2 + 1] = SWAP16(buf[i * 2 + 1]);
1157 static void cdrAttenuate(s16 *buf, int samples, int stereo)
1160 int ll = cdr.AttenuatorLeftToLeft;
1161 int lr = cdr.AttenuatorLeftToRight;
1162 int rl = cdr.AttenuatorRightToLeft;
1163 int rr = cdr.AttenuatorRightToRight;
1165 if (lr == 0 && rl == 0 && 0x78 <= ll && ll <= 0x88 && 0x78 <= rr && rr <= 0x88)
1168 if (!stereo && ll == 0x40 && lr == 0x40 && rl == 0x40 && rr == 0x40)
1172 for (i = 0; i < samples; i++) {
1175 l = (l * ll + r * rl) >> 7;
1176 r = (r * rr + l * lr) >> 7;
1184 for (i = 0; i < samples; i++) {
1186 l = l * (ll + rl) >> 7;
1187 //r = r * (rr + lr) >> 7;
1195 static void cdrReadInterruptSetResult(unsigned char result)
1198 CDR_LOG_I("cdrom: %d:%02d:%02d irq miss, cmd=%02x irqstat=%02x\n",
1199 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2],
1200 cdr.CmdInProgress, cdr.Stat);
1201 cdr.Irq1Pending = result;
1205 cdr.Result[0] = result;
1206 cdr.Stat = (result & STATUS_ERROR) ? DiskError : DataReady;
1210 static void cdrUpdateTransferBuf(const u8 *buf)
1214 memcpy(cdr.Transfer, buf, DATA_SIZE);
1215 CheckPPFCache(cdr.Transfer, cdr.Prev[0], cdr.Prev[1], cdr.Prev[2]);
1216 CDR_LOG("cdr.Transfer %x:%x:%x\n", cdr.Transfer[0], cdr.Transfer[1], cdr.Transfer[2]);
1217 if (cdr.FifoOffset < 2048 + 12)
1218 CDR_LOG("cdrom: FifoOffset(1) %d/%d\n", cdr.FifoOffset, cdr.FifoSize);
1221 static void cdrReadInterrupt(void)
1223 u8 *buf = NULL, *hdr;
1225 SetPlaySeekRead(cdr.StatP, STATUS_READ | STATUS_ROTATING);
1227 ReadTrack(cdr.SetSectorPlay);
1229 buf = CDR_getBuffer();
1234 CDR_LOG_I("cdrReadInterrupt() Log: err\n");
1235 memset(cdr.Transfer, 0, DATA_SIZE);
1236 cdrReadInterruptSetResult(cdr.StatP | STATUS_ERROR);
1240 if (!cdr.Irq1Pending)
1241 cdrUpdateTransferBuf(buf);
1243 if ((!cdr.Muted) && (cdr.Mode & MODE_STRSND) && (!Config.Xa) && (cdr.FirstSector != -1)) { // CD-XA
1245 // Firemen 2: Multi-XA files - briefings, cutscenes
1246 if( cdr.FirstSector == 1 && (cdr.Mode & MODE_SF)==0 ) {
1248 cdr.Channel = hdr[1];
1252 * Skips playing on channel 255.
1253 * Fixes missing audio in Blue's Clues : Blue's Big Musical. (Should also fix Taxi 2)
1254 * TODO : Check if this is the proper behaviour.
1256 if ((hdr[2] & 0x4) && hdr[0] == cdr.File && hdr[1] == cdr.Channel && cdr.Channel != 255) {
1257 int ret = xa_decode_sector(&cdr.Xa, buf + 4, cdr.FirstSector);
1259 cdrAttenuate(cdr.Xa.pcm, cdr.Xa.nsamples, cdr.Xa.stereo);
1260 SPU_playADPCMchannel(&cdr.Xa, psxRegs.cycle, cdr.FirstSector);
1261 cdr.FirstSector = 0;
1263 else cdr.FirstSector = -1;
1268 Croc 2: $40 - only FORM1 (*)
1269 Judge Dredd: $C8 - only FORM1 (*)
1270 Sim Theme Park - no adpcm at all (zero)
1273 if (!(cdr.Mode & MODE_STRSND) || !(buf[4+2] & 0x4))
1274 cdrReadInterruptSetResult(cdr.StatP);
1276 cdr.SetSectorPlay[2]++;
1277 if (cdr.SetSectorPlay[2] == 75) {
1278 cdr.SetSectorPlay[2] = 0;
1279 cdr.SetSectorPlay[1]++;
1280 if (cdr.SetSectorPlay[1] == 60) {
1281 cdr.SetSectorPlay[1] = 0;
1282 cdr.SetSectorPlay[0]++;
1286 if (!cdr.Irq1Pending) {
1287 // update for CdlGetlocP
1288 ReadTrack(cdr.SetSectorPlay);
1291 CDRPLAYREAD_INT((cdr.Mode & MODE_SPEED) ? (cdReadTime / 2) : cdReadTime, 0);
1300 bit 5 - 1 result ready
1302 bit 7 - 1 command being processed
1305 unsigned char cdrRead0(void) {
1306 if (cdr.ResultReady)
1311 cdr.Ctrl |= 0x40; // data fifo not empty
1313 // What means the 0x10 and the 0x08 bits? I only saw it used by the bios
1316 CDR_LOG_IO("cdr r0.sta: %02x\n", cdr.Ctrl);
1318 return psxHu8(0x1800) = cdr.Ctrl;
1321 void cdrWrite0(unsigned char rt) {
1322 CDR_LOG_IO("cdr w0.idx: %02x\n", rt);
1324 cdr.Ctrl = (rt & 3) | (cdr.Ctrl & ~3);
1327 unsigned char cdrRead1(void) {
1328 if ((cdr.ResultP & 0xf) < cdr.ResultC)
1329 psxHu8(0x1801) = cdr.Result[cdr.ResultP & 0xf];
1333 if (cdr.ResultP == cdr.ResultC)
1334 cdr.ResultReady = 0;
1336 CDR_LOG_IO("cdr r1.rsp: %02x #%u\n", psxHu8(0x1801), cdr.ResultP - 1);
1338 return psxHu8(0x1801);
1341 void cdrWrite1(unsigned char rt) {
1342 const char *rnames[] = { "cmd", "smd", "smc", "arr" }; (void)rnames;
1343 CDR_LOG_IO("cdr w1.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1345 switch (cdr.Ctrl & 3) {
1349 cdr.AttenuatorRightToRightT = rt;
1355 #ifdef CDR_LOG_CMD_IRQ
1356 SysPrintf("%u cdrom: CD1 write: %x (%s)", psxRegs.cycle, rt, CmdName[rt]);
1359 SysPrintf(" Param[%d] = {", cdr.ParamC);
1360 for (i = 0; i < cdr.ParamC; i++)
1361 SysPrintf(" %x,", cdr.Param[i]);
1368 cdr.ResultReady = 0;
1371 if (!cdr.CmdInProgress) {
1372 cdr.CmdInProgress = rt;
1373 // should be something like 12k + controller delays
1377 CDR_LOG_I("%u cdrom: cmd while busy: %02x, prev %02x, busy %02x\n",
1378 psxRegs.cycle, rt, cdr.Cmd, cdr.CmdInProgress);
1379 if (cdr.CmdInProgress < 0x100) // no pending 2nd response
1380 cdr.CmdInProgress = rt;
1386 unsigned char cdrRead2(void) {
1387 unsigned char ret = 0;
1389 if (cdr.FifoOffset < cdr.FifoSize)
1390 ret = cdr.Transfer[cdr.FifoOffset++];
1392 CDR_LOG_I("cdrom: read empty fifo (%d)\n", cdr.FifoSize);
1394 CDR_LOG_IO("cdr r2.dat: %02x\n", ret);
1398 void cdrWrite2(unsigned char rt) {
1399 const char *rnames[] = { "prm", "ien", "all", "arl" }; (void)rnames;
1400 CDR_LOG_IO("cdr w2.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1402 switch (cdr.Ctrl & 3) {
1404 if (cdr.ParamC < 8) // FIXME: size and wrapping
1405 cdr.Param[cdr.ParamC++] = rt;
1412 cdr.AttenuatorLeftToLeftT = rt;
1415 cdr.AttenuatorRightToLeftT = rt;
1420 unsigned char cdrRead3(void) {
1422 psxHu8(0x1803) = cdr.Stat | 0xE0;
1424 psxHu8(0x1803) = cdr.Reg2 | 0xE0;
1426 CDR_LOG_IO("cdr r3.%s: %02x\n", (cdr.Ctrl & 1) ? "ifl" : "ien", psxHu8(0x1803));
1427 return psxHu8(0x1803);
1430 void cdrWrite3(unsigned char rt) {
1431 const char *rnames[] = { "req", "ifl", "alr", "ava" }; (void)rnames;
1432 CDR_LOG_IO("cdr w3.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1434 switch (cdr.Ctrl & 3) {
1438 if (cdr.Stat & rt) {
1439 #ifdef CDR_LOG_CMD_IRQ
1440 SysPrintf("%u cdrom: ack %02x (w %02x)\n",
1441 psxRegs.cycle, cdr.Stat & rt, rt);
1443 // note: Croc vs Discworld Noir
1444 if (!(psxRegs.interrupt & (1 << PSXINT_CDR)) &&
1445 (cdr.CmdInProgress || cdr.Irq1Pending))
1446 CDR_INT(850); // 711-993
1454 cdr.AttenuatorLeftToRightT = rt;
1458 memcpy(&cdr.AttenuatorLeftToLeft, &cdr.AttenuatorLeftToLeftT, 4);
1459 CDR_LOG("CD-XA Volume: %02x %02x | %02x %02x\n",
1460 cdr.AttenuatorLeftToLeft, cdr.AttenuatorLeftToRight,
1461 cdr.AttenuatorRightToLeft, cdr.AttenuatorRightToRight);
1467 if ((rt & 0x80) && cdr.FifoOffset < cdr.FifoSize) {
1468 CDR_LOG("cdrom: FifoOffset(2) %d/%d\n", cdr.FifoOffset, cdr.FifoSize);
1470 else if (rt & 0x80) {
1471 switch (cdr.Mode & 0x30) {
1472 case MODE_SIZE_2328:
1474 cdr.FifoOffset = 12;
1475 cdr.FifoSize = 2048 + 12;
1478 case MODE_SIZE_2340:
1481 cdr.FifoSize = 2340;
1485 else if (!(rt & 0xc0))
1486 cdr.FifoOffset = DATA_SIZE; // fifo empty
1489 void psxDma3(u32 madr, u32 bcr, u32 chcr) {
1494 CDR_LOG("psxDma3() Log: *** DMA 3 *** %x addr = %x size = %x\n", chcr, madr, bcr);
1496 switch (chcr & 0x71000000) {
1498 ptr = (u8 *)PSXM(madr);
1500 CDR_LOG_I("psxDma3() Log: *** DMA 3 *** NULL Pointer!\n");
1504 cdsize = (((bcr - 1) & 0xffff) + 1) * 4;
1507 GS CDX: Enhancement CD crash
1510 - Spams DMA3 and gets buffer overrun
1512 size = DATA_SIZE - cdr.FifoOffset;
1517 memcpy(ptr, cdr.Transfer + cdr.FifoOffset, size);
1518 cdr.FifoOffset += size;
1519 psxCpu->Clear(madr, size / 4);
1522 CDR_LOG_I("cdrom: dma3 %d/%d\n", size, cdsize);
1524 CDRDMA_INT((cdsize/4) * 24);
1526 HW_DMA3_CHCR &= SWAPu32(~0x10000000);
1528 HW_DMA3_MADR = SWAPu32(madr + cdsize);
1529 HW_DMA3_BCR &= SWAPu32(0xffff0000);
1533 psxRegs.cycle += (cdsize/4) * 24 - 20;
1538 CDR_LOG_I("psxDma3() Log: Unknown cddma %x\n", chcr);
1542 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1546 void cdrDmaInterrupt(void)
1548 if (HW_DMA3_CHCR & SWAP32(0x01000000))
1550 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1555 static void getCdInfo(void)
1559 CDR_getTN(cdr.ResultTN);
1560 CDR_getTD(0, cdr.SetSectorEnd);
1561 tmp = cdr.SetSectorEnd[0];
1562 cdr.SetSectorEnd[0] = cdr.SetSectorEnd[2];
1563 cdr.SetSectorEnd[2] = tmp;
1567 memset(&cdr, 0, sizeof(cdr));
1573 cdr.FifoOffset = DATA_SIZE; // fifo empty
1574 if (CdromId[0] == '\0') {
1575 cdr.DriveState = DRIVESTATE_STOPPED;
1579 cdr.DriveState = DRIVESTATE_STANDBY;
1580 cdr.StatP = STATUS_ROTATING;
1583 // BIOS player - default values
1584 cdr.AttenuatorLeftToLeft = 0x80;
1585 cdr.AttenuatorLeftToRight = 0x00;
1586 cdr.AttenuatorRightToLeft = 0x00;
1587 cdr.AttenuatorRightToRight = 0x80;
1592 int cdrFreeze(void *f, int Mode) {
1596 if (Mode == 0 && !Config.Cdda)
1599 cdr.freeze_ver = 0x63647202;
1600 gzfreeze(&cdr, sizeof(cdr));
1603 cdr.ParamP = cdr.ParamC;
1604 tmp = cdr.FifoOffset;
1607 gzfreeze(&tmp, sizeof(tmp));
1612 cdr.FifoOffset = tmp;
1613 cdr.FifoSize = (cdr.Mode & 0x20) ? 2340 : 2048 + 12;
1615 // read right sub data
1616 tmpp[0] = btoi(cdr.Prev[0]);
1617 tmpp[1] = btoi(cdr.Prev[1]);
1618 tmpp[2] = btoi(cdr.Prev[2]);
1623 if (cdr.freeze_ver < 0x63647202)
1624 memcpy(cdr.SetSectorPlay, cdr.SetSector, 3);
1626 Find_CurTrack(cdr.SetSectorPlay);
1628 CDR_play(cdr.SetSectorPlay);
1629 if (psxRegs.interrupt & (1 << PSXINT_CDRPLAY_OLD))
1630 CDRPLAYREAD_INT((cdr.Mode & 0x80) ? (cdReadTime / 2) : cdReadTime, 1);
1633 if ((cdr.freeze_ver & 0xffffff00) != 0x63647200) {
1634 // old versions did not latch Reg2, have to fixup..
1635 if (cdr.Reg2 == 0) {
1636 SysPrintf("cdrom: fixing up old savestate\n");
1639 // also did not save Attenuator..
1640 if ((cdr.AttenuatorLeftToLeft | cdr.AttenuatorLeftToRight
1641 | cdr.AttenuatorRightToLeft | cdr.AttenuatorRightToRight) == 0)
1643 cdr.AttenuatorLeftToLeft = cdr.AttenuatorRightToRight = 0x80;
1651 void LidInterrupt(void) {
1653 cdrLidSeekInterrupt();