#include "AppleUSBAudioLevelControl.h" #define super IOAudioLevelControl OSDefineMetaClassAndStructors(AppleUSBAudioLevelControl, IOAudioLevelControl) AppleUSBAudioLevelControl *AppleUSBAudioLevelControl::create (UInt8 theUnitID, UInt8 theInterfaceNumber, UInt8 theControlSelector, UInt8 theChannelNumber, Boolean shouldUpdatePRAM, USBDeviceRequest theUSBDeviceRequest, void *theCallerRefCon, UInt32 subType, UInt32 usage) { AppleUSBAudioLevelControl * control; debug8IOLog ("+AppleUSBAudioLevelControl::create (%d, %d, %d, %d, %p, %lX, %lX)\n", theUnitID, theInterfaceNumber, theControlSelector, theChannelNumber, theUSBDeviceRequest, subType, usage); control = new AppleUSBAudioLevelControl; FailIf (NULL == control, Exit); if (FALSE == control->init (theUnitID, theInterfaceNumber, theControlSelector, theChannelNumber, shouldUpdatePRAM, theUSBDeviceRequest, theCallerRefCon, subType, usage)) { control->release (); control = NULL; } Exit: debug8IOLog ("-AppleUSBAudioLevelControl::create(%d, %d, %d, %d, %p, %lX, %lX)\n", theUnitID, theInterfaceNumber, theControlSelector, theChannelNumber, theUSBDeviceRequest, subType, usage); return control; } bool AppleUSBAudioLevelControl::init (UInt8 theUnitID, UInt8 theInterfaceNumber, UInt8 theControlSelector, UInt8 theChannelNumber, Boolean shouldUpdatePRAM, USBDeviceRequest theUSBDeviceRequest, void *theCallerRefCon, UInt32 subType, UInt32 usage, OSDictionary *properties) { const char * channelName = NULL; SInt16 currentValue; SInt16 deviceMin; SInt16 deviceMax; IOFixed deviceMinDB; IOFixed deviceMaxDB; IOFixed resolutionDB; IOReturn ret; Boolean result; Boolean arePRAMControl; debug8IOLog ("+AppleUSBAudioLevelControl[%p]::init (%d, %d, %d, %d, %p, %p)\n", this, theUnitID, theInterfaceNumber, theControlSelector, theChannelNumber, theUSBDeviceRequest, properties); result = FALSE; arePRAMControl = FALSE; FailIf (NULL == theUSBDeviceRequest, Exit); setValueThreadCall = thread_call_allocate ((thread_call_func_t)updateValueCallback, this); FailIf (NULL == setValueThreadCall, Exit); // If this control is supposed to be a pram volume control, pretend we're just a regular volume control // so that we can get the min and max dB from the device as we init if (theControlSelector == 0xff) { theControlSelector = VOLUME_CONTROL; arePRAMControl = TRUE; } if (kIOAudioLevelControlSubTypeLFEVolume == subType) { // The iSub controls are on channels 1 and 2, but we want to make them look like they're on channel 0 for the HAL // set it to 1 here so we can query the iSub theChannelNumber = 1; } unitID = theUnitID; interfaceNumber = theInterfaceNumber; controlSelector = theControlSelector; channelNumber = theChannelNumber; callerRefCon = theCallerRefCon; usbDeviceRequest = theUSBDeviceRequest; fShouldUpdatePRAM = shouldUpdatePRAM; switch (channelNumber) { case kIOAudioControlChannelIDAll: channelName = kIOAudioControlChannelNameAll; break; case kIOAudioControlChannelIDDefaultLeft: channelName = kIOAudioControlChannelNameLeft; break; case kIOAudioControlChannelIDDefaultRight: channelName = kIOAudioControlChannelNameRight; break; case 0xff: debugIOLog ("++AppleUSBAudioLevelControl: Does not support channel number 0xff.\n"); return FALSE; default: channelName = "Unknown"; break; } currentValue = GetCurVolume (interfaceNumber, channelNumber, &ret); FailIf (kIOReturnSuccess != ret, Exit); debug3IOLog ("channelNumber %d, currentValue = 0x%X, ", channelNumber, currentValue); volRes = GetVolumeResolution (interfaceNumber, channelNumber, &ret); FailIf (kIOReturnSuccess != ret, Exit); debug2IOLog ("vol res = %d, ", volRes); deviceMin = GetMinVolume (interfaceNumber, channelNumber, &ret); FailIf (kIOReturnSuccess != ret, Exit); debug2IOLog ("deviceMin = 0x%X, ", deviceMin); deviceMax = GetMaxVolume (interfaceNumber, channelNumber, &ret); FailIf (kIOReturnSuccess != ret, Exit); debug2IOLog ("deviceMax = 0x%X\n", deviceMax); // Having the device say that it does -infinity dB messes up our math, so set the min at -127.9961dB instead. if ((SInt16)0x8000 == deviceMin) { deviceMin = (SInt16)0x8001; debug2IOLog ("deviceMin adjusted to = %d\n", deviceMin); } deviceMinDB = ConvertUSBVolumeTodB (deviceMin); deviceMaxDB = ConvertUSBVolumeTodB (deviceMax); resolutionDB = ConvertUSBVolumeTodB (volRes); // The volume is incremented in units of this many dB, represented as 1/256 dB (eg 256 == 1dB of control) offset = -deviceMin; debug2IOLog ("offset = %d\n", offset); currentValue = (currentValue + offset) / volRes; if (deviceMin < 0 && deviceMax > 0) { deviceMax += volRes; debug2IOLog ("deviceMax adjusted to = 0x%X\n", deviceMax); } deviceMax = ((deviceMin + offset) + (deviceMax + offset)) / volRes; if (kIOAudioLevelControlSubTypeLFEVolume == subType) { currentValue = currentValue / 2; updateUSBValue (currentValue); } // Set values needed to compute proper PRAM boot beep volume setting fMaxVolume = deviceMax; fMinVolume = deviceMin + offset; deviceMin = -1; if (arePRAMControl) { // If this is a 'pram' control then there is no need to call the hardware. UInt8 curPRAMVol; IODTPlatformExpert * platform = NULL; curPRAMVol = 0; platform = OSDynamicCast (IODTPlatformExpert, getPlatform ()); if (NULL != platform) { platform->readXPRAM ((IOByteCount)kPRamVolumeAddr, &curPRAMVol, (IOByteCount)1); curPRAMVol = (curPRAMVol & 0xF8); } currentValue = curPRAMVol; deviceMin = 0; deviceMax = 7; subType = kIOAudioLevelControlSubTypePRAMVolume; channelName = kIOAudioControlChannelNameAll; theChannelNumber = 0; // force it to the master channel even though we're piggy backing off of the channel 1 control } if (kIOAudioLevelControlSubTypeLFEVolume == subType) { // The iSub controls are on channels 1 and 2, but we want to make them look like they're on channel 0 for the HAL theChannelNumber = 0; channelName = kIOAudioControlChannelNameAll; } // debug4IOLog ("min = %d, max = %d, current = %d\n", (deviceMin + offset) - 1, deviceMax, currentValue); debug4IOLog ("min = %d, max = %d, current = %d\n", deviceMin, deviceMax, currentValue); FailIf (FALSE == super::init (currentValue, deviceMin, deviceMax, deviceMinDB, deviceMaxDB, theChannelNumber, channelName, 0, subType, usage), Exit); result = TRUE; Exit: debug8IOLog ("-AppleUSBAudioLevelControl[%p]::init (%d, %d, %d, %d, %p, %p)\n", this, theUnitID, theInterfaceNumber, theControlSelector, theChannelNumber, theUSBDeviceRequest, properties); return result; } void AppleUSBAudioLevelControl::free () { debug2IOLog ("+AppleUSBAudioLevelControl[%p]::free ()\n", this); if (setValueThreadCall) { thread_call_free (setValueThreadCall); setValueThreadCall = NULL; } super::free (); debug2IOLog ("-AppleUSBAudioLevelControl[%p]::free ()\n", this); } IOReturn AppleUSBAudioLevelControl::performValueChange (OSObject * newValue) { OSNumber * newValueAsNumber; SInt32 newValueAsSInt32; debug3IOLog ("+AppleUSBAudioLevelControl[%p]::performValueChange (%d)\n", this, newValue); newValueAsNumber = OSDynamicCast (OSNumber, newValue); FailIf (NULL == newValueAsNumber, Exit); newValueAsSInt32 = newValueAsNumber->unsigned32BitValue (); debug3IOLog ("++AppleUSBAudioLevelControl[%p]::performValueChange (%ld)\n", this, newValueAsSInt32); if (NULL != setValueThreadCall) { thread_call_enter1 (setValueThreadCall, (thread_call_param_t)newValueAsSInt32); } debug3IOLog ("-AppleUSBAudioLevelControl[%p]::performValueChange (%d)\n", this, newValueAsSInt32); Exit: return kIOReturnSuccess; } void AppleUSBAudioLevelControl::updateUSBValue () { updateUSBValue (getIntValue ()); } void AppleUSBAudioLevelControl::updateUSBValue (SInt32 newValue) { // SInt32 newiSubVolume; SInt16 theValue; SInt16 newVolume; IOReturn ret; debug3IOLog ("+AppleUSBAudioLevelControl[%p]::updateUSBValue (%d)\n", this, newValue); if (newValue < 0) { newVolume = 0x8000; } else { if (newValue > 0) { newVolume = ((newValue - 1) * volRes) - offset; } else { newVolume = (newValue * volRes) - offset; } } debug2IOLog ("volume value is 0x%X\n", newVolume); theValue = HostToUSBWord (newVolume); debug2IOLog ("setting volume to 0x%X (little endian)\n", newVolume); ret = SetCurVolume (interfaceNumber, channelNumber, theValue); if (getSubType () == kIOAudioLevelControlSubTypeLFEVolume) { // We set the iSub's left volume (on channel 1 above), now set it on channel 2 to mimic having only a master volume control ret = SetCurVolume (interfaceNumber, 2, theValue); } if (TRUE == fShouldUpdatePRAM && FALSE == gExpertMode) { // We do that only if we are on a OS 9 like UI guideline WritePRAMVol (newValue, newValue); } if (ret != kIOReturnSuccess) { debug4IOLog ("++AppleUSBAudioLevelContol:updateUSBValue () - set current value for %d:%d failed: 0x%X\n", controlSelector, channelNumber, ret); } debug3IOLog ("-AppleUSBAudioLevelControl[%p]::updateUSBValue (%d)\n", this, newValue); } SInt16 AppleUSBAudioLevelControl::GetCurVolume (UInt8 interfaceNumber, UInt8 channelNumber, IOReturn * error) { IOUSBDevRequest devReq; SInt16 theVolume; devReq.bmRequestType = USBmakebmRequestType (kUSBIn, kUSBClass, kUSBInterface); devReq.bRequest = GET_CUR; devReq.wValue = (controlSelector << 8) | channelNumber; devReq.wIndex = (unitID << 8) | interfaceNumber; devReq.wLength = 2; devReq.pData = &theVolume; *error = usbDeviceRequest (&devReq, callerRefCon); FailIf (kIOReturnSuccess != *error, Error); Exit: return USBToHostWord (theVolume); Error: theVolume = 0; goto Exit; } SInt16 AppleUSBAudioLevelControl::GetMaxVolume (UInt8 interfaceNumber, UInt8 channelNumber, IOReturn * error) { IOUSBDevRequest devReq; SInt16 theVolume; devReq.bmRequestType = USBmakebmRequestType (kUSBIn, kUSBClass, kUSBInterface); devReq.bRequest = GET_MAX; devReq.wValue = (controlSelector << 8) | channelNumber; devReq.wIndex = (unitID << 8) | interfaceNumber; devReq.wLength = 2; devReq.pData = &theVolume; *error = usbDeviceRequest (&devReq, callerRefCon); FailIf (kIOReturnSuccess != *error, Error); Exit: return USBToHostWord (theVolume); Error: theVolume = 0; goto Exit; } SInt16 AppleUSBAudioLevelControl::GetMinVolume (UInt8 interfaceNumber, UInt8 channelNumber, IOReturn * error) { IOUSBDevRequest devReq; SInt16 theVolume; devReq.bmRequestType = USBmakebmRequestType (kUSBIn, kUSBClass, kUSBInterface); devReq.bRequest = GET_MIN; devReq.wValue = (controlSelector << 8) | channelNumber; devReq.wIndex = (unitID << 8) | interfaceNumber; devReq.wLength = 2; devReq.pData = &theVolume; *error = usbDeviceRequest (&devReq, callerRefCon); FailIf (kIOReturnSuccess != *error, Error); Exit: return USBToHostWord (theVolume); Error: theVolume = 0; goto Exit; } UInt16 AppleUSBAudioLevelControl::GetVolumeResolution (UInt8 interfaceNumber, UInt8 channelNumber, IOReturn * error) { IOUSBDevRequest devReq; UInt16 theResolution; devReq.bmRequestType = USBmakebmRequestType (kUSBIn, kUSBClass, kUSBInterface); devReq.bRequest = GET_RES; devReq.wValue = (controlSelector << 8) | channelNumber; devReq.wIndex = (unitID << 8) | interfaceNumber; devReq.wLength = 2; devReq.pData = &theResolution; *error = usbDeviceRequest (&devReq, callerRefCon); FailIf (kIOReturnSuccess != *error, Error); Exit: return USBToHostWord (theResolution); Error: theResolution = 0; goto Exit; } IOReturn AppleUSBAudioLevelControl::SetCurVolume (UInt8 interfaceNumber, UInt8 channelNumber, SInt16 volume) { IOUSBDevRequest devReq; IOReturn error; devReq.bmRequestType = USBmakebmRequestType (kUSBOut, kUSBClass, kUSBInterface); devReq.bRequest = SET_CUR; devReq.wValue = (controlSelector << 8) | channelNumber; devReq.wIndex = (unitID << 8) | interfaceNumber; devReq.wLength = 2; devReq.pData = &volume; FailIf ((TRUE == isInactive()), DeviceInactive); // In case we've been unplugged during sleep error = usbDeviceRequest (&devReq, callerRefCon); FailIf (kIOReturnSuccess != error, Exit); Exit: return error; DeviceInactive: debugIOLog("AppleUSBAudioLevelControl::SetCurVolume ERROR attempt to send a device request to and inactive device\n"); error = kIOReturnError; goto Exit; } void AppleUSBAudioLevelControl::updateValueCallback (void *arg1, void *arg2) { AppleUSBAudioLevelControl *levelControl; SInt32 value; UInt32 subType; debug3IOLog ("+AppleUSBAudioLevelControl::updateValueCallback (%p, %p)\n", (UInt32*)arg1, (UInt32*)arg2); levelControl = OSDynamicCast (AppleUSBAudioLevelControl, (OSObject*)arg1); value = (SInt32)arg2; if (levelControl) { subType = levelControl->getSubType (); if (kIOAudioLevelControlSubTypePRAMVolume == subType) { UInt8 curPRAMVol; IODTPlatformExpert * platform = NULL; platform = OSDynamicCast (IODTPlatformExpert, getPlatform ()); if (NULL != platform) { platform->readXPRAM ((IOByteCount)kPRamVolumeAddr, &curPRAMVol, (IOByteCount)1); curPRAMVol = (curPRAMVol & 0xF8) | value; platform->writeXPRAM ((IOByteCount)kPRamVolumeAddr, &curPRAMVol, (IOByteCount)1); } } else { levelControl->updateUSBValue (value); } } debug3IOLog ("-AppleUSBAudioLevelControl::updateValueCallback (%p, %p)\n", (UInt32*)arg1, (UInt32*)arg2); } // This is how the thing is defined in the USB Audio spec (section 5.2.2.4.3.2 for the curious). // The volume setting of a device is described in 1/256 dB increments using a number that goes from // a max of 0x7fff (127.9961 dB) down to 0x8001 (-127.9961 dB) using standard signed math, but 0x8000 // is actually negative infinity (not -128 dB), so I have to special case it. IOFixed AppleUSBAudioLevelControl::ConvertUSBVolumeTodB (SInt16 volume) { IOFixed dBVolumeFixed; if (volume == (SInt16)0x8000) { dBVolumeFixed = ((SInt16)0x8000 * 256) << 8; // really is negative infinity } else { dBVolumeFixed = volume * 256; } debug3IOLog ("volume = %d, dBVolumeFixed = 0x%X\n", volume, dBVolumeFixed); return dBVolumeFixed; } #pragma mark +PRAM VOLUME //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Calculates the PRAM volume value for stereo volume. UInt8 AppleUSBAudioLevelControl::VolumeToPRAMValue (SInt32 leftVol, SInt32 rightVol) { UInt32 pramVolume; // Volume level to store in PRAM UInt32 averageVolume; // summed volume const UInt32 volumeRange = (fMaxVolume - fMinVolume + 1); UInt32 volumeSteps; if (leftVol < 0) leftVol = 0; if (rightVol < 0) rightVol = 0; averageVolume = (leftVol + rightVol) >> 1; // sum the channel volumes and get an average volumeSteps = volumeRange / kMaximumPRAMVolume; // divide the range by the range of the pramVolume pramVolume = averageVolume / volumeSteps; // Since the volume level in PRAM is only worth three bits, // we round small values up to 1. This avoids SysBeep from // flashing the menu bar when it thinks sound is off and // should really just be very quiet. if ((pramVolume == 0) && (leftVol != 0 || rightVol !=0 )) { pramVolume = 1; } return (pramVolume & 0x07); } void AppleUSBAudioLevelControl::WritePRAMVol (SInt32 leftVol, SInt32 rightVol) { UInt8 pramVolume; UInt8 curPRAMVol; IODTPlatformExpert * platform = NULL; platform = OSDynamicCast (IODTPlatformExpert, getPlatform ()); debug3IOLog ("AppleUSBTrinityAudioDevice::WritePRAMVol leftVol=%lu, rightVol=%lu\n",leftVol, rightVol); if (NULL != platform) { pramVolume = VolumeToPRAMValue (leftVol, rightVol); // get the old value to compare it with platform->readXPRAM ((IOByteCount)kPRamVolumeAddr, &curPRAMVol, (IOByteCount)1); // Update only if there is a change if (pramVolume != (curPRAMVol & 0x07)) { // clear bottom 3 bits of volume control byte from PRAM low memory image curPRAMVol = (curPRAMVol & 0xF8) | pramVolume; debug2IOLog ("AppleUSBAudioLevelControl::WritePRAMVol curPRAMVol=0x%x\n", curPRAMVol); // write out the volume control byte to PRAM platform->writeXPRAM ((IOByteCount)kPRamVolumeAddr, &curPRAMVol, (IOByteCount) 1); } } }