cdrom: try more states
[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);
636static void cdrAttenuate(s16 *buf, int samples, int stereo);
637
638static void msfiAdd(u8 *msfi, u32 count)
639{
640 assert(count < 75);
641 msfi[2] += count;
642 if (msfi[2] >= 75) {
643 msfi[2] -= 75;
644 msfi[1]++;
645 if (msfi[1] == 60) {
646 msfi[1] = 0;
647 msfi[0]++;
648 }
649 }
650}
651
652static void msfiSub(u8 *msfi, u32 count)
653{
654 assert(count < 75);
655 msfi[2] -= count;
656 if ((s8)msfi[2] < 0) {
657 msfi[2] += 75;
658 msfi[1]--;
659 if ((s8)msfi[1] < 0) {
660 msfi[1] = 60;
661 msfi[0]--;
662 }
663 }
664}
665
666void cdrPlayReadInterrupt(void)
667{
668 cdr.LastReadSeekCycles = psxRegs.cycle;
669
670 if (cdr.Reading) {
671 cdrReadInterrupt();
672 return;
673 }
674
675 if (!cdr.Play) return;
676
677 CDR_LOG("CDDA - %02d:%02d:%02d m %02x\n",
678 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2], cdr.Mode);
679
680 cdr.DriveState = DRIVESTATE_PLAY_READ;
681 SetPlaySeekRead(cdr.StatP, STATUS_PLAY);
682 if (memcmp(cdr.SetSectorPlay, cdr.SetSectorEnd, 3) == 0) {
683 CDR_LOG_I("end stop\n");
684 StopCdda();
685 SetPlaySeekRead(cdr.StatP, 0);
686 cdr.TrackChanged = TRUE;
687 cdr.DriveState = DRIVESTATE_PAUSED;
688 }
689 else {
690 CDR_readCDDA(cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2], (u8 *)read_buf);
691 }
692
693 if (!cdr.IrqStat && (cdr.Mode & (MODE_AUTOPAUSE|MODE_REPORT)))
694 cdrPlayInterrupt_Autopause();
695
696 if (!cdr.Muted && cdr.Play && !Config.Cdda) {
697 cdrPrepCdda(read_buf, CD_FRAMESIZE_RAW / 4);
698 cdrAttenuate(read_buf, CD_FRAMESIZE_RAW / 4, 1);
699 SPU_playCDDAchannel(read_buf, CD_FRAMESIZE_RAW, psxRegs.cycle, 0);
700 }
701
702 msfiAdd(cdr.SetSectorPlay, 1);
703
704 // update for CdlGetlocP/autopause
705 generate_subq(cdr.SetSectorPlay);
706
707 CDRPLAYREAD_INT(cdReadTime, 0);
708}
709
710#define CMD_PART2 0x100
711#define CMD_WHILE_NOT_READY 0x200
712
713void cdrInterrupt(void) {
714 int start_rotating = 0;
715 int error = 0;
716 u32 cycles, seekTime = 0;
717 u32 second_resp_time = 0;
718 const void *buf;
719 u8 ParamC;
720 u8 set_loc[3];
721 int read_ok;
722 u16 not_ready = 0;
723 u8 IrqStat = Acknowledge;
724 u16 Cmd;
725 int i;
726
727 if (cdr.IrqStat) {
728 CDR_LOG_I("cmd %02x with irqstat %x\n",
729 cdr.CmdInProgress, cdr.IrqStat);
730 return;
731 }
732 if (cdr.Irq1Pending) {
733 // hand out the "newest" sector, according to nocash
734 cdrUpdateTransferBuf(CDR_getBuffer());
735 CDR_LOG_I("%x:%02x:%02x loaded on ack, cmd=%02x res=%02x\n",
736 cdr.Transfer[0], cdr.Transfer[1], cdr.Transfer[2],
737 cdr.CmdInProgress, cdr.Irq1Pending);
738 SetResultSize(1);
739 cdr.Result[0] = cdr.Irq1Pending;
740 cdr.Irq1Pending = 0;
741 setIrq((cdr.Irq1Pending & STATUS_ERROR) ? DiskError : DataReady, 0x1003);
742 return;
743 }
744
745 // default response
746 SetResultSize(1);
747 cdr.Result[0] = cdr.StatP;
748
749 Cmd = cdr.CmdInProgress;
750 cdr.CmdInProgress = 0;
751 ParamC = cdr.ParamC;
752
753 if (Cmd < 0x100) {
754 cdr.Ctrl &= ~0x80;
755 cdr.ParamC = 0;
756 cdr.Cmd = 0;
757 }
758
759 switch (cdr.DriveState) {
760 case DRIVESTATE_PREPARE_CD:
761 if (Cmd > 2) {
762 // Syphon filter 2 expects commands to work shortly after it sees
763 // STATUS_ROTATING, so give up trying to emulate the startup seq
764 cdr.DriveState = DRIVESTATE_STANDBY;
765 cdr.StatP &= ~STATUS_SEEK;
766 psxRegs.interrupt &= ~(1 << PSXINT_CDRLID);
767 break;
768 }
769 // fallthrough
770 case DRIVESTATE_LID_OPEN:
771 case DRIVESTATE_RESCAN_CD:
772 // no disk or busy with the initial scan, allowed cmds are limited
773 not_ready = CMD_WHILE_NOT_READY;
774 break;
775 }
776
777 switch (Cmd | not_ready) {
778 case CdlNop:
779 case CdlNop + CMD_WHILE_NOT_READY:
780 if (cdr.DriveState != DRIVESTATE_LID_OPEN)
781 cdr.StatP &= ~STATUS_SHELLOPEN;
782 break;
783
784 case CdlSetloc:
785 // case CdlSetloc + CMD_WHILE_NOT_READY: // or is it?
786 CDR_LOG("CDROM setloc command (%02X, %02X, %02X)\n", cdr.Param[0], cdr.Param[1], cdr.Param[2]);
787
788 // MM must be BCD, SS must be BCD and <0x60, FF must be BCD and <0x75
789 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))
790 {
791 CDR_LOG_I("Invalid/out of range seek to %02X:%02X:%02X\n", cdr.Param[0], cdr.Param[1], cdr.Param[2]);
792 if (++cdr.errorRetryhack > 100)
793 break;
794 error = ERROR_BAD_ARGNUM;
795 goto set_error;
796 }
797 else
798 {
799 for (i = 0; i < 3; i++)
800 set_loc[i] = btoi(cdr.Param[i]);
801 memcpy(cdr.SetSector, set_loc, 3);
802 cdr.SetSector[3] = 0;
803 cdr.SetlocPending = 1;
804 cdr.errorRetryhack = 0;
805 }
806 break;
807
808 do_CdlPlay:
809 case CdlPlay:
810 StopCdda();
811 StopReading();
812
813 cdr.FastBackward = 0;
814 cdr.FastForward = 0;
815
816 // BIOS CD Player
817 // - Pause player, hit Track 01/02/../xx (Setloc issued!!)
818
819 if (ParamC != 0 && cdr.Param[0] != 0) {
820 int track = btoi( cdr.Param[0] );
821
822 if (track <= cdr.ResultTN[1])
823 cdr.CurTrack = track;
824
825 CDR_LOG("PLAY track %d\n", cdr.CurTrack);
826
827 if (CDR_getTD((u8)cdr.CurTrack, cdr.ResultTD) != -1) {
828 for (i = 0; i < 3; i++)
829 set_loc[i] = cdr.ResultTD[2 - i];
830 seekTime = cdrSeekTime(set_loc);
831 memcpy(cdr.SetSectorPlay, set_loc, 3);
832 }
833 }
834 else if (cdr.SetlocPending) {
835 seekTime = cdrSeekTime(cdr.SetSector);
836 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
837 }
838 else {
839 CDR_LOG("PLAY Resume @ %d:%d:%d\n",
840 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2]);
841 }
842 cdr.SetlocPending = 0;
843
844 /*
845 Rayman: detect track changes
846 - fixes logo freeze
847
848 Twisted Metal 2: skip PREGAP + starting accurate SubQ
849 - plays tracks without retry play
850
851 Wild 9: skip PREGAP + starting accurate SubQ
852 - plays tracks without retry play
853 */
854 Find_CurTrack(cdr.SetSectorPlay);
855 generate_subq(cdr.SetSectorPlay);
856 cdr.LocL[0] = LOCL_INVALID;
857 cdr.SubqForwardSectors = 1;
858 cdr.TrackChanged = FALSE;
859 cdr.FileChannelSelected = 0;
860 cdr.AdpcmActive = 0;
861 cdr.ReportDelay = 60;
862 cdr.sectorsRead = 0;
863
864 if (!Config.Cdda)
865 CDR_play(cdr.SetSectorPlay);
866
867 SetPlaySeekRead(cdr.StatP, STATUS_SEEK | STATUS_ROTATING);
868
869 // BIOS player - set flag again
870 cdr.Play = TRUE;
871 cdr.DriveState = DRIVESTATE_PLAY_READ;
872
873 CDRPLAYREAD_INT(cdReadTime + seekTime, 1);
874 start_rotating = 1;
875 break;
876
877 case CdlForward:
878 // TODO: error 80 if stopped
879 IrqStat = Complete;
880
881 // GameShark CD Player: Calls 2x + Play 2x
882 cdr.FastForward = 1;
883 cdr.FastBackward = 0;
884 break;
885
886 case CdlBackward:
887 IrqStat = Complete;
888
889 // GameShark CD Player: Calls 2x + Play 2x
890 cdr.FastBackward = 1;
891 cdr.FastForward = 0;
892 break;
893
894 case CdlStandby:
895 if (cdr.DriveState != DRIVESTATE_STOPPED) {
896 error = ERROR_BAD_ARGNUM;
897 goto set_error;
898 }
899 cdr.DriveState = DRIVESTATE_STANDBY;
900 second_resp_time = cdReadTime * 125 / 2;
901 start_rotating = 1;
902 break;
903
904 case CdlStandby + CMD_PART2:
905 IrqStat = Complete;
906 break;
907
908 case CdlStop:
909 if (cdr.Play) {
910 // grab time for current track
911 CDR_getTD((u8)(cdr.CurTrack), cdr.ResultTD);
912
913 cdr.SetSectorPlay[0] = cdr.ResultTD[2];
914 cdr.SetSectorPlay[1] = cdr.ResultTD[1];
915 cdr.SetSectorPlay[2] = cdr.ResultTD[0];
916 }
917
918 StopCdda();
919 StopReading();
920 SetPlaySeekRead(cdr.StatP, 0);
921 cdr.StatP &= ~STATUS_ROTATING;
922 cdr.LocL[0] = LOCL_INVALID;
923
924 second_resp_time = 0x800;
925 if (cdr.DriveState != DRIVESTATE_STOPPED)
926 second_resp_time = cdReadTime * 30 / 2;
927
928 cdr.DriveState = DRIVESTATE_STOPPED;
929 break;
930
931 case CdlStop + CMD_PART2:
932 IrqStat = Complete;
933 break;
934
935 case CdlPause:
936 StopCdda();
937 StopReading();
938
939 // how the drive maintains the position while paused is quite
940 // complicated, this is the minimum to make "Bedlam" happy
941 msfiSub(cdr.SetSectorPlay, MIN_VALUE(cdr.sectorsRead, 4));
942 cdr.sectorsRead = 0;
943
944 /*
945 Gundam Battle Assault 2: much slower (*)
946 - Fixes boot, gameplay
947
948 Hokuto no Ken 2: slower
949 - Fixes intro + subtitles
950
951 InuYasha - Feudal Fairy Tale: slower
952 - Fixes battles
953 */
954 /* Gameblabla - Tightening the timings (as taken from Duckstation).
955 * The timings from Duckstation are based upon hardware tests.
956 * Mednafen's timing don't work for Gundam Battle Assault 2 in PAL/50hz mode,
957 * seems to be timing sensitive as it can depend on the CPU's clock speed.
958 * */
959 if (!(cdr.StatP & (STATUS_PLAY | STATUS_READ)))
960 {
961 second_resp_time = 7000;
962 }
963 else
964 {
965 second_resp_time = (((cdr.Mode & MODE_SPEED) ? 1 : 2) * 1097107);
966 }
967 SetPlaySeekRead(cdr.StatP, 0);
968 cdr.DriveState = DRIVESTATE_PAUSED;
969 break;
970
971 case CdlPause + CMD_PART2:
972 IrqStat = Complete;
973 break;
974
975 case CdlReset:
976 case CdlReset + CMD_WHILE_NOT_READY:
977 StopCdda();
978 StopReading();
979 SetPlaySeekRead(cdr.StatP, 0);
980 cdr.LocL[0] = LOCL_INVALID;
981 cdr.Muted = FALSE;
982 cdr.Mode = MODE_SIZE_2340; /* This fixes This is Football 2, Pooh's Party lockups */
983 cdr.DriveState = DRIVESTATE_PAUSED;
984 second_resp_time = not_ready ? 70000 : 4100000;
985 start_rotating = 1;
986 break;
987
988 case CdlReset + CMD_PART2:
989 case CdlReset + CMD_PART2 + CMD_WHILE_NOT_READY:
990 IrqStat = Complete;
991 break;
992
993 case CdlMute:
994 cdr.Muted = TRUE;
995 break;
996
997 case CdlDemute:
998 cdr.Muted = FALSE;
999 break;
1000
1001 case CdlSetfilter:
1002 cdr.FilterFile = cdr.Param[0];
1003 cdr.FilterChannel = cdr.Param[1];
1004 cdr.FileChannelSelected = 0;
1005 break;
1006
1007 case CdlSetmode:
1008 case CdlSetmode + CMD_WHILE_NOT_READY:
1009 CDR_LOG("cdrWrite1() Log: Setmode %x\n", cdr.Param[0]);
1010 cdr.Mode = cdr.Param[0];
1011 break;
1012
1013 case CdlGetparam:
1014 case CdlGetparam + CMD_WHILE_NOT_READY:
1015 /* Gameblabla : According to mednafen, Result size should be 5 and done this way. */
1016 SetResultSize_(5);
1017 cdr.Result[1] = cdr.Mode;
1018 cdr.Result[2] = 0;
1019 cdr.Result[3] = cdr.FilterFile;
1020 cdr.Result[4] = cdr.FilterChannel;
1021 break;
1022
1023 case CdlGetlocL:
1024 if (cdr.LocL[0] == LOCL_INVALID) {
1025 error = 0x80;
1026 goto set_error;
1027 }
1028 SetResultSize_(8);
1029 memcpy(cdr.Result, cdr.LocL, 8);
1030 break;
1031
1032 case CdlGetlocP:
1033 SetResultSize_(8);
1034 memcpy(&cdr.Result, &cdr.subq, 8);
1035 break;
1036
1037 case CdlReadT: // SetSession?
1038 // really long
1039 second_resp_time = cdReadTime * 290 / 4;
1040 start_rotating = 1;
1041 break;
1042
1043 case CdlReadT + CMD_PART2:
1044 IrqStat = Complete;
1045 break;
1046
1047 case CdlGetTN:
1048 if (CDR_getTN(cdr.ResultTN) == -1) {
1049 assert(0);
1050 }
1051 SetResultSize_(3);
1052 cdr.Result[1] = itob(cdr.ResultTN[0]);
1053 cdr.Result[2] = itob(cdr.ResultTN[1]);
1054 break;
1055
1056 case CdlGetTD:
1057 cdr.Track = btoi(cdr.Param[0]);
1058 if (CDR_getTD(cdr.Track, cdr.ResultTD) == -1) {
1059 error = ERROR_BAD_ARGVAL;
1060 goto set_error;
1061 }
1062 SetResultSize_(3);
1063 cdr.Result[1] = itob(cdr.ResultTD[2]);
1064 cdr.Result[2] = itob(cdr.ResultTD[1]);
1065 // no sector number
1066 //cdr.Result[3] = itob(cdr.ResultTD[0]);
1067 break;
1068
1069 case CdlSeekL:
1070 case CdlSeekP:
1071 StopCdda();
1072 StopReading();
1073 SetPlaySeekRead(cdr.StatP, STATUS_SEEK | STATUS_ROTATING);
1074
1075 seekTime = cdrSeekTime(cdr.SetSector);
1076 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
1077 cdr.DriveState = DRIVESTATE_SEEK;
1078 /*
1079 Crusaders of Might and Magic = 0.5x-4x
1080 - fix cutscene speech start
1081
1082 Eggs of Steel = 2x-?
1083 - fix new game
1084
1085 Medievil = ?-4x
1086 - fix cutscene speech
1087
1088 Rockman X5 = 0.5-4x
1089 - fix capcom logo
1090 */
1091 second_resp_time = cdReadTime + seekTime;
1092 start_rotating = 1;
1093 break;
1094
1095 case CdlSeekL + CMD_PART2:
1096 case CdlSeekP + CMD_PART2:
1097 SetPlaySeekRead(cdr.StatP, 0);
1098 cdr.Result[0] = cdr.StatP;
1099 IrqStat = Complete;
1100
1101 Find_CurTrack(cdr.SetSectorPlay);
1102 read_ok = ReadTrack(cdr.SetSectorPlay);
1103 if (read_ok && (buf = CDR_getBuffer()))
1104 memcpy(cdr.LocL, buf, 8);
1105 UpdateSubq(cdr.SetSectorPlay);
1106 cdr.DriveState = DRIVESTATE_STANDBY;
1107 cdr.TrackChanged = FALSE;
1108 cdr.LastReadSeekCycles = psxRegs.cycle;
1109 break;
1110
1111 case CdlTest:
1112 case CdlTest + CMD_WHILE_NOT_READY:
1113 switch (cdr.Param[0]) {
1114 case 0x20: // System Controller ROM Version
1115 SetResultSize_(4);
1116 memcpy(cdr.Result, Test20, 4);
1117 break;
1118 case 0x22:
1119 SetResultSize_(8);
1120 memcpy(cdr.Result, Test22, 4);
1121 break;
1122 case 0x23: case 0x24:
1123 SetResultSize_(8);
1124 memcpy(cdr.Result, Test23, 4);
1125 break;
1126 }
1127 break;
1128
1129 case CdlID:
1130 second_resp_time = 20480;
1131 break;
1132
1133 case CdlID + CMD_PART2:
1134 SetResultSize_(8);
1135 cdr.Result[0] = cdr.StatP;
1136 cdr.Result[1] = 0;
1137 cdr.Result[2] = 0;
1138 cdr.Result[3] = 0;
1139
1140 // 0x10 - audio | 0x40 - disk missing | 0x80 - unlicensed
1141 if (CDR_getStatus(&stat) == -1 || stat.Type == 0 || stat.Type == 0xff) {
1142 cdr.Result[1] = 0xc0;
1143 }
1144 else {
1145 if (stat.Type == 2)
1146 cdr.Result[1] |= 0x10;
1147 if (CdromId[0] == '\0')
1148 cdr.Result[1] |= 0x80;
1149 }
1150 cdr.Result[0] |= (cdr.Result[1] >> 4) & 0x08;
1151 CDR_LOG_I("CdlID: %02x %02x %02x %02x\n", cdr.Result[0],
1152 cdr.Result[1], cdr.Result[2], cdr.Result[3]);
1153
1154 /* This adds the string "PCSX" in Playstation bios boot screen */
1155 memcpy((char *)&cdr.Result[4], "PCSX", 4);
1156 IrqStat = Complete;
1157 break;
1158
1159 case CdlInit:
1160 case CdlInit + CMD_WHILE_NOT_READY:
1161 StopCdda();
1162 StopReading();
1163 SetPlaySeekRead(cdr.StatP, 0);
1164 // yes, it really sets STATUS_SHELLOPEN
1165 cdr.StatP |= STATUS_SHELLOPEN;
1166 cdr.DriveState = DRIVESTATE_RESCAN_CD;
1167 set_event(PSXINT_CDRLID, 20480);
1168 start_rotating = 1;
1169 break;
1170
1171 case CdlGetQ:
1172 case CdlGetQ + CMD_WHILE_NOT_READY:
1173 break;
1174
1175 case CdlReadToc:
1176 case CdlReadToc + CMD_WHILE_NOT_READY:
1177 cdr.LocL[0] = LOCL_INVALID;
1178 second_resp_time = cdReadTime * 180 / 4;
1179 start_rotating = 1;
1180 break;
1181
1182 case CdlReadToc + CMD_PART2:
1183 case CdlReadToc + CMD_PART2 + CMD_WHILE_NOT_READY:
1184 IrqStat = Complete;
1185 break;
1186
1187 case CdlReadN:
1188 case CdlReadS:
1189 if (cdr.Reading && !cdr.SetlocPending)
1190 break;
1191
1192 Find_CurTrack(cdr.SetlocPending ? cdr.SetSector : cdr.SetSectorPlay);
1193
1194 if ((cdr.Mode & MODE_CDDA) && cdr.CurTrack > 1)
1195 // Read* acts as play for cdda tracks in cdda mode
1196 goto do_CdlPlay;
1197
1198 StopCdda();
1199 if (cdr.SetlocPending) {
1200 seekTime = cdrSeekTime(cdr.SetSector);
1201 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
1202 cdr.SetlocPending = 0;
1203 }
1204 cdr.Reading = 1;
1205 cdr.FileChannelSelected = 0;
1206 cdr.AdpcmActive = 0;
1207
1208 // Fighting Force 2 - update subq time immediately
1209 // - fixes new game
1210 UpdateSubq(cdr.SetSectorPlay);
1211 cdr.LocL[0] = LOCL_INVALID;
1212 cdr.SubqForwardSectors = 1;
1213 cdr.sectorsRead = 0;
1214 cdr.DriveState = DRIVESTATE_SEEK;
1215
1216 cycles = (cdr.Mode & MODE_SPEED) ? cdReadTime : cdReadTime * 2;
1217 cycles += seekTime;
1218 if (Config.hacks.cdr_read_timing)
1219 cycles = cdrAlignTimingHack(cycles);
1220 CDRPLAYREAD_INT(cycles, 1);
1221
1222 SetPlaySeekRead(cdr.StatP, STATUS_SEEK);
1223 start_rotating = 1;
1224 break;
1225
1226 case CdlSync:
1227 default:
1228 error = ERROR_INVALIDCMD;
1229 // FALLTHROUGH
1230
1231 set_error:
1232 SetResultSize_(2);
1233 cdr.Result[0] = cdr.StatP | STATUS_ERROR;
1234 cdr.Result[1] = not_ready ? ERROR_NOTREADY : error;
1235 IrqStat = DiskError;
1236 CDR_LOG_I("cmd %02x error %02x\n", Cmd, cdr.Result[1]);
1237 break;
1238 }
1239
1240 if (cdr.DriveState == DRIVESTATE_STOPPED && start_rotating) {
1241 cdr.DriveState = DRIVESTATE_STANDBY;
1242 cdr.StatP |= STATUS_ROTATING;
1243 }
1244
1245 if (second_resp_time) {
1246 cdr.CmdInProgress = Cmd | 0x100;
1247 set_event(PSXINT_CDR, second_resp_time);
1248 }
1249 else if (cdr.Cmd && cdr.Cmd != (Cmd & 0xff)) {
1250 cdr.CmdInProgress = cdr.Cmd;
1251 CDR_LOG_I("cmd %02x came before %02x finished\n", cdr.Cmd, Cmd);
1252 }
1253
1254 setIrq(IrqStat, Cmd);
1255}
1256
1257#ifdef HAVE_ARMV7
1258 #define ssat32_to_16(v) \
1259 asm("ssat %0,#16,%1" : "=r" (v) : "r" (v))
1260#else
1261 #define ssat32_to_16(v) do { \
1262 if (v < -32768) v = -32768; \
1263 else if (v > 32767) v = 32767; \
1264 } while (0)
1265#endif
1266
1267static void cdrPrepCdda(s16 *buf, int samples)
1268{
1269#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
1270 int i;
1271 for (i = 0; i < samples; i++) {
1272 buf[i * 2 + 0] = SWAP16(buf[i * 2 + 0]);
1273 buf[i * 2 + 1] = SWAP16(buf[i * 2 + 1]);
1274 }
1275#endif
1276}
1277
1278static void cdrAttenuate(s16 *buf, int samples, int stereo)
1279{
1280 int i, l, r;
1281 int ll = cdr.AttenuatorLeftToLeft;
1282 int lr = cdr.AttenuatorLeftToRight;
1283 int rl = cdr.AttenuatorRightToLeft;
1284 int rr = cdr.AttenuatorRightToRight;
1285
1286 if (lr == 0 && rl == 0 && 0x78 <= ll && ll <= 0x88 && 0x78 <= rr && rr <= 0x88)
1287 return;
1288
1289 if (!stereo && ll == 0x40 && lr == 0x40 && rl == 0x40 && rr == 0x40)
1290 return;
1291
1292 if (stereo) {
1293 for (i = 0; i < samples; i++) {
1294 l = buf[i * 2];
1295 r = buf[i * 2 + 1];
1296 l = (l * ll + r * rl) >> 7;
1297 r = (r * rr + l * lr) >> 7;
1298 ssat32_to_16(l);
1299 ssat32_to_16(r);
1300 buf[i * 2] = l;
1301 buf[i * 2 + 1] = r;
1302 }
1303 }
1304 else {
1305 for (i = 0; i < samples; i++) {
1306 l = buf[i];
1307 l = l * (ll + rl) >> 7;
1308 //r = r * (rr + lr) >> 7;
1309 ssat32_to_16(l);
1310 //ssat32_to_16(r);
1311 buf[i] = l;
1312 }
1313 }
1314}
1315
1316static void cdrReadInterruptSetResult(unsigned char result)
1317{
1318 if (cdr.IrqStat) {
1319 CDR_LOG_I("%d:%02d:%02d irq miss, cmd=%02x irqstat=%02x\n",
1320 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2],
1321 cdr.CmdInProgress, cdr.IrqStat);
1322 cdr.Irq1Pending = result;
1323 return;
1324 }
1325 SetResultSize(1);
1326 cdr.Result[0] = result;
1327 setIrq((result & STATUS_ERROR) ? DiskError : DataReady, 0x1004);
1328}
1329
1330static void cdrUpdateTransferBuf(const u8 *buf)
1331{
1332 if (!buf)
1333 return;
1334 memcpy(cdr.Transfer, buf, DATA_SIZE);
1335 CheckPPFCache(cdr.Transfer, cdr.Prev[0], cdr.Prev[1], cdr.Prev[2]);
1336 CDR_LOG("cdr.Transfer %02x:%02x:%02x\n",
1337 cdr.Transfer[0], cdr.Transfer[1], cdr.Transfer[2]);
1338 if (cdr.FifoOffset < 2048 + 12)
1339 CDR_LOG("FifoOffset(1) %d/%d\n", cdr.FifoOffset, cdr.FifoSize);
1340}
1341
1342static void cdrReadInterrupt(void)
1343{
1344 const struct { u8 file, chan, mode, coding; } *subhdr;
1345 const u8 *buf = NULL;
1346 int deliver_data = 1;
1347 u8 subqPos[3];
1348 int read_ok;
1349
1350 memcpy(subqPos, cdr.SetSectorPlay, sizeof(subqPos));
1351 msfiAdd(subqPos, cdr.SubqForwardSectors);
1352 UpdateSubq(subqPos);
1353 if (cdr.SubqForwardSectors < SUBQ_FORWARD_SECTORS) {
1354 cdr.SubqForwardSectors++;
1355 CDRPLAYREAD_INT((cdr.Mode & MODE_SPEED) ? (cdReadTime / 2) : cdReadTime, 0);
1356 return;
1357 }
1358
1359 // note: CdlGetlocL should work as soon as STATUS_READ is indicated
1360 SetPlaySeekRead(cdr.StatP, STATUS_READ | STATUS_ROTATING);
1361 cdr.DriveState = DRIVESTATE_PLAY_READ;
1362 cdr.sectorsRead++;
1363
1364 read_ok = ReadTrack(cdr.SetSectorPlay);
1365 if (read_ok)
1366 buf = CDR_getBuffer();
1367 if (buf == NULL)
1368 read_ok = 0;
1369
1370 if (!read_ok) {
1371 CDR_LOG_I("cdrReadInterrupt() Log: err\n");
1372 cdrReadInterruptSetResult(cdr.StatP | STATUS_ERROR);
1373 cdr.DriveState = DRIVESTATE_PAUSED; // ?
1374 return;
1375 }
1376 memcpy(cdr.LocL, buf, 8);
1377
1378 if (!cdr.IrqStat && !cdr.Irq1Pending)
1379 cdrUpdateTransferBuf(buf);
1380
1381 subhdr = (void *)(buf + 4);
1382 do {
1383 // try to process as adpcm
1384 if (!(cdr.Mode & MODE_STRSND))
1385 break;
1386 if (buf[3] != 2 || (subhdr->mode & 0x44) != 0x44) // or 0x64?
1387 break;
1388 CDR_LOG("f=%d m=%d %d,%3d | %d,%2d | %d,%2d\n", !!(cdr.Mode & MODE_SF), cdr.Muted,
1389 subhdr->file, subhdr->chan, cdr.CurFile, cdr.CurChannel, cdr.FilterFile, cdr.FilterChannel);
1390 if ((cdr.Mode & MODE_SF) && (subhdr->file != cdr.FilterFile || subhdr->chan != cdr.FilterChannel))
1391 break;
1392 if (subhdr->chan & 0xe0) { // ?
1393 if (subhdr->chan != 0xff)
1394 log_unhandled("adpcm %d:%d\n", subhdr->file, subhdr->chan);
1395 break;
1396 }
1397 if (!cdr.FileChannelSelected) {
1398 cdr.CurFile = subhdr->file;
1399 cdr.CurChannel = subhdr->chan;
1400 cdr.FileChannelSelected = 1;
1401 }
1402 else if (subhdr->file != cdr.CurFile || subhdr->chan != cdr.CurChannel)
1403 break;
1404
1405 // accepted as adpcm
1406 deliver_data = 0;
1407
1408 if (Config.Xa)
1409 break;
1410 if (!cdr.Muted && cdr.AdpcmActive) {
1411 cdrAttenuate(cdr.Xa.pcm, cdr.Xa.nsamples, cdr.Xa.stereo);
1412 SPU_playADPCMchannel(&cdr.Xa, psxRegs.cycle, 0);
1413 }
1414 // decode next
1415 cdr.AdpcmActive = !xa_decode_sector(&cdr.Xa, buf + 4, !cdr.AdpcmActive);
1416 } while (0);
1417
1418 if ((cdr.Mode & MODE_SF) && (subhdr->mode & 0x44) == 0x44) // according to nocash
1419 deliver_data = 0;
1420
1421 /*
1422 Croc 2: $40 - only FORM1 (*)
1423 Judge Dredd: $C8 - only FORM1 (*)
1424 Sim Theme Park - no adpcm at all (zero)
1425 */
1426
1427 if (deliver_data)
1428 cdrReadInterruptSetResult(cdr.StatP);
1429
1430 msfiAdd(cdr.SetSectorPlay, 1);
1431
1432 CDRPLAYREAD_INT((cdr.Mode & MODE_SPEED) ? (cdReadTime / 2) : cdReadTime, 0);
1433}
1434
1435/*
1436cdrRead0:
1437 bit 0,1 - reg index
1438 bit 2 - adpcm active
1439 bit 5 - 1 result ready
1440 bit 6 - 1 dma ready
1441 bit 7 - 1 command being processed
1442*/
1443
1444unsigned char cdrRead0(void) {
1445 cdr.Ctrl &= ~0x24;
1446 cdr.Ctrl |= cdr.AdpcmActive << 2;
1447 cdr.Ctrl |= cdr.ResultReady << 5;
1448
1449 cdr.Ctrl |= 0x40; // data fifo not empty
1450
1451 // What means the 0x10 and the 0x08 bits? I only saw it used by the bios
1452 cdr.Ctrl |= 0x18;
1453
1454 CDR_LOG_IO("cdr r0.sta: %02x\n", cdr.Ctrl);
1455
1456 return psxHu8(0x1800) = cdr.Ctrl;
1457}
1458
1459void cdrWrite0(unsigned char rt) {
1460 CDR_LOG_IO("cdr w0.idx: %02x\n", rt);
1461
1462 cdr.Ctrl = (rt & 3) | (cdr.Ctrl & ~3);
1463}
1464
1465unsigned char cdrRead1(void) {
1466 if ((cdr.ResultP & 0xf) < cdr.ResultC)
1467 psxHu8(0x1801) = cdr.Result[cdr.ResultP & 0xf];
1468 else
1469 psxHu8(0x1801) = 0;
1470 cdr.ResultP++;
1471 if (cdr.ResultP == cdr.ResultC)
1472 cdr.ResultReady = 0;
1473
1474 CDR_LOG_IO("cdr r1.rsp: %02x #%u\n", psxHu8(0x1801), cdr.ResultP - 1);
1475
1476 return psxHu8(0x1801);
1477}
1478
1479void cdrWrite1(unsigned char rt) {
1480 const char *rnames[] = { "cmd", "smd", "smc", "arr" }; (void)rnames;
1481 CDR_LOG_IO("cdr w1.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1482
1483 switch (cdr.Ctrl & 3) {
1484 case 0:
1485 break;
1486 case 3:
1487 cdr.AttenuatorRightToRightT = rt;
1488 return;
1489 default:
1490 return;
1491 }
1492
1493#ifdef CDR_LOG_CMD_IRQ
1494 CDR_LOG_I("CD1 write: %x (%s)", rt, CmdName[rt]);
1495 if (cdr.ParamC) {
1496 int i;
1497 SysPrintf(" Param[%d] = {", cdr.ParamC);
1498 for (i = 0; i < cdr.ParamC; i++)
1499 SysPrintf(" %x,", cdr.Param[i]);
1500 SysPrintf("}\n");
1501 } else {
1502 SysPrintf("\n");
1503 }
1504#endif
1505
1506 cdr.ResultReady = 0;
1507 cdr.Ctrl |= 0x80;
1508
1509 if (!cdr.CmdInProgress) {
1510 cdr.CmdInProgress = rt;
1511 // should be something like 12k + controller delays
1512 set_event(PSXINT_CDR, 5000);
1513 }
1514 else {
1515 CDR_LOG_I("cmd while busy: %02x, prev %02x, busy %02x\n",
1516 rt, cdr.Cmd, cdr.CmdInProgress);
1517 if (cdr.CmdInProgress < 0x100) // no pending 2nd response
1518 cdr.CmdInProgress = rt;
1519 }
1520
1521 cdr.Cmd = rt;
1522}
1523
1524unsigned char cdrRead2(void) {
1525 unsigned char ret = cdr.Transfer[0x920];
1526
1527 if (cdr.FifoOffset < cdr.FifoSize)
1528 ret = cdr.Transfer[cdr.FifoOffset++];
1529 else
1530 CDR_LOG_I("read empty fifo (%d)\n", cdr.FifoSize);
1531
1532 CDR_LOG_IO("cdr r2.dat: %02x\n", ret);
1533 return ret;
1534}
1535
1536void cdrWrite2(unsigned char rt) {
1537 const char *rnames[] = { "prm", "ien", "all", "arl" }; (void)rnames;
1538 CDR_LOG_IO("cdr w2.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1539
1540 switch (cdr.Ctrl & 3) {
1541 case 0:
1542 if (cdr.ParamC < 8) // FIXME: size and wrapping
1543 cdr.Param[cdr.ParamC++] = rt;
1544 return;
1545 case 1:
1546 cdr.IrqMask = rt;
1547 setIrq(cdr.IrqStat, 0x1005);
1548 return;
1549 case 2:
1550 cdr.AttenuatorLeftToLeftT = rt;
1551 return;
1552 case 3:
1553 cdr.AttenuatorRightToLeftT = rt;
1554 return;
1555 }
1556}
1557
1558unsigned char cdrRead3(void) {
1559 if (cdr.Ctrl & 0x1)
1560 psxHu8(0x1803) = cdr.IrqStat | 0xE0;
1561 else
1562 psxHu8(0x1803) = cdr.IrqMask | 0xE0;
1563
1564 CDR_LOG_IO("cdr r3.%s: %02x\n", (cdr.Ctrl & 1) ? "ifl" : "ien", psxHu8(0x1803));
1565 return psxHu8(0x1803);
1566}
1567
1568void cdrWrite3(unsigned char rt) {
1569 const char *rnames[] = { "req", "ifl", "alr", "ava" }; (void)rnames;
1570 CDR_LOG_IO("cdr w3.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1571
1572 switch (cdr.Ctrl & 3) {
1573 case 0:
1574 break; // transfer
1575 case 1:
1576 if (cdr.IrqStat & rt) {
1577 u32 nextCycle = psxRegs.intCycle[PSXINT_CDR].sCycle
1578 + psxRegs.intCycle[PSXINT_CDR].cycle;
1579 int pending = psxRegs.interrupt & (1 << PSXINT_CDR);
1580#ifdef CDR_LOG_CMD_IRQ
1581 CDR_LOG_I("ack %02x (w=%02x p=%d,%x,%x,%d)\n",
1582 cdr.IrqStat & rt, rt, !!pending, cdr.CmdInProgress,
1583 cdr.Irq1Pending, nextCycle - psxRegs.cycle);
1584#endif
1585 // note: Croc, Shadow Tower (more) vs Discworld Noir (<993)
1586 if (!pending && (cdr.CmdInProgress || cdr.Irq1Pending))
1587 {
1588 s32 c = 2048;
1589 if (cdr.CmdInProgress) {
1590 c = 2048 - (psxRegs.cycle - nextCycle);
1591 c = MAX_VALUE(c, 512);
1592 }
1593 set_event(PSXINT_CDR, c);
1594 }
1595 }
1596 cdr.IrqStat &= ~rt;
1597
1598 if (rt & 0x40)
1599 cdr.ParamC = 0;
1600 return;
1601 case 2:
1602 cdr.AttenuatorLeftToRightT = rt;
1603 return;
1604 case 3:
1605 if (rt & 0x20) {
1606 memcpy(&cdr.AttenuatorLeftToLeft, &cdr.AttenuatorLeftToLeftT, 4);
1607 CDR_LOG("CD-XA Volume: %02x %02x | %02x %02x\n",
1608 cdr.AttenuatorLeftToLeft, cdr.AttenuatorLeftToRight,
1609 cdr.AttenuatorRightToLeft, cdr.AttenuatorRightToRight);
1610 }
1611 return;
1612 }
1613
1614 // test: Viewpoint
1615 if ((rt & 0x80) && cdr.FifoOffset < cdr.FifoSize) {
1616 CDR_LOG("cdrom: FifoOffset(2) %d/%d\n", cdr.FifoOffset, cdr.FifoSize);
1617 }
1618 else if (rt & 0x80) {
1619 switch (cdr.Mode & (MODE_SIZE_2328|MODE_SIZE_2340)) {
1620 case MODE_SIZE_2328:
1621 case 0x00:
1622 cdr.FifoOffset = 12;
1623 cdr.FifoSize = 2048 + 12;
1624 break;
1625
1626 case MODE_SIZE_2340:
1627 default:
1628 cdr.FifoOffset = 0;
1629 cdr.FifoSize = 2340;
1630 break;
1631 }
1632 }
1633 else if (!(rt & 0xc0))
1634 cdr.FifoOffset = DATA_SIZE; // fifo empty
1635}
1636
1637void psxDma3(u32 madr, u32 bcr, u32 chcr) {
1638 u32 cdsize, max_words;
1639 int size;
1640 u8 *ptr;
1641
1642#if 0
1643 CDR_LOG_I("psxDma3() Log: *** DMA 3 *** %x addr = %x size = %x", chcr, madr, bcr);
1644 if (cdr.FifoOffset == 0) {
1645 ptr = cdr.Transfer;
1646 SysPrintf(" %02x:%02x:%02x", ptr[0], ptr[1], ptr[2]);
1647 }
1648 SysPrintf("\n");
1649#endif
1650
1651 switch (chcr & 0x71000000) {
1652 case 0x11000000:
1653 ptr = getDmaRam(madr, &max_words);
1654 if (ptr == INVALID_PTR) {
1655 CDR_LOG_I("psxDma3() Log: *** DMA 3 *** NULL Pointer!\n");
1656 break;
1657 }
1658
1659 cdsize = (((bcr - 1) & 0xffff) + 1) * 4;
1660
1661 /*
1662 GS CDX: Enhancement CD crash
1663 - Setloc 0:0:0
1664 - CdlPlay
1665 - Spams DMA3 and gets buffer overrun
1666 */
1667 size = DATA_SIZE - cdr.FifoOffset;
1668 if (size > cdsize)
1669 size = cdsize;
1670 if (size > max_words * 4)
1671 size = max_words * 4;
1672 if (size > 0)
1673 {
1674 memcpy(ptr, cdr.Transfer + cdr.FifoOffset, size);
1675 cdr.FifoOffset += size;
1676 }
1677 if (size < cdsize) {
1678 CDR_LOG_I("cdrom: dma3 %d/%d\n", size, cdsize);
1679 memset(ptr + size, cdr.Transfer[0x920], cdsize - size);
1680 }
1681 psxCpu->Clear(madr, cdsize / 4);
1682
1683 set_event(PSXINT_CDRDMA, (cdsize / 4) * 24);
1684
1685 HW_DMA3_CHCR &= SWAPu32(~0x10000000);
1686 if (chcr & 0x100) {
1687 HW_DMA3_MADR = SWAPu32(madr + cdsize);
1688 HW_DMA3_BCR &= SWAPu32(0xffff0000);
1689 }
1690 else {
1691 // halted
1692 psxRegs.cycle += (cdsize/4) * 24 - 20;
1693 }
1694 return;
1695
1696 default:
1697 CDR_LOG_I("psxDma3() Log: Unknown cddma %x\n", chcr);
1698 break;
1699 }
1700
1701 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1702 DMA_INTERRUPT(3);
1703}
1704
1705void cdrDmaInterrupt(void)
1706{
1707 if (HW_DMA3_CHCR & SWAP32(0x01000000))
1708 {
1709 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1710 DMA_INTERRUPT(3);
1711 }
1712}
1713
1714static void getCdInfo(void)
1715{
1716 u8 tmp;
1717
1718 CDR_getTN(cdr.ResultTN);
1719 CDR_getTD(0, cdr.SetSectorEnd);
1720 tmp = cdr.SetSectorEnd[0];
1721 cdr.SetSectorEnd[0] = cdr.SetSectorEnd[2];
1722 cdr.SetSectorEnd[2] = tmp;
1723}
1724
1725void cdrReset() {
1726 memset(&cdr, 0, sizeof(cdr));
1727 cdr.CurTrack = 1;
1728 cdr.FilterFile = 0;
1729 cdr.FilterChannel = 0;
1730 cdr.IrqMask = 0x1f;
1731 cdr.IrqStat = NoIntr;
1732 cdr.FifoOffset = DATA_SIZE; // fifo empty
1733
1734 CDR_getStatus(&stat);
1735 if (stat.Status & STATUS_SHELLOPEN) {
1736 cdr.DriveState = DRIVESTATE_LID_OPEN;
1737 cdr.StatP = STATUS_SHELLOPEN;
1738 }
1739 else if (CdromId[0] == '\0') {
1740 cdr.DriveState = DRIVESTATE_STOPPED;
1741 cdr.StatP = 0;
1742 }
1743 else {
1744 cdr.DriveState = DRIVESTATE_STANDBY;
1745 cdr.StatP = STATUS_ROTATING;
1746 }
1747
1748 // BIOS player - default values
1749 cdr.AttenuatorLeftToLeft = 0x80;
1750 cdr.AttenuatorLeftToRight = 0x00;
1751 cdr.AttenuatorRightToLeft = 0x00;
1752 cdr.AttenuatorRightToRight = 0x80;
1753
1754 getCdInfo();
1755}
1756
1757int cdrFreeze(void *f, int Mode) {
1758 u32 tmp;
1759 u8 tmpp[3];
1760
1761 if (Mode == 0 && !Config.Cdda)
1762 CDR_stop();
1763
1764 cdr.freeze_ver = 0x63647202;
1765 gzfreeze(&cdr, sizeof(cdr));
1766
1767 if (Mode == 1) {
1768 cdr.ParamP = cdr.ParamC;
1769 tmp = cdr.FifoOffset;
1770 }
1771
1772 gzfreeze(&tmp, sizeof(tmp));
1773
1774 if (Mode == 0) {
1775 getCdInfo();
1776
1777 cdr.FifoOffset = tmp < DATA_SIZE ? tmp : DATA_SIZE;
1778 cdr.FifoSize = (cdr.Mode & MODE_SIZE_2340) ? 2340 : 2048 + 12;
1779 if (cdr.SubqForwardSectors > SUBQ_FORWARD_SECTORS)
1780 cdr.SubqForwardSectors = SUBQ_FORWARD_SECTORS;
1781
1782 // read right sub data
1783 tmpp[0] = btoi(cdr.Prev[0]);
1784 tmpp[1] = btoi(cdr.Prev[1]);
1785 tmpp[2] = btoi(cdr.Prev[2]);
1786 cdr.Prev[0]++;
1787 ReadTrack(tmpp);
1788
1789 if (cdr.Play) {
1790 if (cdr.freeze_ver < 0x63647202)
1791 memcpy(cdr.SetSectorPlay, cdr.SetSector, 3);
1792
1793 Find_CurTrack(cdr.SetSectorPlay);
1794 if (!Config.Cdda)
1795 CDR_play(cdr.SetSectorPlay);
1796 }
1797 }
1798
1799 return 0;
1800}
1801
1802void LidInterrupt(void) {
1803 getCdInfo();
1804 cdrLidSeekInterrupt();
1805}