1626 lines
32 KiB
C
1626 lines
32 KiB
C
/*++
|
||
|
||
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;
|
||
}
|
||
}
|
||
|