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