Proper CDDA fix for Fantastic Pinball Kyuutenkai (and remove CDDA playthread as it...
[pcsx_rearmed.git] / libpcsxcore / cdrom.c
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 "cdrom.h"
25 #include "ppf.h"
26 #include "psxdma.h"
27 #include "arm_features.h"
28
29 /* logging */
30 #if 0
31 #define CDR_LOG SysPrintf
32 #else
33 #define CDR_LOG(...)
34 #endif
35 #if 0
36 #define CDR_LOG_I SysPrintf
37 #else
38 #define CDR_LOG_I(...)
39 #endif
40 #if 0
41 #define CDR_LOG_IO SysPrintf
42 #else
43 #define CDR_LOG_IO(...)
44 #endif
45 //#define CDR_LOG_CMD_IRQ
46
47 cdrStruct cdr;
48 static unsigned char *pTransfer;
49 static s16 read_buf[CD_FRAMESIZE_RAW/2];
50
51 /* CD-ROM magic numbers */
52 #define CdlSync        0  /* nocash documentation : "Uh, actually, returns error code 40h = Invalid Command...?" */
53 #define CdlNop         1
54 #define CdlSetloc      2
55 #define CdlPlay        3
56 #define CdlForward     4
57 #define CdlBackward    5
58 #define CdlReadN       6
59 #define CdlStandby     7
60 #define CdlStop        8
61 #define CdlPause       9
62 #define CdlReset       10
63 #define CdlMute        11
64 #define CdlDemute      12
65 #define CdlSetfilter   13
66 #define CdlSetmode     14
67 #define CdlGetparam    15
68 #define CdlGetlocL     16
69 #define CdlGetlocP     17
70 #define CdlReadT       18
71 #define CdlGetTN       19
72 #define CdlGetTD       20
73 #define CdlSeekL       21
74 #define CdlSeekP       22
75 #define CdlSetclock    23
76 #define CdlGetclock    24
77 #define CdlTest        25
78 #define CdlID          26
79 #define CdlReadS       27
80 #define CdlInit        28
81 #define CdlGetQ        29
82 #define CdlReadToc     30
83
84 char *CmdName[0x100]= {
85     "CdlSync",     "CdlNop",       "CdlSetloc",  "CdlPlay",
86     "CdlForward",  "CdlBackward",  "CdlReadN",   "CdlStandby",
87     "CdlStop",     "CdlPause",     "CdlReset",    "CdlMute",
88     "CdlDemute",   "CdlSetfilter", "CdlSetmode", "CdlGetparam",
89     "CdlGetlocL",  "CdlGetlocP",   "CdlReadT",   "CdlGetTN",
90     "CdlGetTD",    "CdlSeekL",     "CdlSeekP",   "CdlSetclock",
91     "CdlGetclock", "CdlTest",      "CdlID",      "CdlReadS",
92     "CdlInit",     NULL,           "CDlReadToc", NULL
93 };
94
95 unsigned char Test04[] = { 0 };
96 unsigned char Test05[] = { 0 };
97 unsigned char Test20[] = { 0x98, 0x06, 0x10, 0xC3 };
98 unsigned char Test22[] = { 0x66, 0x6F, 0x72, 0x20, 0x45, 0x75, 0x72, 0x6F };
99 unsigned char Test23[] = { 0x43, 0x58, 0x44, 0x32, 0x39 ,0x34, 0x30, 0x51 };
100
101 // cdr.Stat:
102 #define NoIntr          0
103 #define DataReady       1
104 #define Complete        2
105 #define Acknowledge     3
106 #define DataEnd         4
107 #define DiskError       5
108
109 /* Modes flags */
110 #define MODE_SPEED       (1<<7) // 0x80
111 #define MODE_STRSND      (1<<6) // 0x40 ADPCM on/off
112 #define MODE_SIZE_2340   (1<<5) // 0x20
113 #define MODE_SIZE_2328   (1<<4) // 0x10
114 #define MODE_SIZE_2048   (0<<4) // 0x00
115 #define MODE_SF          (1<<3) // 0x08 channel on/off
116 #define MODE_REPORT      (1<<2) // 0x04
117 #define MODE_AUTOPAUSE   (1<<1) // 0x02
118 #define MODE_CDDA        (1<<0) // 0x01
119
120 /* Status flags */
121 #define STATUS_PLAY      (1<<7) // 0x80
122 #define STATUS_SEEK      (1<<6) // 0x40
123 #define STATUS_READ      (1<<5) // 0x20
124 #define STATUS_SHELLOPEN (1<<4) // 0x10
125 #define STATUS_UNKNOWN3  (1<<3) // 0x08
126 #define STATUS_UNKNOWN2  (1<<2) // 0x04
127 #define STATUS_ROTATING  (1<<1) // 0x02
128 #define STATUS_ERROR     (1<<0) // 0x01
129
130 /* Errors */
131 #define ERROR_NOTREADY   (1<<7) // 0x80
132 #define ERROR_INVALIDCMD (1<<6) // 0x40
133 #define ERROR_INVALIDARG (1<<5) // 0x20
134
135 // 1x = 75 sectors per second
136 // PSXCLK = 1 sec in the ps
137 // so (PSXCLK / 75) = cdr read time (linuzappz)
138 #define cdReadTime (PSXCLK / 75)
139
140 enum drive_state {
141         DRIVESTATE_STANDBY = 0,
142         DRIVESTATE_LID_OPEN,
143         DRIVESTATE_RESCAN_CD,
144         DRIVESTATE_PREPARE_CD,
145         DRIVESTATE_STOPPED,
146 };
147
148 // for cdr.Seeked
149 enum seeked_state {
150         SEEK_PENDING = 0,
151         SEEK_DONE = 1,
152 };
153
154 static struct CdrStat stat;
155
156 static unsigned int msf2sec(const u8 *msf) {
157         return ((msf[0] * 60 + msf[1]) * 75) + msf[2];
158 }
159
160 // for that weird psemu API..
161 static unsigned int fsm2sec(const u8 *msf) {
162         return ((msf[2] * 60 + msf[1]) * 75) + msf[0];
163 }
164
165 static void sec2msf(unsigned int s, u8 *msf) {
166         msf[0] = s / 75 / 60;
167         s = s - msf[0] * 75 * 60;
168         msf[1] = s / 75;
169         s = s - msf[1] * 75;
170         msf[2] = s;
171 }
172
173 // cdrInterrupt
174 #define CDR_INT(eCycle) { \
175         psxRegs.interrupt |= (1 << PSXINT_CDR); \
176         psxRegs.intCycle[PSXINT_CDR].cycle = eCycle; \
177         psxRegs.intCycle[PSXINT_CDR].sCycle = psxRegs.cycle; \
178         new_dyna_set_event(PSXINT_CDR, eCycle); \
179 }
180
181 // cdrReadInterrupt
182 #define CDREAD_INT(eCycle) { \
183         psxRegs.interrupt |= (1 << PSXINT_CDREAD); \
184         psxRegs.intCycle[PSXINT_CDREAD].cycle = eCycle; \
185         psxRegs.intCycle[PSXINT_CDREAD].sCycle = psxRegs.cycle; \
186         new_dyna_set_event(PSXINT_CDREAD, eCycle); \
187 }
188
189 // cdrLidSeekInterrupt
190 #define CDRLID_INT(eCycle) { \
191         psxRegs.interrupt |= (1 << PSXINT_CDRLID); \
192         psxRegs.intCycle[PSXINT_CDRLID].cycle = eCycle; \
193         psxRegs.intCycle[PSXINT_CDRLID].sCycle = psxRegs.cycle; \
194         new_dyna_set_event(PSXINT_CDRLID, eCycle); \
195 }
196
197 // cdrPlayInterrupt
198 #define CDRMISC_INT(eCycle) { \
199         psxRegs.interrupt |= (1 << PSXINT_CDRPLAY); \
200         psxRegs.intCycle[PSXINT_CDRPLAY].cycle = eCycle; \
201         psxRegs.intCycle[PSXINT_CDRPLAY].sCycle = psxRegs.cycle; \
202         new_dyna_set_event(PSXINT_CDRPLAY, eCycle); \
203 }
204
205 #define StopReading() { \
206         if (cdr.Reading) { \
207                 cdr.Reading = 0; \
208                 psxRegs.interrupt &= ~(1 << PSXINT_CDREAD); \
209         } \
210         cdr.StatP &= ~(STATUS_READ|STATUS_SEEK);\
211 }
212
213 #define StopCdda() { \
214         if (cdr.Play) { \
215                 if (!Config.Cdda) CDR_stop(); \
216                 cdr.StatP &= ~STATUS_PLAY; \
217                 cdr.Play = FALSE; \
218                 cdr.FastForward = 0; \
219                 cdr.FastBackward = 0; \
220                 /*SPU_registerCallback( SPUirq );*/ \
221         } \
222 }
223
224 #define SetResultSize(size) { \
225         cdr.ResultP = 0; \
226         cdr.ResultC = size; \
227         cdr.ResultReady = 1; \
228 }
229
230 static void setIrq(void)
231 {
232         if (cdr.Stat & cdr.Reg2)
233                 psxHu32ref(0x1070) |= SWAP32((u32)0x4);
234 }
235
236 // timing used in this function was taken from tests on real hardware
237 // (yes it's slow, but you probably don't want to modify it)
238 void cdrLidSeekInterrupt()
239 {
240         switch (cdr.DriveState) {
241         default:
242         case DRIVESTATE_STANDBY:
243                 cdr.StatP &= ~STATUS_SEEK;
244
245                 if (CDR_getStatus(&stat) == -1)
246                         return;
247
248                 if (stat.Status & STATUS_SHELLOPEN)
249                 {
250                         StopCdda();
251                         cdr.DriveState = DRIVESTATE_LID_OPEN;
252                         CDRLID_INT(0x800);
253                 }
254                 break;
255
256         case DRIVESTATE_LID_OPEN:
257                 if (CDR_getStatus(&stat) == -1)
258                         stat.Status &= ~STATUS_SHELLOPEN;
259
260                 // 02, 12, 10
261                 if (!(cdr.StatP & STATUS_SHELLOPEN)) {
262                         StopReading();
263                         cdr.StatP |= STATUS_SHELLOPEN;
264
265                         // could generate error irq here, but real hardware
266                         // only sometimes does that
267                         // (not done when lots of commands are sent?)
268
269                         CDRLID_INT(cdReadTime * 30);
270                         break;
271                 }
272                 else if (cdr.StatP & STATUS_ROTATING) {
273                         cdr.StatP &= ~STATUS_ROTATING;
274                 }
275                 else if (!(stat.Status & STATUS_SHELLOPEN)) {
276                         // closed now
277                         CheckCdrom();
278
279                         // cdr.StatP STATUS_SHELLOPEN is "sticky"
280                         // and is only cleared by CdlNop
281
282                         cdr.DriveState = DRIVESTATE_RESCAN_CD;
283                         CDRLID_INT(cdReadTime * 105);
284                         break;
285                 }
286
287                 // recheck for close
288                 CDRLID_INT(cdReadTime * 3);
289                 break;
290
291         case DRIVESTATE_RESCAN_CD:
292                 cdr.StatP |= STATUS_ROTATING;
293                 cdr.DriveState = DRIVESTATE_PREPARE_CD;
294
295                 // this is very long on real hardware, over 6 seconds
296                 // make it a bit faster here...
297                 CDRLID_INT(cdReadTime * 150);
298                 break;
299
300         case DRIVESTATE_PREPARE_CD:
301                 cdr.StatP |= STATUS_SEEK;
302
303                 cdr.DriveState = DRIVESTATE_STANDBY;
304                 CDRLID_INT(cdReadTime * 26);
305                 break;
306         }
307 }
308
309 static void Find_CurTrack(const u8 *time)
310 {
311         int current, sect;
312
313         current = msf2sec(time);
314
315         for (cdr.CurTrack = 1; cdr.CurTrack < cdr.ResultTN[1]; cdr.CurTrack++) {
316                 CDR_getTD(cdr.CurTrack + 1, cdr.ResultTD);
317                 sect = fsm2sec(cdr.ResultTD);
318                 if (sect - current >= 150)
319                         break;
320         }
321 }
322
323 static void generate_subq(const u8 *time)
324 {
325         unsigned char start[3], next[3];
326         unsigned int this_s, start_s, next_s, pregap;
327         int relative_s;
328
329         CDR_getTD(cdr.CurTrack, start);
330         if (cdr.CurTrack + 1 <= cdr.ResultTN[1]) {
331                 pregap = 150;
332                 CDR_getTD(cdr.CurTrack + 1, next);
333         }
334         else {
335                 // last track - cd size
336                 pregap = 0;
337                 next[0] = cdr.SetSectorEnd[2];
338                 next[1] = cdr.SetSectorEnd[1];
339                 next[2] = cdr.SetSectorEnd[0];
340         }
341
342         this_s = msf2sec(time);
343         start_s = fsm2sec(start);
344         next_s = fsm2sec(next);
345
346         cdr.TrackChanged = FALSE;
347
348         if (next_s - this_s < pregap) {
349                 cdr.TrackChanged = TRUE;
350                 cdr.CurTrack++;
351                 start_s = next_s;
352         }
353
354         cdr.subq.Index = 1;
355
356         relative_s = this_s - start_s;
357         if (relative_s < 0) {
358                 cdr.subq.Index = 0;
359                 relative_s = -relative_s;
360         }
361         sec2msf(relative_s, cdr.subq.Relative);
362
363         cdr.subq.Track = itob(cdr.CurTrack);
364         cdr.subq.Relative[0] = itob(cdr.subq.Relative[0]);
365         cdr.subq.Relative[1] = itob(cdr.subq.Relative[1]);
366         cdr.subq.Relative[2] = itob(cdr.subq.Relative[2]);
367         cdr.subq.Absolute[0] = itob(time[0]);
368         cdr.subq.Absolute[1] = itob(time[1]);
369         cdr.subq.Absolute[2] = itob(time[2]);
370 }
371
372 static void ReadTrack(const u8 *time) {
373         unsigned char tmp[3];
374         struct SubQ *subq;
375         u16 crc;
376
377         tmp[0] = itob(time[0]);
378         tmp[1] = itob(time[1]);
379         tmp[2] = itob(time[2]);
380
381         if (memcmp(cdr.Prev, tmp, 3) == 0)
382                 return;
383
384         CDR_LOG("ReadTrack *** %02x:%02x:%02x\n", tmp[0], tmp[1], tmp[2]);
385
386         cdr.RErr = CDR_readTrack(tmp);
387         memcpy(cdr.Prev, tmp, 3);
388
389         if (CheckSBI(time))
390                 return;
391
392         subq = (struct SubQ *)CDR_getBufferSub();
393         if (subq != NULL && cdr.CurTrack == 1) {
394                 crc = calcCrc((u8 *)subq + 12, 10);
395                 if (crc == (((u16)subq->CRC[0] << 8) | subq->CRC[1])) {
396                         cdr.subq.Track = subq->TrackNumber;
397                         cdr.subq.Index = subq->IndexNumber;
398                         memcpy(cdr.subq.Relative, subq->TrackRelativeAddress, 3);
399                         memcpy(cdr.subq.Absolute, subq->AbsoluteAddress, 3);
400                 }
401                 else {
402                         CDR_LOG_I("subq bad crc @%02x:%02x:%02x\n",
403                                 tmp[0], tmp[1], tmp[2]);
404                 }
405         }
406         else {
407                 generate_subq(time);
408         }
409
410         CDR_LOG(" -> %02x,%02x %02x:%02x:%02x %02x:%02x:%02x\n",
411                 cdr.subq.Track, cdr.subq.Index,
412                 cdr.subq.Relative[0], cdr.subq.Relative[1], cdr.subq.Relative[2],
413                 cdr.subq.Absolute[0], cdr.subq.Absolute[1], cdr.subq.Absolute[2]);
414 }
415
416 static void AddIrqQueue(unsigned short irq, unsigned long ecycle) {
417         if (cdr.Irq != 0) {
418                 if (irq == cdr.Irq || irq + 0x100 == cdr.Irq) {
419                         cdr.IrqRepeated = 1;
420                         CDR_INT(ecycle);
421                         return;
422                 }
423
424                 CDR_LOG_I("cdr: override cmd %02x -> %02x\n", cdr.Irq, irq);
425         }
426
427         cdr.Irq = irq;
428         cdr.eCycle = ecycle;
429
430         CDR_INT(ecycle);
431 }
432
433 static void cdrPlayInterrupt_Autopause()
434 {
435         u32 abs_lev_max = 0;
436         boolean abs_lev_chselect;
437         u32 i;
438
439         if ((cdr.Mode & MODE_AUTOPAUSE) && cdr.TrackChanged) {
440                 CDR_LOG( "CDDA STOP\n" );
441
442                 // Magic the Gathering
443                 // - looping territory cdda
444
445                 // ...?
446                 //cdr.ResultReady = 1;
447                 //cdr.Stat = DataReady;
448                 cdr.Stat = DataEnd;
449                 setIrq();
450
451                 StopCdda();
452         }
453         else if (cdr.Mode & MODE_REPORT) {
454                 CDR_readCDDA(cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2], (u8 *)read_buf);
455                 cdr.Result[0] = cdr.StatP;
456                 cdr.Result[1] = cdr.subq.Track;
457                 cdr.Result[2] = cdr.subq.Index;
458                 
459                 abs_lev_chselect = cdr.subq.Absolute[1] & 0x01;
460                 
461                 /* 8 is a hack. For accuracy, it should be 588. */
462                 for (i = 0; i < 8; i++)
463                 {
464                         abs_lev_max = MAX_VALUE(abs_lev_max, abs(read_buf[i * 2 + abs_lev_chselect]));
465                 }
466                 abs_lev_max = MIN_VALUE(abs_lev_max, 32767);
467                 abs_lev_max |= abs_lev_chselect << 15;
468
469                 if (cdr.subq.Absolute[2] & 0x10) {
470                         cdr.Result[3] = cdr.subq.Relative[0];
471                         cdr.Result[4] = cdr.subq.Relative[1] | 0x80;
472                         cdr.Result[5] = cdr.subq.Relative[2];
473                 }
474                 else {
475                         cdr.Result[3] = cdr.subq.Absolute[0];
476                         cdr.Result[4] = cdr.subq.Absolute[1];
477                         cdr.Result[5] = cdr.subq.Absolute[2];
478                 }
479
480                 cdr.Result[6] = abs_lev_max >> 0;
481                 cdr.Result[7] = abs_lev_max >> 8;
482
483                 // Rayman: Logo freeze (resultready + dataready)
484                 cdr.ResultReady = 1;
485                 cdr.Stat = DataReady;
486
487                 SetResultSize(8);
488                 setIrq();
489         }
490 }
491
492 // also handles seek
493 void cdrPlayInterrupt()
494 {
495         if (cdr.Seeked == SEEK_PENDING) {
496                 if (cdr.Stat) {
497                         CDR_LOG_I("cdrom: seek stat hack\n");
498                         CDRMISC_INT(0x1000);
499                         return;
500                 }
501                 SetResultSize(1);
502                 cdr.StatP |= STATUS_ROTATING;
503                 cdr.StatP &= ~STATUS_SEEK;
504                 cdr.Result[0] = cdr.StatP;
505                 cdr.Seeked = SEEK_DONE;
506                 if (cdr.Irq == 0) {
507                         cdr.Stat = Complete;
508                         setIrq();
509                 }
510
511                 if (cdr.SetlocPending) {
512                         memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
513                         cdr.SetlocPending = 0;
514                         cdr.m_locationChanged = TRUE;
515                 }
516                 Find_CurTrack(cdr.SetSectorPlay);
517                 ReadTrack(cdr.SetSectorPlay);
518                 cdr.TrackChanged = FALSE;
519         }
520
521         if (!cdr.Play) return;
522
523         CDR_LOG( "CDDA - %d:%d:%d\n",
524                 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2] );
525
526         if (memcmp(cdr.SetSectorPlay, cdr.SetSectorEnd, 3) == 0) {
527                 StopCdda();
528                 cdr.TrackChanged = TRUE;
529         }
530
531         if (!cdr.Irq && !cdr.Stat && (cdr.Mode & (MODE_AUTOPAUSE|MODE_REPORT)))
532                 cdrPlayInterrupt_Autopause();
533
534         if (!cdr.Play) return;
535         
536         if (CDR_readCDDA && !cdr.Muted && cdr.Mode & MODE_REPORT) {
537                 cdrAttenuate((u8 *)read_buf, CD_FRAMESIZE_RAW / 4, 1);
538                 if (SPU_playCDDAchannel)
539                         SPU_playCDDAchannel((u8 *)read_buf, CD_FRAMESIZE_RAW);
540         }
541
542         cdr.SetSectorPlay[2]++;
543         if (cdr.SetSectorPlay[2] == 75) {
544                 cdr.SetSectorPlay[2] = 0;
545                 cdr.SetSectorPlay[1]++;
546                 if (cdr.SetSectorPlay[1] == 60) {
547                         cdr.SetSectorPlay[1] = 0;
548                         cdr.SetSectorPlay[0]++;
549                 }
550         }
551
552         if (cdr.m_locationChanged)
553         {
554                 CDRMISC_INT(cdReadTime * 30);
555                 cdr.m_locationChanged = FALSE;
556         }
557         else
558         {
559                 CDRMISC_INT(cdReadTime);
560         }
561
562         // update for CdlGetlocP/autopause
563         generate_subq(cdr.SetSectorPlay);
564 }
565
566 void cdrInterrupt() {
567         u16 Irq = cdr.Irq;
568         int no_busy_error = 0;
569         int start_rotating = 0;
570         int error = 0;
571         int delay;
572         unsigned int seekTime = 0;
573
574         // Reschedule IRQ
575         if (cdr.Stat) {
576                 CDR_LOG_I("cdrom: stat hack: %02x %x\n", cdr.Irq, cdr.Stat);
577                 CDR_INT(0x1000);
578                 return;
579         }
580
581         cdr.Ctrl &= ~0x80;
582
583         // default response
584         SetResultSize(1);
585         cdr.Result[0] = cdr.StatP;
586         cdr.Stat = Acknowledge;
587
588         if (cdr.IrqRepeated) {
589                 cdr.IrqRepeated = 0;
590                 if (cdr.eCycle > psxRegs.cycle) {
591                         CDR_INT(cdr.eCycle);
592                         goto finish;
593                 }
594         }
595
596         cdr.Irq = 0;
597
598         switch (Irq) {
599                 case CdlNop:
600                         if (cdr.DriveState != DRIVESTATE_LID_OPEN)
601                                 cdr.StatP &= ~STATUS_SHELLOPEN;
602                         no_busy_error = 1;
603                         break;
604
605                 case CdlSetloc:
606                         break;
607
608                 do_CdlPlay:
609                 case CdlPlay:
610                         StopCdda();
611                         if (cdr.Seeked == SEEK_PENDING) {
612                                 // XXX: wrong, should seek instead..
613                                 cdr.Seeked = SEEK_DONE;
614                         }
615                         if (cdr.SetlocPending) {
616                                 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
617                                 cdr.SetlocPending = 0;
618                                 cdr.m_locationChanged = TRUE;
619                         }
620
621                         // BIOS CD Player
622                         // - Pause player, hit Track 01/02/../xx (Setloc issued!!)
623
624                         if (cdr.ParamC == 0 || cdr.Param[0] == 0) {
625                                 CDR_LOG("PLAY Resume @ %d:%d:%d\n",
626                                         cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2]);
627                         }
628                         else
629                         {
630                                 int track = btoi( cdr.Param[0] );
631
632                                 if (track <= cdr.ResultTN[1])
633                                         cdr.CurTrack = track;
634
635                                 CDR_LOG("PLAY track %d\n", cdr.CurTrack);
636
637                                 if (CDR_getTD((u8)cdr.CurTrack, cdr.ResultTD) != -1) {
638                                         cdr.SetSectorPlay[0] = cdr.ResultTD[2];
639                                         cdr.SetSectorPlay[1] = cdr.ResultTD[1];
640                                         cdr.SetSectorPlay[2] = cdr.ResultTD[0];
641                                 }
642                         }
643
644                         /*
645                         Rayman: detect track changes
646                         - fixes logo freeze
647
648                         Twisted Metal 2: skip PREGAP + starting accurate SubQ
649                         - plays tracks without retry play
650
651                         Wild 9: skip PREGAP + starting accurate SubQ
652                         - plays tracks without retry play
653                         */
654                         Find_CurTrack(cdr.SetSectorPlay);
655                         ReadTrack(cdr.SetSectorPlay);
656                         cdr.TrackChanged = FALSE;
657
658                         if (!Config.Cdda)
659                                 CDR_play(cdr.SetSectorPlay);
660
661                         // Vib Ribbon: gameplay checks flag
662                         cdr.StatP &= ~STATUS_SEEK;
663                         cdr.Result[0] = cdr.StatP;
664
665                         cdr.StatP |= STATUS_PLAY;
666                         
667                         // BIOS player - set flag again
668                         cdr.Play = TRUE;
669
670                         CDRMISC_INT( cdReadTime );
671                         start_rotating = 1;
672                         break;
673
674                 case CdlForward:
675                         // TODO: error 80 if stopped
676                         cdr.Stat = Complete;
677
678                         // GameShark CD Player: Calls 2x + Play 2x
679                         if( cdr.FastForward == 0 ) cdr.FastForward = 2;
680                         else cdr.FastForward++;
681
682                         cdr.FastBackward = 0;
683                         break;
684
685                 case CdlBackward:
686                         cdr.Stat = Complete;
687
688                         // GameShark CD Player: Calls 2x + Play 2x
689                         if( cdr.FastBackward == 0 ) cdr.FastBackward = 2;
690                         else cdr.FastBackward++;
691
692                         cdr.FastForward = 0;
693                         break;
694
695                 case CdlStandby:
696                         if (cdr.DriveState != DRIVESTATE_STOPPED) {
697                                 error = ERROR_INVALIDARG;
698                                 goto set_error;
699                         }
700                         AddIrqQueue(CdlStandby + 0x100, cdReadTime * 125 / 2);
701                         start_rotating = 1;
702                         break;
703
704                 case CdlStandby + 0x100:
705                         cdr.Stat = Complete;
706                         break;
707
708                 case CdlStop:
709                         if (cdr.Play) {
710                                 // grab time for current track
711                                 CDR_getTD((u8)(cdr.CurTrack), cdr.ResultTD);
712
713                                 cdr.SetSectorPlay[0] = cdr.ResultTD[2];
714                                 cdr.SetSectorPlay[1] = cdr.ResultTD[1];
715                                 cdr.SetSectorPlay[2] = cdr.ResultTD[0];
716                         }
717
718                         StopCdda();
719                         StopReading();
720
721                         delay = 0x800;
722                         if (cdr.DriveState == DRIVESTATE_STANDBY)
723                                 delay = cdReadTime * 30 / 2;
724
725                         cdr.DriveState = DRIVESTATE_STOPPED;
726                         AddIrqQueue(CdlStop + 0x100, delay);
727                         break;
728
729                 case CdlStop + 0x100:
730                         cdr.StatP &= ~STATUS_ROTATING;
731                         cdr.Result[0] = cdr.StatP;
732                         cdr.Stat = Complete;
733                         break;
734
735                 case CdlPause:
736                         /*
737                         Gundam Battle Assault 2: much slower (*)
738                         - Fixes boot, gameplay
739
740                         Hokuto no Ken 2: slower
741                         - Fixes intro + subtitles
742
743                         InuYasha - Feudal Fairy Tale: slower
744                         - Fixes battles
745                         */
746                         /* Gameblabla - Tightening the timings (as taken from Duckstation). 
747                          * The timings from Duckstation are based upon hardware tests.
748                          * Mednafen's timing don't work for Gundam Battle Assault 2 in PAL/50hz mode,
749                          * seems to be timing sensitive as it can depend on the CPU's clock speed.
750                          * */
751                         if (cdr.DriveState != DRIVESTATE_STANDBY)
752                         {
753                                 delay = 7000;
754                         }
755                         else
756                         {
757                                 delay = (((cdr.Mode & MODE_SPEED) ? 2 : 1) * (1000000));
758                                 CDRMISC_INT((cdr.Mode & MODE_SPEED) ? cdReadTime / 2 : cdReadTime);
759                         }
760                         AddIrqQueue(CdlPause + 0x100, delay);
761                         cdr.Ctrl |= 0x80;
762                         break;
763
764                 case CdlPause + 0x100:
765                         cdr.StatP &= ~STATUS_READ;
766                         cdr.Result[0] = cdr.StatP;
767                         cdr.Stat = Complete;
768                         break;
769
770                 case CdlReset:
771                         cdr.Muted = FALSE;
772                         cdr.Mode = 0x20; /* This fixes This is Football 2, Pooh's Party lockups */
773                         AddIrqQueue(CdlReset + 0x100, 4100000);
774                         no_busy_error = 1;
775                         start_rotating = 1;
776                         break;
777
778                 case CdlReset + 0x100:
779                         cdr.Stat = Complete;
780                         break;
781
782                 case CdlMute:
783                         cdr.Muted = TRUE;
784                         break;
785
786                 case CdlDemute:
787                         cdr.Muted = FALSE;
788                         break;
789
790                 case CdlSetfilter:
791                         cdr.File = cdr.Param[0];
792                         cdr.Channel = cdr.Param[1];
793                         break;
794
795                 case CdlSetmode:
796                         no_busy_error = 1;
797                         break;
798
799                 case CdlGetparam:
800                         /* Gameblabla : According to mednafen, Result size should be 5 and done this way. */
801                         SetResultSize(5);
802                         cdr.Result[1] = cdr.Mode;
803                         cdr.Result[2] = 0;
804                         cdr.Result[3] = cdr.File;
805                         cdr.Result[4] = cdr.Channel;
806                         no_busy_error = 1;
807                         break;
808
809                 case CdlGetlocL:
810                         SetResultSize(8);
811                         memcpy(cdr.Result, cdr.Transfer, 8);
812                         break;
813
814                 case CdlGetlocP:
815                         SetResultSize(8);
816                         memcpy(&cdr.Result, &cdr.subq, 8);
817
818                         if (!cdr.Play && !cdr.Reading)
819                                 cdr.Result[1] = 0; // HACK?
820                         break;
821
822                 case CdlReadT: // SetSession?
823                         // really long
824                         AddIrqQueue(CdlReadT + 0x100, cdReadTime * 290 / 4);
825                         start_rotating = 1;
826                         break;
827
828                 case CdlReadT + 0x100:
829                         cdr.Stat = Complete;
830                         break;
831
832                 case CdlGetTN:
833                         SetResultSize(3);
834                         if (CDR_getTN(cdr.ResultTN) == -1) {
835                                 cdr.Stat = DiskError;
836                                 cdr.Result[0] |= STATUS_ERROR;
837                         } else {
838                                 cdr.Stat = Acknowledge;
839                                 cdr.Result[1] = itob(cdr.ResultTN[0]);
840                                 cdr.Result[2] = itob(cdr.ResultTN[1]);
841                         }
842                         break;
843
844                 case CdlGetTD:
845                         cdr.Track = btoi(cdr.Param[0]);
846                         SetResultSize(4);
847                         if (CDR_getTD(cdr.Track, cdr.ResultTD) == -1) {
848                                 cdr.Stat = DiskError;
849                                 cdr.Result[0] |= STATUS_ERROR;
850                         } else {
851                                 cdr.Stat = Acknowledge;
852                                 cdr.Result[0] = cdr.StatP;
853                                 cdr.Result[1] = itob(cdr.ResultTD[2]);
854                                 cdr.Result[2] = itob(cdr.ResultTD[1]);
855                                 /* According to Nocash's documentation, the function doesn't care about ff.
856                                  * This can be seen also in Mednafen's implementation. */
857                                 //cdr.Result[3] = itob(cdr.ResultTD[0]);
858                         }
859                         break;
860
861                 case CdlSeekL:
862                 case CdlSeekP:
863                         StopCdda();
864                         StopReading();
865                         cdr.StatP |= STATUS_SEEK;
866
867                         /*
868                         Crusaders of Might and Magic = 0.5x-4x
869                         - fix cutscene speech start
870
871                         Eggs of Steel = 2x-?
872                         - fix new game
873
874                         Medievil = ?-4x
875                         - fix cutscene speech
876
877                         Rockman X5 = 0.5-4x
878                         - fix capcom logo
879                         */
880                         CDRMISC_INT(cdr.Seeked == SEEK_DONE ? 0x800 : cdReadTime * 4);
881                         cdr.Seeked = SEEK_PENDING;
882                         start_rotating = 1;
883                         break;
884
885                 case CdlTest:
886                         switch (cdr.Param[0]) {
887                                 case 0x20: // System Controller ROM Version
888                                         SetResultSize(4);
889                                         memcpy(cdr.Result, Test20, 4);
890                                         break;
891                                 case 0x22:
892                                         SetResultSize(8);
893                                         memcpy(cdr.Result, Test22, 4);
894                                         break;
895                                 case 0x23: case 0x24:
896                                         SetResultSize(8);
897                                         memcpy(cdr.Result, Test23, 4);
898                                         break;
899                         }
900                         no_busy_error = 1;
901                         break;
902
903                 case CdlID:
904                         AddIrqQueue(CdlID + 0x100, 20480);
905                         break;
906
907                 case CdlID + 0x100:
908                         SetResultSize(8);
909                         cdr.Result[0] = cdr.StatP;
910                         cdr.Result[1] = 0;
911                         cdr.Result[2] = 0;
912                         cdr.Result[3] = 0;
913
914                         // 0x10 - audio | 0x40 - disk missing | 0x80 - unlicensed
915                         if (CDR_getStatus(&stat) == -1 || stat.Type == 0 || stat.Type == 0xff) {
916                                 cdr.Result[1] = 0xc0;
917                         }
918                         else {
919                                 if (stat.Type == 2)
920                                         cdr.Result[1] |= 0x10;
921                                 if (CdromId[0] == '\0')
922                                         cdr.Result[1] |= 0x80;
923                         }
924                         cdr.Result[0] |= (cdr.Result[1] >> 4) & 0x08;
925
926                         strncpy((char *)&cdr.Result[4], "PCSX", 4);
927                         cdr.Stat = Complete;
928                         break;
929
930                 case CdlInit:
931                         // yes, it really sets STATUS_SHELLOPEN
932                         cdr.StatP |= STATUS_SHELLOPEN;
933                         cdr.DriveState = DRIVESTATE_RESCAN_CD;
934                         CDRLID_INT(20480);
935                         no_busy_error = 1;
936                         start_rotating = 1;
937                         break;
938
939                 case CdlGetQ:
940                         no_busy_error = 1;
941                         break;
942
943                 case CdlReadToc:
944                         AddIrqQueue(CdlReadToc + 0x100, cdReadTime * 180 / 4);
945                         no_busy_error = 1;
946                         start_rotating = 1;
947                         break;
948
949                 case CdlReadToc + 0x100:
950                         cdr.Stat = Complete;
951                         no_busy_error = 1;
952                         break;
953
954                 case CdlReadN:
955                 case CdlReadS:
956                         if (cdr.SetlocPending) {
957                                 seekTime = abs(msf2sec(cdr.SetSectorPlay) - msf2sec(cdr.SetSector)) * (cdReadTime / 200);
958                                 if(seekTime > 1000000) seekTime = 1000000;
959                                 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
960                                 cdr.SetlocPending = 0;
961                                 cdr.m_locationChanged = TRUE;
962                         }
963                         Find_CurTrack(cdr.SetSectorPlay);
964
965                         if ((cdr.Mode & MODE_CDDA) && cdr.CurTrack > 1)
966                                 // Read* acts as play for cdda tracks in cdda mode
967                                 goto do_CdlPlay;
968
969                         cdr.Reading = 1;
970                         cdr.FirstSector = 1;
971
972                         // Fighting Force 2 - update subq time immediately
973                         // - fixes new game
974                         ReadTrack(cdr.SetSectorPlay);
975
976
977                         // Crusaders of Might and Magic - update getlocl now
978                         // - fixes cutscene speech
979                         {
980                                 u8 *buf = CDR_getBuffer();
981                                 if (buf != NULL)
982                                         memcpy(cdr.Transfer, buf, 8);
983                         }
984
985                         /*
986                         Duke Nukem: Land of the Babes - seek then delay read for one frame
987                         - fixes cutscenes
988                         C-12 - Final Resistance - doesn't like seek
989                         */
990
991                         if (cdr.Seeked != SEEK_DONE) {
992                                 cdr.StatP |= STATUS_SEEK;
993                                 cdr.StatP &= ~STATUS_READ;
994
995                                 // Crusaders of Might and Magic - use short time
996                                 // - fix cutscene speech (startup)
997
998                                 // ??? - use more accurate seek time later
999                                 CDREAD_INT(((cdr.Mode & 0x80) ? (cdReadTime / 2) : cdReadTime * 1) + seekTime);
1000                         } else {
1001                                 cdr.StatP |= STATUS_READ;
1002                                 cdr.StatP &= ~STATUS_SEEK;
1003
1004                                 CDREAD_INT((cdr.Mode & 0x80) ? (cdReadTime / 2) : cdReadTime * 1);
1005                         }
1006
1007                         cdr.Result[0] = cdr.StatP;
1008                         start_rotating = 1;
1009                         break;
1010                 case CdlSync:
1011                 default:
1012                         CDR_LOG_I("Invalid command: %02x\n", Irq);
1013                         error = ERROR_INVALIDCMD;
1014                         // FALLTHROUGH
1015
1016                 set_error:
1017                         SetResultSize(2);
1018                         cdr.Result[0] = cdr.StatP | STATUS_ERROR;
1019                         cdr.Result[1] = error;
1020                         cdr.Stat = DiskError;
1021                         break;
1022         }
1023
1024         if (cdr.DriveState == DRIVESTATE_STOPPED && start_rotating) {
1025                 cdr.DriveState = DRIVESTATE_STANDBY;
1026                 cdr.StatP |= STATUS_ROTATING;
1027         }
1028
1029         if (!no_busy_error) {
1030                 switch (cdr.DriveState) {
1031                 case DRIVESTATE_LID_OPEN:
1032                 case DRIVESTATE_RESCAN_CD:
1033                 case DRIVESTATE_PREPARE_CD:
1034                         SetResultSize(2);
1035                         cdr.Result[0] = cdr.StatP | STATUS_ERROR;
1036                         cdr.Result[1] = ERROR_NOTREADY;
1037                         cdr.Stat = DiskError;
1038                         break;
1039                 }
1040         }
1041
1042 finish:
1043         setIrq();
1044         cdr.ParamC = 0;
1045
1046 #ifdef CDR_LOG_CMD_IRQ
1047         {
1048                 int i;
1049                 SysPrintf("CDR IRQ %d cmd %02x stat %02x: ",
1050                         !!(cdr.Stat & cdr.Reg2), Irq, cdr.Stat);
1051                 for (i = 0; i < cdr.ResultC; i++)
1052                         SysPrintf("%02x ", cdr.Result[i]);
1053                 SysPrintf("\n");
1054         }
1055 #endif
1056 }
1057
1058 #ifdef HAVE_ARMV7
1059  #define ssat32_to_16(v) \
1060   asm("ssat %0,#16,%1" : "=r" (v) : "r" (v))
1061 #else
1062  #define ssat32_to_16(v) do { \
1063   if (v < -32768) v = -32768; \
1064   else if (v > 32767) v = 32767; \
1065  } while (0)
1066 #endif
1067
1068 void cdrAttenuate(s16 *buf, int samples, int stereo)
1069 {
1070         int i, l, r;
1071         int ll = cdr.AttenuatorLeftToLeft;
1072         int lr = cdr.AttenuatorLeftToRight;
1073         int rl = cdr.AttenuatorRightToLeft;
1074         int rr = cdr.AttenuatorRightToRight;
1075
1076         if (lr == 0 && rl == 0 && 0x78 <= ll && ll <= 0x88 && 0x78 <= rr && rr <= 0x88)
1077                 return;
1078
1079         if (!stereo && ll == 0x40 && lr == 0x40 && rl == 0x40 && rr == 0x40)
1080                 return;
1081
1082         if (stereo) {
1083                 for (i = 0; i < samples; i++) {
1084                         l = buf[i * 2];
1085                         r = buf[i * 2 + 1];
1086                         l = (l * ll + r * rl) >> 7;
1087                         r = (r * rr + l * lr) >> 7;
1088                         ssat32_to_16(l);
1089                         ssat32_to_16(r);
1090                         buf[i * 2] = l;
1091                         buf[i * 2 + 1] = r;
1092                 }
1093         }
1094         else {
1095                 for (i = 0; i < samples; i++) {
1096                         l = buf[i];
1097                         l = l * (ll + rl) >> 7;
1098                         //r = r * (rr + lr) >> 7;
1099                         ssat32_to_16(l);
1100                         //ssat32_to_16(r);
1101                         buf[i] = l;
1102                 }
1103         }
1104 }
1105
1106 void cdrReadInterrupt() {
1107         u8 *buf;
1108
1109         if (!cdr.Reading)
1110                 return;
1111
1112         if (cdr.Irq || cdr.Stat) {
1113                 CDR_LOG_I("cdrom: read stat hack %02x %x\n", cdr.Irq, cdr.Stat);
1114                 CDREAD_INT(0x1000);
1115                 return;
1116         }
1117
1118         cdr.OCUP = 1;
1119         SetResultSize(1);
1120         cdr.StatP |= STATUS_READ|STATUS_ROTATING;
1121         cdr.StatP &= ~STATUS_SEEK;
1122         cdr.Result[0] = cdr.StatP;
1123         cdr.Seeked = SEEK_DONE;
1124
1125         ReadTrack(cdr.SetSectorPlay);
1126
1127         buf = CDR_getBuffer();
1128         if (buf == NULL)
1129                 cdr.RErr = -1;
1130
1131         if (cdr.RErr == -1) {
1132                 CDR_LOG_I("cdrReadInterrupt() Log: err\n");
1133                 memset(cdr.Transfer, 0, DATA_SIZE);
1134                 cdr.Stat = DiskError;
1135                 cdr.Result[0] |= STATUS_ERROR;
1136                 CDREAD_INT((cdr.Mode & 0x80) ? (cdReadTime / 2) : cdReadTime);
1137                 return;
1138         }
1139
1140         memcpy(cdr.Transfer, buf, DATA_SIZE);
1141         CheckPPFCache(cdr.Transfer, cdr.Prev[0], cdr.Prev[1], cdr.Prev[2]);
1142
1143
1144         CDR_LOG("cdrReadInterrupt() Log: cdr.Transfer %x:%x:%x\n", cdr.Transfer[0], cdr.Transfer[1], cdr.Transfer[2]);
1145
1146         if ((!cdr.Muted) && (cdr.Mode & MODE_STRSND) && (!Config.Xa) && (cdr.FirstSector != -1)) { // CD-XA
1147                 // Firemen 2: Multi-XA files - briefings, cutscenes
1148                 if( cdr.FirstSector == 1 && (cdr.Mode & MODE_SF)==0 ) {
1149                         cdr.File = cdr.Transfer[4 + 0];
1150                         cdr.Channel = cdr.Transfer[4 + 1];
1151                 }
1152
1153                 /* Gameblabla 
1154                  * Skips playing on channel 255.
1155                  * Fixes missing audio in Blue's Clues : Blue's Big Musical. (Should also fix Taxi 2)
1156                  * TODO : Check if this is the proper behaviour.
1157                  * */
1158                 if((cdr.Transfer[4 + 2] & 0x4) &&
1159                          (cdr.Transfer[4 + 1] == cdr.Channel) &&
1160                          (cdr.Transfer[4 + 0] == cdr.File) && cdr.Channel != 255) {
1161                         int ret = xa_decode_sector(&cdr.Xa, cdr.Transfer+4, cdr.FirstSector);
1162                         if (!ret) {
1163                                 cdrAttenuate(cdr.Xa.pcm, cdr.Xa.nsamples, cdr.Xa.stereo);
1164                                 SPU_playADPCMchannel(&cdr.Xa);
1165                                 cdr.FirstSector = 0;
1166                         }
1167                         else cdr.FirstSector = -1;
1168                 }
1169         }
1170
1171         cdr.SetSectorPlay[2]++;
1172         if (cdr.SetSectorPlay[2] == 75) {
1173                 cdr.SetSectorPlay[2] = 0;
1174                 cdr.SetSectorPlay[1]++;
1175                 if (cdr.SetSectorPlay[1] == 60) {
1176                         cdr.SetSectorPlay[1] = 0;
1177                         cdr.SetSectorPlay[0]++;
1178                 }
1179         }
1180
1181         cdr.Readed = 0;
1182
1183         uint32_t delay = (cdr.Mode & MODE_SPEED) ? (cdReadTime / 2) : cdReadTime;
1184         if (cdr.m_locationChanged) {
1185                 CDREAD_INT(delay * 30);
1186                 cdr.m_locationChanged = FALSE;
1187         } else {
1188                 CDREAD_INT(delay);
1189         }
1190
1191         /*
1192         Croc 2: $40 - only FORM1 (*)
1193         Judge Dredd: $C8 - only FORM1 (*)
1194         Sim Theme Park - no adpcm at all (zero)
1195         */
1196
1197         if (!(cdr.Mode & MODE_STRSND) || !(cdr.Transfer[4+2] & 0x4)) {
1198                 cdr.Stat = DataReady;
1199                 setIrq();
1200         }
1201
1202         // update for CdlGetlocP
1203         ReadTrack(cdr.SetSectorPlay);
1204 }
1205
1206 /*
1207 cdrRead0:
1208         bit 0,1 - mode
1209         bit 2 - unknown
1210         bit 3 - unknown
1211         bit 4 - unknown
1212         bit 5 - 1 result ready
1213         bit 6 - 1 dma ready
1214         bit 7 - 1 command being processed
1215 */
1216
1217 unsigned char cdrRead0(void) {
1218         if (cdr.ResultReady)
1219                 cdr.Ctrl |= 0x20;
1220         else
1221                 cdr.Ctrl &= ~0x20;
1222
1223         if (cdr.OCUP)
1224                 cdr.Ctrl |= 0x40;
1225 //  else
1226 //              cdr.Ctrl &= ~0x40;
1227
1228         // What means the 0x10 and the 0x08 bits? I only saw it used by the bios
1229         cdr.Ctrl |= 0x18;
1230
1231         CDR_LOG_IO("cdr r0: %02x\n", cdr.Ctrl);
1232
1233         return psxHu8(0x1800) = cdr.Ctrl;
1234 }
1235
1236 void cdrWrite0(unsigned char rt) {
1237         CDR_LOG_IO("cdr w0: %02x\n", rt);
1238
1239         cdr.Ctrl = (rt & 3) | (cdr.Ctrl & ~3);
1240 }
1241
1242 unsigned char cdrRead1(void) {
1243         if ((cdr.ResultP & 0xf) < cdr.ResultC)
1244                 psxHu8(0x1801) = cdr.Result[cdr.ResultP & 0xf];
1245         else
1246                 psxHu8(0x1801) = 0;
1247         cdr.ResultP++;
1248         if (cdr.ResultP == cdr.ResultC)
1249                 cdr.ResultReady = 0;
1250
1251         CDR_LOG_IO("cdr r1: %02x\n", psxHu8(0x1801));
1252
1253         return psxHu8(0x1801);
1254 }
1255
1256 void cdrWrite1(unsigned char rt) {
1257         u8 set_loc[3];
1258         int i;
1259
1260         CDR_LOG_IO("cdr w1: %02x\n", rt);
1261
1262         switch (cdr.Ctrl & 3) {
1263         case 0:
1264                 break;
1265         case 3:
1266                 cdr.AttenuatorRightToRightT = rt;
1267                 return;
1268         default:
1269                 return;
1270         }
1271
1272         cdr.Cmd = rt;
1273         cdr.OCUP = 0;
1274
1275 #ifdef CDR_LOG_CMD_IRQ
1276         SysPrintf("CD1 write: %x (%s)", rt, CmdName[rt]);
1277         if (cdr.ParamC) {
1278                 SysPrintf(" Param[%d] = {", cdr.ParamC);
1279                 for (i = 0; i < cdr.ParamC; i++)
1280                         SysPrintf(" %x,", cdr.Param[i]);
1281                 SysPrintf("}\n");
1282         } else {
1283                 SysPrintf("\n");
1284         }
1285 #endif
1286
1287         cdr.ResultReady = 0;
1288         cdr.Ctrl |= 0x80;
1289         // cdr.Stat = NoIntr; 
1290         AddIrqQueue(cdr.Cmd, 0x800);
1291
1292         switch (cdr.Cmd) {
1293         case CdlSetloc:
1294                 CDR_LOG("CDROM setloc command (%02X, %02X, %02X)\n", cdr.Param[0], cdr.Param[1], cdr.Param[2]);
1295
1296                 // MM must be BCD, SS must be BCD and <0x60, FF must be BCD and <0x75
1297                 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))
1298                 {
1299                         CDR_LOG("Invalid/out of range seek to %02X:%02X:%02X\n", cdr.Param[0], cdr.Param[1], cdr.Param[2]);
1300                 }
1301                 else
1302                 {
1303                         for (i = 0; i < 3; i++)
1304                         {
1305                                 set_loc[i] = btoi(cdr.Param[i]);
1306                         }
1307
1308                         i = msf2sec(cdr.SetSectorPlay);
1309                         i = abs(i - msf2sec(set_loc));
1310                         if (i > 16)
1311                                 cdr.Seeked = SEEK_PENDING;
1312
1313                         memcpy(cdr.SetSector, set_loc, 3);
1314                         cdr.SetSector[3] = 0;
1315                         cdr.SetlocPending = 1;
1316                 }
1317                 break;
1318
1319         case CdlReadN:
1320         case CdlReadS:
1321         case CdlPause:
1322                 StopCdda();
1323                 StopReading();
1324                 break;
1325
1326         case CdlInit:
1327         case CdlReset:
1328                 cdr.Seeked = SEEK_DONE;
1329                 StopCdda();
1330                 StopReading();
1331                 break;
1332
1333         case CdlSetmode:
1334                 CDR_LOG("cdrWrite1() Log: Setmode %x\n", cdr.Param[0]);
1335
1336                 cdr.Mode = cdr.Param[0];
1337
1338                 // Squaresoft on PlayStation 1998 Collector's CD Vol. 1
1339                 // - fixes choppy movie sound
1340                 if( cdr.Play && (cdr.Mode & MODE_CDDA) == 0 )
1341                         StopCdda();
1342                 break;
1343         }
1344 }
1345
1346 unsigned char cdrRead2(void) {
1347         unsigned char ret;
1348
1349         if (cdr.Readed == 0) {
1350                 ret = 0;
1351         } else {
1352                 ret = *pTransfer++;
1353         }
1354
1355         CDR_LOG_IO("cdr r2: %02x\n", ret);
1356         return ret;
1357 }
1358
1359 void cdrWrite2(unsigned char rt) {
1360         CDR_LOG_IO("cdr w2: %02x\n", rt);
1361
1362         switch (cdr.Ctrl & 3) {
1363         case 0:
1364                 if (cdr.ParamC < 8) // FIXME: size and wrapping
1365                         cdr.Param[cdr.ParamC++] = rt;
1366                 return;
1367         case 1:
1368                 cdr.Reg2 = rt;
1369                 setIrq();
1370                 return;
1371         case 2:
1372                 cdr.AttenuatorLeftToLeftT = rt;
1373                 return;
1374         case 3:
1375                 cdr.AttenuatorRightToLeftT = rt;
1376                 return;
1377         }
1378 }
1379
1380 unsigned char cdrRead3(void) {
1381         if (cdr.Ctrl & 0x1)
1382                 psxHu8(0x1803) = cdr.Stat | 0xE0;
1383         else
1384                 psxHu8(0x1803) = cdr.Reg2 | 0xE0;
1385
1386         CDR_LOG_IO("cdr r3: %02x\n", psxHu8(0x1803));
1387         return psxHu8(0x1803);
1388 }
1389
1390 void cdrWrite3(unsigned char rt) {
1391         CDR_LOG_IO("cdr w3: %02x\n", rt);
1392
1393         switch (cdr.Ctrl & 3) {
1394         case 0:
1395                 break; // transfer
1396         case 1:
1397                 cdr.Stat &= ~rt;
1398
1399                 if (rt & 0x40)
1400                         cdr.ParamC = 0;
1401                 return;
1402         case 2:
1403                 cdr.AttenuatorLeftToRightT = rt;
1404                 return;
1405         case 3:
1406                 if (rt & 0x20) {
1407                         memcpy(&cdr.AttenuatorLeftToLeft, &cdr.AttenuatorLeftToLeftT, 4);
1408                         CDR_LOG_I("CD-XA Volume: %02x %02x | %02x %02x\n",
1409                                 cdr.AttenuatorLeftToLeft, cdr.AttenuatorLeftToRight,
1410                                 cdr.AttenuatorRightToLeft, cdr.AttenuatorRightToRight);
1411                 }
1412                 return;
1413         }
1414
1415         if ((rt & 0x80) && cdr.Readed == 0) {
1416                 cdr.Readed = 1;
1417                 pTransfer = cdr.Transfer;
1418
1419                 switch (cdr.Mode & 0x30) {
1420                         case MODE_SIZE_2328:
1421                         case 0x00:
1422                                 pTransfer += 12;
1423                                 break;
1424
1425                         case MODE_SIZE_2340:
1426                                 pTransfer += 0;
1427                                 break;
1428
1429                         default:
1430                                 break;
1431                 }
1432         }
1433 }
1434
1435 void psxDma3(u32 madr, u32 bcr, u32 chcr) {
1436         u32 cdsize;
1437         int size;
1438         u8 *ptr;
1439
1440         CDR_LOG("psxDma3() Log: *** DMA 3 *** %x addr = %x size = %x\n", chcr, madr, bcr);
1441
1442         switch (chcr) {
1443                 case 0x11000000:
1444                 case 0x11400100:
1445                         if (cdr.Readed == 0) {
1446                                 CDR_LOG("psxDma3() Log: *** DMA 3 *** NOT READY\n");
1447                                 break;
1448                         }
1449
1450                         cdsize = (bcr & 0xffff) * 4;
1451
1452                         // Ape Escape: bcr = 0001 / 0000
1453                         // - fix boot
1454                         if( cdsize == 0 )
1455                         {
1456                                 switch (cdr.Mode & (MODE_SIZE_2340|MODE_SIZE_2328)) {
1457                                         case MODE_SIZE_2340: cdsize = 2340; break;
1458                                         case MODE_SIZE_2328: cdsize = 2328; break;
1459                                         default:
1460                                         case MODE_SIZE_2048: cdsize = 2048; break;
1461                                 }
1462                         }
1463
1464
1465                         ptr = (u8 *)PSXM(madr);
1466                         if (ptr == NULL) {
1467                                 CDR_LOG("psxDma3() Log: *** DMA 3 *** NULL Pointer!\n");
1468                                 break;
1469                         }
1470
1471                         /*
1472                         GS CDX: Enhancement CD crash
1473                         - Setloc 0:0:0
1474                         - CdlPlay
1475                         - Spams DMA3 and gets buffer overrun
1476                         */
1477                         size = CD_FRAMESIZE_RAW - (pTransfer - cdr.Transfer);
1478                         if (size > cdsize)
1479                                 size = cdsize;
1480                         if (size > 0)
1481                         {
1482                                 memcpy(ptr, pTransfer, size);
1483                         }
1484
1485                         psxCpu->Clear(madr, cdsize / 4);
1486                         pTransfer += cdsize;
1487
1488                         if( chcr == 0x11400100 ) {
1489                                 HW_DMA3_MADR = SWAPu32(madr + cdsize);
1490                                 CDRDMA_INT( (cdsize/4) / 4 );
1491                         }
1492                         else if( chcr == 0x11000000 ) {
1493                                 // CDRDMA_INT( (cdsize/4) * 1 );
1494                                 // halted
1495                                 psxRegs.cycle += (cdsize/4) * 24/2;
1496                                 CDRDMA_INT(16);
1497                         }
1498                         return;
1499
1500                 default:
1501                         CDR_LOG("psxDma3() Log: Unknown cddma %x\n", chcr);
1502                         break;
1503         }
1504
1505         HW_DMA3_CHCR &= SWAP32(~0x01000000);
1506         DMA_INTERRUPT(3);
1507 }
1508
1509 void cdrDmaInterrupt()
1510 {
1511         if (HW_DMA3_CHCR & SWAP32(0x01000000))
1512         {
1513                 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1514                 DMA_INTERRUPT(3);
1515         }
1516 }
1517
1518 static void getCdInfo(void)
1519 {
1520         u8 tmp;
1521
1522         CDR_getTN(cdr.ResultTN);
1523         CDR_getTD(0, cdr.SetSectorEnd);
1524         tmp = cdr.SetSectorEnd[0];
1525         cdr.SetSectorEnd[0] = cdr.SetSectorEnd[2];
1526         cdr.SetSectorEnd[2] = tmp;
1527 }
1528
1529 void cdrReset() {
1530         memset(&cdr, 0, sizeof(cdr));
1531         cdr.CurTrack = 1;
1532         cdr.File = 1;
1533         cdr.Channel = 1;
1534         cdr.Reg2 = 0x1f;
1535         cdr.Stat = NoIntr;
1536         cdr.DriveState = DRIVESTATE_STANDBY;
1537         cdr.StatP = STATUS_ROTATING;
1538         pTransfer = cdr.Transfer;
1539         cdr.SetlocPending = 0;
1540         cdr.m_locationChanged = FALSE;
1541
1542         // BIOS player - default values
1543         cdr.AttenuatorLeftToLeft = 0x80;
1544         cdr.AttenuatorLeftToRight = 0x00;
1545         cdr.AttenuatorRightToLeft = 0x00;
1546         cdr.AttenuatorRightToRight = 0x80;
1547
1548         getCdInfo();
1549 }
1550
1551 int cdrFreeze(void *f, int Mode) {
1552         u32 tmp;
1553         u8 tmpp[3];
1554
1555         if (Mode == 0 && !Config.Cdda)
1556                 CDR_stop();
1557         
1558         cdr.freeze_ver = 0x63647202;
1559         gzfreeze(&cdr, sizeof(cdr));
1560         
1561         if (Mode == 1) {
1562                 cdr.ParamP = cdr.ParamC;
1563                 tmp = pTransfer - cdr.Transfer;
1564         }
1565
1566         gzfreeze(&tmp, sizeof(tmp));
1567
1568         if (Mode == 0) {
1569                 getCdInfo();
1570
1571                 pTransfer = cdr.Transfer + tmp;
1572
1573                 // read right sub data
1574                 tmpp[0] = btoi(cdr.Prev[0]);
1575                 tmpp[1] = btoi(cdr.Prev[1]);
1576                 tmpp[2] = btoi(cdr.Prev[2]);
1577                 cdr.Prev[0]++;
1578                 ReadTrack(tmpp);
1579
1580                 if (cdr.Play) {
1581                         if (cdr.freeze_ver < 0x63647202)
1582                                 memcpy(cdr.SetSectorPlay, cdr.SetSector, 3);
1583
1584                         Find_CurTrack(cdr.SetSectorPlay);
1585                         if (!Config.Cdda)
1586                                 CDR_play(cdr.SetSectorPlay);
1587                 }
1588
1589                 if ((cdr.freeze_ver & 0xffffff00) != 0x63647200) {
1590                         // old versions did not latch Reg2, have to fixup..
1591                         if (cdr.Reg2 == 0) {
1592                                 SysPrintf("cdrom: fixing up old savestate\n");
1593                                 cdr.Reg2 = 7;
1594                         }
1595                         // also did not save Attenuator..
1596                         if ((cdr.AttenuatorLeftToLeft | cdr.AttenuatorLeftToRight
1597                              | cdr.AttenuatorRightToLeft | cdr.AttenuatorRightToRight) == 0)
1598                         {
1599                                 cdr.AttenuatorLeftToLeft = cdr.AttenuatorRightToRight = 0x80;
1600                         }
1601                 }
1602         }
1603
1604         return 0;
1605 }
1606
1607 void LidInterrupt() {
1608         getCdInfo();
1609         StopCdda();
1610         cdrLidSeekInterrupt();
1611 }