adjust the deadzone hack
[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 cdr.FileChannelSelected = 0;
994 break;
995
996 case CdlSetmode:
997 case CdlSetmode + CMD_WHILE_NOT_READY:
998 CDR_LOG("cdrWrite1() Log: Setmode %x\n", cdr.Param[0]);
999 cdr.Mode = cdr.Param[0];
1000 break;
1001
1002 case CdlGetparam:
1003 case CdlGetparam + CMD_WHILE_NOT_READY:
1004 /* Gameblabla : According to mednafen, Result size should be 5 and done this way. */
1005 SetResultSize_(5);
1006 cdr.Result[1] = cdr.Mode;
1007 cdr.Result[2] = 0;
1008 cdr.Result[3] = cdr.FilterFile;
1009 cdr.Result[4] = cdr.FilterChannel;
1010 break;
1011
1012 case CdlGetlocL:
1013 if (cdr.LocL[0] == LOCL_INVALID) {
1014 error = 0x80;
1015 goto set_error;
1016 }
1017 SetResultSize_(8);
1018 memcpy(cdr.Result, cdr.LocL, 8);
1019 break;
1020
1021 case CdlGetlocP:
1022 SetResultSize_(8);
1023 memcpy(&cdr.Result, &cdr.subq, 8);
1024 break;
1025
1026 case CdlReadT: // SetSession?
1027 // really long
1028 second_resp_time = cdReadTime * 290 / 4;
1029 start_rotating = 1;
1030 break;
1031
1032 case CdlReadT + CMD_PART2:
1033 IrqStat = Complete;
1034 break;
1035
1036 case CdlGetTN:
1037 if (CDR_getTN(cdr.ResultTN) == -1) {
1038 assert(0);
1039 }
1040 SetResultSize_(3);
1041 cdr.Result[1] = itob(cdr.ResultTN[0]);
1042 cdr.Result[2] = itob(cdr.ResultTN[1]);
1043 break;
1044
1045 case CdlGetTD:
1046 cdr.Track = btoi(cdr.Param[0]);
1047 if (CDR_getTD(cdr.Track, cdr.ResultTD) == -1) {
1048 error = ERROR_BAD_ARGVAL;
1049 goto set_error;
1050 }
1051 SetResultSize_(3);
1052 cdr.Result[1] = itob(cdr.ResultTD[2]);
1053 cdr.Result[2] = itob(cdr.ResultTD[1]);
1054 // no sector number
1055 //cdr.Result[3] = itob(cdr.ResultTD[0]);
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 IrqStat = 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 IrqStat = 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 IrqStat = 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 IrqStat = 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(IrqStat, 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.IrqStat) {
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.IrqStat);
1308 cdr.Irq1Pending = result;
1309 return;
1310 }
1311 SetResultSize(1);
1312 cdr.Result[0] = result;
1313 setIrq((result & STATUS_ERROR) ? DiskError : DataReady, 0x1004);
1314}
1315
1316static void cdrUpdateTransferBuf(const u8 *buf)
1317{
1318 if (!buf)
1319 return;
1320 memcpy(cdr.Transfer, buf, DATA_SIZE);
1321 CheckPPFCache(cdr.Transfer, cdr.Prev[0], cdr.Prev[1], cdr.Prev[2]);
1322 CDR_LOG("cdr.Transfer %02x:%02x:%02x\n",
1323 cdr.Transfer[0], cdr.Transfer[1], cdr.Transfer[2]);
1324 if (cdr.FifoOffset < 2048 + 12)
1325 CDR_LOG("FifoOffset(1) %d/%d\n", cdr.FifoOffset, cdr.FifoSize);
1326}
1327
1328static void cdrReadInterrupt(void)
1329{
1330 const struct { u8 file, chan, mode, coding; } *subhdr;
1331 const u8 *buf = NULL;
1332 int deliver_data = 1;
1333 u8 subqPos[3];
1334 int read_ok;
1335
1336 memcpy(subqPos, cdr.SetSectorPlay, sizeof(subqPos));
1337 msfiAdd(subqPos, cdr.SubqForwardSectors);
1338 UpdateSubq(subqPos);
1339 if (cdr.SubqForwardSectors < SUBQ_FORWARD_SECTORS) {
1340 cdr.SubqForwardSectors++;
1341 CDRPLAYREAD_INT((cdr.Mode & MODE_SPEED) ? (cdReadTime / 2) : cdReadTime, 0);
1342 return;
1343 }
1344
1345 // note: CdlGetlocL should work as soon as STATUS_READ is indicated
1346 SetPlaySeekRead(cdr.StatP, STATUS_READ | STATUS_ROTATING);
1347 cdr.sectorsRead++;
1348
1349 read_ok = ReadTrack(cdr.SetSectorPlay);
1350 if (read_ok)
1351 buf = CDR_getBuffer();
1352 if (buf == NULL)
1353 read_ok = 0;
1354
1355 if (!read_ok) {
1356 CDR_LOG_I("cdrReadInterrupt() Log: err\n");
1357 cdrReadInterruptSetResult(cdr.StatP | STATUS_ERROR);
1358 return;
1359 }
1360 memcpy(cdr.LocL, buf, 8);
1361
1362 if (!cdr.IrqStat && !cdr.Irq1Pending)
1363 cdrUpdateTransferBuf(buf);
1364
1365 subhdr = (void *)(buf + 4);
1366 do {
1367 // try to process as adpcm
1368 if (!(cdr.Mode & MODE_STRSND))
1369 break;
1370 if (buf[3] != 2 || (subhdr->mode & 0x44) != 0x44) // or 0x64?
1371 break;
1372 CDR_LOG("f=%d m=%d %d,%3d | %d,%2d | %d,%2d\n", !!(cdr.Mode & MODE_SF), cdr.Muted,
1373 subhdr->file, subhdr->chan, cdr.CurFile, cdr.CurChannel, cdr.FilterFile, cdr.FilterChannel);
1374 if ((cdr.Mode & MODE_SF) && (subhdr->file != cdr.FilterFile || subhdr->chan != cdr.FilterChannel))
1375 break;
1376 if (subhdr->chan & 0xe0) { // ?
1377 if (subhdr->chan != 0xff)
1378 log_unhandled("adpcm %d:%d\n", subhdr->file, subhdr->chan);
1379 break;
1380 }
1381 if (!cdr.FileChannelSelected) {
1382 cdr.CurFile = subhdr->file;
1383 cdr.CurChannel = subhdr->chan;
1384 cdr.FileChannelSelected = 1;
1385 }
1386 else if (subhdr->file != cdr.CurFile || subhdr->chan != cdr.CurChannel)
1387 break;
1388
1389 // accepted as adpcm
1390 deliver_data = 0;
1391
1392 if (Config.Xa)
1393 break;
1394 if (!cdr.Muted && cdr.AdpcmActive) {
1395 cdrAttenuate(cdr.Xa.pcm, cdr.Xa.nsamples, cdr.Xa.stereo);
1396 SPU_playADPCMchannel(&cdr.Xa, psxRegs.cycle, 0);
1397 }
1398 // decode next
1399 cdr.AdpcmActive = !xa_decode_sector(&cdr.Xa, buf + 4, !cdr.AdpcmActive);
1400 } while (0);
1401
1402 if ((cdr.Mode & MODE_SF) && (subhdr->mode & 0x44) == 0x44) // according to nocash
1403 deliver_data = 0;
1404
1405 /*
1406 Croc 2: $40 - only FORM1 (*)
1407 Judge Dredd: $C8 - only FORM1 (*)
1408 Sim Theme Park - no adpcm at all (zero)
1409 */
1410
1411 if (deliver_data)
1412 cdrReadInterruptSetResult(cdr.StatP);
1413
1414 msfiAdd(cdr.SetSectorPlay, 1);
1415
1416 CDRPLAYREAD_INT((cdr.Mode & MODE_SPEED) ? (cdReadTime / 2) : cdReadTime, 0);
1417}
1418
1419/*
1420cdrRead0:
1421 bit 0,1 - reg index
1422 bit 2 - adpcm active
1423 bit 5 - 1 result ready
1424 bit 6 - 1 dma ready
1425 bit 7 - 1 command being processed
1426*/
1427
1428unsigned char cdrRead0(void) {
1429 cdr.Ctrl &= ~0x24;
1430 cdr.Ctrl |= cdr.AdpcmActive << 2;
1431 cdr.Ctrl |= cdr.ResultReady << 5;
1432
1433 cdr.Ctrl |= 0x40; // data fifo not empty
1434
1435 // What means the 0x10 and the 0x08 bits? I only saw it used by the bios
1436 cdr.Ctrl |= 0x18;
1437
1438 CDR_LOG_IO("cdr r0.sta: %02x\n", cdr.Ctrl);
1439
1440 return psxHu8(0x1800) = cdr.Ctrl;
1441}
1442
1443void cdrWrite0(unsigned char rt) {
1444 CDR_LOG_IO("cdr w0.idx: %02x\n", rt);
1445
1446 cdr.Ctrl = (rt & 3) | (cdr.Ctrl & ~3);
1447}
1448
1449unsigned char cdrRead1(void) {
1450 if ((cdr.ResultP & 0xf) < cdr.ResultC)
1451 psxHu8(0x1801) = cdr.Result[cdr.ResultP & 0xf];
1452 else
1453 psxHu8(0x1801) = 0;
1454 cdr.ResultP++;
1455 if (cdr.ResultP == cdr.ResultC)
1456 cdr.ResultReady = 0;
1457
1458 CDR_LOG_IO("cdr r1.rsp: %02x #%u\n", psxHu8(0x1801), cdr.ResultP - 1);
1459
1460 return psxHu8(0x1801);
1461}
1462
1463void cdrWrite1(unsigned char rt) {
1464 const char *rnames[] = { "cmd", "smd", "smc", "arr" }; (void)rnames;
1465 CDR_LOG_IO("cdr w1.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1466
1467 switch (cdr.Ctrl & 3) {
1468 case 0:
1469 break;
1470 case 3:
1471 cdr.AttenuatorRightToRightT = rt;
1472 return;
1473 default:
1474 return;
1475 }
1476
1477#ifdef CDR_LOG_CMD_IRQ
1478 CDR_LOG_I("CD1 write: %x (%s)", rt, CmdName[rt]);
1479 if (cdr.ParamC) {
1480 int i;
1481 SysPrintf(" Param[%d] = {", cdr.ParamC);
1482 for (i = 0; i < cdr.ParamC; i++)
1483 SysPrintf(" %x,", cdr.Param[i]);
1484 SysPrintf("}\n");
1485 } else {
1486 SysPrintf("\n");
1487 }
1488#endif
1489
1490 cdr.ResultReady = 0;
1491 cdr.Ctrl |= 0x80;
1492
1493 if (!cdr.CmdInProgress) {
1494 cdr.CmdInProgress = rt;
1495 // should be something like 12k + controller delays
1496 set_event(PSXINT_CDR, 5000);
1497 }
1498 else {
1499 CDR_LOG_I("cmd while busy: %02x, prev %02x, busy %02x\n",
1500 rt, cdr.Cmd, cdr.CmdInProgress);
1501 if (cdr.CmdInProgress < 0x100) // no pending 2nd response
1502 cdr.CmdInProgress = rt;
1503 }
1504
1505 cdr.Cmd = rt;
1506}
1507
1508unsigned char cdrRead2(void) {
1509 unsigned char ret = cdr.Transfer[0x920];
1510
1511 if (cdr.FifoOffset < cdr.FifoSize)
1512 ret = cdr.Transfer[cdr.FifoOffset++];
1513 else
1514 CDR_LOG_I("read empty fifo (%d)\n", cdr.FifoSize);
1515
1516 CDR_LOG_IO("cdr r2.dat: %02x\n", ret);
1517 return ret;
1518}
1519
1520void cdrWrite2(unsigned char rt) {
1521 const char *rnames[] = { "prm", "ien", "all", "arl" }; (void)rnames;
1522 CDR_LOG_IO("cdr w2.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1523
1524 switch (cdr.Ctrl & 3) {
1525 case 0:
1526 if (cdr.ParamC < 8) // FIXME: size and wrapping
1527 cdr.Param[cdr.ParamC++] = rt;
1528 return;
1529 case 1:
1530 cdr.IrqMask = rt;
1531 setIrq(cdr.IrqStat, 0x1005);
1532 return;
1533 case 2:
1534 cdr.AttenuatorLeftToLeftT = rt;
1535 return;
1536 case 3:
1537 cdr.AttenuatorRightToLeftT = rt;
1538 return;
1539 }
1540}
1541
1542unsigned char cdrRead3(void) {
1543 if (cdr.Ctrl & 0x1)
1544 psxHu8(0x1803) = cdr.IrqStat | 0xE0;
1545 else
1546 psxHu8(0x1803) = cdr.IrqMask | 0xE0;
1547
1548 CDR_LOG_IO("cdr r3.%s: %02x\n", (cdr.Ctrl & 1) ? "ifl" : "ien", psxHu8(0x1803));
1549 return psxHu8(0x1803);
1550}
1551
1552void cdrWrite3(unsigned char rt) {
1553 const char *rnames[] = { "req", "ifl", "alr", "ava" }; (void)rnames;
1554 CDR_LOG_IO("cdr w3.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1555
1556 switch (cdr.Ctrl & 3) {
1557 case 0:
1558 break; // transfer
1559 case 1:
1560 if (cdr.IrqStat & rt) {
1561 u32 nextCycle = psxRegs.intCycle[PSXINT_CDR].sCycle
1562 + psxRegs.intCycle[PSXINT_CDR].cycle;
1563 int pending = psxRegs.interrupt & (1 << PSXINT_CDR);
1564#ifdef CDR_LOG_CMD_IRQ
1565 CDR_LOG_I("ack %02x (w=%02x p=%d,%x,%x,%d)\n",
1566 cdr.IrqStat & rt, rt, !!pending, cdr.CmdInProgress,
1567 cdr.Irq1Pending, nextCycle - psxRegs.cycle);
1568#endif
1569 // note: Croc, Shadow Tower (more) vs Discworld Noir (<993)
1570 if (!pending && (cdr.CmdInProgress || cdr.Irq1Pending))
1571 {
1572 s32 c = 2048;
1573 if (cdr.CmdInProgress) {
1574 c = 2048 - (psxRegs.cycle - nextCycle);
1575 c = MAX_VALUE(c, 512);
1576 }
1577 set_event(PSXINT_CDR, c);
1578 }
1579 }
1580 cdr.IrqStat &= ~rt;
1581
1582 if (rt & 0x40)
1583 cdr.ParamC = 0;
1584 return;
1585 case 2:
1586 cdr.AttenuatorLeftToRightT = rt;
1587 return;
1588 case 3:
1589 if (rt & 0x20) {
1590 memcpy(&cdr.AttenuatorLeftToLeft, &cdr.AttenuatorLeftToLeftT, 4);
1591 CDR_LOG("CD-XA Volume: %02x %02x | %02x %02x\n",
1592 cdr.AttenuatorLeftToLeft, cdr.AttenuatorLeftToRight,
1593 cdr.AttenuatorRightToLeft, cdr.AttenuatorRightToRight);
1594 }
1595 return;
1596 }
1597
1598 // test: Viewpoint
1599 if ((rt & 0x80) && cdr.FifoOffset < cdr.FifoSize) {
1600 CDR_LOG("cdrom: FifoOffset(2) %d/%d\n", cdr.FifoOffset, cdr.FifoSize);
1601 }
1602 else if (rt & 0x80) {
1603 switch (cdr.Mode & (MODE_SIZE_2328|MODE_SIZE_2340)) {
1604 case MODE_SIZE_2328:
1605 case 0x00:
1606 cdr.FifoOffset = 12;
1607 cdr.FifoSize = 2048 + 12;
1608 break;
1609
1610 case MODE_SIZE_2340:
1611 default:
1612 cdr.FifoOffset = 0;
1613 cdr.FifoSize = 2340;
1614 break;
1615 }
1616 }
1617 else if (!(rt & 0xc0))
1618 cdr.FifoOffset = DATA_SIZE; // fifo empty
1619}
1620
1621void psxDma3(u32 madr, u32 bcr, u32 chcr) {
1622 u32 cdsize, max_words;
1623 int size;
1624 u8 *ptr;
1625
1626#if 0
1627 CDR_LOG_I("psxDma3() Log: *** DMA 3 *** %x addr = %x size = %x", chcr, madr, bcr);
1628 if (cdr.FifoOffset == 0) {
1629 ptr = cdr.Transfer;
1630 SysPrintf(" %02x:%02x:%02x", ptr[0], ptr[1], ptr[2]);
1631 }
1632 SysPrintf("\n");
1633#endif
1634
1635 switch (chcr & 0x71000000) {
1636 case 0x11000000:
1637 ptr = getDmaRam(madr, &max_words);
1638 if (ptr == INVALID_PTR) {
1639 CDR_LOG_I("psxDma3() Log: *** DMA 3 *** NULL Pointer!\n");
1640 break;
1641 }
1642
1643 cdsize = (((bcr - 1) & 0xffff) + 1) * 4;
1644
1645 /*
1646 GS CDX: Enhancement CD crash
1647 - Setloc 0:0:0
1648 - CdlPlay
1649 - Spams DMA3 and gets buffer overrun
1650 */
1651 size = DATA_SIZE - cdr.FifoOffset;
1652 if (size > cdsize)
1653 size = cdsize;
1654 if (size > max_words * 4)
1655 size = max_words * 4;
1656 if (size > 0)
1657 {
1658 memcpy(ptr, cdr.Transfer + cdr.FifoOffset, size);
1659 cdr.FifoOffset += size;
1660 }
1661 if (size < cdsize) {
1662 CDR_LOG_I("cdrom: dma3 %d/%d\n", size, cdsize);
1663 memset(ptr + size, cdr.Transfer[0x920], cdsize - size);
1664 }
1665 psxCpu->Clear(madr, cdsize / 4);
1666
1667 set_event(PSXINT_CDRDMA, (cdsize / 4) * 24);
1668
1669 HW_DMA3_CHCR &= SWAPu32(~0x10000000);
1670 if (chcr & 0x100) {
1671 HW_DMA3_MADR = SWAPu32(madr + cdsize);
1672 HW_DMA3_BCR &= SWAPu32(0xffff0000);
1673 }
1674 else {
1675 // halted
1676 psxRegs.cycle += (cdsize/4) * 24 - 20;
1677 }
1678 return;
1679
1680 default:
1681 CDR_LOG_I("psxDma3() Log: Unknown cddma %x\n", chcr);
1682 break;
1683 }
1684
1685 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1686 DMA_INTERRUPT(3);
1687}
1688
1689void cdrDmaInterrupt(void)
1690{
1691 if (HW_DMA3_CHCR & SWAP32(0x01000000))
1692 {
1693 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1694 DMA_INTERRUPT(3);
1695 }
1696}
1697
1698static void getCdInfo(void)
1699{
1700 u8 tmp;
1701
1702 CDR_getTN(cdr.ResultTN);
1703 CDR_getTD(0, cdr.SetSectorEnd);
1704 tmp = cdr.SetSectorEnd[0];
1705 cdr.SetSectorEnd[0] = cdr.SetSectorEnd[2];
1706 cdr.SetSectorEnd[2] = tmp;
1707}
1708
1709void cdrReset() {
1710 memset(&cdr, 0, sizeof(cdr));
1711 cdr.CurTrack = 1;
1712 cdr.FilterFile = 0;
1713 cdr.FilterChannel = 0;
1714 cdr.IrqMask = 0x1f;
1715 cdr.IrqStat = NoIntr;
1716 cdr.FifoOffset = DATA_SIZE; // fifo empty
1717
1718 CDR_getStatus(&stat);
1719 if (stat.Status & STATUS_SHELLOPEN) {
1720 cdr.DriveState = DRIVESTATE_LID_OPEN;
1721 cdr.StatP = STATUS_SHELLOPEN;
1722 }
1723 else if (CdromId[0] == '\0') {
1724 cdr.DriveState = DRIVESTATE_STOPPED;
1725 cdr.StatP = 0;
1726 }
1727 else {
1728 cdr.DriveState = DRIVESTATE_STANDBY;
1729 cdr.StatP = STATUS_ROTATING;
1730 }
1731
1732 // BIOS player - default values
1733 cdr.AttenuatorLeftToLeft = 0x80;
1734 cdr.AttenuatorLeftToRight = 0x00;
1735 cdr.AttenuatorRightToLeft = 0x00;
1736 cdr.AttenuatorRightToRight = 0x80;
1737
1738 getCdInfo();
1739}
1740
1741int cdrFreeze(void *f, int Mode) {
1742 u32 tmp;
1743 u8 tmpp[3];
1744
1745 if (Mode == 0 && !Config.Cdda)
1746 CDR_stop();
1747
1748 cdr.freeze_ver = 0x63647202;
1749 gzfreeze(&cdr, sizeof(cdr));
1750
1751 if (Mode == 1) {
1752 cdr.ParamP = cdr.ParamC;
1753 tmp = cdr.FifoOffset;
1754 }
1755
1756 gzfreeze(&tmp, sizeof(tmp));
1757
1758 if (Mode == 0) {
1759 getCdInfo();
1760
1761 cdr.FifoOffset = tmp < DATA_SIZE ? tmp : DATA_SIZE;
1762 cdr.FifoSize = (cdr.Mode & MODE_SIZE_2340) ? 2340 : 2048 + 12;
1763 if (cdr.SubqForwardSectors > SUBQ_FORWARD_SECTORS)
1764 cdr.SubqForwardSectors = SUBQ_FORWARD_SECTORS;
1765
1766 // read right sub data
1767 tmpp[0] = btoi(cdr.Prev[0]);
1768 tmpp[1] = btoi(cdr.Prev[1]);
1769 tmpp[2] = btoi(cdr.Prev[2]);
1770 cdr.Prev[0]++;
1771 ReadTrack(tmpp);
1772
1773 if (cdr.Play) {
1774 if (cdr.freeze_ver < 0x63647202)
1775 memcpy(cdr.SetSectorPlay, cdr.SetSector, 3);
1776
1777 Find_CurTrack(cdr.SetSectorPlay);
1778 if (!Config.Cdda)
1779 CDR_play(cdr.SetSectorPlay);
1780 }
1781 }
1782
1783 return 0;
1784}
1785
1786void LidInterrupt(void) {
1787 getCdInfo();
1788 cdrLidSeekInterrupt();
1789}