mirror of https://github.com/lianthony/NT4.0
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1625 lines
32 KiB
1625 lines
32 KiB
/*++
|
|
|
|
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;
|
|
}
|
|
}
|
|
|