Windows NT 4.0 source code leak
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.
 
 
 
 
 
 

2760 lines
78 KiB

/*****************************************************************************
Copyright (c) 1993 Media Vision Inc. All Rights Reserved
Module Name:
hardware.c
Abstract:
This module contains code for communicating with the ProAudio Hardware.
Author:
Nigel Thompson (NigelT) 7-March-1991
Environment:
Kernel mode
Revision History:
Robin Speed (RobinSp) 29-Jan-1992
Add MIDI, support for soundblaster 1,
EPA 12-29-92
Added support for the PAS 16
*****************************************************************************/
#include "sound.h"
#ifdef ALLOC_PRAGMA
#pragma alloc_text(INIT, HwInitialize)
#pragma alloc_text(INIT, HwInitPAS)
#pragma alloc_text(INIT, InitPAS16)
#pragma alloc_text(INIT, SaveMV101Registers)
#pragma alloc_text(PAGE, RestoreMV101Registers)
#pragma alloc_text(PAGE, HwVUMeter)
#endif
//
// DMA Translation Table
// this translate table converts
// desired DMA channel to the pointer value
// required by IO_PORT_CONFIG_2 ($F389)
//
CONST BYTE DMAxlate[] =
{
4,
1,
2,
3,
0,
5,
6,
7
};
//
// Local Function Prototypes
//
VOID EnablePASMidi( IN OUT PGLOBAL_DEVICE_INFO pGDI);
/*****************************************************************************
* C O D E
*****************************************************************************/
/****************************************************************************
HwSetSpeaker()
Routine Description :
Set the speaker logically 'on' or 'off' to avoid feedback a la sound
blaster
Arguments :
pGDI - pointer to global device data
On - Set to on is this is TRUE
Return Value :
None
****************************************************************************/
VOID HwSetSpeaker(PGLOBAL_DEVICE_INFO pGDI, BOOLEAN On)
{
//
// Don't touch the hardware if the state hasn't changed
//
if (On == pGDI->Hw.SpeakerOn) {
return;
}
//
// Set new state so that when we set the microphone volume the new
// state is reflected
//
pGDI->Hw.SpeakerOn = On;
if (!On) {
//
// We turn off the wave output. It will be turned on again if it
// plays (see mixer.c!SoundWaveoutLineChanged).
//
UpdateInput(pGDI,
IN_PCM,
OUT_AMPLIFIER,
0,
0);
};
}
/****************************************************************************
HwInitialize()
Routine Description :
Write hardware routine addresses into global device data
Arguments :
pGDI - global data
Return Value :
None
****************************************************************************/
VOID HwInitialize( IN OUT PGLOBAL_DEVICE_INFO pGDI )
{
/***** Local Variables *****/
PWAVE_INFO WaveInfo;
PMIDI_INFO MidiInfo;
PSOUND_HARDWARE pHw;
/***** Start *****/
pHw = &pGDI->Hw;
WaveInfo = &pGDI->WaveInfo;
MidiInfo = &pGDI->MidiInfo;
pHw->Key = HARDWARE_KEY;
pHw->SpeakerOn = TRUE;
KeInitializeSpinLock(&pHw->HwSpinLock);
//
// Install Wave and Midi routine addresses
//
dprintf3(("HwInitialize(): ProAudio Setup"));
WaveInfo->HwContext = pHw;
WaveInfo->HwSetupDMA = PASHwSetupDMA;
WaveInfo->HwStopDMA = PASHwStopDMA;
WaveInfo->HwSetWaveFormat = PASHwSetWaveFormat;
MidiInfo->HwContext = pHw;
MidiInfo->HwStartMidiIn = PASHwStartMidiIn;
MidiInfo->HwStopMidiIn = PASHwStopMidiIn;
MidiInfo->HwMidiRead = PASHwMidiRead;
MidiInfo->HwMidiOut = PASHwMidiOut;
} // End HwInitialize()
BOOLEAN
HwWaitForTxComplete(
IN PWAVE_INFO WaveInfo
)
/*++
Routine Description :
Wait until the device stops requesting so we don't shut off the DMA
while it's still trying to request.
Arguments :
WaveInfo - Wave parameters
Return Value :
None
--*/
{
ULONG ulCount ;
if (ulCount = HalReadDmaCounter( WaveInfo->DMABuf.AdapterObject[0] ))
{
ULONG i, ulLastCount = ulCount ;
for (i = 0;
(i < 4000) &&
(ulLastCount !=
(ulCount = HalReadDmaCounter( WaveInfo->DMABuf.AdapterObject[0] )));
i++)
{
ulLastCount = ulCount;
KeStallExecutionProcessor(10);
}
return (i < 4000);
}
else
return TRUE ;
}
/*****************************************************************************
* *
* P A S 1 6 S U P P O R T *
* *
*****************************************************************************/
/*****************************************************************************
HwInitPAS()
Routine Description :
Initialize PAS 16 Hardware
Arguments :
pGDI - global data
Return Value :
TRUE
*****************************************************************************/
VOID HwInitPAS( IN OUT PGLOBAL_DEVICE_INFO pGDI )
{
/***** Local Variables *****/
/***** Start *****/
dprintf2(("HwInitPAS(): Start " ));
//
// Initialize the PAS 16
//
if ( pGDI->PASInfo.Caps.CapsBits.DAC16 )
{
InitPAS16( pGDI );
}
//
// Initialize the PAS Registers
//
InitPASRegs( pGDI );
//
// Initialize the Midi registers
//
EnablePASMidi( pGDI );
//
// Setup the PCM engine
//
InitPCM( pGDI );
} // End HwInitPAS()
/*****************************************************************************
* *
* Pro Audio Spectrum *
* Hardware Initialization Routines *
* *
*****************************************************************************/
/*****************************************************************************
SaveMV101Registers()
Routine Description :
Save all important MV101 registers to be restored at unload time
Arguments :
IN OUT PGLOBAL_DEVICE_INFO pGDI
Return Value :
VOID
*****************************************************************************/
VOID SaveMV101Registers( IN OUT PGLOBAL_DEVICE_INFO pGDI )
{
/***** Local Variables *****/
/***** Start *****/
if ( pGDI->PASInfo.Caps.CapsBits.Slot16 )
{
dprintf2(("SaveMV101Registers(): Saving MV101 registers " ));
pGDI->MV101Regs.SaveReg_B88 = PASX_IN( ((FOUNDINFO *)(&pGDI->PASInfo)),
SERIAL_MIXER );
pGDI->MV101Regs.SaveReg_F8A = PASX_IN( ((FOUNDINFO *)(&pGDI->PASInfo)),
PCM_CONTROL );
pGDI->MV101Regs.SaveReg_8388 = PASX_IN( ((FOUNDINFO *)(&pGDI->PASInfo)),
SYSTEM_CONFIG_1 );
pGDI->MV101Regs.SaveReg_8389 = PASX_IN( ((FOUNDINFO *)(&pGDI->PASInfo)),
SYSTEM_CONFIG_2 );
pGDI->MV101Regs.SaveReg_838A = PASX_IN( ((FOUNDINFO *)(&pGDI->PASInfo)),
SYSTEM_CONFIG_3 );
pGDI->MV101Regs.SaveReg_BF8A = PASX_IN( ((FOUNDINFO *)(&pGDI->PASInfo)),
PRESCALE_DIVIDER );
pGDI->MV101Regs.SaveReg_F388 = PASX_IN( ((FOUNDINFO *)(&pGDI->PASInfo)),
IO_PORT_CONFIG_1 );
pGDI->MV101Regs.SaveReg_F389 = PASX_IN( ((FOUNDINFO *)(&pGDI->PASInfo)),
IO_PORT_CONFIG_2 );
pGDI->MV101Regs.SaveReg_F38A = PASX_IN( ((FOUNDINFO *)(&pGDI->PASInfo)),
IO_PORT_CONFIG_3 );
pGDI->MV101Regs.SaveReg_F788 = PASX_IN( ((FOUNDINFO *)(&pGDI->PASInfo)),
COMPATIBLE_REGISTER_ENABLE );
// NOT USED, Rev D doesn't allow readback
// pGDI->MV101Regs.SaveReg_F789 = PASX_IN( ((FOUNDINFO *)(&pGDI->PASInfo)),
// EMULATION_ADDRESS_POINTER );
// Set the Saved flag
pGDI->MV101Regs.fSavedFlag = TRUE;
//
// Debug Info
//
dprintf4((" SaveMV101Registers(): 0xB88 - SERIAL_MIXER = %XH",
pGDI->MV101Regs.SaveReg_B88 ));
dprintf4((" SaveMV101Registers(): 0xF8A - PCM_CONTROL = %XH",
pGDI->MV101Regs.SaveReg_F8A ));
dprintf4((" SaveMV101Registers(): 0x8388 - SYSTEM_CONFIG_1 = %XH",
pGDI->MV101Regs.SaveReg_8388 ));
dprintf4((" SaveMV101Registers(): 0x8389 - SYSTEM_CONFIG_2 = %XH",
pGDI->MV101Regs.SaveReg_8389 ));
dprintf4((" SaveMV101Registers(): 0x838A - SYSTEM_CONFIG_3 = %XH",
pGDI->MV101Regs.SaveReg_838A ));
dprintf4((" SaveMV101Registers(): 0xBF8A - PRESCALE_DIVIDER = %XH",
pGDI->MV101Regs.SaveReg_BF8A ));
dprintf4((" SaveMV101Registers(): 0xF388 - IO_PORT_CONFIG_1 = %XH",
pGDI->MV101Regs.SaveReg_F388 ));
dprintf4((" SaveMV101Registers(): 0xF389 - IO_PORT_CONFIG_2 = %XH",
pGDI->MV101Regs.SaveReg_F389 ));
dprintf4((" SaveMV101Registers(): 0xF38A - IO_PORT_CONFIG_3 = %XH",
pGDI->MV101Regs.SaveReg_F38A ));
dprintf4((" SaveMV101Registers(): 0xF788 - COMPATIBLE_REGISTER_ENABLE = %XH",
pGDI->MV101Regs.SaveReg_F788 ));
} // End IF (pGDI->PASInfo.Caps.CapsBits.Slot16)
else
{
pGDI->MV101Regs.fSavedFlag = FALSE;
}
} // End SaveMV101Registers()
/*****************************************************************************
RestoreMV101Registers()
Routine Description :
Restore all important MV101 registers at unload time
Arguments :
IN OUT PGLOBAL_DEVICE_INFO pGDI
Return Value :
VOID
*****************************************************************************/
VOID RestoreMV101Registers( IN OUT PGLOBAL_DEVICE_INFO pGDI )
{
/***** Local Variables *****/
/***** Start *****/
// Were the registers saved?
if ( pGDI->MV101Regs.fSavedFlag )
{
dprintf2(("RestoreMV101Registers(): Restoring MV101 registers " ));
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
SERIAL_MIXER,
pGDI->MV101Regs.SaveReg_B88 );
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
PCM_CONTROL,
pGDI->MV101Regs.SaveReg_F8A );
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
SYSTEM_CONFIG_1,
pGDI->MV101Regs.SaveReg_8388 );
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
SYSTEM_CONFIG_2,
pGDI->MV101Regs.SaveReg_8389 );
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
SYSTEM_CONFIG_3,
pGDI->MV101Regs.SaveReg_838A );
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
PRESCALE_DIVIDER,
pGDI->MV101Regs.SaveReg_BF8A );
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
IO_PORT_CONFIG_1,
pGDI->MV101Regs.SaveReg_F388 );
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
IO_PORT_CONFIG_2,
pGDI->MV101Regs.SaveReg_F389 );
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
IO_PORT_CONFIG_3,
pGDI->MV101Regs.SaveReg_F38A );
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
COMPATIBLE_REGISTER_ENABLE,
pGDI->MV101Regs.SaveReg_F788 );
// NOT USED, Rev D doesn't allow readback
// PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
// EMULATION_ADDRESS_POINTER,
// pGDI->MV101Regs.SaveReg_F789 );
} // End IF (pGDI->MV101Regs.fSavedFlag)
} // End RestoreMV101Registers()
/*****************************************************************************
InitPAS16()
Routine Description :
Initialize the PAS 16
Arguments :
IN OUT PGLOBAL_DEVICE_INFO pGDI
Return Value :
VOID
*****************************************************************************/
VOID InitPAS16( IN OUT PGLOBAL_DEVICE_INFO pGDI )
{
/***** Local Variables *****/
BYTE bSysConfigReg;
BYTE bFeatureEnableReg;
/***** Start *****/
dprintf2(("InitPAS16(): Start " ));
//
// Turn OFF Interrupts!!
//
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
INTERRUPT_CTRL_REG,
0 );
dprintf3((" InitPAS16(): 0x0B8B - INTERRUPT_CTRL_REG set to 0 " ));
//
// Disable original PAS emulation
//
// Get the current value
bSysConfigReg = PASX_IN( ((FOUNDINFO *)(&pGDI->PASInfo)),
SYSTEM_CONFIG_1 );
dprintf4((" InitPAS16(): 0x8388 - Sys Config 1 was set to %XH ", bSysConfigReg ));
bSysConfigReg = bSysConfigReg | DISABLE_ORG_PAS_EMULATION;
// Set Sys Config 1
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
SYSTEM_CONFIG_1,
bSysConfigReg );
KeStallExecutionProcessor(10); // pause - wait 10us
dprintf3((" InitPAS16(): 0x8388 - Sys Config 1 set to %XH", bSysConfigReg ));
//
// Set Sys Config 2
//
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
SYSTEM_CONFIG_2,
0 );
dprintf3((" InitPAS16(): 0x8389 - Sys Config 2 set to %XH", 0 ));
//
// Set Sys Config 3
//
// Check for CDPC
if ( pGDI->PASInfo.Caps.CapsBits.CDPC )
{
// Yes, we have a CDPC
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
SYSTEM_CONFIG_3,
0 );
dprintf3((" InitPAS16(): 0x838A - Sys Config 3 for CDPC set to %XH", 0 ));
}
else
{
// NO, NOT a CDPC
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
SYSTEM_CONFIG_3,
INIT_SYS_CONFIG3_VALUE );
dprintf3((" InitPAS16(): 0x838A - Sys Config 3 set to %XH", INIT_SYS_CONFIG3_VALUE ));
}
//
// Setup the Feature Enable Register
//
// Get the current value
bFeatureEnableReg = PASX_IN( ((FOUNDINFO *)(&pGDI->PASInfo)),
FEATURE_ENABLE );
// Enable PCM
bFeatureEnableReg = bFeatureEnableReg | 1;
// Disable
bFeatureEnableReg = bFeatureEnableReg & DISABLE_PCM;
// Send the value
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
FEATURE_ENABLE,
bFeatureEnableReg );
dprintf3((" InitPAS16(): 0x0B88 - Feature Enable register 1 set to %XH", bFeatureEnableReg ));
} // End InitPAS16()
/*****************************************************************************
InitPASRegs()
Routine Description :
Initialize PAS Hardware Registers
Arguments :
pGDI - global data
Return Value :
VOID
*****************************************************************************/
VOID InitPASRegs( IN OUT PGLOBAL_DEVICE_INFO pGDI )
{
/***** Local Variables *****/
BYTE bXlatInterruptNumber;
BYTE bDmaChannel;
BYTE bDMAxlate;
BYTE bIOPortConfig3;
BYTE bTempIOPortConfig3;
/***** Start *****/
dprintf2(("InitPASRegs(): Start " ));
//
// Setup the DMA Pointer in IO_PORT_CONFIG_2 ($F389)
//
// Translate the DMA Channel from the Translation Table
bDmaChannel = (BYTE) pGDI->DmaChannel;
dprintf3((" InitPASRegs(): DMA Channel = %u ", bDmaChannel ));
bDMAxlate = DMAxlate[bDmaChannel];
// Output the value
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
IO_PORT_CONFIG_2,
bDMAxlate );
dprintf3((" InitPASRegs(): DMA Pointer in 0xF389 - IO_PORT_CONFIG_2 set to %XH", bDMAxlate ));
KeStallExecutionProcessor(10); // pause - wait 10us
//
// Setup the Sound Interrupt Pointer in IO_PORT_CONFIG_3
//
// Translate the IRQ
bXlatInterruptNumber = (BYTE) pGDI->InterruptNumber;
if ( bXlatInterruptNumber <= 7 )
{
// Adjust for 0
bXlatInterruptNumber--;
}
else if ( bXlatInterruptNumber < 13 )
{
bXlatInterruptNumber = bXlatInterruptNumber - 3;
}
else
{
// Must be greater than 13
bXlatInterruptNumber = bXlatInterruptNumber - 4;
}
// Get the current value
bIOPortConfig3 = PASX_IN( ((FOUNDINFO *)(&pGDI->PASInfo)),
IO_PORT_CONFIG_3 );
bTempIOPortConfig3 = bIOPortConfig3;
// Mask CD Int in high nibble
bTempIOPortConfig3 = bTempIOPortConfig3 & 0xF0;
// Mask PCM IRQ Pointer in low nibble
bXlatInterruptNumber = bXlatInterruptNumber & 0x0F;
// Combine the two nibbles
bIOPortConfig3 = bTempIOPortConfig3 | bXlatInterruptNumber;
// Output the value
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
IO_PORT_CONFIG_3,
bIOPortConfig3 );
dprintf3((" InitPASRegs(): Sound Int Pointer in 0xF38A - IO_PORT_CONFIG_3 set to %XH", bIOPortConfig3 ));
KeStallExecutionProcessor(10); // pause - wait 10us
//
// Setup SYSTEM_CONFIG_2
//
// Output the value
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
SYSTEM_CONFIG_2,
0 );
dprintf3((" InitPASRegs(): 0x8389 - SYSTEM_CONFIG_2 set to 0 " ));
//
// Init the Cross Channel Value
//
pGDI->PasRegs._crosschannel = bCCr2r+bCCl2l;
//
// Init the Audio Filter
//
pGDI->PasRegs._audiofilt = INIT_AUDIO_FILTER; // 0x21
} // End InitPASRegs()
/*****************************************************************************
EnablePASMidi()
Routine Description :
Initialize PAS MIDI Hardware Registers
This is from Win 3.x mxdEnable()
Arguments :
pGDI - global data
Return Value :
VOID
*****************************************************************************/
VOID EnablePASMidi( IN OUT PGLOBAL_DEVICE_INFO pGDI )
{
/***** Local Variables *****/
PSOUND_HARDWARE pHw;
/***** Start *****/
dprintf2(("EnablePASMidi(): - Start " ));
// EnterCrit
pHw = pGDI->WaveInfo.HwContext;
HwEnter( pHw ); // KeAcquireSpinLock macro
//
// Are we an original PAS
if ( pGDI->PASInfo.wBoardRev == PAS_VERSION_1 )
{
// PAS 1 MIDI Initialization
dprintf1(("ERROR: EnablePASMidi(): Init for PAS 1 NOT IMPLEMENTED" ));
// Get the CODE from mxdEnable()
} // End IF (pGDI->PASInfo.wBoardRev == PAS_VERSION_1)
else
{
// PAS 16 Midi Initialization
// Reset the FIFO on the Midi Control reg
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
PAS2_MIDI_CTRL, // 0x178B
PAS2_MIDI_RESET_FIFO ); // 0x60
dprintf3((" EnablePASMidi(): 0x178B - PAS2_MIDI_CTRL set to %XH", PAS2_MIDI_RESET_FIFO ));
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
PAS2_MIDI_CTRL, // 0x178B
0 );
dprintf3((" EnablePASMidi(): 0x178B - PAS2_MIDI_CTRL set to %XH", 0 ));
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
PAS2_MIDI_STAT, // 0x1B88
0 );
dprintf3((" EnablePASMidi(): 0x1B88 - PAS2_MIDI_STAT set to 0" ));
} // End ELSE
// LeaveCrit
HwLeave( pHw ); // KeReleaseSpinLock macro
} // End EnablePASMidi()
/*****************************************************************************
InitPASMidi()
Routine Description :
Initialize PAS MIDI Hardware Registers for MIDI Input
from midStart()
Arguments :
pGDI - global data
Return Value :
VOID
*****************************************************************************/
VOID InitPASMidi( IN OUT PGLOBAL_DEVICE_INFO pGDI )
{
/***** Local Variables *****/
PSOUND_HARDWARE pHw;
BYTE bInterruptReg;
/***** Start *****/
dprintf2(("InitPASMidi(): Start " ));
// EnterCrit
pHw = pGDI->WaveInfo.HwContext;
HwEnter( pHw ); // KeAcquireSpinLock macro
//
// Are we an original PAS
if ( pGDI->PASInfo.wBoardRev == PAS_VERSION_1 )
{
// PAS 1 MIDI Initialization
dprintf1(("ERROR: InitPASMidi(): Init for PAS 1 NOT IMPLEMENTED" ));
// Get the CODE from midStart()
} // End IF (pGDI->PASInfo.wBoardRev == PAS_VERSION_1)
else
{
// PAS 16 Midi Initialization
// Reset the FIFO on the Midi Control reg
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
PAS2_MIDI_CTRL, // 0x178B
PAS2_MIDI_RESET_FIFO ); // 0x60
dprintf3((" InitPASMidi(): 0x178B - PAS2_MIDI_CTRL set to %XH", PAS2_MIDI_RESET_FIFO ));
// PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
// PAS2_MIDI_CTRL, // 0x178B
// 0 );
//
// dprintf3((" InitPASMidi(): 0x178B - PAS2_MIDI_CTRL set to %XH", 0 ));
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
PAS2_MIDI_STAT, // 0x1B88
0 );
dprintf3((" InitPASMidi(): 0x1B88 - PAS2_MIDI_STAT set to 0" ));
// Enable the FIFO on the Midi Control reg
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
PAS2_MIDI_CTRL, // 0x178B
PAS2_MIDI_RX_IRQ ); // 0x04
dprintf3((" InitPASMidi(): 0x178B - PAS2_MIDI_CTRL set to %XH", PAS2_MIDI_RX_IRQ ));
} // End ELSE
//
// Enable MIDI Interrupts
//
// Get the current Interrupt reg
bInterruptReg = PASX_IN( ((FOUNDINFO *)(&pGDI->PASInfo)),
INTRCTLR );
// Enable the MIDI Interrupt bit
bInterruptReg = bInterruptReg | MIDI_INT_ENABLE;
// Send it out
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
INTRCTLR,
bInterruptReg );
dprintf3((" InitPASMidi(): 0x0B8B - INTRCTLR set to %XH", bInterruptReg ));
// LeaveCrit
HwLeave( pHw ); // KeReleaseSpinLock macro
} // End InitPASMidi()
/*****************************************************************************
InitPCM()
Routine Description :
Initialize the PCM Engine
Arguments :
pGDI - global data
Return Value :
VOID
*****************************************************************************/
VOID InitPCM( IN OUT PGLOBAL_DEVICE_INFO pGDI )
{
/***** Local Variables *****/
/***** Start *****/
dprintf2(("InitPCM(): Start " ));
//
// Init the Setup Type
// flush BOTH interrupts
//
pGDI->PasRegs.TypeOfSetup = bICsamprate + bICsampbuff;
StopPCM( pGDI );
StopDMA( pGDI );
HwWaitForTxComplete( &pGDI->WaveInfo );
} // End InitPCM()
/*****************************************************************************
* *
* Pro Audio Spectrum *
* Wave Callouts *
* *
*****************************************************************************/
/*****************************************************************************
PASHwSetupDMA()
Routine Description :
Start the DMA on the device according to the device parameters
Arguments :
WaveInfo - Wave parameters
Return Value :
TRUE
*****************************************************************************/
BOOLEAN PASHwSetupDMA( IN OUT PWAVE_INFO WaveInfo )
{
/***** Local Variables *****/
PGLOBAL_DEVICE_INFO pGDI;
PSOUND_HARDWARE pHw;
BYTE bFilterValue;
/***** Start *****/
dprintf2(("PASHwSetupDMA(): Start " ));
//
// Get the Global data pointer
//
pHw = WaveInfo->HwContext;
pGDI = CONTAINING_RECORD( pHw, GLOBAL_DEVICE_INFO, Hw );
//
// Set the Filter
// SampleFilterSetting is the index into the table of values
//
bFilterValue = pGDI->PasRegs.SampleFilterSetting;
SetFilter( pGDI, bFilterValue );
//
// Setup the MV DMA I/O registers and
// Setup to output to the DAC
//
SetupPCMDMAIO( pGDI );
return TRUE;
} // End PASHwSetupDMA()
/*****************************************************************************
PASHwStopDMA()
Routine Description :
Stop the DMA on the device according to the device parameters
Arguments :
WaveInfo - Wave parameters
Return Value :
None
*****************************************************************************/
BOOLEAN PASHwStopDMA( IN OUT PWAVE_INFO WaveInfo )
{
/***** Local Variables *****/
PSOUND_HARDWARE pHw;
PGLOBAL_DEVICE_INFO pGDI;
/***** Start *****/
dprintf2(("PASHwStopDMA(): Start " ));
//
// Get the Global data pointer
//
pHw = WaveInfo->HwContext;
pGDI = CONTAINING_RECORD( pHw,
GLOBAL_DEVICE_INFO,
Hw );
// Find Out the Direction and set the TypeOfSetup
if ( WaveInfo->Direction )
{
// Output
pGDI->PasRegs.TypeOfSetup = DMAOUTPUT;
}
else
{
// Input
pGDI->PasRegs.TypeOfSetup = DMAINPUT;
}
//
// Stop the PCM Engine
//
StopPCM( pGDI );
//
// Stop the DMA on the card
//
StopDMA( pGDI );
HwWaitForTxComplete( WaveInfo );
//
// Turn on the speaker if necessary
//
if (!WaveInfo->Direction) {
HwSetSpeaker(pGDI, TRUE);
}
//
// OK
//
return( TRUE );
} // End PASHwStopDMA()
/*****************************************************************************
PASHwSetWaveFormat()
Routine Description :
Set device parameters for PAS wave input/output
Arguments :
WaveInfo - Wave parameters
Return Value :
BOOL
*****************************************************************************/
BOOLEAN PASHwSetWaveFormat( IN OUT PWAVE_INFO WaveInfo )
{
/***** Local Variables *****/
BOOLEAN Rc;
PGLOBAL_DEVICE_INFO pGDI;
/***** Start *****/
dprintf2(("PASHwSetWaveFormat(): Start " ));
pGDI = (PGLOBAL_DEVICE_INFO)
CONTAINING_RECORD(WaveInfo, GLOBAL_DEVICE_INFO, WaveInfo);
//
// Set the actual format
//
Rc = CalcSampleRate(WaveInfo);
//
// Load the DMA in the Cross Channel reg
// Note - we do this here because the DMA enable bit must be
// set BEFORE the DMA is programmed.
//
LoadDMA( pGDI );
return TRUE;
} // End PASHwSetWaveFormat()
/*****************************************************************************
* *
* Pro Audio Spectrum *
* Wave Callout Support Functions *
* *
*****************************************************************************/
/*****************************************************************************
LoadDMA()
Routine Description :
Setup the Cross Channel register
Arguments :
PGLOBAL_DEVICE_INFO pGDI
Return Value :
VOID
*****************************************************************************/
VOID LoadDMA( IN OUT PGLOBAL_DEVICE_INFO pGDI )
{
/***** Local Variables *****/
BYTE bCrossChannel;
/***** Start *****/
dprintf2(("LoadDMA(): Start " ));
//
// Set the input volume etc
//
if (!pGDI->WaveInfo.Direction) {
HwSetSpeaker(pGDI, FALSE);
}
//
// before we enable the DMA,
// let's make sure the DRQ is controlled, not floating
//
bCrossChannel = pGDI->PasRegs._crosschannel; // get the current cross channel
bCrossChannel = bCrossChannel | bCCdrq; // set the DRQ bit to control it
// Output the new value
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
CROSSCHANNEL,
bCrossChannel );
// Save the value
pGDI->PasRegs._crosschannel = bCrossChannel; // save the cross channel
dprintf3((" LoadDMA(): 0x0F8A - Cross Channel set to %XH", bCrossChannel ));
} // End LoadDMA()
/***************************************************************************
;---|*|---------------====< SetupPCMDMAIO() >====---------------
;---|*|
;---|*| Description:
;---|*| Setup the MV DMA I/O registers and
;---|*| Setup to output to the DAC
;---|*|
;---|*| Entry Conditions:
;---|*| IN OUT PGLOBAL_DEVICE_INFO pGDI
;---|*|
;---|*| Exit Conditions:
;---|*| VOID
;---|*|
***************************************************************************/
VOID SetupPCMDMAIO( IN OUT PGLOBAL_DEVICE_INFO pGDI )
{ // SetupPCMDMAIO()
/***** Local Variables *****/
BYTE bSilenceValue;
BYTE bIntControlRegValue;
BYTE bSampleBufferCount;
BYTE bStereoMonoMode;
BYTE bCrossChannel;
BYTE bTempCrossChannel;
BYTE bAudioFilter;
BYTE bPCMDirection;
/***** Start ******/
dprintf2(("SetupPCMDMAIO(): Start " ));
//
// Load the PCM data register with silence
//
if ( pGDI->WaveInfo.BitsPerSample == 8 )
{
// Use the 8 bit silence value
bSilenceValue = 0x80; // 8-bit silence value
}
else
{
// Use the 16 bit silence value
bSilenceValue = 0; // 16-bit silence value
}
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
PCMDATA,
bSilenceValue ); // Send the silence value
dprintf3((" SetupPCMDMAIO(): Silence Value = %XH", bSilenceValue ));
//
// Setup the Sample Buffer Counter Timer (T1 & rate generator)
//
if ( pGDI->DmaChannel > 4 )
{
// 16 Bit DMA
dprintf4((" SetupPCMDMAIO(): Using 16 Bit DMA"));
loadTimer1( pGDI,
(pGDI->SampleRateBasedDmaBufferSize / 2) );
}
else
{
// 8 Bit DMA
dprintf4((" SetupPCMDMAIO(): Using 8 Bit DMA"));
loadTimer1( pGDI,
pGDI->SampleRateBasedDmaBufferSize );
}
//
// Setup the Interrupt Control Register (On "out", it's a GO!)
//
bIntControlRegValue = PASX_IN( ((FOUNDINFO *)(&pGDI->PASInfo)),
INTRCTLRST );
KeStallExecutionProcessor(10); // pause - wait 10us
// flush any pending interrupts
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
INTRCTLRST,
bIntControlRegValue );
dprintf3((" SetupPCMDMAIO(): 0x0B89 - INTRCTLRST set to %XH", bIntControlRegValue ));
//
// Set to Interrupt on sample buffer count
//
bSampleBufferCount = PASX_IN( ((FOUNDINFO *)(&pGDI->PASInfo)),
INTRCTLR );
bSampleBufferCount = bSampleBufferCount | bICsampbuff;
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
INTRCTLR,
bSampleBufferCount );
// Save the value
pGDI->PasRegs._intrctlr = bSampleBufferCount;
dprintf3((" SetupPCMDMAIO(): 0x0B8B - INTRCTLR (sample buf cnt) set to %XH", bSampleBufferCount ));
//
// Cross Channel Setup
//
// Setup the stereo/mono Bits
if ( pGDI->Hw.Stereo )
{
// if stereo mode, don't set mono bit
dprintf4((" SetupPCMDMAIO(): CrossChannel Setup - Stereo Mode"));
bStereoMonoMode = 0;
}
else
{
// Mono
dprintf4((" SetupPCMDMAIO(): CrossChannel Setup - Mono Mode"));
bStereoMonoMode = bCCmono;
}
// Add the direction to the Mode
if ( pGDI->WaveInfo.Direction )
{
// True == out (Play)
dprintf4((" SetupPCMDMAIO(): CrossChannel Setup - Play Mode"));
bPCMDirection = bCCdac;
}
else
{
// FALSE == in (Record)
dprintf4((" SetupPCMDMAIO(): CrossChannel Setup - Record Mode"));
bPCMDirection = 0;
}
// Combine direction and Mode
bStereoMonoMode = bStereoMonoMode | bPCMDirection;
// Enable the PCM bit & DRQ
bStereoMonoMode = bStereoMonoMode | (bCCenapcm+bCCdrq);
// grab all but PCM/DRQ/MONO/DIRECTION from the Cross Channel
bTempCrossChannel = pGDI->PasRegs._crosschannel;
bTempCrossChannel = bTempCrossChannel & 0x0F; // Apply mask
// Merge the states
bCrossChannel = bTempCrossChannel | bStereoMonoMode;
// Disable the PCM Bit
bCrossChannel = bCrossChannel ^ bCCenapcm; // disable the PCM bit
// Output the new value
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
CROSSCHANNEL,
bCrossChannel );
// Enable the PCM Bit
bCrossChannel = bCrossChannel ^ bCCenapcm; // enable the PCM bit
// Output the new value
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
CROSSCHANNEL,
bCrossChannel );
//
// ***** NOTE: *****
// toggling enapcm sets L-R orienation
//
// Save the value
pGDI->PasRegs._crosschannel = bCrossChannel; // save the CrossChannel
dprintf3((" SetupPCMDMAIO(): 0x0F8A - CrossChannel set to %XH", bCrossChannel ));
//
// Setup the audio filter sample bits
//
bAudioFilter = pGDI->PasRegs._audiofilt; // Get the value
bAudioFilter = bAudioFilter | (bFIsrate+bFIsbuff); // enable the sample count/buff counters
bAudioFilter = bAudioFilter | bFImute; // 0x20
// Output the new value
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
AUDIOFILT,
bAudioFilter );
// Save the value
pGDI->PasRegs._audiofilt = bAudioFilter; // save the Audio Filter
dprintf3((" SetupPCMDMAIO(): 0x0B8A - AudioFilter set to %XH", bAudioFilter ));
} // End SetupPCMDMAIO()
/****************************************************************************
;---|*|---------------====< StopPCM() >====---------------
;---|*|
;---|*| Turn off the h/w from making interrupts and DMA requests
;---|*|
;---|*| Entry Conditions:
;---|*| IN OUT PGLOBAL_DEVICE_INFO pGDI
;---|*|
;---|*| Exit Conditions:
;---|*| Nothing
;---|*|
;---|*| Warning: Enables interrupts! ???
;---|*|
****************************************************************************/
VOID StopPCM( IN OUT PGLOBAL_DEVICE_INFO pGDI )
{ // Begin StopPCM()
/**** Local Variables *****/
PSOUND_HARDWARE pHw;
BYTE bAudioFilter;
BYTE bTypeOfSetup;
BYTE bIntrCtlReg;
BYTE bCrossChannel;
BYTE bTimerRunning;
/***** Start *****/
dprintf2(("StopPCM(): Start " ));
// EnterCrit
pHw = pGDI->WaveInfo.HwContext;
HwEnter( pHw ); // KeAcquireSpinLock macro
//
// Audio Filter setup
// clear the audio filter sample timer enable bits
//
bAudioFilter = pGDI->PasRegs._audiofilt; // Get the value
bAudioFilter = bAudioFilter & (~(bFIsrate+bFIsbuff)); // flush the sample
// timer bits
bAudioFilter = bAudioFilter | bFImute; // 0x20
pGDI->PasRegs._audiofilt = bAudioFilter; // Save the value
// Output the new value
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
AUDIOFILT,
bAudioFilter );
dprintf3((" StopPCM(): 0x0B8A - Audio Filter set to = %XH", bAudioFilter ));
//
// clear the Interrupt Control Register
//
bTypeOfSetup = pGDI->PasRegs.TypeOfSetup;
bTypeOfSetup = bTypeOfSetup & (bICsamprate+bICsampbuff);
bTypeOfSetup = ~bTypeOfSetup;
//
// if MV101, leave sample rate running
//
if ( (pGDI->PASInfo.wBoardRev == PAS_VERSION_1) ||
(pGDI->PASInfo.wBoardRev == VERSION_CDPC) )
{
// NOT MV101, so no timer
bTimerRunning = 0;
}
else
{
// Leave Timer running!!
bTimerRunning = bICsamprate;
}
// Apply timer value
bTypeOfSetup = bTypeOfSetup | bTimerRunning;
// Read the Interrupt register
bIntrCtlReg = PASX_IN( ((FOUNDINFO *)(&pGDI->PASInfo)),
INTRCTLR );
// kill sample timer interrupts
bIntrCtlReg = bIntrCtlReg & bTypeOfSetup;
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
INTRCTLR,
bIntrCtlReg );
//
// Save the value
//
pGDI->PasRegs._intrctlr = bIntrCtlReg;
dprintf3((" StopPCM(): 0x0B8B - INTRCTRL Interrupt Control Reg set to = %XH", bIntrCtlReg ));
//
// Cross Channel setup
//
// clear the PCM enable bit
bCrossChannel = pGDI->PasRegs._crosschannel; // get the current cross channel
bCrossChannel = bCrossChannel & (~bCCenapcm);// clear the PCM bit
bCrossChannel = bCrossChannel & (~bCCdac); // if MV101, keep PCM running
// Output the new value
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
CROSSCHANNEL,
bCrossChannel );
// Save the value
pGDI->PasRegs._crosschannel = bCrossChannel; // save the cross channel
dprintf3((" StopPCM(): 0x0F8A - Cross Channel set to %XH", bCrossChannel ));
// LeaveCrit
HwLeave( pHw ); // KeReleaseSpinLock macro
} // StopPCM endp
/****************************************************************************
;---|*|---------------====< StopDMA() >====---------------
;---|*|
;---|*| Turn off DMA on the chip
;---|*|
;---|*| Entry Conditions:
;---|*| IN OUT PGLOBAL_DEVICE_INFO pGDI
;---|*|
;---|*| Exit Conditions:
;---|*| Nothing
;---|*|
****************************************************************************/
VOID StopDMA( IN OUT PGLOBAL_DEVICE_INFO pGDI )
{ // Begin StopDMA()
/***** Local Variables *****/
PSOUND_HARDWARE pHw;
BYTE bCrossChannel;
BYTE bPCMRunning;
/***** Start ******/
dprintf2(("StopDMA(): Start " ));
// EnterCrit
pHw = pGDI->WaveInfo.HwContext;
HwEnter( pHw ); // KeAcquireSpinLock macro
//
// Setup the Cross Channel
//
bCrossChannel = pGDI->PasRegs._crosschannel; // get the current cross channel
bCrossChannel = bCrossChannel & (~bCCdrq); // clear the PCM/DRQ/DAC bit
bPCMRunning = 0;
#if 0
// if MV101 and the VU meters are enabled then
// then the PCM engine can be left running
// HOWEVER, We NEED VU meter Enable/Disable messages for this!
// if MV101, leave PCM running
if ( (pGDI->PASInfo.wBoardRev == PAS_VERSION_1) ||
(pGDI->PASInfo.wBoardRev == VERSION_CDPC) )
{
// NOT MV101, so no PCM
bPCMRunning = 0;
}
else
{
// Leave PCM running!!
bPCMRunning = bCCenapcm;
}
#endif
bCrossChannel = bCrossChannel | bPCMRunning;
bCrossChannel = bCrossChannel & (~bCCmono); // always leave it in stereo mode
// Output the new value
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
CROSSCHANNEL,
bCrossChannel );
// Save the value
pGDI->PasRegs._crosschannel = bCrossChannel;
dprintf3((" StopDMA(): 0x0F8A - Cross Channel set to %XH", bCrossChannel ));
// LeaveCrit
HwLeave( pHw ); // KeReleaseSpinLock macro
} // End StopDMA()
/*****************************************************************************
|*|
|*|----====< BOOL CalcSampleRate() >====----
|*|
|*| Entry Conditions:
|*| WaveInfo - Wave parameters
|*|
|*| This function will set the sample rate and filter
|*| filter settings PROPERLY.
|*|
|*| Sets global variables for sample rate, stereo/mono mode
|*| and number of bits. Does limited sanity check input parms.
|*| Sets sample rate hardware and filters.
|*|
|*| Exit Conditions:
|*| OK == TRUE
|*| Fail == FALSE
|*|
*****************************************************************************/
BOOL CalcSampleRate( IN OUT PWAVE_INFO WaveInfo )
{
/***** Local Variables *****/
ULONG lTicks; // timer value for sample rate
WORD wPrescale;
WORD wTimer;
ULONG lTempSamplesPerSec;
ULONG lBufferSize;
BYTE bTempReg;
PGLOBAL_DEVICE_INFO pGDI;
PSOUND_HARDWARE pHw;
BOOLEAN Different;
#define MASTER_CLOCK ((DWORD)1193180L)
/***** Start *****/
dprintf3(("CalcSampleRate(): Start " ));
//
// Misc Debug Info
//
dprintf3((" CalcSampleRate(): SamplesPerSec = %u ", WaveInfo->SamplesPerSec ));
dprintf3((" CalcSampleRate(): BitsPerSample = %u ", WaveInfo->BitsPerSample ));
dprintf3((" CalcSampleRate(): Channels = %u ", WaveInfo->Channels ));
if ( WaveInfo->Direction )
{
dprintf3((" CalcSampleRate(): Direction = PLAY" ));
}
else
{
dprintf3((" CalcSampleRate(): Direction = RECORD" ));
}
dprintf3((" CalcSampleRate(): DMABusy = %u ", WaveInfo->DMABusy ));
dprintf3((" CalcSampleRate(): DpcQueued = %u ", WaveInfo->DpcQueued ));
//
// Get our Global Data
//
Different = FALSE;
pHw = WaveInfo->HwContext;
pGDI = CONTAINING_RECORD( pHw,
GLOBAL_DEVICE_INFO,
Hw );
//
// Stereo/Mono
//
if ((BOOLEAN)(WaveInfo->Channels > 1) != pHw->Stereo)
{
Different = TRUE;
pHw->Stereo = (BOOLEAN)(WaveInfo->Channels > 1);
}
//
// Sample Rate
//
// Check the range
if ( (WaveInfo->SamplesPerSec < MIN_SAMPLE_RATE) ||
(WaveInfo->SamplesPerSec > MAX_SAMPLE_RATE) )
{
dprintf1(("ERROR: CalcSampleRate(): Invalid Sample Rate = %u",
WaveInfo->SamplesPerSec ));
return (FALSE);
}
lTempSamplesPerSec = WaveInfo->SamplesPerSec * WaveInfo->Channels;
//
// Get the DMA Buffer size based on the Sample Rate
//
lBufferSize = SoundGetDMABufferSize( WaveInfo );
pGDI->SampleRateBasedDmaBufferSize = lBufferSize;
dprintf3((" CalcSampleRate(): Using DMA Buffer Size = %XH ", pGDI->SampleRateBasedDmaBufferSize ));
//
// Bits Per Sample
//
if ( pGDI->PASInfo.Caps.CapsBits.DAC16 )
{
//
// Set the Bits Per Sample
//
// Get the current Sys Config 2
bTempReg = PASX_IN( ((FOUNDINFO *)(&pGDI->PASInfo)),
SYSTEM_CONFIG_2 );
dprintf3((" CalcSampleRate(): 0x8389 - Previous Sys Config 2 was set to %XH", bTempReg ));
// D4 inverts flatline sense (leave this alone!)
// make sure it's off
bTempReg &= (~D4);
switch ( WaveInfo->BitsPerSample )
{
case 8:
default:
dprintf4((" CalcSampleRate(): Using 8 BitsPerSample"));
bTempReg &= (~D2); // D2 enables 16 & 12-bit DMA
bTempReg &= (~D3); // D3 enables 12-bit DMA
break;
case 12:
dprintf4((" CalcSampleRate(): Using 12 BitsPerSample"));
bTempReg |= D2; // D2 enables 16 & 12-bit DMA
bTempReg |= D3; // D3 enables 12-bit DMA
break;
case 16:
dprintf4((" CalcSampleRate(): Using 16 BitsPerSample"));
bTempReg |= D2; // D2 enables 16 & 12-bit DMA
bTempReg &= (~D3); // D3 enables 12-bit DMA
break;
} // End SWITCH (WaveInfo->BitsPerSample)
// Set the new value
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
SYSTEM_CONFIG_2,
bTempReg );
dprintf3((" CalcSampleRate(): 0x8389 - Sys Config 2 set to %XH", bTempReg ));
//
// disable original PAS emulation
//
// Get the current Sys Config 2
bTempReg = PASX_IN( ((FOUNDINFO *)(&pGDI->PASInfo)),
SYSTEM_CONFIG_1 );
bTempReg = bTempReg | 2; // D1 disables original PAS emulation
// Set the new value
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
SYSTEM_CONFIG_1,
bTempReg );
dprintf3((" CalcSampleRate(): 0x8388 - Sys Config 1 set to %XH", bTempReg ));
//
// Set the Sample Rate
//
switch ( WaveInfo->SamplesPerSec )
{
case 11025:
dprintf4((" CalcSampleRate(): Using 11025 SamplesPerSec"));
wPrescale = 2;
wTimer = 80; //20;
break;
case 22050:
dprintf4((" CalcSampleRate(): Using 22050 SamplesPerSec"));
wPrescale = 2;
wTimer = 40;
break;
case 32000:
dprintf4((" CalcSampleRate(): Using 32000 SamplesPerSec"));
wPrescale = 9;
wTimer = 124; // .03% error
break;
case 44100:
dprintf4((" CalcSampleRate(): Using 44100 SamplesPerSec"));
wPrescale = 2; //wNumChannels+1;
wTimer = 20;
break;
case 48000:
dprintf4((" CalcSampleRate(): Using 48000 SamplesPerSec"));
wPrescale = 16; //wNumChannels+1
wTimer = 147;
break;
default:
{
DWORD target_ratio;
DWORD best_ratio;
DWORD test_ratio;
long best_diff;
long last_diff;
long test_diff;
WORD best_p;
WORD best_t;
WORD p;
WORD t;
target_ratio = ( ((DWORD)441000) << 10 )
/( ((DWORD)WaveInfo->SamplesPerSec) );
best_ratio = ((DWORD)300) << 10;
best_diff = ((long)7000) << 10;
best_p = 0;
best_t = 0;
for ( p = 2; p < 256; p++)
{
last_diff=((DWORD)300) << 10;
for ( t = p+1; t < 256; t++)
{
test_ratio= ( ((DWORD)t) << 10 ) / ( ((DWORD)p) );
if ( test_ratio == target_ratio )
{
best_ratio = test_ratio;
best_p = p;
best_t = t;
goto got_em;
} // End IF (test_ratio == target_ratio)
test_diff = test_ratio - target_ratio;
if ( test_diff < 0 )
{
test_diff =- test_diff;
}
if ( test_diff > last_diff )
{
break;
}
if ( test_diff < best_diff )
{
best_ratio = test_ratio;
best_diff = test_diff;
best_p = p;
best_t = t;
}
last_diff = test_diff;
} // End FOR (t < 256)
} // End FOR (p < 256)
got_em:
wPrescale = best_p;
wTimer = best_t;
dprintf4((" CalcSampleRate(): Calculating - Prescale = %XH, Timer = %xH",
wPrescale, wTimer));
} // End case default:
} // End SWITCH (WaveInfo->SamplesPerSec)
// Load the Values
loadPrescale( pGDI,
wPrescale );
loadTimer0( pGDI,
wTimer );
// Set the sample filter index
pGDI->PasRegs.SampleFilterSetting = 0;
} // End IF (pGDI->PASInfo.Caps.CapsBits.DAC16)
else
{
// lTicks becomes # clock ticks/sample
lTicks = MASTER_CLOCK / lTempSamplesPerSec;
wTimer = LOWORD(lTicks);
loadTimer0( pGDI,
wTimer );
// Stereo case
if ( pHw->Stereo )
{
// STEREO CASE
lTempSamplesPerSec/=2L;
}
// Init the sample filter index
pGDI->PasRegs.SampleFilterSetting = 1;
if (lTempSamplesPerSec == 11025L) // kludge!
{
pGDI->PasRegs.SampleFilterSetting = 2;
}
if (lTempSamplesPerSec > (5965L*2)) // Nyquist law: max freq is samples/2
{
pGDI->PasRegs.SampleFilterSetting = 2;
}
if (lTempSamplesPerSec > (8948L*2)) // Nyquist law: max freq is samples/2
{
pGDI->PasRegs.SampleFilterSetting = 3;
}
if (lTempSamplesPerSec > (11931L*2)) // Nyquist law: max freq is samples/2
{
pGDI->PasRegs.SampleFilterSetting = 4;
}
if (lTempSamplesPerSec > (15909L*2)) // Nyquist law: max freq is samples/2
{
pGDI->PasRegs.SampleFilterSetting = 5;
}
if (lTempSamplesPerSec > (17897L*2)) // Nyquist law: max freq is samples/2
{
pGDI->PasRegs.SampleFilterSetting = 6;
}
#if 0
mixOpen((LPHMIXER) &hMixer,0,NULL);
// if sample rate is less than 36K make sure filter is patched
if (lTempSamplesPerSec <= (15909L*2))
mixSetConnections(hMixer,PLAY_IN_PCM,(DWORD) (1L<<PLAY_OUT_PCM));
else
mixSetConnections(hMixer,PLAY_IN_PCM,(DWORD) (1L<<PLAY_OUT_AMPLIFIER));
mixClose(hMixer);
#endif
} // End ELSE
return TRUE;
} // End CalcSampleRate()
/***************************************************************************
;---|*|---------------====< loadTimer0() >====---------------
;---|*|
;---|*|
;---|*|
;---|*| Entry Conditions:
;---|*| IN OUT PGLOBAL_DEVICE_INFO pGDI
;---|*| WORD wRate
;---|*|
;---|*| Exit Conditions:
;---|*| Nothing
;---|*|
***************************************************************************/
VOID loadTimer0( IN OUT PGLOBAL_DEVICE_INFO pGDI,
WORD wRate )
{ // Begin loadTimer0()
/***** Local Variables *****/
PSOUND_HARDWARE pHw;
BYTE bRateLSB;
BYTE bRateMSB;
/***** Start *****/
dprintf3((" loadTimer0(): Sample Rate = %XH", wRate ));
// EnterCrit
pHw = pGDI->WaveInfo.HwContext;
HwEnter( pHw ); // KeAcquireSpinLock macro
// Set the wTimer 0
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
TMRCTLR,
TIMER0_SQR_WAVE ); // 36h wTimer 0 & square wave
dprintf3((" loadTimer0(): 0x138B - TMRCTLR set to %XH", TIMER0_SQR_WAVE ));
// Save the Sample rate
// pGDI->PasRegs._samplerate = wRate; // we save this value in global reg
// Get the Sample Rate MSB and LSB
bRateLSB = (BYTE) (wRate & 0x00FF);
bRateMSB = (BYTE) ((wRate & 0xFF00) >> 8);
// Set the Sample rate - LSB
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
SAMPLERATE,
bRateLSB ); // Sample Rate - LSB
dprintf3((" loadTimer0(): 0x1388 - Sample Rate LSB set to %XH", bRateLSB ));
KeStallExecutionProcessor(1000); // pause, wait 100us
// Set the Sample rate - MSB
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
SAMPLERATE,
bRateMSB ); // Sample Rate - MSB
dprintf3((" loadTimer0(): 0x1388 - Sample Rate MSB set to %XH", bRateMSB ));
// LeaveCrit
HwLeave( pHw ); // KeReleaseSpinLock macro
} // End loadwTimer0()
/***************************************************************************
;---|*|---------------====< loadTimer1() >====---------------
;---|*|
;---|*|
;---|*|
;---|*| Entry Conditions:
;---|*| IN OUT PGLOBAL_DEVICE_INFO pGDI
;---|*| ULONG lDMASize (in bytes)
;---|*|
;---|*| Exit Conditions:
;---|*| Nothing
;---|*|
***************************************************************************/
VOID loadTimer1( IN OUT PGLOBAL_DEVICE_INFO pGDI,
ULONG lDMASize )
{ // Begin loadTimer1()
/***** Local Variables *****/
PSOUND_HARDWARE pHw;
WORD wDRQcount;
BYTE bDRQcountLSB;
BYTE bDRQcountMSB;
/***** Start *****/
dprintf3((" loadTimer1(): DMA buffer size = %XH", lDMASize ));
// EnterCrit
pHw = pGDI->WaveInfo.HwContext;
HwEnter( pHw ); // KeAcquireSpinLock macro
// Set the wTimer 1
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
TMRCTLR,
TIMER1_RATE_GEN ); // 74h wTimer 1 & rate generator
dprintf3((" loadTimer1(): 0x138B - TMRCTLR set to %XH", TIMER1_RATE_GEN ));
KeStallExecutionProcessor(10); // pause, wait 10us
// the buffer Timer interrupt is based on DRQs, not bytes
wDRQcount = (WORD) (lDMASize / 2); // 2 parts to the buffer
dprintf3((" loadTimer1(): Sample Rate Count = %XH", wDRQcount ));
// Save the Sample rate count
// pGDI->PasRegs._samplecnt = wDRQcount; // we save this value in global reg
// Get the Sample Rate Count MSB and LSB
bDRQcountLSB = (BYTE) (wDRQcount & 0xFF);
bDRQcountMSB = (BYTE) ((wDRQcount & 0xFF00) >> 8);
// Set the Sample rate count - LSB
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
SAMPLECNT,
bDRQcountLSB ); // Sample Rate Count - LSB
dprintf3((" loadTimer1(): 0x1389 - Sample Rate Count LSB set to %XH", bDRQcountLSB ));
//
// We need a LARGE delay here
// We could use a large KeStallExecutionProcessor()
// but that did not always work and stopped the system
// HACKHACK - use a dummy printf!!
//
#if DBG
KeStallExecutionProcessor(100); // pause, wait 100us - 10 was NOT enough
#else
{
WORD i;
for ( i = 0; i < 5; i++ )
{
KeStallExecutionProcessor(100); // pause, wait 100us - 10 was NOT enough
}
}
#endif
// Set the Sample rate count - MSB
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
SAMPLECNT,
bDRQcountMSB ); // Sample Rate Count - MSB
dprintf3((" loadTimer1(): 0x1389 - Sample Rate Count MSB set to %XH", bDRQcountMSB ));
// LeaveCrit
HwLeave( pHw ); // KeReleaseSpinLock macro
} // End loadTimer1()
/***************************************************************************
;---|*|---------------====< loadPrescale() >====---------------
;---|*|
;---|*|
;---|*|
;---|*| Entry Conditions:
;---|*| WORD wPrescale
;---|*|
;---|*| Exit Conditions:
;---|*| Nothing
;---|*|
***************************************************************************/
VOID loadPrescale( IN OUT PGLOBAL_DEVICE_INFO pGDI,
WORD wPrescale )
{ // Begin loadPrescale()
/***** Local Variables *****/
BYTE bPrescaleValue;
/***** Start *****/
bPrescaleValue = (BYTE) wPrescale & 0x0F;
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
PRESCALE_DIVIDER,
bPrescaleValue );
dprintf3((" loadPrescale(): 0xBF8A - Prescale set to %XH", bPrescaleValue ));
} // End loadPrescale()
/*****************************************************************************
* *
* Pro Audio Spectrum *
* MIDI Callouts *
* *
*****************************************************************************/
/*****************************************************************************
PASHwStartMidiIn()
Routine Description :
Start midi recording on the PAS
Arguments :
MidiInfo - Midi parameters
Return Value :
BOOL
*****************************************************************************/
BOOLEAN PASHwStartMidiIn( IN OUT PMIDI_INFO MidiInfo )
{
/***** Start *****/
PGLOBAL_DEVICE_INFO pGDI;
PSOUND_HARDWARE pHw;
/***** Start *****/
dprintf2(("PASHwStartMidiIn(): Start " ));
//
// Get the Global data pointer
//
pHw = MidiInfo->HwContext;
pGDI = CONTAINING_RECORD( pHw,
GLOBAL_DEVICE_INFO,
Hw );
PASMidiStart( pGDI );
return ( TRUE );
} // End PASHwStartMidiIn()
/*****************************************************************************
PASHwStopMidiIn()
Routine Description :
Stop midi recording on the PAS
Arguments :
MidiInfo - Midi parameters
Return Value :
BOOL
*****************************************************************************/
BOOLEAN PASHwStopMidiIn( IN OUT PMIDI_INFO MidiInfo )
{
/***** Local Variables *****/
PGLOBAL_DEVICE_INFO pGDI;
PSOUND_HARDWARE pHw;
/***** Start *****/
dprintf2(("PASHwStopMidiIn(): Start " ));
//
// Get the Global data pointer
//
pHw = MidiInfo->HwContext;
pGDI = CONTAINING_RECORD( pHw,
GLOBAL_DEVICE_INFO,
Hw );
PASMidiStop( pGDI );
return ( TRUE );
} // End PASHwStopMidiIn()
/*****************************************************************************
PASHwMidiRead()
Routine Description :
Read a midi byte from the MIDI Hardware on the PAS
Arguments :
MidiInfo - Midi parameters
Return Value :
BOOL
TRUE if Midi Data is return
FALSE if No data available or error occured
*****************************************************************************/
BOOLEAN PASHwMidiRead( IN OUT PMIDI_INFO MidiInfo,
OUT PUCHAR Byte )
{
/***** Local Variables *****/
PGLOBAL_DEVICE_INFO pGDI;
PSOUND_HARDWARE pHw;
UCHAR bMidiReg;
BYTE bMidiData;
/***** Start *****/
dprintf2(("PASHwMidiRead(): Start " ));
//
// Get the Global data pointer
//
pHw = MidiInfo->HwContext;
pGDI = CONTAINING_RECORD( pHw,
GLOBAL_DEVICE_INFO,
Hw );
//
// Check for a PAS 1
//
if ( pGDI->PASInfo.wBoardRev == PAS_VERSION_1 )
{
dprintf1(("ERROR: PASHwMidiRead(): PAS 1 MIDI NOT Implemented " ));
// Code located in MIDI_ISR()
}
else
{
// PAS 16
// Read the Midi Status Register
bMidiReg = PASX_IN( ((FOUNDINFO *)(&pGDI->PASInfo)),
PAS2_MIDI_STAT ); // 0x1B88
// Get the Midi Status Register, this was saved in the ISR
// bMidiReg = pGDI->bMidiStatusReg;
//
// Check for Errors
//
if ( (bMidiReg & MIDI_FRAME_ERROR) ||
(bMidiReg & MIDI_FIFO_ERROR) )
{
//
// Check for Frame Errors or Input FIFO overload
//
if ( bMidiReg & MIDI_FRAME_ERROR )
{
dprintf1(("ERROR: PASHwMidiRead(): Midi Frame Error" ));
// Clear the Error
bMidiReg = bMidiReg & MIDI_FRAME_ERROR;
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
PAS2_MIDI_STAT,
bMidiReg );
// Reset FIFO In Pointer
bMidiReg = PASX_IN( ((FOUNDINFO *)(&pGDI->PASInfo)),
PAS2_MIDI_CTRL );
bMidiReg = bMidiReg | MIDI_RESET_FIFO_PTR;
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
PAS2_MIDI_CTRL,
bMidiReg );
// Release FIFO In Pointer
bMidiReg = bMidiReg & (~MIDI_RESET_FIFO_PTR);
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
PAS2_MIDI_CTRL,
bMidiReg );
return( FALSE );
} // End IF (bMidiReg & MIDI_FRAME_ERROR)
if ( bMidiReg & MIDI_FIFO_ERROR )
{
dprintf1(("ERROR: PASHwMidiRead(): Midi FIFO Error " ));
// Clear the Error
bMidiReg = bMidiReg & MIDI_FIFO_ERROR;
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
PAS2_MIDI_STAT,
bMidiReg );
// Reset FIFO In Pointer
bMidiReg = PASX_IN( ((FOUNDINFO *)(&pGDI->PASInfo)),
PAS2_MIDI_CTRL );
bMidiReg = bMidiReg | MIDI_RESET_FIFO_PTR;
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
PAS2_MIDI_CTRL,
bMidiReg );
// Release FIFO In Pointer
bMidiReg = bMidiReg & (~MIDI_RESET_FIFO_PTR);
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
PAS2_MIDI_CTRL,
bMidiReg );
return( FALSE );
} // End IF (bMidiReg & MIDI_FIFO_ERROR)
} // End IF (MIDI_FRAME_ERROR || MIDI_FIFO_ERROR )
//
// No Errors
// Any Data in the FIFO?
//
if ( bMidiReg & PAS2_MIDI_RX_IRQ ) // 0x04
{
// Get the number of Bytes in the FIFO
// Nothing is done with this value!!
bMidiData = PASX_IN( ((FOUNDINFO *)(&pGDI->PASInfo)),
PAS2_FIFO_PTRS ); // 0x1B89
dprintf5((" PASHwMidiRead(): Buyes in FIFO = %XH", bMidiData ));
// Yes we have data, so get it!!
bMidiData = PASX_IN( ((FOUNDINFO *)(&pGDI->PASInfo)),
PAS2_MIDI_DAT ); // 0x178A
*Byte = bMidiData;
#if DBG_MIDI_IN_DATA
DbgPrint(" %2X ", bMidiData );
#else
dprintf5((" PASHwMidiRead(): Midi Data = %XH", bMidiData ));
#endif
return( TRUE );
} // End IF (bMidiReg & PAS2_MIDI_RX_IRQ)
else
{
dprintf4(("ERROR: PASHwMidiRead(): - FIFO empty" ));
return( FALSE );
}
} // End ELSE (PAS 16)
} // End PASHwMidiRead()
/*****************************************************************************
PASHwMidiOut()
Routine Description :
Write a midi byte to the output
Arguments :
MidiInfo - Midi parameters
Return Value :
VOID
*****************************************************************************/
VOID PASHwMidiOut( IN OUT PMIDI_INFO MidiInfo,
IN PUCHAR Bytes,
IN int Count )
{
/***** Local Variables *****/
PGLOBAL_DEVICE_INFO pGDI;
PSOUND_HARDWARE pHw;
int i;
int j;
BYTE bMidiStatus;
/***** Start *****/
dprintf2(("PASHwMidiOut(): Start " ));
//
// Get the Global data pointer
//
pHw = MidiInfo->HwContext;
pGDI = CONTAINING_RECORD( pHw,
GLOBAL_DEVICE_INFO,
Hw );
//
// Check for a PAS 1
//
if ( pGDI->PASInfo.wBoardRev == PAS_VERSION_1 )
{
dprintf1(("ERROR: PASHwMidiOut(): PAS 1 MIDI NOT Implemented " ));
// Code located in modBytePut()
}
else
{
// PAS 16
//
// 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 );
//
// Mutex Loop
//
for (i = 0; i < 20; i++)
{
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
//
for (j = 0; j < MIDI_OUT_OK_RETRIES; j++)
{
// Get the Midi FIFO Pointer
bMidiStatus = PASX_IN( ((FOUNDINFO *)(&pGDI->PASInfo)),
PAS2_FIFO_PTRS );
// Make sure the hardware can keep up
KeStallExecutionProcessor(2);
// Check the Status
bMidiStatus = bMidiStatus & MIDI_FIFO_OUT_OK_STATUS;
if ( bMidiStatus == MIDI_FIFO_OUTPUT_WAIT )
{
}
else
{
goto OK_To_Send_Byte;
}
} // End FOR (j < MIDI_OUT_OK_RETRIES)
//
// Midi Output Timed out!!
//
dprintf1(("ERROR: PASHwMidiOut(): MIDI Output Timed-out " ));
return;
OK_To_Send_Byte:
//
// 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);
//
// Send the data byte
//
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
PAS2_MIDI_DAT, Byte );
HwLeave(pHw);
dprintf5((" PASHwMidiOut(): Byte = %XH", Byte ));
//
// Move on to next byte
//
Bytes++;
if (--Count == 0)
{
break;
}
} // End FOR (i < 20)
KeReleaseMutex(&pGDI->DeviceMutex, FALSE);
} // End WHILE (Count > 0)
} // End ELSE (PAS 16)
} // End PASHwMidiOut()
/*****************************************************************************
* *
* Pro Audio Spectrum *
* MIDI Callout Support Functions *
* *
*****************************************************************************/
/*****************************************************************************
PASMidiStart()
Routine Description :
Enable the Midi In Hardware on the PAS
Arguments :
IN OUT PGLOBAL_DEVICE_INFO pGDI
Return Value :
VOID
*****************************************************************************/
VOID PASMidiStart( IN OUT PGLOBAL_DEVICE_INFO pGDI )
{
/***** Local Variables *****/
/***** Start *****/
#if DBG_MIDI_IN
dprintf1(("PASMidiStart(): Start " ));
#else
dprintf2(("PASMidiStart(): Start " ));
#endif
//
// Check for a PAS 1
//
if ( pGDI->PASInfo.wBoardRev == PAS_VERSION_1 )
{
dprintf1(("ERROR: PASMidiStart(): PAS 1 MIDI NOT Implemented " ));
} // End IF (pGDI->PASInfo.wBoardRev == PAS_VERSION_1)
else
{
// PAS 16
InitPASMidi( pGDI );
}
} // End PASMidiStart()
/*****************************************************************************
PASMidiStop()
Routine Description :
Turn OFF the Midi In Hardware on the PAS
Arguments :
IN OUT PGLOBAL_DEVICE_INFO pGDI
Return Value :
VOID
*****************************************************************************/
VOID PASMidiStop( IN OUT PGLOBAL_DEVICE_INFO pGDI )
{
/***** Local Variables *****/
PSOUND_HARDWARE pHw;
BYTE bInterruptReg;
/***** Start *****/
#if DBG_MIDI_IN
dprintf1(("PASMidiStop(): Start " ));
#else
dprintf2(("PASMidiStop(): Start " ));
#endif
// EnterCrit
pHw = pGDI->WaveInfo.HwContext;
HwEnter( pHw ); // KeAcquireSpinLock macro
//
// Check for a PAS 1
//
if ( pGDI->PASInfo.wBoardRev == PAS_VERSION_1 )
{
dprintf1(("ERROR: PASMidiStop(): PAS 1 MIDI NOT Implemented " ));
// Get the CODE from midStop()
}
else
{
// PAS 16
// Stop the Midi Interrupts
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
PAS2_MIDI_CTRL,
0 );
} // End ELSE (PAS 16)
//
// Disable MIDI Interrupts
//
// Get the current Interrupt reg
bInterruptReg = PASX_IN( ((FOUNDINFO *)(&pGDI->PASInfo)),
INTRCTLR );
// Disable the MIDI Interrupt bit
bInterruptReg = bInterruptReg & (~MIDI_INT_ENABLE);
// Send it out
PASX_OUT( ((FOUNDINFO *)(&pGDI->PASInfo)),
INTRCTLR,
bInterruptReg );
// LeaveCrit
HwLeave( pHw ); // KeReleaseSpinLock macro
} // End PASMidiStop()
/*
** Read interrupt status
*/
BOOLEAN
HwReadInterruptStatus(
PVOID Context
)
{
UCHAR InterruptStatus;
PGLOBAL_DEVICE_INFO pGDI;
pGDI = Context;
InterruptStatus = PASX_IN(&pGDI->PASInfo, INTRCTLRST);
//
// Why? does this just restore the clipping bit?
//
if (!(InterruptStatus & bISsampbuff)) {
PASX_OUT(&pGDI->PASInfo, INTRCTLRST, (UCHAR)(InterruptStatus | 0x80));
}
//
// Return the clipping bit
//
return (BOOLEAN)InterruptStatus;
}
/*
** Read the current sample level
*/
VOID
HwVUMeter(
IN PGLOBAL_DEVICE_INFO pGDI,
OUT PULONG Volume
)
{
UCHAR InterruptReg;
UCHAR LeftLevel, RightLevel;
if (!IS_MIXER_508(pGDI)) {
return;
}
/*
** Check we're playing
*/
if (!(PASX_IN(&pGDI->PASInfo, CROSSCHANNEL) & bCCenapcm)) {
return;
}
/*
** Read the interrupt status register to get the clipping bits.
*/
InterruptReg = (UCHAR)KeSynchronizeExecution(pGDI->WaveInfo.Interrupt,
HwReadInterruptStatus,
(PVOID)pGDI);
/*
** Read the samples
*/
if (IS_MIXER_508(pGDI)) {
LeftLevel = PASX_IN(&pGDI->PASInfo, VU_LEFT_REG);
RightLevel = PASX_IN(&pGDI->PASInfo, VU_RIGHT_REG);
} else {
LeftLevel = PASX_IN(&pGDI->PASInfo, PCMDATA);
RightLevel = PASX_IN(&pGDI->PASInfo, PCMDATA);
}
/*
** Deal with clipping
*/
if (InterruptReg & bISClip) {
if (InterruptReg & bISPCMlr) {
RightLevel = 0x7F;
} else {
LeftLevel = 0x7F;
}
}
/*
** The values are signed based around 0x80 - so return the abs value
*/
Volume[0] = (ULONG)(LeftLevel & 0x80 ? LeftLevel & 0x7F :
0x7F - LeftLevel);
Volume[1] = (ULONG)(RightLevel & 0x80 ? RightLevel & 0x7F :
0x7F - RightLevel);
}
/************************************ END ***********************************/