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