|
|
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:
1.1.1.8 ! root 65: const char PSG_rcsid[] = "Hatari $Id: psg.c,v 1.19 2008/03/26 22:15:27 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: /**
1.1.1.8 ! root 116: * Write byte to 0xff8800, this is used as a selector for when we read/write
1.1.1.7 root 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.8 ! root 124: /* Store register to select (value in bits 0-3). Use IoAccessCurrentAddress
! 125: * to be able to handle the PSG mirror registers, too. */
! 126: PSGRegisterSelect = IoMem[IoAccessCurrentAddress] & 0x0f;
1.1.1.7 root 127:
128: if ( HATARI_TRACE_LEVEL ( HATARI_TRACE_PSG_WRITE_REG ) )
129: {
130: int nFrameCycles = Cycles_GetCounter(CYCLES_COUNTER_VIDEO);;
131: int nLineCycles = nFrameCycles % nCyclesPerLine;
132: HATARI_TRACE_PRINT ( "write ym sel reg=%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n" ,
133: PSGRegisterSelect, nFrameCycles, nLineCycles, nHBL, M68000_GetPC(), CurrentInstrCycles );
134: }
1.1 root 135: }
136:
1.1.1.2 root 137:
138: /*-----------------------------------------------------------------------*/
1.1.1.7 root 139: /**
140: * Read byte from 0xff8800, returns PSG data
141: */
1.1.1.4 root 142: void PSG_SelectRegister_ReadByte(void)
1.1 root 143: {
1.1.1.6 root 144: M68000_WaitState(4);
145:
1.1.1.5 root 146: if (PSGRegisterSelect == 14)
147: {
148: /* Second parallel port joystick uses centronics strobe bit as fire button: */
149: if (ConfigureParams.Joysticks.Joy[JOYID_PARPORT2].nJoystickMode != JOYSTICK_DISABLED)
150: {
151: if (Joy_GetStickData(JOYID_PARPORT2) & 0x80)
152: PSGRegisters[14] &= ~32;
153: else
154: PSGRegisters[14] |= 32;
155: }
156: }
157: else if (PSGRegisterSelect == 15)
158: {
159: /* PSG register 15 is parallel port data register - used by parallel port joysticks: */
160: if (ConfigureParams.Joysticks.Joy[JOYID_PARPORT1].nJoystickMode != JOYSTICK_DISABLED)
161: {
162: PSGRegisters[15] &= 0x0f;
163: PSGRegisters[15] |= ~Joy_GetStickData(JOYID_PARPORT1) << 4;
164: }
165: if (ConfigureParams.Joysticks.Joy[JOYID_PARPORT2].nJoystickMode != JOYSTICK_DISABLED)
166: {
167: PSGRegisters[15] &= 0xf0;
168: PSGRegisters[15] |= ~Joy_GetStickData(JOYID_PARPORT2) & 0x0f;
169: }
170: }
171:
1.1.1.4 root 172: /* Read data last selected by register */
1.1.1.8 ! root 173: IoMem[IoAccessCurrentAddress] = PSGRegisters[PSGRegisterSelect];
1.1 root 174: }
175:
1.1.1.2 root 176:
177: /*-----------------------------------------------------------------------*/
1.1.1.7 root 178: /**
179: * Write byte to 0xff8802, stores according to PSG select register (write 0xff8800)
180: */
1.1.1.4 root 181: void PSG_DataRegister_WriteByte(void)
182: {
1.1.1.7 root 183: // M68000_WaitState(4);
184: M68000_WaitState(1); /* [NP] FIXME not 100% accurate, but gives good results */
1.1.1.6 root 185:
1.1.1.8 ! root 186: /* Create samples up until this point with current values */
! 187: Sound_Update();
! 188:
! 189: /* Copy value to PSGRegisters[]. Use IoAccessCurrentAddress to be able
! 190: * to handle the PSG mirror registers, too. */
! 191: PSGRegisters[PSGRegisterSelect] = IoMem[IoAccessCurrentAddress];
1.1.1.4 root 192:
1.1.1.7 root 193: /* [NP] Clear unused bits for some regs */
194: if ( ( PSGRegisterSelect == PSG_REG_CHANNEL_A_COARSE ) || ( PSGRegisterSelect == PSG_REG_CHANNEL_B_COARSE )
195: || ( PSGRegisterSelect == PSG_REG_CHANNEL_C_COARSE ) || ( PSGRegisterSelect == PSG_REG_ENV_SHAPE ) )
196: PSGRegisters[PSGRegisterSelect] &= 0x0f; /* only keep bits 0 - 3 */
197:
198: else if ( ( PSGRegisterSelect == PSG_REG_CHANNEL_A_AMP ) || ( PSGRegisterSelect == PSG_REG_CHANNEL_B_AMP )
199: || ( PSGRegisterSelect == PSG_REG_CHANNEL_C_AMP ) )
200: PSGRegisters[PSGRegisterSelect] &= 0x1f; /* only keep bits 0 - 4 */
201:
202:
203: if ( HATARI_TRACE_LEVEL ( HATARI_TRACE_PSG_WRITE_DATA ) )
204: {
205: int nFrameCycles = Cycles_GetCounter(CYCLES_COUNTER_VIDEO);;
206: int nLineCycles = nFrameCycles % nCyclesPerLine;
207: HATARI_TRACE_PRINT ( "write ym data reg=%x val=%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n" ,
1.1.1.8 ! root 208: PSGRegisterSelect, PSGRegisters[PSGRegisterSelect], nFrameCycles, nLineCycles, nHBL, M68000_GetPC(), CurrentInstrCycles );
1.1.1.7 root 209: }
210:
211:
1.1.1.4 root 212: switch (PSGRegisterSelect)
213: {
214:
215: /* Check registers 8,9 and 10 which are 'amplitude' for each channel and
216: * store if wrote to (to check for sample playback) */
217: case PSG_REG_CHANNEL_A_AMP:
218: bWriteChannelAAmp = TRUE;
219: break;
220: case PSG_REG_CHANNEL_B_AMP:
221: bWriteChannelBAmp = TRUE;
222: break;
223: case PSG_REG_CHANNEL_C_AMP:
224: bWriteChannelCAmp = TRUE;
225: break;
226:
227: case PSG_REG_ENV_SHAPE: /* Whenever 'write' to register 13, cause envelope to reset */
228: bEnvelopeFreqFlag = TRUE;
229: bWriteEnvelopeFreq = TRUE;
230: break;
231:
232: /*
233: * FIXME: This is only a prelimary dirty hack!
234: * Port B (Printer port) - writing here needs to be dispatched to the printer
235: * STROBE (Port A bit5) does a short LOW and back to HIGH when the char is valid
236: * To print you need to write the character byte to IOB and you need to toggle STROBE
237: * (like EmuTOS does)....therefor we print when STROBE gets low and last write access to
238: * the PSG was to IOB
239: */
240: case PSG_REG_IO_PORTA:
241: /* Printer dispatching only when printing is activated */
242: if (ConfigureParams.Printer.bEnablePrinting)
243: {
1.1.1.7 root 244: /* Bit 5 - Centronics strobe? If STROBE is low and the last write did go to IOB then
245: * there is data in PORTB to print/transfer to the emulated Centronics port
246: */
247: if ((((PSGRegisters[PSG_REG_IO_PORTA]&(1<<5))==0) && bLastWriteToIOB))
1.1.1.4 root 248: {
249: /* Seems like we want to print something... */
250: Printer_TransferByteTo((unsigned char) PSGRegisters[PSG_REG_IO_PORTB]);
251: }
252: }
1.1.1.7 root 253: /* Bit 3 - Centronics as input */
254: if(PSGRegisters[PSG_REG_IO_PORTA]&(1<<3))
255: {
256: /* FIXME: might be needed if we want to emulate sound sampling hardware */
257: }
258:
259: /* handle Falcon specific bits in PORTA of the PSG */
260: if (ConfigureParams.System.nMachineType == MACHINE_FALCON)
261: {
262: /* Bit 4 - DSP reset? */
263: if(PSGRegisters[PSG_REG_IO_PORTA]&(1<<4))
264: {
265: Log_Printf(LOG_DEBUG, "Calling DSP_Reset?\n");
266: #if ENABLE_DSP_EMU
267: if (ConfigureParams.System.nDSPType == DSP_TYPE_EMU) {
268: DSP_Reset();
269: }
270: #endif
271: }
272: /* Bit 6 - Internal Speaker control */
273: if(PSGRegisters[PSG_REG_IO_PORTA]&(1<<6))
274: {
275: /*Log_Printf(LOG_DEBUG, "Falcon: Internal Speaker state\n");*/
276: /* FIXME: add code to handle? (if we want to emulate the speaker at all? */
277: }
278: /* Bit 7 - Reset IDE? */
279: if(PSGRegisters[PSG_REG_IO_PORTA]&(1<<7))
280: {
281: Log_Printf(LOG_DEBUG, "Falcon: Reset IDE subsystem\n");
282: /* FIXME: add code to handle IDE reset */
283: }
284: }
1.1.1.4 root 285: break;
286: }
287:
288: /* Remember if we wrote to IO Port B */
289: bLastWriteToIOB = (PSGRegisterSelect == PSG_REG_IO_PORTB);
1.1 root 290: }
291:
1.1.1.2 root 292:
293: /*-----------------------------------------------------------------------*/
1.1.1.7 root 294: /**
295: * Read byte from 0xff8802, returns 0xff
296: */
1.1.1.4 root 297: void PSG_DataRegister_ReadByte(void)
1.1 root 298: {
1.1.1.6 root 299: M68000_WaitState(4);
1.1.1.4 root 300:
1.1.1.8 ! root 301: IoMem[IoAccessCurrentAddress] = 0xff;
1.1 root 302: }
1.1.1.7 root 303:
304:
305:
306: /*-----------------------------------------------------------------------*/
307: /**
308: * Write byte to 0xff8801/03. Do nothing, but add some wait states if needed.
309: */
310: void PSG_Void_WriteByte(void)
311: {
312: /* [NP] FIXME If no wait states were added so far, it's possible we're accessing */
313: /* 8801/8803 through a .B instruction, so we need to add a wait state */
314: /* Else, the wait states will be added when writing to 8800/8802 */
315: /* This works so far, but this model is certainly not 100% accurate */
316: if ( nWaitStateCycles == 0 )
317: M68000_WaitState(1);
318:
319: if ( HATARI_TRACE_LEVEL ( HATARI_TRACE_PSG_WRITE_DATA ) )
320: {
321: int nFrameCycles = Cycles_GetCounter(CYCLES_COUNTER_VIDEO);;
322: int nLineCycles = nFrameCycles % nCyclesPerLine;
323: HATARI_TRACE_PRINT ( "write ym 8801/03 video_cyc=%d %d@%d pc=%x instr_cycle %d\n" ,
324: nFrameCycles, nLineCycles, nHBL, M68000_GetPC(), CurrentInstrCycles );
325: }
326: }
327:
328:
329:
330: /*-----------------------------------------------------------------------*/
331: /**
332: * Read byte from 0xff8801/03. Do nothing, but add some wait states.
333: */
334: void PSG_Void_ReadByte(void)
335: {
336: M68000_WaitState(1);
337: }
338:
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.