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