1 /***************************************************************************
2 * Copyright (C) 2007 Ryan Schultz, PCSX-df Team, PCSX team *
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. *
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. *
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 ***************************************************************************/
21 * Handles all CD-ROM registers and functions.
27 #include "arm_features.h"
31 #define CDR_LOG SysPrintf
36 #define CDR_LOG_I SysPrintf
38 #define CDR_LOG_I log_unhandled
41 #define CDR_LOG_IO SysPrintf
43 #define CDR_LOG_IO(...)
45 //#define CDR_LOG_CMD_IRQ
48 // unused members maintain savesate compatibility
49 unsigned char unused0;
50 unsigned char unused1;
52 unsigned char unused2;
58 unsigned char Transfer[DATA_SIZE];
62 unsigned char Relative[3];
63 unsigned char Absolute[3];
65 unsigned char TrackChanged;
66 unsigned char unused3[3];
67 unsigned int freeze_ver;
69 unsigned char Prev[4];
70 unsigned char Param[8];
71 unsigned char Result[16];
75 unsigned char ResultC;
76 unsigned char ResultP;
77 unsigned char ResultReady;
79 unsigned char unused4;
80 unsigned char SetlocPending;
83 unsigned char ResultTN[6];
84 unsigned char ResultTD[4];
85 unsigned char SetSectorPlay[4];
86 unsigned char SetSectorEnd[4];
87 unsigned char SetSector[4];
91 int Mode, File, Channel;
92 unsigned char LocL[8];
112 u8 AttenuatorLeftToLeft, AttenuatorLeftToRight;
113 u8 AttenuatorRightToRight, AttenuatorRightToLeft;
114 u8 AttenuatorLeftToLeftT, AttenuatorLeftToRightT;
115 u8 AttenuatorRightToRightT, AttenuatorRightToLeftT;
117 static s16 read_buf[CD_FRAMESIZE_RAW/2];
119 /* CD-ROM magic numbers */
120 #define CdlSync 0 /* nocash documentation : "Uh, actually, returns error code 40h = Invalid Command...?" */
125 #define CdlBackward 5
133 #define CdlSetfilter 13
134 #define CdlSetmode 14
135 #define CdlGetparam 15
136 #define CdlGetlocL 16
137 #define CdlGetlocP 17
143 #define CdlSetclock 23
144 #define CdlGetclock 24
150 #define CdlReadToc 30
152 #ifdef CDR_LOG_CMD_IRQ
153 static const char * const CmdName[0x100] = {
154 "CdlSync", "CdlNop", "CdlSetloc", "CdlPlay",
155 "CdlForward", "CdlBackward", "CdlReadN", "CdlStandby",
156 "CdlStop", "CdlPause", "CdlReset", "CdlMute",
157 "CdlDemute", "CdlSetfilter", "CdlSetmode", "CdlGetparam",
158 "CdlGetlocL", "CdlGetlocP", "CdlReadT", "CdlGetTN",
159 "CdlGetTD", "CdlSeekL", "CdlSeekP", "CdlSetclock",
160 "CdlGetclock", "CdlTest", "CdlID", "CdlReadS",
161 "CdlInit", NULL, "CDlReadToc", NULL
165 unsigned char Test04[] = { 0 };
166 unsigned char Test05[] = { 0 };
167 unsigned char Test20[] = { 0x98, 0x06, 0x10, 0xC3 };
168 unsigned char Test22[] = { 0x66, 0x6F, 0x72, 0x20, 0x45, 0x75, 0x72, 0x6F };
169 unsigned char Test23[] = { 0x43, 0x58, 0x44, 0x32, 0x39 ,0x34, 0x30, 0x51 };
175 #define Acknowledge 3
180 #define MODE_SPEED (1<<7) // 0x80
181 #define MODE_STRSND (1<<6) // 0x40 ADPCM on/off
182 #define MODE_SIZE_2340 (1<<5) // 0x20
183 #define MODE_SIZE_2328 (1<<4) // 0x10
184 #define MODE_SIZE_2048 (0<<4) // 0x00
185 #define MODE_SF (1<<3) // 0x08 channel on/off
186 #define MODE_REPORT (1<<2) // 0x04
187 #define MODE_AUTOPAUSE (1<<1) // 0x02
188 #define MODE_CDDA (1<<0) // 0x01
191 #define STATUS_PLAY (1<<7) // 0x80
192 #define STATUS_SEEK (1<<6) // 0x40
193 #define STATUS_READ (1<<5) // 0x20
194 #define STATUS_SHELLOPEN (1<<4) // 0x10
195 #define STATUS_UNKNOWN3 (1<<3) // 0x08
196 #define STATUS_UNKNOWN2 (1<<2) // 0x04
197 #define STATUS_ROTATING (1<<1) // 0x02
198 #define STATUS_ERROR (1<<0) // 0x01
201 #define ERROR_NOTREADY (1<<7) // 0x80
202 #define ERROR_INVALIDCMD (1<<6) // 0x40
203 #define ERROR_INVALIDARG (1<<5) // 0x20
205 // 1x = 75 sectors per second
206 // PSXCLK = 1 sec in the ps
207 // so (PSXCLK / 75) = cdr read time (linuzappz)
208 #define cdReadTime (PSXCLK / 75)
210 #define LOCL_INVALID 0xff
213 DRIVESTATE_STANDBY = 0, // pause, play, read
215 DRIVESTATE_RESCAN_CD,
216 DRIVESTATE_PREPARE_CD,
220 static struct CdrStat stat;
222 static unsigned int msf2sec(const u8 *msf) {
223 return ((msf[0] * 60 + msf[1]) * 75) + msf[2];
226 // for that weird psemu API..
227 static unsigned int fsm2sec(const u8 *msf) {
228 return ((msf[2] * 60 + msf[1]) * 75) + msf[0];
231 static void sec2msf(unsigned int s, u8 *msf) {
232 msf[0] = s / 75 / 60;
233 s = s - msf[0] * 75 * 60;
240 #define CDR_INT(eCycle) { \
241 psxRegs.interrupt |= (1 << PSXINT_CDR); \
242 psxRegs.intCycle[PSXINT_CDR].cycle = eCycle; \
243 psxRegs.intCycle[PSXINT_CDR].sCycle = psxRegs.cycle; \
244 new_dyna_set_event(PSXINT_CDR, eCycle); \
247 // cdrPlayReadInterrupt
248 #define CDRPLAYREAD_INT(eCycle, isFirst) { \
250 psxRegs.interrupt |= (1 << PSXINT_CDREAD); \
252 psxRegs.intCycle[PSXINT_CDREAD].sCycle = psxRegs.cycle; \
254 psxRegs.intCycle[PSXINT_CDREAD].sCycle += psxRegs.intCycle[PSXINT_CDREAD].cycle; \
255 psxRegs.intCycle[PSXINT_CDREAD].cycle = e_; \
256 new_dyna_set_event_abs(PSXINT_CDREAD, psxRegs.intCycle[PSXINT_CDREAD].sCycle + e_); \
259 // cdrLidSeekInterrupt
260 #define CDRLID_INT(eCycle) { \
261 psxRegs.interrupt |= (1 << PSXINT_CDRLID); \
262 psxRegs.intCycle[PSXINT_CDRLID].cycle = eCycle; \
263 psxRegs.intCycle[PSXINT_CDRLID].sCycle = psxRegs.cycle; \
264 new_dyna_set_event(PSXINT_CDRLID, eCycle); \
267 #define StopReading() { \
269 psxRegs.interrupt &= ~(1 << PSXINT_CDREAD); \
272 #define StopCdda() { \
273 if (cdr.Play && !Config.Cdda) CDR_stop(); \
275 cdr.FastForward = 0; \
276 cdr.FastBackward = 0; \
279 #define SetPlaySeekRead(x, f) { \
280 x &= ~(STATUS_PLAY | STATUS_SEEK | STATUS_READ); \
284 #define SetResultSize(size) { \
286 cdr.ResultC = size; \
287 cdr.ResultReady = 1; \
290 static void setIrq(int log_cmd)
292 if (cdr.Stat & cdr.Reg2)
293 psxHu32ref(0x1070) |= SWAP32((u32)0x4);
295 #ifdef CDR_LOG_CMD_IRQ
299 SysPrintf("%u cdrom: CDR IRQ=%d cmd %02x stat %02x: ",
300 psxRegs.cycle, !!(cdr.Stat & cdr.Reg2), log_cmd, cdr.Stat);
301 for (i = 0; i < cdr.ResultC; i++)
302 SysPrintf("%02x ", cdr.Result[i]);
308 // timing used in this function was taken from tests on real hardware
309 // (yes it's slow, but you probably don't want to modify it)
310 void cdrLidSeekInterrupt(void)
312 CDR_LOG_I("%u %s cdr.DriveState=%d\n", psxRegs.cycle, __func__, cdr.DriveState);
314 switch (cdr.DriveState) {
316 case DRIVESTATE_STANDBY:
319 SetPlaySeekRead(cdr.StatP, 0);
321 if (CDR_getStatus(&stat) == -1)
324 if (stat.Status & STATUS_SHELLOPEN)
326 cdr.DriveState = DRIVESTATE_LID_OPEN;
331 case DRIVESTATE_LID_OPEN:
332 if (CDR_getStatus(&stat) == -1)
333 stat.Status &= ~STATUS_SHELLOPEN;
336 if (!(cdr.StatP & STATUS_SHELLOPEN)) {
337 cdr.StatP |= STATUS_SHELLOPEN;
339 // could generate error irq here, but real hardware
340 // only sometimes does that
341 // (not done when lots of commands are sent?)
343 CDRLID_INT(cdReadTime * 30);
346 else if (cdr.StatP & STATUS_ROTATING) {
347 cdr.StatP &= ~STATUS_ROTATING;
349 else if (!(stat.Status & STATUS_SHELLOPEN)) {
353 // cdr.StatP STATUS_SHELLOPEN is "sticky"
354 // and is only cleared by CdlNop
356 cdr.DriveState = DRIVESTATE_RESCAN_CD;
357 CDRLID_INT(cdReadTime * 105);
362 CDRLID_INT(cdReadTime * 3);
365 case DRIVESTATE_RESCAN_CD:
366 cdr.StatP |= STATUS_ROTATING;
367 cdr.DriveState = DRIVESTATE_PREPARE_CD;
369 // this is very long on real hardware, over 6 seconds
370 // make it a bit faster here...
371 CDRLID_INT(cdReadTime * 150);
374 case DRIVESTATE_PREPARE_CD:
375 if (cdr.StatP & STATUS_SEEK) {
376 SetPlaySeekRead(cdr.StatP, 0);
377 cdr.DriveState = DRIVESTATE_STANDBY;
380 SetPlaySeekRead(cdr.StatP, STATUS_SEEK);
381 CDRLID_INT(cdReadTime * 26);
387 static void Find_CurTrack(const u8 *time)
391 current = msf2sec(time);
393 for (cdr.CurTrack = 1; cdr.CurTrack < cdr.ResultTN[1]; cdr.CurTrack++) {
394 CDR_getTD(cdr.CurTrack + 1, cdr.ResultTD);
395 sect = fsm2sec(cdr.ResultTD);
396 if (sect - current >= 150)
401 static void generate_subq(const u8 *time)
403 unsigned char start[3], next[3];
404 unsigned int this_s, start_s, next_s, pregap;
407 CDR_getTD(cdr.CurTrack, start);
408 if (cdr.CurTrack + 1 <= cdr.ResultTN[1]) {
410 CDR_getTD(cdr.CurTrack + 1, next);
413 // last track - cd size
415 next[0] = cdr.SetSectorEnd[2];
416 next[1] = cdr.SetSectorEnd[1];
417 next[2] = cdr.SetSectorEnd[0];
420 this_s = msf2sec(time);
421 start_s = fsm2sec(start);
422 next_s = fsm2sec(next);
424 cdr.TrackChanged = FALSE;
426 if (next_s - this_s < pregap) {
427 cdr.TrackChanged = TRUE;
434 relative_s = this_s - start_s;
435 if (relative_s < 0) {
437 relative_s = -relative_s;
439 sec2msf(relative_s, cdr.subq.Relative);
441 cdr.subq.Track = itob(cdr.CurTrack);
442 cdr.subq.Relative[0] = itob(cdr.subq.Relative[0]);
443 cdr.subq.Relative[1] = itob(cdr.subq.Relative[1]);
444 cdr.subq.Relative[2] = itob(cdr.subq.Relative[2]);
445 cdr.subq.Absolute[0] = itob(time[0]);
446 cdr.subq.Absolute[1] = itob(time[1]);
447 cdr.subq.Absolute[2] = itob(time[2]);
450 static int ReadTrack(const u8 *time) {
451 unsigned char tmp[3];
456 tmp[0] = itob(time[0]);
457 tmp[1] = itob(time[1]);
458 tmp[2] = itob(time[2]);
460 if (memcmp(cdr.Prev, tmp, 3) == 0)
463 CDR_LOG("ReadTrack *** %02x:%02x:%02x\n", tmp[0], tmp[1], tmp[2]);
465 read_ok = CDR_readTrack(tmp);
466 memcpy(cdr.Prev, tmp, 3);
471 subq = (struct SubQ *)CDR_getBufferSub();
472 if (subq != NULL && cdr.CurTrack == 1) {
473 crc = calcCrc((u8 *)subq + 12, 10);
474 if (crc == (((u16)subq->CRC[0] << 8) | subq->CRC[1])) {
475 cdr.subq.Track = subq->TrackNumber;
476 cdr.subq.Index = subq->IndexNumber;
477 memcpy(cdr.subq.Relative, subq->TrackRelativeAddress, 3);
478 memcpy(cdr.subq.Absolute, subq->AbsoluteAddress, 3);
481 CDR_LOG_I("subq bad crc @%02x:%02x:%02x\n",
482 tmp[0], tmp[1], tmp[2]);
489 CDR_LOG(" -> %02x,%02x %02x:%02x:%02x %02x:%02x:%02x\n",
490 cdr.subq.Track, cdr.subq.Index,
491 cdr.subq.Relative[0], cdr.subq.Relative[1], cdr.subq.Relative[2],
492 cdr.subq.Absolute[0], cdr.subq.Absolute[1], cdr.subq.Absolute[2]);
497 static void cdrPlayInterrupt_Autopause()
500 boolean abs_lev_chselect;
503 if ((cdr.Mode & MODE_AUTOPAUSE) && cdr.TrackChanged) {
504 CDR_LOG( "CDDA STOP\n" );
506 // Magic the Gathering
507 // - looping territory cdda
510 //cdr.ResultReady = 1;
511 //cdr.Stat = DataReady;
513 setIrq(0x1000); // 0x1000 just for logging purposes
516 SetPlaySeekRead(cdr.StatP, 0);
518 else if (((cdr.Mode & MODE_REPORT) || cdr.FastForward || cdr.FastBackward)) {
519 cdr.Result[0] = cdr.StatP;
520 cdr.Result[1] = cdr.subq.Track;
521 cdr.Result[2] = cdr.subq.Index;
523 abs_lev_chselect = cdr.subq.Absolute[1] & 0x01;
525 /* 8 is a hack. For accuracy, it should be 588. */
526 for (i = 0; i < 8; i++)
528 abs_lev_max = MAX_VALUE(abs_lev_max, abs(read_buf[i * 2 + abs_lev_chselect]));
530 abs_lev_max = MIN_VALUE(abs_lev_max, 32767);
531 abs_lev_max |= abs_lev_chselect << 15;
533 if (cdr.subq.Absolute[2] & 0x10) {
534 cdr.Result[3] = cdr.subq.Relative[0];
535 cdr.Result[4] = cdr.subq.Relative[1] | 0x80;
536 cdr.Result[5] = cdr.subq.Relative[2];
539 cdr.Result[3] = cdr.subq.Absolute[0];
540 cdr.Result[4] = cdr.subq.Absolute[1];
541 cdr.Result[5] = cdr.subq.Absolute[2];
544 cdr.Result[6] = abs_lev_max >> 0;
545 cdr.Result[7] = abs_lev_max >> 8;
547 // Rayman: Logo freeze (resultready + dataready)
549 cdr.Stat = DataReady;
556 static int cdrSeekTime(unsigned char *target)
558 int diff = msf2sec(cdr.SetSectorPlay) - msf2sec(target);
559 int seekTime = abs(diff) * (cdReadTime / 200);
562 * It was originally set to 1000000 for Driver, however it is not high enough for Worms Pinball
563 * and was unreliable for that game.
564 * I also tested it against Mednafen and Driver's titlescreen music starts 25 frames later, not immediatly.
566 * Obviously, this isn't perfect but right now, it should be a bit better.
567 * Games to test this against if you change that setting :
568 * - Driver (titlescreen music delay and retry mission)
569 * - Worms Pinball (Will either not boot or crash in the memory card screen)
570 * - Viewpoint (short pauses if the delay in the ingame music is too long)
572 * It seems that 3386880 * 5 is too much for Driver's titlescreen and it starts skipping.
573 * However, 1000000 is not enough for Worms Pinball to reliably boot.
575 if(seekTime > 3386880 * 2) seekTime = 3386880 * 2;
576 CDR_LOG("seek: %.2f %.2f\n", (float)seekTime / PSXCLK, (float)seekTime / cdReadTime);
580 static void cdrUpdateTransferBuf(const u8 *buf);
581 static void cdrReadInterrupt(void);
582 static void cdrPrepCdda(s16 *buf, int samples);
583 static void cdrAttenuate(s16 *buf, int samples, int stereo);
585 void cdrPlayReadInterrupt(void)
592 if (!cdr.Play) return;
594 CDR_LOG( "CDDA - %d:%d:%d\n",
595 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2] );
597 SetPlaySeekRead(cdr.StatP, STATUS_PLAY);
598 if (memcmp(cdr.SetSectorPlay, cdr.SetSectorEnd, 3) == 0) {
600 SetPlaySeekRead(cdr.StatP, 0);
601 cdr.TrackChanged = TRUE;
604 CDR_readCDDA(cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2], (u8 *)read_buf);
607 if (!cdr.Stat && (cdr.Mode & (MODE_AUTOPAUSE|MODE_REPORT)))
608 cdrPlayInterrupt_Autopause();
610 if (!cdr.Muted && !Config.Cdda) {
611 cdrPrepCdda(read_buf, CD_FRAMESIZE_RAW / 4);
612 cdrAttenuate(read_buf, CD_FRAMESIZE_RAW / 4, 1);
613 SPU_playCDDAchannel(read_buf, CD_FRAMESIZE_RAW, psxRegs.cycle, cdr.FirstSector);
617 cdr.SetSectorPlay[2]++;
618 if (cdr.SetSectorPlay[2] == 75) {
619 cdr.SetSectorPlay[2] = 0;
620 cdr.SetSectorPlay[1]++;
621 if (cdr.SetSectorPlay[1] == 60) {
622 cdr.SetSectorPlay[1] = 0;
623 cdr.SetSectorPlay[0]++;
627 // update for CdlGetlocP/autopause
628 generate_subq(cdr.SetSectorPlay);
630 CDRPLAYREAD_INT(cdReadTime, 0);
633 #define CMD_PART2 0x100
634 #define CMD_WHILE_NOT_READY 0x200
636 void cdrInterrupt(void) {
637 int start_rotating = 0;
639 unsigned int seekTime = 0;
640 u32 second_resp_time = 0;
650 CDR_LOG_I("%u cdrom: cmd %02x with irqstat %x\n",
651 psxRegs.cycle, cdr.CmdInProgress, cdr.Stat);
654 if (cdr.Irq1Pending) {
655 // hand out the "newest" sector, according to nocash
656 cdrUpdateTransferBuf(CDR_getBuffer());
657 CDR_LOG_I("cdrom: %x:%02x:%02x loaded on ack\n",
658 cdr.Transfer[0], cdr.Transfer[1], cdr.Transfer[2]);
660 cdr.Result[0] = cdr.Irq1Pending;
661 cdr.Stat = (cdr.Irq1Pending & STATUS_ERROR) ? DiskError : DataReady;
669 cdr.Result[0] = cdr.StatP;
670 cdr.Stat = Acknowledge;
672 Cmd = cdr.CmdInProgress;
673 cdr.CmdInProgress = 0;
682 switch (cdr.DriveState) {
683 case DRIVESTATE_PREPARE_CD:
685 // Syphon filter 2 expects commands to work shortly after it sees
686 // STATUS_ROTATING, so give up trying to emulate the startup seq
687 cdr.DriveState = DRIVESTATE_STANDBY;
688 cdr.StatP &= ~STATUS_SEEK;
689 psxRegs.interrupt &= ~(1 << PSXINT_CDRLID);
693 case DRIVESTATE_LID_OPEN:
694 case DRIVESTATE_RESCAN_CD:
695 // no disk or busy with the initial scan, allowed cmds are limited
696 not_ready = CMD_WHILE_NOT_READY;
700 switch (Cmd | not_ready) {
702 case CdlNop + CMD_WHILE_NOT_READY:
703 if (cdr.DriveState != DRIVESTATE_LID_OPEN)
704 cdr.StatP &= ~STATUS_SHELLOPEN;
708 case CdlSetloc + CMD_WHILE_NOT_READY:
709 CDR_LOG("CDROM setloc command (%02X, %02X, %02X)\n", cdr.Param[0], cdr.Param[1], cdr.Param[2]);
711 // MM must be BCD, SS must be BCD and <0x60, FF must be BCD and <0x75
712 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))
714 CDR_LOG_I("Invalid/out of range seek to %02X:%02X:%02X\n", cdr.Param[0], cdr.Param[1], cdr.Param[2]);
715 error = ERROR_INVALIDARG;
720 for (i = 0; i < 3; i++)
721 set_loc[i] = btoi(cdr.Param[i]);
722 memcpy(cdr.SetSector, set_loc, 3);
723 cdr.SetSector[3] = 0;
724 cdr.SetlocPending = 1;
733 cdr.FastBackward = 0;
737 // - Pause player, hit Track 01/02/../xx (Setloc issued!!)
739 if (ParamC != 0 && cdr.Param[0] != 0) {
740 int track = btoi( cdr.Param[0] );
742 if (track <= cdr.ResultTN[1])
743 cdr.CurTrack = track;
745 CDR_LOG("PLAY track %d\n", cdr.CurTrack);
747 if (CDR_getTD((u8)cdr.CurTrack, cdr.ResultTD) != -1) {
748 for (i = 0; i < 3; i++)
749 set_loc[i] = cdr.ResultTD[2 - i];
750 seekTime = cdrSeekTime(set_loc);
751 memcpy(cdr.SetSectorPlay, set_loc, 3);
754 else if (cdr.SetlocPending) {
755 seekTime = cdrSeekTime(cdr.SetSector);
756 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
759 CDR_LOG("PLAY Resume @ %d:%d:%d\n",
760 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2]);
762 cdr.SetlocPending = 0;
765 Rayman: detect track changes
768 Twisted Metal 2: skip PREGAP + starting accurate SubQ
769 - plays tracks without retry play
771 Wild 9: skip PREGAP + starting accurate SubQ
772 - plays tracks without retry play
774 Find_CurTrack(cdr.SetSectorPlay);
775 ReadTrack(cdr.SetSectorPlay);
776 cdr.LocL[0] = LOCL_INVALID;
777 cdr.TrackChanged = FALSE;
781 CDR_play(cdr.SetSectorPlay);
783 SetPlaySeekRead(cdr.StatP, STATUS_SEEK | STATUS_ROTATING);
785 // BIOS player - set flag again
788 CDRPLAYREAD_INT(cdReadTime + seekTime, 1);
793 // TODO: error 80 if stopped
796 // GameShark CD Player: Calls 2x + Play 2x
798 cdr.FastBackward = 0;
804 // GameShark CD Player: Calls 2x + Play 2x
805 cdr.FastBackward = 1;
810 if (cdr.DriveState != DRIVESTATE_STOPPED) {
811 error = ERROR_INVALIDARG;
814 second_resp_time = cdReadTime * 125 / 2;
818 case CdlStandby + CMD_PART2:
824 // grab time for current track
825 CDR_getTD((u8)(cdr.CurTrack), cdr.ResultTD);
827 cdr.SetSectorPlay[0] = cdr.ResultTD[2];
828 cdr.SetSectorPlay[1] = cdr.ResultTD[1];
829 cdr.SetSectorPlay[2] = cdr.ResultTD[0];
834 SetPlaySeekRead(cdr.StatP, 0);
835 cdr.StatP &= ~STATUS_ROTATING;
836 cdr.LocL[0] = LOCL_INVALID;
838 second_resp_time = 0x800;
839 if (cdr.DriveState == DRIVESTATE_STANDBY)
840 second_resp_time = cdReadTime * 30 / 2;
842 cdr.DriveState = DRIVESTATE_STOPPED;
845 case CdlStop + CMD_PART2:
853 Gundam Battle Assault 2: much slower (*)
854 - Fixes boot, gameplay
856 Hokuto no Ken 2: slower
857 - Fixes intro + subtitles
859 InuYasha - Feudal Fairy Tale: slower
862 /* Gameblabla - Tightening the timings (as taken from Duckstation).
863 * The timings from Duckstation are based upon hardware tests.
864 * Mednafen's timing don't work for Gundam Battle Assault 2 in PAL/50hz mode,
865 * seems to be timing sensitive as it can depend on the CPU's clock speed.
867 if (!(cdr.StatP & (STATUS_PLAY | STATUS_READ)))
869 second_resp_time = 7000;
873 second_resp_time = (((cdr.Mode & MODE_SPEED) ? 2 : 1) * 1000000);
875 SetPlaySeekRead(cdr.StatP, 0);
878 case CdlPause + CMD_PART2:
883 case CdlReset + CMD_WHILE_NOT_READY:
886 SetPlaySeekRead(cdr.StatP, 0);
887 cdr.LocL[0] = LOCL_INVALID;
889 cdr.Mode = 0x20; /* This fixes This is Football 2, Pooh's Party lockups */
890 second_resp_time = not_ready ? 70000 : 4100000;
894 case CdlReset + CMD_PART2:
895 case CdlReset + CMD_PART2 + CMD_WHILE_NOT_READY:
908 cdr.File = cdr.Param[0];
909 cdr.Channel = cdr.Param[1];
913 case CdlSetmode + CMD_WHILE_NOT_READY:
914 CDR_LOG("cdrWrite1() Log: Setmode %x\n", cdr.Param[0]);
915 cdr.Mode = cdr.Param[0];
919 case CdlGetparam + CMD_WHILE_NOT_READY:
920 /* Gameblabla : According to mednafen, Result size should be 5 and done this way. */
922 cdr.Result[1] = cdr.Mode;
924 cdr.Result[3] = cdr.File;
925 cdr.Result[4] = cdr.Channel;
929 if (cdr.LocL[0] == LOCL_INVALID) {
934 memcpy(cdr.Result, cdr.LocL, 8);
939 memcpy(&cdr.Result, &cdr.subq, 8);
942 case CdlReadT: // SetSession?
944 second_resp_time = cdReadTime * 290 / 4;
948 case CdlReadT + CMD_PART2:
954 if (CDR_getTN(cdr.ResultTN) == -1) {
955 cdr.Stat = DiskError;
956 cdr.Result[0] |= STATUS_ERROR;
958 cdr.Stat = Acknowledge;
959 cdr.Result[1] = itob(cdr.ResultTN[0]);
960 cdr.Result[2] = itob(cdr.ResultTN[1]);
965 cdr.Track = btoi(cdr.Param[0]);
967 if (CDR_getTD(cdr.Track, cdr.ResultTD) == -1) {
968 cdr.Stat = DiskError;
969 cdr.Result[0] |= STATUS_ERROR;
971 cdr.Stat = Acknowledge;
972 cdr.Result[0] = cdr.StatP;
973 cdr.Result[1] = itob(cdr.ResultTD[2]);
974 cdr.Result[2] = itob(cdr.ResultTD[1]);
975 /* According to Nocash's documentation, the function doesn't care about ff.
976 * This can be seen also in Mednafen's implementation. */
977 //cdr.Result[3] = itob(cdr.ResultTD[0]);
985 SetPlaySeekRead(cdr.StatP, STATUS_SEEK | STATUS_ROTATING);
987 seekTime = cdrSeekTime(cdr.SetSector);
988 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
990 Crusaders of Might and Magic = 0.5x-4x
991 - fix cutscene speech start
997 - fix cutscene speech
1002 second_resp_time = cdReadTime + seekTime;
1006 case CdlSeekL + CMD_PART2:
1007 case CdlSeekP + CMD_PART2:
1008 SetPlaySeekRead(cdr.StatP, 0);
1009 cdr.Stat = Complete;
1011 Find_CurTrack(cdr.SetSectorPlay);
1012 read_ok = ReadTrack(cdr.SetSectorPlay);
1013 if (read_ok && (buf = CDR_getBuffer()))
1014 memcpy(cdr.LocL, buf, 8);
1015 cdr.TrackChanged = FALSE;
1019 case CdlTest + CMD_WHILE_NOT_READY:
1020 switch (cdr.Param[0]) {
1021 case 0x20: // System Controller ROM Version
1023 memcpy(cdr.Result, Test20, 4);
1027 memcpy(cdr.Result, Test22, 4);
1029 case 0x23: case 0x24:
1031 memcpy(cdr.Result, Test23, 4);
1037 second_resp_time = 20480;
1040 case CdlID + CMD_PART2:
1042 cdr.Result[0] = cdr.StatP;
1047 // 0x10 - audio | 0x40 - disk missing | 0x80 - unlicensed
1048 if (CDR_getStatus(&stat) == -1 || stat.Type == 0 || stat.Type == 0xff) {
1049 cdr.Result[1] = 0xc0;
1053 cdr.Result[1] |= 0x10;
1054 if (CdromId[0] == '\0')
1055 cdr.Result[1] |= 0x80;
1057 cdr.Result[0] |= (cdr.Result[1] >> 4) & 0x08;
1059 /* This adds the string "PCSX" in Playstation bios boot screen */
1060 memcpy((char *)&cdr.Result[4], "PCSX", 4);
1061 cdr.Stat = Complete;
1065 case CdlInit + CMD_WHILE_NOT_READY:
1068 SetPlaySeekRead(cdr.StatP, 0);
1069 // yes, it really sets STATUS_SHELLOPEN
1070 cdr.StatP |= STATUS_SHELLOPEN;
1071 cdr.DriveState = DRIVESTATE_RESCAN_CD;
1077 case CdlGetQ + CMD_WHILE_NOT_READY:
1081 case CdlReadToc + CMD_WHILE_NOT_READY:
1082 cdr.LocL[0] = LOCL_INVALID;
1083 second_resp_time = cdReadTime * 180 / 4;
1087 case CdlReadToc + CMD_PART2:
1088 case CdlReadToc + CMD_PART2 + CMD_WHILE_NOT_READY:
1089 cdr.Stat = Complete;
1094 if (cdr.Reading && !cdr.SetlocPending)
1097 Find_CurTrack(cdr.SetlocPending ? cdr.SetSector : cdr.SetSectorPlay);
1099 if ((cdr.Mode & MODE_CDDA) && cdr.CurTrack > 1)
1100 // Read* acts as play for cdda tracks in cdda mode
1104 if (cdr.SetlocPending) {
1105 seekTime = cdrSeekTime(cdr.SetSector);
1106 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
1107 cdr.SetlocPending = 0;
1110 cdr.FirstSector = 1;
1112 // Fighting Force 2 - update subq time immediately
1114 ReadTrack(cdr.SetSectorPlay);
1115 cdr.LocL[0] = LOCL_INVALID;
1117 CDRPLAYREAD_INT(((cdr.Mode & 0x80) ? (cdReadTime) : cdReadTime * 2) + seekTime, 1);
1119 SetPlaySeekRead(cdr.StatP, STATUS_SEEK);
1125 error = ERROR_INVALIDCMD;
1129 CDR_LOG_I("cdrom: cmd %02x error %02x\n", Cmd, error);
1131 cdr.Result[0] = cdr.StatP | STATUS_ERROR;
1132 cdr.Result[1] = not_ready ? ERROR_NOTREADY : error;
1133 cdr.Stat = DiskError;
1137 if (cdr.DriveState == DRIVESTATE_STOPPED && start_rotating) {
1138 cdr.DriveState = DRIVESTATE_STANDBY;
1139 cdr.StatP |= STATUS_ROTATING;
1142 if (second_resp_time) {
1143 cdr.CmdInProgress = Cmd | 0x100;
1144 CDR_INT(second_resp_time);
1146 else if (cdr.Cmd && cdr.Cmd != (Cmd & 0xff)) {
1147 cdr.CmdInProgress = cdr.Cmd;
1148 CDR_LOG_I("%u cdrom: cmd %02x came before %02x finished\n",
1149 psxRegs.cycle, cdr.Cmd, Cmd);
1156 #define ssat32_to_16(v) \
1157 asm("ssat %0,#16,%1" : "=r" (v) : "r" (v))
1159 #define ssat32_to_16(v) do { \
1160 if (v < -32768) v = -32768; \
1161 else if (v > 32767) v = 32767; \
1165 static void cdrPrepCdda(s16 *buf, int samples)
1167 #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
1169 for (i = 0; i < samples; i++) {
1170 buf[i * 2 + 0] = SWAP16(buf[i * 2 + 0]);
1171 buf[i * 2 + 1] = SWAP16(buf[i * 2 + 1]);
1176 static void cdrAttenuate(s16 *buf, int samples, int stereo)
1179 int ll = cdr.AttenuatorLeftToLeft;
1180 int lr = cdr.AttenuatorLeftToRight;
1181 int rl = cdr.AttenuatorRightToLeft;
1182 int rr = cdr.AttenuatorRightToRight;
1184 if (lr == 0 && rl == 0 && 0x78 <= ll && ll <= 0x88 && 0x78 <= rr && rr <= 0x88)
1187 if (!stereo && ll == 0x40 && lr == 0x40 && rl == 0x40 && rr == 0x40)
1191 for (i = 0; i < samples; i++) {
1194 l = (l * ll + r * rl) >> 7;
1195 r = (r * rr + l * lr) >> 7;
1203 for (i = 0; i < samples; i++) {
1205 l = l * (ll + rl) >> 7;
1206 //r = r * (rr + lr) >> 7;
1214 static void cdrReadInterruptSetResult(unsigned char result)
1217 CDR_LOG_I("cdrom: %d:%02d:%02d irq miss, cmd=%02x irqstat=%02x\n",
1218 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2],
1219 cdr.CmdInProgress, cdr.Stat);
1220 cdr.Irq1Pending = result;
1224 cdr.Result[0] = result;
1225 cdr.Stat = (result & STATUS_ERROR) ? DiskError : DataReady;
1229 static void cdrUpdateTransferBuf(const u8 *buf)
1233 memcpy(cdr.Transfer, buf, DATA_SIZE);
1234 CheckPPFCache(cdr.Transfer, cdr.Prev[0], cdr.Prev[1], cdr.Prev[2]);
1235 CDR_LOG("cdr.Transfer %x:%x:%x\n", cdr.Transfer[0], cdr.Transfer[1], cdr.Transfer[2]);
1236 if (cdr.FifoOffset < 2048 + 12)
1237 CDR_LOG("cdrom: FifoOffset(1) %d/%d\n", cdr.FifoOffset, cdr.FifoSize);
1240 static void cdrReadInterrupt(void)
1242 u8 *buf = NULL, *hdr;
1245 SetPlaySeekRead(cdr.StatP, STATUS_READ | STATUS_ROTATING);
1247 read_ok = ReadTrack(cdr.SetSectorPlay);
1249 buf = CDR_getBuffer();
1254 CDR_LOG_I("cdrReadInterrupt() Log: err\n");
1255 memset(cdr.Transfer, 0, DATA_SIZE);
1256 cdrReadInterruptSetResult(cdr.StatP | STATUS_ERROR);
1259 memcpy(cdr.LocL, buf, 8);
1261 if (!cdr.Irq1Pending)
1262 cdrUpdateTransferBuf(buf);
1264 if ((!cdr.Muted) && (cdr.Mode & MODE_STRSND) && (!Config.Xa) && (cdr.FirstSector != -1)) { // CD-XA
1266 // Firemen 2: Multi-XA files - briefings, cutscenes
1267 if( cdr.FirstSector == 1 && (cdr.Mode & MODE_SF)==0 ) {
1269 cdr.Channel = hdr[1];
1273 * Skips playing on channel 255.
1274 * Fixes missing audio in Blue's Clues : Blue's Big Musical. (Should also fix Taxi 2)
1275 * TODO : Check if this is the proper behaviour.
1277 if ((hdr[2] & 0x4) && hdr[0] == cdr.File && hdr[1] == cdr.Channel && cdr.Channel != 255) {
1278 int ret = xa_decode_sector(&cdr.Xa, buf + 4, cdr.FirstSector);
1280 cdrAttenuate(cdr.Xa.pcm, cdr.Xa.nsamples, cdr.Xa.stereo);
1281 SPU_playADPCMchannel(&cdr.Xa, psxRegs.cycle, cdr.FirstSector);
1282 cdr.FirstSector = 0;
1284 else cdr.FirstSector = -1;
1289 Croc 2: $40 - only FORM1 (*)
1290 Judge Dredd: $C8 - only FORM1 (*)
1291 Sim Theme Park - no adpcm at all (zero)
1294 if (!(cdr.Mode & MODE_STRSND) || !(buf[4+2] & 0x4))
1295 cdrReadInterruptSetResult(cdr.StatP);
1297 cdr.SetSectorPlay[2]++;
1298 if (cdr.SetSectorPlay[2] == 75) {
1299 cdr.SetSectorPlay[2] = 0;
1300 cdr.SetSectorPlay[1]++;
1301 if (cdr.SetSectorPlay[1] == 60) {
1302 cdr.SetSectorPlay[1] = 0;
1303 cdr.SetSectorPlay[0]++;
1307 if (!cdr.Irq1Pending) {
1308 // update for CdlGetlocP
1309 ReadTrack(cdr.SetSectorPlay);
1312 CDRPLAYREAD_INT((cdr.Mode & MODE_SPEED) ? (cdReadTime / 2) : cdReadTime, 0);
1321 bit 5 - 1 result ready
1323 bit 7 - 1 command being processed
1326 unsigned char cdrRead0(void) {
1327 if (cdr.ResultReady)
1332 cdr.Ctrl |= 0x40; // data fifo not empty
1334 // What means the 0x10 and the 0x08 bits? I only saw it used by the bios
1337 CDR_LOG_IO("cdr r0.sta: %02x\n", cdr.Ctrl);
1339 return psxHu8(0x1800) = cdr.Ctrl;
1342 void cdrWrite0(unsigned char rt) {
1343 CDR_LOG_IO("cdr w0.idx: %02x\n", rt);
1345 cdr.Ctrl = (rt & 3) | (cdr.Ctrl & ~3);
1348 unsigned char cdrRead1(void) {
1349 if ((cdr.ResultP & 0xf) < cdr.ResultC)
1350 psxHu8(0x1801) = cdr.Result[cdr.ResultP & 0xf];
1354 if (cdr.ResultP == cdr.ResultC)
1355 cdr.ResultReady = 0;
1357 CDR_LOG_IO("cdr r1.rsp: %02x #%u\n", psxHu8(0x1801), cdr.ResultP - 1);
1359 return psxHu8(0x1801);
1362 void cdrWrite1(unsigned char rt) {
1363 const char *rnames[] = { "cmd", "smd", "smc", "arr" }; (void)rnames;
1364 CDR_LOG_IO("cdr w1.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1366 switch (cdr.Ctrl & 3) {
1370 cdr.AttenuatorRightToRightT = rt;
1376 #ifdef CDR_LOG_CMD_IRQ
1377 SysPrintf("%u cdrom: CD1 write: %x (%s)", psxRegs.cycle, rt, CmdName[rt]);
1380 SysPrintf(" Param[%d] = {", cdr.ParamC);
1381 for (i = 0; i < cdr.ParamC; i++)
1382 SysPrintf(" %x,", cdr.Param[i]);
1389 cdr.ResultReady = 0;
1392 if (!cdr.CmdInProgress) {
1393 cdr.CmdInProgress = rt;
1394 // should be something like 12k + controller delays
1398 CDR_LOG_I("%u cdrom: cmd while busy: %02x, prev %02x, busy %02x\n",
1399 psxRegs.cycle, rt, cdr.Cmd, cdr.CmdInProgress);
1400 if (cdr.CmdInProgress < 0x100) // no pending 2nd response
1401 cdr.CmdInProgress = rt;
1407 unsigned char cdrRead2(void) {
1408 unsigned char ret = 0;
1410 if (cdr.FifoOffset < cdr.FifoSize)
1411 ret = cdr.Transfer[cdr.FifoOffset++];
1413 CDR_LOG_I("cdrom: read empty fifo (%d)\n", cdr.FifoSize);
1415 CDR_LOG_IO("cdr r2.dat: %02x\n", ret);
1419 void cdrWrite2(unsigned char rt) {
1420 const char *rnames[] = { "prm", "ien", "all", "arl" }; (void)rnames;
1421 CDR_LOG_IO("cdr w2.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1423 switch (cdr.Ctrl & 3) {
1425 if (cdr.ParamC < 8) // FIXME: size and wrapping
1426 cdr.Param[cdr.ParamC++] = rt;
1433 cdr.AttenuatorLeftToLeftT = rt;
1436 cdr.AttenuatorRightToLeftT = rt;
1441 unsigned char cdrRead3(void) {
1443 psxHu8(0x1803) = cdr.Stat | 0xE0;
1445 psxHu8(0x1803) = cdr.Reg2 | 0xE0;
1447 CDR_LOG_IO("cdr r3.%s: %02x\n", (cdr.Ctrl & 1) ? "ifl" : "ien", psxHu8(0x1803));
1448 return psxHu8(0x1803);
1451 void cdrWrite3(unsigned char rt) {
1452 const char *rnames[] = { "req", "ifl", "alr", "ava" }; (void)rnames;
1453 CDR_LOG_IO("cdr w3.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1455 switch (cdr.Ctrl & 3) {
1459 if (cdr.Stat & rt) {
1460 #ifdef CDR_LOG_CMD_IRQ
1461 SysPrintf("%u cdrom: ack %02x (w %02x)\n",
1462 psxRegs.cycle, cdr.Stat & rt, rt);
1464 // note: Croc vs Discworld Noir
1465 if (!(psxRegs.interrupt & (1 << PSXINT_CDR)) &&
1466 (cdr.CmdInProgress || cdr.Irq1Pending))
1467 CDR_INT(850); // 711-993
1475 cdr.AttenuatorLeftToRightT = rt;
1479 memcpy(&cdr.AttenuatorLeftToLeft, &cdr.AttenuatorLeftToLeftT, 4);
1480 CDR_LOG("CD-XA Volume: %02x %02x | %02x %02x\n",
1481 cdr.AttenuatorLeftToLeft, cdr.AttenuatorLeftToRight,
1482 cdr.AttenuatorRightToLeft, cdr.AttenuatorRightToRight);
1488 if ((rt & 0x80) && cdr.FifoOffset < cdr.FifoSize) {
1489 CDR_LOG("cdrom: FifoOffset(2) %d/%d\n", cdr.FifoOffset, cdr.FifoSize);
1491 else if (rt & 0x80) {
1492 switch (cdr.Mode & 0x30) {
1493 case MODE_SIZE_2328:
1495 cdr.FifoOffset = 12;
1496 cdr.FifoSize = 2048 + 12;
1499 case MODE_SIZE_2340:
1502 cdr.FifoSize = 2340;
1506 else if (!(rt & 0xc0))
1507 cdr.FifoOffset = DATA_SIZE; // fifo empty
1510 void psxDma3(u32 madr, u32 bcr, u32 chcr) {
1515 CDR_LOG("psxDma3() Log: *** DMA 3 *** %x addr = %x size = %x\n", chcr, madr, bcr);
1517 switch (chcr & 0x71000000) {
1519 ptr = (u8 *)PSXM(madr);
1521 CDR_LOG_I("psxDma3() Log: *** DMA 3 *** NULL Pointer!\n");
1525 cdsize = (((bcr - 1) & 0xffff) + 1) * 4;
1528 GS CDX: Enhancement CD crash
1531 - Spams DMA3 and gets buffer overrun
1533 size = DATA_SIZE - cdr.FifoOffset;
1538 memcpy(ptr, cdr.Transfer + cdr.FifoOffset, size);
1539 cdr.FifoOffset += size;
1540 psxCpu->Clear(madr, size / 4);
1543 CDR_LOG_I("cdrom: dma3 %d/%d\n", size, cdsize);
1545 CDRDMA_INT((cdsize/4) * 24);
1547 HW_DMA3_CHCR &= SWAPu32(~0x10000000);
1549 HW_DMA3_MADR = SWAPu32(madr + cdsize);
1550 HW_DMA3_BCR &= SWAPu32(0xffff0000);
1554 psxRegs.cycle += (cdsize/4) * 24 - 20;
1559 CDR_LOG_I("psxDma3() Log: Unknown cddma %x\n", chcr);
1563 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1567 void cdrDmaInterrupt(void)
1569 if (HW_DMA3_CHCR & SWAP32(0x01000000))
1571 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1576 static void getCdInfo(void)
1580 CDR_getTN(cdr.ResultTN);
1581 CDR_getTD(0, cdr.SetSectorEnd);
1582 tmp = cdr.SetSectorEnd[0];
1583 cdr.SetSectorEnd[0] = cdr.SetSectorEnd[2];
1584 cdr.SetSectorEnd[2] = tmp;
1588 memset(&cdr, 0, sizeof(cdr));
1594 cdr.FifoOffset = DATA_SIZE; // fifo empty
1595 if (CdromId[0] == '\0') {
1596 cdr.DriveState = DRIVESTATE_STOPPED;
1600 cdr.DriveState = DRIVESTATE_STANDBY;
1601 cdr.StatP = STATUS_ROTATING;
1604 // BIOS player - default values
1605 cdr.AttenuatorLeftToLeft = 0x80;
1606 cdr.AttenuatorLeftToRight = 0x00;
1607 cdr.AttenuatorRightToLeft = 0x00;
1608 cdr.AttenuatorRightToRight = 0x80;
1613 int cdrFreeze(void *f, int Mode) {
1617 if (Mode == 0 && !Config.Cdda)
1620 cdr.freeze_ver = 0x63647202;
1621 gzfreeze(&cdr, sizeof(cdr));
1624 cdr.ParamP = cdr.ParamC;
1625 tmp = cdr.FifoOffset;
1628 gzfreeze(&tmp, sizeof(tmp));
1633 cdr.FifoOffset = tmp;
1634 cdr.FifoSize = (cdr.Mode & 0x20) ? 2340 : 2048 + 12;
1636 // read right sub data
1637 tmpp[0] = btoi(cdr.Prev[0]);
1638 tmpp[1] = btoi(cdr.Prev[1]);
1639 tmpp[2] = btoi(cdr.Prev[2]);
1644 if (cdr.freeze_ver < 0x63647202)
1645 memcpy(cdr.SetSectorPlay, cdr.SetSector, 3);
1647 Find_CurTrack(cdr.SetSectorPlay);
1649 CDR_play(cdr.SetSectorPlay);
1650 if (psxRegs.interrupt & (1 << PSXINT_CDRPLAY_OLD))
1651 CDRPLAYREAD_INT((cdr.Mode & 0x80) ? (cdReadTime / 2) : cdReadTime, 1);
1654 if ((cdr.freeze_ver & 0xffffff00) != 0x63647200) {
1655 // old versions did not latch Reg2, have to fixup..
1656 if (cdr.Reg2 == 0) {
1657 SysPrintf("cdrom: fixing up old savestate\n");
1660 // also did not save Attenuator..
1661 if ((cdr.AttenuatorLeftToLeft | cdr.AttenuatorLeftToRight
1662 | cdr.AttenuatorRightToLeft | cdr.AttenuatorRightToRight) == 0)
1664 cdr.AttenuatorLeftToLeft = cdr.AttenuatorRightToRight = 0x80;
1672 void LidInterrupt(void) {
1674 cdrLidSeekInterrupt();