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