From d0b44c8143d1faf19f596f8607770fe62f8a4408 Mon Sep 17 00:00:00 2001 From: Romain Dolbeau Date: Sun, 8 Jan 2023 15:11:22 +0100 Subject: [PATCH] HDMI Audio --- nubus-to-ztex-gateware/DeclROM/DepVideo.inc | 4 +- nubus-to-ztex-gateware/DeclROM/gen_mode.c | 3 +- nubus-to-ztex-gateware/DeclROM/vid_decl_rom.s | 15 + .../NuBusFPGAHDMIAudio/NuBusFPGAHDMIAudio.c | 1350 +++++++++++++++++ nubus-to-ztex-gateware/VintageBusFPGA_Common | 2 +- nubus-to-ztex-gateware/hdl-util_hdmi | 1 + nubus-to-ztex-gateware/nubus_to_fpga_soc.py | 26 +- 7 files changed, 1389 insertions(+), 12 deletions(-) create mode 100644 nubus-to-ztex-gateware/NuBusFPGAHDMIAudio/NuBusFPGAHDMIAudio.c create mode 160000 nubus-to-ztex-gateware/hdl-util_hdmi diff --git a/nubus-to-ztex-gateware/DeclROM/DepVideo.inc b/nubus-to-ztex-gateware/DeclROM/DepVideo.inc index ff01e54..0e1226d 100644 --- a/nubus-to-ztex-gateware/DeclROM/DepVideo.inc +++ b/nubus-to-ztex-gateware/DeclROM/DepVideo.inc @@ -28,9 +28,11 @@ RB15s = HRES*2 RB24s = HRES*4 DrHwNuBusFPGA = 0xBEEF /* placeholder for GoboFB */ -DrHwNuBusFPGADsk = 0xBEEE /* placeholder for RAM Dsk*/ +DrHwNuBusFPGADsk = 0xBEEE /* placeholder for RAM Dsk */ +DrHwNuBusFPGAAudio = 0xBEED /* placeholder for Audio */ typeDrive = 0x1000 /* placeholder for RAM Dsk*/ +typeAudio = 0x1001 /* placeholder for Audio*/ defmBaseOffset = 0 /* beginning, placeholder */ diff --git a/nubus-to-ztex-gateware/DeclROM/gen_mode.c b/nubus-to-ztex-gateware/DeclROM/gen_mode.c index 038617f..64c9352 100644 --- a/nubus-to-ztex-gateware/DeclROM/gen_mode.c +++ b/nubus-to-ztex-gateware/DeclROM/gen_mode.c @@ -269,7 +269,8 @@ int main(int argc, char **argv) { fprintf(fd, "\tOSLstEntry\tsRsrc_GoboFB_R%hux%hu,_sRsrc_GoboFB_R%hux%hu/* video sRsrc List */\n", hres, vres, hres, vres); } } - fprintf(fd, "\tOSLstEntry\tsRsrc_RAMDsk,_sRsrc_RAMDsk\n"); + //fprintf(fd, "\tOSLstEntry\tsRsrc_RAMDsk,_sRsrc_RAMDsk\n"); + fprintf(fd, "\tOSLstEntry\tsRsrc_HDMIAudio,_sRsrc_HDMIAudio\n"); fprintf(fd, "\tDatLstEntry endOfList, 0\n"); fclose(fd); diff --git a/nubus-to-ztex-gateware/DeclROM/vid_decl_rom.s b/nubus-to-ztex-gateware/DeclROM/vid_decl_rom.s index 24bab64..4315431 100644 --- a/nubus-to-ztex-gateware/DeclROM/vid_decl_rom.s +++ b/nubus-to-ztex-gateware/DeclROM/vid_decl_rom.s @@ -9,6 +9,7 @@ sRsrc_Board = 1 /* board sResource (>0 & <128) */ .include "VidRomDef.s" sRsrc_RAMDsk = 0x90 /* functional sResources */ +sRsrc_HDMIAudio = 0xA0 /* functional sResources */ .global DeclROMDir @@ -158,6 +159,20 @@ _RAMDskDrvrDir: /* _RAMDskEnd020Drvr: */ /* supplied by linker script */ + .section .text.begin + ALIGN 2 +_sRsrc_HDMIAudio: + OSLstEntry sRsrcType,_HDMIAudioType /* video type descriptor */ + .long EndOfList /* end of list */ + + ALIGN 2 +_HDMIAudioType: + .short catProto /* */ + .short typeAudio /* custom */ /* */ + .short drSwApple /* */ + .short DrHwNuBusFPGAAudio /* */ + + /* Declaration ROM directory at end */ .section .romblock ALIGN 2 diff --git a/nubus-to-ztex-gateware/NuBusFPGAHDMIAudio/NuBusFPGAHDMIAudio.c b/nubus-to-ztex-gateware/NuBusFPGAHDMIAudio/NuBusFPGAHDMIAudio.c new file mode 100644 index 0000000..c6c422f --- /dev/null +++ b/nubus-to-ztex-gateware/NuBusFPGAHDMIAudio/NuBusFPGAHDMIAudio.c @@ -0,0 +1,1350 @@ +/* + File: NoiseMaker.c + + Contains: Sample sound output component + + Written by: Kip Olson + + Copyright: © 1994 by Apple Computer, Inc. +*/ + +#include +#include +#include +#include +#include + +#include "Sound.h" +#include "SoundComponents.h" + +#include "Slots.h" +#include "RomDefs.h" + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Hardware +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +#define GOBLIN_BT_OFFSET 0x00900000 + +#define u_int32_t volatile unsigned long + +// this is the DAC registers, not needed for audio except 'debug' is handy +struct goblin_bt_regs { + u_int32_t mode; + u_int32_t vblmask; + u_int32_t videoctrl; + u_int32_t intrclear; + u_int32_t reset; + u_int32_t lutaddr; + u_int32_t lut; + u_int32_t debug; +}; + +#define GOBLIN_CSR_OFFSET 0x00a01000 + +// those are the CSRs, we don't touch the VTG but the audio stuff is in there as well +struct goblin_csr { + u_int32_t goblin_video_framebuffer_vtg_hres; + u_int32_t goblin_video_framebuffer_vtg_hsync_start; + u_int32_t goblin_video_framebuffer_vtg_hsync_end; + u_int32_t goblin_video_framebuffer_vtg_hscan; + u_int32_t goblin_video_framebuffer_vtg_vres; + u_int32_t goblin_video_framebuffer_vtg_vsync_start; + u_int32_t goblin_video_framebuffer_vtg_vsync_end; + u_int32_t goblin_video_framebuffer_vtg_vscan; + u_int32_t goblin_goblin_audio_irqctrl; + u_int32_t goblin_goblin_audio_irqstatus; + u_int32_t goblin_goblin_audio_ctrl; + u_int32_t goblin_goblin_audio_bufstatus; + u_int32_t goblin_goblin_audio_buf0_addr; + u_int32_t goblin_goblin_audio_buf0_size; + u_int32_t goblin_goblin_audio_buf1_addr; + u_int32_t goblin_goblin_audio_buf1_size; +}; + +#define GOBLIN_AUDIOBUFFER_OFFSET 0x00920000 +#define GOBLIN_AUDIOBUFFER_SIZE 0x00002000 +// we currently have 8 KiB (0x2000) of SRAM there, but perhaps we could move that to the SDRAM? + +static inline unsigned long brev(const unsigned long r) { + return (((r&0xFF000000)>>24) | ((r&0xFF0000)>>8) | ((r&0xFF00)<<8) | ((r&0xFF)<<24)); +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Constants +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +#define kDelegateComponentCall ((ComponentRoutine) -1L) // flag that selector should be delegated instead of called + +#define kNoiseMakerVersion 0x00010000 // version for this sound component +#define kRequiredSndMgrMajorRev 3 // Sound Manager version required to run this component + +// hardware settings + +#define kSampleSizesCount 1 // 2 +#define k8BitSamples 8 // 8-bit samples +#define k16BitSamples 16 // 16-bit samples + +#define kSampleRatesCount 1 // 3 +#define kSupportsSampleRateRange false // set to true to use sample rate range +#define kSampleRateMin 0x2B110000 // sample rate min = 11.025 kHz +#define kSampleRateMax 0xAC440000 // sample rate max = 44.100 kHz + +#define kSampleRate44 0xAC440000 // 44.1000 kHz rate +#define kSampleRate22 0x56220000 // 22.050 kHz rate +#define kSampleRate11 0x2B110000 // 11.025 kHz rate + +#define kChannelsCount 1 // 2 +#define kNumChannelsMono 1 // mono +#define kNumChannelsStereo 2 // stereo + +#define kInterruptBufferSamples GOBLIN_AUDIOBUFFER_SIZE/(4 * kNumChannelsStereo * k16BitSamples >> 3) // size of interrupt buffer in samples + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Data Structures +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +/* Data structure passed to some GetInfo calls */ + +typedef struct { + short count; + Handle handle; +} HandleList, *HandleListPtr; + +/* Preferences data structure. A handle containing this data structure is stored in the + Sound Preferences file and is loaded into the sound component refcon when it is opened. */ + +typedef struct { + UnsignedFixed sampleRate; + short sampleSize; + short numChannels; + unsigned long volume; +} PreferencesRecord, *PreferencesPtr, **PreferencesHandle; + +/* Sound component globals */ + +typedef struct { + +// these are general purpose variables that every sound component will need + ComponentInstance self; // ourselves + ComponentInstance sourceComponent; // component to call when hardware needs more data + SoundComponentDataPtr sourceDataPtr; // pointer to source data structure + Handle globalsHandle; // handle to component globals + Boolean inSystemHeap; // true if component loaded in system heap, false if in app heap + Boolean prefsChanged; // true if preferences have changed + PreferencesHandle prefsHandle; // global preferences + +// these are variables specific to this implementation + SoundComponentData hwSettings; // current hardware settings + unsigned long hwVolume; // current hardware volume + Boolean hwInterruptsOn; // true if sound is playing + Boolean hwInitialized; // true if hardware was initialized by __InitOutputDevice + SlotIntQElement *siqel; + struct goblin_bt_regs* bt; // for debug + struct goblin_csr* csr; // CSR, including audio control + u_int32_t* buf0; // primary address for first buffer (host view) + u_int32_t* buf1; // primary address for second buffer (host view) + unsigned char lastbuf; + unsigned char slot; +} + GlobalsRecord, *GlobalsPtr; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Compatibility with old names (?) +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + typedef ComponentFunctionUPP ComponentRoutine; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Prototypes +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +pascal ComponentResult main(ComponentParameters *params, GlobalsPtr globals); + +ComponentRoutine GetComponentRoutine(short selector); +pascal ComponentResult __ComponentRegister(GlobalsPtr globals); +pascal ComponentResult __ComponentVersion(void *unused1); +pascal ComponentResult __ComponentCanDo(void *unused1, short selector); +pascal ComponentResult __ComponentOpen(void *unused1, ComponentInstance self); +pascal ComponentResult __ComponentClose(GlobalsPtr globals, ComponentInstance self); +pascal ComponentResult __InitOutputDevice(GlobalsPtr globals, long actions); +pascal ComponentResult __GetInfo(GlobalsPtr globals, SoundSource sourceID, OSType selector, void *infoPtr); +pascal ComponentResult __SetInfo(GlobalsPtr globals, SoundSource sourceID, OSType selector, void *infoPtr); +pascal ComponentResult __StartSource(GlobalsPtr globals, short count, SoundSource *sources); +pascal ComponentResult __PlaySourceBuffer(GlobalsPtr globals, SoundSource sourceID, SoundParamBlockPtr pb, long actions); + +Handle NewHandleLockClear(long len, Boolean inSystemHeap); +PreferencesHandle GetPreferences(ComponentInstance self, Boolean inSystemHeap); +void SavePreferences(ComponentInstance self, PreferencesHandle prefsHandle); +OSErr SetupHardware(GlobalsPtr globals); +void ReleaseHardware(GlobalsPtr globals); +OSErr StartHardware(GlobalsPtr globals); +void StopHardware(GlobalsPtr globals); +void SuspendHardware(GlobalsPtr globals); +void ResumeHardware(GlobalsPtr globals); +OSErr SetHardwareVolume(GlobalsPtr globals, unsigned long volume); +pascal void InterruptRoutine(SndChannelPtr chan, SndCommand *cmd); +SoundComponentDataPtr GetMoreSource(GlobalsPtr globals); +void CopySamplesToHardware(GlobalsPtr globals, SoundComponentDataPtr sourceDataPtr); + +asm pascal void irqTrampoline(void); + + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Main Component Entry Point +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +/* ======================================================================================= + NuBusFPGAHDMIAudio + + The function of this routine is to dispatch to the appropriate component method. It first + calls finds the address of the method to dispatch to using the selector provided in the + what field of the parameter block. If the address is -1L, then this selector should + be delegated. If the address is nil, this selector is not supported. + ======================================================================================= */ + +pascal ComponentResult main(ComponentParameters *params, GlobalsPtr globals) +{ + ComponentRoutine theRtn; + ComponentResult result; + + if (globals) { + if ((*globals).bt) { + (*(*globals).bt).debug = 'MAIN'; + (*(*globals).bt).debug = params->what; + } else { + (*(struct goblin_bt_regs*)0xfc900000).debug = 'MaIn'; + (*(struct goblin_bt_regs*)0xfc900000).debug = params->what; + } + } else { + (*(struct goblin_bt_regs*)0xfc900000).debug = 'main'; + (*(struct goblin_bt_regs*)0xfc900000).debug = params->what; + } + + theRtn = GetComponentRoutine(params->what); // get address of component routine + + if (theRtn == nil) // selector not implemented + result = badComponentSelector; + else if (theRtn == kDelegateComponentCall) // selector should be delegated + result = DelegateComponentCall(params, globals->sourceComponent); + else + result = CallComponentFunctionWithStorage((Handle) globals, params, (ComponentFunctionUPP) theRtn); + + return (result); +} + +/* ======================================================================================= + GetComponentRoutine + + The function of this routine is to return the address of the appropriate component method. + To do this, the routine must deal with 3 selector ranges: + + -5 to -1 These are the standard Component Manager selectors that all components + must share. Refer to the Component Manager documentation for more info. + + 0 to 255 These selectors cannot be delegated. If the sound component does not implement + one of these selectors, it should return the badComponentSelector error. + + 256 to ° These selectors should be delegated. If the sound component does not implement + one of these selectors, it should use DelegateComponentCall() to pass + this selector on up the chain. If the sound component does implement this + selector, it should first delegate the selector, then perform the function. + ======================================================================================= */ + +ComponentRoutine GetComponentRoutine(short selector) +{ + void *theRtn; + + if (selector < 0) + switch (selector) // standard component selectors + { + case kComponentRegisterSelect: + theRtn = __ComponentRegister; + break; + + case kComponentVersionSelect: + theRtn = __ComponentVersion; + break; + + case kComponentCanDoSelect: + theRtn = __ComponentCanDo; + break; + + case kComponentCloseSelect: + theRtn = __ComponentClose; + break; + + case kComponentOpenSelect: + theRtn = __ComponentOpen; + break; + + default: + theRtn = nil; // unknown selector, so fail + break; + } + else if (selector < kDelegatedSoundComponentSelectors) // selectors that cannot be delegated + switch (selector) + { + case kSoundComponentInitOutputDeviceSelect: + theRtn = __InitOutputDevice; + break; + + default: + theRtn = nil; // unknown selector, so fail + break; + } + else // selectors that can be delegated + switch (selector) + { + case kSoundComponentGetInfoSelect: + theRtn = __GetInfo; + break; + + case kSoundComponentSetInfoSelect: + theRtn = __SetInfo; + break; + + case kSoundComponentStartSourceSelect: + theRtn = __StartSource; + break; + + case kSoundComponentPlaySourceBufferSelect: + theRtn = __PlaySourceBuffer; + break; + + default: + theRtn = kDelegateComponentCall; // unknown selector, so delegate it + break; + } + + return (theRtn); +} + + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Component Manager Methods +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +/* ============================================================================== + Component Register + + This routine is called once, usually at boot time, when the Component Manager + is first registering this sound component. This routine should check to see if the proper + hardware is installed and return 0 if it is. If the hardware is not installed, + the routine should return 1 and this component will not be registered. This is + also an opportunity to do one-time initializations and perhaps register this + component again if more than one hardware device is available. Global state information + can also be saved in the component refcon by calling SetComponentRefCon(); + + NOTE: The cmpWantsRegisterMessage bit must be set in the component flags of the + sound component in order for this routine to be called. + NOTE: This routine is never called at interrupt time. + ============================================================================== */ + +pascal ComponentResult __ComponentRegister(GlobalsPtr globals) +{ + long result; + NumVersion version; + + if ((Gestalt(gestaltSoundAttr, &result) == noErr) && // snd gestalt is available + (result & (1L << gestaltSoundIOMgrPresent))) // snd dispatcher is available + { + version = SndSoundManagerVersion(); // get the Sound Manager version + if (version.majorRev >= kRequiredSndMgrMajorRev) // it's what we need + { + // Check for hardware here. We are always installed, so we return 0 + OSErr ret = noErr; + SpBlock mySpBlock; + SInfoRecord mySInfoRecord; + + mySpBlock.spResult = (long)&mySInfoRecord; + mySpBlock.spSlot = 0x9; + mySpBlock.spID = 0; + mySpBlock.spExtDev = 0; + mySpBlock.spCategory = catProto; + mySpBlock.spCType = 0x1001; + mySpBlock.spDrvrSW = drSwApple; + mySpBlock.spDrvrHW = 0xbeed; + mySpBlock.spTBMask = 0; + + ret = SNextTypeSRsrc(&mySpBlock); + if (ret) + return 1; // oups + + (*globals).slot = mySpBlock.spSlot; + (*globals).bt = (struct goblin_bt_regs*)(0xF0000000 | + ((unsigned long)mySpBlock.spSlot)<<24 | + GOBLIN_BT_OFFSET); + + + (*(*globals).bt).debug = 'REGI'; + + (*(*globals).bt).debug = 'slot'; + (*(*globals).bt).debug = mySpBlock.spSlot; + + (*globals).csr = (struct goblin_csr*)(0xF0000000 | + ((unsigned long)mySpBlock.spSlot)<<24 | + GOBLIN_CSR_OFFSET); + + (*(*globals).bt).debug = 'csr '; + (*(*globals).bt).debug = (unsigned long)(*globals).csr; + + return (0); // install this sound component + } + } + + return (1); // do not install component +} + +/* ============================================================================== + Component Version + + This routine is called to determine the current version of the sound component. + It should return a fixed-point value containing the version, like 0x10001 + for version 1.1. The version given here must match the version stored in + the 'thng' resource. + + NOTE: This routine is never called at interrupt time. + ============================================================================== */ + +pascal ComponentResult __ComponentVersion(void *unused1) +{ +#pragma unused (unused1) + + return (kNoiseMakerVersion); // return sound component version +} + +/* ============================================================================== + Component CanDo + + This routine is called to determine if a particular selector is implemented. + It should return 1 if this is a valid selector, or 0 if the selector is not + implemented or if the selector is always delegated. + + NOTE: This routine is never called at interrupt time. + ============================================================================== */ + +pascal ComponentResult __ComponentCanDo(void *unused1, short selector) +{ +#pragma unused (unused1) + + ComponentRoutine theRtn; + + theRtn = GetComponentRoutine(selector); // see if this selector is implemented + + if ((theRtn == nil) || // selector is not implemented + (theRtn == kDelegateComponentCall)) // or selector is always delegated + return (0); // no can do + else + return (1); // selector is implemented +} + +/* ============================================================================== + Component Open + + This routine is called when the Component Manager creates an instance of this + component. The routine should allocate global variables in the appropriate heap + and call SetComponentInstanceStorage() so the Component Manager can remember + the globals and pass them to all the method calls. + + Determining the heap to use can be tricky. The Component Manager will normally + load the component code into the system heap, which is good, since many applications + will be sharing this component to play sound. In this case, the components's global + variable storage should also be created in the system heap. + + However, if system heap memory is tight, the Component Manager will load + the component into the application heap of the first application that plays sound. + When this happens, the component should create global storage in the application heap + instead. The Sound Manager will make sure that other applications will not try + to play sound while the component is in this application heap. + + To determine the proper heap to use, call GetComponentInstanceA5(). If the value + returned is 0, then the component was loaded into the system heap, and all storage + should be allocated there. If the value returned is non-zero, the component is in + the application heap specifed by returned A5 value, and all storage should be + allocated in this application heap. + + NOTE: If the component is loaded into the application heap, the value returned by + GetComponentRefCon() will be 0. + NOTE: Do not attempt to initialize or access hardware in this call, since the + Component Manager will call Open() BEFORE calling Register(). + Instead, initialize the hardware during InitOutputDevice(), described below. + NOTE: This routine is never called at interrupt time. + ============================================================================== */ + +pascal ComponentResult __ComponentOpen(void *unused1, ComponentInstance self) +{ +#pragma unused (unused1) + + Handle h; + GlobalsPtr globals; + Boolean inSystemHeap; + + inSystemHeap = GetComponentInstanceA5(self) == 0; // find out if we were loaded in app heap or system heap + + h = NewHandleLockClear(sizeof(GlobalsRecord), inSystemHeap); // get space for globals in appropriate heap + if (h == nil) + return(MemError()); + + globals = (GlobalsPtr) *h; + + (*globals).siqel = (SlotIntQElement*)NewPtrSysClear(sizeof(SlotIntQElement)); + if ((*globals).siqel == nil) { + DisposeHandle(h); + return(MemError()); + } + + SetComponentInstanceStorage (self, (Handle) globals); // save pointer to our globals + + globals->self = self; // remember ourselves + globals->globalsHandle = h; // remember the handle + globals->inSystemHeap = inSystemHeap; // remember which heap we are in + globals->prefsHandle = GetPreferences(self, inSystemHeap); // retrieve or create preferences + if (globals->prefsHandle == nil) // could create preferences + return (memFullErr); // we can't run + + return (noErr); +} + +/* ============================================================================== + Component Close + + This routine is called when the Component Manager is closing the instance of + this component. If the hardware was initialized, the routine should make sure + all remaining data is written to the hardware and that the hardware is completely + turned off. It should delete all global storage and close any other components + that were opened. + + NOTE: Be sure to check that the globals pointer passed in to this routine is + not set to NIL. If the Open() routine fails for any reason, the Component + Manager will call this routine passing in a NIL for the globals. + NOTE: This routine is never called at interrupt time. + ============================================================================== */ + +pascal ComponentResult __ComponentClose(GlobalsPtr globals, ComponentInstance self) +{ + if (globals) // we have some globals + { + if (globals->hwInitialized) // hardware was initialized + { + ReleaseHardware(globals); // make sure the hardware is off and release it + HUnlock((Handle) globals->prefsHandle); // let prefs handle roam now + } + + if (globals->sourceComponent) // we opened a mixer + CloseMixerSoundComponent(globals->sourceComponent); // close it + + if (globals->prefsChanged) // preferences changed + SavePreferences(self, globals->prefsHandle); // save them + + if (!globals->inSystemHeap) // prefs are in app heap + DisposeHandle((Handle) globals->prefsHandle); // dispose of them + + DisposePtr((char*)globals->siqel); + DisposeHandle(globals->globalsHandle); // dispose our storage + } + + return (noErr); +} + + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Standard Sound Component Methods +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +/* ============================================================================== + InitOutputDevice + + This routine is called once when the Sound Manager first opens this sound component. + The routine should initialize the hardware to default values, allocate the + appropriate mixer and create any other memory that is required. + + NOTE: This routine is never called at interrupt time. + ============================================================================== */ + +pascal ComponentResult __InitOutputDevice(GlobalsPtr globals, long actions) +{ +#pragma unused (actions) + + ComponentResult result; + PreferencesPtr prefsPtr; + OSErr ret = noErr; + SpBlock mySpBlock; + SInfoRecord mySInfoRecord; + + mySpBlock.spResult = (long)&mySInfoRecord; + mySpBlock.spSlot = 0x9; + mySpBlock.spID = 0; + mySpBlock.spExtDev = 0; + mySpBlock.spCategory = catProto; + mySpBlock.spCType = 0x1001; + mySpBlock.spDrvrSW = drSwApple; + mySpBlock.spDrvrHW = 0xbeed; + mySpBlock.spTBMask = 0; + + ret = SNextTypeSRsrc(&mySpBlock); + if (ret) + return notEnoughHardware; // oups + + (*globals).slot = mySpBlock.spSlot; + (*globals).bt = (struct goblin_bt_regs*)(0xF0000000 | + ((unsigned long)mySpBlock.spSlot)<<24 | + GOBLIN_BT_OFFSET); + + (*(*globals).bt).debug = 'INIT'; + + (*(*globals).bt).debug = 'SLOT'; + (*(*globals).bt).debug = mySpBlock.spSlot; + + (*globals).csr = (struct goblin_csr*)(0xF0000000 | + ((unsigned long)mySpBlock.spSlot)<<24 | + GOBLIN_CSR_OFFSET); + + (*(*globals).bt).debug = 'CSR '; + (*(*globals).bt).debug = (unsigned long)(*globals).csr; + + // Open the mixer and tell it the type of data it should output. The + // description includes sample format, sample rate, sample size, number of channels + // and the size of your optimal interrupt buffer. If a mixer cannot be found that + // will output this type of data, an error will be returned. + + prefsPtr = *globals->prefsHandle; // get settings from preferences + + // set to hardware defaults + + globals->hwSettings.flags = 0; + globals->hwSettings.format = (prefsPtr->sampleSize == 8) ? kOffsetBinary : kTwosComplement; + globals->hwSettings.sampleRate = prefsPtr->sampleRate; + globals->hwSettings.sampleSize = prefsPtr->sampleSize; + globals->hwSettings.numChannels = prefsPtr->numChannels; + globals->hwSettings.sampleCount = kInterruptBufferSamples * 2; + + // open mixer that will output this format + + result = OpenMixerSoundComponent(&globals->hwSettings, 0, &globals->sourceComponent); + if (result != noErr) + return (result); + + result = SetupHardware(globals); // setup the hardware to these settings + + if (result == noErr) + { + globals->hwInitialized = true; // hardware is ready to go + HLock((Handle) globals->prefsHandle); // lock prefs so we can use them at interrupt time + } + + return (result); +} + +/* ============================================================================== + GetInfo + + This routine returns information about this output component to the Sound Manager. + A 4-byte OSType selector is used to determine the type and size of the information + to return. If the component does not support a selector, it should delegate this + call on up the chain. + + NOTE: This can be called at interrupt time. However, selectors that return + a handle will not be called at interrupt time. + ============================================================================== */ + +pascal ComponentResult __GetInfo(GlobalsPtr globals, SoundSource sourceID, + OSType selector, void *infoPtr) +{ + HandleListPtr listPtr; + short *sp; + UnsignedFixed *lp; + Handle h; + PreferencesPtr prefsPtr; + ComponentResult result = noErr; + + prefsPtr = *globals->prefsHandle; // get settings from preferences + + switch (selector) + { + case siSampleSize: // return current sample size + *((short *) infoPtr) = prefsPtr->sampleSize; + break; + + case siSampleSizeAvailable: // return samples sizes available + h = NewHandle(sizeof(short) * kSampleSizesCount); // space for sample sizes + if (h == nil) + return (MemError()); + + listPtr = (HandleListPtr) infoPtr; + listPtr->count = kSampleSizesCount; // no. sample sizes in handle + listPtr->handle = h; // handle to be returned + + sp = (short *) *h; // store sample sizes in handle + //*sp++ = k8BitSamples; + *sp++ = k16BitSamples; + break; + + case siSampleRate: // return current sample rate + *((Fixed *) infoPtr) = prefsPtr->sampleRate; + break; + + case siSampleRateAvailable: // return sample rates available + h = NewHandle(sizeof(UnsignedFixed) * kSampleRatesCount); // space for sample rates + if (h == nil) + return (MemError()); + + listPtr = (HandleListPtr) infoPtr; + listPtr->count = kSampleRatesCount; // no. sample rates in handle + listPtr->handle = h; // handle to be returned + + lp = (UnsignedFixed *) *h; + + // If the hardware can support a range of sample rate values, then the + // list count should be set to zero and the min and max sample rate values + // should be stored in the handle. + + if (kSupportsSampleRateRange) + { + listPtr->count = 0; + *lp++ = kSampleRateMin; // min + *lp++ = kSampleRateMax; // max + } + + // If the hardware supports a limited set of sample rates, then the list count + // should be set to the number of sample rates and this list of rates should be + // stored in the handle. + + else + { + //*lp++ = kSampleRate11; // store sample rates in handle + //*lp++ = kSampleRate22; + *lp++ = kSampleRate44; + } + break; + + case siNumberChannels: // return current no. channels + *((short *) infoPtr) = prefsPtr->numChannels; + break; + + case siChannelAvailable: // return channels available + h = NewHandle(sizeof(short) * kChannelsCount); // space for channels + if (h == nil) + return (MemError()); + + listPtr = (HandleListPtr) infoPtr; + listPtr->count = kChannelsCount; // no. channels in handle + listPtr->handle = h; // handle to be returned + + sp = (short *) *h; // store channels in handle + //*sp++ = kNumChannelsMono; + *sp++ = kNumChannelsStereo; + break; + + case siHardwareVolume: + *((long *)infoPtr) = prefsPtr->volume; + break; + + // if you do not handle this selector, then delegate it up the chain + default: + result = SoundComponentGetInfo(globals->sourceComponent, sourceID, selector, infoPtr); + break; + + } + + return (result); +} + +/* ============================================================================== + SetInfo + + This routine sets information about this component. A 4-byte OSType selector is + used to determine the type and size of the information to apply. If the component + does not support a selector, it should delegate this call on up the chain. + + NOTE: This can be called at interrupt time. + ============================================================================== */ + +pascal ComponentResult __SetInfo(GlobalsPtr globals, SoundSource sourceID, + OSType selector, void *infoPtr) +{ + PreferencesPtr prefsPtr; + ComponentResult result = noErr; + + prefsPtr = *globals->prefsHandle; // get settings from preferences + + switch (selector) + { + case siSampleSize: // set sample size + { + short sampleSize = (short) infoPtr; + + if (/*(sampleSize == k8BitSamples) ||*/ // make sure it is a valid sample size + (sampleSize == k16BitSamples)) + { + prefsPtr->sampleSize = sampleSize; // save new size in prefs + globals->prefsChanged = true; // save prefs on close + } + else + result = siInvalidSampleSize; + break; + } + + case siSampleRate: // set sample rate + { + UnsignedFixed sampleRate = (UnsignedFixed) infoPtr; + + if (kSupportsSampleRateRange) // sample rate range + { + if ((kSampleRateMin <= sampleRate) && (sampleRate <= kSampleRateMax)) // make sure it is a valid sample rate + { + prefsPtr->sampleRate = sampleRate; // save new rate in prefs + globals->prefsChanged = true; // save prefs on close + } + else + result = siInvalidSampleRate; + } + else + { + if (/*(sampleRate == kSampleRate11) || // make sure it is a valid sample rate + (sampleRate == kSampleRate22) ||*/ + (sampleRate == kSampleRate44)) + { + prefsPtr->sampleRate = sampleRate; // save new rate in prefs + globals->prefsChanged = true; // save prefs on close + } + else + result = siInvalidSampleSize; + } + break; + } + + case siNumberChannels: // set no. channels + { + short numChannels = (short) infoPtr; + + if (/*(numChannels == kNumChannelsMono) ||*/ // make sure it is a valid no. channels + (numChannels == kNumChannelsStereo)) + { + prefsPtr->numChannels = numChannels; // save new num channels in prefs + globals->prefsChanged = true; // save prefs on close + } + else + result = notEnoughHardware; + break; + } + + case siHardwareVolume: + prefsPtr->volume = (long) infoPtr; // save volume in prefs + globals->prefsChanged = true; // save prefs on close + result = SetHardwareVolume(globals, prefsPtr->volume); + break; + + // if you do not handle this selector, then call up the chain + default: + result = SoundComponentSetInfo(globals->sourceComponent, sourceID, selector, infoPtr); + break; + } + + return (result); +} + +/* ============================================================================== + StartSource + + This routine is used to start sounds playing that are currently paused. It should + first delegate this call up the chain so the rest of the chain can prepare + to play this sound. Then, if the hardware is not already started it should be + turned on. + + NOTE: This can be called at interrupt time. + ============================================================================== */ + +pascal ComponentResult __StartSource(GlobalsPtr globals, short count, SoundSource *sources) +{ + ComponentResult result; + + // tell the mixer to start these sources + result = SoundComponentStartSource(globals->sourceComponent, count, sources); + if (result != noErr) + return (result); + + // make sure hardware interrupts are running + result = StartHardware(globals); + + return (result); +} + +/* ============================================================================== + PlaySourceBuffer + + This routine is used to specify a new sound to play and conditionally start + the hardware playing that sound.It should first delegate this call up the + chain so the rest of the chain can prepare to play this sound. Then, if the + hardware is not already started it should be turned on. + + NOTE: This can be called at interrupt time. + ============================================================================== */ + +pascal ComponentResult __PlaySourceBuffer(GlobalsPtr globals, SoundSource sourceID, SoundParamBlockPtr pb, long actions) +{ + ComponentResult result; + + (*(*globals).bt).debug = 'PLAY'; + + // tell mixer to start playing this new buffer + result = SoundComponentPlaySourceBuffer(globals->sourceComponent, sourceID, pb, actions); + if (result != noErr) + return (result); + + // if the kSourcePaused bit is set, then do not turn on your hardware just yet + // (the assumption is that StartSource() will later be used to start this sound playing). + // If this bit is not set, turn your hardware interrupts on. + + if (!(actions & kSourcePaused)) + result = StartHardware(globals); + + + (*(*globals).bt).debug = 'YALP'; + (*(*globals).bt).debug = result; + + return (result); +} + + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// This routine creates a locked handle in the requested heap. + +Handle NewHandleLockClear(long len, Boolean inSystemHeap) // allocate a new handle, lock it down and clear it +{ + Handle h; + + if (inSystemHeap) // we are loaded into the system heap + { + ReserveMemSys(len); // create it low down in system heap + h = NewHandleSysClear(len); + } + else // we are loaded into the app heap + { + h = NewHandleClear(len); // create our memory there and move it out of the way + if (h) MoveHHi(h); + } + + if (h) HLock(h); // lock it down + return (h); +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// This routine gets the preferences from the refCon or the preferences file +// or creates default preferences if it has to. + +PreferencesHandle GetPreferences(ComponentInstance self, Boolean inSystemHeap) +{ + Handle prefsHandle, componentName; + PreferencesPtr prefsPtr; + ComponentDescription componentDesc; + OSErr err; + + prefsHandle = (Handle) GetComponentRefcon((Component) self); // get prefs from component refcon + if (prefsHandle) + return ((PreferencesHandle) prefsHandle); // valid prefs in refcon, so we are done + + componentName = NewHandle(0); // make space for name + if (componentName == nil) + goto NewNameHandleFailed; + + err = GetComponentInfo((Component) self, &componentDesc, componentName, nil, nil); // get component name and subtype + if (err != noErr) + goto InfoFailed; + + prefsHandle = NewHandleLockClear(sizeof(PreferencesRecord), inSystemHeap); // create space for prefs handle + if (prefsHandle == nil) + goto NewPrefsHandleFailed; + + HUnlock(prefsHandle); // don't leave prefs handle locked down forever + HLock(componentName); + + err = GetSoundPreference(componentDesc.componentSubType, (StringPtr) *componentName, prefsHandle); // get prefs handle from file + if (err != noErr) // no file or prefs not in file + goto GetPrefsFailed; + + if (GetHandleSize(prefsHandle) != sizeof(PreferencesRecord)) // older version of preferences + goto GetPrefsFailed; // start with all new preferences + + DisposeHandle(componentName); + SetComponentRefcon((Component) self, (long) prefsHandle); // save prefs in refcon + + return ((PreferencesHandle) prefsHandle); // we are done + + + /* If we end up here, it means that the preferences could not be loaded out of the + sound preferences file for some reason, so we need to generate some default preferences. */ + +GetPrefsFailed: + DisposeHandle(prefsHandle); +NewPrefsHandleFailed: +InfoFailed: + DisposeHandle(componentName); +NewNameHandleFailed: + + prefsHandle = NewHandleLockClear(sizeof(PreferencesRecord), inSystemHeap); // create space for prefs handle + if (prefsHandle == nil) + return (nil); + + HUnlock(prefsHandle); // don't leave prefs handle locked down forever + prefsPtr = (PreferencesPtr) *prefsHandle; + + prefsPtr->sampleSize = k16BitSamples;//k8BitSamples; // initialize prefs to defaults + prefsPtr->sampleRate = kSampleRate44;//kSampleRate22; + prefsPtr->numChannels = kNumChannelsStereo; + prefsPtr->volume = (kFullVolume << 16) | kFullVolume; + + SetComponentRefcon((Component) self, (long) prefsHandle); // save prefs in refcon + + return ((PreferencesHandle) prefsHandle); // we are done +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// This routine saves the hardware globals in the preferences file + +void SavePreferences(ComponentInstance self, PreferencesHandle prefsHandle) +{ + Handle componentName; + ComponentDescription componentDesc; + OSErr err; + + componentName = NewHandle(0); // make space for name + if (componentName == nil) + goto NewNameHandleFailed; + + err = GetComponentInfo((Component) self, &componentDesc, componentName, nil, nil); // get component name and subtype + if (err != noErr) + goto InfoFailed; + + HLock(componentName); + err = SetSoundPreference(componentDesc.componentSubType, (StringPtr) *componentName, (Handle) prefsHandle); + +InfoFailed: + DisposeHandle(componentName); +NewNameHandleFailed: + + return; +} + + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Hardware-specific Methods +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Initialize the hardware. Our "hardware" just uses the Sound Manager to +// play sounds. Your hardware would probably do something completely different. +// +// We have to find another output component to play sound with, or the Sound Manager +// will try to use our component by default when we call SndNewChannel. We do this +// by searching for another output component, and passing that component to SndNewChannel +// with a flag set telling the Sound Manager to use this component instead of the default. + + +OSErr SetupHardware(GlobalsPtr globals) +{ + OSErr err = noErr; + + // host view ; currently mapped to the SRAM, could be anywhere + // in fact, I'm not sure how MacOS would handle it, but it could be in host memory + // using the wishbone DMA word-by-word + // (or using a more sophisticated approach and using block request for 8/16/32/64 bytes) + (*globals).buf0 = (u_int32_t*)(0xF0000000 | + ((unsigned long)(*globals).slot)<<24 | + GOBLIN_AUDIOBUFFER_OFFSET); + (*globals).buf1 = (u_int32_t*)(0xF0000000 | + ((unsigned long)(*globals).slot)<<24 | + GOBLIN_AUDIOBUFFER_OFFSET | + (GOBLIN_AUDIOBUFFER_SIZE >> 1)); // offset by 4 KiB + + (*(*globals).bt).debug = 'bufX'; + (*(*globals).bt).debug = (unsigned long)(*globals).buf0; + (*(*globals).bt).debug = (unsigned long)(*globals).buf1; + (*(*globals).bt).debug = brev((unsigned long)&(*(*globals).csr).goblin_goblin_audio_buf0_addr); + (*(*globals).bt).debug = brev((unsigned long)&(*(*globals).csr).goblin_goblin_audio_buf1_addr); + + // HW view (internal Wishbone addresses, so w/o the slot number) + (*(*globals).csr).goblin_goblin_audio_buf0_addr = brev((unsigned long)(0xF0000000 | + GOBLIN_AUDIOBUFFER_OFFSET)); + (*(*globals).csr).goblin_goblin_audio_buf1_addr = brev((unsigned long)(0xF0000000 | + GOBLIN_AUDIOBUFFER_OFFSET | + (GOBLIN_AUDIOBUFFER_SIZE >> 1))); + + // FIXME TODO: irq + (*(*globals).siqel).sqType = sIQType; + (*(*globals).siqel).sqPrio = 6; + (*(*globals).siqel).sqAddr = irqTrampoline; + (*(*globals).siqel).sqParm = (long)globals; + + SIntInstall((*globals).siqel, (*globals).slot); + + return (err); +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Release the hardware. + +void ReleaseHardware(GlobalsPtr globals) +{ + StopHardware(globals); // make sure hardware is off + + // remove interrupt + SIntRemove((*globals).siqel, (*globals).slot); +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Turn on hardware interrupts. + +OSErr StartHardware(GlobalsPtr globals) +{ + OSErr err = noErr; + + if (!globals->hwInterruptsOn) + { + globals->hwInterruptsOn = true; // the hardware will soon be on + + (*(*globals).csr).goblin_goblin_audio_irqctrl = brev(0x3); // enable interrup, clear previous + + (*(*globals).csr).goblin_goblin_audio_buf1_size = brev(1); // make pretend + (*globals).buf1[0] = 0; // silence + + (*globals).lastbuf = 1; + + (*(*globals).csr).goblin_goblin_audio_ctrl = brev(0x00010101); // auto-stop play buf1, this will just force an interrupt + + + } + + return (err); +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Turn off hardware interrupts. + +void StopHardware(GlobalsPtr globals) +{ + + // stop playing + // race condition ? + (*(*globals).csr).goblin_goblin_audio_ctrl = (*(*globals).csr).goblin_goblin_audio_ctrl | brev(0x00010000); // add auto-stop + + if (globals->hwInterruptsOn) + { + (*(*globals).csr).goblin_goblin_audio_irqctrl = brev(0x2); // clear low-order bit (enable) & clear irq (second bit) + + globals->hwInterruptsOn = false; // the hardware is now off + } +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Temporarily suspend hardware interrupts, so the interrupt routine can work in peace. + +void SuspendHardware(GlobalsPtr globals) +{ + if (globals->hwInterruptsOn) + { + // Suspend hardware interrupts here + (*(*globals).csr).goblin_goblin_audio_irqctrl = 0; // clear low-order bit (enable) + } +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Resume hardware interrupts after they were suspended. + +void ResumeHardware(GlobalsPtr globals) +{ + if (globals->hwInterruptsOn) + { + (*(*globals).csr).goblin_goblin_audio_irqctrl = brev(0x1); // set low-order bit (enable) + } +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Set hardware volume. + +OSErr SetHardwareVolume(GlobalsPtr globals, unsigned long volume) +{ + OSErr err = noErr; + + // fixme TODO + + return (err); +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// This routine is called every hardware interrupt to fill the hardware +// with audio data. First it should suspend interrupts so it will not +// be interrupted again. Then it should get more data from the source mixer, +// and copy the data to the hardware. On the way out, if all data was copied, +// try to get some more so it will be available immediately next interrupt. +/* +pascal void InterruptRoutine(SndChannelPtr chan, SndCommand *cmd) +{ +#pragma unused (chan) + + GlobalsPtr globals; + SoundComponentDataPtr sourceDataPtr; + + globals = (GlobalsPtr) cmd->param2; // get globals from command + + SuspendHardware(globals); // suspend interrupts while we are processing + + sourceDataPtr = GetMoreSource(globals); // get source from mixer + if (sourceDataPtr == nil) // no more source + { + StopHardware(globals); // turn hardware off + return; + } + + CopySamplesToHardware(globals, sourceDataPtr); // fulfill hardware request + +// Normally, you will want to check to see if you have run out +// of data here and get more right away so you will be ready for +// the next interrupt. This example does not have any hardware +// to copy the data to, so it leaves the data in the mixer buffer and +// thus cannot call for more until it has been played. + +// if (sourceDataPtr->sampleCount == 0) // exhausted the source +// sourceDataPtr = GetMoreSource(globals); // get more for next time + + ResumeHardware(globals); // resume interrupts +} +*/ +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// This routine returns the data pointer to your mixer source. If there +// is no source or it is empty, it will call down the chain to fill it up. + +SoundComponentDataPtr GetMoreSource(GlobalsPtr globals) +{ + ComponentResult result; + SoundComponentDataPtr sourceDataPtr = globals->sourceDataPtr; + + if ((sourceDataPtr == nil) || (sourceDataPtr->sampleCount == 0)) // no data - better get some + { + result = SoundComponentGetSourceData(globals->sourceComponent, &globals->sourceDataPtr); + sourceDataPtr = globals->sourceDataPtr; + + if ((result != noErr) || // error getting data + (sourceDataPtr == nil) || // source has no data pointer to return + (sourceDataPtr->sampleCount == 0)) // source has no more data + { + return (nil); // stop the presses + } + } + + + + (*(*globals).bt).debug = 'srcd'; + (*(*globals).bt).debug = sourceDataPtr->numChannels; + (*(*globals).bt).debug = sourceDataPtr->sampleSize; + (*(*globals).bt).debug = sourceDataPtr->sampleRate; + (*(*globals).bt).debug = sourceDataPtr->sampleCount; + + return (sourceDataPtr); // return pointer to source +} + + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Example-specific Methods +// +// These routines are only needed to make this example work and as such should not be +// used in your sound component. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// This routine copies the data returned by the mixer to the hardware. + +void CopySamplesToHardware(GlobalsPtr globals, SoundComponentDataPtr sourceDataPtr) +{ + OSErr err = noErr; + unsigned long status = brev((*(*globals).csr).goblin_goblin_audio_ctrl); + + if (status & 0x0100) { + (*(*globals).bt).debug = 'cont'; + // already playing, so the 'lastbuf' is empty + // and we are playing lastbuf xor 1 + switch ((*globals).lastbuf) { + case 0: + BlockMove(sourceDataPtr->buffer, (void*)((*globals).buf0), 4 * sourceDataPtr->sampleCount); + (*(*globals).csr).goblin_goblin_audio_buf0_size = brev(sourceDataPtr->sampleCount); + (*globals).lastbuf = 1; + //(*(*globals).csr).goblin_goblin_audio_ctrl = brev(0x0100); // play buf0 // redundant ? + break; + case 1: + default: + BlockMove(sourceDataPtr->buffer, (void*)((*globals).buf1), 4 * sourceDataPtr->sampleCount); + (*(*globals).csr).goblin_goblin_audio_buf1_size = brev(sourceDataPtr->sampleCount); + (*globals).lastbuf = 0; + //(*(*globals).csr).goblin_goblin_audio_ctrl = brev(0x0101); // play buf1 // redundant ? + break; + } + sourceDataPtr->sampleCount = 0; // sound has been played + } else { + // not yet playing, put half of the samples in each buffer + // so that when the first buffer is empty, we'll be playing the second while reloading + long buf0count = sourceDataPtr->sampleCount / 2; + long buf1count = sourceDataPtr->sampleCount - buf0count; + (*(*globals).bt).debug = 'new '; + (*(*globals).bt).debug = buf0count; + (*(*globals).bt).debug = buf1count; + BlockMove(sourceDataPtr->buffer, (void*)((*globals).buf0), 4 * buf0count); + BlockMove(sourceDataPtr->buffer + 4*buf0count, (void*)((*globals).buf1), 4 * buf1count); + (*(*globals).csr).goblin_goblin_audio_buf0_size = brev(buf0count); + (*(*globals).csr).goblin_goblin_audio_buf1_size = brev(buf1count); + (*globals).lastbuf = 0; + sourceDataPtr->sampleCount = 0; // sound has been played + // now play for real + (*(*globals).csr).goblin_goblin_audio_ctrl = brev(0x0100); // play buf0 + } +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Hardware IRQ handling + +short irqFct(GlobalsPtr globals); +#pragma parameter __D0 irqFct(__A1) +short irqFct(GlobalsPtr globals) +{ + SoundComponentDataPtr sourceDataPtr; + long irqstatus; + + (*(*globals).bt).debug = 'irq '; + + // first, is that one of ours ? + irqstatus = brev((*(*globals).csr).goblin_goblin_audio_irqstatus); + if ((irqstatus & 1) == 0) { + // nope + return 0; + } + + (*(*globals).bt).debug = 'irqA'; + + // yes, ours, clear & suspend it before dealing with it + (*(*globals).csr).goblin_goblin_audio_irqctrl = brev(0x2); + + sourceDataPtr = GetMoreSource(globals); // get source from mixer + if (sourceDataPtr == nil) // no more source + { + StopHardware(globals); // turn hardware off + } + else + { + CopySamplesToHardware(globals, sourceDataPtr); // fulfill hardware request + + (*(*globals).csr).goblin_goblin_audio_irqctrl = brev(0x1); // restart interrupt + } + + return 1; +} +// only save what needs to preserve, then call the 'real' function +asm pascal void irqTrampoline(void) +{ + MOVEM.L D1-D6/A0/A2-A5,-(A7) + BSR irqFct + MOVEM.L (A7)+,D1-D6/A0/A2-A5 + RTS +} \ No newline at end of file diff --git a/nubus-to-ztex-gateware/VintageBusFPGA_Common b/nubus-to-ztex-gateware/VintageBusFPGA_Common index 59e3973..c18819f 160000 --- a/nubus-to-ztex-gateware/VintageBusFPGA_Common +++ b/nubus-to-ztex-gateware/VintageBusFPGA_Common @@ -1 +1 @@ -Subproject commit 59e39731defcad164ff30f395f7fa079e63a78af +Subproject commit c18819f169c556ada9fc0d6358c3965aef09ee48 diff --git a/nubus-to-ztex-gateware/hdl-util_hdmi b/nubus-to-ztex-gateware/hdl-util_hdmi new file mode 160000 index 0000000..01c18e4 --- /dev/null +++ b/nubus-to-ztex-gateware/hdl-util_hdmi @@ -0,0 +1 @@ +Subproject commit 01c18e4cbba4aeb5cebecc71cd9bb14ae7bf49b3 diff --git a/nubus-to-ztex-gateware/nubus_to_fpga_soc.py b/nubus-to-ztex-gateware/nubus_to_fpga_soc.py index 35ebd21..9a559ae 100644 --- a/nubus-to-ztex-gateware/nubus_to_fpga_soc.py +++ b/nubus-to-ztex-gateware/nubus_to_fpga_soc.py @@ -142,6 +142,7 @@ class _CRG(Module): num_clk = num_clk + 1 platform.add_platform_command("create_generated_clock -name hdmi5x_clk [get_pins {{{{MMCME2_ADV_{}/CLKOUT{}}}}}]".format(num_adv, num_clk)) num_clk = num_clk + 1 + self.comb += video_pll.reset.eq(~rst_nubus_n) #platform.add_false_path_constraints(self.cd_sys.clk, self.cd_vga.clk) platform.add_false_path_constraints(self.cd_sys.clk, video_pll.clkin) @@ -215,6 +216,7 @@ class NuBusFPGA(SoCCore): "goblin_accel_ram" : 0xF0902000, # accel for goblin (scratch ram) "stat" : 0xF0903000, # stat "goblin_accel_rom" : 0xF0910000, # accel for goblin (rom) + "goblin_audio_ram" : 0xF0920000, # audio for goblin (RAM buffers) "csr" : 0xF0A00000, # CSR "pingmaster": 0xF0B00000, "rom": 0xF0FF8000, # ROM at the end (32 KiB of it ATM) @@ -375,16 +377,17 @@ class NuBusFPGA(SoCCore): self.submodules.fromsbus_fifo = ClockDomainsRenamer({"write": "nubus", "read": "sys"})(AsyncFIFOBuffered(width=layout_len(self.fromsbus_layout), depth=512//data_width)) self.submodules.fromsbus_req_fifo = ClockDomainsRenamer({"read": "nubus", "write": "sys"})(AsyncFIFOBuffered(width=layout_len(self.fromsbus_req_layout), depth=512//data_width)) irq_line = self.platform.request("nmrq_3v3_n") # active low - fb_irq = Signal() # active low - dma_irq = Signal() # active low - led0 = platform.request("user_led", 0) - led1 = platform.request("user_led", 1) - self.comb += [ - led0.eq(~fb_irq), - led1.eq(~dma_irq), - ] + fb_irq = Signal(reset = 1) # active low + dma_irq = Signal(reset = 1) # active low + audio_irq = Signal(reset = 1) # active low + #led0 = platform.request("user_led", 0) + #led1 = platform.request("user_led", 1) + #self.comb += [ + # led0.eq(~fb_irq), + # led1.eq(~dma_irq), + #] - self.comb += irq_line.eq(fb_irq & dma_irq) # active low, enable if one is low + self.comb += irq_line.eq(fb_irq & dma_irq & audio_irq) # active low, enable if one is low self.submodules.exchange_with_mem = ExchangeWithMem(soc=self, platform=platform, @@ -429,6 +432,11 @@ class NuBusFPGA(SoCCore): else: # GoblinAlt contains its own PHY self.submodules.goblin = GoblinAlt(soc=self, timings=goblin_res, clock_domain="hdmi", irq_line=fb_irq, endian="little", hwcursor=False, truecolor=True) + # it also has a bus master so that the audio bit can fetch data from Wishbone + self.bus.add_master(name="GoblinAudio", master=self.goblin.goblin_audio.busmaster) + self.add_ram("goblin_audio_ram", origin=self.mem_map["goblin_audio_ram"], size=2**13, mode="rw") # 8 KiB buffer, planned as 2*4KiB + self.comb += [ audio_irq.eq(self.goblin.goblin_audio.irq), ] + self.bus.add_slave("goblin_bt", self.goblin.bus, SoCRegion(origin=self.mem_map.get("goblin_bt", None), size=0x1000, cached=False)) #pad_user_led_0 = platform.request("user_led", 0) #pad_user_led_1 = platform.request("user_led", 1)