cdrom: attempt to improve reset
[pcsx_rearmed.git] / libpcsxcore / cdrom.c
... / ...
CommitLineData
1/***************************************************************************
2 * Copyright (C) 2007 Ryan Schultz, PCSX-df Team, PCSX team *
3 * *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
8 * *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, write to the *
16 * Free Software Foundation, Inc., *
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *
18 ***************************************************************************/
19
20/*
21* Handles all CD-ROM registers and functions.
22*/
23
24#include <assert.h>
25#include "cdrom.h"
26#include "misc.h"
27#include "ppf.h"
28#include "psxdma.h"
29#include "psxevents.h"
30#include "arm_features.h"
31
32/* logging */
33#if 0
34#define CDR_LOG SysPrintf
35#else
36#define CDR_LOG(...)
37#endif
38#if 0
39#define CDR_LOG_I SysPrintf
40#else
41#define CDR_LOG_I(fmt, ...) \
42 log_unhandled("%u cdrom: " fmt, psxRegs.cycle, ##__VA_ARGS__)
43#endif
44#if 0
45#define CDR_LOG_IO SysPrintf
46#else
47#define CDR_LOG_IO(...)
48#endif
49//#define CDR_LOG_CMD_IRQ
50
51static struct {
52 // unused members maintain savesate compatibility
53 unsigned char unused0;
54 unsigned char unused1;
55 unsigned char IrqMask;
56 unsigned char unused2;
57 unsigned char Ctrl;
58 unsigned char IrqStat;
59
60 unsigned char StatP;
61
62 unsigned char Transfer[DATA_SIZE];
63 struct {
64 unsigned char Track;
65 unsigned char Index;
66 unsigned char Relative[3];
67 unsigned char Absolute[3];
68 } subq;
69 unsigned char TrackChanged;
70 unsigned char ReportDelay;
71 unsigned char unused3;
72 unsigned short sectorsRead;
73 unsigned int freeze_ver;
74
75 unsigned char Prev[4];
76 unsigned char Param[8];
77 unsigned char Result[16];
78
79 unsigned char ParamC;
80 unsigned char ParamP;
81 unsigned char ResultC;
82 unsigned char ResultP;
83 unsigned char ResultReady;
84 unsigned char Cmd;
85 unsigned char SubqForwardSectors;
86 unsigned char SetlocPending;
87 u32 Reading;
88
89 unsigned char ResultTN[6];
90 unsigned char ResultTD[4];
91 unsigned char SetSectorPlay[4];
92 unsigned char SetSectorEnd[4];
93 unsigned char SetSector[4];
94 unsigned char Track;
95 boolean Play, Muted;
96 int CurTrack;
97 unsigned char Mode;
98 unsigned char FileChannelSelected;
99 unsigned char CurFile, CurChannel;
100 int FilterFile, FilterChannel;
101 unsigned char LocL[8];
102 int unused4;
103
104 xa_decode_t Xa;
105
106 u16 FifoOffset;
107 u16 FifoSize;
108
109 u16 CmdInProgress;
110 u8 Irq1Pending;
111 u8 AdpcmActive;
112 u32 LastReadSeekCycles;
113
114 u8 unused7;
115
116 u8 DriveState; // enum drive_state
117 u8 FastForward;
118 u8 FastBackward;
119 u8 errorRetryhack;
120
121 u8 AttenuatorLeftToLeft, AttenuatorLeftToRight;
122 u8 AttenuatorRightToRight, AttenuatorRightToLeft;
123 u8 AttenuatorLeftToLeftT, AttenuatorLeftToRightT;
124 u8 AttenuatorRightToRightT, AttenuatorRightToLeftT;
125} cdr;
126static s16 read_buf[CD_FRAMESIZE_RAW/2];
127
128/* CD-ROM magic numbers */
129#define CdlSync 0 /* nocash documentation : "Uh, actually, returns error code 40h = Invalid Command...?" */
130#define CdlNop 1
131#define CdlSetloc 2
132#define CdlPlay 3
133#define CdlForward 4
134#define CdlBackward 5
135#define CdlReadN 6
136#define CdlStandby 7
137#define CdlStop 8
138#define CdlPause 9
139#define CdlReset 10
140#define CdlMute 11
141#define CdlDemute 12
142#define CdlSetfilter 13
143#define CdlSetmode 14
144#define CdlGetparam 15
145#define CdlGetlocL 16
146#define CdlGetlocP 17
147#define CdlReadT 18
148#define CdlGetTN 19
149#define CdlGetTD 20
150#define CdlSeekL 21
151#define CdlSeekP 22
152#define CdlSetclock 23
153#define CdlGetclock 24
154#define CdlTest 25
155#define CdlID 26
156#define CdlReadS 27
157#define CdlInit 28
158#define CdlGetQ 29
159#define CdlReadToc 30
160
161#ifdef CDR_LOG_CMD_IRQ
162static const char * const CmdName[0x100] = {
163 "CdlSync", "CdlNop", "CdlSetloc", "CdlPlay",
164 "CdlForward", "CdlBackward", "CdlReadN", "CdlStandby",
165 "CdlStop", "CdlPause", "CdlReset", "CdlMute",
166 "CdlDemute", "CdlSetfilter", "CdlSetmode", "CdlGetparam",
167 "CdlGetlocL", "CdlGetlocP", "CdlReadT", "CdlGetTN",
168 "CdlGetTD", "CdlSeekL", "CdlSeekP", "CdlSetclock",
169 "CdlGetclock", "CdlTest", "CdlID", "CdlReadS",
170 "CdlInit", NULL, "CDlReadToc", NULL
171};
172#endif
173
174unsigned char Test04[] = { 0 };
175unsigned char Test05[] = { 0 };
176unsigned char Test20[] = { 0x98, 0x06, 0x10, 0xC3 };
177unsigned char Test22[] = { 0x66, 0x6F, 0x72, 0x20, 0x45, 0x75, 0x72, 0x6F };
178unsigned char Test23[] = { 0x43, 0x58, 0x44, 0x32, 0x39 ,0x34, 0x30, 0x51 };
179
180// cdr.IrqStat:
181#define NoIntr 0
182#define DataReady 1
183#define Complete 2
184#define Acknowledge 3
185#define DataEnd 4
186#define DiskError 5
187
188/* Modes flags */
189#define MODE_SPEED (1<<7) // 0x80
190#define MODE_STRSND (1<<6) // 0x40 ADPCM on/off
191#define MODE_SIZE_2340 (1<<5) // 0x20
192#define MODE_SIZE_2328 (1<<4) // 0x10
193#define MODE_SIZE_2048 (0<<4) // 0x00
194#define MODE_SF (1<<3) // 0x08 channel on/off
195#define MODE_REPORT (1<<2) // 0x04
196#define MODE_AUTOPAUSE (1<<1) // 0x02
197#define MODE_CDDA (1<<0) // 0x01
198
199/* Status flags */
200#define STATUS_PLAY (1<<7) // 0x80
201#define STATUS_SEEK (1<<6) // 0x40
202#define STATUS_READ (1<<5) // 0x20
203#define STATUS_SHELLOPEN (1<<4) // 0x10
204#define STATUS_UNKNOWN3 (1<<3) // 0x08
205#define STATUS_SEEKERROR (1<<2) // 0x04
206#define STATUS_ROTATING (1<<1) // 0x02
207#define STATUS_ERROR (1<<0) // 0x01
208
209/* Errors */
210#define ERROR_NOTREADY (1<<7) // 0x80
211#define ERROR_INVALIDCMD (1<<6) // 0x40
212#define ERROR_BAD_ARGNUM (1<<5) // 0x20
213#define ERROR_BAD_ARGVAL (1<<4) // 0x10
214#define ERROR_SHELLOPEN (1<<3) // 0x08
215
216// 1x = 75 sectors per second
217// PSXCLK = 1 sec in the ps
218// so (PSXCLK / 75) = cdr read time (linuzappz)
219#define cdReadTime (PSXCLK / 75)
220
221#define LOCL_INVALID 0xff
222#define SUBQ_FORWARD_SECTORS 2u
223
224enum drive_state {
225 DRIVESTATE_STANDBY = 0, // different from paused
226 DRIVESTATE_LID_OPEN,
227 DRIVESTATE_RESCAN_CD,
228 DRIVESTATE_PREPARE_CD,
229 DRIVESTATE_STOPPED,
230 DRIVESTATE_PAUSED,
231 DRIVESTATE_PLAY_READ,
232 DRIVESTATE_SEEK,
233};
234
235static struct CdrStat stat;
236
237static unsigned int msf2sec(const u8 *msf) {
238 return ((msf[0] * 60 + msf[1]) * 75) + msf[2];
239}
240
241// for that weird psemu API..
242static unsigned int fsm2sec(const u8 *msf) {
243 return ((msf[2] * 60 + msf[1]) * 75) + msf[0];
244}
245
246static void sec2msf(unsigned int s, u8 *msf) {
247 msf[0] = s / 75 / 60;
248 s = s - msf[0] * 75 * 60;
249 msf[1] = s / 75;
250 s = s - msf[1] * 75;
251 msf[2] = s;
252}
253
254// cdrPlayReadInterrupt
255#define CDRPLAYREAD_INT(eCycle, isFirst) { \
256 u32 e_ = eCycle; \
257 psxRegs.interrupt |= (1 << PSXINT_CDREAD); \
258 if (isFirst) \
259 psxRegs.intCycle[PSXINT_CDREAD].sCycle = psxRegs.cycle; \
260 else \
261 psxRegs.intCycle[PSXINT_CDREAD].sCycle += psxRegs.intCycle[PSXINT_CDREAD].cycle; \
262 psxRegs.intCycle[PSXINT_CDREAD].cycle = e_; \
263 set_event_raw_abs(PSXINT_CDREAD, psxRegs.intCycle[PSXINT_CDREAD].sCycle + e_); \
264}
265
266#define StopReading() { \
267 cdr.Reading = 0; \
268 psxRegs.interrupt &= ~(1 << PSXINT_CDREAD); \
269}
270
271#define StopCdda() { \
272 if (cdr.Play && !Config.Cdda) CDR_stop(); \
273 cdr.Play = FALSE; \
274 cdr.FastForward = 0; \
275 cdr.FastBackward = 0; \
276}
277
278#define SetPlaySeekRead(x, f) { \
279 x &= ~(STATUS_PLAY | STATUS_SEEK | STATUS_READ); \
280 x |= f; \
281}
282
283#define SetResultSize_(size) { \
284 cdr.ResultP = 0; \
285 cdr.ResultC = size; \
286 cdr.ResultReady = 1; \
287}
288
289#define SetResultSize(size) { \
290 if (cdr.ResultP < cdr.ResultC) \
291 CDR_LOG_I("overwriting result, len=%u\n", cdr.ResultC); \
292 SetResultSize_(size); \
293}
294
295static void setIrq(u8 irq, int log_cmd)
296{
297 u8 old = cdr.IrqStat & cdr.IrqMask ? 1 : 0;
298 u8 new_ = irq & cdr.IrqMask ? 1 : 0;
299
300 cdr.IrqStat = irq;
301 if ((old ^ new_) & new_)
302 psxHu32ref(0x1070) |= SWAP32((u32)0x4);
303
304#ifdef CDR_LOG_CMD_IRQ
305 if (cdr.IrqStat)
306 {
307 int i;
308 CDR_LOG_I("CDR IRQ=%d cmd %02x irqstat %02x: ",
309 !!(cdr.IrqStat & cdr.IrqMask), log_cmd, cdr.IrqStat);
310 for (i = 0; i < cdr.ResultC; i++)
311 SysPrintf("%02x ", cdr.Result[i]);
312 SysPrintf("\n");
313 }
314#endif
315}
316
317// timing used in this function was taken from tests on real hardware
318// (yes it's slow, but you probably don't want to modify it)
319void cdrLidSeekInterrupt(void)
320{
321 CDR_LOG_I("%s cdr.DriveState=%d\n", __func__, cdr.DriveState);
322
323 switch (cdr.DriveState) {
324 default:
325 case DRIVESTATE_STANDBY:
326 StopCdda();
327 //StopReading();
328 SetPlaySeekRead(cdr.StatP, 0);
329
330 if (CDR_getStatus(&stat) == -1)
331 return;
332
333 if (stat.Status & STATUS_SHELLOPEN)
334 {
335 memset(cdr.Prev, 0xff, sizeof(cdr.Prev));
336 cdr.DriveState = DRIVESTATE_LID_OPEN;
337 set_event(PSXINT_CDRLID, 0x800);
338 }
339 break;
340
341 case DRIVESTATE_LID_OPEN:
342 if (CDR_getStatus(&stat) == -1)
343 stat.Status &= ~STATUS_SHELLOPEN;
344
345 // 02, 12, 10
346 if (!(cdr.StatP & STATUS_SHELLOPEN)) {
347 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 cdr.LastReadSeekCycles = psxRegs.cycle;
664
665 if (cdr.Reading) {
666 cdrReadInterrupt();
667 return;
668 }
669
670 if (!cdr.Play) return;
671
672 CDR_LOG("CDDA - %02d:%02d:%02d m %02x\n",
673 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2], cdr.Mode);
674
675 cdr.DriveState = DRIVESTATE_PLAY_READ;
676 SetPlaySeekRead(cdr.StatP, STATUS_PLAY);
677 if (memcmp(cdr.SetSectorPlay, cdr.SetSectorEnd, 3) == 0) {
678 CDR_LOG_I("end stop\n");
679 StopCdda();
680 SetPlaySeekRead(cdr.StatP, 0);
681 cdr.TrackChanged = TRUE;
682 cdr.DriveState = DRIVESTATE_PAUSED;
683 }
684 else {
685 CDR_readCDDA(cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2], (u8 *)read_buf);
686 }
687
688 if (!cdr.IrqStat && (cdr.Mode & (MODE_AUTOPAUSE|MODE_REPORT)))
689 cdrPlayInterrupt_Autopause();
690
691 if (cdr.Play && !Config.Cdda) {
692 cdrPrepCdda(read_buf, CD_FRAMESIZE_RAW / 4);
693 SPU_playCDDAchannel(read_buf, CD_FRAMESIZE_RAW, psxRegs.cycle, 0);
694 }
695
696 msfiAdd(cdr.SetSectorPlay, 1);
697
698 // update for CdlGetlocP/autopause
699 generate_subq(cdr.SetSectorPlay);
700
701 CDRPLAYREAD_INT(cdReadTime, 0);
702}
703
704static void softReset(void)
705{
706 CDR_getStatus(&stat);
707 if (stat.Status & STATUS_SHELLOPEN) {
708 cdr.DriveState = DRIVESTATE_LID_OPEN;
709 cdr.StatP = STATUS_SHELLOPEN;
710 }
711 else if (CdromId[0] == '\0') {
712 cdr.DriveState = DRIVESTATE_STOPPED;
713 cdr.StatP = 0;
714 }
715 else {
716 cdr.DriveState = DRIVESTATE_STANDBY;
717 cdr.StatP = STATUS_ROTATING;
718 }
719
720 cdr.FifoOffset = DATA_SIZE; // fifo empty
721 cdr.LocL[0] = LOCL_INVALID;
722 cdr.Mode = MODE_SIZE_2340;
723 cdr.Muted = FALSE;
724 SPU_setCDvol(cdr.AttenuatorLeftToLeft, cdr.AttenuatorLeftToRight,
725 cdr.AttenuatorRightToLeft, cdr.AttenuatorRightToRight, psxRegs.cycle);
726}
727
728#define CMD_PART2 0x100
729#define CMD_WHILE_NOT_READY 0x200
730
731void cdrInterrupt(void) {
732 int start_rotating = 0;
733 int error = 0;
734 u32 cycles, seekTime = 0;
735 u32 second_resp_time = 0;
736 const void *buf;
737 u8 ParamC;
738 u8 set_loc[3];
739 int read_ok;
740 u16 not_ready = 0;
741 u8 IrqStat = Acknowledge;
742 u8 DriveStateOld;
743 u16 Cmd;
744 int i;
745
746 if (cdr.IrqStat) {
747 CDR_LOG_I("cmd %02x with irqstat %x\n",
748 cdr.CmdInProgress, cdr.IrqStat);
749 return;
750 }
751 if (cdr.Irq1Pending) {
752 // hand out the "newest" sector, according to nocash
753 cdrUpdateTransferBuf(CDR_getBuffer());
754 CDR_LOG_I("%x:%02x:%02x loaded on ack, cmd=%02x res=%02x\n",
755 cdr.Transfer[0], cdr.Transfer[1], cdr.Transfer[2],
756 cdr.CmdInProgress, cdr.Irq1Pending);
757 SetResultSize(1);
758 cdr.Result[0] = cdr.Irq1Pending;
759 cdr.Irq1Pending = 0;
760 setIrq((cdr.Irq1Pending & STATUS_ERROR) ? DiskError : DataReady, 0x1003);
761 return;
762 }
763
764 // default response
765 SetResultSize(1);
766 cdr.Result[0] = cdr.StatP;
767
768 Cmd = cdr.CmdInProgress;
769 cdr.CmdInProgress = 0;
770 ParamC = cdr.ParamC;
771
772 if (Cmd < 0x100) {
773 cdr.Ctrl &= ~0x80;
774 cdr.ParamC = 0;
775 cdr.Cmd = 0;
776 }
777
778 switch (cdr.DriveState) {
779 case DRIVESTATE_PREPARE_CD:
780 if (Cmd > 2) {
781 // Syphon filter 2 expects commands to work shortly after it sees
782 // STATUS_ROTATING, so give up trying to emulate the startup seq
783 cdr.DriveState = DRIVESTATE_STANDBY;
784 cdr.StatP &= ~STATUS_SEEK;
785 psxRegs.interrupt &= ~(1 << PSXINT_CDRLID);
786 break;
787 }
788 // fallthrough
789 case DRIVESTATE_LID_OPEN:
790 case DRIVESTATE_RESCAN_CD:
791 // no disk or busy with the initial scan, allowed cmds are limited
792 not_ready = CMD_WHILE_NOT_READY;
793 break;
794 }
795
796 switch (Cmd | not_ready) {
797 case CdlNop:
798 case CdlNop + CMD_WHILE_NOT_READY:
799 if (cdr.DriveState != DRIVESTATE_LID_OPEN)
800 cdr.StatP &= ~STATUS_SHELLOPEN;
801 break;
802
803 case CdlSetloc:
804 // case CdlSetloc + CMD_WHILE_NOT_READY: // or is it?
805 CDR_LOG("CDROM setloc command (%02X, %02X, %02X)\n", cdr.Param[0], cdr.Param[1], cdr.Param[2]);
806
807 // MM must be BCD, SS must be BCD and <0x60, FF must be BCD and <0x75
808 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))
809 {
810 CDR_LOG_I("Invalid/out of range seek to %02X:%02X:%02X\n", cdr.Param[0], cdr.Param[1], cdr.Param[2]);
811 if (++cdr.errorRetryhack > 100)
812 break;
813 error = ERROR_BAD_ARGNUM;
814 goto set_error;
815 }
816 else
817 {
818 for (i = 0; i < 3; i++)
819 set_loc[i] = btoi(cdr.Param[i]);
820 memcpy(cdr.SetSector, set_loc, 3);
821 cdr.SetSector[3] = 0;
822 cdr.SetlocPending = 1;
823 cdr.errorRetryhack = 0;
824 }
825 break;
826
827 do_CdlPlay:
828 case CdlPlay:
829 StopCdda();
830 StopReading();
831
832 cdr.FastBackward = 0;
833 cdr.FastForward = 0;
834
835 // BIOS CD Player
836 // - Pause player, hit Track 01/02/../xx (Setloc issued!!)
837
838 if (ParamC != 0 && cdr.Param[0] != 0) {
839 int track = btoi( cdr.Param[0] );
840
841 if (track <= cdr.ResultTN[1])
842 cdr.CurTrack = track;
843
844 CDR_LOG("PLAY track %d\n", cdr.CurTrack);
845
846 if (CDR_getTD((u8)cdr.CurTrack, cdr.ResultTD) != -1) {
847 for (i = 0; i < 3; i++)
848 set_loc[i] = cdr.ResultTD[2 - i];
849 seekTime = cdrSeekTime(set_loc);
850 memcpy(cdr.SetSectorPlay, set_loc, 3);
851 }
852 }
853 else if (cdr.SetlocPending) {
854 seekTime = cdrSeekTime(cdr.SetSector);
855 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
856 }
857 else {
858 CDR_LOG("PLAY Resume @ %d:%d:%d\n",
859 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2]);
860 }
861 cdr.SetlocPending = 0;
862
863 /*
864 Rayman: detect track changes
865 - fixes logo freeze
866
867 Twisted Metal 2: skip PREGAP + starting accurate SubQ
868 - plays tracks without retry play
869
870 Wild 9: skip PREGAP + starting accurate SubQ
871 - plays tracks without retry play
872 */
873 Find_CurTrack(cdr.SetSectorPlay);
874 generate_subq(cdr.SetSectorPlay);
875 cdr.LocL[0] = LOCL_INVALID;
876 cdr.SubqForwardSectors = 1;
877 cdr.TrackChanged = FALSE;
878 cdr.FileChannelSelected = 0;
879 cdr.AdpcmActive = 0;
880 cdr.ReportDelay = 60;
881 cdr.sectorsRead = 0;
882
883 if (!Config.Cdda)
884 CDR_play(cdr.SetSectorPlay);
885
886 SetPlaySeekRead(cdr.StatP, STATUS_SEEK | STATUS_ROTATING);
887
888 // BIOS player - set flag again
889 cdr.Play = TRUE;
890 cdr.DriveState = DRIVESTATE_PLAY_READ;
891
892 CDRPLAYREAD_INT(cdReadTime + seekTime, 1);
893 start_rotating = 1;
894 break;
895
896 case CdlForward:
897 // TODO: error 80 if stopped
898 IrqStat = Complete;
899
900 // GameShark CD Player: Calls 2x + Play 2x
901 cdr.FastForward = 1;
902 cdr.FastBackward = 0;
903 break;
904
905 case CdlBackward:
906 IrqStat = Complete;
907
908 // GameShark CD Player: Calls 2x + Play 2x
909 cdr.FastBackward = 1;
910 cdr.FastForward = 0;
911 break;
912
913 case CdlStandby:
914 if (cdr.DriveState != DRIVESTATE_STOPPED) {
915 error = ERROR_BAD_ARGNUM;
916 goto set_error;
917 }
918 cdr.DriveState = DRIVESTATE_STANDBY;
919 second_resp_time = cdReadTime * 125 / 2;
920 start_rotating = 1;
921 break;
922
923 case CdlStandby + CMD_PART2:
924 IrqStat = Complete;
925 break;
926
927 case CdlStop:
928 if (cdr.Play) {
929 // grab time for current track
930 CDR_getTD((u8)(cdr.CurTrack), cdr.ResultTD);
931
932 cdr.SetSectorPlay[0] = cdr.ResultTD[2];
933 cdr.SetSectorPlay[1] = cdr.ResultTD[1];
934 cdr.SetSectorPlay[2] = cdr.ResultTD[0];
935 }
936
937 StopCdda();
938 StopReading();
939 SetPlaySeekRead(cdr.StatP, 0);
940 cdr.StatP &= ~STATUS_ROTATING;
941 cdr.LocL[0] = LOCL_INVALID;
942
943 second_resp_time = 0x800;
944 if (cdr.DriveState != DRIVESTATE_STOPPED)
945 second_resp_time = cdReadTime * 30 / 2;
946
947 cdr.DriveState = DRIVESTATE_STOPPED;
948 break;
949
950 case CdlStop + CMD_PART2:
951 IrqStat = Complete;
952 break;
953
954 case CdlPause:
955 if (cdr.AdpcmActive) {
956 cdr.AdpcmActive = 0;
957 cdr.Xa.nsamples = 0;
958 SPU_playADPCMchannel(&cdr.Xa, psxRegs.cycle, 1); // flush adpcm
959 }
960 StopCdda();
961 StopReading();
962
963 // how the drive maintains the position while paused is quite
964 // complicated, this is the minimum to make "Bedlam" happy
965 msfiSub(cdr.SetSectorPlay, MIN_VALUE(cdr.sectorsRead, 4));
966 cdr.sectorsRead = 0;
967
968 /*
969 Gundam Battle Assault 2: much slower (*)
970 - Fixes boot, gameplay
971
972 Hokuto no Ken 2: slower
973 - Fixes intro + subtitles
974
975 InuYasha - Feudal Fairy Tale: slower
976 - Fixes battles
977 */
978 /* Gameblabla - Tightening the timings (as taken from Duckstation).
979 * The timings from Duckstation are based upon hardware tests.
980 * Mednafen's timing don't work for Gundam Battle Assault 2 in PAL/50hz mode,
981 * seems to be timing sensitive as it can depend on the CPU's clock speed.
982 * */
983 if (!(cdr.StatP & (STATUS_PLAY | STATUS_READ)))
984 {
985 second_resp_time = 7000;
986 }
987 else
988 {
989 second_resp_time = (((cdr.Mode & MODE_SPEED) ? 1 : 2) * 1097107);
990 }
991 SetPlaySeekRead(cdr.StatP, 0);
992 DriveStateOld = cdr.DriveState;
993 cdr.DriveState = DRIVESTATE_PAUSED;
994 if (DriveStateOld == DRIVESTATE_SEEK) {
995 // According to Duckstation this fails, but the
996 // exact conditions and effects are not clear.
997 // Moto Racer World Tour seems to rely on this.
998 // For now assume pause works anyway, just errors out.
999 error = ERROR_NOTREADY;
1000 goto set_error;
1001 }
1002 break;
1003
1004 case CdlPause + CMD_PART2:
1005 IrqStat = Complete;
1006 break;
1007
1008 case CdlReset:
1009 case CdlReset + CMD_WHILE_NOT_READY:
1010 // note: nocash and Duckstation calls this 'Init', but
1011 // the official SDK calls it 'Reset', and so do we
1012 StopCdda();
1013 StopReading();
1014 softReset();
1015 second_resp_time = not_ready ? 70000 : 4100000;
1016 start_rotating = 1;
1017 break;
1018
1019 case CdlReset + CMD_PART2:
1020 case CdlReset + CMD_PART2 + CMD_WHILE_NOT_READY:
1021 IrqStat = Complete;
1022 break;
1023
1024 case CdlMute:
1025 cdr.Muted = TRUE;
1026 SPU_setCDvol(0, 0, 0, 0, psxRegs.cycle);
1027 break;
1028
1029 case CdlDemute:
1030 cdr.Muted = FALSE;
1031 SPU_setCDvol(cdr.AttenuatorLeftToLeft, cdr.AttenuatorLeftToRight,
1032 cdr.AttenuatorRightToLeft, cdr.AttenuatorRightToRight, psxRegs.cycle);
1033 break;
1034
1035 case CdlSetfilter:
1036 cdr.FilterFile = cdr.Param[0];
1037 cdr.FilterChannel = cdr.Param[1];
1038 cdr.FileChannelSelected = 0;
1039 break;
1040
1041 case CdlSetmode:
1042 case CdlSetmode + CMD_WHILE_NOT_READY:
1043 CDR_LOG("cdrWrite1() Log: Setmode %x\n", cdr.Param[0]);
1044 cdr.Mode = cdr.Param[0];
1045 break;
1046
1047 case CdlGetparam:
1048 case CdlGetparam + CMD_WHILE_NOT_READY:
1049 /* Gameblabla : According to mednafen, Result size should be 5 and done this way. */
1050 SetResultSize_(5);
1051 cdr.Result[1] = cdr.Mode;
1052 cdr.Result[2] = 0;
1053 cdr.Result[3] = cdr.FilterFile;
1054 cdr.Result[4] = cdr.FilterChannel;
1055 break;
1056
1057 case CdlGetlocL:
1058 if (cdr.LocL[0] == LOCL_INVALID) {
1059 error = 0x80;
1060 goto set_error;
1061 }
1062 SetResultSize_(8);
1063 memcpy(cdr.Result, cdr.LocL, 8);
1064 break;
1065
1066 case CdlGetlocP:
1067 SetResultSize_(8);
1068 memcpy(&cdr.Result, &cdr.subq, 8);
1069 break;
1070
1071 case CdlReadT: // SetSession?
1072 // really long
1073 second_resp_time = cdReadTime * 290 / 4;
1074 start_rotating = 1;
1075 break;
1076
1077 case CdlReadT + CMD_PART2:
1078 IrqStat = Complete;
1079 break;
1080
1081 case CdlGetTN:
1082 if (CDR_getTN(cdr.ResultTN) == -1) {
1083 assert(0);
1084 }
1085 SetResultSize_(3);
1086 cdr.Result[1] = itob(cdr.ResultTN[0]);
1087 cdr.Result[2] = itob(cdr.ResultTN[1]);
1088 break;
1089
1090 case CdlGetTD:
1091 cdr.Track = btoi(cdr.Param[0]);
1092 if (CDR_getTD(cdr.Track, cdr.ResultTD) == -1) {
1093 error = ERROR_BAD_ARGVAL;
1094 goto set_error;
1095 }
1096 SetResultSize_(3);
1097 cdr.Result[1] = itob(cdr.ResultTD[2]);
1098 cdr.Result[2] = itob(cdr.ResultTD[1]);
1099 // no sector number
1100 //cdr.Result[3] = itob(cdr.ResultTD[0]);
1101 break;
1102
1103 case CdlSeekL:
1104 case CdlSeekP:
1105 StopCdda();
1106 StopReading();
1107 SetPlaySeekRead(cdr.StatP, STATUS_SEEK | STATUS_ROTATING);
1108
1109 seekTime = cdrSeekTime(cdr.SetSector);
1110 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
1111 cdr.DriveState = DRIVESTATE_SEEK;
1112 /*
1113 Crusaders of Might and Magic = 0.5x-4x
1114 - fix cutscene speech start
1115
1116 Eggs of Steel = 2x-?
1117 - fix new game
1118
1119 Medievil = ?-4x
1120 - fix cutscene speech
1121
1122 Rockman X5 = 0.5-4x
1123 - fix capcom logo
1124 */
1125 second_resp_time = cdReadTime + seekTime;
1126 start_rotating = 1;
1127 break;
1128
1129 case CdlSeekL + CMD_PART2:
1130 case CdlSeekP + CMD_PART2:
1131 SetPlaySeekRead(cdr.StatP, 0);
1132 cdr.Result[0] = cdr.StatP;
1133 IrqStat = Complete;
1134
1135 Find_CurTrack(cdr.SetSectorPlay);
1136 read_ok = ReadTrack(cdr.SetSectorPlay);
1137 if (read_ok && (buf = CDR_getBuffer()))
1138 memcpy(cdr.LocL, buf, 8);
1139 UpdateSubq(cdr.SetSectorPlay);
1140 cdr.DriveState = DRIVESTATE_STANDBY;
1141 cdr.TrackChanged = FALSE;
1142 cdr.LastReadSeekCycles = psxRegs.cycle;
1143 break;
1144
1145 case CdlTest:
1146 case CdlTest + CMD_WHILE_NOT_READY:
1147 switch (cdr.Param[0]) {
1148 case 0x20: // System Controller ROM Version
1149 SetResultSize_(4);
1150 memcpy(cdr.Result, Test20, 4);
1151 break;
1152 case 0x22:
1153 SetResultSize_(8);
1154 memcpy(cdr.Result, Test22, 4);
1155 break;
1156 case 0x23: case 0x24:
1157 SetResultSize_(8);
1158 memcpy(cdr.Result, Test23, 4);
1159 break;
1160 }
1161 break;
1162
1163 case CdlID:
1164 second_resp_time = 20480;
1165 break;
1166
1167 case CdlID + CMD_PART2:
1168 SetResultSize_(8);
1169 cdr.Result[0] = cdr.StatP;
1170 cdr.Result[1] = 0;
1171 cdr.Result[2] = 0;
1172 cdr.Result[3] = 0;
1173
1174 // 0x10 - audio | 0x40 - disk missing | 0x80 - unlicensed
1175 if (CDR_getStatus(&stat) == -1 || stat.Type == 0 || stat.Type == 0xff) {
1176 cdr.Result[1] = 0xc0;
1177 }
1178 else {
1179 if (stat.Type == 2)
1180 cdr.Result[1] |= 0x10;
1181 if (CdromId[0] == '\0')
1182 cdr.Result[1] |= 0x80;
1183 }
1184 cdr.Result[0] |= (cdr.Result[1] >> 4) & 0x08;
1185 CDR_LOG_I("CdlID: %02x %02x %02x %02x\n", cdr.Result[0],
1186 cdr.Result[1], cdr.Result[2], cdr.Result[3]);
1187
1188 /* This adds the string "PCSX" in Playstation bios boot screen */
1189 memcpy((char *)&cdr.Result[4], "PCSX", 4);
1190 IrqStat = Complete;
1191 break;
1192
1193 case CdlInit:
1194 case CdlInit + CMD_WHILE_NOT_READY:
1195 StopCdda();
1196 StopReading();
1197 SetPlaySeekRead(cdr.StatP, 0);
1198 // yes, it really sets STATUS_SHELLOPEN
1199 cdr.StatP |= STATUS_SHELLOPEN;
1200 cdr.DriveState = DRIVESTATE_RESCAN_CD;
1201 set_event(PSXINT_CDRLID, 20480);
1202 start_rotating = 1;
1203 break;
1204
1205 case CdlGetQ:
1206 case CdlGetQ + CMD_WHILE_NOT_READY:
1207 break;
1208
1209 case CdlReadToc:
1210 case CdlReadToc + CMD_WHILE_NOT_READY:
1211 cdr.LocL[0] = LOCL_INVALID;
1212 second_resp_time = cdReadTime * 180 / 4;
1213 start_rotating = 1;
1214 break;
1215
1216 case CdlReadToc + CMD_PART2:
1217 case CdlReadToc + CMD_PART2 + CMD_WHILE_NOT_READY:
1218 IrqStat = Complete;
1219 break;
1220
1221 case CdlReadN:
1222 case CdlReadS:
1223 if (cdr.Reading && !cdr.SetlocPending)
1224 break;
1225
1226 Find_CurTrack(cdr.SetlocPending ? cdr.SetSector : cdr.SetSectorPlay);
1227
1228 if ((cdr.Mode & MODE_CDDA) && cdr.CurTrack > 1)
1229 // Read* acts as play for cdda tracks in cdda mode
1230 goto do_CdlPlay;
1231
1232 StopCdda();
1233 if (cdr.SetlocPending) {
1234 seekTime = cdrSeekTime(cdr.SetSector);
1235 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
1236 cdr.SetlocPending = 0;
1237 }
1238 cdr.Reading = 1;
1239 cdr.FileChannelSelected = 0;
1240 cdr.AdpcmActive = 0;
1241
1242 // Fighting Force 2 - update subq time immediately
1243 // - fixes new game
1244 UpdateSubq(cdr.SetSectorPlay);
1245 cdr.LocL[0] = LOCL_INVALID;
1246 cdr.SubqForwardSectors = 1;
1247 cdr.sectorsRead = 0;
1248 cdr.DriveState = DRIVESTATE_SEEK;
1249
1250 cycles = (cdr.Mode & MODE_SPEED) ? cdReadTime : cdReadTime * 2;
1251 cycles += seekTime;
1252 if (Config.hacks.cdr_read_timing)
1253 cycles = cdrAlignTimingHack(cycles);
1254 CDRPLAYREAD_INT(cycles, 1);
1255
1256 SetPlaySeekRead(cdr.StatP, STATUS_SEEK);
1257 start_rotating = 1;
1258 break;
1259
1260 case CdlSync:
1261 default:
1262 error = ERROR_INVALIDCMD;
1263 // FALLTHROUGH
1264
1265 set_error:
1266 SetResultSize_(2);
1267 cdr.Result[0] = cdr.StatP | STATUS_ERROR;
1268 cdr.Result[1] = not_ready ? ERROR_NOTREADY : error;
1269 IrqStat = DiskError;
1270 CDR_LOG_I("cmd %02x error %02x\n", Cmd, cdr.Result[1]);
1271 break;
1272 }
1273
1274 if (cdr.DriveState == DRIVESTATE_STOPPED && start_rotating) {
1275 cdr.DriveState = DRIVESTATE_STANDBY;
1276 cdr.StatP |= STATUS_ROTATING;
1277 }
1278
1279 if (second_resp_time) {
1280 cdr.CmdInProgress = Cmd | 0x100;
1281 set_event(PSXINT_CDR, second_resp_time);
1282 }
1283 else if (cdr.Cmd && cdr.Cmd != (Cmd & 0xff)) {
1284 cdr.CmdInProgress = cdr.Cmd;
1285 CDR_LOG_I("cmd %02x came before %02x finished\n", cdr.Cmd, Cmd);
1286 }
1287
1288 setIrq(IrqStat, Cmd);
1289}
1290
1291#ifdef HAVE_ARMV7
1292 #define ssat32_to_16(v) \
1293 asm("ssat %0,#16,%1" : "=r" (v) : "r" (v))
1294#else
1295 #define ssat32_to_16(v) do { \
1296 if (v < -32768) v = -32768; \
1297 else if (v > 32767) v = 32767; \
1298 } while (0)
1299#endif
1300
1301static void cdrPrepCdda(s16 *buf, int samples)
1302{
1303#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
1304 int i;
1305 for (i = 0; i < samples; i++) {
1306 buf[i * 2 + 0] = SWAP16(buf[i * 2 + 0]);
1307 buf[i * 2 + 1] = SWAP16(buf[i * 2 + 1]);
1308 }
1309#endif
1310}
1311
1312static void cdrReadInterruptSetResult(unsigned char result)
1313{
1314 if (cdr.IrqStat) {
1315 CDR_LOG_I("%d:%02d:%02d irq miss, cmd=%02x irqstat=%02x\n",
1316 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2],
1317 cdr.CmdInProgress, cdr.IrqStat);
1318 cdr.Irq1Pending = result;
1319 return;
1320 }
1321 SetResultSize(1);
1322 cdr.Result[0] = result;
1323 setIrq((result & STATUS_ERROR) ? DiskError : DataReady, 0x1004);
1324}
1325
1326static void cdrUpdateTransferBuf(const u8 *buf)
1327{
1328 if (!buf)
1329 return;
1330 memcpy(cdr.Transfer, buf, DATA_SIZE);
1331 CheckPPFCache(cdr.Transfer, cdr.Prev[0], cdr.Prev[1], cdr.Prev[2]);
1332 CDR_LOG("cdr.Transfer %02x:%02x:%02x\n",
1333 cdr.Transfer[0], cdr.Transfer[1], cdr.Transfer[2]);
1334 if (cdr.FifoOffset < 2048 + 12)
1335 CDR_LOG("FifoOffset(1) %d/%d\n", cdr.FifoOffset, cdr.FifoSize);
1336}
1337
1338static void cdrReadInterrupt(void)
1339{
1340 const struct { u8 file, chan, mode, coding; } *subhdr;
1341 const u8 *buf = NULL;
1342 int deliver_data = 1;
1343 u8 subqPos[3];
1344 int read_ok;
1345 int is_start;
1346
1347 memcpy(subqPos, cdr.SetSectorPlay, sizeof(subqPos));
1348 msfiAdd(subqPos, cdr.SubqForwardSectors);
1349 UpdateSubq(subqPos);
1350 if (cdr.SubqForwardSectors < SUBQ_FORWARD_SECTORS) {
1351 cdr.SubqForwardSectors++;
1352 CDRPLAYREAD_INT((cdr.Mode & MODE_SPEED) ? (cdReadTime / 2) : cdReadTime, 0);
1353 return;
1354 }
1355
1356 // note: CdlGetlocL should work as soon as STATUS_READ is indicated
1357 SetPlaySeekRead(cdr.StatP, STATUS_READ | STATUS_ROTATING);
1358 cdr.DriveState = DRIVESTATE_PLAY_READ;
1359 cdr.sectorsRead++;
1360
1361 read_ok = ReadTrack(cdr.SetSectorPlay);
1362 if (read_ok)
1363 buf = CDR_getBuffer();
1364 if (buf == NULL)
1365 read_ok = 0;
1366
1367 if (!read_ok) {
1368 CDR_LOG_I("cdrReadInterrupt() Log: err\n");
1369 cdrReadInterruptSetResult(cdr.StatP | STATUS_ERROR);
1370 cdr.DriveState = DRIVESTATE_PAUSED; // ?
1371 return;
1372 }
1373 memcpy(cdr.LocL, buf, 8);
1374
1375 if (!cdr.IrqStat && !cdr.Irq1Pending)
1376 cdrUpdateTransferBuf(buf);
1377
1378 subhdr = (void *)(buf + 4);
1379 do {
1380 // try to process as adpcm
1381 if (!(cdr.Mode & MODE_STRSND))
1382 break;
1383 if (buf[3] != 2 || (subhdr->mode & 0x44) != 0x44) // or 0x64?
1384 break;
1385 CDR_LOG("f=%d m=%d %d,%3d | %d,%2d | %d,%2d\n", !!(cdr.Mode & MODE_SF), cdr.Muted,
1386 subhdr->file, subhdr->chan, cdr.CurFile, cdr.CurChannel, cdr.FilterFile, cdr.FilterChannel);
1387 if ((cdr.Mode & MODE_SF) && (subhdr->file != cdr.FilterFile || subhdr->chan != cdr.FilterChannel))
1388 break;
1389 if (subhdr->chan & 0xe0) { // ?
1390 if (subhdr->chan != 0xff)
1391 log_unhandled("adpcm %d:%d\n", subhdr->file, subhdr->chan);
1392 break;
1393 }
1394 if (!cdr.FileChannelSelected) {
1395 cdr.CurFile = subhdr->file;
1396 cdr.CurChannel = subhdr->chan;
1397 cdr.FileChannelSelected = 1;
1398 }
1399 else if (subhdr->file != cdr.CurFile || subhdr->chan != cdr.CurChannel)
1400 break;
1401
1402 // accepted as adpcm
1403 deliver_data = 0;
1404
1405 if (Config.Xa)
1406 break;
1407 is_start = !cdr.AdpcmActive;
1408 cdr.AdpcmActive = !xa_decode_sector(&cdr.Xa, buf + 4, is_start);
1409 if (cdr.AdpcmActive)
1410 SPU_playADPCMchannel(&cdr.Xa, psxRegs.cycle, is_start);
1411 } while (0);
1412
1413 if ((cdr.Mode & MODE_SF) && (subhdr->mode & 0x44) == 0x44) // according to nocash
1414 deliver_data = 0;
1415
1416 /*
1417 Croc 2: $40 - only FORM1 (*)
1418 Judge Dredd: $C8 - only FORM1 (*)
1419 Sim Theme Park - no adpcm at all (zero)
1420 */
1421
1422 if (deliver_data)
1423 cdrReadInterruptSetResult(cdr.StatP);
1424
1425 msfiAdd(cdr.SetSectorPlay, 1);
1426
1427 CDRPLAYREAD_INT((cdr.Mode & MODE_SPEED) ? (cdReadTime / 2) : cdReadTime, 0);
1428}
1429
1430/*
1431cdrRead0:
1432 bit 0,1 - reg index
1433 bit 2 - adpcm active
1434 bit 5 - 1 result ready
1435 bit 6 - 1 dma ready
1436 bit 7 - 1 command being processed
1437*/
1438
1439unsigned char cdrRead0(void) {
1440 cdr.Ctrl &= ~0x24;
1441 cdr.Ctrl |= cdr.AdpcmActive << 2;
1442 cdr.Ctrl |= cdr.ResultReady << 5;
1443
1444 cdr.Ctrl |= 0x40; // data fifo not empty
1445
1446 // What means the 0x10 and the 0x08 bits? I only saw it used by the bios
1447 cdr.Ctrl |= 0x18;
1448
1449 CDR_LOG_IO("cdr r0.sta: %02x\n", cdr.Ctrl);
1450
1451 return psxHu8(0x1800) = cdr.Ctrl;
1452}
1453
1454void cdrWrite0(unsigned char rt) {
1455 CDR_LOG_IO("cdr w0.x.idx: %02x\n", rt);
1456
1457 cdr.Ctrl = (rt & 3) | (cdr.Ctrl & ~3);
1458}
1459
1460unsigned char cdrRead1(void) {
1461 if ((cdr.ResultP & 0xf) < cdr.ResultC)
1462 psxHu8(0x1801) = cdr.Result[cdr.ResultP & 0xf];
1463 else
1464 psxHu8(0x1801) = 0;
1465 cdr.ResultP++;
1466 if (cdr.ResultP == cdr.ResultC)
1467 cdr.ResultReady = 0;
1468
1469 CDR_LOG_IO("cdr r1.x.rsp: %02x #%u\n", psxHu8(0x1801), cdr.ResultP - 1);
1470
1471 return psxHu8(0x1801);
1472}
1473
1474void cdrWrite1(unsigned char rt) {
1475 const char *rnames[] = { "0.cmd", "1.smd", "2.smc", "3.arr" }; (void)rnames;
1476 CDR_LOG_IO("cdr w1.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1477
1478 switch (cdr.Ctrl & 3) {
1479 case 0:
1480 break;
1481 case 3:
1482 cdr.AttenuatorRightToRightT = rt;
1483 return;
1484 default:
1485 return;
1486 }
1487
1488#ifdef CDR_LOG_CMD_IRQ
1489 CDR_LOG_I("CD1 write: %x (%s)", rt, CmdName[rt]);
1490 if (cdr.ParamC) {
1491 int i;
1492 SysPrintf(" Param[%d] = {", cdr.ParamC);
1493 for (i = 0; i < cdr.ParamC; i++)
1494 SysPrintf(" %x,", cdr.Param[i]);
1495 SysPrintf("}");
1496 }
1497 SysPrintf(" @%08x\n", psxRegs.pc);
1498#endif
1499
1500 cdr.ResultReady = 0;
1501 cdr.Ctrl |= 0x80;
1502
1503 if (!cdr.CmdInProgress) {
1504 cdr.CmdInProgress = rt;
1505 // should be something like 12k + controller delays
1506 set_event(PSXINT_CDR, 5000);
1507 }
1508 else {
1509 CDR_LOG_I("cmd while busy: %02x, prev %02x, busy %02x\n",
1510 rt, cdr.Cmd, cdr.CmdInProgress);
1511 if (cdr.CmdInProgress < 0x100) // no pending 2nd response
1512 cdr.CmdInProgress = rt;
1513 }
1514
1515 cdr.Cmd = rt;
1516}
1517
1518unsigned char cdrRead2(void) {
1519 unsigned char ret = cdr.Transfer[0x920];
1520
1521 if (cdr.FifoOffset < cdr.FifoSize)
1522 ret = cdr.Transfer[cdr.FifoOffset++];
1523 else
1524 CDR_LOG_I("read empty fifo (%d)\n", cdr.FifoSize);
1525
1526 CDR_LOG_IO("cdr r2.x.dat: %02x\n", ret);
1527 return ret;
1528}
1529
1530void cdrWrite2(unsigned char rt) {
1531 const char *rnames[] = { "0.prm", "1.ien", "2.all", "3.arl" }; (void)rnames;
1532 CDR_LOG_IO("cdr w2.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1533
1534 switch (cdr.Ctrl & 3) {
1535 case 0:
1536 if (cdr.ParamC < 8) // FIXME: size and wrapping
1537 cdr.Param[cdr.ParamC++] = rt;
1538 return;
1539 case 1:
1540 cdr.IrqMask = rt;
1541 setIrq(cdr.IrqStat, 0x1005);
1542 return;
1543 case 2:
1544 cdr.AttenuatorLeftToLeftT = rt;
1545 return;
1546 case 3:
1547 cdr.AttenuatorRightToLeftT = rt;
1548 return;
1549 }
1550}
1551
1552unsigned char cdrRead3(void) {
1553 if (cdr.Ctrl & 0x1)
1554 psxHu8(0x1803) = cdr.IrqStat | 0xE0;
1555 else
1556 psxHu8(0x1803) = cdr.IrqMask | 0xE0;
1557
1558 CDR_LOG_IO("cdr r3.%d.%s: %02x\n", cdr.Ctrl & 3,
1559 (cdr.Ctrl & 1) ? "ifl" : "ien", psxHu8(0x1803));
1560 return psxHu8(0x1803);
1561}
1562
1563void cdrWrite3(unsigned char rt) {
1564 const char *rnames[] = { "0.req", "1.ifl", "2.alr", "3.ava" }; (void)rnames;
1565 u8 ll, lr, rl, rr;
1566 CDR_LOG_IO("cdr w3.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1567
1568 switch (cdr.Ctrl & 3) {
1569 case 0:
1570 break; // transfer
1571 case 1:
1572 if (cdr.IrqStat & rt) {
1573 u32 nextCycle = psxRegs.intCycle[PSXINT_CDR].sCycle
1574 + psxRegs.intCycle[PSXINT_CDR].cycle;
1575 int pending = psxRegs.interrupt & (1 << PSXINT_CDR);
1576#ifdef CDR_LOG_CMD_IRQ
1577 CDR_LOG_I("ack %02x (w=%02x p=%d,%x,%x,%d)\n",
1578 cdr.IrqStat & rt, rt, !!pending, cdr.CmdInProgress,
1579 cdr.Irq1Pending, nextCycle - psxRegs.cycle);
1580#endif
1581 // note: Croc, Shadow Tower (more) vs Discworld Noir (<993)
1582 if (!pending && (cdr.CmdInProgress || cdr.Irq1Pending))
1583 {
1584 s32 c = 2048;
1585 if (cdr.CmdInProgress) {
1586 c = 2048 - (psxRegs.cycle - nextCycle);
1587 c = MAX_VALUE(c, 512);
1588 }
1589 set_event(PSXINT_CDR, c);
1590 }
1591 }
1592 cdr.IrqStat &= ~rt;
1593
1594 if (rt & 0x40)
1595 cdr.ParamC = 0;
1596 return;
1597 case 2:
1598 cdr.AttenuatorLeftToRightT = rt;
1599 return;
1600 case 3:
1601 if (rt & 0x01)
1602 log_unhandled("Mute ADPCM?\n");
1603 if (rt & 0x20) {
1604 ll = cdr.AttenuatorLeftToLeftT; lr = cdr.AttenuatorLeftToRightT;
1605 rl = cdr.AttenuatorRightToLeftT; rr = cdr.AttenuatorRightToRightT;
1606 if (ll == cdr.AttenuatorLeftToLeft &&
1607 lr == cdr.AttenuatorLeftToRight &&
1608 rl == cdr.AttenuatorRightToLeft &&
1609 rr == cdr.AttenuatorRightToRight)
1610 return;
1611 cdr.AttenuatorLeftToLeft = ll; cdr.AttenuatorLeftToRight = lr;
1612 cdr.AttenuatorRightToLeft = rl; cdr.AttenuatorRightToRight = rr;
1613 CDR_LOG_I("CD-XA Volume: %02x %02x | %02x %02x\n", ll, lr, rl, rr);
1614 SPU_setCDvol(ll, lr, rl, rr, psxRegs.cycle);
1615 }
1616 return;
1617 }
1618
1619 // test: Viewpoint
1620 if ((rt & 0x80) && cdr.FifoOffset < cdr.FifoSize) {
1621 CDR_LOG("cdrom: FifoOffset(2) %d/%d\n", cdr.FifoOffset, cdr.FifoSize);
1622 }
1623 else if (rt & 0x80) {
1624 switch (cdr.Mode & (MODE_SIZE_2328|MODE_SIZE_2340)) {
1625 case MODE_SIZE_2328:
1626 case 0x00:
1627 cdr.FifoOffset = 12;
1628 cdr.FifoSize = 2048 + 12;
1629 break;
1630
1631 case MODE_SIZE_2340:
1632 default:
1633 cdr.FifoOffset = 0;
1634 cdr.FifoSize = 2340;
1635 break;
1636 }
1637 }
1638 else if (!(rt & 0xc0))
1639 cdr.FifoOffset = DATA_SIZE; // fifo empty
1640}
1641
1642void psxDma3(u32 madr, u32 bcr, u32 chcr) {
1643 u32 cdsize, max_words;
1644 int size;
1645 u8 *ptr;
1646
1647#if 0
1648 CDR_LOG_I("psxDma3() Log: *** DMA 3 *** %x addr = %x size = %x", chcr, madr, bcr);
1649 if (cdr.FifoOffset == 0) {
1650 ptr = cdr.Transfer;
1651 SysPrintf(" %02x:%02x:%02x", ptr[0], ptr[1], ptr[2]);
1652 }
1653 SysPrintf("\n");
1654#endif
1655
1656 switch (chcr & 0x71000000) {
1657 case 0x11000000:
1658 madr &= ~3;
1659 ptr = getDmaRam(madr, &max_words);
1660 if (ptr == INVALID_PTR) {
1661 CDR_LOG_I("psxDma3() Log: *** DMA 3 *** NULL Pointer!\n");
1662 break;
1663 }
1664
1665 cdsize = (((bcr - 1) & 0xffff) + 1) * 4;
1666
1667 /*
1668 GS CDX: Enhancement CD crash
1669 - Setloc 0:0:0
1670 - CdlPlay
1671 - Spams DMA3 and gets buffer overrun
1672 */
1673 size = DATA_SIZE - cdr.FifoOffset;
1674 if (size > cdsize)
1675 size = cdsize;
1676 if (size > max_words * 4)
1677 size = max_words * 4;
1678 if (size > 0)
1679 {
1680 memcpy(ptr, cdr.Transfer + cdr.FifoOffset, size);
1681 cdr.FifoOffset += size;
1682 }
1683 if (size < cdsize) {
1684 CDR_LOG_I("cdrom: dma3 %d/%d\n", size, cdsize);
1685 memset(ptr + size, cdr.Transfer[0x920], cdsize - size);
1686 }
1687 psxCpu->Clear(madr, cdsize / 4);
1688
1689 set_event(PSXINT_CDRDMA, (cdsize / 4) * 24);
1690
1691 HW_DMA3_CHCR &= SWAPu32(~0x10000000);
1692 if (chcr & 0x100) {
1693 HW_DMA3_MADR = SWAPu32(madr + cdsize);
1694 HW_DMA3_BCR &= SWAPu32(0xffff0000);
1695 }
1696 else {
1697 // halted
1698 psxRegs.cycle += (cdsize/4) * 24 - 20;
1699 }
1700 return;
1701
1702 default:
1703 CDR_LOG_I("psxDma3() Log: Unknown cddma %x\n", chcr);
1704 break;
1705 }
1706
1707 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1708 DMA_INTERRUPT(3);
1709}
1710
1711void cdrDmaInterrupt(void)
1712{
1713 if (HW_DMA3_CHCR & SWAP32(0x01000000))
1714 {
1715 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1716 DMA_INTERRUPT(3);
1717 }
1718}
1719
1720static void getCdInfo(void)
1721{
1722 u8 tmp;
1723
1724 CDR_getTN(cdr.ResultTN);
1725 CDR_getTD(0, cdr.SetSectorEnd);
1726 tmp = cdr.SetSectorEnd[0];
1727 cdr.SetSectorEnd[0] = cdr.SetSectorEnd[2];
1728 cdr.SetSectorEnd[2] = tmp;
1729}
1730
1731void cdrReset() {
1732 memset(&cdr, 0, sizeof(cdr));
1733 cdr.CurTrack = 1;
1734 cdr.FilterFile = 0;
1735 cdr.FilterChannel = 0;
1736 cdr.IrqMask = 0x1f;
1737 cdr.IrqStat = NoIntr;
1738
1739 // BIOS player - default values
1740 cdr.AttenuatorLeftToLeft = 0x80;
1741 cdr.AttenuatorLeftToRight = 0x00;
1742 cdr.AttenuatorRightToLeft = 0x00;
1743 cdr.AttenuatorRightToRight = 0x80;
1744
1745 softReset();
1746 getCdInfo();
1747}
1748
1749int cdrFreeze(void *f, int Mode) {
1750 u32 tmp;
1751 u8 tmpp[3];
1752
1753 if (Mode == 0 && !Config.Cdda)
1754 CDR_stop();
1755
1756 cdr.freeze_ver = 0x63647202;
1757 gzfreeze(&cdr, sizeof(cdr));
1758
1759 if (Mode == 1) {
1760 cdr.ParamP = cdr.ParamC;
1761 tmp = cdr.FifoOffset;
1762 }
1763
1764 gzfreeze(&tmp, sizeof(tmp));
1765
1766 if (Mode == 0) {
1767 u8 ll = 0, lr = 0, rl = 0, rr = 0;
1768 getCdInfo();
1769
1770 cdr.FifoOffset = tmp < DATA_SIZE ? tmp : DATA_SIZE;
1771 cdr.FifoSize = (cdr.Mode & MODE_SIZE_2340) ? 2340 : 2048 + 12;
1772 if (cdr.SubqForwardSectors > SUBQ_FORWARD_SECTORS)
1773 cdr.SubqForwardSectors = SUBQ_FORWARD_SECTORS;
1774
1775 // read right sub data
1776 tmpp[0] = btoi(cdr.Prev[0]);
1777 tmpp[1] = btoi(cdr.Prev[1]);
1778 tmpp[2] = btoi(cdr.Prev[2]);
1779 cdr.Prev[0]++;
1780 ReadTrack(tmpp);
1781
1782 if (cdr.Play) {
1783 if (cdr.freeze_ver < 0x63647202)
1784 memcpy(cdr.SetSectorPlay, cdr.SetSector, 3);
1785
1786 Find_CurTrack(cdr.SetSectorPlay);
1787 if (!Config.Cdda)
1788 CDR_play(cdr.SetSectorPlay);
1789 }
1790 if (!cdr.Muted)
1791 ll = cdr.AttenuatorLeftToLeft, lr = cdr.AttenuatorLeftToLeft,
1792 rl = cdr.AttenuatorRightToLeft, rr = cdr.AttenuatorRightToRight;
1793 SPU_setCDvol(ll, lr, rl, rr, psxRegs.cycle);
1794 }
1795
1796 return 0;
1797}
1798
1799void LidInterrupt(void) {
1800 getCdInfo();
1801 cdrLidSeekInterrupt();
1802}