eliminate event code duplication
[pcsx_rearmed.git] / libpcsxcore / cdrom.c
... / ...
CommitLineData
1/***************************************************************************
2 * Copyright (C) 2007 Ryan Schultz, PCSX-df Team, PCSX team *
3 * *
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. *
8 * *
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. *
13 * *
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 ***************************************************************************/
19
20/*
21* Handles all CD-ROM registers and functions.
22*/
23
24#include <assert.h>
25#include "cdrom.h"
26#include "misc.h"
27#include "ppf.h"
28#include "psxdma.h"
29#include "psxevents.h"
30#include "arm_features.h"
31
32/* logging */
33#if 0
34#define CDR_LOG SysPrintf
35#else
36#define CDR_LOG(...)
37#endif
38#if 0
39#define CDR_LOG_I SysPrintf
40#else
41#define CDR_LOG_I(fmt, ...) \
42 log_unhandled("%u cdrom: " fmt, psxRegs.cycle, ##__VA_ARGS__)
43#endif
44#if 0
45#define CDR_LOG_IO SysPrintf
46#else
47#define CDR_LOG_IO(...)
48#endif
49//#define CDR_LOG_CMD_IRQ
50
51static struct {
52 // unused members maintain savesate compatibility
53 unsigned char unused0;
54 unsigned char unused1;
55 unsigned char Reg2;
56 unsigned char unused2;
57 unsigned char Ctrl;
58 unsigned char Stat;
59
60 unsigned char StatP;
61
62 unsigned char Transfer[DATA_SIZE];
63 struct {
64 unsigned char Track;
65 unsigned char Index;
66 unsigned char Relative[3];
67 unsigned char Absolute[3];
68 } subq;
69 unsigned char TrackChanged;
70 unsigned char ReportDelay;
71 unsigned char unused3;
72 unsigned short sectorsRead;
73 unsigned int freeze_ver;
74
75 unsigned char Prev[4];
76 unsigned char Param[8];
77 unsigned char Result[16];
78
79 unsigned char ParamC;
80 unsigned char ParamP;
81 unsigned char ResultC;
82 unsigned char ResultP;
83 unsigned char ResultReady;
84 unsigned char Cmd;
85 unsigned char SubqForwardSectors;
86 unsigned char SetlocPending;
87 u32 Reading;
88
89 unsigned char ResultTN[6];
90 unsigned char ResultTD[4];
91 unsigned char SetSectorPlay[4];
92 unsigned char SetSectorEnd[4];
93 unsigned char SetSector[4];
94 unsigned char Track;
95 boolean Play, Muted;
96 int CurTrack;
97 int Mode, File, Channel;
98 unsigned char LocL[8];
99 int FirstSector;
100
101 xa_decode_t Xa;
102
103 u16 FifoOffset;
104 u16 FifoSize;
105
106 u16 CmdInProgress;
107 u8 Irq1Pending;
108 u8 unused5;
109 u32 LastReadCycles;
110
111 u8 unused7;
112
113 u8 DriveState;
114 u8 FastForward;
115 u8 FastBackward;
116 u8 errorRetryhack;
117
118 u8 AttenuatorLeftToLeft, AttenuatorLeftToRight;
119 u8 AttenuatorRightToRight, AttenuatorRightToLeft;
120 u8 AttenuatorLeftToLeftT, AttenuatorLeftToRightT;
121 u8 AttenuatorRightToRightT, AttenuatorRightToLeftT;
122} cdr;
123static s16 read_buf[CD_FRAMESIZE_RAW/2];
124
125/* CD-ROM magic numbers */
126#define CdlSync 0 /* nocash documentation : "Uh, actually, returns error code 40h = Invalid Command...?" */
127#define CdlNop 1
128#define CdlSetloc 2
129#define CdlPlay 3
130#define CdlForward 4
131#define CdlBackward 5
132#define CdlReadN 6
133#define CdlStandby 7
134#define CdlStop 8
135#define CdlPause 9
136#define CdlReset 10
137#define CdlMute 11
138#define CdlDemute 12
139#define CdlSetfilter 13
140#define CdlSetmode 14
141#define CdlGetparam 15
142#define CdlGetlocL 16
143#define CdlGetlocP 17
144#define CdlReadT 18
145#define CdlGetTN 19
146#define CdlGetTD 20
147#define CdlSeekL 21
148#define CdlSeekP 22
149#define CdlSetclock 23
150#define CdlGetclock 24
151#define CdlTest 25
152#define CdlID 26
153#define CdlReadS 27
154#define CdlInit 28
155#define CdlGetQ 29
156#define CdlReadToc 30
157
158#ifdef CDR_LOG_CMD_IRQ
159static const char * const CmdName[0x100] = {
160 "CdlSync", "CdlNop", "CdlSetloc", "CdlPlay",
161 "CdlForward", "CdlBackward", "CdlReadN", "CdlStandby",
162 "CdlStop", "CdlPause", "CdlReset", "CdlMute",
163 "CdlDemute", "CdlSetfilter", "CdlSetmode", "CdlGetparam",
164 "CdlGetlocL", "CdlGetlocP", "CdlReadT", "CdlGetTN",
165 "CdlGetTD", "CdlSeekL", "CdlSeekP", "CdlSetclock",
166 "CdlGetclock", "CdlTest", "CdlID", "CdlReadS",
167 "CdlInit", NULL, "CDlReadToc", NULL
168};
169#endif
170
171unsigned char Test04[] = { 0 };
172unsigned char Test05[] = { 0 };
173unsigned char Test20[] = { 0x98, 0x06, 0x10, 0xC3 };
174unsigned char Test22[] = { 0x66, 0x6F, 0x72, 0x20, 0x45, 0x75, 0x72, 0x6F };
175unsigned char Test23[] = { 0x43, 0x58, 0x44, 0x32, 0x39 ,0x34, 0x30, 0x51 };
176
177// cdr.Stat:
178#define NoIntr 0
179#define DataReady 1
180#define Complete 2
181#define Acknowledge 3
182#define DataEnd 4
183#define DiskError 5
184
185/* Modes flags */
186#define MODE_SPEED (1<<7) // 0x80
187#define MODE_STRSND (1<<6) // 0x40 ADPCM on/off
188#define MODE_SIZE_2340 (1<<5) // 0x20
189#define MODE_SIZE_2328 (1<<4) // 0x10
190#define MODE_SIZE_2048 (0<<4) // 0x00
191#define MODE_SF (1<<3) // 0x08 channel on/off
192#define MODE_REPORT (1<<2) // 0x04
193#define MODE_AUTOPAUSE (1<<1) // 0x02
194#define MODE_CDDA (1<<0) // 0x01
195
196/* Status flags */
197#define STATUS_PLAY (1<<7) // 0x80
198#define STATUS_SEEK (1<<6) // 0x40
199#define STATUS_READ (1<<5) // 0x20
200#define STATUS_SHELLOPEN (1<<4) // 0x10
201#define STATUS_UNKNOWN3 (1<<3) // 0x08
202#define STATUS_UNKNOWN2 (1<<2) // 0x04
203#define STATUS_ROTATING (1<<1) // 0x02
204#define STATUS_ERROR (1<<0) // 0x01
205
206/* Errors */
207#define ERROR_NOTREADY (1<<7) // 0x80
208#define ERROR_INVALIDCMD (1<<6) // 0x40
209#define ERROR_INVALIDARG (1<<5) // 0x20
210
211// 1x = 75 sectors per second
212// PSXCLK = 1 sec in the ps
213// so (PSXCLK / 75) = cdr read time (linuzappz)
214#define cdReadTime (PSXCLK / 75)
215
216#define LOCL_INVALID 0xff
217#define SUBQ_FORWARD_SECTORS 2u
218
219enum drive_state {
220 DRIVESTATE_STANDBY = 0, // pause, play, read
221 DRIVESTATE_LID_OPEN,
222 DRIVESTATE_RESCAN_CD,
223 DRIVESTATE_PREPARE_CD,
224 DRIVESTATE_STOPPED,
225};
226
227static struct CdrStat stat;
228
229static unsigned int msf2sec(const u8 *msf) {
230 return ((msf[0] * 60 + msf[1]) * 75) + msf[2];
231}
232
233// for that weird psemu API..
234static unsigned int fsm2sec(const u8 *msf) {
235 return ((msf[2] * 60 + msf[1]) * 75) + msf[0];
236}
237
238static void sec2msf(unsigned int s, u8 *msf) {
239 msf[0] = s / 75 / 60;
240 s = s - msf[0] * 75 * 60;
241 msf[1] = s / 75;
242 s = s - msf[1] * 75;
243 msf[2] = s;
244}
245
246// cdrPlayReadInterrupt
247#define CDRPLAYREAD_INT(eCycle, isFirst) { \
248 u32 e_ = eCycle; \
249 psxRegs.interrupt |= (1 << PSXINT_CDREAD); \
250 if (isFirst) \
251 psxRegs.intCycle[PSXINT_CDREAD].sCycle = psxRegs.cycle; \
252 else \
253 psxRegs.intCycle[PSXINT_CDREAD].sCycle += psxRegs.intCycle[PSXINT_CDREAD].cycle; \
254 psxRegs.intCycle[PSXINT_CDREAD].cycle = e_; \
255 set_event_raw_abs(PSXINT_CDREAD, psxRegs.intCycle[PSXINT_CDREAD].sCycle + e_); \
256}
257
258#define StopReading() { \
259 cdr.Reading = 0; \
260 psxRegs.interrupt &= ~(1 << PSXINT_CDREAD); \
261}
262
263#define StopCdda() { \
264 if (cdr.Play && !Config.Cdda) CDR_stop(); \
265 cdr.Play = FALSE; \
266 cdr.FastForward = 0; \
267 cdr.FastBackward = 0; \
268}
269
270#define SetPlaySeekRead(x, f) { \
271 x &= ~(STATUS_PLAY | STATUS_SEEK | STATUS_READ); \
272 x |= f; \
273}
274
275#define SetResultSize(size) { \
276 cdr.ResultP = 0; \
277 cdr.ResultC = size; \
278 cdr.ResultReady = 1; \
279}
280
281static void setIrq(int log_cmd)
282{
283 if (cdr.Stat & cdr.Reg2)
284 psxHu32ref(0x1070) |= SWAP32((u32)0x4);
285
286#ifdef CDR_LOG_CMD_IRQ
287 if (cdr.Stat)
288 {
289 int i;
290 CDR_LOG_I("CDR IRQ=%d cmd %02x stat %02x: ",
291 !!(cdr.Stat & cdr.Reg2), log_cmd, cdr.Stat);
292 for (i = 0; i < cdr.ResultC; i++)
293 SysPrintf("%02x ", cdr.Result[i]);
294 SysPrintf("\n");
295 }
296#endif
297}
298
299// timing used in this function was taken from tests on real hardware
300// (yes it's slow, but you probably don't want to modify it)
301void cdrLidSeekInterrupt(void)
302{
303 CDR_LOG_I("%s cdr.DriveState=%d\n", __func__, cdr.DriveState);
304
305 switch (cdr.DriveState) {
306 default:
307 case DRIVESTATE_STANDBY:
308 StopCdda();
309 StopReading();
310 SetPlaySeekRead(cdr.StatP, 0);
311
312 if (CDR_getStatus(&stat) == -1)
313 return;
314
315 if (stat.Status & STATUS_SHELLOPEN)
316 {
317 memset(cdr.Prev, 0xff, sizeof(cdr.Prev));
318 cdr.DriveState = DRIVESTATE_LID_OPEN;
319 set_event(PSXINT_CDRLID, 0x800);
320 }
321 break;
322
323 case DRIVESTATE_LID_OPEN:
324 if (CDR_getStatus(&stat) == -1)
325 stat.Status &= ~STATUS_SHELLOPEN;
326
327 // 02, 12, 10
328 if (!(cdr.StatP & STATUS_SHELLOPEN)) {
329 cdr.StatP |= STATUS_SHELLOPEN;
330
331 // could generate error irq here, but real hardware
332 // only sometimes does that
333 // (not done when lots of commands are sent?)
334
335 set_event(PSXINT_CDRLID, cdReadTime * 30);
336 break;
337 }
338 else if (cdr.StatP & STATUS_ROTATING) {
339 cdr.StatP &= ~STATUS_ROTATING;
340 }
341 else if (!(stat.Status & STATUS_SHELLOPEN)) {
342 // closed now
343 CheckCdrom();
344
345 // cdr.StatP STATUS_SHELLOPEN is "sticky"
346 // and is only cleared by CdlNop
347
348 cdr.DriveState = DRIVESTATE_RESCAN_CD;
349 set_event(PSXINT_CDRLID, cdReadTime * 105);
350 break;
351 }
352
353 // recheck for close
354 set_event(PSXINT_CDRLID, cdReadTime * 3);
355 break;
356
357 case DRIVESTATE_RESCAN_CD:
358 cdr.StatP |= STATUS_ROTATING;
359 cdr.DriveState = DRIVESTATE_PREPARE_CD;
360
361 // this is very long on real hardware, over 6 seconds
362 // make it a bit faster here...
363 set_event(PSXINT_CDRLID, cdReadTime * 150);
364 break;
365
366 case DRIVESTATE_PREPARE_CD:
367 if (cdr.StatP & STATUS_SEEK) {
368 SetPlaySeekRead(cdr.StatP, 0);
369 cdr.DriveState = DRIVESTATE_STANDBY;
370 }
371 else {
372 SetPlaySeekRead(cdr.StatP, STATUS_SEEK);
373 set_event(PSXINT_CDRLID, cdReadTime * 26);
374 }
375 break;
376 }
377}
378
379static void Find_CurTrack(const u8 *time)
380{
381 int current, sect;
382
383 current = msf2sec(time);
384
385 for (cdr.CurTrack = 1; cdr.CurTrack < cdr.ResultTN[1]; cdr.CurTrack++) {
386 CDR_getTD(cdr.CurTrack + 1, cdr.ResultTD);
387 sect = fsm2sec(cdr.ResultTD);
388 if (sect - current >= 150)
389 break;
390 }
391}
392
393static void generate_subq(const u8 *time)
394{
395 unsigned char start[3], next[3];
396 unsigned int this_s, start_s, next_s, pregap;
397 int relative_s;
398
399 CDR_getTD(cdr.CurTrack, start);
400 if (cdr.CurTrack + 1 <= cdr.ResultTN[1]) {
401 pregap = 150;
402 CDR_getTD(cdr.CurTrack + 1, next);
403 }
404 else {
405 // last track - cd size
406 pregap = 0;
407 next[0] = cdr.SetSectorEnd[2];
408 next[1] = cdr.SetSectorEnd[1];
409 next[2] = cdr.SetSectorEnd[0];
410 }
411
412 this_s = msf2sec(time);
413 start_s = fsm2sec(start);
414 next_s = fsm2sec(next);
415
416 cdr.TrackChanged = FALSE;
417
418 if (next_s - this_s < pregap) {
419 cdr.TrackChanged = TRUE;
420 cdr.CurTrack++;
421 start_s = next_s;
422 }
423
424 cdr.subq.Index = 1;
425
426 relative_s = this_s - start_s;
427 if (relative_s < 0) {
428 cdr.subq.Index = 0;
429 relative_s = -relative_s;
430 }
431 sec2msf(relative_s, cdr.subq.Relative);
432
433 cdr.subq.Track = itob(cdr.CurTrack);
434 cdr.subq.Relative[0] = itob(cdr.subq.Relative[0]);
435 cdr.subq.Relative[1] = itob(cdr.subq.Relative[1]);
436 cdr.subq.Relative[2] = itob(cdr.subq.Relative[2]);
437 cdr.subq.Absolute[0] = itob(time[0]);
438 cdr.subq.Absolute[1] = itob(time[1]);
439 cdr.subq.Absolute[2] = itob(time[2]);
440}
441
442static int ReadTrack(const u8 *time)
443{
444 unsigned char tmp[3];
445 int read_ok;
446
447 tmp[0] = itob(time[0]);
448 tmp[1] = itob(time[1]);
449 tmp[2] = itob(time[2]);
450
451 if (memcmp(cdr.Prev, tmp, 3) == 0)
452 return 1;
453
454 CDR_LOG("ReadTrack *** %02x:%02x:%02x\n", tmp[0], tmp[1], tmp[2]);
455
456 read_ok = CDR_readTrack(tmp);
457 if (read_ok)
458 memcpy(cdr.Prev, tmp, 3);
459 return read_ok;
460}
461
462static void UpdateSubq(const u8 *time)
463{
464 const struct SubQ *subq;
465 u16 crc;
466
467 if (CheckSBI(time))
468 return;
469
470 subq = (struct SubQ *)CDR_getBufferSub(MSF2SECT(time[0], time[1], time[2]));
471 if (subq != NULL && cdr.CurTrack == 1) {
472 crc = calcCrc((u8 *)subq + 12, 10);
473 if (crc == (((u16)subq->CRC[0] << 8) | subq->CRC[1])) {
474 cdr.subq.Track = subq->TrackNumber;
475 cdr.subq.Index = subq->IndexNumber;
476 memcpy(cdr.subq.Relative, subq->TrackRelativeAddress, 3);
477 memcpy(cdr.subq.Absolute, subq->AbsoluteAddress, 3);
478 }
479 else {
480 CDR_LOG_I("subq bad crc @%02d:%02d:%02d\n",
481 time[0], time[1], time[2]);
482 }
483 }
484 else {
485 generate_subq(time);
486 }
487
488 CDR_LOG(" -> %02x,%02x %02x:%02x:%02x %02x:%02x:%02x\n",
489 cdr.subq.Track, cdr.subq.Index,
490 cdr.subq.Relative[0], cdr.subq.Relative[1], cdr.subq.Relative[2],
491 cdr.subq.Absolute[0], cdr.subq.Absolute[1], cdr.subq.Absolute[2]);
492}
493
494static void cdrPlayInterrupt_Autopause()
495{
496 u32 abs_lev_max = 0;
497 boolean abs_lev_chselect;
498 u32 i;
499
500 if ((cdr.Mode & MODE_AUTOPAUSE) && cdr.TrackChanged) {
501 CDR_LOG( "CDDA STOP\n" );
502
503 // Magic the Gathering
504 // - looping territory cdda
505
506 // ...?
507 //cdr.ResultReady = 1;
508 //cdr.Stat = DataReady;
509 cdr.Stat = DataEnd;
510 setIrq(0x1000); // 0x1000 just for logging purposes
511
512 StopCdda();
513 SetPlaySeekRead(cdr.StatP, 0);
514 }
515 else if ((cdr.Mode & MODE_REPORT) && !cdr.ReportDelay &&
516 ((cdr.subq.Absolute[2] & 0x0f) == 0 || cdr.FastForward || cdr.FastBackward))
517 {
518 cdr.Result[0] = cdr.StatP;
519 cdr.Result[1] = cdr.subq.Track;
520 cdr.Result[2] = cdr.subq.Index;
521
522 abs_lev_chselect = cdr.subq.Absolute[1] & 0x01;
523
524 /* 8 is a hack. For accuracy, it should be 588. */
525 for (i = 0; i < 8; i++)
526 {
527 abs_lev_max = MAX_VALUE(abs_lev_max, abs(read_buf[i * 2 + abs_lev_chselect]));
528 }
529 abs_lev_max = MIN_VALUE(abs_lev_max, 32767);
530 abs_lev_max |= abs_lev_chselect << 15;
531
532 if (cdr.subq.Absolute[2] & 0x10) {
533 cdr.Result[3] = cdr.subq.Relative[0];
534 cdr.Result[4] = cdr.subq.Relative[1] | 0x80;
535 cdr.Result[5] = cdr.subq.Relative[2];
536 }
537 else {
538 cdr.Result[3] = cdr.subq.Absolute[0];
539 cdr.Result[4] = cdr.subq.Absolute[1];
540 cdr.Result[5] = cdr.subq.Absolute[2];
541 }
542
543 cdr.Result[6] = abs_lev_max >> 0;
544 cdr.Result[7] = abs_lev_max >> 8;
545
546 // Rayman: Logo freeze (resultready + dataready)
547 cdr.ResultReady = 1;
548 cdr.Stat = DataReady;
549
550 SetResultSize(8);
551 setIrq(0x1001);
552 }
553
554 if (cdr.ReportDelay)
555 cdr.ReportDelay--;
556}
557
558// LastReadCycles
559static int cdrSeekTime(unsigned char *target)
560{
561 int diff = msf2sec(cdr.SetSectorPlay) - msf2sec(target);
562 int pausePenalty, seekTime = abs(diff) * (cdReadTime / 2000);
563 seekTime = MAX_VALUE(seekTime, 20000);
564
565 // need this stupidly long penalty or else Spyro2 intro desyncs
566 pausePenalty = (s32)(psxRegs.cycle - cdr.LastReadCycles) > cdReadTime * 8 ? cdReadTime * 25 : 0;
567 seekTime += pausePenalty;
568
569 seekTime = MIN_VALUE(seekTime, PSXCLK * 2 / 3);
570 CDR_LOG("seek: %.2f %.2f\n", (float)seekTime / PSXCLK, (float)seekTime / cdReadTime);
571 return seekTime;
572}
573
574static u32 cdrAlignTimingHack(u32 cycles)
575{
576 /*
577 * timing hack for T'ai Fu - Wrath of the Tiger:
578 * The game has a bug where it issues some cdc commands from a low priority
579 * vint handler, however there is a higher priority default bios handler
580 * that acks the vint irq and returns, so game's handler is not reached
581 * (see bios irq handler chains at e004 and the game's irq handling func
582 * at 80036810). For the game to work, vint has to arrive after the bios
583 * vint handler rejects some other irq (of which only cd and rcnt2 are
584 * active), but before the game's handler loop reads I_STAT. The time
585 * window for this is quite small (~1k cycles of so). Apparently this
586 * somehow happens naturally on the real hardware.
587 *
588 * Note: always enforcing this breaks other games like Crash PAL version
589 * (inputs get dropped because bios handler doesn't see interrupts).
590 */
591 u32 vint_rel;
592 if (psxRegs.cycle - rcnts[3].cycleStart > 250000)
593 return cycles;
594 vint_rel = rcnts[3].cycleStart + 63000 - psxRegs.cycle;
595 vint_rel += PSXCLK / 60;
596 while ((s32)(vint_rel - cycles) < 0)
597 vint_rel += PSXCLK / 60;
598 return vint_rel;
599}
600
601static void cdrUpdateTransferBuf(const u8 *buf);
602static void cdrReadInterrupt(void);
603static void cdrPrepCdda(s16 *buf, int samples);
604static void cdrAttenuate(s16 *buf, int samples, int stereo);
605
606static void msfiAdd(u8 *msfi, u32 count)
607{
608 assert(count < 75);
609 msfi[2] += count;
610 if (msfi[2] >= 75) {
611 msfi[2] -= 75;
612 msfi[1]++;
613 if (msfi[1] == 60) {
614 msfi[1] = 0;
615 msfi[0]++;
616 }
617 }
618}
619
620static void msfiSub(u8 *msfi, u32 count)
621{
622 assert(count < 75);
623 msfi[2] -= count;
624 if ((s8)msfi[2] < 0) {
625 msfi[2] += 75;
626 msfi[1]--;
627 if ((s8)msfi[1] < 0) {
628 msfi[1] = 60;
629 msfi[0]--;
630 }
631 }
632}
633
634void cdrPlayReadInterrupt(void)
635{
636 cdr.LastReadCycles = psxRegs.cycle;
637
638 if (cdr.Reading) {
639 cdrReadInterrupt();
640 return;
641 }
642
643 if (!cdr.Play) return;
644
645 CDR_LOG( "CDDA - %d:%d:%d\n",
646 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2] );
647
648 SetPlaySeekRead(cdr.StatP, STATUS_PLAY);
649 if (memcmp(cdr.SetSectorPlay, cdr.SetSectorEnd, 3) == 0) {
650 StopCdda();
651 SetPlaySeekRead(cdr.StatP, 0);
652 cdr.TrackChanged = TRUE;
653 }
654 else {
655 CDR_readCDDA(cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2], (u8 *)read_buf);
656 }
657
658 if (!cdr.Stat && (cdr.Mode & (MODE_AUTOPAUSE|MODE_REPORT)))
659 cdrPlayInterrupt_Autopause();
660
661 if (!cdr.Muted && !Config.Cdda) {
662 cdrPrepCdda(read_buf, CD_FRAMESIZE_RAW / 4);
663 cdrAttenuate(read_buf, CD_FRAMESIZE_RAW / 4, 1);
664 SPU_playCDDAchannel(read_buf, CD_FRAMESIZE_RAW, psxRegs.cycle, cdr.FirstSector);
665 cdr.FirstSector = 0;
666 }
667
668 msfiAdd(cdr.SetSectorPlay, 1);
669
670 // update for CdlGetlocP/autopause
671 generate_subq(cdr.SetSectorPlay);
672
673 CDRPLAYREAD_INT(cdReadTime, 0);
674}
675
676#define CMD_PART2 0x100
677#define CMD_WHILE_NOT_READY 0x200
678
679void cdrInterrupt(void) {
680 int start_rotating = 0;
681 int error = 0;
682 u32 cycles, seekTime = 0;
683 u32 second_resp_time = 0;
684 const void *buf;
685 u8 ParamC;
686 u8 set_loc[3];
687 int read_ok;
688 u16 not_ready = 0;
689 u16 Cmd;
690 int i;
691
692 if (cdr.Stat) {
693 CDR_LOG_I("cmd %02x with irqstat %x\n",
694 cdr.CmdInProgress, cdr.Stat);
695 return;
696 }
697 if (cdr.Irq1Pending) {
698 // hand out the "newest" sector, according to nocash
699 cdrUpdateTransferBuf(CDR_getBuffer());
700 CDR_LOG_I("%x:%02x:%02x loaded on ack, cmd=%02x res=%02x\n",
701 cdr.Transfer[0], cdr.Transfer[1], cdr.Transfer[2],
702 cdr.CmdInProgress, cdr.Irq1Pending);
703 SetResultSize(1);
704 cdr.Result[0] = cdr.Irq1Pending;
705 cdr.Stat = (cdr.Irq1Pending & STATUS_ERROR) ? DiskError : DataReady;
706 cdr.Irq1Pending = 0;
707 setIrq(0x1003);
708 return;
709 }
710
711 // default response
712 SetResultSize(1);
713 cdr.Result[0] = cdr.StatP;
714 cdr.Stat = Acknowledge;
715
716 Cmd = cdr.CmdInProgress;
717 cdr.CmdInProgress = 0;
718 ParamC = cdr.ParamC;
719
720 if (Cmd < 0x100) {
721 cdr.Ctrl &= ~0x80;
722 cdr.ParamC = 0;
723 cdr.Cmd = 0;
724 }
725
726 switch (cdr.DriveState) {
727 case DRIVESTATE_PREPARE_CD:
728 if (Cmd > 2) {
729 // Syphon filter 2 expects commands to work shortly after it sees
730 // STATUS_ROTATING, so give up trying to emulate the startup seq
731 cdr.DriveState = DRIVESTATE_STANDBY;
732 cdr.StatP &= ~STATUS_SEEK;
733 psxRegs.interrupt &= ~(1 << PSXINT_CDRLID);
734 break;
735 }
736 // fallthrough
737 case DRIVESTATE_LID_OPEN:
738 case DRIVESTATE_RESCAN_CD:
739 // no disk or busy with the initial scan, allowed cmds are limited
740 not_ready = CMD_WHILE_NOT_READY;
741 break;
742 }
743
744 switch (Cmd | not_ready) {
745 case CdlNop:
746 case CdlNop + CMD_WHILE_NOT_READY:
747 if (cdr.DriveState != DRIVESTATE_LID_OPEN)
748 cdr.StatP &= ~STATUS_SHELLOPEN;
749 break;
750
751 case CdlSetloc:
752 case CdlSetloc + CMD_WHILE_NOT_READY:
753 CDR_LOG("CDROM setloc command (%02X, %02X, %02X)\n", cdr.Param[0], cdr.Param[1], cdr.Param[2]);
754
755 // MM must be BCD, SS must be BCD and <0x60, FF must be BCD and <0x75
756 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))
757 {
758 CDR_LOG_I("Invalid/out of range seek to %02X:%02X:%02X\n", cdr.Param[0], cdr.Param[1], cdr.Param[2]);
759 if (++cdr.errorRetryhack > 100)
760 break;
761 error = ERROR_INVALIDARG;
762 goto set_error;
763 }
764 else
765 {
766 for (i = 0; i < 3; i++)
767 set_loc[i] = btoi(cdr.Param[i]);
768 memcpy(cdr.SetSector, set_loc, 3);
769 cdr.SetSector[3] = 0;
770 cdr.SetlocPending = 1;
771 cdr.errorRetryhack = 0;
772 }
773 break;
774
775 do_CdlPlay:
776 case CdlPlay:
777 StopCdda();
778 StopReading();
779
780 cdr.FastBackward = 0;
781 cdr.FastForward = 0;
782
783 // BIOS CD Player
784 // - Pause player, hit Track 01/02/../xx (Setloc issued!!)
785
786 if (ParamC != 0 && cdr.Param[0] != 0) {
787 int track = btoi( cdr.Param[0] );
788
789 if (track <= cdr.ResultTN[1])
790 cdr.CurTrack = track;
791
792 CDR_LOG("PLAY track %d\n", cdr.CurTrack);
793
794 if (CDR_getTD((u8)cdr.CurTrack, cdr.ResultTD) != -1) {
795 for (i = 0; i < 3; i++)
796 set_loc[i] = cdr.ResultTD[2 - i];
797 seekTime = cdrSeekTime(set_loc);
798 memcpy(cdr.SetSectorPlay, set_loc, 3);
799 }
800 }
801 else if (cdr.SetlocPending) {
802 seekTime = cdrSeekTime(cdr.SetSector);
803 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
804 }
805 else {
806 CDR_LOG("PLAY Resume @ %d:%d:%d\n",
807 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2]);
808 }
809 cdr.SetlocPending = 0;
810
811 /*
812 Rayman: detect track changes
813 - fixes logo freeze
814
815 Twisted Metal 2: skip PREGAP + starting accurate SubQ
816 - plays tracks without retry play
817
818 Wild 9: skip PREGAP + starting accurate SubQ
819 - plays tracks without retry play
820 */
821 Find_CurTrack(cdr.SetSectorPlay);
822 generate_subq(cdr.SetSectorPlay);
823 cdr.LocL[0] = LOCL_INVALID;
824 cdr.SubqForwardSectors = 1;
825 cdr.TrackChanged = FALSE;
826 cdr.FirstSector = 1;
827 cdr.ReportDelay = 60;
828 cdr.sectorsRead = 0;
829
830 if (!Config.Cdda)
831 CDR_play(cdr.SetSectorPlay);
832
833 SetPlaySeekRead(cdr.StatP, STATUS_SEEK | STATUS_ROTATING);
834
835 // BIOS player - set flag again
836 cdr.Play = TRUE;
837
838 CDRPLAYREAD_INT(cdReadTime + seekTime, 1);
839 start_rotating = 1;
840 break;
841
842 case CdlForward:
843 // TODO: error 80 if stopped
844 cdr.Stat = Complete;
845
846 // GameShark CD Player: Calls 2x + Play 2x
847 cdr.FastForward = 1;
848 cdr.FastBackward = 0;
849 break;
850
851 case CdlBackward:
852 cdr.Stat = Complete;
853
854 // GameShark CD Player: Calls 2x + Play 2x
855 cdr.FastBackward = 1;
856 cdr.FastForward = 0;
857 break;
858
859 case CdlStandby:
860 if (cdr.DriveState != DRIVESTATE_STOPPED) {
861 error = ERROR_INVALIDARG;
862 goto set_error;
863 }
864 second_resp_time = cdReadTime * 125 / 2;
865 start_rotating = 1;
866 break;
867
868 case CdlStandby + CMD_PART2:
869 cdr.Stat = Complete;
870 break;
871
872 case CdlStop:
873 if (cdr.Play) {
874 // grab time for current track
875 CDR_getTD((u8)(cdr.CurTrack), cdr.ResultTD);
876
877 cdr.SetSectorPlay[0] = cdr.ResultTD[2];
878 cdr.SetSectorPlay[1] = cdr.ResultTD[1];
879 cdr.SetSectorPlay[2] = cdr.ResultTD[0];
880 }
881
882 StopCdda();
883 StopReading();
884 SetPlaySeekRead(cdr.StatP, 0);
885 cdr.StatP &= ~STATUS_ROTATING;
886 cdr.LocL[0] = LOCL_INVALID;
887
888 second_resp_time = 0x800;
889 if (cdr.DriveState == DRIVESTATE_STANDBY)
890 second_resp_time = cdReadTime * 30 / 2;
891
892 cdr.DriveState = DRIVESTATE_STOPPED;
893 break;
894
895 case CdlStop + CMD_PART2:
896 cdr.Stat = Complete;
897 break;
898
899 case CdlPause:
900 StopCdda();
901 StopReading();
902
903 // how the drive maintains the position while paused is quite
904 // complicated, this is the minimum to make "Bedlam" happy
905 msfiSub(cdr.SetSectorPlay, MIN_VALUE(cdr.sectorsRead, 4));
906 cdr.sectorsRead = 0;
907
908 /*
909 Gundam Battle Assault 2: much slower (*)
910 - Fixes boot, gameplay
911
912 Hokuto no Ken 2: slower
913 - Fixes intro + subtitles
914
915 InuYasha - Feudal Fairy Tale: slower
916 - Fixes battles
917 */
918 /* Gameblabla - Tightening the timings (as taken from Duckstation).
919 * The timings from Duckstation are based upon hardware tests.
920 * Mednafen's timing don't work for Gundam Battle Assault 2 in PAL/50hz mode,
921 * seems to be timing sensitive as it can depend on the CPU's clock speed.
922 * */
923 if (!(cdr.StatP & (STATUS_PLAY | STATUS_READ)))
924 {
925 second_resp_time = 7000;
926 }
927 else
928 {
929 second_resp_time = (((cdr.Mode & MODE_SPEED) ? 1 : 2) * 1097107);
930 }
931 SetPlaySeekRead(cdr.StatP, 0);
932 break;
933
934 case CdlPause + CMD_PART2:
935 cdr.Stat = Complete;
936 break;
937
938 case CdlReset:
939 case CdlReset + CMD_WHILE_NOT_READY:
940 StopCdda();
941 StopReading();
942 SetPlaySeekRead(cdr.StatP, 0);
943 cdr.LocL[0] = LOCL_INVALID;
944 cdr.Muted = FALSE;
945 cdr.Mode = MODE_SIZE_2340; /* This fixes This is Football 2, Pooh's Party lockups */
946 second_resp_time = not_ready ? 70000 : 4100000;
947 start_rotating = 1;
948 break;
949
950 case CdlReset + CMD_PART2:
951 case CdlReset + CMD_PART2 + CMD_WHILE_NOT_READY:
952 cdr.Stat = Complete;
953 break;
954
955 case CdlMute:
956 cdr.Muted = TRUE;
957 break;
958
959 case CdlDemute:
960 cdr.Muted = FALSE;
961 break;
962
963 case CdlSetfilter:
964 cdr.File = cdr.Param[0];
965 cdr.Channel = cdr.Param[1];
966 break;
967
968 case CdlSetmode:
969 case CdlSetmode + CMD_WHILE_NOT_READY:
970 CDR_LOG("cdrWrite1() Log: Setmode %x\n", cdr.Param[0]);
971 cdr.Mode = cdr.Param[0];
972 break;
973
974 case CdlGetparam:
975 case CdlGetparam + CMD_WHILE_NOT_READY:
976 /* Gameblabla : According to mednafen, Result size should be 5 and done this way. */
977 SetResultSize(5);
978 cdr.Result[1] = cdr.Mode;
979 cdr.Result[2] = 0;
980 cdr.Result[3] = cdr.File;
981 cdr.Result[4] = cdr.Channel;
982 break;
983
984 case CdlGetlocL:
985 if (cdr.LocL[0] == LOCL_INVALID) {
986 error = 0x80;
987 goto set_error;
988 }
989 SetResultSize(8);
990 memcpy(cdr.Result, cdr.LocL, 8);
991 break;
992
993 case CdlGetlocP:
994 SetResultSize(8);
995 memcpy(&cdr.Result, &cdr.subq, 8);
996 break;
997
998 case CdlReadT: // SetSession?
999 // really long
1000 second_resp_time = cdReadTime * 290 / 4;
1001 start_rotating = 1;
1002 break;
1003
1004 case CdlReadT + CMD_PART2:
1005 cdr.Stat = Complete;
1006 break;
1007
1008 case CdlGetTN:
1009 SetResultSize(3);
1010 if (CDR_getTN(cdr.ResultTN) == -1) {
1011 cdr.Stat = DiskError;
1012 cdr.Result[0] |= STATUS_ERROR;
1013 } else {
1014 cdr.Stat = Acknowledge;
1015 cdr.Result[1] = itob(cdr.ResultTN[0]);
1016 cdr.Result[2] = itob(cdr.ResultTN[1]);
1017 }
1018 break;
1019
1020 case CdlGetTD:
1021 cdr.Track = btoi(cdr.Param[0]);
1022 SetResultSize(4);
1023 if (CDR_getTD(cdr.Track, cdr.ResultTD) == -1) {
1024 cdr.Stat = DiskError;
1025 cdr.Result[0] |= STATUS_ERROR;
1026 } else {
1027 cdr.Stat = Acknowledge;
1028 cdr.Result[0] = cdr.StatP;
1029 cdr.Result[1] = itob(cdr.ResultTD[2]);
1030 cdr.Result[2] = itob(cdr.ResultTD[1]);
1031 /* According to Nocash's documentation, the function doesn't care about ff.
1032 * This can be seen also in Mednafen's implementation. */
1033 //cdr.Result[3] = itob(cdr.ResultTD[0]);
1034 }
1035 break;
1036
1037 case CdlSeekL:
1038 case CdlSeekP:
1039 StopCdda();
1040 StopReading();
1041 SetPlaySeekRead(cdr.StatP, STATUS_SEEK | STATUS_ROTATING);
1042
1043 seekTime = cdrSeekTime(cdr.SetSector);
1044 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
1045 /*
1046 Crusaders of Might and Magic = 0.5x-4x
1047 - fix cutscene speech start
1048
1049 Eggs of Steel = 2x-?
1050 - fix new game
1051
1052 Medievil = ?-4x
1053 - fix cutscene speech
1054
1055 Rockman X5 = 0.5-4x
1056 - fix capcom logo
1057 */
1058 second_resp_time = cdReadTime + seekTime;
1059 start_rotating = 1;
1060 break;
1061
1062 case CdlSeekL + CMD_PART2:
1063 case CdlSeekP + CMD_PART2:
1064 SetPlaySeekRead(cdr.StatP, 0);
1065 cdr.Result[0] = cdr.StatP;
1066 cdr.Stat = Complete;
1067
1068 Find_CurTrack(cdr.SetSectorPlay);
1069 read_ok = ReadTrack(cdr.SetSectorPlay);
1070 if (read_ok && (buf = CDR_getBuffer()))
1071 memcpy(cdr.LocL, buf, 8);
1072 UpdateSubq(cdr.SetSectorPlay);
1073 cdr.TrackChanged = FALSE;
1074 break;
1075
1076 case CdlTest:
1077 case CdlTest + CMD_WHILE_NOT_READY:
1078 switch (cdr.Param[0]) {
1079 case 0x20: // System Controller ROM Version
1080 SetResultSize(4);
1081 memcpy(cdr.Result, Test20, 4);
1082 break;
1083 case 0x22:
1084 SetResultSize(8);
1085 memcpy(cdr.Result, Test22, 4);
1086 break;
1087 case 0x23: case 0x24:
1088 SetResultSize(8);
1089 memcpy(cdr.Result, Test23, 4);
1090 break;
1091 }
1092 break;
1093
1094 case CdlID:
1095 second_resp_time = 20480;
1096 break;
1097
1098 case CdlID + CMD_PART2:
1099 SetResultSize(8);
1100 cdr.Result[0] = cdr.StatP;
1101 cdr.Result[1] = 0;
1102 cdr.Result[2] = 0;
1103 cdr.Result[3] = 0;
1104
1105 // 0x10 - audio | 0x40 - disk missing | 0x80 - unlicensed
1106 if (CDR_getStatus(&stat) == -1 || stat.Type == 0 || stat.Type == 0xff) {
1107 cdr.Result[1] = 0xc0;
1108 }
1109 else {
1110 if (stat.Type == 2)
1111 cdr.Result[1] |= 0x10;
1112 if (CdromId[0] == '\0')
1113 cdr.Result[1] |= 0x80;
1114 }
1115 cdr.Result[0] |= (cdr.Result[1] >> 4) & 0x08;
1116
1117 /* This adds the string "PCSX" in Playstation bios boot screen */
1118 memcpy((char *)&cdr.Result[4], "PCSX", 4);
1119 cdr.Stat = Complete;
1120 break;
1121
1122 case CdlInit:
1123 case CdlInit + CMD_WHILE_NOT_READY:
1124 StopCdda();
1125 StopReading();
1126 SetPlaySeekRead(cdr.StatP, 0);
1127 // yes, it really sets STATUS_SHELLOPEN
1128 cdr.StatP |= STATUS_SHELLOPEN;
1129 cdr.DriveState = DRIVESTATE_RESCAN_CD;
1130 set_event(PSXINT_CDRLID, 20480);
1131 start_rotating = 1;
1132 break;
1133
1134 case CdlGetQ:
1135 case CdlGetQ + CMD_WHILE_NOT_READY:
1136 break;
1137
1138 case CdlReadToc:
1139 case CdlReadToc + CMD_WHILE_NOT_READY:
1140 cdr.LocL[0] = LOCL_INVALID;
1141 second_resp_time = cdReadTime * 180 / 4;
1142 start_rotating = 1;
1143 break;
1144
1145 case CdlReadToc + CMD_PART2:
1146 case CdlReadToc + CMD_PART2 + CMD_WHILE_NOT_READY:
1147 cdr.Stat = Complete;
1148 break;
1149
1150 case CdlReadN:
1151 case CdlReadS:
1152 if (cdr.Reading && !cdr.SetlocPending)
1153 break;
1154
1155 Find_CurTrack(cdr.SetlocPending ? cdr.SetSector : cdr.SetSectorPlay);
1156
1157 if ((cdr.Mode & MODE_CDDA) && cdr.CurTrack > 1)
1158 // Read* acts as play for cdda tracks in cdda mode
1159 goto do_CdlPlay;
1160
1161 StopCdda();
1162 if (cdr.SetlocPending) {
1163 seekTime = cdrSeekTime(cdr.SetSector);
1164 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
1165 cdr.SetlocPending = 0;
1166 }
1167 cdr.Reading = 1;
1168 cdr.FirstSector = 1;
1169
1170 // Fighting Force 2 - update subq time immediately
1171 // - fixes new game
1172 UpdateSubq(cdr.SetSectorPlay);
1173 cdr.LocL[0] = LOCL_INVALID;
1174 cdr.SubqForwardSectors = 1;
1175 cdr.sectorsRead = 0;
1176
1177 cycles = (cdr.Mode & MODE_SPEED) ? cdReadTime : cdReadTime * 2;
1178 cycles += seekTime;
1179 if (Config.hacks.cdr_read_timing)
1180 cycles = cdrAlignTimingHack(cycles);
1181 CDRPLAYREAD_INT(cycles, 1);
1182
1183 SetPlaySeekRead(cdr.StatP, STATUS_SEEK);
1184 start_rotating = 1;
1185 break;
1186
1187 case CdlSync:
1188 default:
1189 error = ERROR_INVALIDCMD;
1190 // FALLTHROUGH
1191
1192 set_error:
1193 CDR_LOG_I("cmd %02x error %02x\n", Cmd, error);
1194 SetResultSize(2);
1195 cdr.Result[0] = cdr.StatP | STATUS_ERROR;
1196 cdr.Result[1] = not_ready ? ERROR_NOTREADY : error;
1197 cdr.Stat = DiskError;
1198 break;
1199 }
1200
1201 if (cdr.DriveState == DRIVESTATE_STOPPED && start_rotating) {
1202 cdr.DriveState = DRIVESTATE_STANDBY;
1203 cdr.StatP |= STATUS_ROTATING;
1204 }
1205
1206 if (second_resp_time) {
1207 cdr.CmdInProgress = Cmd | 0x100;
1208 set_event(PSXINT_CDR, second_resp_time);
1209 }
1210 else if (cdr.Cmd && cdr.Cmd != (Cmd & 0xff)) {
1211 cdr.CmdInProgress = cdr.Cmd;
1212 CDR_LOG_I("cmd %02x came before %02x finished\n", cdr.Cmd, Cmd);
1213 }
1214
1215 setIrq(Cmd);
1216}
1217
1218#ifdef HAVE_ARMV7
1219 #define ssat32_to_16(v) \
1220 asm("ssat %0,#16,%1" : "=r" (v) : "r" (v))
1221#else
1222 #define ssat32_to_16(v) do { \
1223 if (v < -32768) v = -32768; \
1224 else if (v > 32767) v = 32767; \
1225 } while (0)
1226#endif
1227
1228static void cdrPrepCdda(s16 *buf, int samples)
1229{
1230#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
1231 int i;
1232 for (i = 0; i < samples; i++) {
1233 buf[i * 2 + 0] = SWAP16(buf[i * 2 + 0]);
1234 buf[i * 2 + 1] = SWAP16(buf[i * 2 + 1]);
1235 }
1236#endif
1237}
1238
1239static void cdrAttenuate(s16 *buf, int samples, int stereo)
1240{
1241 int i, l, r;
1242 int ll = cdr.AttenuatorLeftToLeft;
1243 int lr = cdr.AttenuatorLeftToRight;
1244 int rl = cdr.AttenuatorRightToLeft;
1245 int rr = cdr.AttenuatorRightToRight;
1246
1247 if (lr == 0 && rl == 0 && 0x78 <= ll && ll <= 0x88 && 0x78 <= rr && rr <= 0x88)
1248 return;
1249
1250 if (!stereo && ll == 0x40 && lr == 0x40 && rl == 0x40 && rr == 0x40)
1251 return;
1252
1253 if (stereo) {
1254 for (i = 0; i < samples; i++) {
1255 l = buf[i * 2];
1256 r = buf[i * 2 + 1];
1257 l = (l * ll + r * rl) >> 7;
1258 r = (r * rr + l * lr) >> 7;
1259 ssat32_to_16(l);
1260 ssat32_to_16(r);
1261 buf[i * 2] = l;
1262 buf[i * 2 + 1] = r;
1263 }
1264 }
1265 else {
1266 for (i = 0; i < samples; i++) {
1267 l = buf[i];
1268 l = l * (ll + rl) >> 7;
1269 //r = r * (rr + lr) >> 7;
1270 ssat32_to_16(l);
1271 //ssat32_to_16(r);
1272 buf[i] = l;
1273 }
1274 }
1275}
1276
1277static void cdrReadInterruptSetResult(unsigned char result)
1278{
1279 if (cdr.Stat) {
1280 CDR_LOG_I("%d:%02d:%02d irq miss, cmd=%02x irqstat=%02x\n",
1281 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2],
1282 cdr.CmdInProgress, cdr.Stat);
1283 cdr.Irq1Pending = result;
1284 return;
1285 }
1286 SetResultSize(1);
1287 cdr.Result[0] = result;
1288 cdr.Stat = (result & STATUS_ERROR) ? DiskError : DataReady;
1289 setIrq(0x1004);
1290}
1291
1292static void cdrUpdateTransferBuf(const u8 *buf)
1293{
1294 if (!buf)
1295 return;
1296 memcpy(cdr.Transfer, buf, DATA_SIZE);
1297 CheckPPFCache(cdr.Transfer, cdr.Prev[0], cdr.Prev[1], cdr.Prev[2]);
1298 CDR_LOG("cdr.Transfer %02x:%02x:%02x\n",
1299 cdr.Transfer[0], cdr.Transfer[1], cdr.Transfer[2]);
1300 if (cdr.FifoOffset < 2048 + 12)
1301 CDR_LOG("FifoOffset(1) %d/%d\n", cdr.FifoOffset, cdr.FifoSize);
1302}
1303
1304static void cdrReadInterrupt(void)
1305{
1306 u8 *buf = NULL, *hdr;
1307 u8 subqPos[3];
1308 int read_ok;
1309
1310 memcpy(subqPos, cdr.SetSectorPlay, sizeof(subqPos));
1311 msfiAdd(subqPos, cdr.SubqForwardSectors);
1312 UpdateSubq(subqPos);
1313 if (cdr.SubqForwardSectors < SUBQ_FORWARD_SECTORS) {
1314 cdr.SubqForwardSectors++;
1315 CDRPLAYREAD_INT((cdr.Mode & MODE_SPEED) ? (cdReadTime / 2) : cdReadTime, 0);
1316 return;
1317 }
1318
1319 // note: CdlGetlocL should work as soon as STATUS_READ is indicated
1320 SetPlaySeekRead(cdr.StatP, STATUS_READ | STATUS_ROTATING);
1321 cdr.sectorsRead++;
1322
1323 read_ok = ReadTrack(cdr.SetSectorPlay);
1324 if (read_ok)
1325 buf = CDR_getBuffer();
1326 if (buf == NULL)
1327 read_ok = 0;
1328
1329 if (!read_ok) {
1330 CDR_LOG_I("cdrReadInterrupt() Log: err\n");
1331 cdrReadInterruptSetResult(cdr.StatP | STATUS_ERROR);
1332 return;
1333 }
1334 memcpy(cdr.LocL, buf, 8);
1335
1336 if (!cdr.Stat && !cdr.Irq1Pending)
1337 cdrUpdateTransferBuf(buf);
1338
1339 if ((!cdr.Muted) && (cdr.Mode & MODE_STRSND) && (!Config.Xa) && (cdr.FirstSector != -1)) { // CD-XA
1340 hdr = buf + 4;
1341 // Firemen 2: Multi-XA files - briefings, cutscenes
1342 if( cdr.FirstSector == 1 && (cdr.Mode & MODE_SF)==0 ) {
1343 cdr.File = hdr[0];
1344 cdr.Channel = hdr[1];
1345 }
1346
1347 /* Gameblabla
1348 * Skips playing on channel 255.
1349 * Fixes missing audio in Blue's Clues : Blue's Big Musical. (Should also fix Taxi 2)
1350 * TODO : Check if this is the proper behaviour.
1351 * */
1352 if ((hdr[2] & 0x4) && hdr[0] == cdr.File && hdr[1] == cdr.Channel && cdr.Channel != 255) {
1353 int ret = xa_decode_sector(&cdr.Xa, buf + 4, cdr.FirstSector);
1354 if (!ret) {
1355 cdrAttenuate(cdr.Xa.pcm, cdr.Xa.nsamples, cdr.Xa.stereo);
1356 SPU_playADPCMchannel(&cdr.Xa, psxRegs.cycle, cdr.FirstSector);
1357 cdr.FirstSector = 0;
1358 }
1359 else cdr.FirstSector = -1;
1360 }
1361 }
1362
1363 /*
1364 Croc 2: $40 - only FORM1 (*)
1365 Judge Dredd: $C8 - only FORM1 (*)
1366 Sim Theme Park - no adpcm at all (zero)
1367 */
1368
1369 if (!(cdr.Mode & MODE_STRSND) || !(buf[4+2] & 0x4))
1370 cdrReadInterruptSetResult(cdr.StatP);
1371
1372 msfiAdd(cdr.SetSectorPlay, 1);
1373
1374 CDRPLAYREAD_INT((cdr.Mode & MODE_SPEED) ? (cdReadTime / 2) : cdReadTime, 0);
1375}
1376
1377/*
1378cdrRead0:
1379 bit 0,1 - mode
1380 bit 2 - unknown
1381 bit 3 - unknown
1382 bit 4 - unknown
1383 bit 5 - 1 result ready
1384 bit 6 - 1 dma ready
1385 bit 7 - 1 command being processed
1386*/
1387
1388unsigned char cdrRead0(void) {
1389 if (cdr.ResultReady)
1390 cdr.Ctrl |= 0x20;
1391 else
1392 cdr.Ctrl &= ~0x20;
1393
1394 cdr.Ctrl |= 0x40; // data fifo not empty
1395
1396 // What means the 0x10 and the 0x08 bits? I only saw it used by the bios
1397 cdr.Ctrl |= 0x18;
1398
1399 CDR_LOG_IO("cdr r0.sta: %02x\n", cdr.Ctrl);
1400
1401 return psxHu8(0x1800) = cdr.Ctrl;
1402}
1403
1404void cdrWrite0(unsigned char rt) {
1405 CDR_LOG_IO("cdr w0.idx: %02x\n", rt);
1406
1407 cdr.Ctrl = (rt & 3) | (cdr.Ctrl & ~3);
1408}
1409
1410unsigned char cdrRead1(void) {
1411 if ((cdr.ResultP & 0xf) < cdr.ResultC)
1412 psxHu8(0x1801) = cdr.Result[cdr.ResultP & 0xf];
1413 else
1414 psxHu8(0x1801) = 0;
1415 cdr.ResultP++;
1416 if (cdr.ResultP == cdr.ResultC)
1417 cdr.ResultReady = 0;
1418
1419 CDR_LOG_IO("cdr r1.rsp: %02x #%u\n", psxHu8(0x1801), cdr.ResultP - 1);
1420
1421 return psxHu8(0x1801);
1422}
1423
1424void cdrWrite1(unsigned char rt) {
1425 const char *rnames[] = { "cmd", "smd", "smc", "arr" }; (void)rnames;
1426 CDR_LOG_IO("cdr w1.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1427
1428 switch (cdr.Ctrl & 3) {
1429 case 0:
1430 break;
1431 case 3:
1432 cdr.AttenuatorRightToRightT = rt;
1433 return;
1434 default:
1435 return;
1436 }
1437
1438#ifdef CDR_LOG_CMD_IRQ
1439 CDR_LOG_I("CD1 write: %x (%s)", rt, CmdName[rt]);
1440 if (cdr.ParamC) {
1441 int i;
1442 SysPrintf(" Param[%d] = {", cdr.ParamC);
1443 for (i = 0; i < cdr.ParamC; i++)
1444 SysPrintf(" %x,", cdr.Param[i]);
1445 SysPrintf("}\n");
1446 } else {
1447 SysPrintf("\n");
1448 }
1449#endif
1450
1451 cdr.ResultReady = 0;
1452 cdr.Ctrl |= 0x80;
1453
1454 if (!cdr.CmdInProgress) {
1455 cdr.CmdInProgress = rt;
1456 // should be something like 12k + controller delays
1457 set_event(PSXINT_CDR, 5000);
1458 }
1459 else {
1460 CDR_LOG_I("cmd while busy: %02x, prev %02x, busy %02x\n",
1461 rt, cdr.Cmd, cdr.CmdInProgress);
1462 if (cdr.CmdInProgress < 0x100) // no pending 2nd response
1463 cdr.CmdInProgress = rt;
1464 }
1465
1466 cdr.Cmd = rt;
1467}
1468
1469unsigned char cdrRead2(void) {
1470 unsigned char ret = cdr.Transfer[0x920];
1471
1472 if (cdr.FifoOffset < cdr.FifoSize)
1473 ret = cdr.Transfer[cdr.FifoOffset++];
1474 else
1475 CDR_LOG_I("read empty fifo (%d)\n", cdr.FifoSize);
1476
1477 CDR_LOG_IO("cdr r2.dat: %02x\n", ret);
1478 return ret;
1479}
1480
1481void cdrWrite2(unsigned char rt) {
1482 const char *rnames[] = { "prm", "ien", "all", "arl" }; (void)rnames;
1483 CDR_LOG_IO("cdr w2.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1484
1485 switch (cdr.Ctrl & 3) {
1486 case 0:
1487 if (cdr.ParamC < 8) // FIXME: size and wrapping
1488 cdr.Param[cdr.ParamC++] = rt;
1489 return;
1490 case 1:
1491 cdr.Reg2 = rt;
1492 setIrq(0x1005);
1493 return;
1494 case 2:
1495 cdr.AttenuatorLeftToLeftT = rt;
1496 return;
1497 case 3:
1498 cdr.AttenuatorRightToLeftT = rt;
1499 return;
1500 }
1501}
1502
1503unsigned char cdrRead3(void) {
1504 if (cdr.Ctrl & 0x1)
1505 psxHu8(0x1803) = cdr.Stat | 0xE0;
1506 else
1507 psxHu8(0x1803) = cdr.Reg2 | 0xE0;
1508
1509 CDR_LOG_IO("cdr r3.%s: %02x\n", (cdr.Ctrl & 1) ? "ifl" : "ien", psxHu8(0x1803));
1510 return psxHu8(0x1803);
1511}
1512
1513void cdrWrite3(unsigned char rt) {
1514 const char *rnames[] = { "req", "ifl", "alr", "ava" }; (void)rnames;
1515 CDR_LOG_IO("cdr w3.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1516
1517 switch (cdr.Ctrl & 3) {
1518 case 0:
1519 break; // transfer
1520 case 1:
1521 if (cdr.Stat & rt) {
1522 u32 nextCycle = psxRegs.intCycle[PSXINT_CDR].sCycle
1523 + psxRegs.intCycle[PSXINT_CDR].cycle;
1524 int pending = psxRegs.interrupt & (1 << PSXINT_CDR);
1525#ifdef CDR_LOG_CMD_IRQ
1526 CDR_LOG_I("ack %02x (w=%02x p=%d,%x,%x,%d)\n", cdr.Stat & rt, rt,
1527 !!pending, cdr.CmdInProgress,
1528 cdr.Irq1Pending, nextCycle - psxRegs.cycle);
1529#endif
1530 // note: Croc, Shadow Tower (more) vs Discworld Noir (<993)
1531 if (!pending && (cdr.CmdInProgress || cdr.Irq1Pending))
1532 {
1533 s32 c = 2048;
1534 if (cdr.CmdInProgress) {
1535 c = 2048 - (psxRegs.cycle - nextCycle);
1536 c = MAX_VALUE(c, 512);
1537 }
1538 set_event(PSXINT_CDR, c);
1539 }
1540 }
1541 cdr.Stat &= ~rt;
1542
1543 if (rt & 0x40)
1544 cdr.ParamC = 0;
1545 return;
1546 case 2:
1547 cdr.AttenuatorLeftToRightT = rt;
1548 return;
1549 case 3:
1550 if (rt & 0x20) {
1551 memcpy(&cdr.AttenuatorLeftToLeft, &cdr.AttenuatorLeftToLeftT, 4);
1552 CDR_LOG("CD-XA Volume: %02x %02x | %02x %02x\n",
1553 cdr.AttenuatorLeftToLeft, cdr.AttenuatorLeftToRight,
1554 cdr.AttenuatorRightToLeft, cdr.AttenuatorRightToRight);
1555 }
1556 return;
1557 }
1558
1559 // test: Viewpoint
1560 if ((rt & 0x80) && cdr.FifoOffset < cdr.FifoSize) {
1561 CDR_LOG("cdrom: FifoOffset(2) %d/%d\n", cdr.FifoOffset, cdr.FifoSize);
1562 }
1563 else if (rt & 0x80) {
1564 switch (cdr.Mode & (MODE_SIZE_2328|MODE_SIZE_2340)) {
1565 case MODE_SIZE_2328:
1566 case 0x00:
1567 cdr.FifoOffset = 12;
1568 cdr.FifoSize = 2048 + 12;
1569 break;
1570
1571 case MODE_SIZE_2340:
1572 default:
1573 cdr.FifoOffset = 0;
1574 cdr.FifoSize = 2340;
1575 break;
1576 }
1577 }
1578 else if (!(rt & 0xc0))
1579 cdr.FifoOffset = DATA_SIZE; // fifo empty
1580}
1581
1582void psxDma3(u32 madr, u32 bcr, u32 chcr) {
1583 u32 cdsize, max_words;
1584 int size;
1585 u8 *ptr;
1586
1587#if 0
1588 CDR_LOG_I("psxDma3() Log: *** DMA 3 *** %x addr = %x size = %x", chcr, madr, bcr);
1589 if (cdr.FifoOffset == 0) {
1590 ptr = cdr.Transfer;
1591 SysPrintf(" %02x:%02x:%02x", ptr[0], ptr[1], ptr[2]);
1592 }
1593 SysPrintf("\n");
1594#endif
1595
1596 switch (chcr & 0x71000000) {
1597 case 0x11000000:
1598 ptr = getDmaRam(madr, &max_words);
1599 if (ptr == INVALID_PTR) {
1600 CDR_LOG_I("psxDma3() Log: *** DMA 3 *** NULL Pointer!\n");
1601 break;
1602 }
1603
1604 cdsize = (((bcr - 1) & 0xffff) + 1) * 4;
1605
1606 /*
1607 GS CDX: Enhancement CD crash
1608 - Setloc 0:0:0
1609 - CdlPlay
1610 - Spams DMA3 and gets buffer overrun
1611 */
1612 size = DATA_SIZE - cdr.FifoOffset;
1613 if (size > cdsize)
1614 size = cdsize;
1615 if (size > max_words * 4)
1616 size = max_words * 4;
1617 if (size > 0)
1618 {
1619 memcpy(ptr, cdr.Transfer + cdr.FifoOffset, size);
1620 cdr.FifoOffset += size;
1621 }
1622 if (size < cdsize) {
1623 CDR_LOG_I("cdrom: dma3 %d/%d\n", size, cdsize);
1624 memset(ptr + size, cdr.Transfer[0x920], cdsize - size);
1625 }
1626 psxCpu->Clear(madr, cdsize / 4);
1627
1628 set_event(PSXINT_CDRDMA, (cdsize / 4) * 24);
1629
1630 HW_DMA3_CHCR &= SWAPu32(~0x10000000);
1631 if (chcr & 0x100) {
1632 HW_DMA3_MADR = SWAPu32(madr + cdsize);
1633 HW_DMA3_BCR &= SWAPu32(0xffff0000);
1634 }
1635 else {
1636 // halted
1637 psxRegs.cycle += (cdsize/4) * 24 - 20;
1638 }
1639 return;
1640
1641 default:
1642 CDR_LOG_I("psxDma3() Log: Unknown cddma %x\n", chcr);
1643 break;
1644 }
1645
1646 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1647 DMA_INTERRUPT(3);
1648}
1649
1650void cdrDmaInterrupt(void)
1651{
1652 if (HW_DMA3_CHCR & SWAP32(0x01000000))
1653 {
1654 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1655 DMA_INTERRUPT(3);
1656 }
1657}
1658
1659static void getCdInfo(void)
1660{
1661 u8 tmp;
1662
1663 CDR_getTN(cdr.ResultTN);
1664 CDR_getTD(0, cdr.SetSectorEnd);
1665 tmp = cdr.SetSectorEnd[0];
1666 cdr.SetSectorEnd[0] = cdr.SetSectorEnd[2];
1667 cdr.SetSectorEnd[2] = tmp;
1668}
1669
1670void cdrReset() {
1671 memset(&cdr, 0, sizeof(cdr));
1672 cdr.CurTrack = 1;
1673 cdr.File = 1;
1674 cdr.Channel = 1;
1675 cdr.Reg2 = 0x1f;
1676 cdr.Stat = NoIntr;
1677 cdr.FifoOffset = DATA_SIZE; // fifo empty
1678 if (CdromId[0] == '\0') {
1679 cdr.DriveState = DRIVESTATE_STOPPED;
1680 cdr.StatP = 0;
1681 }
1682 else {
1683 cdr.DriveState = DRIVESTATE_STANDBY;
1684 cdr.StatP = STATUS_ROTATING;
1685 }
1686
1687 // BIOS player - default values
1688 cdr.AttenuatorLeftToLeft = 0x80;
1689 cdr.AttenuatorLeftToRight = 0x00;
1690 cdr.AttenuatorRightToLeft = 0x00;
1691 cdr.AttenuatorRightToRight = 0x80;
1692
1693 getCdInfo();
1694}
1695
1696int cdrFreeze(void *f, int Mode) {
1697 u32 tmp;
1698 u8 tmpp[3];
1699
1700 if (Mode == 0 && !Config.Cdda)
1701 CDR_stop();
1702
1703 cdr.freeze_ver = 0x63647202;
1704 gzfreeze(&cdr, sizeof(cdr));
1705
1706 if (Mode == 1) {
1707 cdr.ParamP = cdr.ParamC;
1708 tmp = cdr.FifoOffset;
1709 }
1710
1711 gzfreeze(&tmp, sizeof(tmp));
1712
1713 if (Mode == 0) {
1714 getCdInfo();
1715
1716 cdr.FifoOffset = tmp < DATA_SIZE ? tmp : DATA_SIZE;
1717 cdr.FifoSize = (cdr.Mode & MODE_SIZE_2340) ? 2340 : 2048 + 12;
1718 if (cdr.SubqForwardSectors > SUBQ_FORWARD_SECTORS)
1719 cdr.SubqForwardSectors = SUBQ_FORWARD_SECTORS;
1720
1721 // read right sub data
1722 tmpp[0] = btoi(cdr.Prev[0]);
1723 tmpp[1] = btoi(cdr.Prev[1]);
1724 tmpp[2] = btoi(cdr.Prev[2]);
1725 cdr.Prev[0]++;
1726 ReadTrack(tmpp);
1727
1728 if (cdr.Play) {
1729 if (cdr.freeze_ver < 0x63647202)
1730 memcpy(cdr.SetSectorPlay, cdr.SetSector, 3);
1731
1732 Find_CurTrack(cdr.SetSectorPlay);
1733 if (!Config.Cdda)
1734 CDR_play(cdr.SetSectorPlay);
1735 }
1736
1737 if ((cdr.freeze_ver & 0xffffff00) != 0x63647200) {
1738 // old versions did not latch Reg2, have to fixup..
1739 if (cdr.Reg2 == 0) {
1740 SysPrintf("cdrom: fixing up old savestate\n");
1741 cdr.Reg2 = 7;
1742 }
1743 // also did not save Attenuator..
1744 if ((cdr.AttenuatorLeftToLeft | cdr.AttenuatorLeftToRight
1745 | cdr.AttenuatorRightToLeft | cdr.AttenuatorRightToRight) == 0)
1746 {
1747 cdr.AttenuatorLeftToLeft = cdr.AttenuatorRightToRight = 0x80;
1748 }
1749 }
1750 }
1751
1752 return 0;
1753}
1754
1755void LidInterrupt(void) {
1756 getCdInfo();
1757 cdrLidSeekInterrupt();
1758}