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.
1582 lines
28 KiB
1582 lines
28 KiB
/*++
|
|
|
|
Copyright (c) 1993 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
hardware.c
|
|
|
|
Abstract:
|
|
|
|
This module contains code for communicating with the hardware
|
|
on the Microsoft sound system card.
|
|
|
|
This is just a port of the windows code
|
|
|
|
Author:
|
|
|
|
Robin Speed (RobinSp) 20-Oct-1992
|
|
|
|
Environment:
|
|
|
|
Kernel mode
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
#include "sound.h"
|
|
|
|
//
|
|
// Internal routines
|
|
//
|
|
|
|
|
|
WAVE_INTERFACE_ROUTINE HwSetupDMA;
|
|
WAVE_INTERFACE_ROUTINE HwStopDMA;
|
|
|
|
|
|
// WAVE_INTERFACE_ROUTINE HwPauseDMA;
|
|
// WAVE_INTERFACE_ROUTINE HwResumeDMA;
|
|
|
|
VOID
|
|
HwEnterLPM(
|
|
PSOUND_HARDWARE pHw,
|
|
PUCHAR gbReg
|
|
);
|
|
VOID
|
|
HwLeaveLPM(
|
|
PSOUND_HARDWARE pHw,
|
|
PUCHAR gbReg
|
|
);
|
|
VOID
|
|
HwExtMute(
|
|
PSOUND_HARDWARE pHw,
|
|
BOOLEAN On
|
|
);
|
|
BOOLEAN
|
|
CODECWaitForReady(
|
|
PSOUND_HARDWARE pHw
|
|
);
|
|
|
|
BOOLEAN
|
|
HwIsIoValid(
|
|
PSOUND_HARDWARE pHw
|
|
);
|
|
|
|
BOOLEAN
|
|
HwIsDMAValid(
|
|
PSOUND_HARDWARE pHw,
|
|
ULONG DmaChannel
|
|
);
|
|
|
|
BOOLEAN
|
|
HwIsInterruptValid(
|
|
PSOUND_HARDWARE pHw,
|
|
ULONG Interrupt
|
|
);
|
|
|
|
//
|
|
// Remove initialization stuff from resident memory
|
|
//
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
#pragma alloc_text(INIT,HwInitialize)
|
|
#pragma alloc_text(INIT,HwIsIoValid)
|
|
#pragma alloc_text(INIT,HwIsDMAValid)
|
|
#pragma alloc_text(INIT,HwIsInterruptValid)
|
|
#endif
|
|
|
|
/* globals - copied from windows driver */
|
|
|
|
/* crystals to use with what rates */
|
|
#ifdef USEONECRYSTAL
|
|
static CONST UCHAR rateSend[] = {
|
|
0x01, 0x0f,
|
|
0x03, 0x05, 0x07,
|
|
0x0d, 0x09,
|
|
0x0b
|
|
};
|
|
#define NUMRATES (sizeof(rateSend)/sizeof(rateSend[0]))
|
|
static CONST USHORT rates[] = {
|
|
(5510 + 6620) / 2, (6620 + 11025) / 2,
|
|
(USHORT)((11025 + 18900L) / 2),
|
|
(USHORT)((18900L + 22050L) / 2), (USHORT)((22050L + 33075L) / 2),
|
|
(USHORT)((33075L + 37800L) / 2), (USHORT)((37800L + 44100L) / 2)
|
|
};
|
|
static CONST USHORT rate[] = {
|
|
5510, 6620, 11025, 18900, 22050, 33075, 37800, 44100
|
|
};
|
|
#else
|
|
static CONST UCHAR rateSend[] = {
|
|
0x01, 0x0f,
|
|
0x00, 0x0e,
|
|
0x03, 0x02,
|
|
0x05, 0x07,
|
|
0x04, 0x06,
|
|
0x0d, 0x09,
|
|
0x0b, 0x0c
|
|
};
|
|
#define NUMRATES (sizeof(rateSend)/sizeof(rateSend[0]))
|
|
static CONST USHORT rates[] = {
|
|
(5510 + 6620) / 2, (6620 + 8000) / 2,
|
|
(8000 + 9600) / 2, (9600 + 11025) / 2,
|
|
(USHORT)(( 11025 + 16000) / 2), (USHORT)((16000L + 18900L) / 2),
|
|
(USHORT)((18900L + 22050L) / 2), (USHORT)((22050L + 27420L) / 2),
|
|
(USHORT)((27420L + 32000L) / 2), (USHORT)((32000L + 33075L) / 2),
|
|
(USHORT)((33075L + 37800L) / 2), (USHORT)((37800L + 44100L) / 2),
|
|
(USHORT)((44100L + 48000L) / 2)
|
|
};
|
|
static CONST USHORT rate[] = {
|
|
5510, 6620, 8000, 9600, 11025, 16000, 18900, 22050,
|
|
27420, 32000, 33075, 37800, 44100, 48000
|
|
};
|
|
#endif
|
|
|
|
|
|
/*******************************************************************
|
|
*
|
|
* Configuration support routines (called first)
|
|
*
|
|
*******************************************************************/
|
|
|
|
|
|
|
|
BOOLEAN
|
|
HwIsIoValid(
|
|
PSOUND_HARDWARE pHw
|
|
)
|
|
/*++
|
|
|
|
Routine Description :
|
|
|
|
Tests if the currently selected IO port matches that on the
|
|
card.
|
|
|
|
Arguments :
|
|
|
|
pHw - global device info
|
|
|
|
Return Value :
|
|
|
|
TRUE if the device is there, FALSE otherwise
|
|
|
|
--*/
|
|
{
|
|
UCHAR CodecAddress;
|
|
UCHAR CodecVersion;
|
|
|
|
//
|
|
// Check for CODEC presence - this is in three stages :
|
|
// 1. Check CODEC not busy
|
|
// 2. Verify CODEC version
|
|
// 3. Performn write/read test
|
|
//
|
|
// At the same time (if all valid) write into the hardware info
|
|
// whether is might be a compaq board.
|
|
//
|
|
|
|
CodecAddress = INPORT(pHw, CODEC_ADDRESS);
|
|
if (CodecAddress & CODEC_IS_BUSY) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Set CODEC address to MISC_REGISTER - preserve the MCE
|
|
//
|
|
|
|
OUTPORT(pHw, CODEC_ADDRESS, (CodecAddress & 0x40) | REGISTER_MISC);
|
|
|
|
//
|
|
// Read the version
|
|
//
|
|
|
|
CodecVersion = INPORT(pHw, CODEC_DATA);
|
|
|
|
//
|
|
// CS4231 could be in Mode 2... we don't support Mode 2 yet.
|
|
// SO, turn this bit off and then compare the read to the
|
|
// chip version. The bit should've cleared.
|
|
// If bit 7 is set, we're talking to a CS part.
|
|
//
|
|
|
|
//
|
|
// Check the version
|
|
//
|
|
|
|
dprintf2(("Codec Version = %02lXH", (ULONG)CodecVersion));
|
|
|
|
switch ((CodecVersion & 0x80) ? CodecVersion & ~CS4231_MISC_MODE2 :
|
|
CodecVersion) {
|
|
case VER_AD1848J:
|
|
pHw->CODECClass = CODEC_J_CLASS;
|
|
break;
|
|
|
|
case VER_AD1848K:
|
|
pHw->CODECClass = CODEC_K_CLASS;
|
|
break;
|
|
|
|
case VER_CS4248:
|
|
|
|
//
|
|
// Clear mode 2 if set (we don't support it)
|
|
//
|
|
if (CodecVersion & CS4231_MISC_MODE2) {
|
|
OUTPORT(pHw, CODEC_DATA, VER_CS4248);
|
|
OUTPORT(pHw, CODEC_DATA, CS4231_MISC_MODE2);
|
|
|
|
if (INPORT(pHw, CODEC_DATA) & CS4231_MISC_MODE2) {
|
|
pHw->CODECClass = CODEC_KPLUS_CLASS;
|
|
} else {
|
|
pHw->CODECClass = CODEC_K_CLASS;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
goto RestoreAddress;
|
|
}
|
|
|
|
//
|
|
// Do read/write test. Make sure not to modify the top 4 bits of
|
|
// the version as changing these can result in a undocumented test
|
|
// mode to be entered
|
|
// The lower nibble is not writeable so we should get the codec version
|
|
// back again.
|
|
//
|
|
|
|
OUTPORT(pHw, CODEC_DATA, CodecVersion ^ 0x0F);
|
|
if (INPORT(pHw, CODEC_DATA) == CodecVersion) {
|
|
return TRUE;
|
|
} else {
|
|
//
|
|
// Try to restore the version info
|
|
//
|
|
OUTPORT(pHw, CODEC_DATA, CodecVersion);
|
|
}
|
|
|
|
RestoreAddress:
|
|
|
|
//
|
|
// Try to restore the address info
|
|
//
|
|
|
|
OUTPORT(pHw, CODEC_ADDRESS, CodecAddress);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
BOOLEAN
|
|
HwIsDMAValid(
|
|
PSOUND_HARDWARE pHw,
|
|
ULONG DmaChannel
|
|
)
|
|
/*++
|
|
|
|
Routine Description :
|
|
|
|
Tests if the currently selected DMA channel is suitable
|
|
|
|
Arguments :
|
|
|
|
pHw - global device info
|
|
|
|
Return Value :
|
|
|
|
TRUE if the channel is OK, FALSE otherwise
|
|
|
|
--*/
|
|
{
|
|
#if (_ON_PLANNAR_)
|
|
|
|
return TRUE;
|
|
|
|
#else
|
|
|
|
if (pHw->CompaqBA == NULL) {
|
|
|
|
//
|
|
// Can't select channel 0 if the card is in an 8-bit slot
|
|
//
|
|
|
|
return (BOOLEAN)!((INPORT(pHw, BOARD_ID) & 0x80) && DmaChannel == 0);
|
|
} else {
|
|
UCHAR CompaqPIDR;
|
|
|
|
UCHAR Expected;
|
|
|
|
Expected = (UCHAR)(DmaChannel == 0 ? 0x40 :
|
|
DmaChannel == 1 ? 0x80 :
|
|
0xC0);
|
|
|
|
//
|
|
// Compaq machines are not configurable
|
|
//
|
|
|
|
CompaqPIDR = READ_PORT_UCHAR(pHw->CompaqBA + BOARD_ID);
|
|
|
|
if (CompaqPIDR != 0xFF) {
|
|
|
|
if ((UCHAR)(CompaqPIDR & 0xC0) == Expected) {
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
HwIsInterruptValid(
|
|
PSOUND_HARDWARE pHw,
|
|
ULONG Interrupt
|
|
)
|
|
/*++
|
|
|
|
Routine Description :
|
|
|
|
Tests if the currently selected DMA channel is suitable
|
|
|
|
Arguments :
|
|
|
|
pHw - global device info
|
|
|
|
Return Value :
|
|
|
|
TRUE if the channel is OK, FALSE otherwise
|
|
|
|
--*/
|
|
{
|
|
#if (_ON_PLANNAR_)
|
|
|
|
return TRUE;
|
|
|
|
#else
|
|
|
|
if (pHw->CompaqBA == NULL) {
|
|
//
|
|
// Have we checked out the interrupts?
|
|
//
|
|
|
|
if (!pHw->ValidSet && pHw->CompaqBA == NULL) {
|
|
static CONST ULONG InterruptChoices[] = VALID_INTERRUPTS;
|
|
int i;
|
|
|
|
for (i = 0; InterruptChoices[i] != 0xFFFF; i++) {
|
|
UCHAR bConfig;
|
|
ULONG ThisInterrupt;
|
|
|
|
ThisInterrupt = InterruptChoices[i];
|
|
|
|
//
|
|
// See if the card thinks the interrupt is OK
|
|
//
|
|
|
|
bConfig = (UCHAR)(0x40 |
|
|
(ThisInterrupt == 7 ? 0x08 :
|
|
ThisInterrupt == 9 ? 0x10 :
|
|
ThisInterrupt == 10 ? 0x18 :
|
|
ThisInterrupt == 11 ? 0x20 :
|
|
0));
|
|
|
|
OUTPORT(pHw, BOARD_CONFIG, bConfig);
|
|
|
|
if (INPORT(pHw, BOARD_ID) & 0x40) {
|
|
//
|
|
// Interrupt OK
|
|
//
|
|
|
|
pHw->ValidInterrupts |= (1 << ThisInterrupt);
|
|
}
|
|
}
|
|
|
|
|
|
pHw->ValidSet = TRUE;
|
|
}
|
|
|
|
//
|
|
// Can't select interrupts 10 and 11 if the card is in an 8-bit slot
|
|
//
|
|
|
|
return (BOOLEAN)((pHw->ValidInterrupts & (1 << Interrupt)) &&
|
|
!((INPORT(pHw, BOARD_ID) & 0x80) &&
|
|
(Interrupt == 10 || Interrupt == 11)));
|
|
} else {
|
|
UCHAR CompaqPIDR;
|
|
|
|
UCHAR Expected;
|
|
|
|
switch (Interrupt) {
|
|
case 10:
|
|
Expected = 0x10;
|
|
break;
|
|
|
|
case 11:
|
|
Expected = 0x20;
|
|
break;
|
|
|
|
case 7:
|
|
Expected = 0x30;
|
|
break;
|
|
|
|
default:
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Compaq machines are not configurable
|
|
//
|
|
|
|
CompaqPIDR = READ_PORT_UCHAR(pHw->CompaqBA + BOARD_ID);
|
|
|
|
if (CompaqPIDR != 0xFF) {
|
|
|
|
if ((UCHAR)(CompaqPIDR & 0x30) == Expected) {
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/*******************************************************************
|
|
*
|
|
* Synchronization
|
|
*
|
|
*******************************************************************/
|
|
|
|
VOID
|
|
HwEnter(
|
|
PSOUND_HARDWARE pHw
|
|
)
|
|
{
|
|
KeWaitForSingleObject(&pHw->CODECMutex,
|
|
Executive,
|
|
KernelMode,
|
|
FALSE, // Not alertable
|
|
NULL);
|
|
|
|
}
|
|
VOID
|
|
HwLeave(
|
|
PSOUND_HARDWARE pHw
|
|
)
|
|
{
|
|
KeReleaseMutex(&pHw->CODECMutex, FALSE);
|
|
}
|
|
|
|
|
|
/*******************************************************************
|
|
*
|
|
* Initialize structures and real hardware
|
|
*
|
|
*******************************************************************/
|
|
|
|
|
|
|
|
BOOLEAN
|
|
HwInitialize(
|
|
PWAVE_INFO WaveInfo,
|
|
PSOUND_HARDWARE pHw,
|
|
ULONG DmaChannel,
|
|
ULONG Interrupt
|
|
)
|
|
/*++
|
|
|
|
Routine Description :
|
|
|
|
Initialize everything on the card
|
|
|
|
Arguments :
|
|
|
|
pHw - global device info
|
|
DmaChannel - DMA channel to use
|
|
Interrupt - Interrupt to use
|
|
|
|
Return Value :
|
|
|
|
TRUE if OK, FALSE otherwise (timeout on some write)
|
|
|
|
--*/
|
|
{
|
|
UCHAR gbReg[NUMBER_OF_REGISTERS];
|
|
static CONST UCHAR InitRegValues[] = SOUND_REGISTER_INIT;
|
|
|
|
WaveInfo->HwContext = pHw;
|
|
WaveInfo->HwSetupDMA = HwSetupDMA;
|
|
WaveInfo->HwStopDMA = HwStopDMA;
|
|
WaveInfo->HwSetWaveFormat = HwSetWaveFormat;
|
|
// WaveInfo->HwPauseDMA = HwPauseDMA;
|
|
// WaveInfo->HwResumeDMA = HwResumeDMA;
|
|
|
|
pHw->Key = HARDWARE_KEY;
|
|
|
|
pHw->WaitLoop = 1000000;
|
|
|
|
//
|
|
// This value is not a rate used currently!
|
|
//
|
|
pHw->Format = 8;
|
|
|
|
//
|
|
// Initialize CODEC access mutex
|
|
//
|
|
|
|
KeInitializeMutex(&pHw->CODECMutex,
|
|
3 // Level
|
|
);
|
|
|
|
|
|
//
|
|
// First set the board configuration for the DMA channel and
|
|
// interrupt. See game_reg.doc for definitions
|
|
//
|
|
#if !(_ON_PLANNAR_)
|
|
|
|
if (pHw->CompaqBA == NULL) {
|
|
OUTPORT(pHw, BOARD_CONFIG,
|
|
(Interrupt == 7 ? 0x08 :
|
|
Interrupt == 9 ? 0x10 :
|
|
Interrupt == 10 ? 0x18 :
|
|
Interrupt == 11 ? 0x20 :
|
|
0) +
|
|
(DmaChannel == 0 ? 0x01 :
|
|
DmaChannel == 1 ? 0x02 :
|
|
DmaChannel == 3 ? 0x03 :
|
|
0));
|
|
}
|
|
|
|
#endif
|
|
|
|
//
|
|
// Initialize CODEC, making sure its off and muted, and that
|
|
// everything is in a known state.
|
|
//
|
|
|
|
HwEnter(pHw);
|
|
|
|
HwExtMute(pHw, TRUE);
|
|
|
|
//
|
|
// Enter low power mode
|
|
//
|
|
|
|
HwEnterLPM(pHw, gbReg);
|
|
|
|
//
|
|
// Acknowledge any interrupts pending just in case
|
|
//
|
|
|
|
OUTPORT(pHw, CODEC_STATUS, 0);
|
|
CODECRegisterWrite (pHw, REGISTER_INTERFACE, 0x04); /* disable playback, etc. */
|
|
|
|
/* BUGFIX - no number - 7/28/92 - get rid of whining by using 44 kHz */
|
|
CODECRegisterWrite (pHw, REGISTER_DATAFORMAT,
|
|
InitRegValues[REGISTER_DATAFORMAT]);
|
|
|
|
|
|
HwLeaveLPM(pHw, gbReg);
|
|
|
|
//
|
|
// Intialize the PBIC registers
|
|
//
|
|
{
|
|
UCHAR i;
|
|
|
|
for (i = 0; i < 16; i++) {
|
|
CODECRegisterWrite(pHw, i, InitRegValues[i]);
|
|
}
|
|
}
|
|
|
|
HwExtMute(pHw, FALSE);
|
|
|
|
HwLeave(pHw);
|
|
|
|
//
|
|
// See if writes were OK
|
|
//
|
|
|
|
return (BOOLEAN)!pHw->BadBoard;
|
|
}
|
|
|
|
|
|
|
|
/*******************************************************************
|
|
*
|
|
* Volume and mixing control
|
|
*
|
|
*******************************************************************/
|
|
|
|
|
|
|
|
|
|
/*******************************************************************
|
|
*
|
|
* Wave format setting
|
|
*
|
|
*******************************************************************/
|
|
|
|
|
|
ULONG
|
|
HwNearestRate(
|
|
ULONG samPerSec
|
|
)
|
|
/*++
|
|
|
|
Routine Description :
|
|
|
|
Returns nearest rate we support to rate input
|
|
|
|
Arguments :
|
|
|
|
samPerSec - Rate requested
|
|
|
|
Return Value :
|
|
|
|
Nearest rate supported
|
|
|
|
--*/
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
/* find the closest sampling rate */
|
|
|
|
for (i = 0; i < (NUMRATES - 1); i++)
|
|
if (samPerSec < (ULONG)rates[i])
|
|
break;
|
|
|
|
return rate[i];
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
HwSetWaveFormat(
|
|
IN PWAVE_INFO WaveInfo
|
|
)
|
|
/*++
|
|
|
|
Routine Description :
|
|
|
|
Set the wave format as specified for the device
|
|
|
|
Arguments :
|
|
|
|
Context - pointer to device's local device info
|
|
|
|
Return Value :
|
|
|
|
TRUE if format changed, FALSE otherwise
|
|
|
|
--*/
|
|
{
|
|
|
|
UCHAR gbReg[NUMBER_OF_REGISTERS];
|
|
UCHAR send, i;
|
|
PSOUND_HARDWARE pHw;
|
|
|
|
pHw = (PSOUND_HARDWARE)WaveInfo->HwContext;
|
|
|
|
ASSERTMSG("Hardware structure invalid",
|
|
pHw != NULL && pHw->Key == HARDWARE_KEY);
|
|
|
|
/* find the closest sampling rate */
|
|
|
|
for (i = 0, send = rateSend[NUMRATES - 1]; i < (NUMRATES - 1); i++) {
|
|
if (WaveInfo->SamplesPerSec < (ULONG)rates[i]) {
|
|
send = rateSend[i];
|
|
break;
|
|
};
|
|
};
|
|
|
|
/* stereo or mono */
|
|
send |= ((UCHAR) (WaveInfo->Channels - 1) << 4);
|
|
|
|
/* Check for companded formats */
|
|
|
|
if (WaveInfo->WaveFormat != NULL) {
|
|
switch (WaveInfo->WaveFormat->wFormatTag) {
|
|
case WAVE_FORMAT_MULAW:
|
|
send |= FORMAT_MULAW << 5;
|
|
break;
|
|
|
|
case WAVE_FORMAT_ALAW:
|
|
send |= FORMAT_ALAW << 5;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* quantizing format */
|
|
send |= (WaveInfo->BitsPerSample - 8) << 3;
|
|
|
|
|
|
/* set codec to low power mode, write, and set to high power mode.
|
|
Only do this if we have a different sampling rate than before */
|
|
|
|
if (send != pHw->Format) {
|
|
|
|
HwEnter(pHw);
|
|
|
|
//
|
|
// No need to synchronize with the ISR because we're not
|
|
// running any DMA at this point
|
|
//
|
|
|
|
ASSERTMSG("Trying to set format while DMA running!", !WaveInfo->DMABusy);
|
|
|
|
pHw->Format = (UCHAR) send;
|
|
|
|
/* mute the outputs */
|
|
|
|
HwExtMute (pHw, TRUE);
|
|
|
|
HwEnterLPM(pHw, gbReg);
|
|
|
|
CODECRegisterWrite (pHw, REGISTER_DATAFORMAT, pHw->Format);
|
|
|
|
HwLeaveLPM(pHw, gbReg);
|
|
|
|
/* unmute the outputs */
|
|
|
|
HwExtMute (pHw, FALSE);
|
|
|
|
HwLeave(pHw);
|
|
|
|
return TRUE;
|
|
|
|
} else {
|
|
return FALSE;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
VOID
|
|
HwEnterLPM(
|
|
PSOUND_HARDWARE pHw,
|
|
PUCHAR gbReg
|
|
)
|
|
/*++
|
|
|
|
Routine Description :
|
|
|
|
Enter low-power mode. This mutes the PBIC,
|
|
and then tells it to enter LPM. HwLeaveLPM() must
|
|
follow soon after this because this function mutes the
|
|
output.
|
|
|
|
Arguments :
|
|
|
|
pHw - global device info
|
|
|
|
Return Value :
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
UCHAR i;
|
|
UCHAR bTemp;
|
|
|
|
/* remember the old volume registers & then mute each one of them */
|
|
|
|
for (i = REGISTER_LEFTAUX1; i <= REGISTER_RIGHTOUTPUT; i++) {
|
|
gbReg[i] = bTemp = (UCHAR) CODECRegisterRead(pHw, i);
|
|
CODECRegisterWrite (pHw, i, (UCHAR)(bTemp | 0x80));
|
|
};
|
|
|
|
/* make sure that the record gain is not too high 'cause if
|
|
it is we get strange cliping results. This is a bug
|
|
in the pbic */
|
|
|
|
for (i = REGISTER_LEFTINPUT; i <= REGISTER_RIGHTINPUT; i++) {
|
|
gbReg[i] = bTemp = CODECRegisterRead(pHw, i);
|
|
if ((bTemp & 0x0f) > 13)
|
|
bTemp = (UCHAR)((bTemp & (UCHAR)0xf0) | (UCHAR)13);
|
|
CODECRegisterWrite (pHw, i, bTemp);
|
|
};
|
|
|
|
/* turn MCE on */
|
|
|
|
pHw->bPower = LOW_POWER;
|
|
|
|
/* make sure that we're not initializing */
|
|
if ( CODECWaitForReady(pHw) ) {
|
|
OUTPORT (pHw, CODEC_ADDRESS, pHw->bPower);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
VOID
|
|
HwLeaveLPM(
|
|
PSOUND_HARDWARE pHw,
|
|
PUCHAR gbReg
|
|
)
|
|
/*++
|
|
|
|
Routine Description :
|
|
|
|
The leaves low-power mode. It tells the
|
|
PBIC to leave low-power mode, waits for the autocalibration
|
|
to stop, and then un-mutes.
|
|
|
|
A timer is set so that nobody accesses the device too soon
|
|
afterwards. When the timer completes the timer Dpc routine
|
|
will restore the register values
|
|
|
|
Arguments :
|
|
|
|
pHw - global device info
|
|
|
|
Return Value :
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
UCHAR i, bAuto;
|
|
ULONG Time;
|
|
|
|
pHw->bPower = HIGH_POWER;
|
|
|
|
|
|
/* make sure that we're not initializing */
|
|
|
|
if ( CODECWaitForReady(pHw) )
|
|
{
|
|
/* see if we're going to autocalibrate */
|
|
|
|
OUTPORT (pHw, CODEC_ADDRESS, (UCHAR)(pHw->bPower | REGISTER_INTERFACE));
|
|
bAuto = INPORT(pHw, CODEC_DATA);
|
|
|
|
/* if we're going to autocalibrate then wait for it to get done */
|
|
|
|
if (bAuto & AUTO_CAL) {
|
|
OUTPORT (pHw, CODEC_ADDRESS, (UCHAR) (pHw->bPower | REGISTER_TEST));
|
|
|
|
|
|
/* wait for autocalibration to start, and then stop. The current
|
|
register then the test register. */
|
|
|
|
Time = 30;
|
|
while (( (~INPORT(pHw, CODEC_DATA)) & 0x20) && (Time--)) {
|
|
KeStallExecutionProcessor(1);
|
|
}
|
|
Time = pHw->WaitLoop;
|
|
while (( (INPORT(pHw, CODEC_DATA)) & 0x20) && (Time--)) {
|
|
KeStallExecutionProcessor(1);
|
|
};
|
|
};
|
|
|
|
/* wait 10 milliseconds before turning off the internal mute.
|
|
We need to do this to get rid of clicks. */
|
|
|
|
SoundDelay(TIMEDELAY);
|
|
|
|
/* restore the old volume registers */
|
|
|
|
for (i = REGISTER_LEFTINPUT; i <= REGISTER_RIGHTOUTPUT; i++)
|
|
CODECRegisterWrite (pHw, i, gbReg[i]);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
VOID
|
|
HwExtMute(
|
|
PSOUND_HARDWARE pHw,
|
|
BOOLEAN On
|
|
)
|
|
/*++
|
|
|
|
Routine Description :
|
|
|
|
This turns the external mute on/off, with the
|
|
required delays (of 12 millisec when turning off)
|
|
|
|
Arguments :
|
|
|
|
pHw - global device info
|
|
On - turns mute on
|
|
|
|
Return Value :
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
UCHAR PrevDSP;
|
|
|
|
if (pHw->CompaqBA != NULL) {
|
|
PrevDSP = READ_PORT_UCHAR ((PUCHAR)(pHw->CompaqBA + BOARD_ID));
|
|
if (On) {
|
|
PrevDSP |= 0x0A;
|
|
} else {
|
|
PrevDSP &= 0xF8;
|
|
}
|
|
WRITE_PORT_UCHAR((PUCHAR)((pHw->CompaqBA) + BOARD_ID), PrevDSP);
|
|
|
|
SoundDelay(TIMEDELAY);
|
|
|
|
} else {
|
|
#if (_ON_PLANNAR_) //the external mute uses negative logic
|
|
PrevDSP =
|
|
(UCHAR)(CODECRegisterRead(pHw, REGISTER_DSP) | (0xc0));
|
|
|
|
if (On) {
|
|
pHw->gbMuteFilter &= (UCHAR) (~(0x40));
|
|
} else {
|
|
//
|
|
// wait 10 milliseconds before turning off the external mute. We need
|
|
// do this to get rid of clicks.
|
|
//
|
|
SoundDelay(TIMEDELAY);
|
|
pHw->gbMuteFilter |= (UCHAR) ((0x40));
|
|
}
|
|
#else
|
|
PrevDSP =
|
|
(UCHAR)(CODECRegisterRead(pHw, REGISTER_DSP) & ~(0xc0));
|
|
|
|
if (On) {
|
|
pHw->gbMuteFilter |= (UCHAR) ((0x40));
|
|
} else {
|
|
//
|
|
// wait 10 milliseconds before turning off the external mute. We need
|
|
// do this to get rid of clicks.
|
|
//
|
|
SoundDelay(TIMEDELAY);
|
|
pHw->gbMuteFilter &= (UCHAR) (~(0x40));
|
|
}
|
|
#endif
|
|
CODECRegisterWrite (
|
|
pHw,
|
|
REGISTER_DSP,
|
|
(UCHAR)(PrevDSP | pHw->gbMuteFilter));
|
|
}
|
|
}
|
|
|
|
|
|
|
|
BOOLEAN
|
|
HwSetupDMA(
|
|
PWAVE_INFO WaveInfo
|
|
)
|
|
/*++
|
|
|
|
Routine Description :
|
|
|
|
Start the DMA
|
|
|
|
We need to be synchronized to do this so we don't get
|
|
confused by interrupts
|
|
|
|
Arguments :
|
|
|
|
Context - global device info
|
|
|
|
Return Value :
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
UCHAR gbReg[NUMBER_OF_REGISTERS]; // For enter/leave lpm
|
|
ULONG CODECSamples;
|
|
PSOUND_HARDWARE pHw;
|
|
PGLOBAL_DEVICE_INFO pGDI;
|
|
|
|
pHw = (PSOUND_HARDWARE)WaveInfo->HwContext;
|
|
pGDI = CONTAINING_RECORD(pHw, GLOBAL_DEVICE_INFO, Hw);
|
|
|
|
ASSERTMSG("Hardware structure invalid",
|
|
pHw != NULL && pHw->Key == HARDWARE_KEY);
|
|
|
|
HwEnter(pHw);
|
|
|
|
//
|
|
// clear any pending interrupts
|
|
// Why ???
|
|
//
|
|
|
|
CODECRegisterWrite (pHw, REGISTER_DSP, pHw->gbMuteFilter);
|
|
OUTPORT (pHw, CODEC_STATUS, 0);
|
|
|
|
|
|
if (!WaveInfo->Direction) {
|
|
if (pHw->CODECClass == CODEC_J_CLASS) {
|
|
|
|
//
|
|
// Mute the outputs for record
|
|
//
|
|
|
|
HwExtMute(pHw, TRUE);
|
|
}
|
|
|
|
MixSetADCHardware(pGDI, (ULONG)-1L);
|
|
|
|
/* IMPORTANT: it seems that we need to enter LPM before recording
|
|
or it doesnt record right! */
|
|
|
|
/* enter lpm */
|
|
|
|
HwEnterLPM(pHw, gbReg);
|
|
|
|
} else {
|
|
|
|
//
|
|
// Set the volume for this device
|
|
//
|
|
|
|
MixSetVolume(pGDI, ControlLineoutWaveoutVolume);
|
|
}
|
|
|
|
//
|
|
// tell the codec's DMA how many samples between each interrupt
|
|
//
|
|
|
|
CODECSamples = (WaveInfo->DoubleBuffer.BufferSize << 2) /
|
|
(WaveInfo->Channels * WaveInfo->BitsPerSample) - 1;
|
|
|
|
CODECRegisterWrite (pHw,
|
|
REGISTER_LOWERBASE,
|
|
(UCHAR) (CODECSamples & 0xFF)); /* low count */
|
|
|
|
CODECRegisterWrite (pHw,
|
|
REGISTER_UPPERBASE,
|
|
(UCHAR) ((CODECSamples >> 8) & 0xFF));/* high count */
|
|
|
|
|
|
//
|
|
// If we're paused we should be using resume
|
|
//
|
|
|
|
ASSERTMSG("Start output DMA called in paused state!",
|
|
!WaveInfo->Direction ||
|
|
((PLOCAL_DEVICE_INFO)WaveInfo->DeviceObject->DeviceExtension)->
|
|
State != WAVE_DD_STOPPED);
|
|
|
|
//
|
|
// say that we want to record or play
|
|
//
|
|
|
|
CODECRegisterWrite (pHw,
|
|
REGISTER_INTERFACE,
|
|
(UCHAR)(!WaveInfo->Direction ? 0x46 | AUTO_CAL :
|
|
0x05));
|
|
|
|
//
|
|
// start the dma - from now on we can get interrupts
|
|
//
|
|
|
|
CODECRegisterWrite (pHw,
|
|
REGISTER_DSP,
|
|
(UCHAR)(pHw->gbMuteFilter | (UCHAR)(0x02)));
|
|
|
|
|
|
if (!WaveInfo->Direction) {
|
|
|
|
// we're going into high power to record
|
|
//
|
|
// We can get interrupts here but nobody else programs the DMA
|
|
// while it's running
|
|
|
|
HwLeaveLPM(pHw, gbReg);
|
|
|
|
if (pHw->CODECClass == CODEC_J_CLASS) {
|
|
HwExtMute(pHw, FALSE);
|
|
}
|
|
}
|
|
|
|
HwLeave(pHw);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
VOID
|
|
HwDo11KHz(
|
|
PVOID Context
|
|
)
|
|
/*++
|
|
|
|
Routine Description :
|
|
|
|
Stop the card hissing after we complete a low sampling rate
|
|
sound.
|
|
|
|
Arguments :
|
|
|
|
Context - Hardware context
|
|
|
|
Return Value :
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
PSOUND_HARDWARE pHw;
|
|
UCHAR gbReg[NUMBER_OF_REGISTERS];
|
|
|
|
pHw = Context;
|
|
|
|
dprintf2(("Quiescing hissing at low rates"));
|
|
|
|
HwExtMute (pHw, TRUE);
|
|
HwEnterLPM(pHw, gbReg);
|
|
HwLeaveLPM(pHw, gbReg);
|
|
HwExtMute (pHw, FALSE);
|
|
}
|
|
|
|
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 ;
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
HwStopDMA(
|
|
PWAVE_INFO WaveInfo
|
|
)
|
|
/*++
|
|
|
|
Routine Description :
|
|
|
|
Stop the DMA
|
|
|
|
Whoever calls this routine had better first make sure that no
|
|
Dpc routine is going to run in parallel!
|
|
|
|
For wave input the caller MUST NOT own the spin lock because
|
|
we're going to do waits in here
|
|
|
|
Note we're also NOT doing the windows hack for wave out
|
|
when the sampling rate is too low because that would
|
|
involve doing waits in a dpc or some other complex design.
|
|
|
|
Arguments :
|
|
|
|
Context - global device info
|
|
|
|
Return Value :
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
UCHAR gbReg[NUMBER_OF_REGISTERS]; // For enter/leave lpm
|
|
PSOUND_HARDWARE pHw;
|
|
PGLOBAL_DEVICE_INFO pGDI;
|
|
|
|
pHw = (PSOUND_HARDWARE)WaveInfo->HwContext;
|
|
pGDI = (PGLOBAL_DEVICE_INFO)CONTAINING_RECORD(pHw, GLOBAL_DEVICE_INFO, Hw);
|
|
|
|
ASSERTMSG("Hardware structure invalid",
|
|
pHw != NULL && pHw->Key == HARDWARE_KEY);
|
|
|
|
HwEnter(pHw);
|
|
|
|
if (pHw->CODECClass == CODEC_J_CLASS && !WaveInfo->Direction) {
|
|
HwExtMute (pHw, TRUE);
|
|
}
|
|
|
|
//
|
|
// turn off the DAC outputs, and minimized ADC gain
|
|
//
|
|
|
|
CODECRegisterWrite (pHw, REGISTER_LEFTOUTPUT, 0x3f);
|
|
CODECRegisterWrite (pHw, REGISTER_RIGHTOUTPUT, 0x3f);
|
|
CODECRegisterWrite (pHw, REGISTER_LEFTINPUT, 0x00);
|
|
CODECRegisterWrite (pHw, REGISTER_RIGHTINPUT, 0x00);
|
|
|
|
if (!WaveInfo->Direction) {
|
|
HwEnterLPM(pHw, gbReg);
|
|
}
|
|
|
|
//
|
|
// tell the DSP to shut off
|
|
//
|
|
|
|
CODECRegisterWrite (pHw,
|
|
REGISTER_DSP,
|
|
pHw->gbMuteFilter); /* diable the interrupts */
|
|
|
|
OUTPORT (pHw, CODEC_STATUS, 0x00); /* clear any pending */
|
|
|
|
CODECRegisterWrite (pHw,
|
|
REGISTER_INTERFACE,
|
|
0x00); /* kill DMA */
|
|
|
|
if (!WaveInfo->Direction) {
|
|
HwLeaveLPM(pHw, gbReg);
|
|
}
|
|
|
|
HwWaitForTxComplete( WaveInfo );
|
|
|
|
//
|
|
// wait for the DMA to stop
|
|
//
|
|
|
|
if (WaveInfo->Direction) {
|
|
// HwDMAWaitForTxComplete(pHw);
|
|
|
|
|
|
// If we want to fix this we need a thread to run this on
|
|
|
|
/* BUGFIX:1199 - 7/21/92 - Mike Rozak */
|
|
/* if 11 kHz playback then enter/leave MCE to reduce the noise */
|
|
if (pHw->CODECClass == CODEC_J_CLASS &&
|
|
WaveInfo->SamplesPerSec <= 17000) {
|
|
|
|
HwDo11KHz(pHw);
|
|
};
|
|
|
|
} else {
|
|
// HwDMAWaitForTxComplete(pHw);
|
|
};
|
|
|
|
if (pHw->CODECClass == CODEC_J_CLASS && !WaveInfo->Direction) {
|
|
HwExtMute (pHw, FALSE);
|
|
};
|
|
|
|
HwLeave(pHw);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
BOOLEAN
|
|
HwPauseDAC(
|
|
PWAVE_INFO WaveInfo
|
|
)
|
|
/*++
|
|
|
|
Routine Description :
|
|
|
|
Pause output DMA
|
|
|
|
This routine runs at DEVICE level because the DMA is probably
|
|
running
|
|
|
|
Arguments :
|
|
|
|
Context - global device info
|
|
|
|
Return Value :
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
PSOUND_HARDWARE pHw;
|
|
|
|
pHw = WaveInfo->HwContext;
|
|
|
|
HwEnter(pHw);
|
|
|
|
//
|
|
// However, someone may have paused us first to fix overrun
|
|
//
|
|
|
|
if (!pHw->Paused) {
|
|
|
|
/* turn off the DAC outputs */
|
|
|
|
CODECRegisterWrite (pHw, REGISTER_LEFTOUTPUT, 0x3f);
|
|
CODECRegisterWrite (pHw, REGISTER_RIGHTOUTPUT, 0x3f);
|
|
|
|
/* tell the CODEC to pause */
|
|
|
|
CODECRegisterWrite (pHw, REGISTER_INTERFACE, 0x04); /* dont want to play or record */
|
|
CODECRegisterWrite (pHw,
|
|
REGISTER_DSP,
|
|
(UCHAR)(pHw->gbMuteFilter |
|
|
(UCHAR) 0x00)); /* stop the interrupts */
|
|
}
|
|
|
|
HwLeave(pHw);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
|
|
BOOLEAN
|
|
HwResumeDAC(
|
|
PVOID Context
|
|
)
|
|
/*++
|
|
|
|
Routine Description :
|
|
|
|
Resume output DMA
|
|
|
|
This routine runs at DEVICE level because the DMA is probably
|
|
running
|
|
|
|
Arguments :
|
|
|
|
Context - global device info
|
|
|
|
Return Value :
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
|
|
PSOUND_HARDWARE pHw;
|
|
|
|
pHw = Context;
|
|
|
|
//
|
|
// However, someone may have resumed us first to fix overrun
|
|
//
|
|
|
|
if (!pHw->Paused) {
|
|
|
|
HwEnter(pHw);
|
|
|
|
/* send to CODEC saying to continue */
|
|
CODECRegisterWrite (pHw, REGISTER_INTERFACE, 0x05); /* want to play & not record */
|
|
CODECRegisterWrite (pHw,
|
|
REGISTER_DSP,
|
|
(UCHAR)(pHw->gbMuteFilter | (UCHAR)0x02)); /* start interrupts */
|
|
|
|
/* turn on the DAC outputs */
|
|
|
|
HwSetVolume(...)
|
|
CODECRegisterWrite (pHw, REGISTER_LEFTOUTPUT, pHw->DACToPBICLeft);
|
|
CODECRegisterWrite (pHw, REGISTER_RIGHTOUTPUT, pHw->DACToPBICRight);
|
|
|
|
HwLeave(pHw);
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
//
|
|
// Routines to read/write the PBIC (??) registers
|
|
//
|
|
|
|
|
|
BOOLEAN
|
|
CODECWaitForReady(
|
|
IN PSOUND_HARDWARE pHw
|
|
)
|
|
/*++
|
|
|
|
Routine Description :
|
|
|
|
Wait for PBIC to finish initializing (if it is). This function
|
|
will timeout after XX milliseconds if the hardware is not functioning
|
|
properly (return FALSE).
|
|
|
|
If the PBIC is not initializing, then this function will return
|
|
immediately (which should be the normal case).
|
|
|
|
The timeout value must be at least 256 samples at 8kHz. We compute
|
|
this for 300 samples at 8kHz to be safe.
|
|
|
|
This should be 300/8192 = 36 milliseconds (!)
|
|
|
|
Arguments :
|
|
|
|
pHw - global device info
|
|
|
|
Return Value :
|
|
|
|
TRUE if wait was successful
|
|
|
|
--*/
|
|
{
|
|
int i;
|
|
|
|
|
|
ASSERTMSG("Bad hardware structure key", pHw->Key == HARDWARE_KEY);
|
|
|
|
//
|
|
// Make sure the Mutex is being used
|
|
//
|
|
|
|
//INVALID: ASSERT(KeReadStateMutex(&pHw->CODECMutex) != 1);
|
|
|
|
if (pHw->BadBoard) {
|
|
return FALSE; // Failed previously
|
|
}
|
|
|
|
if (!(INPORT(pHw, CODEC_ADDRESS) & CODEC_IS_BUSY)) {
|
|
return TRUE;
|
|
}
|
|
|
|
for (i = 0; i < 36000; i++) {
|
|
KeStallExecutionProcessor(10); // Hope this only happens at
|
|
// PASSIVE level!
|
|
|
|
if (!(INPORT(pHw, CODEC_ADDRESS) & CODEC_IS_BUSY)) {
|
|
return TRUE;
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// Timed out
|
|
//
|
|
|
|
pHw->BadBoard = TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
VOID
|
|
CODECRegisterWrite(
|
|
IN PSOUND_HARDWARE pHw,
|
|
IN UCHAR RegisterNumber,
|
|
IN UCHAR Value
|
|
)
|
|
/*++
|
|
|
|
Routine Description :
|
|
|
|
Write to one of the wave registers
|
|
|
|
Arguments :
|
|
|
|
pHw - global device info
|
|
RegisterNumber - register to use
|
|
Value - value to write
|
|
|
|
Return Value :
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
//
|
|
// Wait for PBIC
|
|
//
|
|
|
|
if (!CODECWaitForReady(pHw)) {
|
|
//
|
|
// Bad board!
|
|
//
|
|
return;
|
|
}
|
|
|
|
|
|
//
|
|
// Select the register
|
|
//
|
|
|
|
OUTPORT(pHw, CODEC_ADDRESS, pHw->bPower | RegisterNumber);
|
|
|
|
//
|
|
// Write the value to the data port
|
|
//
|
|
|
|
OUTPORT(pHw, CODEC_DATA, Value);
|
|
}
|
|
|
|
|
|
UCHAR
|
|
CODECRegisterRead(
|
|
IN PSOUND_HARDWARE pHw,
|
|
IN UCHAR RegisterNumber
|
|
)
|
|
/*++
|
|
|
|
Routine Description :
|
|
|
|
Read from one of the wave registers
|
|
|
|
Arguments :
|
|
|
|
pHw - global device info
|
|
RegisterNumber - register to use
|
|
|
|
Return Value :
|
|
|
|
Value read
|
|
|
|
--*/
|
|
{
|
|
//
|
|
// Wait for PBIC
|
|
//
|
|
|
|
if (!CODECWaitForReady(pHw)) {
|
|
//
|
|
// Bad board!
|
|
//
|
|
return 0xFF;
|
|
}
|
|
|
|
|
|
//
|
|
// Select the register
|
|
//
|
|
|
|
OUTPORT(pHw, CODEC_ADDRESS, pHw->bPower | RegisterNumber);
|
|
|
|
//
|
|
// Read the value from the data port
|
|
//
|
|
|
|
return INPORT(pHw, CODEC_DATA);
|
|
}
|
|
|
|
|