|
|
1.1 root 1: /*
1.1.1.4 root 2: Hatari - psg.c
1.1 root 3:
1.1.1.4 root 4: This file is distributed under the GNU Public License, version 2 or at
5: your option any later version. Read the file gpl.txt for details.
1.1.1.3 root 6:
1.1.1.4 root 7: Programmable Sound Generator (YM-2149) - PSG
1.1.1.3 root 8:
1.1.1.4 root 9: Also used for the printer (centronics) port emulation (PSG Port B, Register 15)
1.1 root 10: */
1.1.1.7 root 11:
12:
13: /* 2007/04/14 [NP] First approximation to get cycle accurate accesses to ff8800/02 */
14: /* by cumulating wait state of 1 cycle and rounding the final */
15: /* result to 4. */
16: /* 2007/04/29 [NP] Functions PSG_Void_WriteByte and PSG_Void_ReadByte to handle */
17: /* accesses to $ff8801/03. These adresses have no effect, but they */
18: /* must give a 1 cycle wait state (e.g. move.l d0,ff8800). */
19: /* 2007/09/29 [NP] Replace printf by calls to HATARI_TRACE. */
20: /* 2007/10/23 [NP] In PSG_Void_WriteByte, add a wait state only if no wait state */
21: /* were added so far (hack, but gives good result). */
22: /* 2007/11/18 [NP] In PSG_DataRegister_WriteByte, set unused bit to 0, in case */
23: /* the data reg is read later (fix Mindbomb Demo / BBC). */
1.1.1.9 ! root 24: /* 2008/04/20 [NP] In PSG_DataRegister_WriteByte, set unused bit to 0 for register */
! 25: /* 6 too (noise period). */
! 26: /* 2008/07/27 [NP] Better separation between accesses to the YM hardware registers */
! 27: /* and the sound rendering routines. Use Sound_WriteReg() to pass */
! 28: /* all writes to the sound rendering functions. This allows to */
! 29: /* have sound.c independant of psg.c (to ease replacement of */
! 30: /* sound.c by another rendering method). */
! 31: /* 2008/08/11 [NP] Set drive leds. */
! 32: /* 2008/10/16 [NP] When writing to $ff8800, register select should not be masked */
! 33: /* with 0xf, it's a real 8 bits register where all bits are */
! 34: /* significant. This means only value <16 should be considered as */
! 35: /* valid register selection. When reg select is >= 16, all writes */
! 36: /* and reads in $ff8802 should be ignored. */
! 37: /* (fix European Demo Intro, which sets addr reg to 0x10 when */
! 38: /* sample playback is disabled). */
! 39:
1.1.1.7 root 40:
41:
42: /* Emulating wait states when accessing $ff8800/01/02/03 with different 'move' variants */
43: /* is a complex task. So far, adding 1 cycle wait state to each access and rounding the */
44: /* final number to 4 gives some good results, but this is certainly not the way it's */
45: /* working for real in the ST. */
46: /* The following examples show some verified wait states for different accesses : */
47: /* lea $ffff8800,a1 */
48: /* lea $ffff8802,a2 */
49: /* lea $ffff8801,a3 */
50: /* */
51: /* movep.w d1,(a1) ; 20 16+4 (ventura loader) */
52: /* movep.l d1,(a1) ; 28 24+4 (ventura loader, ulm loader) */
53: /* */
54: /* movep.l d6,0(a5) ; 28 24+4 (SNY I, TCB) */
55: /* movep.w d5,0(a5) ; 20 16+4 (SNY I, TCB) */
56: /* */
57: /* move.b d1,(a1) ; 12 8+4 */
58: /* move.b d1,(a2) ; 12 8+4 */
59: /* move.b d1,(a3) ; 12 8+4 (crickey ulm hidden) */
60: /* */
61: /* move.w d1,(a1) ; 12 8+4 */
62: /* move.w d1,(a2) ; 12 8+4 */
63: /* move.l d1,(a1) ; 16 12+4 (ulm loader) */
64: /* */
65: /* movem.l d1,(a1) ; 20 16+4 */
66: /* movem.l d1-d2,(a1) ; 28 24+4 */
67: /* movem.l d1-d3,(a1) ; 40 32+4+4 */
68: /* movem.l d1-d4,(a1) ; 48 40+4+4 */
69: /* movem.l d1-d5,(a1) ; 60 48+4+4+4 */
70: /* movem.l d1-d6,(a1) ; 68 56+4+4+4 */
71: /* movem.l d1-d7,(a1) ; 80 64+4+4+4+4 */
72: /* movem.l d0-d7,(a1) ; 88 72+4+4+4+4 */
73: /* */
74: /* This gives the following "model" : */
75: /* - each access to $ff8800 or $ff8802 add 1 cycle wait state */
76: /* - access to $ff8801 or $ff8803 give 0 wait state, except if this is the only */
77: /* register accessed (move.b for example). */
78:
79:
80:
1.1.1.9 ! root 81: const char PSG_rcsid[] = "Hatari $Id: psg.c,v 1.34 2008/11/03 20:46:05 thothy Exp $";
1.1 root 82:
83: #include "main.h"
1.1.1.3 root 84: #include "configuration.h"
1.1.1.4 root 85: #include "ioMem.h"
1.1.1.5 root 86: #include "joy.h"
1.1.1.7 root 87: #include "log.h"
1.1.1.4 root 88: #include "m68000.h"
1.1 root 89: #include "memorySnapShot.h"
90: #include "sound.h"
1.1.1.4 root 91: #include "printer.h" /* because Printer I/O goes through PSG Register 15 */
1.1.1.3 root 92: #include "psg.h"
1.1.1.7 root 93: #if ENABLE_DSP_EMU
94: #include "falcon/dsp.h"
95: #endif
96: #include "video.h"
1.1.1.9 ! root 97: #include "statusbar.h"
! 98: #include "mfp.h"
! 99:
1.1 root 100:
1.1.1.4 root 101: Uint8 PSGRegisterSelect; /* 0xff8800 (read/write) */
102: Uint8 PSGRegisters[16]; /* Register in PSG, see PSG_REG_xxxx */
103:
1.1.1.9 ! root 104: static unsigned int LastStrobe=0; /* Falling edge of Strobe used for printer */
1.1 root 105:
106:
107: /*-----------------------------------------------------------------------*/
1.1.1.7 root 108: /**
109: * Reset variables used in PSG
110: */
1.1 root 111: void PSG_Reset(void)
112: {
1.1.1.4 root 113: PSGRegisterSelect = 0;
114: memset(PSGRegisters, 0, sizeof(PSGRegisters));
1.1.1.9 ! root 115: LastStrobe=0;
1.1 root 116: }
117:
1.1.1.2 root 118:
119: /*-----------------------------------------------------------------------*/
1.1.1.7 root 120: /**
121: * Save/Restore snapshot of local variables ('MemorySnapShot_Store' handles type)
122: */
1.1.1.9 ! root 123: void PSG_MemorySnapShot_Capture(bool bSave)
1.1 root 124: {
1.1.1.4 root 125: /* Save/Restore details */
126: MemorySnapShot_Store(&PSGRegisterSelect, sizeof(PSGRegisterSelect));
127: MemorySnapShot_Store(PSGRegisters, sizeof(PSGRegisters));
1.1.1.9 ! root 128: MemorySnapShot_Store(&LastStrobe, sizeof(LastStrobe));
1.1 root 129: }
130:
1.1.1.2 root 131:
132: /*-----------------------------------------------------------------------*/
1.1.1.7 root 133: /**
1.1.1.8 root 134: * Write byte to 0xff8800, this is used as a selector for when we read/write
1.1.1.7 root 135: * to address 0xff8802
136: */
1.1.1.4 root 137: void PSG_SelectRegister_WriteByte(void)
1.1 root 138: {
1.1.1.7 root 139: // M68000_WaitState(4);
140: M68000_WaitState(1); /* [NP] FIXME not 100% accurate, but gives good results */
1.1.1.4 root 141:
1.1.1.9 ! root 142: /* Store register used to read/write in $ff8802. This register */
! 143: /* is 8 bits on the YM2149, this means it should not be masked */
! 144: /* with 0xf. Instead, we keep the 8 bits, but we must ignore */
! 145: /* read/write to ff8802 when PSGRegisterSelect >= 16 */
! 146: /* Use IoAccessCurrentAddress to be able to handle the PSG mirror registers, too. */
! 147: PSGRegisterSelect = IoMem[IoAccessCurrentAddress];
1.1.1.7 root 148:
149: if ( HATARI_TRACE_LEVEL ( HATARI_TRACE_PSG_WRITE_REG ) )
150: {
151: int nFrameCycles = Cycles_GetCounter(CYCLES_COUNTER_VIDEO);;
152: int nLineCycles = nFrameCycles % nCyclesPerLine;
1.1.1.9 ! root 153: HATARI_TRACE_PRINT ( "write ym sel reg=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n" ,
1.1.1.7 root 154: PSGRegisterSelect, nFrameCycles, nLineCycles, nHBL, M68000_GetPC(), CurrentInstrCycles );
155: }
1.1 root 156: }
157:
1.1.1.2 root 158:
159: /*-----------------------------------------------------------------------*/
1.1.1.7 root 160: /**
161: * Read byte from 0xff8800, returns PSG data
162: */
1.1.1.4 root 163: void PSG_SelectRegister_ReadByte(void)
1.1 root 164: {
1.1.1.6 root 165: M68000_WaitState(4);
166:
1.1.1.9 ! root 167: /* Is a valid PSG register currently selected ? */
! 168: if ( PSGRegisterSelect >= 16 )
! 169: return; /* not valid, ignore read and do nothing */
! 170:
1.1.1.5 root 171: if (PSGRegisterSelect == 14)
172: {
173: /* Second parallel port joystick uses centronics strobe bit as fire button: */
174: if (ConfigureParams.Joysticks.Joy[JOYID_PARPORT2].nJoystickMode != JOYSTICK_DISABLED)
175: {
176: if (Joy_GetStickData(JOYID_PARPORT2) & 0x80)
177: PSGRegisters[14] &= ~32;
178: else
179: PSGRegisters[14] |= 32;
180: }
181: }
182: else if (PSGRegisterSelect == 15)
183: {
184: /* PSG register 15 is parallel port data register - used by parallel port joysticks: */
185: if (ConfigureParams.Joysticks.Joy[JOYID_PARPORT1].nJoystickMode != JOYSTICK_DISABLED)
186: {
187: PSGRegisters[15] &= 0x0f;
188: PSGRegisters[15] |= ~Joy_GetStickData(JOYID_PARPORT1) << 4;
189: }
190: if (ConfigureParams.Joysticks.Joy[JOYID_PARPORT2].nJoystickMode != JOYSTICK_DISABLED)
191: {
192: PSGRegisters[15] &= 0xf0;
193: PSGRegisters[15] |= ~Joy_GetStickData(JOYID_PARPORT2) & 0x0f;
194: }
195: }
196:
1.1.1.4 root 197: /* Read data last selected by register */
1.1.1.8 root 198: IoMem[IoAccessCurrentAddress] = PSGRegisters[PSGRegisterSelect];
1.1 root 199: }
200:
1.1.1.2 root 201:
202: /*-----------------------------------------------------------------------*/
1.1.1.7 root 203: /**
204: * Write byte to 0xff8802, stores according to PSG select register (write 0xff8800)
205: */
1.1.1.4 root 206: void PSG_DataRegister_WriteByte(void)
207: {
1.1.1.7 root 208: // M68000_WaitState(4);
209: M68000_WaitState(1); /* [NP] FIXME not 100% accurate, but gives good results */
1.1.1.6 root 210:
1.1.1.9 ! root 211: if ( HATARI_TRACE_LEVEL ( HATARI_TRACE_PSG_WRITE_DATA ) )
! 212: {
! 213: int nFrameCycles = Cycles_GetCounter(CYCLES_COUNTER_VIDEO);;
! 214: int nLineCycles = nFrameCycles % nCyclesPerLine;
! 215: HATARI_TRACE_PRINT ( "write ym data reg=0x%x val=0x%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n" ,
! 216: PSGRegisterSelect, IoMem[IoAccessCurrentAddress], nFrameCycles, nLineCycles, nHBL, M68000_GetPC(), CurrentInstrCycles );
! 217: }
! 218:
! 219: /* Is a valid PSG register currently selected ? */
! 220: if ( PSGRegisterSelect >= 16 )
! 221: return; /* not valid, ignore write and do nothing */
! 222:
1.1.1.8 root 223: /* Create samples up until this point with current values */
224: Sound_Update();
225:
226: /* Copy value to PSGRegisters[]. Use IoAccessCurrentAddress to be able
227: * to handle the PSG mirror registers, too. */
228: PSGRegisters[PSGRegisterSelect] = IoMem[IoAccessCurrentAddress];
1.1.1.4 root 229:
1.1.1.9 ! root 230: /* Clear unused bits for some regs */
1.1.1.7 root 231: if ( ( PSGRegisterSelect == PSG_REG_CHANNEL_A_COARSE ) || ( PSGRegisterSelect == PSG_REG_CHANNEL_B_COARSE )
232: || ( PSGRegisterSelect == PSG_REG_CHANNEL_C_COARSE ) || ( PSGRegisterSelect == PSG_REG_ENV_SHAPE ) )
233: PSGRegisters[PSGRegisterSelect] &= 0x0f; /* only keep bits 0 - 3 */
234:
235: else if ( ( PSGRegisterSelect == PSG_REG_CHANNEL_A_AMP ) || ( PSGRegisterSelect == PSG_REG_CHANNEL_B_AMP )
1.1.1.9 ! root 236: || ( PSGRegisterSelect == PSG_REG_CHANNEL_C_AMP ) || ( PSGRegisterSelect == PSG_REG_NOISE_GENERATOR ) )
1.1.1.7 root 237: PSGRegisters[PSGRegisterSelect] &= 0x1f; /* only keep bits 0 - 4 */
238:
239:
240:
1.1.1.9 ! root 241: if ( PSGRegisterSelect < NUM_PSG_SOUND_REGISTERS )
1.1.1.4 root 242: {
1.1.1.9 ! root 243: /* Copy sound related registers 0..13 to the sound module's internal buffer */
! 244: Sound_WriteReg ( PSGRegisterSelect , PSGRegisters[PSGRegisterSelect] );
! 245: }
1.1.1.4 root 246:
1.1.1.9 ! root 247: else if ( PSGRegisterSelect == PSG_REG_IO_PORTA )
! 248: {
! 249: /*
! 250: * FIXME: This is only a prelimary dirty hack!
! 251: * Port B (Printer port) - writing here needs to be dispatched to the printer
! 252: * STROBE (Port A bit5) does a short LOW and back to HIGH when the char is valid
! 253: * To print you need to write the character byte to IOB and you need to toggle STROBE
! 254: * (like EmuTOS does).
! 255: */
1.1.1.4 root 256: /* Printer dispatching only when printing is activated */
257: if (ConfigureParams.Printer.bEnablePrinting)
258: {
1.1.1.9 ! root 259: /* Bit 5 - Centronics strobe? If STROBE is low and the LastStrobe was high,
! 260: then print/transfer to the emulated Centronics port.
1.1.1.7 root 261: */
1.1.1.9 ! root 262: if (LastStrobe && ( (PSGRegisters[PSG_REG_IO_PORTA]&(1<<5)) == 0 ))
1.1.1.4 root 263: {
264: /* Seems like we want to print something... */
1.1.1.9 ! root 265: Printer_TransferByteTo(PSGRegisters[PSG_REG_IO_PORTB]);
! 266: /* Initiate a possible GPIP0 Printer BUSY interrupt */
! 267: MFP_InputOnChannel(MFP_GPIP_0_BIT,MFP_IERB,&MFP_IPRB);
! 268: /* Initiate a possible GPIP1 Falcon ACK interrupt */
! 269: if (ConfigureParams.System.nMachineType == MACHINE_FALCON)
! 270: MFP_InputOnChannel(MFP_GPIP_1_BIT,MFP_IERB,&MFP_IPRB);
1.1.1.4 root 271: }
272: }
1.1.1.9 ! root 273: LastStrobe = PSGRegisters[PSG_REG_IO_PORTA]&(1<<5);
! 274:
! 275: /* Bit 0-2 : side and drive select */
! 276: if ( (PSGRegisters[PSG_REG_IO_PORTA]&(1<<1)) == 0 )
! 277: {
! 278: /* floppy drive A is ON */
! 279: Statusbar_SetFloppyLed(DRIVE_LED_A, TRUE);
! 280: }
! 281: else
! 282: {
! 283: Statusbar_SetFloppyLed(DRIVE_LED_A, FALSE);
! 284: }
! 285: if ( (PSGRegisters[PSG_REG_IO_PORTA]&(1<<2)) == 0 )
! 286: {
! 287: /* floppy drive B is ON */
! 288: Statusbar_SetFloppyLed(DRIVE_LED_B, TRUE);
! 289: }
! 290: else
! 291: {
! 292: Statusbar_SetFloppyLed(DRIVE_LED_B, FALSE);
! 293: }
! 294:
1.1.1.7 root 295: /* Bit 3 - Centronics as input */
296: if(PSGRegisters[PSG_REG_IO_PORTA]&(1<<3))
297: {
298: /* FIXME: might be needed if we want to emulate sound sampling hardware */
299: }
300:
301: /* handle Falcon specific bits in PORTA of the PSG */
302: if (ConfigureParams.System.nMachineType == MACHINE_FALCON)
303: {
304: /* Bit 4 - DSP reset? */
305: if(PSGRegisters[PSG_REG_IO_PORTA]&(1<<4))
306: {
307: Log_Printf(LOG_DEBUG, "Calling DSP_Reset?\n");
308: #if ENABLE_DSP_EMU
309: if (ConfigureParams.System.nDSPType == DSP_TYPE_EMU) {
310: DSP_Reset();
311: }
312: #endif
313: }
314: /* Bit 6 - Internal Speaker control */
315: if(PSGRegisters[PSG_REG_IO_PORTA]&(1<<6))
316: {
317: /*Log_Printf(LOG_DEBUG, "Falcon: Internal Speaker state\n");*/
318: /* FIXME: add code to handle? (if we want to emulate the speaker at all? */
319: }
320: /* Bit 7 - Reset IDE? */
321: if(PSGRegisters[PSG_REG_IO_PORTA]&(1<<7))
322: {
323: Log_Printf(LOG_DEBUG, "Falcon: Reset IDE subsystem\n");
324: /* FIXME: add code to handle IDE reset */
325: }
326: }
1.1.1.9 ! root 327:
1.1.1.4 root 328: }
1.1 root 329: }
330:
1.1.1.2 root 331:
332: /*-----------------------------------------------------------------------*/
1.1.1.7 root 333: /**
334: * Read byte from 0xff8802, returns 0xff
335: */
1.1.1.4 root 336: void PSG_DataRegister_ReadByte(void)
1.1 root 337: {
1.1.1.6 root 338: M68000_WaitState(4);
1.1.1.4 root 339:
1.1.1.8 root 340: IoMem[IoAccessCurrentAddress] = 0xff;
1.1 root 341: }
1.1.1.7 root 342:
343:
344:
345: /*-----------------------------------------------------------------------*/
346: /**
347: * Write byte to 0xff8801/03. Do nothing, but add some wait states if needed.
348: */
349: void PSG_Void_WriteByte(void)
350: {
351: /* [NP] FIXME If no wait states were added so far, it's possible we're accessing */
352: /* 8801/8803 through a .B instruction, so we need to add a wait state */
353: /* Else, the wait states will be added when writing to 8800/8802 */
354: /* This works so far, but this model is certainly not 100% accurate */
355: if ( nWaitStateCycles == 0 )
356: M68000_WaitState(1);
357:
358: if ( HATARI_TRACE_LEVEL ( HATARI_TRACE_PSG_WRITE_DATA ) )
359: {
360: int nFrameCycles = Cycles_GetCounter(CYCLES_COUNTER_VIDEO);;
361: int nLineCycles = nFrameCycles % nCyclesPerLine;
362: HATARI_TRACE_PRINT ( "write ym 8801/03 video_cyc=%d %d@%d pc=%x instr_cycle %d\n" ,
363: nFrameCycles, nLineCycles, nHBL, M68000_GetPC(), CurrentInstrCycles );
364: }
365: }
366:
367:
368:
369: /*-----------------------------------------------------------------------*/
370: /**
371: * Read byte from 0xff8801/03. Do nothing, but add some wait states.
372: */
373: void PSG_Void_ReadByte(void)
374: {
375: M68000_WaitState(1);
376: }
377:
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.