/*++ Copyright (c) 1991-1994 Microsoft Corporation Module Name: hardware.c Abstract: This module contains code for communicating with the DSP on the Soundblaster card. Environment: Kernel mode Revision History: --*/ #include "sound.h" // // Pre declare stuff // USHORT dspGetVersion( PSOUND_HARDWARE pHw ); BOOLEAN dspStartNonAutoDMA( PSOUND_HARDWARE pHw, ULONG Size, BOOLEAN Direction ); BOOLEAN HwSetWaveFormat( IN PWAVE_INFO WaveInfo ); MIDI_INTERFACE_ROUTINE HwStartMidiIn; MIDI_INTERFACE_ROUTINE HwStopMidiIn; MIDI_INTERFACE_ROUTINE MPU401StartMidiIn; MIDI_INTERFACE_ROUTINE MPU401StopMidiIn; // // Remove initialization stuff from resident memory // #ifdef ALLOC_PRAGMA #pragma alloc_text(INIT,HwInitialize) #pragma alloc_text(INIT,dspGetVersion) #pragma alloc_text(PAGE, dspSpeakerOn) #pragma alloc_text(PAGE, dspSpeakerOff) #pragma alloc_text(PAGE, dspReset) #pragma alloc_text(PAGE, dspStartNonAutoDMA) #pragma alloc_text(PAGE, HwSetWaveFormat) #pragma alloc_text(PAGE, HwStartMidiIn) #pragma alloc_text(PAGE, HwStopMidiIn) #pragma alloc_text(PAGE, MPU401StartMidiIn) #pragma alloc_text(PAGE, MPU401StopMidiIn) #endif UCHAR dspRead( IN PSOUND_HARDWARE pHw ) /*++ Routine Description: Read the DSP data port Time out occurs after about 1ms Arguments: pHw - Pointer to the device extension data. pvalue - Pointer to the UCHAR to receive the result Return Value: Value read --*/ { USHORT uCount; UCHAR Value; ASSERT(pHw->Key == HARDWARE_KEY); uCount = 100; Value = 0xFF; // If fail look like port not populated while (uCount--) { int InnerCount; // // Protect all reads and writes with a spin lock // HwEnter(pHw); // // Inner count loop protects against dynamic deadlock with // midi. // for (InnerCount = 0; InnerCount < 10; InnerCount++) { if (INPORT(pHw, DATA_AVAIL_PORT) & 0x80) { Value = INPORT(pHw, DATA_PORT); uCount = 0; break; } KeStallExecutionProcessor(1); } HwLeave(pHw); } // timed out return Value; } BOOLEAN dspReset( PSOUND_HARDWARE pHw ) /*++ Routine Description: Reset the DSP Arguments: pHw - pointer to the device extension data Return Value: The return value is TRUE if the dsp was reset, FALSE if an error occurred. --*/ { // // When we reset we'll lose the format information so initialize it // now. Also the speaker is nominally OFF after reset. // pHw->SetFormat = TRUE; pHw->SpeakerOn = FALSE; // // try for a reset - note that midi output may be running at this // point so we need the spin lock while we're trying to reset // HwEnter(pHw); OUTPORT(pHw, RESET_PORT, 1); KeStallExecutionProcessor(3); // wait 3 us OUTPORT(pHw, RESET_PORT, 0); HwLeave(pHw); // we should get 0xAA at the data port now if (dspRead(pHw) != 0xAA) { // // timed out or other screw up // // dprintf1(("Failed to reset DSP")); return FALSE; } return TRUE; } BOOLEAN dspWrite( PSOUND_HARDWARE pHw, UCHAR value ) /*++ Routine Description: Write a command or data to the DSP Arguments: pHw - Pointer to the device extension data value - the value to be written Return Value: TRUE if written correctly , FALSE otherwise --*/ { ULONG uCount; ASSERT(pHw->Key == HARDWARE_KEY); uCount = 100; while (uCount--) { int InnerCount; HwEnter(pHw); // // Inner count loop protects against dynamic deadlock with // midi output. // for (InnerCount = 0; InnerCount < 10; InnerCount++) { if (!(INPORT(pHw, DATA_STATUS_PORT) & 0x80)) { OUTPORT(pHw, DATA_STATUS_PORT, value); break; } KeStallExecutionProcessor(1); // 1 us } HwLeave(pHw); if (InnerCount < 10) { return TRUE; } } dprintf1(("Failed to write %x to dsp", (ULONG)value)); return FALSE; } BOOLEAN dspWriteNoLock( PSOUND_HARDWARE pHw, UCHAR value ) /*++ Routine Description: Write a command or data to the DSP. The call assumes the caller has acquired the spin lock Arguments: pHw - Pointer to the device extension data value - the value to be written Return Value: TRUE if written correctly , FALSE otherwise --*/ { int uCount; ASSERT(pHw->Key == HARDWARE_KEY); uCount = 1000; while (uCount--) { if (!(INPORT(pHw, DATA_STATUS_PORT) & 0x80)) { OUTPORT(pHw, DATA_STATUS_PORT, value); break; } KeStallExecutionProcessor(1); // 1 us } if (uCount >= 0) { return TRUE; } dprintf1(("Failed to write %x to dsp", (ULONG)value)); return FALSE; } USHORT dspGetVersion( PSOUND_HARDWARE pHw ) /*++ Routine Description: Get the DSP software version Arguments: pHw - pointer to the hardware data Return Value: The return value contains the major version in the high byte and the minor version in the low byte. If an error occurs then the return value is zero. --*/ { UCHAR major, minor; // we have a card, try to read the version number if (dspWrite(pHw, DSP_GET_VERSION)) { major = dspRead(pHw); minor = dspRead(pHw); return (USHORT)((((USHORT)major) << 8) + (USHORT)minor); } return 0; } BOOLEAN dspSpeakerOn( PSOUND_HARDWARE pHw ) /*++ Routine Description: Turn the speaker on Arguments: pHw - pointer to the device extension data Return Value: TRUE --*/ { int i; if (SB16(pHw)) { return TRUE; } if (!pHw->SpeakerOn) { // // Thunderboard likes a gap // KeStallExecutionProcessor(100); dspWrite(pHw, DSP_SPEAKER_ON); pHw->SpeakerOn = TRUE; // // Now wait until it's OK again (up to 112 ms) // for (i = 0; i < 3; i++) { if (!(INPORT(pHw, DATA_STATUS_PORT) & 0x80)) { break; } SoundDelay(38); } dprintf4(("Waited %d ms for speaker to go on", i * 38)); } return TRUE; } BOOLEAN dspSpeakerOff( PSOUND_HARDWARE pHw ) /*++ Routine Description: Turn the speaker on Arguments: pHw - pointer to the device extension data Return Value: TRUE --*/ { int i; if (SB16(pHw)) { return TRUE; } /* if (pHw->SpeakerOn) */ { dspWrite(pHw, DSP_SPEAKER_OFF); pHw->SpeakerOn = FALSE; // // Now wait until it's OK again (up to 225 ms) // for (i = 0; i < 6; i++) { if (!(INPORT(pHw, DATA_STATUS_PORT) & 0x80)) { break; } SoundDelay(38); } dprintf4(("Waited %d ms for speaker to go off", i * 38)); } return TRUE; } BOOLEAN dspStartAutoDMA( PWAVE_INFO WaveInfo ) /*++ Routine Description: This routine begins the output of a new set of dma transfers. It sets up the dsp sample rate and then programs the dsp to start making dma requests. This routine completes the action started by sndProgramOutputDMA Arguments: pHw - Pointer to global device data Return Value: TRUE if operation is sucessful, FALSE otherwise --*/ { ULONG SampleRate; PSOUND_HARDWARE pHw; ULONG Size; ASSERT(WaveInfo->Key == WAVE_INFO_KEY); pHw = (PSOUND_HARDWARE)WaveInfo->HwContext; Size = WaveInfo->DoubleBuffer.BufferSize / (WaveInfo->BitsPerSample / 8); if (SB16(pHw)) { // // Write command, mode, length low, length high // dspWrite(pHw, (UCHAR)(pHw->SixteenBit ? (WaveInfo->Direction ? DSP_START_DAC16 : DSP_START_ADC16) : (WaveInfo->Direction ? DSP_START_DAC8 : DSP_START_ADC8))); // // Mode: // 0x10 means 16-bit (could use bitspersample & 0x10!) // 0x20 means stereo // dspWrite(pHw, (UCHAR) ((WaveInfo->Channels > 1 ? 0x20 : 0) | WaveInfo->BitsPerSample & 0x10)); dspWrite(pHw, (UCHAR)((Size/2 - 1) & 0x00FF)); dspWrite(pHw, (UCHAR)(((Size/2 - 1) >> 8) & 0x00FF)); } else { // // Program the DSP to start the transfer by sending the // block size command followed by the low // byte and then the high byte of the block size - 1. // Then send the start auto-init command. // Note that the block size is half the dma buffer size. // dspWrite(pHw, DSP_SET_BLOCK_SIZE); dspWrite(pHw, (UCHAR)((Size/2 - 1) & 0x00FF)); dspWrite(pHw, (UCHAR)(((Size/2 - 1) >> 8) & 0x00FF)); if (!WaveInfo->Direction) { dspWrite(pHw, (UCHAR)(pHw->HighSpeed ? DSP_READ_HS : DSP_READ_AUTO)); } else { dspWrite(pHw, (UCHAR)(pHw->HighSpeed ? DSP_WRITE_HS : DSP_WRITE_AUTO)); } dprintf3(("DMA started")); } dprintf3(("DMA started")); return TRUE; } BOOLEAN dspStartNonAutoDMA( PSOUND_HARDWARE pHw, ULONG Size, BOOLEAN Direction ) /*++ Routine Description: This routine begins the output of a new set of dma transfers. It sets up the dsp sample rate and then programs the dsp to start making dma requests. This routine completes the action started by sndProgramOutputDMA. Soundblaster 1 only Arguments: pHw - Pointer to global device data Return Value: TRUE if operation is sucessful, FALSE otherwise --*/ { ULONG SampleRate; // // Program the DSP to start the transfer by sending the // Read/Write command followed by the low // byte and then the high byte of the block size - 1. // Note that the block size is half the dma buffer size. // if (!Direction) { dspWrite(pHw, DSP_READ); } else { dspWrite(pHw, DSP_WRITE); } dspWrite(pHw, (UCHAR)((Size/2 - 1) & 0x00FF)); dspWrite(pHw, (UCHAR)(((Size/2 - 1) >> 8) & 0x00FF)); dprintf3(("DMA started")); // // Initialize half we're doing next // pHw->Half = UpperHalf; // // Return the power fail status // return TRUE; } BOOLEAN dspIMixerReadWrite( PVOID Context ) { PGLOBAL_DEVICE_INFO pGDI; pGDI = Context; OUTPORT(&pGDI->Hw, MIX_ADDR_PORT, pGDI->Hw.MixerReg); if (pGDI->Hw.MixerWrite) { OUTPORT(&pGDI->Hw, MIX_DATA_PORT, pGDI->Hw.MixerValue); } else { pGDI->Hw.MixerValue = INPORT(&pGDI->Hw, MIX_DATA_PORT); } return TRUE; } VOID dspWriteMixer( PGLOBAL_DEVICE_INFO pGDI, UCHAR MixerReg, UCHAR Value ) /*++ Routine Description: Write a value to a mixer register Arguments: pGDI - pointer to device instance Return Value: none --*/ { // // The big problem here is to synch with the ISR // so for now use the big stick and synch directly // // NOTE - we ASSUME all callers own the mixer mutex // so we can share a parameter passing area in the GDI // #if 0 // KeReadStateMutex ends up pointing to KeReadStateMutant which is not // defined in the context of this file. Remove the ASSERT. ASSERT(KeReadStateMutex(&pGDI->DeviceMutex) != 1); #endif pGDI->Hw.MixerReg = MixerReg; pGDI->Hw.MixerValue = Value; pGDI->Hw.MixerWrite = TRUE; KeSynchronizeExecution(pGDI->WaveInfo.Interrupt, dspIMixerReadWrite, (PVOID)pGDI); } UCHAR dspReadMixer( PGLOBAL_DEVICE_INFO pGDI, UCHAR MixerReg ) /*++ Routine Description: Read a value from a mixer register Arguments: pGDI - pointer to device instance Return Value: none --*/ { // // The big problem here is to synch with the ISR // so for now use the big stick and synch directly // // NOTE - we ASSUME all callers own the mixer mutex // so we can share a parameter passing area in the GDI // #if 0 // KeReadStateMutex ends up pointing to KeReadStateMutant which is not // defined in the context of this file. Remove the ASSERT. ASSERT(KeReadStateMutex(&pGDI->DeviceMutex) != 1); #endif pGDI->Hw.MixerReg = MixerReg; pGDI->Hw.MixerWrite = FALSE; KeSynchronizeExecution(pGDI->WaveInfo.Interrupt, dspIMixerReadWrite, (PVOID)pGDI); return pGDI->Hw.MixerValue; } BOOLEAN HwSetupDMA( IN PWAVE_INFO WaveInfo ) /*++ Routine Description : Start the DMA on the device according to the device parameters Arguments : WaveInfo - Wave parameters Return Value : None --*/ { PSOUND_HARDWARE pHw; pHw = WaveInfo->HwContext; // // Turn the speaker off for input // if (!WaveInfo->Direction) { dspSpeakerOff(pHw); } else { // // This would not normally be necessary but when the DMA // gets locked out by the SCSI horrible things happen so // we turn on the speaker here. Normally the flag will // stop us actually turning it on // dspSpeakerOn(pHw); } // // Do different things depending on the type of card // Sound blaster 1 cannot use auto-init DMA whereas all // the others can // if (SB1(pHw)) { dspStartNonAutoDMA(pHw, WaveInfo->DoubleBuffer.BufferSize, WaveInfo->Direction); } else { dspStartAutoDMA(WaveInfo); } return TRUE; } BOOLEAN HwWaitForTxComplete( IN PGLOBAL_DEVICE_INFO pGDI ) /*++ Routine Description : Wait until the device stops requesting so we don't shut off the DMA while it's still trying to request. Arguments : pGDI - Global device instance info Return Value : None --*/ { PUCHAR DMAStatusPort; ULONG PortNumber; UCHAR Mask; BOOLEAN Rc; ULONG MemType; int i; // // HACK HACK // if (pGDI->Hw.SixteenBit && pGDI->DmaChannel16 != 0xFFFFFFFF) { PortNumber = 0xD0; Mask = (UCHAR)(1 << pGDI->DmaChannel16); } else { PortNumber = 0x08; Mask = (UCHAR)(0x10 << pGDI->DmaChannel); } DMAStatusPort = SoundMapPortAddress( pGDI->BusType, pGDI->BusNumber, PortNumber, 1, &MemType); dprintf3(("Wait for Tx complete, status = %2.2X", (ULONG)READ_PORT_UCHAR(DMAStatusPort))); // // Poll the 'request' bit // for (i = 0, Rc = FALSE; i < 4000; i++) { if ((READ_PORT_UCHAR(DMAStatusPort) & Mask) == 0) { Rc = TRUE; break; } KeStallExecutionProcessor(10); } if (!Rc) { dprintf1(("HwWaitForTxComplete failed! DMA Status = %2.2X", (ULONG)READ_PORT_UCHAR(DMAStatusPort))); } // // Unmap the port if necessary // if (MemType == 0) { MmUnmapIoSpace(DMAStatusPort, 1); } return Rc; } BOOLEAN dspCancelInterrupt( PVOID Context ) /*++ Routine Description : Make sure we don't get any more interrupts and synth with the ISR Arguments : Context - pointer to global instance data Return Value : None --*/ { PGLOBAL_DEVICE_INFO pGDI; pGDI = Context; ASSERT(pGDI->Key == GDI_KEY); if (SB16(&pGDI->Hw) && pGDI->WaveInfo.Channels > 1) { INPORT(&pGDI->Hw, DMA_16_ACK_PORT); } else { INPORT(&pGDI->Hw, DATA_AVAIL_PORT); } return TRUE; } BOOLEAN HwStopDMA( IN PWAVE_INFO WaveInfo ) /*++ Routine Description : Stop the DMA on the device according to the device parameters Arguments : WaveInfo - Wave parameters Return Value : None --*/ { PSOUND_HARDWARE pHw; BOOLEAN Rc; PGLOBAL_DEVICE_INFO pGDI; pHw = (PSOUND_HARDWARE)WaveInfo->HwContext; pGDI = (PGLOBAL_DEVICE_INFO)CONTAINING_RECORD(pHw, GLOBAL_DEVICE_INFO, Hw); if (pHw->HighSpeed) { dspReset(pHw); dspSpeakerOff(pHw); Rc = TRUE; } else { Rc = (BOOLEAN) ((!SB16(pHw) || dspWrite(pHw, (UCHAR)(pHw->SixteenBit ? DSP_HALT_DMA16 : DSP_HALT_DMA))) && dspWrite(pHw, (UCHAR)(pHw->SixteenBit ? DSP_PAUSE_DMA16 : DSP_PAUSE_DMA)) && HwWaitForTxComplete(pGDI)); if (!Rc) { // // Resetting then setting the speaker on seems to be the only // way to recover! // dspReset(pHw); // // The speaker is off after reset. The next time we play // something we'll call dspSpeakerOn which will turn it on. // } } if (!WaveInfo->Direction) { dspSpeakerOn(pHw); } // // Synch with the ISR and cancel any hanging interrupts // (they might already dispatched on another processor!). // KeSynchronizeExecution(WaveInfo->Interrupt, dspCancelInterrupt, pGDI); return Rc; } BOOLEAN HwSetWaveFormat( IN PWAVE_INFO WaveInfo ) /*++ Routine Description : Set device parameters for wave input/output Arguments : WaveInfo - Wave parameters Return Value : None --*/ { PSOUND_HARDWARE pHw; UCHAR Format; PGLOBAL_DEVICE_INFO pGDI; pHw = WaveInfo->HwContext; pGDI = CONTAINING_RECORD(pHw, GLOBAL_DEVICE_INFO, Hw); // // This routine is called for 2 reasons // 1. To set the format // 2. In case anything else is required before DMA is enabled // // If FormatChanged is FALSE then no change of format has occurred. // if (WaveInfo->FormatChanged || pHw->SetFormat) { UCHAR Channels; UCHAR Left; UCHAR Right; pHw->SetFormat = FALSE; // // Do SB16 // if (SB16(pHw)) { pHw->SixteenBit = (BOOLEAN)(WaveInfo->BitsPerSample == 16); dspWrite(pHw, (UCHAR)(WaveInfo->Direction ? DSP_SET_DAC_RATE : DSP_SET_ADC_RATE)); // // High byte first, the low byte // dspWrite(pHw, (UCHAR)(0xFF & (WaveInfo->SamplesPerSec >> 8))); dspWrite(pHw, (UCHAR)(0xFF & WaveInfo->SamplesPerSec)); // // Choose the right DMA channel // if (WaveInfo->BitsPerSample == 16 && pGDI->DmaChannel16 != 0xFFFFFFFF) { WaveInfo->DMABuf.AdapterObject[0] = pGDI->Adapter[1]; } else { WaveInfo->DMABuf.AdapterObject[0] = pGDI->Adapter[0]; } // // Set up stereo/mono stuff for routing input // Channels = dspReadMixer(pGDI, (UCHAR)0x3d) | dspReadMixer(pGDI, (UCHAR)0x3e); if (WaveInfo->Channels > 1) { Left = Channels & 0x55; Right = Channels & 0x2b; } else { Left = Channels; Right = 0; } dspWriteMixer(pGDI, (UCHAR)0x3d, Left); dspWriteMixer(pGDI, (UCHAR)0x3e, Right); } else { ULONG TimeFactor; UCHAR TimeConstant; int i; // // the card only does 4kHz up // ASSERT(WaveInfo->SamplesPerSec >= 4000); // // Compute the timing factor as (65536 - 256000000 / rate) >> 8 // For 4kHz this is 6, for 23kHz it is 212. // TimeFactor = 65536 - (256000000 / (WaveInfo->SamplesPerSec * WaveInfo->Channels)); TimeConstant = (UCHAR)(0xFF & (TimeFactor >> 8)); // // Do this twice for the Pro - some obscure bug with // high speed mode. // for (i = 0; i < 2; i++) { dspWrite(pHw, DSP_SET_SAMPLE_RATE); KeStallExecutionProcessor(10); dspWrite(pHw, (UCHAR) TimeConstant); KeStallExecutionProcessor(10); } // // Remember whether we're going to need 'high speed' // mode. // pHw->HighSpeed = HwHighSpeed(pHw, WaveInfo->Channels, WaveInfo->SamplesPerSec, WaveInfo->Direction); // // For the PRO select stereo if requested // if (SBPRO(pHw)) { if (WaveInfo->Direction) { UCHAR OutputSetting; // // Set the output setting register // OutputSetting = dspReadMixer(pGDI, OUTPUT_SETTING_REG); OutputSetting &= ~0x22; if (WaveInfo->Channels > 1) { OutputSetting |= 0x02; } // // Set output filter // Turn off for high rates or Stereo // (see the Creative DDK). if (WaveInfo->SamplesPerSec > 23000 || WaveInfo->Channels > 1) { OutputSetting |= 0x20; } dspWriteMixer(pGDI, OUTPUT_SETTING_REG, OutputSetting); // // Now try and switch the channels(!) by writing 1 // byte of sound output (we don't want to do what the // book is and use single cycle mode because of the // hassle of setting up the DMA. // if (WaveInfo->Channels > 1) { dspWrite(pHw, 0x10); dspWrite(pHw, 0x80); } } else { UCHAR InputSetting; InputSetting = dspReadMixer(pGDI, INPUT_SETTING_REG); // // Set input filter // InputSetting &= ~0x28; if (WaveInfo->SamplesPerSec > 36000 || WaveInfo->Channels > 1) { InputSetting |= 0x20; } else { if (WaveInfo->SamplesPerSec >= 18000) { InputSetting |= 0x08; } } dspWriteMixer(pGDI, INPUT_SETTING_REG, InputSetting); dspWrite(pHw, (UCHAR)(WaveInfo->Channels == 1 ? DSP_INPUT_MONO : DSP_INPUT_STEREO)); } } } } // // Do any stuff we need to do before DMA is actually turned on. // This is only necessary for 'high speed' transfers // // BUG BUG Look at the book for funny stuff. return TRUE; } BOOLEAN HwStartMidiIn( IN PMIDI_INFO MidiInfo ) /*++ Routine Description : Start midi recording Arguments : MidiInfo - Midi parameters Return Value : None --*/ { PSOUND_HARDWARE pHw; pHw = MidiInfo->HwContext; // // Write start midi input to device // if (SB1(pHw)) { return dspWrite(pHw, DSP_MIDI_READ); } else { return dspWrite(pHw, DSP_MIDI_READ_UART); } } BOOLEAN HwStopMidiIn( IN PMIDI_INFO MidiInfo ) /*++ Routine Description : Stop midi recording Arguments : MidiInfo - Midi parameters Return Value : None --*/ { PSOUND_HARDWARE pHw; pHw = MidiInfo->HwContext; if (SB1(pHw)) { // // Start = stop in this case // HwStartMidiIn(MidiInfo); } else { // // The only way to stop is to reset the DSP // Note that this is called only by the app so // output cannot be going on at this time (because we // have the device mutex). // dspReset(pHw); dspSpeakerOn(pHw); } return TRUE; } BOOLEAN HwMidiRead( IN PMIDI_INFO MidiInfo, OUT PUCHAR Byte ) /*++ Routine Description : Read a midi byte from the recording Arguments : MidiInfo - Midi parameters Return Value : None --*/ { PSOUND_HARDWARE pHw; pHw = MidiInfo->HwContext; if (INPORT(pHw, DATA_AVAIL_PORT) & 0x80) { *Byte = INPORT(pHw, DATA_PORT); return TRUE; } else { return FALSE; } } VOID HwMidiOut( IN PMIDI_INFO MidiInfo, IN PUCHAR Bytes, IN int Count ) /*++ Routine Description : Write a midi byte to the output Arguments : MidiInfo - Midi parameters Return Value : None --*/ { PSOUND_HARDWARE pHw; PGLOBAL_DEVICE_INFO pGDI; int i, j; pHw = MidiInfo->HwContext; pGDI = CONTAINING_RECORD(pHw, GLOBAL_DEVICE_INFO, Hw); // // Loop sending data to device. Synchronize with wave and midi input // using the DeviceMutex for everything except the Dpc // routine for which we use the wave output spin lock // while (Count > 0) { // // Synchronize with everything except Dpc routines // (Note we don't use this for the whole of the output // because we don't want wave output to be held off // while we output thousands of Midi bytes, but we // then need to synchronize access to the midi output // which we do with the MidiMutex // KeWaitForSingleObject(&pGDI->DeviceMutex, Executive, KernelMode, FALSE, // Not alertable NULL); for (i = 0; i < 20; i++) { // // If input is active we don't need to specify MIDI write // for version 2 or later (can't be overlapped anyway for // version 1 so the extra test is unnecessary). // if (MidiInfo->fMidiInStarted) { ASSERT(!SB1(pHw)); dspWrite(pHw, Bytes[0]); // // Apparently we have to wait 400 us in this case // KeStallExecutionProcessor(400); } else { UCHAR Byte = Bytes[0]; // Don't take an exception while // we hold the spin lock! // // We don't want to hold on to the spin lock for too // long and since we can only send out 4 bytes per ms // we are rather slow. Hence wait until the device // is ready before entering the spin lock // { int j; for (j = 0; j < 250; j++) { if (INPORT(pHw, DATA_STATUS_PORT) & 0x80) { KeStallExecutionProcessor(1); } else { break; } } } // // Synch with any Dpc routines. This requires that // any write sequences done in a Dpc routine also // hold the spin lock over all the writes. // HwEnter(pHw); dspWriteNoLock(pHw, DSP_MIDI_WRITE); dspWriteNoLock(pHw, Byte); HwLeave(pHw); } // // Move on to next byte // Bytes++; if (--Count == 0) { break; } } KeReleaseMutex(&pGDI->DeviceMutex, FALSE); } } BOOLEAN MPU401StartInput( PVOID Context ) { PSOUND_HARDWARE pHw; pHw = Context; if (pHw->MPU401.InputActive) { return FALSE; } else { // // Clear out our hw input buffer // pHw->MPU401.ReadPosition = 0; pHw->MPU401.WritePosition = 0; // // Input will start as soon as we set InputActive // pHw->MPU401.InputActive = TRUE; return TRUE; } } BOOLEAN MPU401StartMidiIn( IN PMIDI_INFO MidiInfo ) /*++ Routine Description : Start midi recording Arguments : MidiInfo - Midi parameters Return Value : None --*/ { PSOUND_HARDWARE pHw; PGLOBAL_DEVICE_INFO pGDI; pHw = MidiInfo->HwContext; pGDI = CONTAINING_RECORD(pHw, GLOBAL_DEVICE_INFO, Hw); return KeSynchronizeExecution(pGDI->WaveInfo.Interrupt, MPU401StartInput, pHw); } BOOLEAN MPU401StopMidiIn( IN PMIDI_INFO MidiInfo ) /*++ Routine Description : Stop midi recording Arguments : MidiInfo - Midi parameters Return Value : None --*/ { PSOUND_HARDWARE pHw; pHw = MidiInfo->HwContext; if (pHw->MPU401.InputActive) { pHw->MPU401.InputActive = FALSE; return TRUE; } else { return FALSE; } } BOOLEAN MPU401MidiRead( IN PMIDI_INFO MidiInfo, OUT PUCHAR Byte ) /*++ Routine Description : Read a midi byte from the recording Arguments : MidiInfo - Midi parameters Return Value : None --*/ { PSOUND_HARDWARE pHw; int ReadPosition; pHw = MidiInfo->HwContext; // // We rely on volatile quantities to synchonize with the ISR. // This means we may miss the odd byte the the ISR is queueing but // if we do that means we are running so the Dpc can (and therefore // will) be queue by the ISR. // ReadPosition = pHw->MPU401.ReadPosition; if (ReadPosition != pHw->MPU401.WritePosition) { *Byte = pHw->MPU401.MidiData[ReadPosition]; ReadPosition++; if (ReadPosition == sizeof(pHw->MPU401.MidiData)) { ReadPosition = 0; } pHw->MPU401.ReadPosition = ReadPosition; return TRUE; } return FALSE; } VOID MPU401MidiOut( IN PMIDI_INFO MidiInfo, IN PUCHAR Bytes, IN int Count ) /*++ Routine Description : Write a midi byte to the output Arguments : MidiInfo - Midi parameters Return Value : None --*/ { PSOUND_HARDWARE pHw; pHw = MidiInfo->HwContext; // // Loop sending data to device. // for (;Count > 0; Count--, Bytes++) { MPU401Write(pHw->MPU401.PortBase, FALSE, *Bytes); } } BOOLEAN MPU401Write( PUCHAR MPU401PortBase, BOOLEAN Command, UCHAR Byte) { int i; PUCHAR PortAddress; if (Command) { PortAddress = MPU401PortBase + MPU401_REG_COMMAND; } else { PortAddress = MPU401PortBase + MPU401_REG_DATA; } // // Wait for receive ready // for (i = 0; ; i++) { if (!(READ_PORT_UCHAR(MPU401PortBase + MPU401_REG_STATUS) & MPU401_DRR)) { WRITE_PORT_UCHAR(PortAddress, Byte); return TRUE; } if (i > 10000) { dprintf1(("MPU401 timeout out waiting for ready")); return FALSE; } KeStallExecutionProcessor(1); } } VOID HwInitialize( IN OUT PGLOBAL_DEVICE_INFO pGDI ) /*++ Routine Description : Write hardware routine addresses into global device data Arguments : pGDI - global data Return Value : None --*/ { PWAVE_INFO WaveInfo; PMIDI_INFO MidiInfo; PSOUND_HARDWARE pHw; pHw = &pGDI->Hw; WaveInfo = &pGDI->WaveInfo; MidiInfo = &pGDI->MidiInfo; ASSERT(pHw->Key == HARDWARE_KEY); // // Install Wave and Midi routine addresses // WaveInfo->HwContext = pHw; WaveInfo->HwSetupDMA = HwSetupDMA; WaveInfo->HwStopDMA = HwStopDMA; WaveInfo->HwSetWaveFormat = HwSetWaveFormat; MidiInfo->HwContext = pHw; if (pHw->MPU401.PortBase == NULL) { MidiInfo->HwStartMidiIn = HwStartMidiIn; MidiInfo->HwStopMidiIn = HwStopMidiIn; MidiInfo->HwMidiRead = HwMidiRead; MidiInfo->HwMidiOut = HwMidiOut; } else { MidiInfo->HwStartMidiIn = MPU401StartMidiIn; MidiInfo->HwStopMidiIn = MPU401StopMidiIn; MidiInfo->HwMidiRead = MPU401MidiRead; MidiInfo->HwMidiOut = MPU401MidiOut; } }