1 /***************************************************************************
2 * Copyright (C) 2007 Ryan Schultz, PCSX-df Team, PCSX team *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, write to the *
16 * Free Software Foundation, Inc., *
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *
18 ***************************************************************************/
21 * Handles all CD-ROM registers and functions.
27 #include "arm_features.h"
31 #define CDR_LOG SysPrintf
36 #define CDR_LOG_I SysPrintf
38 #define CDR_LOG_I(...)
41 #define CDR_LOG_IO SysPrintf
43 #define CDR_LOG_IO(...)
45 //#define CDR_LOG_CMD_IRQ
48 static unsigned char *pTransfer;
50 /* CD-ROM magic numbers */
51 #define CdlSync 0 /* nocash documentation : "Uh, actually, returns error code 40h = Invalid Command...?" */
64 #define CdlSetfilter 13
66 #define CdlGetparam 15
74 #define CdlSetclock 23
75 #define CdlGetclock 24
83 char *CmdName[0x100]= {
84 "CdlSync", "CdlNop", "CdlSetloc", "CdlPlay",
85 "CdlForward", "CdlBackward", "CdlReadN", "CdlStandby",
86 "CdlStop", "CdlPause", "CdlReset", "CdlMute",
87 "CdlDemute", "CdlSetfilter", "CdlSetmode", "CdlGetparam",
88 "CdlGetlocL", "CdlGetlocP", "CdlReadT", "CdlGetTN",
89 "CdlGetTD", "CdlSeekL", "CdlSeekP", "CdlSetclock",
90 "CdlGetclock", "CdlTest", "CdlID", "CdlReadS",
91 "CdlInit", NULL, "CDlReadToc", NULL
94 unsigned char Test04[] = { 0 };
95 unsigned char Test05[] = { 0 };
96 unsigned char Test20[] = { 0x98, 0x06, 0x10, 0xC3 };
97 unsigned char Test22[] = { 0x66, 0x6F, 0x72, 0x20, 0x45, 0x75, 0x72, 0x6F };
98 unsigned char Test23[] = { 0x43, 0x58, 0x44, 0x32, 0x39 ,0x34, 0x30, 0x51 };
104 #define Acknowledge 3
109 #define MODE_SPEED (1<<7) // 0x80
110 #define MODE_STRSND (1<<6) // 0x40 ADPCM on/off
111 #define MODE_SIZE_2340 (1<<5) // 0x20
112 #define MODE_SIZE_2328 (1<<4) // 0x10
113 #define MODE_SIZE_2048 (0<<4) // 0x00
114 #define MODE_SF (1<<3) // 0x08 channel on/off
115 #define MODE_REPORT (1<<2) // 0x04
116 #define MODE_AUTOPAUSE (1<<1) // 0x02
117 #define MODE_CDDA (1<<0) // 0x01
120 #define STATUS_PLAY (1<<7) // 0x80
121 #define STATUS_SEEK (1<<6) // 0x40
122 #define STATUS_READ (1<<5) // 0x20
123 #define STATUS_SHELLOPEN (1<<4) // 0x10
124 #define STATUS_UNKNOWN3 (1<<3) // 0x08
125 #define STATUS_UNKNOWN2 (1<<2) // 0x04
126 #define STATUS_ROTATING (1<<1) // 0x02
127 #define STATUS_ERROR (1<<0) // 0x01
130 #define ERROR_NOTREADY (1<<7) // 0x80
131 #define ERROR_INVALIDCMD (1<<6) // 0x40
132 #define ERROR_INVALIDARG (1<<5) // 0x20
134 // 1x = 75 sectors per second
135 // PSXCLK = 1 sec in the ps
136 // so (PSXCLK / 75) = cdr read time (linuzappz)
137 #define cdReadTime (PSXCLK / 75)
140 DRIVESTATE_STANDBY = 0,
142 DRIVESTATE_RESCAN_CD,
143 DRIVESTATE_PREPARE_CD,
153 static struct CdrStat stat;
155 static unsigned int msf2sec(const u8 *msf) {
156 return ((msf[0] * 60 + msf[1]) * 75) + msf[2];
159 // for that weird psemu API..
160 static unsigned int fsm2sec(const u8 *msf) {
161 return ((msf[2] * 60 + msf[1]) * 75) + msf[0];
164 static void sec2msf(unsigned int s, u8 *msf) {
165 msf[0] = s / 75 / 60;
166 s = s - msf[0] * 75 * 60;
173 #define CDR_INT(eCycle) { \
174 psxRegs.interrupt |= (1 << PSXINT_CDR); \
175 psxRegs.intCycle[PSXINT_CDR].cycle = eCycle; \
176 psxRegs.intCycle[PSXINT_CDR].sCycle = psxRegs.cycle; \
177 new_dyna_set_event(PSXINT_CDR, eCycle); \
181 #define CDREAD_INT(eCycle) { \
182 psxRegs.interrupt |= (1 << PSXINT_CDREAD); \
183 psxRegs.intCycle[PSXINT_CDREAD].cycle = eCycle; \
184 psxRegs.intCycle[PSXINT_CDREAD].sCycle = psxRegs.cycle; \
185 new_dyna_set_event(PSXINT_CDREAD, eCycle); \
188 // cdrLidSeekInterrupt
189 #define CDRLID_INT(eCycle) { \
190 psxRegs.interrupt |= (1 << PSXINT_CDRLID); \
191 psxRegs.intCycle[PSXINT_CDRLID].cycle = eCycle; \
192 psxRegs.intCycle[PSXINT_CDRLID].sCycle = psxRegs.cycle; \
193 new_dyna_set_event(PSXINT_CDRLID, eCycle); \
197 #define CDRMISC_INT(eCycle) { \
198 psxRegs.interrupt |= (1 << PSXINT_CDRPLAY); \
199 psxRegs.intCycle[PSXINT_CDRPLAY].cycle = eCycle; \
200 psxRegs.intCycle[PSXINT_CDRPLAY].sCycle = psxRegs.cycle; \
201 new_dyna_set_event(PSXINT_CDRPLAY, eCycle); \
204 #define StopReading() { \
207 psxRegs.interrupt &= ~(1 << PSXINT_CDREAD); \
209 cdr.StatP &= ~(STATUS_READ|STATUS_SEEK);\
212 #define StopCdda() { \
214 if (!Config.Cdda) CDR_stop(); \
215 cdr.StatP &= ~STATUS_PLAY; \
217 cdr.FastForward = 0; \
218 cdr.FastBackward = 0; \
219 /*SPU_registerCallback( SPUirq );*/ \
223 #define SetResultSize(size) { \
225 cdr.ResultC = size; \
226 cdr.ResultReady = 1; \
229 static void setIrq(void)
231 if (cdr.Stat & cdr.Reg2)
232 psxHu32ref(0x1070) |= SWAP32((u32)0x4);
235 // timing used in this function was taken from tests on real hardware
236 // (yes it's slow, but you probably don't want to modify it)
237 void cdrLidSeekInterrupt()
239 switch (cdr.DriveState) {
241 case DRIVESTATE_STANDBY:
242 cdr.StatP &= ~STATUS_SEEK;
244 if (CDR_getStatus(&stat) == -1)
247 if (stat.Status & STATUS_SHELLOPEN)
250 cdr.DriveState = DRIVESTATE_LID_OPEN;
255 case DRIVESTATE_LID_OPEN:
256 if (CDR_getStatus(&stat) == -1)
257 stat.Status &= ~STATUS_SHELLOPEN;
260 if (!(cdr.StatP & STATUS_SHELLOPEN)) {
262 cdr.StatP |= STATUS_SHELLOPEN;
264 // could generate error irq here, but real hardware
265 // only sometimes does that
266 // (not done when lots of commands are sent?)
268 CDRLID_INT(cdReadTime * 30);
271 else if (cdr.StatP & STATUS_ROTATING) {
272 cdr.StatP &= ~STATUS_ROTATING;
274 else if (!(stat.Status & STATUS_SHELLOPEN)) {
278 // cdr.StatP STATUS_SHELLOPEN is "sticky"
279 // and is only cleared by CdlNop
281 cdr.DriveState = DRIVESTATE_RESCAN_CD;
282 CDRLID_INT(cdReadTime * 105);
287 CDRLID_INT(cdReadTime * 3);
290 case DRIVESTATE_RESCAN_CD:
291 cdr.StatP |= STATUS_ROTATING;
292 cdr.DriveState = DRIVESTATE_PREPARE_CD;
294 // this is very long on real hardware, over 6 seconds
295 // make it a bit faster here...
296 CDRLID_INT(cdReadTime * 150);
299 case DRIVESTATE_PREPARE_CD:
300 cdr.StatP |= STATUS_SEEK;
302 cdr.DriveState = DRIVESTATE_STANDBY;
303 CDRLID_INT(cdReadTime * 26);
308 static void Find_CurTrack(const u8 *time)
312 current = msf2sec(time);
314 for (cdr.CurTrack = 1; cdr.CurTrack < cdr.ResultTN[1]; cdr.CurTrack++) {
315 CDR_getTD(cdr.CurTrack + 1, cdr.ResultTD);
316 sect = fsm2sec(cdr.ResultTD);
317 if (sect - current >= 150)
322 static void generate_subq(const u8 *time)
324 unsigned char start[3], next[3];
325 unsigned int this_s, start_s, next_s, pregap;
328 CDR_getTD(cdr.CurTrack, start);
329 if (cdr.CurTrack + 1 <= cdr.ResultTN[1]) {
331 CDR_getTD(cdr.CurTrack + 1, next);
334 // last track - cd size
336 next[0] = cdr.SetSectorEnd[2];
337 next[1] = cdr.SetSectorEnd[1];
338 next[2] = cdr.SetSectorEnd[0];
341 this_s = msf2sec(time);
342 start_s = fsm2sec(start);
343 next_s = fsm2sec(next);
345 cdr.TrackChanged = FALSE;
347 if (next_s - this_s < pregap) {
348 cdr.TrackChanged = TRUE;
355 relative_s = this_s - start_s;
356 if (relative_s < 0) {
358 relative_s = -relative_s;
360 sec2msf(relative_s, cdr.subq.Relative);
362 cdr.subq.Track = itob(cdr.CurTrack);
363 cdr.subq.Relative[0] = itob(cdr.subq.Relative[0]);
364 cdr.subq.Relative[1] = itob(cdr.subq.Relative[1]);
365 cdr.subq.Relative[2] = itob(cdr.subq.Relative[2]);
366 cdr.subq.Absolute[0] = itob(time[0]);
367 cdr.subq.Absolute[1] = itob(time[1]);
368 cdr.subq.Absolute[2] = itob(time[2]);
371 static void ReadTrack(const u8 *time) {
372 unsigned char tmp[3];
376 tmp[0] = itob(time[0]);
377 tmp[1] = itob(time[1]);
378 tmp[2] = itob(time[2]);
380 if (memcmp(cdr.Prev, tmp, 3) == 0)
383 CDR_LOG("ReadTrack *** %02x:%02x:%02x\n", tmp[0], tmp[1], tmp[2]);
385 cdr.RErr = CDR_readTrack(tmp);
386 memcpy(cdr.Prev, tmp, 3);
391 subq = (struct SubQ *)CDR_getBufferSub();
392 if (subq != NULL && cdr.CurTrack == 1) {
393 crc = calcCrc((u8 *)subq + 12, 10);
394 if (crc == (((u16)subq->CRC[0] << 8) | subq->CRC[1])) {
395 cdr.subq.Track = subq->TrackNumber;
396 cdr.subq.Index = subq->IndexNumber;
397 memcpy(cdr.subq.Relative, subq->TrackRelativeAddress, 3);
398 memcpy(cdr.subq.Absolute, subq->AbsoluteAddress, 3);
401 CDR_LOG_I("subq bad crc @%02x:%02x:%02x\n",
402 tmp[0], tmp[1], tmp[2]);
409 CDR_LOG(" -> %02x,%02x %02x:%02x:%02x %02x:%02x:%02x\n",
410 cdr.subq.Track, cdr.subq.Index,
411 cdr.subq.Relative[0], cdr.subq.Relative[1], cdr.subq.Relative[2],
412 cdr.subq.Absolute[0], cdr.subq.Absolute[1], cdr.subq.Absolute[2]);
415 static void AddIrqQueue(unsigned short irq, unsigned long ecycle) {
417 if (irq == cdr.Irq || irq + 0x100 == cdr.Irq) {
423 CDR_LOG_I("cdr: override cmd %02x -> %02x\n", cdr.Irq, irq);
432 static void cdrPlayInterrupt_Autopause()
434 if ((cdr.Mode & MODE_AUTOPAUSE) && cdr.TrackChanged) {
435 CDR_LOG( "CDDA STOP\n" );
437 // Magic the Gathering
438 // - looping territory cdda
441 //cdr.ResultReady = 1;
442 //cdr.Stat = DataReady;
448 else if (((cdr.Mode & MODE_REPORT) || cdr.FastForward || cdr.FastBackward)) {
450 cdr.Result[0] = cdr.StatP;
451 cdr.Result[1] = cdr.subq.Track;
452 cdr.Result[2] = cdr.subq.Index;
454 if (cdr.subq.Absolute[2] & 0x10) {
455 cdr.Result[3] = cdr.subq.Relative[0];
456 cdr.Result[4] = cdr.subq.Relative[1] | 0x80;
457 cdr.Result[5] = cdr.subq.Relative[2];
460 cdr.Result[3] = cdr.subq.Absolute[0];
461 cdr.Result[4] = cdr.subq.Absolute[1];
462 cdr.Result[5] = cdr.subq.Absolute[2];
468 // Rayman: Logo freeze (resultready + dataready)
470 cdr.Stat = DataReady;
478 void cdrPlayInterrupt()
480 if (cdr.Seeked == SEEK_PENDING) {
482 CDR_LOG_I("cdrom: seek stat hack\n");
487 cdr.StatP |= STATUS_ROTATING;
488 cdr.StatP &= ~STATUS_SEEK;
489 cdr.Result[0] = cdr.StatP;
490 cdr.Seeked = SEEK_DONE;
497 if (cdr.SetlocPending) {
498 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
499 cdr.SetlocPending = 0;
500 cdr.m_locationChanged = TRUE;
502 Find_CurTrack(cdr.SetSectorPlay);
503 ReadTrack(cdr.SetSectorPlay);
504 cdr.TrackChanged = FALSE;
507 if (!cdr.Play) return;
509 CDR_LOG( "CDDA - %d:%d:%d\n",
510 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2] );
512 if (memcmp(cdr.SetSectorPlay, cdr.SetSectorEnd, 3) == 0) {
514 cdr.TrackChanged = TRUE;
517 if (!cdr.Irq && !cdr.Stat && (cdr.Mode & (MODE_AUTOPAUSE|MODE_REPORT)))
518 cdrPlayInterrupt_Autopause();
520 if (!cdr.Play) return;
522 cdr.SetSectorPlay[2]++;
523 if (cdr.SetSectorPlay[2] == 75) {
524 cdr.SetSectorPlay[2] = 0;
525 cdr.SetSectorPlay[1]++;
526 if (cdr.SetSectorPlay[1] == 60) {
527 cdr.SetSectorPlay[1] = 0;
528 cdr.SetSectorPlay[0]++;
532 if (cdr.m_locationChanged)
534 CDRMISC_INT(cdReadTime * 30);
535 cdr.m_locationChanged = FALSE;
539 CDRMISC_INT(cdReadTime);
542 // update for CdlGetlocP/autopause
543 generate_subq(cdr.SetSectorPlay);
546 void cdrInterrupt() {
548 int no_busy_error = 0;
549 int start_rotating = 0;
552 unsigned int seekTime = 0;
556 CDR_LOG_I("cdrom: stat hack: %02x %x\n", cdr.Irq, cdr.Stat);
565 cdr.Result[0] = cdr.StatP;
566 cdr.Stat = Acknowledge;
568 if (cdr.IrqRepeated) {
570 if (cdr.eCycle > psxRegs.cycle) {
580 if (cdr.DriveState != DRIVESTATE_LID_OPEN)
581 cdr.StatP &= ~STATUS_SHELLOPEN;
591 /* It would set it to SEEK_DONE*/
592 cdr.Seeked = SEEK_PENDING;
594 cdr.FastBackward = 0;
597 if (cdr.SetlocPending) {
598 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
599 cdr.SetlocPending = 0;
600 cdr.m_locationChanged = TRUE;
604 // - Pause player, hit Track 01/02/../xx (Setloc issued!!)
606 if (cdr.ParamC == 0 || cdr.Param[0] == 0) {
607 CDR_LOG("PLAY Resume @ %d:%d:%d\n",
608 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2]);
612 int track = btoi( cdr.Param[0] );
614 if (track <= cdr.ResultTN[1])
615 cdr.CurTrack = track;
617 CDR_LOG("PLAY track %d\n", cdr.CurTrack);
619 if (CDR_getTD((u8)cdr.CurTrack, cdr.ResultTD) != -1) {
620 cdr.SetSectorPlay[0] = cdr.ResultTD[2];
621 cdr.SetSectorPlay[1] = cdr.ResultTD[1];
622 cdr.SetSectorPlay[2] = cdr.ResultTD[0];
627 Rayman: detect track changes
630 Twisted Metal 2: skip PREGAP + starting accurate SubQ
631 - plays tracks without retry play
633 Wild 9: skip PREGAP + starting accurate SubQ
634 - plays tracks without retry play
636 Find_CurTrack(cdr.SetSectorPlay);
637 ReadTrack(cdr.SetSectorPlay);
638 cdr.TrackChanged = FALSE;
641 CDR_play(cdr.SetSectorPlay);
643 // Vib Ribbon: gameplay checks flag
644 cdr.StatP &= ~STATUS_SEEK;
645 cdr.Result[0] = cdr.StatP;
647 cdr.StatP |= STATUS_PLAY;
649 // BIOS player - set flag again
652 CDRMISC_INT( cdReadTime );
657 // TODO: error 80 if stopped
660 // GameShark CD Player: Calls 2x + Play 2x
662 cdr.FastBackward = 0;
668 // GameShark CD Player: Calls 2x + Play 2x
674 if (cdr.DriveState != DRIVESTATE_STOPPED) {
675 error = ERROR_INVALIDARG;
678 AddIrqQueue(CdlStandby + 0x100, cdReadTime * 125 / 2);
682 case CdlStandby + 0x100:
689 // grab time for current track
690 CDR_getTD((u8)(cdr.CurTrack), cdr.ResultTD);
692 cdr.SetSectorPlay[0] = cdr.ResultTD[2];
693 cdr.SetSectorPlay[1] = cdr.ResultTD[1];
694 cdr.SetSectorPlay[2] = cdr.ResultTD[0];
701 if (cdr.DriveState == DRIVESTATE_STANDBY)
702 delay = cdReadTime * 30 / 2;
704 cdr.DriveState = DRIVESTATE_STOPPED;
705 AddIrqQueue(CdlStop + 0x100, delay);
708 case CdlStop + 0x100:
709 cdr.StatP &= ~STATUS_ROTATING;
710 cdr.Result[0] = cdr.StatP;
717 Gundam Battle Assault 2: much slower (*)
718 - Fixes boot, gameplay
720 Hokuto no Ken 2: slower
721 - Fixes intro + subtitles
723 InuYasha - Feudal Fairy Tale: slower
726 AddIrqQueue(CdlPause + 0x100, cdReadTime * 3);
730 case CdlPause + 0x100:
731 cdr.StatP &= ~STATUS_READ;
732 cdr.Result[0] = cdr.StatP;
739 cdr.Mode = 0x20; /* Needed for This is Football 2, Pooh's Party and possibly others. */
740 AddIrqQueue(CdlReset + 0x100, 4100000);
745 case CdlReset + 0x100:
759 cdr.File = cdr.Param[0];
760 cdr.Channel = cdr.Param[1];
769 cdr.Result[1] = cdr.Mode;
771 cdr.Result[3] = cdr.File;
772 cdr.Result[4] = cdr.Channel;
778 memcpy(cdr.Result, cdr.Transfer, 8);
783 memcpy(&cdr.Result, &cdr.subq, 8);
785 if (!cdr.Play && !cdr.Reading)
786 cdr.Result[1] = 0; // HACK?
789 case CdlReadT: // SetSession?
791 AddIrqQueue(CdlReadT + 0x100, cdReadTime * 290 / 4);
795 case CdlReadT + 0x100:
802 if (CDR_getTN(cdr.ResultTN) == -1) {
803 cdr.Stat = DiskError;
804 cdr.Result[0] |= STATUS_ERROR;
806 cdr.Stat = Acknowledge;
807 cdr.Result[1] = itob(cdr.ResultTN[0]);
808 cdr.Result[2] = itob(cdr.ResultTN[1]);
813 cdr.Track = btoi(cdr.Param[0]);
815 if (CDR_getTD(cdr.Track, cdr.ResultTD) == -1) {
816 cdr.Stat = DiskError;
817 cdr.Result[0] |= STATUS_ERROR;
819 cdr.Stat = Acknowledge;
820 cdr.Result[0] = cdr.StatP;
821 cdr.Result[1] = itob(cdr.ResultTD[2]);
822 cdr.Result[2] = itob(cdr.ResultTD[1]);
823 cdr.Result[3] = itob(cdr.ResultTD[0]);
831 cdr.StatP |= STATUS_SEEK;
834 Crusaders of Might and Magic = 0.5x-4x
835 - fix cutscene speech start
841 - fix cutscene speech
846 CDRMISC_INT(cdr.Seeked == SEEK_DONE ? 0x800 : cdReadTime * 4);
847 cdr.Seeked = SEEK_PENDING;
852 switch (cdr.Param[0]) {
853 case 0x20: // System Controller ROM Version
855 memcpy(cdr.Result, Test20, 4);
859 memcpy(cdr.Result, Test22, 4);
861 case 0x23: case 0x24:
863 memcpy(cdr.Result, Test23, 4);
870 AddIrqQueue(CdlID + 0x100, 20480);
875 cdr.Result[0] = cdr.StatP;
880 // 0x10 - audio | 0x40 - disk missing | 0x80 - unlicensed
881 if (CDR_getStatus(&stat) == -1 || stat.Type == 0 || stat.Type == 0xff) {
882 cdr.Result[1] = 0xc0;
886 cdr.Result[1] |= 0x10;
887 if (CdromId[0] == '\0')
888 cdr.Result[1] |= 0x80;
890 cdr.Result[0] |= (cdr.Result[1] >> 4) & 0x08;
892 /* This adds the string "PCSX" in Playstation bios boot screen */
893 memcpy((char *)&cdr.Result[4], "PCSX", 4);
899 // yes, it really sets STATUS_SHELLOPEN
900 cdr.StatP |= STATUS_SHELLOPEN;
901 cdr.DriveState = DRIVESTATE_RESCAN_CD;
912 AddIrqQueue(CdlReadToc + 0x100, cdReadTime * 180 / 4);
917 case CdlReadToc + 0x100:
925 if (cdr.SetlocPending) {
926 seekTime = abs(msf2sec(cdr.SetSectorPlay) - msf2sec(cdr.SetSector)) * (cdReadTime / 200);
929 * It was originally set to 1000000 for Driver, however it is not high enough for Worms Pinball
930 * and was unreliable for that game.
931 * I also tested it against Mednafen and Driver's titlescreen music starts 25 frames later, not immediatly.
933 * Obviously, this isn't perfect but right now, it should be a bit better.
934 * Games to test this against if you change that setting :
935 * - Driver (titlescreen music delay and retry mission)
936 * - Worms Pinball (Will either not boot or crash in the memory card screen)
937 * - Viewpoint (short pauses if the delay in the ingame music is too long)
939 * It seems that 3386880 * 5 is too much for Driver's titlescreen and it starts skipping.
940 * However, 1000000 is not enough for Worms Pinball to reliably boot.
942 if(seekTime > 3386880 * 2) seekTime = 3386880 * 2;
943 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
944 cdr.SetlocPending = 0;
945 cdr.m_locationChanged = TRUE;
947 Find_CurTrack(cdr.SetSectorPlay);
949 if ((cdr.Mode & MODE_CDDA) && cdr.CurTrack > 1)
950 // Read* acts as play for cdda tracks in cdda mode
956 // Fighting Force 2 - update subq time immediately
958 ReadTrack(cdr.SetSectorPlay);
961 // Crusaders of Might and Magic - update getlocl now
962 // - fixes cutscene speech
964 u8 *buf = CDR_getBuffer();
966 memcpy(cdr.Transfer, buf, 8);
970 Duke Nukem: Land of the Babes - seek then delay read for one frame
972 C-12 - Final Resistance - doesn't like seek
976 By nicolasnoble from PCSX Redux :
977 "It LOOKS like this logic is wrong, therefore disabling it with `&& false` for now.
978 For "PoPoLoCrois Monogatari II", the game logic will soft lock and will never issue GetLocP to detect
979 the end of its XA streams, as it seems to assume ReadS will not return a status byte with the SEEK
980 flag set. I think the reasonning is that since it's invalid to call GetLocP while seeking, the game
981 tries to protect itself against errors by preventing from issuing a GetLocP while it knows the
982 last status was "seek". But this makes the logic just softlock as it'll never get a notification
983 about the fact the drive is done seeking and the read actually started.
984 In other words, this state machine here is probably wrong in assuming the response to ReadS/ReadN is
985 done right away. It's rather when it's done seeking, and the read has actually started. This probably
986 requires a bit more work to make sure seek delays are processed properly.
987 Checked with a few games, this seems to work fine."
989 Gameblabla additional notes :
990 This still needs the "+ seekTime" that PCSX Redux doesn't have for the Driver "retry" mission error.
992 cdr.StatP |= STATUS_READ;
993 cdr.StatP &= ~STATUS_SEEK;
994 CDREAD_INT(((cdr.Mode & 0x80) ? (cdReadTime) : cdReadTime * 2) + seekTime);
996 cdr.Result[0] = cdr.StatP;
1002 CDR_LOG_I("Invalid command: %02x\n", Irq);
1003 error = ERROR_INVALIDCMD;
1008 cdr.Result[0] = cdr.StatP | STATUS_ERROR;
1009 cdr.Result[1] = error;
1010 cdr.Stat = DiskError;
1014 if (cdr.DriveState == DRIVESTATE_STOPPED && start_rotating) {
1015 cdr.DriveState = DRIVESTATE_STANDBY;
1016 cdr.StatP |= STATUS_ROTATING;
1019 if (!no_busy_error) {
1020 switch (cdr.DriveState) {
1021 case DRIVESTATE_LID_OPEN:
1022 case DRIVESTATE_RESCAN_CD:
1023 case DRIVESTATE_PREPARE_CD:
1025 cdr.Result[0] = cdr.StatP | STATUS_ERROR;
1026 cdr.Result[1] = ERROR_NOTREADY;
1027 cdr.Stat = DiskError;
1036 #ifdef CDR_LOG_CMD_IRQ
1039 SysPrintf("CDR IRQ %d cmd %02x stat %02x: ",
1040 !!(cdr.Stat & cdr.Reg2), Irq, cdr.Stat);
1041 for (i = 0; i < cdr.ResultC; i++)
1042 SysPrintf("%02x ", cdr.Result[i]);
1049 #define ssat32_to_16(v) \
1050 asm("ssat %0,#16,%1" : "=r" (v) : "r" (v))
1052 #define ssat32_to_16(v) do { \
1053 if (v < -32768) v = -32768; \
1054 else if (v > 32767) v = 32767; \
1058 void cdrAttenuate(s16 *buf, int samples, int stereo)
1061 int ll = cdr.AttenuatorLeftToLeft;
1062 int lr = cdr.AttenuatorLeftToRight;
1063 int rl = cdr.AttenuatorRightToLeft;
1064 int rr = cdr.AttenuatorRightToRight;
1066 if (lr == 0 && rl == 0 && 0x78 <= ll && ll <= 0x88 && 0x78 <= rr && rr <= 0x88)
1069 if (!stereo && ll == 0x40 && lr == 0x40 && rl == 0x40 && rr == 0x40)
1073 for (i = 0; i < samples; i++) {
1076 l = (l * ll + r * rl) >> 7;
1077 r = (r * rr + l * lr) >> 7;
1085 for (i = 0; i < samples; i++) {
1087 l = l * (ll + rl) >> 7;
1088 //r = r * (rr + lr) >> 7;
1096 void cdrReadInterrupt() {
1102 if (cdr.Irq || cdr.Stat) {
1103 CDR_LOG_I("cdrom: read stat hack %02x %x\n", cdr.Irq, cdr.Stat);
1110 cdr.StatP |= STATUS_READ|STATUS_ROTATING;
1111 cdr.StatP &= ~STATUS_SEEK;
1112 cdr.Result[0] = cdr.StatP;
1113 cdr.Seeked = SEEK_DONE;
1115 ReadTrack(cdr.SetSectorPlay);
1117 buf = CDR_getBuffer();
1121 if (cdr.RErr == 0) {
1122 CDR_LOG_I("cdrReadInterrupt() Log: err\n");
1123 memset(cdr.Transfer, 0, DATA_SIZE);
1124 cdr.Stat = DiskError;
1125 cdr.Result[0] |= STATUS_ERROR;
1126 CDREAD_INT((cdr.Mode & 0x80) ? (cdReadTime / 2) : cdReadTime);
1130 memcpy(cdr.Transfer, buf, DATA_SIZE);
1131 CheckPPFCache(cdr.Transfer, cdr.Prev[0], cdr.Prev[1], cdr.Prev[2]);
1134 CDR_LOG("cdrReadInterrupt() Log: cdr.Transfer %x:%x:%x\n", cdr.Transfer[0], cdr.Transfer[1], cdr.Transfer[2]);
1136 if ((!cdr.Muted) && (cdr.Mode & MODE_STRSND) && (!Config.Xa) && (cdr.FirstSector != -1)) { // CD-XA
1137 // Firemen 2: Multi-XA files - briefings, cutscenes
1138 if( cdr.FirstSector == 1 && (cdr.Mode & MODE_SF)==0 ) {
1139 cdr.File = cdr.Transfer[4 + 0];
1140 cdr.Channel = cdr.Transfer[4 + 1];
1144 * Skips playing on channel 255.
1145 * Fixes missing audio in Blue's Clues : Blue's Big Musical. (Should also fix Taxi 2)
1146 * TODO : Check if this is the proper behaviour.
1148 if((cdr.Transfer[4 + 2] & 0x4) &&
1149 (cdr.Transfer[4 + 1] == cdr.Channel) &&
1150 (cdr.Transfer[4 + 0] == cdr.File) && cdr.Channel != 255) {
1151 int ret = xa_decode_sector(&cdr.Xa, cdr.Transfer+4, cdr.FirstSector);
1153 cdrAttenuate(cdr.Xa.pcm, cdr.Xa.nsamples, cdr.Xa.stereo);
1154 SPU_playADPCMchannel(&cdr.Xa);
1155 cdr.FirstSector = 0;
1157 else cdr.FirstSector = -1;
1161 cdr.SetSectorPlay[2]++;
1162 if (cdr.SetSectorPlay[2] == 75) {
1163 cdr.SetSectorPlay[2] = 0;
1164 cdr.SetSectorPlay[1]++;
1165 if (cdr.SetSectorPlay[1] == 60) {
1166 cdr.SetSectorPlay[1] = 0;
1167 cdr.SetSectorPlay[0]++;
1173 uint32_t delay = (cdr.Mode & MODE_SPEED) ? (cdReadTime / 2) : cdReadTime;
1174 if (cdr.m_locationChanged) {
1175 CDREAD_INT(delay * 30);
1176 cdr.m_locationChanged = FALSE;
1182 Croc 2: $40 - only FORM1 (*)
1183 Judge Dredd: $C8 - only FORM1 (*)
1184 Sim Theme Park - no adpcm at all (zero)
1187 if (!(cdr.Mode & MODE_STRSND) || !(cdr.Transfer[4+2] & 0x4)) {
1188 cdr.Stat = DataReady;
1192 // update for CdlGetlocP
1193 ReadTrack(cdr.SetSectorPlay);
1202 bit 5 - 1 result ready
1204 bit 7 - 1 command being processed
1207 unsigned char cdrRead0(void) {
1208 if (cdr.ResultReady)
1216 // cdr.Ctrl &= ~0x40;
1218 // What means the 0x10 and the 0x08 bits? I only saw it used by the bios
1221 CDR_LOG_IO("cdr r0: %02x\n", cdr.Ctrl);
1223 return psxHu8(0x1800) = cdr.Ctrl;
1226 void cdrWrite0(unsigned char rt) {
1227 CDR_LOG_IO("cdr w0: %02x\n", rt);
1229 cdr.Ctrl = (rt & 3) | (cdr.Ctrl & ~3);
1232 unsigned char cdrRead1(void) {
1233 if ((cdr.ResultP & 0xf) < cdr.ResultC)
1234 psxHu8(0x1801) = cdr.Result[cdr.ResultP & 0xf];
1238 if (cdr.ResultP == cdr.ResultC)
1239 cdr.ResultReady = 0;
1241 CDR_LOG_IO("cdr r1: %02x\n", psxHu8(0x1801));
1243 return psxHu8(0x1801);
1246 void cdrWrite1(unsigned char rt) {
1250 CDR_LOG_IO("cdr w1: %02x\n", rt);
1252 switch (cdr.Ctrl & 3) {
1256 cdr.AttenuatorRightToRightT = rt;
1265 #ifdef CDR_LOG_CMD_IRQ
1266 SysPrintf("CD1 write: %x (%s)", rt, CmdName[rt]);
1268 SysPrintf(" Param[%d] = {", cdr.ParamC);
1269 for (i = 0; i < cdr.ParamC; i++)
1270 SysPrintf(" %x,", cdr.Param[i]);
1277 cdr.ResultReady = 0;
1279 // cdr.Stat = NoIntr;
1280 AddIrqQueue(cdr.Cmd, 0x800);
1284 CDR_LOG("CDROM setloc command (%02X, %02X, %02X)\n", cdr.Param[0], cdr.Param[1], cdr.Param[2]);
1286 // MM must be BCD, SS must be BCD and <0x60, FF must be BCD and <0x75
1287 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))
1289 CDR_LOG("Invalid/out of range seek to %02X:%02X:%02X\n", cdr.Param[0], cdr.Param[1], cdr.Param[2]);
1293 for (i = 0; i < 3; i++)
1295 set_loc[i] = btoi(cdr.Param[i]);
1298 i = msf2sec(cdr.SetSectorPlay);
1299 i = abs(i - msf2sec(set_loc));
1301 cdr.Seeked = SEEK_PENDING;
1303 memcpy(cdr.SetSector, set_loc, 3);
1304 cdr.SetSector[3] = 0;
1305 cdr.SetlocPending = 1;
1318 cdr.Seeked = SEEK_DONE;
1324 CDR_LOG("cdrWrite1() Log: Setmode %x\n", cdr.Param[0]);
1326 cdr.Mode = cdr.Param[0];
1328 // Squaresoft on PlayStation 1998 Collector's CD Vol. 1
1329 // - fixes choppy movie sound
1330 if( cdr.Play && (cdr.Mode & MODE_CDDA) == 0 )
1336 unsigned char cdrRead2(void) {
1339 if (cdr.Readed == 0) {
1345 CDR_LOG_IO("cdr r2: %02x\n", ret);
1349 void cdrWrite2(unsigned char rt) {
1350 CDR_LOG_IO("cdr w2: %02x\n", rt);
1352 switch (cdr.Ctrl & 3) {
1354 if (cdr.ParamC < 8) // FIXME: size and wrapping
1355 cdr.Param[cdr.ParamC++] = rt;
1362 cdr.AttenuatorLeftToLeftT = rt;
1365 cdr.AttenuatorRightToLeftT = rt;
1370 unsigned char cdrRead3(void) {
1372 psxHu8(0x1803) = cdr.Stat | 0xE0;
1374 psxHu8(0x1803) = cdr.Reg2 | 0xE0;
1376 CDR_LOG_IO("cdr r3: %02x\n", psxHu8(0x1803));
1377 return psxHu8(0x1803);
1380 void cdrWrite3(unsigned char rt) {
1381 CDR_LOG_IO("cdr w3: %02x\n", rt);
1383 switch (cdr.Ctrl & 3) {
1393 cdr.AttenuatorLeftToRightT = rt;
1397 memcpy(&cdr.AttenuatorLeftToLeft, &cdr.AttenuatorLeftToLeftT, 4);
1398 CDR_LOG_I("CD-XA Volume: %02x %02x | %02x %02x\n",
1399 cdr.AttenuatorLeftToLeft, cdr.AttenuatorLeftToRight,
1400 cdr.AttenuatorRightToLeft, cdr.AttenuatorRightToRight);
1405 if ((rt & 0x80) && cdr.Readed == 0) {
1407 pTransfer = cdr.Transfer;
1409 switch (cdr.Mode & 0x30) {
1410 case MODE_SIZE_2328:
1415 case MODE_SIZE_2340:
1425 void psxDma3(u32 madr, u32 bcr, u32 chcr) {
1430 CDR_LOG("psxDma3() Log: *** DMA 3 *** %x addr = %x size = %x\n", chcr, madr, bcr);
1435 if (cdr.Readed == 0) {
1436 CDR_LOG("psxDma3() Log: *** DMA 3 *** NOT READY\n");
1440 cdsize = (bcr & 0xffff) * 4;
1442 // Ape Escape: bcr = 0001 / 0000
1446 switch (cdr.Mode & (MODE_SIZE_2340|MODE_SIZE_2328)) {
1447 case MODE_SIZE_2340: cdsize = 2340; break;
1448 case MODE_SIZE_2328: cdsize = 2328; break;
1450 case MODE_SIZE_2048: cdsize = 2048; break;
1455 ptr = (u8 *)PSXM(madr);
1457 CDR_LOG("psxDma3() Log: *** DMA 3 *** NULL Pointer!\n");
1462 GS CDX: Enhancement CD crash
1465 - Spams DMA3 and gets buffer overrun
1467 size = CD_FRAMESIZE_RAW - (pTransfer - cdr.Transfer);
1472 memcpy(ptr, pTransfer, size);
1475 psxCpu->Clear(madr, cdsize / 4);
1476 pTransfer += cdsize;
1478 if( chcr == 0x11400100 ) {
1479 HW_DMA3_MADR = SWAPu32(madr + cdsize);
1480 CDRDMA_INT( (cdsize/4) / 4 );
1482 else if( chcr == 0x11000000 ) {
1483 // CDRDMA_INT( (cdsize/4) * 1 );
1485 psxRegs.cycle += (cdsize/4) * 24/2;
1491 CDR_LOG("psxDma3() Log: Unknown cddma %x\n", chcr);
1495 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1499 void cdrDmaInterrupt()
1501 if (HW_DMA3_CHCR & SWAP32(0x01000000))
1503 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1508 static void getCdInfo(void)
1512 CDR_getTN(cdr.ResultTN);
1513 CDR_getTD(0, cdr.SetSectorEnd);
1514 tmp = cdr.SetSectorEnd[0];
1515 cdr.SetSectorEnd[0] = cdr.SetSectorEnd[2];
1516 cdr.SetSectorEnd[2] = tmp;
1520 memset(&cdr, 0, sizeof(cdr));
1526 cdr.DriveState = DRIVESTATE_STANDBY;
1527 cdr.StatP = STATUS_ROTATING;
1528 pTransfer = cdr.Transfer;
1529 cdr.SetlocPending = 0;
1530 cdr.m_locationChanged = FALSE;
1532 // BIOS player - default values
1533 cdr.AttenuatorLeftToLeft = 0x80;
1534 cdr.AttenuatorLeftToRight = 0x00;
1535 cdr.AttenuatorRightToLeft = 0x00;
1536 cdr.AttenuatorRightToRight = 0x80;
1541 int cdrFreeze(void *f, int Mode) {
1545 if (Mode == 0 && !Config.Cdda)
1548 cdr.freeze_ver = 0x63647202;
1549 gzfreeze(&cdr, sizeof(cdr));
1552 cdr.ParamP = cdr.ParamC;
1553 tmp = pTransfer - cdr.Transfer;
1556 gzfreeze(&tmp, sizeof(tmp));
1561 pTransfer = cdr.Transfer + tmp;
1563 // read right sub data
1564 tmpp[0] = btoi(cdr.Prev[0]);
1565 tmpp[1] = btoi(cdr.Prev[1]);
1566 tmpp[2] = btoi(cdr.Prev[2]);
1571 if (cdr.freeze_ver < 0x63647202)
1572 memcpy(cdr.SetSectorPlay, cdr.SetSector, 3);
1574 Find_CurTrack(cdr.SetSectorPlay);
1576 CDR_play(cdr.SetSectorPlay);
1579 if ((cdr.freeze_ver & 0xffffff00) != 0x63647200) {
1580 // old versions did not latch Reg2, have to fixup..
1581 if (cdr.Reg2 == 0) {
1582 SysPrintf("cdrom: fixing up old savestate\n");
1585 // also did not save Attenuator..
1586 if ((cdr.AttenuatorLeftToLeft | cdr.AttenuatorLeftToRight
1587 | cdr.AttenuatorRightToLeft | cdr.AttenuatorRightToRight) == 0)
1589 cdr.AttenuatorLeftToLeft = cdr.AttenuatorRightToRight = 0x80;
1597 void LidInterrupt() {
1600 cdrLidSeekInterrupt();