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