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