#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 looses the beginning of sounds if it has auto powered down
#define NUM_POWER_STATES					2

#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;

	debug3IOLog ("+AppleiSubEngine[%p]::closeiSub (%p)\n", this, forClient);

	if (forClient == audioEngine) {
		if ((NULL != muteControl || NULL != iSubVolumeControl) && NULL != audioEngine) {
			debugIOLog ("Removing iSub audio controls\n");
	
			if (NULL != iSubVolumeControl) {
				result = audioEngine->removeDefaultAudioControl (iSubVolumeControl);
				iSubVolumeControl = NULL;
				#if DEBUGLOG
				if (kIOReturnSuccess != result) IOLog ("Error 0x%x removing left iSub control\n", result);
				#endif
			}
	
			if (NULL != muteControl) {
				result = audioEngine->removeDefaultAudioControl (muteControl);
				muteControl = NULL;
				#if DEBUGLOG
				if (kIOReturnSuccess != result) IOLog ("Error 0x%x removing mute iSub control\n", 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
			audioEngine = NULL;
#if DEBUGLOG
		} else {
			if (NULL == muteControl) {IOLog ("NULL == muteControl\n");}
			if (NULL == iSubVolumeControl) {IOLog ("NULL == iSubVolumeControl\n");}
			if (NULL == audioEngine) {IOLog ("NULL == audioEngine\n");}
#endif
		}
	} else {
		debugIOLog ("the wrong client tried to close us\n");
	}

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

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

	debug2IOLog ("+AppleiSubEngine[%p]::free ()\n", this);

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

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

	if (NULL != sampleBuffer) {
		IOFree (sampleBuffer, bufferSize);
		sampleBuffer = NULL;
	}

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

	super::free ();

	debug2IOLog ("-AppleiSubEngine[%p]::free ()\n", 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) {
    bool						resultCode;

	resultCode = FALSE;

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

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

	audioEngine->pauseAudioEngine ();
	audioEngine->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.
	iSubVolumeControl = AppleUSBAudioLevelControl::create (kiSubFeatureUnitID,
													controlInterface->GetInterfaceNumber (),
													VOLUME_CONTROL,
													kiSubVolumeControlMasterChannelNum,
													FALSE,
													(USBDeviceRequest)&deviceRequest,
													this,
													kIOAudioLevelControlSubTypeLFEVolume,
													kIOAudioControlUsageOutput);
	FailIf (NULL == iSubVolumeControl, Exit);

	audioEngine->addDefaultAudioControl (iSubVolumeControl);

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

	audioEngine->addDefaultAudioControl (muteControl);

	audioEngine->completeConfigurationChange ();
	audioEngine->resumeAudioEngine ();

	resultCode = TRUE;

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

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

	debug3IOLog ("+AppleiSubEngine[%p]::init (%p)\n", this, properties);

	resultCode = 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:
	debug4IOLog ("-AppleiSubEngine[%p]::init (%p) = %d\n", 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}
	};

	debug3IOLog ("+AppleiSubEngine[%p]::start (%p)\n", this, provider);

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

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

	ourInterfaceNumber = streamInterface->GetInterfaceNumber ();
	debug2IOLog ("AppleiSubEngine->ourInterfaceNumber = %d\n", ourInterfaceNumber);
	// aml 1.18.02 replaced constant with member
	alternateInterfaceID = mFormat.altInterface;		

	PMinit ();
	streamInterface->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'
	frameListSize = CalculateNumSamplesPerBuffer (mFormat.outputSampleRate, NUM_ISUB_FRAMES_PER_LIST) * mFormat.numChannels * mFormat.bytesPerSample;
	debug4IOLog ("format = %ldHz, %ld channels, %ld bits\n", mFormat.outputSampleRate, mFormat.numChannels, mFormat.bytesPerSample * 8);
#if AML_DEBUGLOG
	IOLog ("AppleiSubEngine::handleOpen: frameListSize = %d\n", frameListSize);
#endif
	debug2IOLog ("frameListSize = %d\n", frameListSize);
	// aml 2.13.02 replaced kInputSampleRate with mOutputSampleRate
	// aml 2.13.02 added num channels and bit depth members, instead of '4'
	bufferSize = CalculateNumSamplesPerBuffer (mFormat.outputSampleRate, NUM_ISUB_FRAMES_PER_LIST, NUM_ISUB_FRAME_LISTS) * mFormat.numChannels * mFormat.bytesPerSample;
	debug2IOLog ("bufferSize = %d\n", bufferSize);
	sampleBuffer = IOMallocAligned (round_page (bufferSize), PAGE_SIZE);
	FailIf (NULL == sampleBuffer, Exit);
	// aml 2.13.02 changed from clearing by longs to words
	for (i = 0; i < bufferSize / sizeof(UInt16); i++) {
		((UInt16*)sampleBuffer)[i] = 0;
	}
	sampleBufferDescriptor = IOMemoryDescriptor::withAddress (sampleBuffer, bufferSize, kIODirectionNone);
	FailIf (NULL == sampleBufferDescriptor, Exit);

	FailIf (kIOReturnSuccess != PrepareFrameLists (frameListSize), Exit);
//	FailIf (TRUE == streamOpened, Exit);
//	IOLog ("after TRUE == streamOpened\n");
	shouldCloseStream = FALSE;
	FailIf (FALSE == streamInterface->open (this), Exit);
	streamOpened = TRUE;

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

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

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

	thePipe = streamInterface->FindNextPipe (NULL, &audioIsochEndpoint);
	FailIf (NULL == thePipe, Exit);
	thePipe->retain ();

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

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

	numUSBFrameListsNotOutstanding = 0;
	numQueued = 0;
	currentFrameList = 0;
	currentByteOffset = 0;
	theFirstFrame = streamInterface->GetDevice()->GetBus()->GetFrameNumber () + kMinimumiSubFrameOffset;
	for (frameListNum = currentFrameList; numQueued < NUM_ISUB_FRAME_LISTS_TO_QUEUE; frameListNum++) {
		resultIOReturn = WriteFrameList (frameListNum);
		FailIf (kIOReturnSuccess != resultIOReturn, Exit);
		numQueued++;
	}
	debug2IOLog ("writeFrameList resultIOReturn = 0x%lx\n", resultIOReturn);

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

	registerService ();
	resultBool = TRUE;

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

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

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

	if (FALSE == iSubUSBRunning) {
		// close our stream interface and go away
		if ((streamInterface != NULL) && (streamInterface == provider)) {
			debugIOLog ("willTerminate closing iSub down\n");
			if (NULL != thePipe) {
				thePipe->release ();
				thePipe = NULL;
			}
			streamInterface->close (this);
			streamOpened = FALSE;
		}
	} else {
		shouldCloseStream = TRUE;
	}

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

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

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

	debug4IOLog("AppleiSubEngine[%p]::setPowerState (%lu, %p)\n", this, powerStateOrdinal, device);

	if (device == this) {
		switch (powerStateOrdinal) {
			case 0:		// Power off state
				debugIOLog ("sleeping...\n");
				sleeping = TRUE;
				StopiSub ();
				result = IOPMNoErr;
				break;
			case 1:		// Power on state
				debugIOLog ("waking...\n");
				sleeping = FALSE;
				// Don't do the reset on this thread because it takes too long.
/*				if (NULL != streamInterface) {
					streamInterface->GetDevice()->ResetDevice ();
					// The ResetDevice causes the iSub to loose its volume and mute states, so put them back.
					if (NULL != muteControl) {
						muteControl->flushValue ();
					}
	
					if (NULL != iSubVolumeControl) {
						iSubVolumeControl->flushValue ();
					}
				} */
	
				result = IOPMNoErr;
				break;
			default:
				result = IOPMNoSuchState;
		}
	}

	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;

	debug4IOLog ("+AppleiSubEngine[%p]::deviceRequest (%p, %p)\n", self, request, completion);
	result = kIOReturnSuccess;
	if (self->streamInterface && request) {
		result = self->streamInterface->DeviceRequest (request, completion);
    }

	debug4IOLog ("-AppleiSubEngine[%p]::deviceRequest (%p, %p)\n", self, request, completion);
	return result;
}

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

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

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

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

	result = kIOReturnError;

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

		soundBuffer[frameIndex] = IOMemoryDescriptor::withAddress ((UInt8 *)usbCompletion[frameIndex].parameter, frameListSize, kIODirectionNone);
		FailIf (NULL == soundBuffer[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;

	debug2IOLog ("+AppleiSubEngine[%p]::StartiSub ()\n", this);

	FailWithAction (TRUE == sleeping, resultCode = kIOReturnOffline, Exit);
	resultCode = kIOReturnError;
	iSubRunning = TRUE;

#if ABORT_PIPE_ON_START
	FailIf (NULL == thePipe, Exit);
	thePipe->Abort ();		// let's kill all outstanding IO and start right back at the beginning
	iSubUSBRunning = FALSE;
//	FailIf (NULL == streamInterface, Exit);
//	theFirstFrame = streamInterface->GetDevice()->GetBus()->GetFrameNumber () + kMinimumiSubFrameOffset;
//	for (frameListNum = currentFrameList; numQueued < NUM_ISUB_FRAME_LISTS_TO_QUEUE; frameListNum++) {
//		resultCode = WriteFrameList (frameListNum);
//		FailIf (kIOReturnSuccess != resultCode, Exit);
//		numQueued++;
//	}
#else
	loopCount = 0xFFFFFFFF;			// so that it will go to 0 in the write completion routine when the frames are aborted
	currentFrameList = NUM_ISUB_FRAME_LISTS - 1;
//	theFirstFrame = 0;		// force the completion routine to calculate the correct starting frame
//	FailIf (NULL == thePipe, Exit);
//	thePipe->Abort ();		// let's kill all outstanding IO and start right back at the beginning
//	iSubUSBRunning = FALSE;
//	currentByteOffset = 0;
#endif // ABORT_PIPE_ON_START

	resultCode = kIOReturnSuccess;

	if (FALSE == iSubUSBRunning) {
		loopCount = 0;

		numUSBFrameListsNotOutstanding = 0;
		numQueued = 0;
		currentFrameList = 0;
		currentByteOffset = 0;
		FailIf (NULL == streamInterface, Exit);
		theFirstFrame = streamInterface->GetDevice()->GetBus()->GetFrameNumber () + kMinimumiSubFrameOffset;
		for (frameListNum = currentFrameList; numQueued < NUM_ISUB_FRAME_LISTS_TO_QUEUE; frameListNum++) {
			resultCode = WriteFrameList (frameListNum);
			FailIf (kIOReturnSuccess != resultCode, Exit);
			numQueued++;
		}
	}

Exit:
	debug3IOLog ("-AppleiSubEngine[%p]::StartiSub (), result = %d\n", this, resultCode);

	return resultCode;
}

IOReturn AppleiSubEngine::StopiSub (void) {

	debug2IOLog ("+AppleiSubEngine[%p]::StopiSub ()\n", this);

	iSubRunning = FALSE;

	debug2IOLog ("-AppleiSubEngine[%p]::StopiSub ()\n", 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++) {
			theFrames[firstFrame + frameIndex].frStatus = -1;
			if ((frameIndex % additionalSampleFrameFreq) == (UInt16)(additionalSampleFrameFreq - 1)) {
				theFrames[firstFrame + frameIndex].frReqCount = alternateFrameSize;
			} else {
				theFrames[firstFrame + frameIndex].frReqCount = averageFrameSize;
			}
			theFrames[firstFrame + frameIndex].frActCount = 0;
		}
	} else {
		for (frameIndex = 0; frameIndex < NUM_ISUB_FRAMES_PER_LIST; frameIndex++) {
			theFrames[firstFrame + frameIndex].frStatus = -1;
			theFrames[firstFrame + frameIndex].frReqCount = averageFrameSize;
			theFrames[firstFrame + frameIndex].frActCount = 0;
		}
	}

    FailIf (NULL == thePipe, Exit);
//	retain ();		// Don't want the driver being terminated until our completion routine runs.
    result = thePipe->Write (soundBuffer[frameListNum], theFirstFrame, NUM_ISUB_FRAMES_PER_LIST, &theFrames[firstFrame], &usbCompletion[frameListNum]);

	if (result != kIOReturnSuccess) {
		if (streamInterface) {
			debug6IOLog ("++AppleiSubEngine[%p]::WriteFrameList (%d) - error writing to pipe at frame %lu - current = %lu: 0x%x\n", this, frameListNum, (UInt32)theFirstFrame, (UInt32)streamInterface->GetDevice()->GetBus()->GetFrameNumber(), result);
		}
		iSubUSBRunning = FALSE;
	} else {
		theFirstFrame += NUM_ISUB_FRAMES_PER_LIST;
		iSubUSBRunning = 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->sleeping, Exit);

	if (result != kIOReturnSuccess) {
#if DEBUGLOG
		IOLog ("++AppleiSubEngine::WriteHandler () - error 0x%x\n", result);
#endif
		FailIf (NULL == self->streamInterface, Exit);
		currentUSBFrame = self->streamInterface->GetDevice()->GetBus()->GetFrameNumber ();
		switch (result) {
#if ABORT_PIPE_ON_START
			case kIOReturnAborted:
#if AML_DEBUGLOG
				IOLog ("AppleiSubEngine::WriteHandler() aborted.\n");
#endif
				goto Exit;			// do nothing
				break;
			case kIOReturnOverrun:
#else
			case kIOReturnOverrun:
			case kIOReturnAborted:
#endif
			default:
				// skip ahead and see if that helps
				if (self->theFirstFrame <= currentUSBFrame) {
					self->theFirstFrame = currentUSBFrame + kMinimumiSubFrameOffset;
#if DEBUGLOG
					IOLog ("+++AppleiSubEngine::skipping ahead to frame %ld\n", self->theFirstFrame);
#endif
				}
				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->frameListSize / sizeof(UInt16); i++) {
		((UInt16*)buffer)[i] = 0;
	}

	if ((NUM_ISUB_FRAME_LISTS - 1) == self->currentFrameList) {
		if (TRUE == self->iSubRunning) {
			self->loopCount++;
			self->currentByteOffset = 0;
		}
		self->currentFrameList = 0;
	} else {
		self->currentFrameList++;
		if (TRUE == self->iSubRunning) {
			self->currentByteOffset = self->currentFrameList * self->frameListSize;
		}
	}

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

Exit:
	if (TRUE == self->shouldCloseStream) {
		#if DEBUGLOG
		IOLog ("++AppleiSubEngine[%p]::WriteHandler () - stopping: %d\n", self, self->numUSBFrameListsNotOutstanding);
		#endif
		self->numUSBFrameListsNotOutstanding++;
		if (self->numUSBFrameListsNotOutstanding == NUM_ISUB_FRAME_LISTS_TO_QUEUE) {
			#if DEBUGLOG
			IOLog ("iSub last write completed, closing streamInterface\n");
			#endif
			if (NULL != self->thePipe) {
				self->thePipe->release ();
				self->thePipe = NULL;
			}

			self->streamInterface->close (self);
			self->streamOpened = FALSE;
			self->iSubUSBRunning = FALSE;
		}
	}
//	self->release ();
	return;
}
