1 /***************************************************************************
2 * Copyright (C) 2007 Ryan Schultz, PCSX-df Team, PCSX team *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, write to the *
16 * Free Software Foundation, Inc., *
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *
18 ***************************************************************************/
21 * Handles all CD-ROM registers and functions.
27 #include "arm_features.h"
31 #define CDR_LOG SysPrintf
36 #define CDR_LOG_I SysPrintf
38 #define CDR_LOG_I(...)
41 #define CDR_LOG_IO SysPrintf
43 #define CDR_LOG_IO(...)
45 //#define CDR_LOG_CMD_IRQ
49 unsigned char Reg1Mode;
51 unsigned char CmdProcess;
57 unsigned char Transfer[DATA_SIZE];
61 unsigned char Relative[3];
62 unsigned char Absolute[3];
64 unsigned char TrackChanged;
65 boolean m_locationChanged;
66 unsigned char pad1[2];
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,
213 DRIVESTATE_RESCAN_CD,
214 DRIVESTATE_PREPARE_CD,
224 static struct CdrStat stat;
226 static unsigned int msf2sec(const u8 *msf) {
227 return ((msf[0] * 60 + msf[1]) * 75) + msf[2];
230 // for that weird psemu API..
231 static unsigned int fsm2sec(const u8 *msf) {
232 return ((msf[2] * 60 + msf[1]) * 75) + msf[0];
235 static void sec2msf(unsigned int s, u8 *msf) {
236 msf[0] = s / 75 / 60;
237 s = s - msf[0] * 75 * 60;
244 #define CDR_INT(eCycle) { \
245 psxRegs.interrupt |= (1 << PSXINT_CDR); \
246 psxRegs.intCycle[PSXINT_CDR].cycle = eCycle; \
247 psxRegs.intCycle[PSXINT_CDR].sCycle = psxRegs.cycle; \
248 new_dyna_set_event(PSXINT_CDR, eCycle); \
252 #define CDREAD_INT(eCycle) { \
253 psxRegs.interrupt |= (1 << PSXINT_CDREAD); \
254 psxRegs.intCycle[PSXINT_CDREAD].cycle = eCycle; \
255 psxRegs.intCycle[PSXINT_CDREAD].sCycle = psxRegs.cycle; \
256 new_dyna_set_event(PSXINT_CDREAD, eCycle); \
259 // cdrLidSeekInterrupt
260 #define CDRLID_INT(eCycle) { \
261 psxRegs.interrupt |= (1 << PSXINT_CDRLID); \
262 psxRegs.intCycle[PSXINT_CDRLID].cycle = eCycle; \
263 psxRegs.intCycle[PSXINT_CDRLID].sCycle = psxRegs.cycle; \
264 new_dyna_set_event(PSXINT_CDRLID, eCycle); \
268 #define CDRMISC_INT(eCycle) { \
269 psxRegs.interrupt |= (1 << PSXINT_CDRPLAY); \
270 psxRegs.intCycle[PSXINT_CDRPLAY].cycle = eCycle; \
271 psxRegs.intCycle[PSXINT_CDRPLAY].sCycle = psxRegs.cycle; \
272 new_dyna_set_event(PSXINT_CDRPLAY, eCycle); \
275 #define StopReading() { \
278 psxRegs.interrupt &= ~(1 << PSXINT_CDREAD); \
280 cdr.StatP &= ~(STATUS_READ|STATUS_SEEK);\
283 #define StopCdda() { \
285 if (!Config.Cdda) CDR_stop(); \
286 cdr.StatP &= ~STATUS_PLAY; \
288 cdr.FastForward = 0; \
289 cdr.FastBackward = 0; \
290 /*SPU_registerCallback( SPUirq );*/ \
294 #define SetResultSize(size) { \
296 cdr.ResultC = size; \
297 cdr.ResultReady = 1; \
300 static void setIrq(int log_cmd)
302 if (cdr.Stat & cdr.Reg2)
303 psxHu32ref(0x1070) |= SWAP32((u32)0x4);
305 #ifdef CDR_LOG_CMD_IRQ
308 SysPrintf("CDR IRQ=%d cmd %02x stat %02x: ",
309 !!(cdr.Stat & cdr.Reg2), log_cmd, cdr.Stat);
310 for (i = 0; i < cdr.ResultC; i++)
311 SysPrintf("%02x ", cdr.Result[i]);
317 // timing used in this function was taken from tests on real hardware
318 // (yes it's slow, but you probably don't want to modify it)
319 void cdrLidSeekInterrupt()
321 switch (cdr.DriveState) {
323 case DRIVESTATE_STANDBY:
324 cdr.StatP &= ~STATUS_SEEK;
326 if (CDR_getStatus(&stat) == -1)
329 if (stat.Status & STATUS_SHELLOPEN)
332 cdr.DriveState = DRIVESTATE_LID_OPEN;
337 case DRIVESTATE_LID_OPEN:
338 if (CDR_getStatus(&stat) == -1)
339 stat.Status &= ~STATUS_SHELLOPEN;
342 if (!(cdr.StatP & STATUS_SHELLOPEN)) {
344 cdr.StatP |= STATUS_SHELLOPEN;
346 // could generate error irq here, but real hardware
347 // only sometimes does that
348 // (not done when lots of commands are sent?)
350 CDRLID_INT(cdReadTime * 30);
353 else if (cdr.StatP & STATUS_ROTATING) {
354 cdr.StatP &= ~STATUS_ROTATING;
356 else if (!(stat.Status & STATUS_SHELLOPEN)) {
360 // cdr.StatP STATUS_SHELLOPEN is "sticky"
361 // and is only cleared by CdlNop
363 cdr.DriveState = DRIVESTATE_RESCAN_CD;
364 CDRLID_INT(cdReadTime * 105);
369 CDRLID_INT(cdReadTime * 3);
372 case DRIVESTATE_RESCAN_CD:
373 cdr.StatP |= STATUS_ROTATING;
374 cdr.DriveState = DRIVESTATE_PREPARE_CD;
376 // this is very long on real hardware, over 6 seconds
377 // make it a bit faster here...
378 CDRLID_INT(cdReadTime * 150);
381 case DRIVESTATE_PREPARE_CD:
382 cdr.StatP |= STATUS_SEEK;
384 cdr.DriveState = DRIVESTATE_STANDBY;
385 CDRLID_INT(cdReadTime * 26);
390 static void Find_CurTrack(const u8 *time)
394 current = msf2sec(time);
396 for (cdr.CurTrack = 1; cdr.CurTrack < cdr.ResultTN[1]; cdr.CurTrack++) {
397 CDR_getTD(cdr.CurTrack + 1, cdr.ResultTD);
398 sect = fsm2sec(cdr.ResultTD);
399 if (sect - current >= 150)
404 static void generate_subq(const u8 *time)
406 unsigned char start[3], next[3];
407 unsigned int this_s, start_s, next_s, pregap;
410 CDR_getTD(cdr.CurTrack, start);
411 if (cdr.CurTrack + 1 <= cdr.ResultTN[1]) {
413 CDR_getTD(cdr.CurTrack + 1, next);
416 // last track - cd size
418 next[0] = cdr.SetSectorEnd[2];
419 next[1] = cdr.SetSectorEnd[1];
420 next[2] = cdr.SetSectorEnd[0];
423 this_s = msf2sec(time);
424 start_s = fsm2sec(start);
425 next_s = fsm2sec(next);
427 cdr.TrackChanged = FALSE;
429 if (next_s - this_s < pregap) {
430 cdr.TrackChanged = TRUE;
437 relative_s = this_s - start_s;
438 if (relative_s < 0) {
440 relative_s = -relative_s;
442 sec2msf(relative_s, cdr.subq.Relative);
444 cdr.subq.Track = itob(cdr.CurTrack);
445 cdr.subq.Relative[0] = itob(cdr.subq.Relative[0]);
446 cdr.subq.Relative[1] = itob(cdr.subq.Relative[1]);
447 cdr.subq.Relative[2] = itob(cdr.subq.Relative[2]);
448 cdr.subq.Absolute[0] = itob(time[0]);
449 cdr.subq.Absolute[1] = itob(time[1]);
450 cdr.subq.Absolute[2] = itob(time[2]);
453 static void ReadTrack(const u8 *time) {
454 unsigned char tmp[3];
458 tmp[0] = itob(time[0]);
459 tmp[1] = itob(time[1]);
460 tmp[2] = itob(time[2]);
462 if (memcmp(cdr.Prev, tmp, 3) == 0)
465 CDR_LOG("ReadTrack *** %02x:%02x:%02x\n", tmp[0], tmp[1], tmp[2]);
467 cdr.NoErr = CDR_readTrack(tmp);
468 memcpy(cdr.Prev, tmp, 3);
473 subq = (struct SubQ *)CDR_getBufferSub();
474 if (subq != NULL && cdr.CurTrack == 1) {
475 crc = calcCrc((u8 *)subq + 12, 10);
476 if (crc == (((u16)subq->CRC[0] << 8) | subq->CRC[1])) {
477 cdr.subq.Track = subq->TrackNumber;
478 cdr.subq.Index = subq->IndexNumber;
479 memcpy(cdr.subq.Relative, subq->TrackRelativeAddress, 3);
480 memcpy(cdr.subq.Absolute, subq->AbsoluteAddress, 3);
483 CDR_LOG_I("subq bad crc @%02x:%02x:%02x\n",
484 tmp[0], tmp[1], tmp[2]);
491 CDR_LOG(" -> %02x,%02x %02x:%02x:%02x %02x:%02x:%02x\n",
492 cdr.subq.Track, cdr.subq.Index,
493 cdr.subq.Relative[0], cdr.subq.Relative[1], cdr.subq.Relative[2],
494 cdr.subq.Absolute[0], cdr.subq.Absolute[1], cdr.subq.Absolute[2]);
497 static void AddIrqQueue(unsigned short irq, unsigned long ecycle) {
499 if (irq == cdr.Irq || irq + 0x100 == cdr.Irq) {
505 CDR_LOG_I("cdr: override cmd %02x -> %02x\n", cdr.Irq, irq);
514 static void cdrPlayInterrupt_Autopause()
517 boolean abs_lev_chselect;
520 if ((cdr.Mode & MODE_AUTOPAUSE) && cdr.TrackChanged) {
521 CDR_LOG( "CDDA STOP\n" );
523 // Magic the Gathering
524 // - looping territory cdda
527 //cdr.ResultReady = 1;
528 //cdr.Stat = DataReady;
534 else if (((cdr.Mode & MODE_REPORT) || cdr.FastForward || cdr.FastBackward)) {
535 cdr.Result[0] = cdr.StatP;
536 cdr.Result[1] = cdr.subq.Track;
537 cdr.Result[2] = cdr.subq.Index;
539 abs_lev_chselect = cdr.subq.Absolute[1] & 0x01;
541 /* 8 is a hack. For accuracy, it should be 588. */
542 for (i = 0; i < 8; i++)
544 abs_lev_max = MAX_VALUE(abs_lev_max, abs(read_buf[i * 2 + abs_lev_chselect]));
546 abs_lev_max = MIN_VALUE(abs_lev_max, 32767);
547 abs_lev_max |= abs_lev_chselect << 15;
549 if (cdr.subq.Absolute[2] & 0x10) {
550 cdr.Result[3] = cdr.subq.Relative[0];
551 cdr.Result[4] = cdr.subq.Relative[1] | 0x80;
552 cdr.Result[5] = cdr.subq.Relative[2];
555 cdr.Result[3] = cdr.subq.Absolute[0];
556 cdr.Result[4] = cdr.subq.Absolute[1];
557 cdr.Result[5] = cdr.subq.Absolute[2];
560 cdr.Result[6] = abs_lev_max >> 0;
561 cdr.Result[7] = abs_lev_max >> 8;
563 // Rayman: Logo freeze (resultready + dataready)
565 cdr.Stat = DataReady;
573 void cdrPlayInterrupt()
575 if (cdr.Seeked == SEEK_PENDING) {
577 CDR_LOG_I("cdrom: seek stat hack\n");
582 cdr.StatP |= STATUS_ROTATING;
583 cdr.StatP &= ~STATUS_SEEK;
584 cdr.Result[0] = cdr.StatP;
585 cdr.Seeked = SEEK_DONE;
591 if (cdr.SetlocPending) {
592 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
593 cdr.SetlocPending = 0;
594 cdr.m_locationChanged = TRUE;
596 Find_CurTrack(cdr.SetSectorPlay);
597 ReadTrack(cdr.SetSectorPlay);
598 cdr.TrackChanged = FALSE;
601 if (!cdr.Play) return;
603 CDR_LOG( "CDDA - %d:%d:%d\n",
604 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2] );
606 if (memcmp(cdr.SetSectorPlay, cdr.SetSectorEnd, 3) == 0) {
608 cdr.TrackChanged = TRUE;
611 CDR_readCDDA(cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2], (u8 *)read_buf);
614 if (!cdr.Irq && !cdr.Stat && (cdr.Mode & (MODE_AUTOPAUSE|MODE_REPORT)))
615 cdrPlayInterrupt_Autopause();
617 if (CDR_readCDDA && !cdr.Muted && !Config.Cdda) {
618 cdrAttenuate(read_buf, CD_FRAMESIZE_RAW / 4, 1);
619 if (SPU_playCDDAchannel)
620 SPU_playCDDAchannel(read_buf, CD_FRAMESIZE_RAW);
623 cdr.SetSectorPlay[2]++;
624 if (cdr.SetSectorPlay[2] == 75) {
625 cdr.SetSectorPlay[2] = 0;
626 cdr.SetSectorPlay[1]++;
627 if (cdr.SetSectorPlay[1] == 60) {
628 cdr.SetSectorPlay[1] = 0;
629 cdr.SetSectorPlay[0]++;
633 if (cdr.m_locationChanged)
635 CDRMISC_INT(cdReadTime * 30);
636 cdr.m_locationChanged = FALSE;
640 CDRMISC_INT(cdReadTime);
643 // update for CdlGetlocP/autopause
644 generate_subq(cdr.SetSectorPlay);
647 void cdrInterrupt() {
649 int no_busy_error = 0;
650 int start_rotating = 0;
653 unsigned int seekTime = 0;
659 CDR_LOG_I("cdrom: stat hack: %02x %x\n", cdr.Irq, cdr.Stat);
668 cdr.Result[0] = cdr.StatP;
669 cdr.Stat = Acknowledge;
671 if (cdr.IrqRepeated) {
673 if (cdr.eCycle > psxRegs.cycle) {
683 if (cdr.DriveState != DRIVESTATE_LID_OPEN)
684 cdr.StatP &= ~STATUS_SHELLOPEN;
689 CDR_LOG("CDROM setloc command (%02X, %02X, %02X)\n", cdr.Param[0], cdr.Param[1], cdr.Param[2]);
691 // MM must be BCD, SS must be BCD and <0x60, FF must be BCD and <0x75
692 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))
694 CDR_LOG("Invalid/out of range seek to %02X:%02X:%02X\n", cdr.Param[0], cdr.Param[1], cdr.Param[2]);
695 error = ERROR_INVALIDARG;
700 for (i = 0; i < 3; i++)
702 set_loc[i] = btoi(cdr.Param[i]);
705 i = msf2sec(cdr.SetSectorPlay);
706 i = abs(i - msf2sec(set_loc));
708 cdr.Seeked = SEEK_PENDING;
710 memcpy(cdr.SetSector, set_loc, 3);
711 cdr.SetSector[3] = 0;
712 cdr.SetlocPending = 1;
719 if (cdr.Seeked == SEEK_PENDING) {
720 // XXX: wrong, should seek instead..
721 cdr.Seeked = SEEK_DONE;
724 cdr.FastBackward = 0;
727 if (cdr.SetlocPending) {
728 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
729 cdr.SetlocPending = 0;
730 cdr.m_locationChanged = TRUE;
734 // - Pause player, hit Track 01/02/../xx (Setloc issued!!)
736 if (cdr.ParamC == 0 || cdr.Param[0] == 0) {
737 CDR_LOG("PLAY Resume @ %d:%d:%d\n",
738 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2]);
742 int track = btoi( cdr.Param[0] );
744 if (track <= cdr.ResultTN[1])
745 cdr.CurTrack = track;
747 CDR_LOG("PLAY track %d\n", cdr.CurTrack);
749 if (CDR_getTD((u8)cdr.CurTrack, cdr.ResultTD) != -1) {
750 cdr.SetSectorPlay[0] = cdr.ResultTD[2];
751 cdr.SetSectorPlay[1] = cdr.ResultTD[1];
752 cdr.SetSectorPlay[2] = cdr.ResultTD[0];
757 Rayman: detect track changes
760 Twisted Metal 2: skip PREGAP + starting accurate SubQ
761 - plays tracks without retry play
763 Wild 9: skip PREGAP + starting accurate SubQ
764 - plays tracks without retry play
766 Find_CurTrack(cdr.SetSectorPlay);
767 ReadTrack(cdr.SetSectorPlay);
768 cdr.TrackChanged = FALSE;
772 CDR_play(cdr.SetSectorPlay);
774 // Vib Ribbon: gameplay checks flag
775 cdr.StatP &= ~STATUS_SEEK;
776 cdr.Result[0] = cdr.StatP;
778 cdr.StatP |= STATUS_PLAY;
780 // BIOS player - set flag again
783 CDRMISC_INT( cdReadTime );
788 // TODO: error 80 if stopped
791 // GameShark CD Player: Calls 2x + Play 2x
793 cdr.FastBackward = 0;
799 // GameShark CD Player: Calls 2x + Play 2x
800 cdr.FastBackward = 1;
805 if (cdr.DriveState != DRIVESTATE_STOPPED) {
806 error = ERROR_INVALIDARG;
809 AddIrqQueue(CdlStandby + 0x100, cdReadTime * 125 / 2);
813 case CdlStandby + 0x100:
819 // grab time for current track
820 CDR_getTD((u8)(cdr.CurTrack), cdr.ResultTD);
822 cdr.SetSectorPlay[0] = cdr.ResultTD[2];
823 cdr.SetSectorPlay[1] = cdr.ResultTD[1];
824 cdr.SetSectorPlay[2] = cdr.ResultTD[0];
831 if (cdr.DriveState == DRIVESTATE_STANDBY)
832 delay = cdReadTime * 30 / 2;
834 cdr.DriveState = DRIVESTATE_STOPPED;
835 AddIrqQueue(CdlStop + 0x100, delay);
838 case CdlStop + 0x100:
839 cdr.StatP &= ~STATUS_ROTATING;
840 cdr.Result[0] = cdr.StatP;
846 Gundam Battle Assault 2: much slower (*)
847 - Fixes boot, gameplay
849 Hokuto no Ken 2: slower
850 - Fixes intro + subtitles
852 InuYasha - Feudal Fairy Tale: slower
855 /* Gameblabla - Tightening the timings (as taken from Duckstation).
856 * The timings from Duckstation are based upon hardware tests.
857 * Mednafen's timing don't work for Gundam Battle Assault 2 in PAL/50hz mode,
858 * seems to be timing sensitive as it can depend on the CPU's clock speed.
860 if (cdr.DriveState == DRIVESTATE_STANDBY)
866 delay = (((cdr.Mode & MODE_SPEED) ? 2 : 1) * (1000000));
867 CDRMISC_INT((cdr.Mode & MODE_SPEED) ? cdReadTime / 2 : cdReadTime);
869 AddIrqQueue(CdlPause + 0x100, delay);
873 case CdlPause + 0x100:
874 cdr.StatP &= ~STATUS_READ;
875 cdr.Result[0] = cdr.StatP;
881 cdr.Mode = 0x20; /* This fixes This is Football 2, Pooh's Party lockups */
882 AddIrqQueue(CdlReset + 0x100, 4100000);
887 case CdlReset + 0x100:
900 cdr.File = cdr.Param[0];
901 cdr.Channel = cdr.Param[1];
909 /* Gameblabla : According to mednafen, Result size should be 5 and done this way. */
911 cdr.Result[1] = cdr.Mode;
913 cdr.Result[3] = cdr.File;
914 cdr.Result[4] = cdr.Channel;
920 memcpy(cdr.Result, cdr.Transfer, 8);
925 memcpy(&cdr.Result, &cdr.subq, 8);
928 case CdlReadT: // SetSession?
930 AddIrqQueue(CdlReadT + 0x100, cdReadTime * 290 / 4);
934 case CdlReadT + 0x100:
940 if (CDR_getTN(cdr.ResultTN) == -1) {
941 cdr.Stat = DiskError;
942 cdr.Result[0] |= STATUS_ERROR;
944 cdr.Stat = Acknowledge;
945 cdr.Result[1] = itob(cdr.ResultTN[0]);
946 cdr.Result[2] = itob(cdr.ResultTN[1]);
951 cdr.Track = btoi(cdr.Param[0]);
953 if (CDR_getTD(cdr.Track, cdr.ResultTD) == -1) {
954 cdr.Stat = DiskError;
955 cdr.Result[0] |= STATUS_ERROR;
957 cdr.Stat = Acknowledge;
958 cdr.Result[0] = cdr.StatP;
959 cdr.Result[1] = itob(cdr.ResultTD[2]);
960 cdr.Result[2] = itob(cdr.ResultTD[1]);
961 /* According to Nocash's documentation, the function doesn't care about ff.
962 * This can be seen also in Mednafen's implementation. */
963 //cdr.Result[3] = itob(cdr.ResultTD[0]);
971 cdr.StatP |= STATUS_SEEK;
974 Crusaders of Might and Magic = 0.5x-4x
975 - fix cutscene speech start
981 - fix cutscene speech
986 CDRMISC_INT(cdr.Seeked == SEEK_DONE ? 0x800 : cdReadTime * 4);
987 cdr.Seeked = SEEK_PENDING;
988 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
993 switch (cdr.Param[0]) {
994 case 0x20: // System Controller ROM Version
996 memcpy(cdr.Result, Test20, 4);
1000 memcpy(cdr.Result, Test22, 4);
1002 case 0x23: case 0x24:
1004 memcpy(cdr.Result, Test23, 4);
1011 AddIrqQueue(CdlID + 0x100, 20480);
1016 cdr.Result[0] = cdr.StatP;
1021 // 0x10 - audio | 0x40 - disk missing | 0x80 - unlicensed
1022 if (CDR_getStatus(&stat) == -1 || stat.Type == 0 || stat.Type == 0xff) {
1023 cdr.Result[1] = 0xc0;
1027 cdr.Result[1] |= 0x10;
1028 if (CdromId[0] == '\0')
1029 cdr.Result[1] |= 0x80;
1031 cdr.Result[0] |= (cdr.Result[1] >> 4) & 0x08;
1033 /* This adds the string "PCSX" in Playstation bios boot screen */
1034 memcpy((char *)&cdr.Result[4], "PCSX", 4);
1035 cdr.Stat = Complete;
1039 // yes, it really sets STATUS_SHELLOPEN
1040 cdr.StatP |= STATUS_SHELLOPEN;
1041 cdr.DriveState = DRIVESTATE_RESCAN_CD;
1052 AddIrqQueue(CdlReadToc + 0x100, cdReadTime * 180 / 4);
1057 case CdlReadToc + 0x100:
1058 cdr.Stat = Complete;
1064 if (cdr.SetlocPending) {
1065 seekTime = abs(msf2sec(cdr.SetSectorPlay) - msf2sec(cdr.SetSector)) * (cdReadTime / 200);
1068 * It was originally set to 1000000 for Driver, however it is not high enough for Worms Pinball
1069 * and was unreliable for that game.
1070 * I also tested it against Mednafen and Driver's titlescreen music starts 25 frames later, not immediatly.
1072 * Obviously, this isn't perfect but right now, it should be a bit better.
1073 * Games to test this against if you change that setting :
1074 * - Driver (titlescreen music delay and retry mission)
1075 * - Worms Pinball (Will either not boot or crash in the memory card screen)
1076 * - Viewpoint (short pauses if the delay in the ingame music is too long)
1078 * It seems that 3386880 * 5 is too much for Driver's titlescreen and it starts skipping.
1079 * However, 1000000 is not enough for Worms Pinball to reliably boot.
1081 if(seekTime > 3386880 * 2) seekTime = 3386880 * 2;
1082 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
1083 cdr.SetlocPending = 0;
1084 cdr.m_locationChanged = TRUE;
1086 Find_CurTrack(cdr.SetSectorPlay);
1088 if ((cdr.Mode & MODE_CDDA) && cdr.CurTrack > 1)
1089 // Read* acts as play for cdda tracks in cdda mode
1093 cdr.FirstSector = 1;
1095 // Fighting Force 2 - update subq time immediately
1097 ReadTrack(cdr.SetSectorPlay);
1100 // Crusaders of Might and Magic - update getlocl now
1101 // - fixes cutscene speech
1103 u8 *buf = CDR_getBuffer();
1105 memcpy(cdr.Transfer, buf, 8);
1109 Duke Nukem: Land of the Babes - seek then delay read for one frame
1111 C-12 - Final Resistance - doesn't like seek
1115 By nicolasnoble from PCSX Redux :
1116 "It LOOKS like this logic is wrong, therefore disabling it with `&& false` for now.
1117 For "PoPoLoCrois Monogatari II", the game logic will soft lock and will never issue GetLocP to detect
1118 the end of its XA streams, as it seems to assume ReadS will not return a status byte with the SEEK
1119 flag set. I think the reasonning is that since it's invalid to call GetLocP while seeking, the game
1120 tries to protect itself against errors by preventing from issuing a GetLocP while it knows the
1121 last status was "seek". But this makes the logic just softlock as it'll never get a notification
1122 about the fact the drive is done seeking and the read actually started.
1124 In other words, this state machine here is probably wrong in assuming the response to ReadS/ReadN is
1125 done right away. It's rather when it's done seeking, and the read has actually started. This probably
1126 requires a bit more work to make sure seek delays are processed properly.
1127 Checked with a few games, this seems to work fine."
1129 Gameblabla additional notes :
1130 This still needs the "+ seekTime" that PCSX Redux doesn't have for the Driver "retry" mission error.
1132 cdr.StatP |= STATUS_READ;
1133 cdr.StatP &= ~STATUS_SEEK;
1135 CDREAD_INT(((cdr.Mode & 0x80) ? (cdReadTime) : cdReadTime * 2) + seekTime);
1137 cdr.Result[0] = cdr.StatP;
1142 CDR_LOG_I("Invalid command: %02x\n", Irq);
1143 error = ERROR_INVALIDCMD;
1148 cdr.Result[0] = cdr.StatP | STATUS_ERROR;
1149 cdr.Result[1] = error;
1150 cdr.Stat = DiskError;
1154 if (cdr.DriveState == DRIVESTATE_STOPPED && start_rotating) {
1155 cdr.DriveState = DRIVESTATE_STANDBY;
1156 cdr.StatP |= STATUS_ROTATING;
1159 if (!no_busy_error) {
1160 switch (cdr.DriveState) {
1161 case DRIVESTATE_LID_OPEN:
1162 case DRIVESTATE_RESCAN_CD:
1163 case DRIVESTATE_PREPARE_CD:
1165 cdr.Result[0] = cdr.StatP | STATUS_ERROR;
1166 cdr.Result[1] = ERROR_NOTREADY;
1167 cdr.Stat = DiskError;
1178 #define ssat32_to_16(v) \
1179 asm("ssat %0,#16,%1" : "=r" (v) : "r" (v))
1181 #define ssat32_to_16(v) do { \
1182 if (v < -32768) v = -32768; \
1183 else if (v > 32767) v = 32767; \
1187 void cdrAttenuate(s16 *buf, int samples, int stereo)
1190 int ll = cdr.AttenuatorLeftToLeft;
1191 int lr = cdr.AttenuatorLeftToRight;
1192 int rl = cdr.AttenuatorRightToLeft;
1193 int rr = cdr.AttenuatorRightToRight;
1195 if (lr == 0 && rl == 0 && 0x78 <= ll && ll <= 0x88 && 0x78 <= rr && rr <= 0x88)
1198 if (!stereo && ll == 0x40 && lr == 0x40 && rl == 0x40 && rr == 0x40)
1202 for (i = 0; i < samples; i++) {
1205 l = (l * ll + r * rl) >> 7;
1206 r = (r * rr + l * lr) >> 7;
1214 for (i = 0; i < samples; i++) {
1216 l = l * (ll + rl) >> 7;
1217 //r = r * (rr + lr) >> 7;
1225 void cdrReadInterrupt() {
1231 if (cdr.Irq || cdr.Stat) {
1232 CDR_LOG_I("cdrom: read stat hack %02x %x\n", cdr.Irq, cdr.Stat);
1239 cdr.StatP |= STATUS_READ|STATUS_ROTATING;
1240 cdr.StatP &= ~STATUS_SEEK;
1241 cdr.Result[0] = cdr.StatP;
1242 cdr.Seeked = SEEK_DONE;
1244 ReadTrack(cdr.SetSectorPlay);
1246 buf = CDR_getBuffer();
1251 CDR_LOG_I("cdrReadInterrupt() Log: err\n");
1252 memset(cdr.Transfer, 0, DATA_SIZE);
1253 cdr.Stat = DiskError;
1254 cdr.Result[0] |= STATUS_ERROR;
1255 CDREAD_INT((cdr.Mode & 0x80) ? (cdReadTime / 2) : cdReadTime);
1259 memcpy(cdr.Transfer, buf, DATA_SIZE);
1260 CheckPPFCache(cdr.Transfer, cdr.Prev[0], cdr.Prev[1], cdr.Prev[2]);
1263 CDR_LOG("cdrReadInterrupt() Log: cdr.Transfer %x:%x:%x\n", cdr.Transfer[0], cdr.Transfer[1], cdr.Transfer[2]);
1265 if ((!cdr.Muted) && (cdr.Mode & MODE_STRSND) && (!Config.Xa) && (cdr.FirstSector != -1)) { // CD-XA
1266 // Firemen 2: Multi-XA files - briefings, cutscenes
1267 if( cdr.FirstSector == 1 && (cdr.Mode & MODE_SF)==0 ) {
1268 cdr.File = cdr.Transfer[4 + 0];
1269 cdr.Channel = cdr.Transfer[4 + 1];
1273 * Skips playing on channel 255.
1274 * Fixes missing audio in Blue's Clues : Blue's Big Musical. (Should also fix Taxi 2)
1275 * TODO : Check if this is the proper behaviour.
1277 if((cdr.Transfer[4 + 2] & 0x4) &&
1278 (cdr.Transfer[4 + 1] == cdr.Channel) &&
1279 (cdr.Transfer[4 + 0] == cdr.File) && cdr.Channel != 255) {
1280 int ret = xa_decode_sector(&cdr.Xa, cdr.Transfer+4, cdr.FirstSector);
1282 cdrAttenuate(cdr.Xa.pcm, cdr.Xa.nsamples, cdr.Xa.stereo);
1285 * This is a hack for Megaman X4, Castlevania etc...
1286 * that regressed from the new m_locationChanged and CDROM timings changes.
1287 * It is mostly noticeable in Castevania however and the stuttering can be very jarring.
1289 * According to PCSX redux authors, we shouldn't cause a location change if
1290 * the sector difference is too small.
1291 * I attempted to go with that approach but came empty handed.
1292 * So for now, let's just set cdr.m_locationChanged to false when playing back any ADPCM samples.
1293 * This does not regress Crash Team Racing's intro at least.
1295 cdr.m_locationChanged = FALSE;
1296 SPU_playADPCMchannel(&cdr.Xa);
1297 cdr.FirstSector = 0;
1299 else cdr.FirstSector = -1;
1303 cdr.SetSectorPlay[2]++;
1304 if (cdr.SetSectorPlay[2] == 75) {
1305 cdr.SetSectorPlay[2] = 0;
1306 cdr.SetSectorPlay[1]++;
1307 if (cdr.SetSectorPlay[1] == 60) {
1308 cdr.SetSectorPlay[1] = 0;
1309 cdr.SetSectorPlay[0]++;
1315 uint32_t delay = (cdr.Mode & MODE_SPEED) ? (cdReadTime / 2) : cdReadTime;
1316 if (cdr.m_locationChanged) {
1317 CDREAD_INT(delay * 30);
1318 cdr.m_locationChanged = FALSE;
1324 Croc 2: $40 - only FORM1 (*)
1325 Judge Dredd: $C8 - only FORM1 (*)
1326 Sim Theme Park - no adpcm at all (zero)
1329 if (!(cdr.Mode & MODE_STRSND) || !(cdr.Transfer[4+2] & 0x4)) {
1330 cdr.Stat = DataReady;
1334 // update for CdlGetlocP
1335 ReadTrack(cdr.SetSectorPlay);
1344 bit 5 - 1 result ready
1346 bit 7 - 1 command being processed
1349 unsigned char cdrRead0(void) {
1350 if (cdr.ResultReady)
1358 // cdr.Ctrl &= ~0x40;
1360 // What means the 0x10 and the 0x08 bits? I only saw it used by the bios
1363 CDR_LOG_IO("cdr r0.sta: %02x\n", cdr.Ctrl);
1365 return psxHu8(0x1800) = cdr.Ctrl;
1368 void cdrWrite0(unsigned char rt) {
1369 CDR_LOG_IO("cdr w0.idx: %02x\n", rt);
1371 cdr.Ctrl = (rt & 3) | (cdr.Ctrl & ~3);
1374 unsigned char cdrRead1(void) {
1375 if ((cdr.ResultP & 0xf) < cdr.ResultC)
1376 psxHu8(0x1801) = cdr.Result[cdr.ResultP & 0xf];
1380 if (cdr.ResultP == cdr.ResultC)
1381 cdr.ResultReady = 0;
1383 CDR_LOG_IO("cdr r1.rsp: %02x #%u\n", psxHu8(0x1801), cdr.ResultP - 1);
1385 return psxHu8(0x1801);
1388 void cdrWrite1(unsigned char rt) {
1389 const char *rnames[] = { "cmd", "smd", "smc", "arr" }; (void)rnames;
1390 CDR_LOG_IO("cdr w1.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1392 switch (cdr.Ctrl & 3) {
1396 cdr.AttenuatorRightToRightT = rt;
1405 #ifdef CDR_LOG_CMD_IRQ
1406 SysPrintf("CD1 write: %x (%s)", rt, CmdName[rt]);
1409 SysPrintf(" Param[%d] = {", cdr.ParamC);
1410 for (i = 0; i < cdr.ParamC; i++)
1411 SysPrintf(" %x,", cdr.Param[i]);
1418 cdr.ResultReady = 0;
1420 // cdr.Stat = NoIntr;
1421 AddIrqQueue(cdr.Cmd, 0x800);
1434 cdr.Seeked = SEEK_DONE;
1440 CDR_LOG("cdrWrite1() Log: Setmode %x\n", cdr.Param[0]);
1442 cdr.Mode = cdr.Param[0];
1444 // Squaresoft on PlayStation 1998 Collector's CD Vol. 1
1445 // - fixes choppy movie sound
1446 if( cdr.Play && (cdr.Mode & MODE_CDDA) == 0 )
1452 unsigned char cdrRead2(void) {
1455 if (cdr.Readed == 0) {
1461 CDR_LOG_IO("cdr r2.dat: %02x\n", ret);
1465 void cdrWrite2(unsigned char rt) {
1466 const char *rnames[] = { "prm", "ien", "all", "arl" }; (void)rnames;
1467 CDR_LOG_IO("cdr w2.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1469 switch (cdr.Ctrl & 3) {
1471 if (cdr.ParamC < 8) // FIXME: size and wrapping
1472 cdr.Param[cdr.ParamC++] = rt;
1479 cdr.AttenuatorLeftToLeftT = rt;
1482 cdr.AttenuatorRightToLeftT = rt;
1487 unsigned char cdrRead3(void) {
1489 psxHu8(0x1803) = cdr.Stat | 0xE0;
1491 psxHu8(0x1803) = cdr.Reg2 | 0xE0;
1493 CDR_LOG_IO("cdr r3.%s: %02x\n", (cdr.Ctrl & 1) ? "ifl" : "ien", psxHu8(0x1803));
1494 return psxHu8(0x1803);
1497 void cdrWrite3(unsigned char rt) {
1498 const char *rnames[] = { "req", "ifl", "alr", "ava" }; (void)rnames;
1499 CDR_LOG_IO("cdr w3.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1501 switch (cdr.Ctrl & 3) {
1511 cdr.AttenuatorLeftToRightT = rt;
1515 memcpy(&cdr.AttenuatorLeftToLeft, &cdr.AttenuatorLeftToLeftT, 4);
1516 CDR_LOG_I("CD-XA Volume: %02x %02x | %02x %02x\n",
1517 cdr.AttenuatorLeftToLeft, cdr.AttenuatorLeftToRight,
1518 cdr.AttenuatorRightToLeft, cdr.AttenuatorRightToRight);
1523 if ((rt & 0x80) && cdr.Readed == 0) {
1525 pTransfer = cdr.Transfer;
1527 switch (cdr.Mode & 0x30) {
1528 case MODE_SIZE_2328:
1533 case MODE_SIZE_2340:
1543 void psxDma3(u32 madr, u32 bcr, u32 chcr) {
1548 CDR_LOG("psxDma3() Log: *** DMA 3 *** %x addr = %x size = %x\n", chcr, madr, bcr);
1553 if (cdr.Readed == 0) {
1554 CDR_LOG("psxDma3() Log: *** DMA 3 *** NOT READY\n");
1558 cdsize = (bcr & 0xffff) * 4;
1560 // Ape Escape: bcr = 0001 / 0000
1564 switch (cdr.Mode & (MODE_SIZE_2340|MODE_SIZE_2328)) {
1565 case MODE_SIZE_2340: cdsize = 2340; break;
1566 case MODE_SIZE_2328: cdsize = 2328; break;
1568 case MODE_SIZE_2048: cdsize = 2048; break;
1573 ptr = (u8 *)PSXM(madr);
1574 if (ptr == INVALID_PTR) {
1575 CDR_LOG("psxDma3() Log: *** DMA 3 *** NULL Pointer!\n");
1580 GS CDX: Enhancement CD crash
1583 - Spams DMA3 and gets buffer overrun
1585 size = CD_FRAMESIZE_RAW - (pTransfer - cdr.Transfer);
1590 memcpy(ptr, pTransfer, size);
1593 psxCpu->Clear(madr, cdsize / 4);
1594 pTransfer += cdsize;
1596 if( chcr == 0x11400100 ) {
1597 HW_DMA3_MADR = SWAPu32(madr + cdsize);
1598 CDRDMA_INT( (cdsize/4) / 4 );
1600 else if( chcr == 0x11000000 ) {
1601 // CDRDMA_INT( (cdsize/4) * 1 );
1603 psxRegs.cycle += (cdsize/4) * 24/2;
1609 CDR_LOG("psxDma3() Log: Unknown cddma %x\n", chcr);
1613 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1617 void cdrDmaInterrupt()
1619 if (HW_DMA3_CHCR & SWAP32(0x01000000))
1621 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1626 static void getCdInfo(void)
1630 CDR_getTN(cdr.ResultTN);
1631 CDR_getTD(0, cdr.SetSectorEnd);
1632 tmp = cdr.SetSectorEnd[0];
1633 cdr.SetSectorEnd[0] = cdr.SetSectorEnd[2];
1634 cdr.SetSectorEnd[2] = tmp;
1638 memset(&cdr, 0, sizeof(cdr));
1644 cdr.DriveState = DRIVESTATE_STANDBY;
1645 cdr.StatP = STATUS_ROTATING;
1646 pTransfer = cdr.Transfer;
1648 // BIOS player - default values
1649 cdr.AttenuatorLeftToLeft = 0x80;
1650 cdr.AttenuatorLeftToRight = 0x00;
1651 cdr.AttenuatorRightToLeft = 0x00;
1652 cdr.AttenuatorRightToRight = 0x80;
1657 int cdrFreeze(void *f, int Mode) {
1661 if (Mode == 0 && !Config.Cdda)
1664 cdr.freeze_ver = 0x63647202;
1665 gzfreeze(&cdr, sizeof(cdr));
1668 cdr.ParamP = cdr.ParamC;
1669 tmp = pTransfer - cdr.Transfer;
1672 gzfreeze(&tmp, sizeof(tmp));
1677 pTransfer = cdr.Transfer + tmp;
1679 // read right sub data
1680 tmpp[0] = btoi(cdr.Prev[0]);
1681 tmpp[1] = btoi(cdr.Prev[1]);
1682 tmpp[2] = btoi(cdr.Prev[2]);
1687 if (cdr.freeze_ver < 0x63647202)
1688 memcpy(cdr.SetSectorPlay, cdr.SetSector, 3);
1690 Find_CurTrack(cdr.SetSectorPlay);
1692 CDR_play(cdr.SetSectorPlay);
1695 if ((cdr.freeze_ver & 0xffffff00) != 0x63647200) {
1696 // old versions did not latch Reg2, have to fixup..
1697 if (cdr.Reg2 == 0) {
1698 SysPrintf("cdrom: fixing up old savestate\n");
1701 // also did not save Attenuator..
1702 if ((cdr.AttenuatorLeftToLeft | cdr.AttenuatorLeftToRight
1703 | cdr.AttenuatorRightToLeft | cdr.AttenuatorRightToRight) == 0)
1705 cdr.AttenuatorLeftToLeft = cdr.AttenuatorRightToRight = 0x80;
1713 void LidInterrupt() {
1716 cdrLidSeekInterrupt();