1521 lines
61 KiB
C++
1521 lines
61 KiB
C++
|
/*****************************************************************************
|
||
|
* mintopo.cpp - SB16 topology miniport implementation
|
||
|
*****************************************************************************
|
||
|
* Copyright (c) 1997-2000 Microsoft Corporation. All Rights Reserved.
|
||
|
*/
|
||
|
|
||
|
#include "limits.h"
|
||
|
#include "mintopo.h"
|
||
|
|
||
|
#define STR_MODULENAME "sb16topo: "
|
||
|
|
||
|
#define CHAN_LEFT 0
|
||
|
#define CHAN_RIGHT 1
|
||
|
#define CHAN_MASTER (-1)
|
||
|
|
||
|
|
||
|
#pragma code_seg("PAGE")
|
||
|
|
||
|
|
||
|
/*****************************************************************************
|
||
|
* CreateMiniportTopologySB16()
|
||
|
*****************************************************************************
|
||
|
* Creates a topology miniport object for the SB16 adapter. This uses a
|
||
|
* macro from STDUNK.H to do all the work.
|
||
|
*/
|
||
|
NTSTATUS
|
||
|
CreateMiniportTopologySB16
|
||
|
(
|
||
|
OUT PUNKNOWN * Unknown,
|
||
|
IN REFCLSID,
|
||
|
IN PUNKNOWN UnknownOuter OPTIONAL,
|
||
|
IN POOL_TYPE PoolType
|
||
|
)
|
||
|
{
|
||
|
PAGED_CODE();
|
||
|
|
||
|
ASSERT(Unknown);
|
||
|
|
||
|
STD_CREATE_BODY_(CMiniportTopologySB16,Unknown,UnknownOuter,PoolType,PMINIPORTTOPOLOGY);
|
||
|
}
|
||
|
|
||
|
/*****************************************************************************
|
||
|
* CMiniportTopologySB16::NonDelegatingQueryInterface()
|
||
|
*****************************************************************************
|
||
|
* Obtains an interface. This function works just like a COM QueryInterface
|
||
|
* call and is used if the object is not being aggregated.
|
||
|
*/
|
||
|
STDMETHODIMP
|
||
|
CMiniportTopologySB16::
|
||
|
NonDelegatingQueryInterface
|
||
|
(
|
||
|
IN REFIID Interface,
|
||
|
OUT PVOID * Object
|
||
|
)
|
||
|
{
|
||
|
PAGED_CODE();
|
||
|
|
||
|
ASSERT(Object);
|
||
|
|
||
|
_DbgPrintF(DEBUGLVL_VERBOSE,("[CMiniportTopologySB16::NonDelegatingQueryInterface]"));
|
||
|
|
||
|
if (IsEqualGUIDAligned(Interface,IID_IUnknown))
|
||
|
{
|
||
|
*Object = PVOID(PUNKNOWN(PMINIPORTTOPOLOGY(this)));
|
||
|
}
|
||
|
else
|
||
|
if (IsEqualGUIDAligned(Interface,IID_IMiniport))
|
||
|
{
|
||
|
*Object = PVOID(PMINIPORT(this));
|
||
|
}
|
||
|
else
|
||
|
if (IsEqualGUIDAligned(Interface,IID_IMiniportTopology))
|
||
|
{
|
||
|
*Object = PVOID(PMINIPORTTOPOLOGY(this));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
*Object = NULL;
|
||
|
}
|
||
|
|
||
|
if (*Object)
|
||
|
{
|
||
|
//
|
||
|
// We reference the interface for the caller.
|
||
|
//
|
||
|
PUNKNOWN(*Object)->AddRef();
|
||
|
return STATUS_SUCCESS;
|
||
|
}
|
||
|
|
||
|
return STATUS_INVALID_PARAMETER;
|
||
|
}
|
||
|
|
||
|
/*****************************************************************************
|
||
|
* CMiniportTopologySB16::~CMiniportTopologySB16()
|
||
|
*****************************************************************************
|
||
|
* Destructor.
|
||
|
*/
|
||
|
CMiniportTopologySB16::
|
||
|
~CMiniportTopologySB16
|
||
|
( void
|
||
|
)
|
||
|
{
|
||
|
PAGED_CODE();
|
||
|
|
||
|
_DbgPrintF(DEBUGLVL_VERBOSE,("[CMiniportTopologySB16::~CMiniportTopologySB16]"));
|
||
|
|
||
|
if (AdapterCommon)
|
||
|
{
|
||
|
#ifdef EVENT_SUPPORT
|
||
|
AdapterCommon->SetTopologyMiniport (NULL);
|
||
|
#endif
|
||
|
AdapterCommon->SaveMixerSettingsToRegistry();
|
||
|
AdapterCommon->Release();
|
||
|
}
|
||
|
#ifdef EVENT_SUPPORT
|
||
|
if (PortEvents)
|
||
|
{
|
||
|
PortEvents->Release ();
|
||
|
PortEvents = NULL;
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
/*****************************************************************************
|
||
|
* CMiniportTopologySB16::Init()
|
||
|
*****************************************************************************
|
||
|
* Initializes a the miniport.
|
||
|
*/
|
||
|
STDMETHODIMP
|
||
|
CMiniportTopologySB16::
|
||
|
Init
|
||
|
(
|
||
|
IN PUNKNOWN UnknownAdapter,
|
||
|
IN PRESOURCELIST ResourceList,
|
||
|
IN PPORTTOPOLOGY Port
|
||
|
)
|
||
|
{
|
||
|
PAGED_CODE();
|
||
|
|
||
|
ASSERT(UnknownAdapter);
|
||
|
ASSERT(Port);
|
||
|
|
||
|
_DbgPrintF(DEBUGLVL_VERBOSE,("[CMiniportTopologySB16::Init]"));
|
||
|
|
||
|
NTSTATUS ntStatus =
|
||
|
UnknownAdapter->QueryInterface
|
||
|
(
|
||
|
IID_IAdapterCommon,
|
||
|
(PVOID *) &AdapterCommon
|
||
|
);
|
||
|
|
||
|
if (NT_SUCCESS(ntStatus))
|
||
|
{
|
||
|
#ifdef EVENT_SUPPORT
|
||
|
//
|
||
|
// Get the port event interface.
|
||
|
//
|
||
|
NTSTATUS ntStatus2 = Port->QueryInterface (IID_IPortEvents, (PVOID *)&PortEvents);
|
||
|
if (NT_SUCCESS(ntStatus2))
|
||
|
{
|
||
|
//
|
||
|
// We need to notify AdapterCommon of the miniport interface.
|
||
|
// AdapterCommon needs this in his ISR to fire the event.
|
||
|
//
|
||
|
AdapterCommon->SetTopologyMiniport ((PTOPOMINIPORTSB16)this);
|
||
|
|
||
|
//
|
||
|
// Enable external volume control interrupt.
|
||
|
//
|
||
|
BYTE bIntrMask = AdapterCommon->MixerRegRead (0x83);
|
||
|
bIntrMask |= 0x10;
|
||
|
AdapterCommon->MixerRegWrite (0x83, bIntrMask);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
AdapterCommon->MixerReset();
|
||
|
}
|
||
|
|
||
|
return ntStatus;
|
||
|
}
|
||
|
|
||
|
/*****************************************************************************
|
||
|
* CMiniportTopologySB16::GetDescription()
|
||
|
*****************************************************************************
|
||
|
* Gets the topology.
|
||
|
*/
|
||
|
STDMETHODIMP
|
||
|
CMiniportTopologySB16::
|
||
|
GetDescription
|
||
|
(
|
||
|
OUT PPCFILTER_DESCRIPTOR * OutFilterDescriptor
|
||
|
)
|
||
|
{
|
||
|
PAGED_CODE();
|
||
|
|
||
|
ASSERT(OutFilterDescriptor);
|
||
|
|
||
|
_DbgPrintF(DEBUGLVL_VERBOSE,("[CMiniportTopologySB16::GetDescription]"));
|
||
|
|
||
|
*OutFilterDescriptor = &MiniportFilterDescriptor;
|
||
|
|
||
|
return STATUS_SUCCESS;
|
||
|
}
|
||
|
|
||
|
/*****************************************************************************
|
||
|
* PropertyHandler_OnOff()
|
||
|
*****************************************************************************
|
||
|
* Accesses a KSAUDIO_ONOFF value property.
|
||
|
*/
|
||
|
static
|
||
|
NTSTATUS
|
||
|
PropertyHandler_OnOff
|
||
|
(
|
||
|
IN PPCPROPERTY_REQUEST PropertyRequest
|
||
|
)
|
||
|
{
|
||
|
PAGED_CODE();
|
||
|
|
||
|
ASSERT(PropertyRequest);
|
||
|
|
||
|
_DbgPrintF(DEBUGLVL_VERBOSE,("[PropertyHandler_OnOff]"));
|
||
|
|
||
|
CMiniportTopologySB16 *that =
|
||
|
(CMiniportTopologySB16 *) ((PMINIPORTTOPOLOGY) PropertyRequest->MajorTarget);
|
||
|
|
||
|
NTSTATUS ntStatus = STATUS_INVALID_PARAMETER;
|
||
|
BYTE data;
|
||
|
LONG channel;
|
||
|
|
||
|
// validate node
|
||
|
if (PropertyRequest->Node != ULONG(-1))
|
||
|
{
|
||
|
if(PropertyRequest->Verb & KSPROPERTY_TYPE_GET)
|
||
|
{
|
||
|
// get the instance channel parameter
|
||
|
if(PropertyRequest->InstanceSize >= sizeof(LONG))
|
||
|
{
|
||
|
channel = *(PLONG(PropertyRequest->Instance));
|
||
|
|
||
|
// validate and get the output parameter
|
||
|
if (PropertyRequest->ValueSize >= sizeof(BOOL))
|
||
|
{
|
||
|
PBOOL OnOff = PBOOL(PropertyRequest->Value);
|
||
|
|
||
|
// switch on node id
|
||
|
switch(PropertyRequest->Node)
|
||
|
{
|
||
|
case MIC_AGC: // Microphone AGC Control (mono)
|
||
|
// check if AGC property request on mono/left channel
|
||
|
if( ( PropertyRequest->PropertyItem->Id == KSPROPERTY_AUDIO_AGC ) &&
|
||
|
( channel == CHAN_LEFT ) )
|
||
|
{
|
||
|
data = that->ReadBitsFromMixer( DSP_MIX_AGCIDX,
|
||
|
1,
|
||
|
MIXBIT_MIC_AGC );
|
||
|
*OnOff = data ? FALSE : TRUE;
|
||
|
PropertyRequest->ValueSize = sizeof(BOOL);
|
||
|
ntStatus = STATUS_SUCCESS;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case MIC_LINEOUT_MUTE: // Microphone Lineout Mute Control (mono)
|
||
|
// check if MUTE property request on mono/left channel
|
||
|
if( ( PropertyRequest->PropertyItem->Id == KSPROPERTY_AUDIO_MUTE ) &&
|
||
|
( channel == CHAN_LEFT ) )
|
||
|
{
|
||
|
data = that->ReadBitsFromMixer( DSP_MIX_OUTMIXIDX,
|
||
|
1,
|
||
|
MIXBIT_MIC_LINEOUT );
|
||
|
*OnOff = data ? FALSE : TRUE;
|
||
|
PropertyRequest->ValueSize = sizeof(BOOL);
|
||
|
ntStatus = STATUS_SUCCESS;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
} else if(PropertyRequest->Verb & KSPROPERTY_TYPE_SET)
|
||
|
{
|
||
|
// get the instance channel parameter
|
||
|
if(PropertyRequest->InstanceSize >= sizeof(LONG))
|
||
|
{
|
||
|
channel = *(PLONG(PropertyRequest->Instance));
|
||
|
|
||
|
// validate and get the input parameter
|
||
|
if (PropertyRequest->ValueSize == sizeof(BOOL))
|
||
|
{
|
||
|
BYTE value = *(PBOOL(PropertyRequest->Value)) ? 0 : 1;
|
||
|
|
||
|
// switch on the node id
|
||
|
switch(PropertyRequest->Node)
|
||
|
{
|
||
|
case MIC_AGC: // Microphone AGC Control (mono)
|
||
|
// check if AGC property request on mono/left channel
|
||
|
if( ( PropertyRequest->PropertyItem->Id == KSPROPERTY_AUDIO_AGC ) &&
|
||
|
( channel == CHAN_LEFT ) )
|
||
|
{
|
||
|
that->WriteBitsToMixer( DSP_MIX_AGCIDX,
|
||
|
1,
|
||
|
MIXBIT_MIC_AGC,
|
||
|
value );
|
||
|
ntStatus = STATUS_SUCCESS;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case MIC_LINEOUT_MUTE: // Microphone Lineout Mute Control (mono)
|
||
|
// check if MUTE property request on mono/left channel
|
||
|
if( ( PropertyRequest->PropertyItem->Id == KSPROPERTY_AUDIO_MUTE ) &&
|
||
|
( channel == CHAN_LEFT ) )
|
||
|
{
|
||
|
that->WriteBitsToMixer( DSP_MIX_OUTMIXIDX,
|
||
|
1,
|
||
|
MIXBIT_MIC_LINEOUT,
|
||
|
value );
|
||
|
ntStatus = STATUS_SUCCESS;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
} else if(PropertyRequest->Verb & KSPROPERTY_TYPE_BASICSUPPORT)
|
||
|
{
|
||
|
if ( ( (PropertyRequest->Node == MIC_AGC) && (PropertyRequest->PropertyItem->Id == KSPROPERTY_AUDIO_AGC) ) ||
|
||
|
( (PropertyRequest->Node == MIC_LINEOUT_MUTE) && (PropertyRequest->PropertyItem->Id == KSPROPERTY_AUDIO_MUTE) ) )
|
||
|
{
|
||
|
if(PropertyRequest->ValueSize >= (sizeof(KSPROPERTY_DESCRIPTION)))
|
||
|
{
|
||
|
// if return buffer can hold a KSPROPERTY_DESCRIPTION, return it
|
||
|
PKSPROPERTY_DESCRIPTION PropDesc = PKSPROPERTY_DESCRIPTION(PropertyRequest->Value);
|
||
|
|
||
|
PropDesc->AccessFlags = KSPROPERTY_TYPE_BASICSUPPORT |
|
||
|
KSPROPERTY_TYPE_GET |
|
||
|
KSPROPERTY_TYPE_SET;
|
||
|
PropDesc->DescriptionSize = sizeof(KSPROPERTY_DESCRIPTION);
|
||
|
PropDesc->PropTypeSet.Set = KSPROPTYPESETID_General;
|
||
|
PropDesc->PropTypeSet.Id = VT_BOOL;
|
||
|
PropDesc->PropTypeSet.Flags = 0;
|
||
|
PropDesc->MembersListCount = 0;
|
||
|
PropDesc->Reserved = 0;
|
||
|
|
||
|
// set the return value size
|
||
|
PropertyRequest->ValueSize = sizeof(KSPROPERTY_DESCRIPTION);
|
||
|
ntStatus = STATUS_SUCCESS;
|
||
|
} else if(PropertyRequest->ValueSize >= sizeof(ULONG))
|
||
|
{
|
||
|
// if return buffer can hold a ULONG, return the access flags
|
||
|
PULONG AccessFlags = PULONG(PropertyRequest->Value);
|
||
|
|
||
|
*AccessFlags = KSPROPERTY_TYPE_BASICSUPPORT |
|
||
|
KSPROPERTY_TYPE_GET |
|
||
|
KSPROPERTY_TYPE_SET;
|
||
|
|
||
|
// set the return value size
|
||
|
PropertyRequest->ValueSize = sizeof(ULONG);
|
||
|
ntStatus = STATUS_SUCCESS;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return ntStatus;
|
||
|
}
|
||
|
|
||
|
/*****************************************************************************
|
||
|
* BasicSupportHandler()
|
||
|
*****************************************************************************
|
||
|
* Assists in BASICSUPPORT accesses on level properties
|
||
|
*/
|
||
|
static
|
||
|
NTSTATUS
|
||
|
BasicSupportHandler
|
||
|
(
|
||
|
IN PPCPROPERTY_REQUEST PropertyRequest
|
||
|
)
|
||
|
{
|
||
|
PAGED_CODE();
|
||
|
|
||
|
ASSERT(PropertyRequest);
|
||
|
|
||
|
_DbgPrintF(DEBUGLVL_VERBOSE,("[BasicSupportHandler]"));
|
||
|
|
||
|
NTSTATUS ntStatus = STATUS_INVALID_DEVICE_REQUEST;
|
||
|
|
||
|
if(PropertyRequest->ValueSize >= (sizeof(KSPROPERTY_DESCRIPTION)))
|
||
|
{
|
||
|
// if return buffer can hold a KSPROPERTY_DESCRIPTION, return it
|
||
|
PKSPROPERTY_DESCRIPTION PropDesc = PKSPROPERTY_DESCRIPTION(PropertyRequest->Value);
|
||
|
|
||
|
PropDesc->AccessFlags = KSPROPERTY_TYPE_BASICSUPPORT |
|
||
|
KSPROPERTY_TYPE_GET |
|
||
|
KSPROPERTY_TYPE_SET;
|
||
|
PropDesc->DescriptionSize = sizeof(KSPROPERTY_DESCRIPTION) +
|
||
|
sizeof(KSPROPERTY_MEMBERSHEADER) +
|
||
|
sizeof(KSPROPERTY_STEPPING_LONG);
|
||
|
PropDesc->PropTypeSet.Set = KSPROPTYPESETID_General;
|
||
|
PropDesc->PropTypeSet.Id = VT_I4;
|
||
|
PropDesc->PropTypeSet.Flags = 0;
|
||
|
PropDesc->MembersListCount = 1;
|
||
|
PropDesc->Reserved = 0;
|
||
|
|
||
|
// if return buffer cn also hold a range description, return it too
|
||
|
if(PropertyRequest->ValueSize >= (sizeof(KSPROPERTY_DESCRIPTION) +
|
||
|
sizeof(KSPROPERTY_MEMBERSHEADER) +
|
||
|
sizeof(KSPROPERTY_STEPPING_LONG)))
|
||
|
{
|
||
|
// fill in the members header
|
||
|
PKSPROPERTY_MEMBERSHEADER Members = PKSPROPERTY_MEMBERSHEADER(PropDesc + 1);
|
||
|
|
||
|
Members->MembersFlags = KSPROPERTY_MEMBER_STEPPEDRANGES;
|
||
|
Members->MembersSize = sizeof(KSPROPERTY_STEPPING_LONG);
|
||
|
Members->MembersCount = 1;
|
||
|
Members->Flags = 0;
|
||
|
|
||
|
// fill in the stepped range
|
||
|
PKSPROPERTY_STEPPING_LONG Range = PKSPROPERTY_STEPPING_LONG(Members + 1);
|
||
|
|
||
|
switch(PropertyRequest->Node)
|
||
|
{
|
||
|
case WAVEOUT_VOLUME:
|
||
|
case SYNTH_VOLUME:
|
||
|
case CD_VOLUME:
|
||
|
case LINEIN_VOLUME:
|
||
|
case MIC_VOLUME:
|
||
|
case LINEOUT_VOL:
|
||
|
Range->Bounds.SignedMaximum = 0; // 0 (dB) * 0x10000
|
||
|
Range->Bounds.SignedMinimum = 0xFFC20000; // -62 (dB) * 0x10000
|
||
|
Range->SteppingDelta = 0x20000; // 2 (dB) * 0x10000
|
||
|
break;
|
||
|
|
||
|
case LINEOUT_GAIN:
|
||
|
case WAVEIN_GAIN:
|
||
|
Range->Bounds.SignedMaximum = 0x120000; // 18 (dB) * 0x10000
|
||
|
Range->Bounds.SignedMinimum = 0; // 0 (dB) * 0x10000
|
||
|
Range->SteppingDelta = 0x60000; // 6 (dB) * 0x10000
|
||
|
break;
|
||
|
|
||
|
case LINEOUT_BASS:
|
||
|
case LINEOUT_TREBLE:
|
||
|
Range->Bounds.SignedMaximum = 0xE0000; // 14 (dB) * 0x10000
|
||
|
Range->Bounds.SignedMinimum = 0xFFF20000; // -14 (dB) * 0x10000
|
||
|
Range->SteppingDelta = 0x20000; // 2 (dB) * 0x10000
|
||
|
break;
|
||
|
|
||
|
}
|
||
|
Range->Reserved = 0;
|
||
|
|
||
|
_DbgPrintF(DEBUGLVL_BLAB, ("---Node: %d Max: 0x%X Min: 0x%X Step: 0x%X",PropertyRequest->Node,
|
||
|
Range->Bounds.SignedMaximum,
|
||
|
Range->Bounds.SignedMinimum,
|
||
|
Range->SteppingDelta));
|
||
|
|
||
|
// set the return value size
|
||
|
PropertyRequest->ValueSize = sizeof(KSPROPERTY_DESCRIPTION) +
|
||
|
sizeof(KSPROPERTY_MEMBERSHEADER) +
|
||
|
sizeof(KSPROPERTY_STEPPING_LONG);
|
||
|
} else
|
||
|
{
|
||
|
// set the return value size
|
||
|
PropertyRequest->ValueSize = sizeof(KSPROPERTY_DESCRIPTION);
|
||
|
}
|
||
|
ntStatus = STATUS_SUCCESS;
|
||
|
|
||
|
} else if(PropertyRequest->ValueSize >= sizeof(ULONG))
|
||
|
{
|
||
|
// if return buffer can hold a ULONG, return the access flags
|
||
|
PULONG AccessFlags = PULONG(PropertyRequest->Value);
|
||
|
|
||
|
*AccessFlags = KSPROPERTY_TYPE_BASICSUPPORT |
|
||
|
KSPROPERTY_TYPE_GET |
|
||
|
KSPROPERTY_TYPE_SET;
|
||
|
|
||
|
// set the return value size
|
||
|
PropertyRequest->ValueSize = sizeof(ULONG);
|
||
|
ntStatus = STATUS_SUCCESS;
|
||
|
|
||
|
}
|
||
|
|
||
|
return ntStatus;
|
||
|
}
|
||
|
|
||
|
/*****************************************************************************
|
||
|
* PropertyHandler_Level()
|
||
|
*****************************************************************************
|
||
|
* Accesses a KSAUDIO_LEVEL property.
|
||
|
*/
|
||
|
static
|
||
|
NTSTATUS
|
||
|
PropertyHandler_Level
|
||
|
(
|
||
|
IN PPCPROPERTY_REQUEST PropertyRequest
|
||
|
)
|
||
|
{
|
||
|
PAGED_CODE();
|
||
|
|
||
|
ASSERT(PropertyRequest);
|
||
|
|
||
|
_DbgPrintF(DEBUGLVL_VERBOSE,("[PropertyHandler_Level]"));
|
||
|
|
||
|
CMiniportTopologySB16 *that =
|
||
|
(CMiniportTopologySB16 *) ((PMINIPORTTOPOLOGY) PropertyRequest->MajorTarget);
|
||
|
|
||
|
NTSTATUS ntStatus = STATUS_INVALID_PARAMETER;
|
||
|
ULONG count;
|
||
|
LONG channel;
|
||
|
|
||
|
// validate node
|
||
|
if(PropertyRequest->Node != ULONG(-1))
|
||
|
{
|
||
|
if(PropertyRequest->Verb & KSPROPERTY_TYPE_GET)
|
||
|
{
|
||
|
// get the instance channel parameter
|
||
|
if(PropertyRequest->InstanceSize >= sizeof(LONG))
|
||
|
{
|
||
|
channel = *(PLONG(PropertyRequest->Instance));
|
||
|
|
||
|
// only support get requests on either mono/left (0) or right (1) channels
|
||
|
if ( (channel == CHAN_LEFT) || (channel == CHAN_RIGHT) )
|
||
|
{
|
||
|
// validate and get the output parameter
|
||
|
if (PropertyRequest->ValueSize >= sizeof(LONG))
|
||
|
{
|
||
|
PLONG Level = (PLONG)PropertyRequest->Value;
|
||
|
|
||
|
// switch on node if
|
||
|
switch(PropertyRequest->Node)
|
||
|
{
|
||
|
case WAVEOUT_VOLUME:
|
||
|
case SYNTH_VOLUME:
|
||
|
case CD_VOLUME:
|
||
|
case LINEIN_VOLUME:
|
||
|
case MIC_VOLUME:
|
||
|
case LINEOUT_VOL:
|
||
|
// check if volume property request
|
||
|
if(PropertyRequest->PropertyItem->Id == KSPROPERTY_AUDIO_VOLUMELEVEL)
|
||
|
{
|
||
|
// bail out if a right channel request on the mono mic volume
|
||
|
if( (PropertyRequest->Node == MIC_VOLUME) && (channel != CHAN_LEFT) )
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
*Level = ControlValueCache[ AccessParams[PropertyRequest->Node].CacheOffset + channel ];
|
||
|
|
||
|
#ifdef EVENT_SUPPORT
|
||
|
//
|
||
|
// see if there is a volume changed, update if neccessary.
|
||
|
//
|
||
|
BYTE data = that->ReadBitsFromMixer (
|
||
|
BYTE(AccessParams[PropertyRequest->Node].BaseRegister
|
||
|
+channel+DSP_MIX_BASEIDX),
|
||
|
5, 3);
|
||
|
|
||
|
//
|
||
|
// Convert the dB value into a register value. No boundary check.
|
||
|
// Register is 0 - 31 representing -62dB - 0dB.
|
||
|
//
|
||
|
if (data != ((*Level >> 17) + 31))
|
||
|
{
|
||
|
//
|
||
|
// Convert the register into dB value.
|
||
|
// Register is 0 - 31 representing -62dB - 0dB.
|
||
|
//
|
||
|
*Level = (data - 31) << 17;
|
||
|
ControlValueCache[ AccessParams[PropertyRequest->Node].CacheOffset + channel] = *Level;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
PropertyRequest->ValueSize = sizeof(LONG);
|
||
|
ntStatus = STATUS_SUCCESS;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case LINEOUT_GAIN:
|
||
|
case WAVEIN_GAIN:
|
||
|
// check if volume property request
|
||
|
if(PropertyRequest->PropertyItem->Id == KSPROPERTY_AUDIO_VOLUMELEVEL)
|
||
|
{
|
||
|
*Level = ControlValueCache[ AccessParams[PropertyRequest->Node].CacheOffset + channel ];
|
||
|
PropertyRequest->ValueSize = sizeof(LONG);
|
||
|
ntStatus = STATUS_SUCCESS;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case LINEOUT_BASS:
|
||
|
case LINEOUT_TREBLE:
|
||
|
if( ( (PropertyRequest->PropertyItem->Id == KSPROPERTY_AUDIO_BASS) &&
|
||
|
(PropertyRequest->Node == LINEOUT_BASS) ) ||
|
||
|
( (PropertyRequest->PropertyItem->Id == KSPROPERTY_AUDIO_TREBLE) &&
|
||
|
(PropertyRequest->Node == LINEOUT_TREBLE) ) )
|
||
|
{
|
||
|
*Level = ControlValueCache[ AccessParams[PropertyRequest->Node].CacheOffset + channel ];
|
||
|
PropertyRequest->ValueSize = sizeof(LONG);
|
||
|
ntStatus = STATUS_SUCCESS;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
} else if(PropertyRequest->Verb & KSPROPERTY_TYPE_SET)
|
||
|
{
|
||
|
// get the instance channel parameter
|
||
|
if(PropertyRequest->InstanceSize >= sizeof(LONG))
|
||
|
{
|
||
|
channel = *(PLONG(PropertyRequest->Instance));
|
||
|
|
||
|
// only support set requests on either mono/left (0), right (1), or master (-1) channels
|
||
|
if ( (channel == CHAN_LEFT) || (channel == CHAN_RIGHT) || (channel == CHAN_MASTER))
|
||
|
{
|
||
|
// validate and get the input parameter
|
||
|
if (PropertyRequest->ValueSize == sizeof(LONG))
|
||
|
{
|
||
|
PLONG Level = (PLONG)PropertyRequest->Value;
|
||
|
|
||
|
// switch on the node id
|
||
|
switch(PropertyRequest->Node)
|
||
|
{
|
||
|
case WAVEOUT_VOLUME:
|
||
|
case SYNTH_VOLUME:
|
||
|
case CD_VOLUME:
|
||
|
case LINEIN_VOLUME:
|
||
|
case MIC_VOLUME:
|
||
|
case LINEOUT_VOL:
|
||
|
if(PropertyRequest->PropertyItem->Id == KSPROPERTY_AUDIO_VOLUMELEVEL)
|
||
|
{
|
||
|
// convert the level to register bits
|
||
|
if(*Level <= (-62 << 16))
|
||
|
{
|
||
|
count = 0;
|
||
|
} else if(*Level >= 0)
|
||
|
{
|
||
|
count = 0x1F;
|
||
|
} else
|
||
|
{
|
||
|
count = ((*Level >> 17) + 31) & 0x1F;
|
||
|
}
|
||
|
|
||
|
// set right channel if channel requested is right or master
|
||
|
// and node is not mic volume (mono)
|
||
|
if ( ( (channel == CHAN_RIGHT) || (channel == CHAN_MASTER) ) &&
|
||
|
( PropertyRequest->Node != MIC_VOLUME ) )
|
||
|
{
|
||
|
// cache the commanded control value
|
||
|
ControlValueCache[ AccessParams[PropertyRequest->Node].CacheOffset + CHAN_RIGHT ] = *Level;
|
||
|
|
||
|
that->WriteBitsToMixer( AccessParams[PropertyRequest->Node].BaseRegister+1,
|
||
|
5,
|
||
|
3,
|
||
|
BYTE(count) );
|
||
|
ntStatus = STATUS_SUCCESS;
|
||
|
}
|
||
|
// set the left channel if channel requested is left or master
|
||
|
if ( (channel == CHAN_LEFT) || (channel == CHAN_MASTER) )
|
||
|
{
|
||
|
// cache the commanded control value
|
||
|
ControlValueCache[ AccessParams[PropertyRequest->Node].CacheOffset + CHAN_LEFT ] = *Level;
|
||
|
|
||
|
that->WriteBitsToMixer( AccessParams[PropertyRequest->Node].BaseRegister,
|
||
|
5,
|
||
|
3,
|
||
|
BYTE(count) );
|
||
|
ntStatus = STATUS_SUCCESS;
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case LINEOUT_GAIN:
|
||
|
case WAVEIN_GAIN:
|
||
|
if(PropertyRequest->PropertyItem->Id == KSPROPERTY_AUDIO_VOLUMELEVEL)
|
||
|
{
|
||
|
// determine register bits
|
||
|
if(*Level >= (18 << 16))
|
||
|
{
|
||
|
count = 0x3;
|
||
|
} else if(*Level <= 0)
|
||
|
{
|
||
|
count = 0;
|
||
|
} else
|
||
|
{
|
||
|
count = (*Level >> 17) / 3;
|
||
|
}
|
||
|
|
||
|
// set right channel if channel requested is right or master
|
||
|
if ( (channel == CHAN_RIGHT) || (channel == CHAN_MASTER) )
|
||
|
{
|
||
|
// cache the commanded control value
|
||
|
ControlValueCache[ AccessParams[PropertyRequest->Node].CacheOffset + CHAN_RIGHT ] = *Level;
|
||
|
|
||
|
that->WriteBitsToMixer( AccessParams[PropertyRequest->Node].BaseRegister+1,
|
||
|
2,
|
||
|
6,
|
||
|
BYTE(count) );
|
||
|
ntStatus = STATUS_SUCCESS;
|
||
|
}
|
||
|
// set the left channel if channel requested is left or master
|
||
|
if ( (channel == CHAN_LEFT) || (channel == CHAN_MASTER) )
|
||
|
{
|
||
|
// cache the commanded control value
|
||
|
ControlValueCache[ AccessParams[PropertyRequest->Node].CacheOffset + CHAN_LEFT ] = *Level;
|
||
|
|
||
|
that->WriteBitsToMixer( AccessParams[PropertyRequest->Node].BaseRegister,
|
||
|
2,
|
||
|
6,
|
||
|
BYTE(count) );
|
||
|
ntStatus = STATUS_SUCCESS;
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case LINEOUT_BASS:
|
||
|
case LINEOUT_TREBLE:
|
||
|
if( ( (PropertyRequest->PropertyItem->Id == KSPROPERTY_AUDIO_BASS) &&
|
||
|
(PropertyRequest->Node == LINEOUT_BASS) ) ||
|
||
|
( (PropertyRequest->PropertyItem->Id == KSPROPERTY_AUDIO_TREBLE) &&
|
||
|
(PropertyRequest->Node == LINEOUT_TREBLE) ) )
|
||
|
{
|
||
|
// determine register bits
|
||
|
if(*Level <= (-14 << 16))
|
||
|
{
|
||
|
count = 0;
|
||
|
} else if(*Level >= (14 << 16))
|
||
|
{
|
||
|
count = 0xF;
|
||
|
} else
|
||
|
{
|
||
|
count = ((*Level >> 16) + 14) >> 1;
|
||
|
}
|
||
|
|
||
|
// set right channel if channel requested is right or master
|
||
|
if ( (channel == CHAN_RIGHT) || (channel == CHAN_MASTER) )
|
||
|
{
|
||
|
// cache the commanded control value
|
||
|
ControlValueCache[ AccessParams[PropertyRequest->Node].CacheOffset + CHAN_RIGHT ] = *Level;
|
||
|
|
||
|
that->WriteBitsToMixer( AccessParams[PropertyRequest->Node].BaseRegister + 1,
|
||
|
4,
|
||
|
4,
|
||
|
BYTE(count) );
|
||
|
ntStatus = STATUS_SUCCESS;
|
||
|
}
|
||
|
// set the left channel if channel requested is left or master
|
||
|
if ( (channel == CHAN_LEFT) || (channel == CHAN_MASTER) )
|
||
|
{
|
||
|
// cache the commanded control value
|
||
|
ControlValueCache[ AccessParams[PropertyRequest->Node].CacheOffset + CHAN_LEFT ] = *Level;
|
||
|
|
||
|
that->WriteBitsToMixer( AccessParams[PropertyRequest->Node].BaseRegister,
|
||
|
4,
|
||
|
4,
|
||
|
BYTE(count) );
|
||
|
ntStatus = STATUS_SUCCESS;
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
} else if(PropertyRequest->Verb & KSPROPERTY_TYPE_BASICSUPPORT)
|
||
|
{
|
||
|
// service basic support request
|
||
|
switch(PropertyRequest->Node)
|
||
|
{
|
||
|
case WAVEOUT_VOLUME:
|
||
|
case SYNTH_VOLUME:
|
||
|
case CD_VOLUME:
|
||
|
case LINEIN_VOLUME:
|
||
|
case MIC_VOLUME:
|
||
|
case LINEOUT_VOL:
|
||
|
case LINEOUT_GAIN:
|
||
|
case WAVEIN_GAIN:
|
||
|
if(PropertyRequest->PropertyItem->Id == KSPROPERTY_AUDIO_VOLUMELEVEL)
|
||
|
{
|
||
|
ntStatus = BasicSupportHandler(PropertyRequest);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case LINEOUT_BASS:
|
||
|
case LINEOUT_TREBLE:
|
||
|
if( ( (PropertyRequest->PropertyItem->Id == KSPROPERTY_AUDIO_BASS) &&
|
||
|
(PropertyRequest->Node == LINEOUT_BASS) ) ||
|
||
|
( (PropertyRequest->PropertyItem->Id == KSPROPERTY_AUDIO_TREBLE) &&
|
||
|
(PropertyRequest->Node == LINEOUT_TREBLE) ) )
|
||
|
{
|
||
|
ntStatus = BasicSupportHandler(PropertyRequest);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return ntStatus;
|
||
|
}
|
||
|
|
||
|
/*****************************************************************************
|
||
|
* PropertyHandler_SuperMixCaps()
|
||
|
*****************************************************************************
|
||
|
* Handles supermixer caps accesses
|
||
|
*/
|
||
|
static
|
||
|
NTSTATUS
|
||
|
PropertyHandler_SuperMixCaps
|
||
|
(
|
||
|
IN PPCPROPERTY_REQUEST PropertyRequest
|
||
|
)
|
||
|
{
|
||
|
PAGED_CODE();
|
||
|
|
||
|
ASSERT(PropertyRequest);
|
||
|
|
||
|
_DbgPrintF(DEBUGLVL_VERBOSE,("[PropertyHandler_SuperMixCaps]"));
|
||
|
|
||
|
CMiniportTopologySB16 *that =
|
||
|
(CMiniportTopologySB16 *) ((PMINIPORTTOPOLOGY) PropertyRequest->MajorTarget);
|
||
|
|
||
|
NTSTATUS ntStatus = STATUS_INVALID_PARAMETER;
|
||
|
ULONG count;
|
||
|
|
||
|
// validate node
|
||
|
if(PropertyRequest->Node != ULONG(-1))
|
||
|
{
|
||
|
if(PropertyRequest->Verb & KSPROPERTY_TYPE_GET)
|
||
|
{
|
||
|
switch(PropertyRequest->Node)
|
||
|
{
|
||
|
// Full 2x2 Switches
|
||
|
case SYNTH_WAVEIN_SUPERMIX:
|
||
|
case CD_WAVEIN_SUPERMIX:
|
||
|
case LINEIN_WAVEIN_SUPERMIX:
|
||
|
if(!PropertyRequest->ValueSize)
|
||
|
{
|
||
|
PropertyRequest->ValueSize = 2 * sizeof(ULONG) + 4 * sizeof(KSAUDIO_MIX_CAPS);
|
||
|
ntStatus = STATUS_BUFFER_OVERFLOW;
|
||
|
} else if(PropertyRequest->ValueSize == 2 * sizeof(ULONG))
|
||
|
{
|
||
|
PKSAUDIO_MIXCAP_TABLE MixCaps = (PKSAUDIO_MIXCAP_TABLE)PropertyRequest->Value;
|
||
|
MixCaps->InputChannels = 2;
|
||
|
MixCaps->OutputChannels = 2;
|
||
|
ntStatus = STATUS_SUCCESS;
|
||
|
} else if(PropertyRequest->ValueSize >= 2 * sizeof(ULONG) + 4 * sizeof(KSAUDIO_MIX_CAPS))
|
||
|
{
|
||
|
PropertyRequest->ValueSize = 2 * sizeof(ULONG) + 4 * sizeof(KSAUDIO_MIX_CAPS);
|
||
|
|
||
|
PKSAUDIO_MIXCAP_TABLE MixCaps = (PKSAUDIO_MIXCAP_TABLE)PropertyRequest->Value;
|
||
|
MixCaps->InputChannels = 2;
|
||
|
MixCaps->OutputChannels = 2;
|
||
|
for(count = 0; count < 4; count++)
|
||
|
{
|
||
|
MixCaps->Capabilities[count].Mute = TRUE;
|
||
|
MixCaps->Capabilities[count].Minimum = 0;
|
||
|
MixCaps->Capabilities[count].Maximum = 0;
|
||
|
MixCaps->Capabilities[count].Reset = 0;
|
||
|
}
|
||
|
ntStatus = STATUS_SUCCESS;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
// Limited 2x2 Switches
|
||
|
case CD_LINEOUT_SUPERMIX:
|
||
|
case LINEIN_LINEOUT_SUPERMIX:
|
||
|
if(!PropertyRequest->ValueSize)
|
||
|
{
|
||
|
PropertyRequest->ValueSize = 2 * sizeof(ULONG) + 4 * sizeof(KSAUDIO_MIX_CAPS);
|
||
|
ntStatus = STATUS_BUFFER_OVERFLOW;
|
||
|
} else if(PropertyRequest->ValueSize == 2 * sizeof(ULONG))
|
||
|
{
|
||
|
PKSAUDIO_MIXCAP_TABLE MixCaps = (PKSAUDIO_MIXCAP_TABLE)PropertyRequest->Value;
|
||
|
MixCaps->InputChannels = 2;
|
||
|
MixCaps->OutputChannels = 2;
|
||
|
ntStatus = STATUS_SUCCESS;
|
||
|
} else if(PropertyRequest->ValueSize >= 2 * sizeof(ULONG) + 4 * sizeof(KSAUDIO_MIX_CAPS))
|
||
|
{
|
||
|
PropertyRequest->ValueSize = 2 * sizeof(ULONG) + 4 * sizeof(KSAUDIO_MIX_CAPS);
|
||
|
|
||
|
PKSAUDIO_MIXCAP_TABLE MixCaps = (PKSAUDIO_MIXCAP_TABLE)PropertyRequest->Value;
|
||
|
MixCaps->InputChannels = 2;
|
||
|
MixCaps->OutputChannels = 2;
|
||
|
for(count = 0; count < 4; count++)
|
||
|
{
|
||
|
if((count == 0) || (count == 3))
|
||
|
{
|
||
|
MixCaps->Capabilities[count].Mute = TRUE;
|
||
|
MixCaps->Capabilities[count].Minimum = 0;
|
||
|
MixCaps->Capabilities[count].Maximum = 0;
|
||
|
MixCaps->Capabilities[count].Reset = 0;
|
||
|
} else
|
||
|
{
|
||
|
MixCaps->Capabilities[count].Mute = FALSE;
|
||
|
MixCaps->Capabilities[count].Minimum = LONG_MIN;
|
||
|
MixCaps->Capabilities[count].Maximum = LONG_MIN;
|
||
|
MixCaps->Capabilities[count].Reset = LONG_MIN;
|
||
|
}
|
||
|
}
|
||
|
ntStatus = STATUS_SUCCESS;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
|
||
|
// 1x2 Switch
|
||
|
case MIC_WAVEIN_SUPERMIX:
|
||
|
if(!PropertyRequest->ValueSize)
|
||
|
{
|
||
|
PropertyRequest->ValueSize = 2 * sizeof(ULONG) + 2 * sizeof(KSAUDIO_MIX_CAPS);
|
||
|
ntStatus = STATUS_BUFFER_OVERFLOW;
|
||
|
} else if(PropertyRequest->ValueSize == 2 * sizeof(ULONG))
|
||
|
{
|
||
|
PKSAUDIO_MIXCAP_TABLE MixCaps = (PKSAUDIO_MIXCAP_TABLE)PropertyRequest->Value;
|
||
|
MixCaps->InputChannels = 1;
|
||
|
MixCaps->OutputChannels = 2;
|
||
|
ntStatus = STATUS_SUCCESS;
|
||
|
} else if(PropertyRequest->ValueSize >= 2 * sizeof(ULONG) + 2 * sizeof(KSAUDIO_MIX_CAPS))
|
||
|
{
|
||
|
PropertyRequest->ValueSize = 2 * sizeof(ULONG) + 2 * sizeof(KSAUDIO_MIX_CAPS);
|
||
|
|
||
|
PKSAUDIO_MIXCAP_TABLE MixCaps = (PKSAUDIO_MIXCAP_TABLE)PropertyRequest->Value;
|
||
|
MixCaps->InputChannels = 1;
|
||
|
MixCaps->OutputChannels = 2;
|
||
|
for(count = 0; count < 2; count++)
|
||
|
{
|
||
|
MixCaps->Capabilities[count].Mute = TRUE;
|
||
|
MixCaps->Capabilities[count].Minimum = 0;
|
||
|
MixCaps->Capabilities[count].Maximum = 0;
|
||
|
MixCaps->Capabilities[count].Reset = 0;
|
||
|
}
|
||
|
ntStatus = STATUS_SUCCESS;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
} else if(PropertyRequest->Verb & KSPROPERTY_TYPE_BASICSUPPORT)
|
||
|
{
|
||
|
// service basic support request
|
||
|
switch(PropertyRequest->Node)
|
||
|
{
|
||
|
case SYNTH_WAVEIN_SUPERMIX:
|
||
|
case CD_WAVEIN_SUPERMIX:
|
||
|
case LINEIN_WAVEIN_SUPERMIX:
|
||
|
case CD_LINEOUT_SUPERMIX:
|
||
|
case LINEIN_LINEOUT_SUPERMIX:
|
||
|
case MIC_WAVEIN_SUPERMIX:
|
||
|
if(PropertyRequest->ValueSize >= (sizeof(KSPROPERTY_DESCRIPTION)))
|
||
|
{
|
||
|
// if return buffer can hold a KSPROPERTY_DESCRIPTION, return it
|
||
|
PKSPROPERTY_DESCRIPTION PropDesc = PKSPROPERTY_DESCRIPTION(PropertyRequest->Value);
|
||
|
|
||
|
PropDesc->AccessFlags = KSPROPERTY_TYPE_BASICSUPPORT |
|
||
|
KSPROPERTY_TYPE_GET;
|
||
|
PropDesc->DescriptionSize = sizeof(KSPROPERTY_DESCRIPTION);
|
||
|
PropDesc->PropTypeSet.Set = KSPROPTYPESETID_General;
|
||
|
PropDesc->PropTypeSet.Id = VT_ARRAY;
|
||
|
PropDesc->PropTypeSet.Flags = 0;
|
||
|
PropDesc->MembersListCount = 0;
|
||
|
PropDesc->Reserved = 0;
|
||
|
|
||
|
// set the return value size
|
||
|
PropertyRequest->ValueSize = sizeof(KSPROPERTY_DESCRIPTION);
|
||
|
ntStatus = STATUS_SUCCESS;
|
||
|
} else if(PropertyRequest->ValueSize >= sizeof(ULONG))
|
||
|
{
|
||
|
// if return buffer can hold a ULONG, return the access flags
|
||
|
PULONG AccessFlags = PULONG(PropertyRequest->Value);
|
||
|
|
||
|
*AccessFlags = KSPROPERTY_TYPE_BASICSUPPORT |
|
||
|
KSPROPERTY_TYPE_GET;
|
||
|
|
||
|
// set the return value size
|
||
|
PropertyRequest->ValueSize = sizeof(ULONG);
|
||
|
ntStatus = STATUS_SUCCESS;
|
||
|
}
|
||
|
ntStatus = STATUS_SUCCESS;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return ntStatus;
|
||
|
}
|
||
|
|
||
|
/*****************************************************************************
|
||
|
* PropertyHandler_SuperMixTable()
|
||
|
*****************************************************************************
|
||
|
* Handles supermixer level accesses
|
||
|
*/
|
||
|
static
|
||
|
NTSTATUS
|
||
|
PropertyHandler_SuperMixTable
|
||
|
(
|
||
|
IN PPCPROPERTY_REQUEST PropertyRequest
|
||
|
)
|
||
|
{
|
||
|
PAGED_CODE();
|
||
|
|
||
|
ASSERT(PropertyRequest);
|
||
|
|
||
|
_DbgPrintF(DEBUGLVL_VERBOSE,("[PropertyHandler_SuperMixTable]"));
|
||
|
|
||
|
CMiniportTopologySB16 *that =
|
||
|
(CMiniportTopologySB16 *) ((PMINIPORTTOPOLOGY) PropertyRequest->MajorTarget);
|
||
|
|
||
|
NTSTATUS ntStatus = STATUS_INVALID_PARAMETER;
|
||
|
BYTE dataL,dataR;
|
||
|
|
||
|
// validate node
|
||
|
if(PropertyRequest->Node != ULONG(-1))
|
||
|
{
|
||
|
if(PropertyRequest->Verb & KSPROPERTY_TYPE_GET)
|
||
|
{
|
||
|
switch(PropertyRequest->Node)
|
||
|
{
|
||
|
// Full 2x2 Switches
|
||
|
case SYNTH_WAVEIN_SUPERMIX:
|
||
|
case CD_WAVEIN_SUPERMIX:
|
||
|
case LINEIN_WAVEIN_SUPERMIX:
|
||
|
if(!PropertyRequest->ValueSize)
|
||
|
{
|
||
|
PropertyRequest->ValueSize = 4 * sizeof(KSAUDIO_MIXLEVEL);
|
||
|
ntStatus = STATUS_BUFFER_OVERFLOW;
|
||
|
} else if(PropertyRequest->ValueSize >= 4 * sizeof(KSAUDIO_MIXLEVEL))
|
||
|
{
|
||
|
PropertyRequest->ValueSize = 4 * sizeof(KSAUDIO_MIXLEVEL);
|
||
|
|
||
|
PKSAUDIO_MIXLEVEL MixLevel = (PKSAUDIO_MIXLEVEL)PropertyRequest->Value;
|
||
|
|
||
|
dataL = that->ReadBitsFromMixer( DSP_MIX_ADCMIXIDX_L,
|
||
|
2,
|
||
|
AccessParams[PropertyRequest->Node].BaseRegister );
|
||
|
dataR = that->ReadBitsFromMixer( DSP_MIX_ADCMIXIDX_R,
|
||
|
2,
|
||
|
AccessParams[PropertyRequest->Node].BaseRegister );
|
||
|
|
||
|
MixLevel[0].Mute = dataL & 0x2 ? FALSE : TRUE; // left to left mute
|
||
|
MixLevel[0].Level = 0;
|
||
|
|
||
|
MixLevel[1].Mute = dataR & 0x2 ? FALSE : TRUE; // left to right mute
|
||
|
MixLevel[1].Level = 0;
|
||
|
|
||
|
MixLevel[2].Mute = dataL & 0x1 ? FALSE : TRUE; // right to left mute
|
||
|
MixLevel[2].Level = 0;
|
||
|
|
||
|
MixLevel[3].Mute = dataR & 0x1 ? FALSE : TRUE; // right to right mute
|
||
|
MixLevel[3].Level = 0;
|
||
|
|
||
|
ntStatus = STATUS_SUCCESS;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
// Limited 2x2 Switches
|
||
|
case CD_LINEOUT_SUPERMIX:
|
||
|
case LINEIN_LINEOUT_SUPERMIX:
|
||
|
if(!PropertyRequest->ValueSize)
|
||
|
{
|
||
|
PropertyRequest->ValueSize = 4 * sizeof(KSAUDIO_MIXLEVEL);
|
||
|
ntStatus = STATUS_BUFFER_OVERFLOW;
|
||
|
} else if(PropertyRequest->ValueSize >= 4 * sizeof(KSAUDIO_MIXLEVEL))
|
||
|
{
|
||
|
PropertyRequest->ValueSize = 4 * sizeof(KSAUDIO_MIXLEVEL);
|
||
|
|
||
|
PKSAUDIO_MIXLEVEL MixLevel = (PKSAUDIO_MIXLEVEL)PropertyRequest->Value;
|
||
|
|
||
|
dataL = that->ReadBitsFromMixer( DSP_MIX_OUTMIXIDX,
|
||
|
2,
|
||
|
AccessParams[PropertyRequest->Node].BaseRegister );
|
||
|
|
||
|
MixLevel[0].Mute = dataL & 0x2 ? FALSE : TRUE; // left to left mute
|
||
|
MixLevel[0].Level = 0;
|
||
|
|
||
|
MixLevel[1].Mute = FALSE;
|
||
|
MixLevel[1].Level = LONG_MIN;
|
||
|
|
||
|
MixLevel[2].Mute = FALSE;
|
||
|
MixLevel[2].Level = LONG_MIN;
|
||
|
|
||
|
MixLevel[3].Mute = dataL & 0x1 ? FALSE : TRUE; // right to right mute
|
||
|
MixLevel[3].Level = 0;
|
||
|
|
||
|
ntStatus = STATUS_SUCCESS;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
|
||
|
// 1x2 Switch
|
||
|
case MIC_WAVEIN_SUPERMIX:
|
||
|
if(!PropertyRequest->ValueSize)
|
||
|
{
|
||
|
PropertyRequest->ValueSize = 2 * sizeof(KSAUDIO_MIXLEVEL);
|
||
|
ntStatus = STATUS_BUFFER_OVERFLOW;
|
||
|
} else if(PropertyRequest->ValueSize >= 2 * sizeof(KSAUDIO_MIXLEVEL))
|
||
|
{
|
||
|
PropertyRequest->ValueSize = 2 * sizeof(KSAUDIO_MIXLEVEL);
|
||
|
|
||
|
PKSAUDIO_MIXLEVEL MixLevel = (PKSAUDIO_MIXLEVEL)PropertyRequest->Value;
|
||
|
|
||
|
dataL = that->ReadBitsFromMixer( DSP_MIX_ADCMIXIDX_L,
|
||
|
1,
|
||
|
MIXBIT_MIC_WAVEIN );
|
||
|
dataR = that->ReadBitsFromMixer( DSP_MIX_ADCMIXIDX_R,
|
||
|
1,
|
||
|
MIXBIT_MIC_WAVEIN );
|
||
|
|
||
|
MixLevel[0].Mute = dataL & 0x1 ? FALSE : TRUE; // mono to left mute
|
||
|
MixLevel[0].Level = 0;
|
||
|
|
||
|
MixLevel[1].Mute = dataR & 0x1 ? FALSE : TRUE; // mono to right mute
|
||
|
MixLevel[1].Level = 0;
|
||
|
|
||
|
ntStatus = STATUS_SUCCESS;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
} else if(PropertyRequest->Verb & KSPROPERTY_TYPE_SET)
|
||
|
{
|
||
|
switch(PropertyRequest->Node)
|
||
|
{
|
||
|
// Full 2x2 Switches
|
||
|
case SYNTH_WAVEIN_SUPERMIX:
|
||
|
case CD_WAVEIN_SUPERMIX:
|
||
|
case LINEIN_WAVEIN_SUPERMIX:
|
||
|
if(PropertyRequest->ValueSize == 4 * sizeof(KSAUDIO_MIXLEVEL))
|
||
|
{
|
||
|
PKSAUDIO_MIXLEVEL MixLevel = (PKSAUDIO_MIXLEVEL)PropertyRequest->Value;
|
||
|
|
||
|
dataL = MixLevel[0].Mute ? 0x0 : 0x2;
|
||
|
dataL |= MixLevel[2].Mute ? 0x0 : 0x1;
|
||
|
|
||
|
dataR = MixLevel[1].Mute ? 0x0 : 0x2;
|
||
|
dataR |= MixLevel[3].Mute ? 0x0 : 0x1;
|
||
|
|
||
|
|
||
|
that->WriteBitsToMixer( DSP_MIX_ADCMIXIDX_L,
|
||
|
2,
|
||
|
AccessParams[PropertyRequest->Node].BaseRegister,
|
||
|
dataL );
|
||
|
|
||
|
that->WriteBitsToMixer( DSP_MIX_ADCMIXIDX_R,
|
||
|
2,
|
||
|
AccessParams[PropertyRequest->Node].BaseRegister,
|
||
|
dataR );
|
||
|
|
||
|
ntStatus = STATUS_SUCCESS;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
// Limited 2x2 Switches
|
||
|
case CD_LINEOUT_SUPERMIX:
|
||
|
case LINEIN_LINEOUT_SUPERMIX:
|
||
|
if(PropertyRequest->ValueSize == 4 * sizeof(KSAUDIO_MIXLEVEL))
|
||
|
{
|
||
|
PKSAUDIO_MIXLEVEL MixLevel = (PKSAUDIO_MIXLEVEL)PropertyRequest->Value;
|
||
|
|
||
|
dataL = MixLevel[0].Mute ? 0x0 : 0x2;
|
||
|
dataL |= MixLevel[3].Mute ? 0x0 : 0x1;
|
||
|
|
||
|
that->WriteBitsToMixer( DSP_MIX_OUTMIXIDX,
|
||
|
2,
|
||
|
AccessParams[PropertyRequest->Node].BaseRegister,
|
||
|
dataL );
|
||
|
|
||
|
ntStatus = STATUS_SUCCESS;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
|
||
|
// 1x2 Switch
|
||
|
case MIC_WAVEIN_SUPERMIX:
|
||
|
if(PropertyRequest->ValueSize == 2 * sizeof(KSAUDIO_MIXLEVEL))
|
||
|
{
|
||
|
PKSAUDIO_MIXLEVEL MixLevel = (PKSAUDIO_MIXLEVEL)PropertyRequest->Value;
|
||
|
|
||
|
dataL = MixLevel[0].Mute ? 0x0 : 0x1;
|
||
|
dataR = MixLevel[1].Mute ? 0x0 : 0x1;
|
||
|
|
||
|
that->WriteBitsToMixer( DSP_MIX_ADCMIXIDX_L,
|
||
|
1,
|
||
|
MIXBIT_MIC_WAVEIN,
|
||
|
dataL );
|
||
|
|
||
|
that->WriteBitsToMixer( DSP_MIX_ADCMIXIDX_R,
|
||
|
1,
|
||
|
MIXBIT_MIC_WAVEIN,
|
||
|
dataR );
|
||
|
|
||
|
ntStatus = STATUS_SUCCESS;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
} else if(PropertyRequest->Verb & KSPROPERTY_TYPE_BASICSUPPORT)
|
||
|
{
|
||
|
// service basic support request
|
||
|
switch(PropertyRequest->Node)
|
||
|
{
|
||
|
case SYNTH_WAVEIN_SUPERMIX:
|
||
|
case CD_WAVEIN_SUPERMIX:
|
||
|
case LINEIN_WAVEIN_SUPERMIX:
|
||
|
case CD_LINEOUT_SUPERMIX:
|
||
|
case LINEIN_LINEOUT_SUPERMIX:
|
||
|
case MIC_WAVEIN_SUPERMIX:
|
||
|
if(PropertyRequest->ValueSize >= (sizeof(KSPROPERTY_DESCRIPTION)))
|
||
|
{
|
||
|
// if return buffer can hold a KSPROPERTY_DESCRIPTION, return it
|
||
|
PKSPROPERTY_DESCRIPTION PropDesc = PKSPROPERTY_DESCRIPTION(PropertyRequest->Value);
|
||
|
|
||
|
PropDesc->AccessFlags = KSPROPERTY_TYPE_BASICSUPPORT |
|
||
|
KSPROPERTY_TYPE_GET |
|
||
|
KSPROPERTY_TYPE_SET;
|
||
|
PropDesc->DescriptionSize = sizeof(KSPROPERTY_DESCRIPTION);
|
||
|
PropDesc->PropTypeSet.Set = KSPROPTYPESETID_General;
|
||
|
PropDesc->PropTypeSet.Id = VT_ARRAY;
|
||
|
PropDesc->PropTypeSet.Flags = 0;
|
||
|
PropDesc->MembersListCount = 0;
|
||
|
PropDesc->Reserved = 0;
|
||
|
|
||
|
// set the return value size
|
||
|
PropertyRequest->ValueSize = sizeof(KSPROPERTY_DESCRIPTION);
|
||
|
ntStatus = STATUS_SUCCESS;
|
||
|
} else if(PropertyRequest->ValueSize >= sizeof(ULONG))
|
||
|
{
|
||
|
// if return buffer can hold a ULONG, return the access flags
|
||
|
PULONG AccessFlags = PULONG(PropertyRequest->Value);
|
||
|
|
||
|
*AccessFlags = KSPROPERTY_TYPE_BASICSUPPORT |
|
||
|
KSPROPERTY_TYPE_GET |
|
||
|
KSPROPERTY_TYPE_SET;
|
||
|
|
||
|
// set the return value size
|
||
|
PropertyRequest->ValueSize = sizeof(ULONG);
|
||
|
ntStatus = STATUS_SUCCESS;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return ntStatus;
|
||
|
}
|
||
|
|
||
|
/*****************************************************************************
|
||
|
* PropertyHandler_CpuResources()
|
||
|
*****************************************************************************
|
||
|
* Processes a KSPROPERTY_AUDIO_CPU_RESOURCES request
|
||
|
*/
|
||
|
static
|
||
|
NTSTATUS
|
||
|
PropertyHandler_CpuResources
|
||
|
(
|
||
|
IN PPCPROPERTY_REQUEST PropertyRequest
|
||
|
)
|
||
|
{
|
||
|
PAGED_CODE();
|
||
|
|
||
|
ASSERT(PropertyRequest);
|
||
|
|
||
|
_DbgPrintF(DEBUGLVL_VERBOSE,("[PropertyHandler_CpuResources]"));
|
||
|
|
||
|
NTSTATUS ntStatus = STATUS_INVALID_DEVICE_REQUEST;
|
||
|
|
||
|
// validate node
|
||
|
if(PropertyRequest->Node != ULONG(-1))
|
||
|
{
|
||
|
if(PropertyRequest->Verb & KSPROPERTY_TYPE_GET)
|
||
|
{
|
||
|
if(PropertyRequest->ValueSize >= sizeof(LONG))
|
||
|
{
|
||
|
*(PLONG(PropertyRequest->Value)) = KSAUDIO_CPU_RESOURCES_NOT_HOST_CPU;
|
||
|
PropertyRequest->ValueSize = sizeof(LONG);
|
||
|
ntStatus = STATUS_SUCCESS;
|
||
|
} else
|
||
|
{
|
||
|
ntStatus = STATUS_BUFFER_TOO_SMALL;
|
||
|
}
|
||
|
} else if(PropertyRequest->Verb & KSPROPERTY_TYPE_BASICSUPPORT)
|
||
|
{
|
||
|
if(PropertyRequest->ValueSize >= (sizeof(KSPROPERTY_DESCRIPTION)))
|
||
|
{
|
||
|
// if return buffer can hold a KSPROPERTY_DESCRIPTION, return it
|
||
|
PKSPROPERTY_DESCRIPTION PropDesc = PKSPROPERTY_DESCRIPTION(PropertyRequest->Value);
|
||
|
|
||
|
PropDesc->AccessFlags = KSPROPERTY_TYPE_BASICSUPPORT |
|
||
|
KSPROPERTY_TYPE_GET;
|
||
|
PropDesc->DescriptionSize = sizeof(KSPROPERTY_DESCRIPTION);
|
||
|
PropDesc->PropTypeSet.Set = KSPROPTYPESETID_General;
|
||
|
PropDesc->PropTypeSet.Id = VT_I4;
|
||
|
PropDesc->PropTypeSet.Flags = 0;
|
||
|
PropDesc->MembersListCount = 0;
|
||
|
PropDesc->Reserved = 0;
|
||
|
|
||
|
// set the return value size
|
||
|
PropertyRequest->ValueSize = sizeof(KSPROPERTY_DESCRIPTION);
|
||
|
ntStatus = STATUS_SUCCESS;
|
||
|
} else if(PropertyRequest->ValueSize >= sizeof(ULONG))
|
||
|
{
|
||
|
// if return buffer can hold a ULONG, return the access flags
|
||
|
PULONG AccessFlags = PULONG(PropertyRequest->Value);
|
||
|
|
||
|
*AccessFlags = KSPROPERTY_TYPE_BASICSUPPORT |
|
||
|
KSPROPERTY_TYPE_GET |
|
||
|
KSPROPERTY_TYPE_SET;
|
||
|
|
||
|
// set the return value size
|
||
|
PropertyRequest->ValueSize = sizeof(ULONG);
|
||
|
ntStatus = STATUS_SUCCESS;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return ntStatus;
|
||
|
}
|
||
|
|
||
|
/*****************************************************************************
|
||
|
* PropertyHandler_ComponentId()
|
||
|
*****************************************************************************
|
||
|
* Processes a KSPROPERTY_GENERAL_COMPONENTID request
|
||
|
*/
|
||
|
NTSTATUS
|
||
|
PropertyHandler_ComponentId
|
||
|
(
|
||
|
IN PPCPROPERTY_REQUEST PropertyRequest
|
||
|
)
|
||
|
{
|
||
|
PAGED_CODE();
|
||
|
|
||
|
ASSERT(PropertyRequest);
|
||
|
|
||
|
_DbgPrintF(DEBUGLVL_VERBOSE,("[PropertyHandler_ComponentId]"));
|
||
|
|
||
|
NTSTATUS ntStatus = STATUS_INVALID_DEVICE_REQUEST;
|
||
|
|
||
|
if(PropertyRequest->Verb & KSPROPERTY_TYPE_GET)
|
||
|
{
|
||
|
if(PropertyRequest->ValueSize >= sizeof(KSCOMPONENTID))
|
||
|
{
|
||
|
PKSCOMPONENTID pComponentId = (PKSCOMPONENTID)
|
||
|
PropertyRequest->Value;
|
||
|
|
||
|
INIT_MMREG_MID(&pComponentId->Manufacturer, MM_MICROSOFT);
|
||
|
pComponentId->Product = PID_MSSB16;
|
||
|
pComponentId->Name = NAME_MSSB16;
|
||
|
pComponentId->Component = GUID_NULL; // Not used for extended caps.
|
||
|
pComponentId->Version = MSSB16_VERSION;
|
||
|
pComponentId->Revision = MSSB16_REVISION;
|
||
|
|
||
|
PropertyRequest->ValueSize = sizeof(KSCOMPONENTID);
|
||
|
ntStatus = STATUS_SUCCESS;
|
||
|
} else if(PropertyRequest->ValueSize == 0)
|
||
|
{
|
||
|
PropertyRequest->ValueSize = sizeof(KSCOMPONENTID);
|
||
|
ntStatus = STATUS_BUFFER_OVERFLOW;
|
||
|
} else
|
||
|
{
|
||
|
PropertyRequest->ValueSize = 0;
|
||
|
ntStatus = STATUS_BUFFER_TOO_SMALL;
|
||
|
}
|
||
|
} else if(PropertyRequest->Verb & KSPROPERTY_TYPE_BASICSUPPORT)
|
||
|
{
|
||
|
if(PropertyRequest->ValueSize >= sizeof(ULONG))
|
||
|
{
|
||
|
// if return buffer can hold a ULONG, return the access flags
|
||
|
PULONG AccessFlags = PULONG(PropertyRequest->Value);
|
||
|
|
||
|
*AccessFlags = KSPROPERTY_TYPE_BASICSUPPORT |
|
||
|
KSPROPERTY_TYPE_GET;
|
||
|
|
||
|
// set the return value size
|
||
|
PropertyRequest->ValueSize = sizeof(ULONG);
|
||
|
ntStatus = STATUS_SUCCESS;
|
||
|
} else
|
||
|
{
|
||
|
PropertyRequest->ValueSize = 0;
|
||
|
ntStatus = STATUS_BUFFER_TOO_SMALL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return ntStatus;
|
||
|
}
|
||
|
|
||
|
/*****************************************************************************
|
||
|
* ThisManyOnes()
|
||
|
*****************************************************************************
|
||
|
* Returns a byte with the indicated number of ones in the low end.
|
||
|
*/
|
||
|
inline
|
||
|
BYTE
|
||
|
ThisManyOnes
|
||
|
(
|
||
|
IN BYTE Ones
|
||
|
)
|
||
|
{
|
||
|
return ~(BYTE(0xff) << Ones);
|
||
|
}
|
||
|
|
||
|
/*****************************************************************************
|
||
|
* CMiniportTopologySB16::ReadBitsFromMixer()
|
||
|
*****************************************************************************
|
||
|
* Reads specified bits from a mixer register.
|
||
|
*/
|
||
|
BYTE
|
||
|
CMiniportTopologySB16::
|
||
|
ReadBitsFromMixer
|
||
|
(
|
||
|
BYTE Reg,
|
||
|
BYTE Bits,
|
||
|
BYTE Shift
|
||
|
)
|
||
|
{
|
||
|
BYTE data = AdapterCommon->MixerRegRead(Reg);
|
||
|
|
||
|
return( data >> Shift) & ThisManyOnes(Bits);
|
||
|
}
|
||
|
|
||
|
/*****************************************************************************
|
||
|
* CMiniportTopologySB16::WriteBitsToMixer()
|
||
|
*****************************************************************************
|
||
|
* Writes specified bits to a mixer register.
|
||
|
*/
|
||
|
void
|
||
|
CMiniportTopologySB16::
|
||
|
WriteBitsToMixer
|
||
|
(
|
||
|
BYTE Reg,
|
||
|
BYTE Bits,
|
||
|
BYTE Shift,
|
||
|
BYTE Value
|
||
|
)
|
||
|
{
|
||
|
BYTE mask = ThisManyOnes(Bits) << Shift;
|
||
|
BYTE data = AdapterCommon->MixerRegRead(Reg);
|
||
|
|
||
|
if(Reg < DSP_MIX_MAXREGS)
|
||
|
{
|
||
|
AdapterCommon->MixerRegWrite( Reg,
|
||
|
(data & ~mask) | ( (Value << Shift) & mask));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#ifdef EVENT_SUPPORT
|
||
|
/*****************************************************************************
|
||
|
* CMiniportTopologySB16::EventHandler
|
||
|
*****************************************************************************
|
||
|
* This is the generic event handler.
|
||
|
*/
|
||
|
NTSTATUS CMiniportTopologySB16::EventHandler
|
||
|
(
|
||
|
IN PPCEVENT_REQUEST EventRequest
|
||
|
)
|
||
|
{
|
||
|
PAGED_CODE();
|
||
|
|
||
|
ASSERT(EventRequest);
|
||
|
|
||
|
_DbgPrintF (DEBUGLVL_VERBOSE, ("CMiniportTopologyICH::EventHandler"));
|
||
|
|
||
|
// The major target is the object pointer to the topology miniport.
|
||
|
CMiniportTopologySB16 *that =
|
||
|
(CMiniportTopologySB16 *)(PMINIPORTTOPOLOGY(EventRequest->MajorTarget));
|
||
|
|
||
|
ASSERT (that);
|
||
|
|
||
|
// Validate the node.
|
||
|
if (EventRequest->Node != LINEOUT_VOL)
|
||
|
return STATUS_INVALID_PARAMETER;
|
||
|
|
||
|
// What is to do?
|
||
|
switch (EventRequest->Verb)
|
||
|
{
|
||
|
// Do we support event handling?!?
|
||
|
case PCEVENT_VERB_SUPPORT:
|
||
|
_DbgPrintF (DEBUGLVL_VERBOSE, ("BasicSupport Query for Event."));
|
||
|
break;
|
||
|
|
||
|
// We should add the event now!
|
||
|
case PCEVENT_VERB_ADD:
|
||
|
_DbgPrintF (DEBUGLVL_VERBOSE, ("Adding Event."));
|
||
|
|
||
|
// If we have the interface and EventEntry is defined ...
|
||
|
if ((EventRequest->EventEntry) && (that->PortEvents))
|
||
|
{
|
||
|
that->PortEvents->AddEventToEventList (EventRequest->EventEntry);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return STATUS_UNSUCCESSFUL;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case PCEVENT_VERB_REMOVE:
|
||
|
// We cannot remove the event but we can stop generating the
|
||
|
// events. However, it also doesn't hurt to always generate them ...
|
||
|
_DbgPrintF (DEBUGLVL_VERBOSE, ("Removing Event."));
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
return STATUS_INVALID_PARAMETER;
|
||
|
}
|
||
|
|
||
|
return STATUS_SUCCESS;
|
||
|
}
|
||
|
|
||
|
#pragma code_seg()
|
||
|
/*****************************************************************************
|
||
|
* CMiniportTopologySB16::ServiceEvent()
|
||
|
*****************************************************************************
|
||
|
* This routine is called by the ISR to handle the event (volume) interrupt.
|
||
|
*/
|
||
|
STDMETHODIMP_(void) CMiniportTopologySB16::ServiceEvent (void)
|
||
|
{
|
||
|
//
|
||
|
// Generate an event for the master volume (as an example)
|
||
|
//
|
||
|
if (PortEvents)
|
||
|
{
|
||
|
PortEvents->GenerateEventList (NULL, KSEVENT_CONTROL_CHANGE,
|
||
|
FALSE, ULONG(-1), TRUE,
|
||
|
LINEOUT_VOL);
|
||
|
}
|
||
|
}
|
||
|
#endif // EVENT_SUPPORT
|
||
|
|