/*
 * Copyright (c) 1998-2006 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */
 
#include "AppleiSubEngine.h"
#include "AppleUSBAudioCommon.h"
#include "AppleUSBAudioLevelControl.h"
#include "AppleUSBAudioMuteControl.h"
#include "USBAudioObject.h"

#include <IOKit/audio/IOAudioStream.h>
#include <IOKit/audio/IOAudioEngine.h>
#include <IOKit/pwr_mgt/RootDomain.h>

#define kiSubFeatureUnitID					2
#define kiSubMuteControlChannelNum			0
#define kiSubVolumeControlMasterChannelNum	1

// Has to be true or else the iSub loses the beginning of sounds if it has auto powered down
#define NUM_POWER_STATES					2

#define LOGISUBDELAY FALSE

#define super IOService

// aml 02.14.02 mandatory definitions of static constants
const UInt32 AppleiSubEngine::kDefaultOutputSampleRate;		
const UInt32 AppleiSubEngine::kDefaultNumChannels;		
const UInt32 AppleiSubEngine::kDefaultBytesPerSample;		
const iSubAltInterfaceType AppleiSubEngine::kDefaultiSubAltInterface;		

OSDefineMetaClassAndStructors (AppleiSubEngine, super)

#pragma mark -IOKit Routines-

void AppleiSubEngine::closeiSub (IOService * forClient) {
	IOReturn				result;

	debugIOLog ("+ AppleiSubEngine[%p]::closeiSub (%p)", this, forClient);

	if (forClient == mAudioEngine) 
	{
		if (    (    (NULL != mMuteControl)
				  || (NULL != miSubVolumeControl)) 
			 && (NULL != mAudioEngine)) 
		{
			debugIOLog ("? AppleiSubEngine[%p]::closeiSub () - Removing iSub audio controls", this);
	
			if (NULL != miSubVolumeControl) 
			{
				result = mAudioEngine->removeDefaultAudioControl (miSubVolumeControl);
				miSubVolumeControl->release ();
				miSubVolumeControl = NULL;
				#if DEBUGLOGGING
				if (kIOReturnSuccess != result) debugIOLog ("! AppleiSubEngine[%p]::closeiSub () - Error 0x%x removing left iSub control", this, result);
				#endif
			}
	
			if (NULL != mMuteControl) 
			{
				result = mAudioEngine->removeDefaultAudioControl (mMuteControl);
				mMuteControl->release ();
				mMuteControl = NULL;
				#if DEBUGLOGGING
				if (kIOReturnSuccess != result) debugIOLog ("! AppleiSubEngine[%p]::closeiSub () - Error 0x%x removing mute iSub control", this, result);
				#endif
			}
	
			release ();		// It's safe to release us now, there are no more controls that might be able to call into us.
			// Don't try to use our audio engine host anymore
			mAudioEngine = NULL;
		#if DEBUGLOGGING
		} 
		else 
		{
			if (NULL == mMuteControl) {debugIOLog ("? AppleiSubEngine[%p]::closeiSub () - NULL == mMuteControl", this);}
			if (NULL == miSubVolumeControl) {debugIOLog ("? AppleiSubEngine[%p]::closeiSub () - NULL == miSubVolumeControl", this);}
			if (NULL == mAudioEngine) {debugIOLog ("? AppleiSubEngine[%p]::closeiSub () - NULL == mAudioEngine", this);}
		#endif
		}
	} 
	else 
	{
		debugIOLog ("! AppleiSubEngine[%p]::closeiSub () - The wrong client tried to close the iSub.", this);
	}

	debugIOLog ("- AppleiSubEngine[%p]::closeiSub (%p)", this, forClient);
}

