|
|
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). */
! 24:
! 25:
! 26: /* Emulating wait states when accessing $ff8800/01/02/03 with different 'move' variants */
! 27: /* is a complex task. So far, adding 1 cycle wait state to each access and rounding the */
! 28: /* final number to 4 gives some good results, but this is certainly not the way it's */
! 29: /* working for real in the ST. */
! 30: /* The following examples show some verified wait states for different accesses : */
! 31: /* lea $ffff8800,a1 */
! 32: /* lea $ffff8802,a2 */
! 33: /* lea $ffff8801,a3 */
! 34: /* */
! 35: /* movep.w d1,(a1) ; 20 16+4 (ventura loader) */
! 36: /* movep.l d1,(a1) ; 28 24+4 (ventura loader, ulm loader) */
! 37: /* */
! 38: /* movep.l d6,0(a5) ; 28 24+4 (SNY I, TCB) */
! 39: /* movep.w d5,0(a5) ; 20 16+4 (SNY I, TCB) */
! 40: /* */
! 41: /* move.b d1,(a1) ; 12 8+4 */
! 42: /* move.b d1,(a2) ; 12 8+4 */
! 43: /* move.b d1,(a3) ; 12 8+4 (crickey ulm hidden) */
! 44: /* */
! 45: /* move.w d1,(a1) ; 12 8+4 */
! 46: /* move.w d1,(a2) ; 12 8+4 */
! 47: /* move.l d1,(a1) ; 16 12+4 (ulm loader) */
! 48: /* */
! 49: /* movem.l d1,(a1) ; 20 16+4 */
! 50: /* movem.l d1-d2,(a1) ; 28 24+4 */
! 51: /* movem.l d1-d3,(a1) ; 40 32+4+4 */
! 52: /* movem.l d1-d4,(a1) ; 48 40+4+4 */
! 53: /* movem.l d1-d5,(a1) ; 60 48+4+4+4 */
! 54: /* movem.l d1-d6,(a1) ; 68 56+4+4+4 */
! 55: /* movem.l d1-d7,(a1) ; 80 64+4+4+4+4 */
! 56: /* movem.l d0-d7,(a1) ; 88 72+4+4+4+4 */
! 57: /* */
! 58: /* This gives the following "model" : */
! 59: /* - each access to $ff8800 or $ff8802 add 1 cycle wait state */
! 60: /* - access to $ff8801 or $ff8803 give 0 wait state, except if this is the only */
! 61: /* register accessed (move.b for example). */
! 62:
! 63:
! 64:
! 65: const char PSG_rcsid[] = "Hatari $Id: psg.c,v 1.18 2008/01/24 21:21:54 thothy Exp $";
1.1 root 66:
67: #include "main.h"
1.1.1.3 root 68: #include "configuration.h"
1.1.1.4 root 69: #include "ioMem.h"
1.1.1.5 root 70: #include "joy.h"
1.1.1.7 ! root 71: #include "log.h"
1.1.1.4 root 72: #include "m68000.h"
1.1 root 73: #include "memorySnapShot.h"
74: #include "sound.h"
1.1.1.4 root 75: #include "printer.h" /* because Printer I/O goes through PSG Register 15 */
1.1.1.3 root 76: #include "psg.h"
1.1.1.7 ! root 77: #if ENABLE_DSP_EMU
! 78: #include "falcon/dsp.h"
! 79: #endif
! 80: #include "video.h"
! 81: #include "trace.h"
1.1 root 82:
1.1.1.4 root 83: Uint8 PSGRegisterSelect; /* 0xff8800 (read/write) */
84: Uint8 PSGRegisters[16]; /* Register in PSG, see PSG_REG_xxxx */
85:
86: static BOOL bLastWriteToIOB; /* boolean flag: did the last write to the PSG go to IOB? */
1.1 root 87:
88:
89: /*-----------------------------------------------------------------------*/
1.1.1.7 ! root 90: /**
! 91: * Reset variables used in PSG
! 92: */
1.1 root 93: void PSG_Reset(void)
94: {
1.1.1.4 root 95: PSGRegisterSelect = 0;
96: memset(PSGRegisters, 0, sizeof(PSGRegisters));
97: bLastWriteToIOB = FALSE;
1.1 root 98: }
99:
1.1.1.2 root 100:
101: /*-----------------------------------------------------------------------*/
1.1.1.7 ! root 102: /**
! 103: * Save/Restore snapshot of local variables ('MemorySnapShot_Store' handles type)
! 104: */
1.1 root 105: void PSG_MemorySnapShot_Capture(BOOL bSave)
106: {
1.1.1.4 root 107: /* Save/Restore details */
108: MemorySnapShot_Store(&PSGRegisterSelect, sizeof(PSGRegisterSelect));
109: MemorySnapShot_Store(PSGRegisters, sizeof(PSGRegisters));
110: MemorySnapShot_Store(&bLastWriteToIOB, sizeof(bLastWriteToIOB));
1.1 root 111: }
112:
1.1.1.2 root 113:
114: /*-----------------------------------------------------------------------*/
1.1.1.7 ! root 115: /**
! 116: * Write byte to 0xff88000, this is used as a selector for when we read/write
! 117: * to address 0xff8802
! 118: */
1.1.1.4 root 119: void PSG_SelectRegister_WriteByte(void)
1.1 root 120: {
1.1.1.7 ! root 121: // M68000_WaitState(4);
! 122: M68000_WaitState(1); /* [NP] FIXME not 100% accurate, but gives good results */
1.1.1.4 root 123:
1.1.1.6 root 124: PSGRegisterSelect = IoMem[0xff8800] & 0x0f; /* Store register to select (value in bits 0-3) */
1.1.1.7 ! root 125:
! 126: if ( HATARI_TRACE_LEVEL ( HATARI_TRACE_PSG_WRITE_REG ) )
! 127: {
! 128: int nFrameCycles = Cycles_GetCounter(CYCLES_COUNTER_VIDEO);;
! 129: int nLineCycles = nFrameCycles % nCyclesPerLine;
! 130: HATARI_TRACE_PRINT ( "write ym sel reg=%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n" ,
! 131: PSGRegisterSelect, nFrameCycles, nLineCycles, nHBL, M68000_GetPC(), CurrentInstrCycles );
! 132: }
1.1 root 133: }
134:
1.1.1.2 root 135:
136: /*-----------------------------------------------------------------------*/
1.1.1.7 ! root 137: /**
! 138: * Read byte from 0xff8800, returns PSG data
! 139: */
1.1.1.4 root 140: void PSG_SelectRegister_ReadByte(void)
1.1 root 141: {
1.1.1.6 root 142: M68000_WaitState(4);
143:
1.1.1.5 root 144: if (PSGRegisterSelect == 14)
145: {
146: /* Second parallel port joystick uses centronics strobe bit as fire button: */
147: if (ConfigureParams.Joysticks.Joy[JOYID_PARPORT2].nJoystickMode != JOYSTICK_DISABLED)
148: {
149: if (Joy_GetStickData(JOYID_PARPORT2) & 0x80)
150: PSGRegisters[14] &= ~32;
151: else
152: PSGRegisters[14] |= 32;
153: }
154: }
155: else if (PSGRegisterSelect == 15)
156: {
157: /* PSG register 15 is parallel port data register - used by parallel port joysticks: */
158: if (ConfigureParams.Joysticks.Joy[JOYID_PARPORT1].nJoystickMode != JOYSTICK_DISABLED)
159: {
160: PSGRegisters[15] &= 0x0f;
161: PSGRegisters[15] |= ~Joy_GetStickData(JOYID_PARPORT1) << 4;
162: }
163: if (ConfigureParams.Joysticks.Joy[JOYID_PARPORT2].nJoystickMode != JOYSTICK_DISABLED)
164: {
165: PSGRegisters[15] &= 0xf0;
166: PSGRegisters[15] |= ~Joy_GetStickData(JOYID_PARPORT2) & 0x0f;
167: }
168: }
169:
1.1.1.4 root 170: /* Read data last selected by register */
171: IoMem[0xff8800] = PSGRegisters[PSGRegisterSelect];
1.1 root 172: }
173:
1.1.1.2 root 174:
175: /*-----------------------------------------------------------------------*/
1.1.1.7 ! root 176: /**
! 177: * Write byte to 0xff8802, stores according to PSG select register (write 0xff8800)
! 178: */
1.1.1.4 root 179: void PSG_DataRegister_WriteByte(void)
180: {
1.1.1.7 ! root 181: // M68000_WaitState(4);
! 182: M68000_WaitState(1); /* [NP] FIXME not 100% accurate, but gives good results */
1.1.1.6 root 183:
1.1.1.7 ! root 184: Sound_Update(); /* Create samples up until this point with current values */
1.1.1.4 root 185: PSGRegisters[PSGRegisterSelect] = IoMem[0xff8802]; /* Write value to PSGRegisters[] */
186:
1.1.1.7 ! root 187: /* [NP] Clear unused bits for some regs */
! 188: if ( ( PSGRegisterSelect == PSG_REG_CHANNEL_A_COARSE ) || ( PSGRegisterSelect == PSG_REG_CHANNEL_B_COARSE )
! 189: || ( PSGRegisterSelect == PSG_REG_CHANNEL_C_COARSE ) || ( PSGRegisterSelect == PSG_REG_ENV_SHAPE ) )
! 190: PSGRegisters[PSGRegisterSelect] &= 0x0f; /* only keep bits 0 - 3 */
! 191:
! 192: else if ( ( PSGRegisterSelect == PSG_REG_CHANNEL_A_AMP ) || ( PSGRegisterSelect == PSG_REG_CHANNEL_B_AMP )
! 193: || ( PSGRegisterSelect == PSG_REG_CHANNEL_C_AMP ) )
! 194: PSGRegisters[PSGRegisterSelect] &= 0x1f; /* only keep bits 0 - 4 */
! 195:
! 196:
! 197: if ( HATARI_TRACE_LEVEL ( HATARI_TRACE_PSG_WRITE_DATA ) )
! 198: {
! 199: int nFrameCycles = Cycles_GetCounter(CYCLES_COUNTER_VIDEO);;
! 200: int nLineCycles = nFrameCycles % nCyclesPerLine;
! 201: HATARI_TRACE_PRINT ( "write ym data reg=%x val=%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n" ,
! 202: PSGRegisterSelect, IoMem[0xff8802], nFrameCycles, nLineCycles, nHBL, M68000_GetPC(), CurrentInstrCycles );
! 203: }
! 204:
! 205:
1.1.1.4 root 206: switch (PSGRegisterSelect)
207: {
208:
209: /* Check registers 8,9 and 10 which are 'amplitude' for each channel and
210: * store if wrote to (to check for sample playback) */
211: case PSG_REG_CHANNEL_A_AMP:
212: bWriteChannelAAmp = TRUE;
213: break;
214: case PSG_REG_CHANNEL_B_AMP:
215: bWriteChannelBAmp = TRUE;
216: break;
217: case PSG_REG_CHANNEL_C_AMP:
218: bWriteChannelCAmp = TRUE;
219: break;
220:
221: case PSG_REG_ENV_SHAPE: /* Whenever 'write' to register 13, cause envelope to reset */
222: bEnvelopeFreqFlag = TRUE;
223: bWriteEnvelopeFreq = TRUE;
224: break;
225:
226: /*
227: * FIXME: This is only a prelimary dirty hack!
228: * Port B (Printer port) - writing here needs to be dispatched to the printer
229: * STROBE (Port A bit5) does a short LOW and back to HIGH when the char is valid
230: * To print you need to write the character byte to IOB and you need to toggle STROBE
231: * (like EmuTOS does)....therefor we print when STROBE gets low and last write access to
232: * the PSG was to IOB
233: */
234: case PSG_REG_IO_PORTA:
235: /* Printer dispatching only when printing is activated */
236: if (ConfigureParams.Printer.bEnablePrinting)
237: {
1.1.1.7 ! root 238: /* Bit 5 - Centronics strobe? If STROBE is low and the last write did go to IOB then
! 239: * there is data in PORTB to print/transfer to the emulated Centronics port
! 240: */
! 241: if ((((PSGRegisters[PSG_REG_IO_PORTA]&(1<<5))==0) && bLastWriteToIOB))
1.1.1.4 root 242: {
243: /* Seems like we want to print something... */
244: Printer_TransferByteTo((unsigned char) PSGRegisters[PSG_REG_IO_PORTB]);
245: }
246: }
1.1.1.7 ! root 247: /* Bit 3 - Centronics as input */
! 248: if(PSGRegisters[PSG_REG_IO_PORTA]&(1<<3))
! 249: {
! 250: /* FIXME: might be needed if we want to emulate sound sampling hardware */
! 251: }
! 252:
! 253: /* handle Falcon specific bits in PORTA of the PSG */
! 254: if (ConfigureParams.System.nMachineType == MACHINE_FALCON)
! 255: {
! 256: /* Bit 4 - DSP reset? */
! 257: if(PSGRegisters[PSG_REG_IO_PORTA]&(1<<4))
! 258: {
! 259: Log_Printf(LOG_DEBUG, "Calling DSP_Reset?\n");
! 260: #if ENABLE_DSP_EMU
! 261: if (ConfigureParams.System.nDSPType == DSP_TYPE_EMU) {
! 262: DSP_Reset();
! 263: }
! 264: #endif
! 265: }
! 266: /* Bit 6 - Internal Speaker control */
! 267: if(PSGRegisters[PSG_REG_IO_PORTA]&(1<<6))
! 268: {
! 269: /*Log_Printf(LOG_DEBUG, "Falcon: Internal Speaker state\n");*/
! 270: /* FIXME: add code to handle? (if we want to emulate the speaker at all? */
! 271: }
! 272: /* Bit 7 - Reset IDE? */
! 273: if(PSGRegisters[PSG_REG_IO_PORTA]&(1<<7))
! 274: {
! 275: Log_Printf(LOG_DEBUG, "Falcon: Reset IDE subsystem\n");
! 276: /* FIXME: add code to handle IDE reset */
! 277: }
! 278: }
1.1.1.4 root 279: break;
280: }
281:
282: /* Remember if we wrote to IO Port B */
283: bLastWriteToIOB = (PSGRegisterSelect == PSG_REG_IO_PORTB);
1.1 root 284: }
285:
1.1.1.2 root 286:
287: /*-----------------------------------------------------------------------*/
1.1.1.7 ! root 288: /**
! 289: * Read byte from 0xff8802, returns 0xff
! 290: */
1.1.1.4 root 291: void PSG_DataRegister_ReadByte(void)
1.1 root 292: {
1.1.1.6 root 293: M68000_WaitState(4);
1.1.1.4 root 294:
1.1.1.6 root 295: IoMem[0xff8802] = 0xff;
1.1 root 296: }
1.1.1.7 ! root 297:
! 298:
! 299:
! 300: /*-----------------------------------------------------------------------*/
! 301: /**
! 302: * Write byte to 0xff8801/03. Do nothing, but add some wait states if needed.
! 303: */
! 304: void PSG_Void_WriteByte(void)
! 305: {
! 306: /* [NP] FIXME If no wait states were added so far, it's possible we're accessing */
! 307: /* 8801/8803 through a .B instruction, so we need to add a wait state */
! 308: /* Else, the wait states will be added when writing to 8800/8802 */
! 309: /* This works so far, but this model is certainly not 100% accurate */
! 310: if ( nWaitStateCycles == 0 )
! 311: M68000_WaitState(1);
! 312:
! 313: if ( HATARI_TRACE_LEVEL ( HATARI_TRACE_PSG_WRITE_DATA ) )
! 314: {
! 315: int nFrameCycles = Cycles_GetCounter(CYCLES_COUNTER_VIDEO);;
! 316: int nLineCycles = nFrameCycles % nCyclesPerLine;
! 317: HATARI_TRACE_PRINT ( "write ym 8801/03 video_cyc=%d %d@%d pc=%x instr_cycle %d\n" ,
! 318: nFrameCycles, nLineCycles, nHBL, M68000_GetPC(), CurrentInstrCycles );
! 319: }
! 320: }
! 321:
! 322:
! 323:
! 324: /*-----------------------------------------------------------------------*/
! 325: /**
! 326: * Read byte from 0xff8801/03. Do nothing, but add some wait states.
! 327: */
! 328: void PSG_Void_ReadByte(void)
! 329: {
! 330: M68000_WaitState(1);
! 331: }
! 332:
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.