void AppleiSubEngine::free (void) {
	UInt32							frameIndex;

	debugIOLog ("+ AppleiSubEngine[%p]::free ()", this);

	if (NULL != mPipe) 
	{
		mPipe->release ();
		mPipe = NULL;
	}

	if (NULL != mSampleBufferDescriptor) 
	{
		mSampleBufferDescriptor->release ();
		mSampleBufferDescriptor = NULL;
	}

	if (NULL != mSampleBuffer) 
	{
		IOFree (mSampleBuffer, mBufferSize);
		mSampleBuffer = NULL;
	}

	for (frameIndex = 0; frameIndex < NUM_ISUB_FRAME_LISTS; frameIndex++) 
	{
		if (NULL != mSoundBuffer[frameIndex]) 
		{
			mSoundBuffer[frameIndex]->release ();
			mSoundBuffer[frameIndex] = NULL;
		}
	}

	super::free ();

	debugIOLog ("- AppleiSubEngine[%p]::free ()", this);
	return;
}

// This has to be called on the audio engine's work loop so that we don't interrupt the IOAudioFamily
bool AppleiSubEngine::openiSub (IOService * forClient, iSubRequestCloseRoutine theiSubRequestCloseRoutine) {
    bool						resultCode;

	resultCode = FALSE;

	// Only one client at a time!
	FailIf (NULL != mAudioEngine, Exit);
	mAudioEngine = OSDynamicCast (IOAudioEngine, forClient);
	FailIf (NULL == mAudioEngine, Exit);

	FailIf (NULL == theiSubRequestCloseRoutine, Exit);
	miSubRequestCloseRoutine = theiSubRequestCloseRoutine;

//	mAudioEngine->joinPMtree (this);
//	if (pm_vars != NULL) 
//	{
//		registerPowerDriver (this, iSubPowerStates, NUM_POWER_STATES);
//		changePowerStateTo (1);
//	}

	mAudioEngine->pauseAudioEngine ();
	mAudioEngine->beginConfigurationChange ();
	retain ();		// As long as there are controls out there, don't let ourselves be released.

	// Only need a master volume control now that the iSub is running mono.
	miSubVolumeControl = AppleUSBAudioLevelControl::create (kiSubFeatureUnitID,
													mControlInterface->GetInterfaceNumber (),
													VOLUME_CONTROL,
													kiSubVolumeControlMasterChannelNum,
													FALSE,
													(USBDeviceRequest)&deviceRequest,
													this,
													kIOAudioLevelControlSubTypeLFEVolume,
													kIOAudioControlUsageOutput);
	FailIf (NULL == miSubVolumeControl, Exit);

	mAudioEngine->addDefaultAudioControl (miSubVolumeControl);

	mMuteControl = AppleUSBAudioMuteControl::create (kiSubFeatureUnitID,
													mControlInterface->GetInterfaceNumber (),
													0,
													(USBDeviceRequest)&deviceRequest,
													this,
													kIOAudioControlUsageOutput,
													kIOAudioToggleControlSubTypeLFEMute);
	FailIf (NULL == mMuteControl, Exit);

	mAudioEngine->addDefaultAudioControl (mMuteControl);

	mAudioEngine->completeConfigurationChange ();
	mAudioEngine->resumeAudioEngine ();

	mNeedToSync = false;	// aml [3095619]
	
	resultCode = TRUE;

Exit:
	debugIOLog ("- AppleiSubEngine[%p]::handleOpen (%p, 0x%lx, %p) = %d", this, forClient, resultCode);
	return resultCode;
}

bool AppleiSubEngine::init (OSDictionary * properties) {
	Boolean							resultCode;

	debugIOLog ("+ AppleiSubEngine[%p]::init (%p)", this, properties);

	resultCode = FALSE;
	mTerminatingDriver = FALSE;

	FailIf (FALSE == super::init (properties), Exit);
	
	resultCode = TRUE;

	// aml 2.28.02 initialize member vars
	mFormat.altInterface = kDefaultiSubAltInterface;		
	mFormat.numChannels = kDefaultNumChannels;		
	mFormat.bytesPerSample = kDefaultBytesPerSample;		
	mFormat.outputSampleRate = kDefaultOutputSampleRate;
	
Exit:
	debugIOLog ("- AppleiSubEngine[%p]::init (%p) = %d", this, properties, resultCode);
	return resultCode;
}

bool AppleiSubEngine::start (IOService * provider) {
	Boolean							resultBool;
	IOUSBFindInterfaceRequest		findRequest;
	IOUSBFindEndpointRequest		audioIsochEndpoint;
	IOReturn						resultIOReturn;
	UInt32							i;
	UInt32							frameListNum;
	UInt16							numQueued;
	static IOPMPowerState			iSubPowerStates[2] = {
		{kIOPMPowerStateVersion1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{kIOPMPowerStateVersion1, kIOPMDeviceUsable, kIOPMPowerOn, kIOPMPowerOn, 0, 0, 0, 0, 0, 0, 0, 0}
	};

	debugIOLog ("+ AppleiSubEngine[%p]::start (%p)", this, provider);

	resultBool = FALSE;
	FailIf (FALSE == super::start (provider), Exit);

	mStreamInterface = OSDynamicCast (IOUSBInterface, provider);
	FailIf (NULL == mStreamInterface, Exit);

	mThisInterfaceNumber = mStreamInterface->GetInterfaceNumber ();
	debugIOLog ("? AppleiSubEngine[%p]::start () - AppleiSubEngine->mThisInterfaceNumber = %d", this, mThisInterfaceNumber);
	// aml 1.18.02 replaced constant with member
	mAltInterfaceID = mFormat.altInterface;		

	PMinit ();
	mStreamInterface->joinPMtree (this);
	if (pm_vars != NULL) 
	{
		registerPowerDriver (this, iSubPowerStates, NUM_POWER_STATES);
		changePowerStateTo (1);
	}

	// aml 1.18.02 replaced sample rate with member
	// aml 2.13.02 replaced kInputSampleRate with mOutputSampleRate
	// aml 2.13.02 added num channels and bit depth constants, instead of '4'
	mFrameListSize = CalculateNumSamplesPerBuffer (mFormat.outputSampleRate, NUM_ISUB_FRAMES_PER_LIST) * mFormat.numChannels * mFormat.bytesPerSample;
	debugIOLog ("? AppleiSubEngine[%p]::start () - format = %ldHz, %ld channels, %ld bits", this, mFormat.outputSampleRate, mFormat.numChannels, mFormat.bytesPerSample * 8);
	#if AML_DEBUGLOG
	debugIOLog ("? AppleiSubEngine[%p]::start () - handleOpen: mFrameListSize = %d", mFrameListSize, this);
	#endif
	debugIOLog ("? AppleiSubEngine[%p]::start () - mFrameListSize = %d", this, mFrameListSize);
	// aml 2.13.02 replaced kInputSampleRate with mOutputSampleRate
	// aml 2.13.02 added num channels and bit depth members, instead of '4'
	mBufferSize = CalculateNumSamplesPerBuffer (mFormat.outputSampleRate, NUM_ISUB_FRAMES_PER_LIST, NUM_ISUB_FRAME_LISTS) * mFormat.numChannels * mFormat.bytesPerSample;
	debugIOLog ("? AppleiSubEngine[%p]::start () - mBufferSize = %d", this, mBufferSize);
	mSampleBuffer = IOMallocAligned (mBufferSize, PAGE_SIZE);
	FailIf (NULL == mSampleBuffer, Exit);
	// aml 2.13.02 changed from clearing by longs to words
	for (i = 0; i < mBufferSize / sizeof(UInt16); i++) 
	{
		((UInt16*)mSampleBuffer)[i] = 0;
	}
	mSampleBufferDescriptor = IOMemoryDescriptor::withAddress (mSampleBuffer, mBufferSize, kIODirectionNone);
	FailIf (NULL == mSampleBufferDescriptor, Exit);

	FailIf (kIOReturnSuccess != PrepareFrameLists (mFrameListSize), Exit);
//	FailIf (TRUE == mStreamOpened, Exit);
//	debugIOLog ("after TRUE == mStreamOpened");
	mShouldCloseStream = FALSE;
	FailIf (FALSE == mStreamInterface->open (this), Exit);
	mStreamOpened = TRUE;

	resultIOReturn = mStreamInterface->SetAlternateInterface (this, kRootAlternateSetting);
	FailIf (kIOReturnSuccess != resultIOReturn, Exit);

	// aml 1.18.02 replaced 4 (16 bit stereo alternate interface) with member
 	resultIOReturn = mStreamInterface->SetAlternateInterface (this, mFormat.altInterface);
	FailIf (kIOReturnSuccess != resultIOReturn, Exit);

	// Acquire a PIPE for the isochronous stream.
	audioIsochEndpoint.type = kUSBIsoc;
	audioIsochEndpoint.direction = kUSBOut;

	mPipe = mStreamInterface->FindNextPipe (NULL, &audioIsochEndpoint);
	FailIf (NULL == mPipe, Exit);
	mPipe->retain ();

	// The iSub is a little funky and reseting it improves (but doesn't totally eliminate) funky behavior.
	// mStreamInterface->GetDevice()->ResetDevice ();

	// Start the iSub playing silence now
	mLoopCount = 0;

	mNumUSBFrameListsNotOutstanding = 0;
	numQueued = 0;
	mCurrentFrameList = 0;
	mCurrentByteOffset = 0;
	mFirstFrame = mStreamInterface->GetDevice()->GetBus()->GetFrameNumber () + kMinimumiSubFrameOffset;
	for (frameListNum = mCurrentFrameList; numQueued < NUM_ISUB_FRAME_LISTS_TO_QUEUE; frameListNum++) 
	{
		resultIOReturn = WriteFrameList (frameListNum);
		FailIf (kIOReturnSuccess != resultIOReturn, Exit);
		numQueued++;
	}
	debugIOLog ("? AppleiSubEngine[%p]::start () - writeFrameList resultIOReturn = 0x%lx", this, resultIOReturn);

	// Find control interface on iSub device
	findRequest.bInterfaceClass = 1;
	findRequest.bInterfaceSubClass = 1;
	findRequest.bInterfaceProtocol = kIOUSBFindInterfaceDontCare;
	findRequest.bAlternateSetting = kIOUSBFindInterfaceDontCare;
	mControlInterface = mStreamInterface->GetDevice()->FindNextInterface (NULL, &findRequest);
	FailIf (NULL == mControlInterface, Exit);

	registerService ();
	resultBool = TRUE;

Exit:
	debugIOLog ("- AppleiSubEngine[%p]::start (%p) = %d", this, provider, resultBool);
	return resultBool;
}

void AppleiSubEngine::stop (IOService *provider) {
	debugIOLog("+ AppleiSubEngine::stop");

	PMstop();

	super::stop(provider);

	debugIOLog("- AppleiSubEngine::stop");
}

bool AppleiSubEngine::willTerminate (IOService * provider, IOOptionBits options) {
	bool					resultCode;

	debugIOLog ("+ AppleiSubEngine[%p]::willTerminate (%p, 0x%x) rc =", this, provider, options, getRetainCount ());

	mTerminatingDriver = TRUE;

	if (NULL != mAudioEngine) 
	{
		(miSubRequestCloseRoutine)(mAudioEngine);
	}

	if (FALSE == miSubUSBRunning) 
	{
		// close our stream interface and go away
		if (    (mStreamInterface != NULL) 
			 && (mStreamInterface == provider)) 
		{
			debugIOLog ("? AppleiSubEngine[%p]::willTerminate () - Closing iSub down", this);
			if (NULL != mPipe) 
			{
				mPipe->release ();
				mPipe = NULL;
			}
			mStreamInterface->close (this);
			mStreamOpened = FALSE;
		}
	} 
	else 
	{
		mShouldCloseStream = TRUE;
	}

	resultCode = super::willTerminate (provider, options);

	debugIOLog ("- AppleiSubEngine[%p]::willTerminate (%p, 0x%x) = %d, rc=%d", this, provider, options, resultCode, getRetainCount ());
	return resultCode;
}

IOReturn AppleiSubEngine::setPowerState (unsigned long powerStateOrdinal, IOService *device) {
	IOReturn					result;

	debugIOLog("+ AppleiSubEngine[%p]::setPowerState (%lu, %p)", this, powerStateOrdinal, device);

	result = kIOReturnError;

	if (device == this) 
	{
		switch (powerStateOrdinal) 
		{
			case 0:		// Power off state
				debugIOLog ("? AppleiSubEngine[%p]::setPowerState () - iSub is going to sleep. Setting mIsSleeping = TRUE ...", this);
				mIsSleeping = TRUE;
				StopiSub ();
				result = IOPMNoErr;
				break;
			case 1:		// Power on state
				if (mIsSleeping)
				{
					debugIOLog ("? AppleiSubEngine[%p]::setPowerState () - iSub is waking...");
				}
				mIsSleeping = FALSE;
				result = IOPMNoErr;
				break;
			default:
				result = IOPMNoSuchState;
		}
	}
	debugIOLog("- AppleiSubEngine[%p]::setPowerState (%lu, %p) = 0x%x", this, powerStateOrdinal, device, result);
	return result;
}

#pragma mark -iSub Routines-

UInt32 AppleiSubEngine::CalculateNumSamplesPerBuffer (UInt32 sampleRate, UInt32 theNumFramesPerList, UInt32 theNumFrameLists) {
	UInt32							numSamplesPerFrameList;
	UInt32 							totalFrames;
	UInt32							numAlternateFrames;
	UInt32							numAverageFrames;
	UInt16							averageSamplesPerFrame;
	UInt16							additionalSampleFrameFreq;

	CalculateSamplesPerFrame (sampleRate, &averageSamplesPerFrame, &additionalSampleFrameFreq);
	if (0 == additionalSampleFrameFreq) 
	{
		numSamplesPerFrameList = averageSamplesPerFrame * theNumFramesPerList * theNumFrameLists;
	} 
	else 
	{
		totalFrames = theNumFramesPerList * theNumFrameLists;

		numAlternateFrames = totalFrames / additionalSampleFrameFreq;
		numAverageFrames = totalFrames - numAlternateFrames;
		numSamplesPerFrameList = (numAverageFrames * averageSamplesPerFrame) + (numAlternateFrames * (averageSamplesPerFrame + 1));
	}

	return numSamplesPerFrameList;
}

void AppleiSubEngine::CalculateSamplesPerFrame (UInt32 sampleRate, UInt16 * averageSamplesPerFrame, UInt16 * additionalSampleFrameFreq) {
	UInt32							divisor;

	*averageSamplesPerFrame = sampleRate / 1000;

	divisor = (sampleRate % 1000);

	if (divisor)
		*additionalSampleFrameFreq = 1000 / divisor;
	else
		*additionalSampleFrameFreq = 0;
}

IOReturn AppleiSubEngine::deviceRequest (IOUSBDevRequest * request, AppleiSubEngine * self, IOUSBCompletion * completion) {
	IOReturn						result;

	debugIOLog ("+ AppleiSubEngine[%p]::deviceRequest (%p, %p)", self, request, completion);
	result = kIOReturnSuccess;
	if (    (self->mStreamInterface)
		 && (request)
		 && (FALSE == self->mTerminatingDriver)) 
	{
		result = self->mStreamInterface->DeviceRequest (request, completion);
	}

	debugIOLog ("- AppleiSubEngine[%p]::deviceRequest (%p, %p) = 0x%x", self, request, completion, result);
	return result;
}

UInt32 AppleiSubEngine::GetCurrentByteCount (void) {
	return mCurrentByteOffset;
}

UInt32 AppleiSubEngine::GetCurrentLoopCount (void) {
	return mLoopCount;
}

IOMemoryDescriptor * AppleiSubEngine::GetSampleBuffer (void) {
	return mSampleBufferDescriptor;
}

IOReturn AppleiSubEngine::PrepareFrameLists (UInt32 frameListSize) {
	IOReturn						result;
	UInt32							frameIndex;

	result = kIOReturnError;

	for (frameIndex = 0; frameIndex < NUM_ISUB_FRAME_LISTS; frameIndex++) 
	{
		mUSBCompletion[frameIndex].target = (void *)this;
		mUSBCompletion[frameIndex].parameter = (void *)((UInt8 *)mSampleBuffer + (frameIndex * frameListSize));	// pointer into the buffer that is the start of this frame list
		mUSBCompletion[frameIndex].action = WriteHandler;

		mSoundBuffer[frameIndex] = IOMemoryDescriptor::withAddress ((UInt8 *)mUSBCompletion[frameIndex].parameter, frameListSize, kIODirectionNone);
		FailIf (NULL == mSoundBuffer[frameIndex], Exit);
	}

	result = kIOReturnSuccess;

Exit:
	return result;
}

IOReturn AppleiSubEngine::StartiSub (void) {
	UInt32							frameListNum;
	UInt16							numQueued = 0;	// aml this was unititialized and the for loop below never ran sometimes!
    IOReturn						resultCode;

	debugIOLog ("+ AppleiSubEngine[%p]::StartiSub ()", this);
	// [rdar://4206816] If sleeping, give the power thread a chance to wake it up (maximum of 500 ms). /thw
	if (mIsSleeping)
	{
		for (UInt8 i = 0; i < 5 && mIsSleeping; i++)
		{
			IOSleep (100);
		}
	}
	
	FailWithAction (TRUE == mIsSleeping, resultCode = kIOReturnOffline, Exit);
	resultCode = kIOReturnError;
	miSubRunning = TRUE;

	#if ABORT_PIPE_ON_START
	FailIf (NULL == mPipe, Exit);
	mPipe->Abort ();		// let's kill all outstanding IO and start right back at the beginning
	miSubUSBRunning = FALSE;
	#else
	mLoopCount = 0xFFFFFFFF;			// so that it will go to 0 in the write completion routine when the frames are aborted
	mCurrentFrameList = NUM_ISUB_FRAME_LISTS - 1;
	#endif // ABORT_PIPE_ON_START

	resultCode = kIOReturnSuccess;

	if (FALSE == miSubUSBRunning) 
	{
		mLoopCount = 0;

		mNumUSBFrameListsNotOutstanding = 0;
		numQueued = 0;
		mCurrentFrameList = 0;
		mCurrentByteOffset = 0;
		FailIf (NULL == mStreamInterface, Exit);
		mFirstFrame = mStreamInterface->GetDevice()->GetBus()->GetFrameNumber () + kMinimumiSubFrameOffset;
		for (frameListNum = mCurrentFrameList; numQueued < NUM_ISUB_FRAME_LISTS_TO_QUEUE; frameListNum++) 
		{
			resultCode = WriteFrameList (frameListNum);
			FailIf (kIOReturnSuccess != resultCode, Exit);
			numQueued++;
		}
	}

Exit:
	debugIOLog ("- AppleiSubEngine[%p]::StartiSub (), result = 0x%x", this, resultCode);

	return resultCode;
}

IOReturn AppleiSubEngine::StopiSub (void) {

	debugIOLog ("+ AppleiSubEngine[%p]::StopiSub ()", this);

	miSubRunning = FALSE;

	debugIOLog ("- AppleiSubEngine[%p]::StopiSub ()", this);

	return kIOReturnSuccess;
}

IOReturn AppleiSubEngine::WriteFrameList (UInt32 frameListNum) {
    UInt32							frameIndex;
	UInt32							firstFrame;
	UInt16							averageFrameSamples;
	UInt16							averageFrameSize;
	UInt16							alternateFrameSize;
	UInt16							additionalSampleFrameFreq;
    IOReturn						result;

	result = kIOReturnError;

	firstFrame = (frameListNum % NUM_ISUB_FRAME_LISTS_TO_QUEUE) * NUM_ISUB_FRAMES_PER_LIST;

	// aml 2.13.02 replaced kInputSampleRate with kOutputSampleRate
	CalculateSamplesPerFrame (mFormat.outputSampleRate, &averageFrameSamples, &additionalSampleFrameFreq);
	// aml 2.13.02 added num channels and bit depth constants, instead of '4'
	averageFrameSize = averageFrameSamples * mFormat.numChannels * mFormat.bytesPerSample;
	alternateFrameSize = (averageFrameSamples + 1) * mFormat.numChannels * mFormat.bytesPerSample;

	if (additionalSampleFrameFreq) 
	{
		for (frameIndex = 0; frameIndex < NUM_ISUB_FRAMES_PER_LIST; frameIndex++) 
		{
			mFrames[firstFrame + frameIndex].frStatus = -1;
			if ((frameIndex % additionalSampleFrameFreq) == (UInt16)(additionalSampleFrameFreq - 1)) 
			{
				mFrames[firstFrame + frameIndex].frReqCount = alternateFrameSize;
			} 
			else 
			{
				mFrames[firstFrame + frameIndex].frReqCount = averageFrameSize;
			}
			mFrames[firstFrame + frameIndex].frActCount = 0;
		}
	} 
	else 
	{
		for (frameIndex = 0; frameIndex < NUM_ISUB_FRAMES_PER_LIST; frameIndex++) 
		{
			mFrames[firstFrame + frameIndex].frStatus = -1;
			mFrames[firstFrame + frameIndex].frReqCount = averageFrameSize;
			mFrames[firstFrame + frameIndex].frActCount = 0;
		}
	}

    FailIf (NULL == mPipe, Exit);
	// retain ();		// Don't want the driver being terminated until our completion routine runs.
    result = mPipe->Write (mSoundBuffer[frameListNum], mFirstFrame, NUM_ISUB_FRAMES_PER_LIST, &mFrames[firstFrame], &mUSBCompletion[frameListNum]);

	if (result != kIOReturnSuccess) 
	{
		if (mStreamInterface) 
		{
			debugIOLog ("! AppleiSubEngine[%p]::WriteFrameList (%d) - error writing to pipe at frame %lu - current = %lu: 0x%x", this, frameListNum, (UInt32)mFirstFrame, (UInt32)mStreamInterface->GetDevice()->GetBus()->GetFrameNumber(), result);
		}
		miSubUSBRunning = FALSE;
	} 
	else 
	{
		mFirstFrame += NUM_ISUB_FRAMES_PER_LIST;
		miSubUSBRunning = TRUE;
	}

Exit:
	return result;
}

void AppleiSubEngine::WriteHandler (void * object, void * buffer, IOReturn result, IOUSBIsocFrame * pFrames) {
	AppleiSubEngine *				self;
	UInt64							currentUSBFrame;
	UInt32							frameListToWrite;
	UInt32							i;

	self = (AppleiSubEngine *)object;

	FailIf (TRUE == self->mIsSleeping, Exit);

	if (result != kIOReturnSuccess) 
	{
		if (result != kIOReturnAborted)
		{
			// Currently AppleUSBAudio aborts the pipe any time the stream is started. We don't need to see the error for this, as it is expected.
			debugIOLog ("! AppleiSubEngine::WriteHandler () - error 0x%x", result);
		}
		FailIf (NULL == self->mStreamInterface, Exit);
		currentUSBFrame = self->mStreamInterface->GetDevice()->GetBus()->GetFrameNumber ();
		switch (result) 
		{
			#if ABORT_PIPE_ON_START
			case kIOReturnAborted:
			#if AML_DEBUGLOG
			debugIOLog ("? AppleiSubEngine::WriteHandler() - Aborted.");
			#endif
			goto Exit;			// do nothing
			break;
			case kIOReturnOverrun:
			#else
			case kIOReturnOverrun:
			case kIOReturnAborted:
			#endif
			default:
				// skip ahead and see if that helps
				if (self->mFirstFrame <= currentUSBFrame) 
				{
					self->mFirstFrame = currentUSBFrame + kMinimumiSubFrameOffset;
					debugIOLog ("! AppleiSubEngine::WriteHandler () - Skipping ahead to frame %ld", self->mFirstFrame);
				}
				break;
		}
	}

	// aml 4.25.02 moved below error checking!

	// Zero the data in the buffer so that this buffer just contains silence
	// aml 2.13.02 changed from clearing by longs to words
	for (i = 0; i < self->mFrameListSize / sizeof(UInt16); i++) 
	{
		((UInt16*)buffer)[i] = 0;
	}

	if ((NUM_ISUB_FRAME_LISTS - 1) == self->mCurrentFrameList) 
	{
		if (TRUE == self->miSubRunning) 
		{
			self->mLoopCount++;
			self->mCurrentByteOffset = 0;
		}
		self->mCurrentFrameList = 0;
	} 
	else 
	{
		self->mCurrentFrameList++;
		if (TRUE == self->miSubRunning) 
		{
			self->mCurrentByteOffset = self->mCurrentFrameList * self->mFrameListSize;
		}
	}

	if (FALSE == self->mShouldCloseStream) 
	{
		frameListToWrite = self->mCurrentFrameList + NUM_ISUB_FRAME_LISTS_TO_QUEUE - 1;
		if (frameListToWrite >= NUM_ISUB_FRAME_LISTS) 
		{
			frameListToWrite -= NUM_ISUB_FRAME_LISTS;
		}
		self->WriteFrameList (frameListToWrite);
	}

	// aml - added for [3095619].  Keep track of time differences between calls to monitor scheduling latency
	// and set the need to sync flag if we are held off for more time than all of our queued data.
	AbsoluteTime					t_now;
	AbsoluteTime					t_old;
	UInt64							nanos_del;
	
	t_old = self->mLastTime;
	clock_get_uptime (&t_now);
	self->mLastTime = t_now;
	SUB_ABSOLUTETIME (&t_now, &t_old);		// t_now -= t_old;
	absolutetime_to_nanoseconds (t_now, &nanos_del);

	#if LOGISUBDELAY
	nanos_del = nanos_del / (1000);
	if (nanos_del > 11000) 
	{
		debugIOLog ("? AppleiSubEngine::WriteHandler () - delta = %ld microseconds", (UInt32)(nanos_del));
	} 
	else if (nanos_del < 9000) 
	{
		debugIOLog ("? AppleiSubEngine::WriteHandler () - delta = %ld microseconds", (UInt32)(nanos_del));
	}
	#endif

	if (nanos_del > ((NUM_ISUB_FRAME_LISTS_TO_QUEUE * NUM_ISUB_FRAMES_PER_LIST + 1) * 1000 * 1000)) 
	{
		self->mNeedToSync = true;
	}	
	// end added for [3095619]
	
Exit:
	if (TRUE == self->mShouldCloseStream) 
	{
		debugIOLog ("? AppleiSubEngine[%p]::WriteHandler () - stopping: %d", self, self->mNumUSBFrameListsNotOutstanding);
		self->mNumUSBFrameListsNotOutstanding++;
		if (self->mNumUSBFrameListsNotOutstanding == NUM_ISUB_FRAME_LISTS_TO_QUEUE) 
		{
			debugIOLog ("? AppleiSubEngine[%p]::WriteHandler () - iSub last write completed, closing mStreamInterface", self);
			if (NULL != self->mPipe) 
			{
				self->mPipe->release ();
				self->mPipe = NULL;
			}

			self->mStreamInterface->close (self);
			self->mStreamOpened = FALSE;
			self->miSubUSBRunning = FALSE;
		}
	}
//	self->release ();
	return;
}
