/*
 * Copyright (c) 2002-2005 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@
 */
/*
 * Copyright (c) 2002-2005 Apple Computer, Inc.  All rights reserved.
 *
 *
 */


// IMPORTANT: All its marked as WORKAROUND needs to be removed once I get updated HW

//#define CONTROL_LOOP_DEBUG 1
//#define CONTROL_LOOP_RUNTIMELOG 1

#ifdef CONTROL_LOOP_DEBUG
#define PM11_2_DLOG(fmt, args...)  kprintf(fmt, ## args)
#else
#define PM11_2_DLOG(fmt, args...)
#endif

#if defined (CONTROL_LOOP_DEBUG) || defined (CONTROL_LOOP_RUNTIMELOG) 
#define PM11_2_RUNTIMELOG(fmt, args...)  kprintf(fmt, ## args)
#else
#define PM11_2_RUNTIMELOG(fmt, args...)
#endif

#include "IOPlatformPluginDefs.h"
#include "IOPlatformPluginSymbols.h"

#include "PowerMac11_2_CPUsCtrlLoop.h"
#include "PowerMac11_2_ThermalProfile.h"

#include "IOGroupVirtualControl.h"

/*
	Pmax = 95W
	Tmax = 85¡
	Poffset = -3.6W (note the negative sign)
	Tmax_offset = 2¡
	Gr = 0.55¡/W (note that SMU systems treat this field differently than Q77)
	Gp = 10
	Gd = 150
	Power_Integral_History = 6
	Interval should be 1s, assuming sensors can be read fast enough.
*/

// Note that the coefficients (Gp, Gr, Gd) are placeholders and are most-likely wrong for Q63.
UInt8 gCore0AlternateDataOnlyForTesting[ 24 ] = {
0xC8, 0x06, 0x02, 0x7F, 0xFF, 0x02, 0xFF, 0x06, 0xFC, 0x66, 0x00, 0x5f,
0x00, 0xA0, 0x00, 0x00, 0x00, 0x08, 0xCC, 0xCD, 0x09, 0x60, 0x00, 0x00
};

UInt8 gCore1AlternateDataOnlyForTesting[ 24 ] = {
0xC9, 0x06, 0x02, 0x7F, 0xFF, 0x02, 0xFF, 0x06, 0xFC, 0x66, 0x00, 0x5f,
0x00, 0xA0, 0x00, 0x00, 0x00, 0x08, 0xCC, 0xCD, 0x09, 0x60, 0x00, 0x00
};

// Just initialize this "global" to null:
OSDictionary *PowerMac11_2_CPUsCtrlLoop::gIOGroupVirtualControlDictionary = NULL;

#undef super
#define super IOPlatformPIDCtrlLoop
OSDefineMetaClassAndStructors( PowerMac11_2_CPUsCtrlLoop, IOPlatformPIDCtrlLoop )

SensorValue PowerMac11_2_CPUsCtrlLoop::getSensorValue( IOPlatformSensor *sensor)
{
	SensorValue currentValue;

	if ( sensor != NULL )
	{
		currentValue = sensor->forceAndFetchCurrentValue();
		//currentValue = sensor->fetchCurrentValue();
		sensor->setCurrentValue( currentValue );
		
		PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop %s %ld %d %d\n", sensor->getSensorDescKey()->getCStringNoCopy(), currentValue.sensValue, currentValue.thermValue.intValue, currentValue.thermValue.fracValue);
	}
	else
	{
		currentValue.sensValue = 0;
	}

	return currentValue;
}

bool PowerMac11_2_CPUsCtrlLoop::allControlsAndSensorsRegistredCheck( )
{
	if ( mGroupControl )
	{
		bool allSensorsRegistred = true;
		int i;
		
		for ( i = 0; ( i <  mActualNumberOfInputSets ) && allSensorsRegistred ; i ++ )
		{
			allSensorsRegistred &= (
				( mInputSetArray[i].powerSensor->isRegistered() == kOSBooleanTrue ) &&
				( mInputSetArray[i].temperatureSensor->isRegistered() == kOSBooleanTrue ) );
				
				
			if ( allSensorsRegistred )
			{
				IOService *sensorDriver =  mInputSetArray[ i ].temperatureSensor->getSensorDriver();
				
				if ( sensorDriver )
				{
					IOService *temperatureSensorDevice = sensorDriver->getProvider();
					if ( temperatureSensorDevice )
					{
						mInputSetArray[ i ].referenceSAT = temperatureSensorDevice->getProvider();
						if ( mInputSetArray[ i ].referenceSAT == NULL )
						{
							PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::AllControlsAndSensorsRegistredCheck SAT for %s:%d \t %s:%d\n",
								mInputSetArray[i].temperatureSensor->getSensorDescKey()->getCStringNoCopy(), mInputSetArray[i].temperatureSensor->isRegistered() == kOSBooleanTrue,
								mInputSetArray[i].powerSensor->getSensorDescKey()->getCStringNoCopy(), mInputSetArray[i].powerSensor->isRegistered() == kOSBooleanTrue);
						}
						else
						{
							kprintf("PowerMac11_2_CPUsCtrlLoop::allControlsAndSensorsRegistredCheck set %d -> %s\n", i, mInputSetArray[ i ].referenceSAT->getMetaClass()->getClassName());
							mInputSetArray[ i ].referenceSAT->retain();
						}
					}
				}
			}
				
			PM11_2_RUNTIMELOG("PowerMac11_2_CPUsCtrlLoop::AllControlsAndSensorsRegistredCheck %s:%d \t %s:%d\n",
				mInputSetArray[i].temperatureSensor->getSensorDescKey()->getCStringNoCopy(), mInputSetArray[i].temperatureSensor->isRegistered() == kOSBooleanTrue,
				mInputSetArray[i].powerSensor->getSensorDescKey()->getCStringNoCopy(), mInputSetArray[i].powerSensor->isRegistered() == kOSBooleanTrue);
		}

		mAllSensorsRegistred = allSensorsRegistred;
		mAllControlsRegistred = ( mGroupControl->isGroupRegistered() == kOSBooleanTrue );

		PM11_2_RUNTIMELOG("PowerMac11_2_CPUsCtrlLoop::AllControlsAndSensorsRegistredCheck for %d reads  %d, %d, %d!\n",
			( getCtrlLoopID() ? getCtrlLoopID()->unsigned16BitValue() : 0xFF ),
			mAllControlsRegistred, mAllSensorsRegistred, ctrlloopState);

		if ( ( mAllControlsRegistred ) && ( mAllSensorsRegistred ) )
		{
			PM11_2_RUNTIMELOG("PowerMac11_2_CPUsCtrlLoop::AllControlsAndSensorsRegistredCheck allRegistered!\n");

			ctrlloopState = kIOPCtrlLoopFirstAdjustment;

			// set the deadline
			deadlinePassed();
		}
		
		return ( ( mAllControlsRegistred ) && ( mAllSensorsRegistred ) );
	}
	
	return false;
}

// This is very machine-dependent, should you need to inherit this control loop
// you will likely need to complery rev this method:

OSData *PowerMac11_2_CPUsCtrlLoop::getRawDatasetFor( UInt8 cpuid, UInt8 coreid )
{
	OSData	*sdbPartitionData = NULL;
	OSData	*pidDataSet = NULL;
	OSData	*fvtDataSet = NULL;
	IOService *sat = NULL;
	IOService *ioI2CSMUSat = NULL;
	
	PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::getRawDatasetFor( %d, %d )\n", cpuid, coreid);
	
	// If there is no reference SAT look for it (again)
	// Choose the corrcet sat for the given CPU:
	switch ( cpuid )
	{
		case 0:
			sat = (IOService*) IOService::fromPath ("/smu/@0/@b/@b0", gIODTPlane);
		break;
		
		case 1:
			sat = (IOService*) IOService::fromPath ("/smu/@0/@b/@b2", gIODTPlane);
		break;
		
		default:
			kprintf("PowerMac11_2_CPUsCtrlLoop::getRawDatasetFor( %d ) cpuid can be only 0 or 1\n", cpuid);
			return NULL;
		break;
	}

	if ( sat == NULL )
	{
		kprintf("PowerMac11_2_CPUsCtrlLoop::getRawDatasetFor( %d ) sat can not be found\n", cpuid);
		return NULL;
	}

	// gets its child:
	ioI2CSMUSat = OSDynamicCast( IOService, sat->getChildEntry( gIOServicePlane ) ); 
	if ( ioI2CSMUSat == NULL )
	{
		kprintf("PowerMac11_2_CPUsCtrlLoop::getRawDatasetFor( %d ) sat does not have a matching driver\n", cpuid );
		return NULL;
	}
	else
	{
		kprintf("PowerMac11_2_CPUsCtrlLoop::getRawDatasetFor( %d ) %s\n", cpuid, ioI2CSMUSat->getMetaClass()->getClassName());
	}

	
	// and then gets the correct partition data:
	switch ( coreid )
	{
		case 0:
			PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::getRawDatasetFor sdb-partition-C8\n");
			pidDataSet = OSDynamicCast( OSData, ioI2CSMUSat->getProperty( "sdb-partition-C8" ) );
			if ( pidDataSet == NULL)
			{
				pidDataSet = OSData::withBytes( gCore0AlternateDataOnlyForTesting, sizeof( gCore0AlternateDataOnlyForTesting ) );
				kprintf("PowerMac11_2_CPUsCtrlLoop::getRawDatasetFor( %d, %d ) missing sdb-partition-C8 fall back on predefined data.\n", cpuid, coreid);
			}
		break;
		
		case 1:
			PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::getRawDatasetFor sdb-partition-C8\n");
			pidDataSet = OSDynamicCast( OSData, ioI2CSMUSat->getProperty( "sdb-partition-C9" ) );
			if ( pidDataSet == NULL)
			{
				pidDataSet = OSData::withBytes( gCore1AlternateDataOnlyForTesting, sizeof( gCore1AlternateDataOnlyForTesting ) );
				kprintf("PowerMac11_2_CPUsCtrlLoop::getRawDatasetFor( %d, %d ) missing sdb-partition-C9 fall back on predefined data.\n", cpuid, coreid);
			}
		break;
		
		default:
			PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::getRawDatasetFor( %d, %d ) core id can be only 0 or 1\n", cpuid, coreid);
			return NULL;
		break;
	}
	

	// and then gets the correct partition data:
	switch ( coreid )
	{
		case 0:
			PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::getRawDatasetFor sdb-partition-C4\n");
			fvtDataSet = OSDynamicCast( OSData, ioI2CSMUSat->getProperty( "sdb-partition-C4" ) );
			if ( fvtDataSet == NULL)
			{
				kprintf("PowerMac11_2_CPUsCtrlLoop::getRawDatasetFor( %d, %d ) missing sdb-partition-C4 fails.\n", cpuid, coreid);
				return NULL;
			}
		break;
		
		case 1:
			PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::getRawDatasetFor sdb-partition-C5\n");
			fvtDataSet = OSDynamicCast( OSData, ioI2CSMUSat->getProperty( "sdb-partition-C5" ) );
			if ( fvtDataSet == NULL)
			{
				kprintf("PowerMac11_2_CPUsCtrlLoop::getRawDatasetFor( %d, %d ) missing sdb-partition-C5 fails.\n", cpuid, coreid);
				return NULL;
			}
		break;
		
		default:
			PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::getRawDatasetFor( %d, %d ) core id can be only 0 or 1\n", cpuid, coreid);
			return NULL;
		break;
	}

	sdbPartitionData = pidDataSet;
	sdbPartitionData->appendBytes(fvtDataSet);
	
	return sdbPartitionData;
}

OSData *PowerMac11_2_CPUsCtrlLoop::getSubData( OSData * source, UInt32 offset, UInt32 length)
{
	const void* dataPointer;

	if ( ( source == NULL ) || ( source->getLength() - offset < length ) )
	{
		PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::getSubData(%p, %ld, %ld ) (%d) invalid parameters\n",
			source, offset, length, source->getLength() );

		return( NULL );
	}

	if ( ( dataPointer = source->getBytesNoCopy( offset, length ) ) == NULL )
	{
		PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::getSubData(%p, %ld, %ld ) source can not be read\n",
			source, offset, length);

		return( NULL );
	}	
	
	return OSData::withBytes( dataPointer, length );
}

OSDictionary* PowerMac11_2_CPUsCtrlLoop::getPIDDataset( const OSDictionary* dict, UInt8 cpuID, UInt8 coreID )
{
	UInt8			*buffer;
	const OSData	*tmpData;
	const OSNumber	*tmpNumber;
	OSDictionary	*dataset;
	OSData			*sdbPartitionData;
	
	PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::getPIDDataset\n");
	
	if ( ( dataset = OSDictionary::withCapacity( 14 ) ) == NULL )
	{
		PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::getPIDDataset: Cannot allocate PID dataset.\n" );

		return( NULL );
	}

	if ( ( sdbPartitionData = getRawDatasetFor( cpuID, coreID ) ) == NULL )
	{
		PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::getPIDDataset: Failed to fetch the sdbPartitionData.\n" );
		
		dataset->release();
		return( NULL );
	}

	// Proportional gain (G_p) (12.20).
	if ( ( tmpData = getSubData( sdbPartitionData, 12, 4 ) ) == NULL )
	{
		PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::getPIDDataset: Failed to fetch G_p.\n" );
		
		dataset->release();
		return( NULL );
	}
	dataset->setObject( kIOPPIDCtrlLoopProportionalGainKey, tmpData );
	tmpData->release();

	// Reset gain (G_r) (12.20) -- Actually used as power integral gain.
	if ( ( tmpData = getSubData( sdbPartitionData, 16, 4 ) ) == NULL )
	{
		PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::getPIDDataset: Failed to fetch G_r.\n" );
		
		dataset->release();
		return( NULL );
	}
	dataset->setObject( kIOPPIDCtrlLoopResetGainKey, tmpData );
	tmpData->release();

	// Derivative gain (G_d) (12.20).
	if ( ( tmpData = getSubData( sdbPartitionData, 20, 4 ) ) == NULL )
	{
		PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::getPIDDataset: Failed to fetch G_d.\n" );
		
		dataset->release();
		return( NULL );
	}
	dataset->setObject( kIOPPIDCtrlLoopDerivativeGainKey, tmpData );
	tmpData->release();

	// History length (8-bit integer).
	if ( ( buffer = (UInt8*)sdbPartitionData->getBytesNoCopy( 7, 1 ) ) == NULL )
	{
		PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::getPIDDataset: Failed to fetch history length.\n" );

		dataset->release();
		return( NULL );
	}

	tmpNumber = OSNumber::withNumber( buffer[ 0 ], 8 );
	dataset->setObject( kIOPPIDCtrlLoopHistoryLenKey, tmpNumber );
	tmpNumber->release();
	
	// Target temperature delta (16.16).  Comes from partition 0x17.
	if ( ( buffer = (UInt8*)sdbPartitionData->getBytesNoCopy( 5, 1 ) ) == NULL )
	{
		PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::getPIDDataset: Failed to fetch Tmax delta..\n" );

		dataset->release();
		return( NULL );
	}

	tmpNumber = OSNumber::withNumber( buffer[ 0 ] << 16, 32 );
	dataset->setObject( kIOPPIDCtrlLoopTargetTempDelta, tmpNumber );
	tmpNumber->release();

	// Max power (16.16).
	if ( ( buffer = (UInt8*)sdbPartitionData->getBytesNoCopy( 10, 2 ) ) == NULL )
	{
		PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::getPIDDataset: Failed to fetch max power.\n" );

		dataset->release();
		return( NULL );
	}
	tmpNumber = OSNumber::withNumber( ( (UInt32)OSReadBigInt16( buffer, 0 ) ) << 16, 32 );
	dataset->setObject( kIOPPIDCtrlLoopMaxPowerKey, tmpNumber );
	tmpNumber->release();

	// Power adjustment (16.16).
	if ( ( buffer = (UInt8*)sdbPartitionData->getBytesNoCopy( 8, 2 ) ) == NULL )
	{
		PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::getPIDDataset: Failed to fetch max power adjustment.\n" );

		dataset->release();
		return( NULL );
	}
	UInt16 eight_plus_eight_fixed = OSReadBigInt16( buffer, 0 );
	bool theNumberIsNegative = ( eight_plus_eight_fixed & 0x8000 );
	UInt32	newFixedValueForm = ( theNumberIsNegative ? ( 0xFFFF0000 | eight_plus_eight_fixed ) : eight_plus_eight_fixed ) << 8 ;	
	tmpNumber = OSNumber::withNumber( newFixedValueForm, 32 );
	dataset->setObject( kIOPPIDCtrlLoopMaxPowerAdjustmentKey, tmpNumber );
	tmpNumber->release();

	// The iteration interval is not stored in the SDB.  Set it to 1 second.
	dataset->setObject( kIOPPIDCtrlLoopIntervalKey, gIOPPluginOne );
	
	// if the data is expteded wih the FTV get the max temp out of it:
	if ( sdbPartitionData->getLength() > 24)
	{
		// Max Temperature (24+9).
		if ( ( buffer = (UInt8*)sdbPartitionData->getBytesNoCopy( 33, 1 ) ) == NULL )
		{
			PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::getPIDDataset: Failed to fetch max temp.\n" );

			dataset->release();
			return( NULL );
		}

		tmpNumber = OSNumber::withNumber( buffer[ 0 ] << 16, 32 );
		dataset->setObject( kIOPPIDCtrlLoopInputMaxKey, tmpNumber );
		tmpNumber->release();
	}

	// Extra dataset for overtemp:
	tmpNumber = OSNumber::withNumber( kPM11_2CPU_DEFAULT_slewAverageOffset << 16, 32 );
	dataset->setObject( kOverTempSlewAverageOffset, tmpNumber );
	tmpNumber->release();

	tmpNumber = OSNumber::withNumber( kPM11_2CPU_DEFAULT_slewOffset << 16, 32 );
	dataset->setObject( kOverTempSlewImmediateOffset, tmpNumber );
	tmpNumber->release();

	tmpNumber = OSNumber::withNumber( kPM11_2CPU_DEFAULT_endOfSlewOffset << 16, 32 );
	dataset->setObject( kEndOfSlewOffset, tmpNumber );
	tmpNumber->release();

	tmpNumber = OSNumber::withNumber( kPM11_2CPU_DEFAULT_slewAverageNumberOfSamples, 32 );
	dataset->setObject( kOverTempSlewAverageNumberOfSamples, tmpNumber );
	tmpNumber->release();

	// Extra dataset for sleep
	tmpNumber = OSNumber::withNumber( kPM11_2CPU_DEFAULT_sleepAverageOffset << 16, 32 );
	dataset->setObject( kOverTempSleepAverageOffset, tmpNumber );
	tmpNumber->release();

	tmpNumber = OSNumber::withNumber( kPM11_2CPU_DEFAULT_sleepOffset << 16, 32 );
	dataset->setObject( kOverTempSleepImmediateOffset, tmpNumber );
	tmpNumber->release();

	tmpNumber = OSNumber::withNumber( kPM11_2CPU_DEFAULT_sleepAverageNumberOfSamples, 32 );
	dataset->setObject( kOverTempSleepAverageNumberOfSamples, tmpNumber );
	tmpNumber->release();

	tmpNumber = OSNumber::withNumber( kPM11_2CPU_GlobalContorlDecreaseFanLimit, 32 );
	dataset->setObject( kIOGlobalContorlDecreaseFanLimit, tmpNumber );
	tmpNumber->release();

	if ( numberOfCores( ) == 4)
	{
		tmpNumber = OSNumber::withNumber( kMinReasonableOutputFor4Way, 32 );
		dataset->setObject( kIOOverrideMinValueTargetValue, tmpNumber );
		tmpNumber->release();
	}
	else
	{
		tmpNumber = OSNumber::withNumber( kMinReasonableOutputFor2Way, 32 );
		dataset->setObject( kIOOverrideMinValueTargetValue, tmpNumber );
		tmpNumber->release();
	}

	return( dataset );
}


/* static*/ IOGroupVirtualControl *PowerMac11_2_CPUsCtrlLoop::groupVirtualControlForID(OSString *groupID)
{
	IOGroupVirtualControl *groupControl = NULL;

	if ( gIOGroupVirtualControlDictionary == NULL )
	{
		gIOGroupVirtualControlDictionary = OSDictionary::withCapacity( 1 );
	}
	
	if ( gIOGroupVirtualControlDictionary )
	{
		groupControl = OSDynamicCast( IOGroupVirtualControl, gIOGroupVirtualControlDictionary->getObject( groupID ) );
		
		if ( groupControl == NULL )
		{
			groupControl = new IOGroupVirtualControl;
			
			if ( groupControl )
			{
				groupControl->init();
				
				if ( gIOGroupVirtualControlDictionary->setObject( groupID , groupControl ) )
				{
					// Now the object is in the dictionary, so I can release it, the
					// dictionary will keep the retain count:
					groupControl->release();
				}
			}
		}
	
	}
	
	return groupControl;
}

bool PowerMac11_2_CPUsCtrlLoop::init( void )
{
	bool result = super::init();
	
	PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::init %d returns %d\n", ( getCtrlLoopID() ? getCtrlLoopID()->unsigned16BitValue() : 0xFF )  ,result);

	mOverrideMinOutputValue = 0;

	return result;
}

UInt8 PowerMac11_2_CPUsCtrlLoop::numberOfCores( )
{
	OSIterator	*iterator;
	int numberOfCPUs = 0;

	// the number of cores is found counting the number of CPUS (since we have one cpu object
	// per core).
	iterator = IOService::getMatchingServices( IOService::serviceMatching( "MacRISC4CPU" ) );

	while ( ( iterator ) && ( iterator->getNextObject() ) )
	{		
		numberOfCPUs++;
	}

	if( iterator )
		iterator->release();
	
	return( numberOfCPUs );

}

void PowerMac11_2_CPUsCtrlLoop::free( void )
{
	if ( mGroupControl )
	{
		mGroupControl->release();
		mGroupControl = NULL;
	}

	if ( mActualNumberOfInputSets > 0 )
	{
		int i;
		
		for ( i = 0 ; i < mActualNumberOfInputSets ; i++ )
		{
			if ( mInputSetArray[ i ].referenceSAT )
				mInputSetArray[ i ].referenceSAT->release();
				
			if ( mInputSetArray[ i ].temperatureSensor )
				mInputSetArray[ i ].temperatureSensor->release();
				
			if ( mInputSetArray[ i ].powerSensor )
				mInputSetArray[ i ].powerSensor->release();

			mInputSetArray[ i ].temperatureSensor = NULL;
			mInputSetArray[ i ].powerSensor = NULL;
			mInputSetArray[ i ].referenceSAT = NULL;
		}
	}

	// Do not call super:free() because the superclass panics
	//super::free();
}

IOReturn PowerMac11_2_CPUsCtrlLoop::initPlatformCtrlLoop(const OSDictionary *dict)
{
	IOReturn result = kIOReturnSuccess;
	OSArray *array;
	OSDictionary *metaStateDict;
	OSNumber *number;
	
	PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::initPlatformCtrlLoop %d\n", ( getCtrlLoopID() ? getCtrlLoopID()->unsigned16BitValue() : 0xFF ));
	
	if ( ( number = OSDynamicCast( OSNumber, dict->getObject( kIOControlLoopCPUID ) ) ) == NULL )
	{
		PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::initPlatformCtrlLoop no %s property\n", kIOControlLoopCPUID);
		return kIOReturnError;
	}
	else
	{
		mCpuID = number->unsigned8BitValue();
	}
	
	if ( ( number = OSDynamicCast( OSNumber, dict->getObject( kIOControlLoopCoreID ) ) ) == NULL )
	{
		PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::initPlatformCtrlLoop no %s property\n", kIOControlLoopCoreID);
		return kIOReturnError;
	}
	else
	{
		mCoreID = number->unsigned8BitValue();
	}
	
	PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::initPlatformCtrlLoop %d cpu %d core %d\n",( getCtrlLoopID() ? getCtrlLoopID()->unsigned16BitValue() : 0xFF ), mCpuID, mCoreID);

	array = OSDynamicCast(OSArray, dict->getObject(gIOPPluginThermalMetaStatesKey) );
	if (array == NULL)
	{
		PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::initPlatformCtrlLoop no meta state array\n");
		return kIOReturnError;
	}

	// Create the metastate array from information in the thermal profile and the SDB.
	if ( ( metaStateDict = getPIDDataset( dict , mCpuID , mCoreID) ) == NULL )
	{
		PM11_2_DLOG( "PowerMac11_2_CPUsCtrlLoop::initPlatformCtrlLoop %d Cannot get PID dataset.\n", ( getCtrlLoopID() ? getCtrlLoopID()->unsigned16BitValue() : 0xFF ));

		return( kIOReturnError );
	}

	array->replaceObject( 0, metaStateDict );

	result = super::initPlatformCtrlLoop(dict);
	if ( result != kIOReturnSuccess )
	{
		PM11_2_DLOG( "PowerMac11_2_CPUsCtrlLoop::initPlatformCtrlLoop: super fails with error %08lx.\n", (UInt32)result);

		return result;
	}

	// Finds out which common control group this control loop refers to:
	// Common control loops group the output of multiple control loops to a single
	// control. This "virtual" control chooses which output to use and sets all its
	// belonging controls to that output:
	OSString *controlGroupName = OSDynamicCast( OSString, dict->getObject( kControlGroupingID ) );
	
	if ( controlGroupName != NULL )
	{
		mGroupControl = groupVirtualControlForID( controlGroupName );
		
		if ( mGroupControl == NULL )
		{
			PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::initPlatformCtrlLoop can not find or allcoate a mGroupControl for %s!!\n", controlGroupName->getCStringNoCopy());
			return(kIOReturnError);
		}
		else
		{
			mGroupControl->retain();
			mGroupControl->registerControlLoop( this );
		}
	}
	else
	{
		PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::initPlatformCtrlLoop can not find object for kControlGroupingID !!\n");
		return(kIOReturnError);
	}

	// Iterate trough all the controls and fill up the OutputSet. The controls should be in the following sequence:
	// Intake1 / Exaust 1 / Pump 1 / ... / Intake n / Exaust n / Pump n
	if ( ( array = OSDynamicCast( OSArray, dict->getObject(kIOPPluginThermalControlIDsKey) ) ) != NULL )
	{
		int i, tmpActualNumberOfOutputSets = array->getCount() / 3;
	
		if ( tmpActualNumberOfOutputSets == 0 )
		{
			PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::initPlatformCtrlLoop empty controls array!!\n");
			return(kIOReturnError);
		}
	
		for ( i = 0; i <  tmpActualNumberOfOutputSets; i ++ )
		{
			IOPlatformControl *intakeFan	= platformPlugin->lookupControlByID( OSDynamicCast(OSNumber, array->getObject( i * 3 ) ) );
			IOPlatformControl *exaustFan	= platformPlugin->lookupControlByID( OSDynamicCast(OSNumber, array->getObject( ( i * 3 ) + 1) ) );
			IOPlatformControl *pump		= platformPlugin->lookupControlByID( OSDynamicCast(OSNumber, array->getObject( ( i * 3 ) + 2) ) );

			if ( ( intakeFan == 0) || ( exaustFan == 0 ) )
			{
				PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::initPlatformCtrlLoop missing control or wrong id %p %p %p) !!\n", intakeFan, exaustFan, pump);
				return(kIOReturnError);
			}
			else
			{
				if ( outputControl == NULL )
					outputControl = exaustFan;
	
				mGroupControl->addExaustFan( exaustFan );
				mGroupControl->addIntakeFan( intakeFan );
				
				if ( pump != NULL )
					mGroupControl->addPump( pump );
					
				PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::initPlatformCtrlLoop adding control set %d of %d (%d) %p %p %p) !!\n", i, tmpActualNumberOfOutputSets, array->getCount() , intakeFan, exaustFan, pump);
			}
		}

	}
	else
	{
		PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::initPlatformCtrlLoop no sec control ID!!\n");
		return(kIOReturnError);
	}	

	// Iterate trough all the sensors and fill up the InputSet. The sensors should be in the following sequence:
	// Temperature 1 / Power  1 / ... / Temperature n / Power n

	if ( ( array = OSDynamicCast( OSArray, dict->getObject(kIOPPluginThermalSensorIDsKey) ) ) != NULL )
	{
		int i, maxNumberOfInputSets = numberOfCores( );
		
		mActualNumberOfInputSets = array->getCount() / 2;

		if ( mActualNumberOfInputSets > maxNumberOfInputSets )
			mActualNumberOfInputSets = maxNumberOfInputSets;

		if ( mActualNumberOfInputSets == 0 )
		{
			PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::initPlatformCtrlLoop empty sensors array!!\n");
			return(kIOReturnError);
		}				

		// sanity check: the sensors should be multiples of 2:
		assert ( ( mActualNumberOfInputSets % 2 ) == 0 );
	
		for ( i = 0; i <  mActualNumberOfInputSets; i ++ )
		{
			mInputSetArray[ i ].temperatureSensor = platformPlugin->lookupSensorByID( OSDynamicCast(OSNumber, array->getObject( ( i * 2 ) + 0) ) );
			mInputSetArray[ i ].powerSensor       = platformPlugin->lookupSensorByID( OSDynamicCast(OSNumber, array->getObject( ( i * 2 ) + 1) ) );

			if ( ( mInputSetArray[ i ].temperatureSensor == 0 ) ||( mInputSetArray[ i ].powerSensor == 0 ) )
			{
				PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::initPlatformCtrlLoop missing sensor or wrong id ( %d)  %p %p) !!\n",
					i * 2, mInputSetArray[ i ].temperatureSensor, mInputSetArray[ i ].powerSensor);
					
				return(kIOReturnError);
			}
			else
			{			
				mInputSetArray[ i ].temperatureSensor->retain();
				mInputSetArray[ i ].powerSensor->retain();

				if ( inputSensor == NULL )
					inputSensor = mInputSetArray[ i ].temperatureSensor;

				PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::initPlatformCtrlLoop  sensor set %d : %s %s) !!\n", i ,
					mInputSetArray[ i ].temperatureSensor->getSensorDescKey()->getCStringNoCopy(),
					mInputSetArray[ i ].powerSensor->getSensorDescKey()->getCStringNoCopy());
			}
		}
	}
	else
	{
		PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::initPlatformCtrlLoop no sec sensor ID!!\n");
		return(kIOReturnError);
	}	

	PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::initPlatformCtrlLoop %d returns %08lx\n", ( getCtrlLoopID() ? getCtrlLoopID()->unsigned16BitValue() : 0xFF ), (UInt32)result);
	return( result );
}

bool PowerMac11_2_CPUsCtrlLoop::updateMetaState( void )
{
	const OSArray * metaStateArray;
	const OSDictionary * metaStateDict;
	const OSNumber * newMetaState;
	
	PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::updateMetaState %d\n", ( getCtrlLoopID() ? getCtrlLoopID()->unsigned16BitValue() : 0xFF ) );

	// else if there is an overtemp condition, use meta-state 1
	// else if there is a forced meta state, use it
	// else, use meta-state 0

	if ((metaStateArray = OSDynamicCast(OSArray, infoDict->getObject(gIOPPluginThermalMetaStatesKey))) == NULL)
	{
		PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::updateMetaState no meta state array\n");
		return(false);
	}

	// Check for overtemp condition
	if (( platformPlugin->envArrayCondIsTrue(gIOPPluginEnvInternalOvertemp )) ||
	    ( platformPlugin->envArrayCondIsTrue(gIOPPluginEnvExternalOvertemp )))
	{
		//IOLog("PowerMac11_2_CPUsCtrlLoop::updateMetaState Entering Overtemp Mode (%d) ! %d !\n", platformPlugin->envArrayCondIsTrue(gIOPPluginEnvExternalOvertemp ) , platformPlugin->envArrayCondIsTrue(gIOPPluginEnvInternalOvertemp ));

		if ((metaStateDict = OSDynamicCast(OSDictionary, metaStateArray->getObject(1))) != NULL &&
		    (cacheMetaState( metaStateDict ) == true))
		{
			// successfully entered overtemp mode
			setMetaState( gIOPPluginOne );
			return(true);
		}
		else
		{
			IOLog("PowerMac11_2_CPUsCtrlLoop::updateMetaState Overtemp Mode Failed!\n");
		}
	}

	// Look for forced meta state
	if ((metaStateDict = OSDynamicCast(OSDictionary, infoDict->getObject(gIOPPluginForceCtrlLoopMetaStateKey))) != NULL)
	{
		if (cacheMetaState( metaStateDict ) == true)
		{
			PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::updateMetaState using forced meta state\n");
			newMetaState = OSNumber::withNumber( 0xFFFFFFFF, 32 );
			setMetaState( newMetaState );
			newMetaState->release();
			return(true);
		}
		else
		{
			PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::updateMetaState forced meta state is invalid, removing...\n");
			infoDict->removeObject(gIOPPluginForceCtrlLoopMetaStateKey);
		}
	}

	// Use default "Normal" meta state
	metaStateDict = OSDynamicCast(OSDictionary, metaStateArray->getObject(0));
	if ( metaStateDict == NULL )
	{
		// can't find a valid meta state, nothing we can really do except log an error
		PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::updateMetaState no valid meta states!\n");
		return(false);
	}
	
	if ( ! cacheMetaState( metaStateDict ) )
	{
		// can't find a valid meta state, nothing we can really do except log an error
		PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::updateMetaState cacheMetaState fails!\n");
		return(false);
	}

	PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::updateMetaState use meta state zero\n");
	setMetaState( gIOPPluginZero );

	// Re-check the sensors/controls state:
	if ( ( ! mAllControlsRegistred ) || (! mAllSensorsRegistred ) )
		allControlsAndSensorsRegistredCheck( );
	
	return(true);
}

bool PowerMac11_2_CPUsCtrlLoop::cacheMetaState( const OSDictionary* metaState )
{
	const OSNumber *numOverride;
	const OSNumber *numInterval;

	PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::cacheMetaState %d\n", ( getCtrlLoopID() ? getCtrlLoopID()->unsigned16BitValue() : 0xFF ) );

	// cache the interval.  it is listed in seconds.
	if ((numInterval = OSDynamicCast(OSNumber, metaState->getObject(kIOPPIDCtrlLoopIntervalKey))) != NULL)
	{
		UInt32 tempInterval = numInterval->unsigned32BitValue();

		if ((tempInterval == 0) || (tempInterval > 300))
		{
			PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::cacheMetaState meta state interval is out of bounds\n");
			return false;
		}
		
		intervalSec = tempInterval;
	}
	else
	{
		PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::cacheMetaState meta state interval is absent defaults to 1\n");
		intervalSec = 1;
	}

	// if there is an output-override key, flag it.  Otherwise, look for the full
	// set of coefficients, setpoints and output bounds
	if ((numOverride = OSDynamicCast(OSNumber, metaState->getObject(kIOPPIDCtrlLoopOutputOverrideKey))) != NULL)
	{
		overrideActive = true;
		outputOverride = numOverride;
		outputOverride->retain();

		PM11_2_DLOG("*** PID CACHE *** Override: 0x%08lX\n", (UInt32)outputOverride->unsigned32BitValue());
		//IOLog("*** PID CACHE *** Override: 0x%08lX\n", (UInt32)outputOverride->unsigned32BitValue());
	}
	else
	{
		const OSData *dataG_p, *dataG_d, *dataG_r;
		const OSNumber *numInputTarget, *numInputMax, *numOutputMin, *numOutputMax, *numHistLen, *numPowerMax, *numPowerAdj;
		
		const OSNumber *numOverTempSlewAverageOffset, *numOverTempSlewImmediateOffset, *numEndOfSlewOffset, *numOverTempSlewAverageNumberOfSamples;
		const OSNumber *numOverTempSleepAverageOffset, *numOverTempSleepImmediateOffset, *numOverTempSleepAverageNumberOfSamples;
	
		const OSNumber *decresingLimitingFactorNumber, *overrideMinValueNumber;
		
		samplePoint * sample, * newHistoryArray;
		unsigned int i;
		UInt32 newHistoryLen;
	 
		// look for G_p, G_d, G_r, input-target, output-max, output-min
		if ((dataG_p = OSDynamicCast(OSData, metaState->getObject(kIOPPIDCtrlLoopProportionalGainKey))) == NULL)
		{
			PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::cacheMetaState meta state has no G_p\n");
			return false;
		}

		if ((dataG_d = OSDynamicCast(OSData, metaState->getObject(kIOPPIDCtrlLoopDerivativeGainKey))) == NULL)
		{
			PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::cacheMetaState meta state has no G_d\n");
			return false;
		}

		if ((dataG_r = OSDynamicCast(OSData, metaState->getObject(kIOPPIDCtrlLoopResetGainKey))) == NULL)
		{
			PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::cacheMetaState meta state has no G_r\n");
			return false;
		}

		if ((numPowerMax = OSDynamicCast(OSNumber, metaState->getObject(kIOPPIDCtrlLoopMaxPowerKey))) == NULL)
		{
			PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::cacheMetaState meta state has no power-max\n");
			return false;
		}

		if ((numPowerAdj = OSDynamicCast(OSNumber, metaState->getObject(kIOPPIDCtrlLoopMaxPowerAdjustmentKey))) == NULL)
		{
			PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::cacheMetaState meta state has no power-max-adjustment\n");
			return false;
		}

		if ((numHistLen = OSDynamicCast(OSNumber, metaState->getObject(kIOPPIDCtrlLoopHistoryLenKey))) == NULL)
		{
			PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::cacheMetaState meta state has no history-length\n");
			return false;
		}
	
		if ((numInputTarget = OSDynamicCast(OSNumber, metaState->getObject(kIOPPIDCtrlLoopTargetTempDelta))) == NULL)
		{
			PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::cacheMetaState meta state has no intput-target delta\n");
			return false;
		}

		if ((numInputMax = OSDynamicCast(OSNumber, metaState->getObject(kIOPPIDCtrlLoopInputMaxKey))) == NULL)
		{
			PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::cacheMetaState meta state has no input-max\n");
			return false;
		}

		if ((numOverTempSlewAverageOffset = OSDynamicCast(OSNumber, metaState->getObject( kOverTempSlewAverageOffset))) == NULL)
		{
			PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::cacheMetaState meta state has no kOverTempSlewAverageOffset\n");
			return false;
		}

		if ((numOverTempSlewImmediateOffset = OSDynamicCast(OSNumber, metaState->getObject(kOverTempSlewImmediateOffset))) == NULL)
		{
			PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::cacheMetaState meta state has no kOverTempSlewImmediateOffset\n");
			return false;
		}

		if ((numEndOfSlewOffset = OSDynamicCast(OSNumber, metaState->getObject(kEndOfSlewOffset))) == NULL)
		{
			PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::cacheMetaState meta state has no kEndOfSlewOffset\n");
			return false;
		}

		if ((numOverTempSlewAverageNumberOfSamples = OSDynamicCast(OSNumber, metaState->getObject(kOverTempSlewAverageNumberOfSamples))) == NULL)
		{
			PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::cacheMetaState meta state has no kOverTempSlewAverageNumberOfSamples\n");
			return false;
		}

		if ((numOverTempSleepAverageOffset = OSDynamicCast(OSNumber, metaState->getObject( kOverTempSleepAverageOffset))) == NULL)
		{
			PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::cacheMetaState meta state has no kOverTempSleepAverageOffset\n");
			return false;
		}

		if ((numOverTempSleepImmediateOffset = OSDynamicCast(OSNumber, metaState->getObject(kOverTempSleepImmediateOffset))) == NULL)
		{
			PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::cacheMetaState meta state has no kOverTempSleepImmediateOffset\n");
			return false;
		}

		if ((numOverTempSleepAverageNumberOfSamples = OSDynamicCast(OSNumber, metaState->getObject(kOverTempSleepAverageNumberOfSamples))) == NULL)
		{
			PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::cacheMetaState meta state has no kOverTempSleepAverageNumberOfSamples\n");
			return false;
		}

		if ((numOutputMin = OSDynamicCast(OSNumber, metaState->getObject(kIOPPIDCtrlLoopOutputMinKey))) == NULL)
		{
			PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::cacheMetaState meta state has no output-min\n");
		}

		if ((numOutputMax = OSDynamicCast(OSNumber, metaState->getObject(kIOPPIDCtrlLoopOutputMaxKey))) == NULL)
		{
			PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::cacheMetaState meta state has no output-max\n");
		}

		if ((decresingLimitingFactorNumber = OSDynamicCast(OSNumber, metaState->getObject(kIOGlobalContorlDecreaseFanLimit))) == NULL)
		{
			PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::cacheMetaState meta state has no decrease-fan-limit\n");
		}


		if ((overrideMinValueNumber = OSDynamicCast(OSNumber, metaState->getObject(kIOOverrideMinValueTargetValue))) == NULL)
		{
			PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::cacheMetaState meta state has no kIOOverrideMinValueTargetValue\n");
		}

		overrideActive = false;
		if (outputOverride)
		{
			outputOverride->release();
			outputOverride = NULL;
		}

		G_p = *((SInt32 *) dataG_p->getBytesNoCopy());
		G_d = *((SInt32 *) dataG_d->getBytesNoCopy());
		G_r = *((SInt32 *) dataG_r->getBytesNoCopy());
	
		powerMaxAdj.sensValue = ((SInt32) numPowerMax->unsigned32BitValue()) - ((SInt32) numPowerAdj->unsigned32BitValue());

		PM11_2_RUNTIMELOG("***** PowerMac11_2_CPUsCtrlLoop::cacheMetaState powerMaxAdj: %ld == ( %ld - %ld)\n", powerMaxAdj.sensValue >> 16, ((SInt32) numPowerMax->unsigned32BitValue()) / (1 << 16), ((SInt32) numPowerAdj->unsigned32BitValue())  / (1 << 16));

		// Max Tempoerature:
		inputMax.sensValue = (SInt32)numInputMax->unsigned32BitValue();
		
		if ( numInputTarget )
		{
			inputTarget.sensValue = inputMax.sensValue - (SInt32)numInputTarget->unsigned32BitValue();
		}
		
		if ( numOutputMin )
			outputMin = numOutputMin->unsigned32BitValue();
		else
			outputMin = 1500; // Min 1500 RPM
			
		if ( numOutputMax )
			outputMax = numOutputMax->unsigned32BitValue();
		else
			outputMax = 0;
			
		if ( overrideMinValueNumber )
			mOverrideMinOutputValue = overrideMinValueNumber->unsigned32BitValue();
		else
			mOverrideMinOutputValue = 0;
	
		// resize the history array if necessary
		newHistoryLen = numHistLen->unsigned32BitValue();
		if (newHistoryLen == 0) newHistoryLen = 2;	// must be two or more in order to have a valid
													// derivative term
        
		// This limits how fast the fans can decrease their speed:
		if ( ( mGroupControl ) && ( decresingLimitingFactorNumber ) )
			mGroupControl->setDecresingLimitingFactor( decresingLimitingFactorNumber->unsigned32BitValue() );
		
		if (newHistoryLen != historyLen)
		{
			newHistoryArray = (samplePoint *) IOMalloc( sizeof(samplePoint) * newHistoryLen );
			bzero( newHistoryArray, sizeof(samplePoint) * newHistoryLen );

			// copy samples from the old array into the new
			for (i=0; i<historyLen && i<newHistoryLen; i++)
			{
				sample = sampleAtIndex(i);

				(&(newHistoryArray[i]))->sample.sensValue = sample->sample.sensValue;
				(&(newHistoryArray[i]))->error.sensValue = sample->error.sensValue;
			}

			IOFree( historyArray, sizeof(samplePoint) * historyLen );

			historyArray = newHistoryArray;
			historyLen = newHistoryLen;
			latestSample = 0;
		}

		// Overtemp Offsets:
		mOverTempSlewAverageOffset.sensValue = (SInt32)numOverTempSlewAverageOffset->unsigned32BitValue();
		mOverTempSlewImmediateOffset.sensValue = (SInt32)numOverTempSlewImmediateOffset->unsigned32BitValue();
		mEndOfSlewOffset.sensValue = (SInt32)numEndOfSlewOffset->unsigned32BitValue();
		mOverTempSlewAverageNumberOfSamples = (int)numOverTempSlewAverageNumberOfSamples->unsigned32BitValue();
	
		// Sleep Offsets:
		mOverTempSleepImmediateOffset.sensValue = (SInt32)numOverTempSleepImmediateOffset->unsigned32BitValue();
		mOverTempSleepAverageOffset.sensValue = (SInt32)numOverTempSleepAverageOffset->unsigned32BitValue();
		mOverTempSleepAverageNumberOfSamples = (int)numOverTempSleepAverageNumberOfSamples->unsigned32BitValue();

		PM11_2_RUNTIMELOG("mOverTempSlewAverageOffset %ld, mOverTempSlewImmediateOffset %ld, mEndOfSlewOffset %ld, mOverTempSlewAverageNumberOfSamples %d\n",
			mOverTempSlewAverageOffset.sensValue >> 16, mOverTempSlewImmediateOffset.sensValue >> 16, mEndOfSlewOffset.sensValue >> 16, mOverTempSlewAverageNumberOfSamples);

		PM11_2_RUNTIMELOG("mOverTempSleepImmediateOffset %ld, mOverTempSleepAverageOffset %ld, mOverTempSleepAverageNumberOfSamples %d\n",
			mOverTempSleepImmediateOffset.sensValue >> 16, mOverTempSleepAverageOffset.sensValue >> 16, mOverTempSleepAverageNumberOfSamples);
		
		int newTemperatureHistorySize = max ( mOverTempSlewAverageNumberOfSamples, mOverTempSleepAverageNumberOfSamples);
		
		if ( newTemperatureHistorySize !=  mTemperatureHistorySize )
		{
			if ( mTemperatureHistory )
			{
				IOFree ( mTemperatureHistory , sizeof(SensorValue) * mTemperatureHistorySize );
				mTemperatureHistory= NULL;
			}
		
			mTemperatureHistorySize = newTemperatureHistorySize;
			mTemperatureHistory = (SensorValue*)IOMalloc( sizeof(SensorValue) * newTemperatureHistorySize );
			bzero( mTemperatureHistory, sizeof(SensorValue) * newTemperatureHistorySize );
			mCurrentPointInTemperatureHistory = 0;
		}
		
		PM11_2_RUNTIMELOG("*** PID CACHE %d *** G_p: 0x%08lX G_d: 0x%08lX G_r: 0x%08lX\n"
		                  "******************** inT: 0x%08lX oMi: 0x%08lX oMa: 0x%08lX\n",
						  ( getCtrlLoopID() ? getCtrlLoopID()->unsigned16BitValue() : 0xFF ),
					      G_p, G_d, G_r, inputTarget.sensValue, outputMin, outputMax);
	}
        

	// set the interval
	clock_interval_to_absolutetime_interval(intervalSec, NSEC_PER_SEC, &interval);
	PM11_2_RUNTIMELOG("***************** Interval: %u\n", (unsigned int)intervalSec);

	return(true);
}

SensorValue PowerMac11_2_CPUsCtrlLoop::temperatureAverageOnLastNSamples( int numberOfSamples )
{
	SensorValue average = { 0 };
	int lastEntry = ( ( mCurrentPointInTemperatureHistory == 0 ) ? (mTemperatureHistorySize - 1 ) : mCurrentPointInTemperatureHistory - 1);
	int numberOfEntriesToAdd = numberOfSamples;
	
	while ( numberOfEntriesToAdd )
	{
		average.sensValue += mTemperatureHistory[ lastEntry ].sensValue;
		lastEntry = ( ( lastEntry == 0 ) ? (mTemperatureHistorySize - 1 ) : lastEntry - 1);
		numberOfEntriesToAdd--;
	}
	
	average.sensValue /= numberOfSamples;
	
	PM11_2_RUNTIMELOG("%d - temperatureAverageOnLastNSamples(%d) %ld\n",
		( getCtrlLoopID() ? getCtrlLoopID()->unsigned16BitValue() : 0xFF ),
		numberOfSamples ,average.sensValue >> 16 );

	return average;
}

void PowerMac11_2_CPUsCtrlLoop::deadlinePassed( void )
{
	bool deadlineAbsolute = (ctrlloopState == kIOPCtrlLoopFirstAdjustment);
	SensorValue maxPower = { 0 }, maxTemperature = { 0 };
	int i;
	
	if ( mAllControlsRegistred && mAllSensorsRegistred )
	{
		bool oldValueForTimerCallBackActive = timerCallbackActive;
	
		// Force adjustControls to update the targets
		timerCallbackActive = true;

		// getAggreateSensorValue
		// ======================

		// acquire sample (getAggreateSensorValue does not really work since it reads only one value):
		for ( i = 0; i <  mActualNumberOfInputSets; i ++ )
		{
			if ( mInputSetArray[ i ].referenceSAT != NULL )
			{
				if ( mInputSetArray[ i ].referenceSAT->setProperty("lock-sensors", kOSBooleanTrue) == false )
				{
					kprintf("WARNING WARNING lock-sensors true fails\n");
				}
			}
			else
			{
				kprintf("deadlinePassed WARNING WARNING missing SAT\n");
			}
		
			SensorValue currentPower = getSensorValue (mInputSetArray[ i ].powerSensor );
			SensorValue currentTemp	= getSensorValue (mInputSetArray[ i ].temperatureSensor);
		
			if ( maxTemperature.sensValue < currentTemp.sensValue )
				maxTemperature.sensValue = currentTemp.sensValue;

			if ( maxPower.sensValue < currentPower.sensValue )
				maxPower.sensValue = currentPower.sensValue;

			if ( mInputSetArray[ i ].referenceSAT != NULL )
			{
				if ( mInputSetArray[ i ].referenceSAT->setProperty("lock-sensors", kOSBooleanFalse) == false )
				{
					kprintf("WARNING WARNING lock-sensors false fails\n");
				}
			}
			else
			{
				kprintf("deadlinePassed WARNING WARNING missing SAT\n");
			}
		}

		// WORKAROUND: sensors aew way off I get values that are about 1/2 of what they should be. Multiply by 2:
		//maxPower.sensValue *= 2;
		//maxTemperature.sensValue *= 2;

		PM11_2_RUNTIMELOG("**** %d *** power=%ld temp=%ld\n", ( getCtrlLoopID() ? getCtrlLoopID()->unsigned16BitValue() : 0xFF ), maxPower.sensValue >> 16, maxTemperature.sensValue >> 16);
		
		// Add the max temperature to the hisory array of temprature:
		if (tempIndex == 0)
			tempIndex = kTemperatureSensorBufferSize - 1;
		else
			tempIndex --;
			
		tempHistory[tempIndex] = maxTemperature;
				
		// Now add the power to the history array (which is the history array of power):
		// move the top of the power array to the next spot -- it's circular
		if (latestSample == 0)
			latestSample = historyLen - 1;
		else
			latestSample --;

		// store the sample in the history
		historyArray[latestSample].sample.sensValue = maxPower.sensValue;
		
		// calculate the error term and store it (the average will be calculated on this)
		historyArray[latestSample].error.sensValue = powerMaxAdj.sensValue - maxPower.sensValue;
		
		PM11_2_RUNTIMELOG("**** %d *** index %d power=%ld \n", ( getCtrlLoopID() ? getCtrlLoopID()->unsigned16BitValue() : 0xFF ), latestSample, historyArray[latestSample].error.sensValue >> 16);

		// Add the last value to the history:
		mTemperatureHistory[ mCurrentPointInTemperatureHistory ] = maxTemperature;
		mCurrentPointInTemperatureHistory = ( ( mCurrentPointInTemperatureHistory == (mTemperatureHistorySize - 1 ) ) ? 0 : mCurrentPointInTemperatureHistory + 1);

#if 0
		// Use the average calculated above to see if we are in overtemp:
		bool overtempSlewingAverage = ( temperatureAverageOnLastNSamples( mOverTempSlewAverageNumberOfSamples ).sensValue >= ( ( 35 << 16 ) + mOverTempSlewAverageOffset.sensValue ) ) ? true : false;
		bool overtempSlewingImmediate = ( maxTemperature.sensValue >= ( ( 35 << 16 ) + mOverTempSlewImmediateOffset.sensValue ) ) ? true : false;
		bool stopSlewingNow =  ( maxTemperature.sensValue < ( ( 35 << 16 ) + mEndOfSlewOffset.sensValue ) ) ? true : false;
		
		// See if the sleep constions are met
		bool overtempImmediateSleep = (maxTemperature.sensValue >= ( ( 35 << 16 ) + mOverTempSleepImmediateOffset.sensValue ) )  ? true : false;
		bool overtempAverageSleep = (temperatureAverageOnLastNSamples( mOverTempSleepAverageNumberOfSamples ).sensValue >= ( ( 35 << 16 ) + mOverTempSleepAverageOffset.sensValue ) )  ? true : false;
#else
		// Use the average calculated above to see if we are in overtemp:
		bool overtempSlewingAverage = ( temperatureAverageOnLastNSamples( mOverTempSlewAverageNumberOfSamples ).sensValue >= ( inputMax.sensValue + mOverTempSlewAverageOffset.sensValue ) ) ? true : false;
		bool overtempSlewingImmediate = ( maxTemperature.sensValue >= ( inputMax.sensValue + mOverTempSlewImmediateOffset.sensValue ) ) ? true : false;
		bool stopSlewingNow =  ( maxTemperature.sensValue < ( inputMax.sensValue + mEndOfSlewOffset.sensValue ) ) ? true : false;
		
		// See if the sleep constions are met
		bool overtempImmediateSleep = (maxTemperature.sensValue >= ( inputMax.sensValue + mOverTempSleepImmediateOffset.sensValue ) )  ? true : false;
		bool overtempAverageSleep = (temperatureAverageOnLastNSamples( mOverTempSleepAverageNumberOfSamples ).sensValue >= ( inputMax.sensValue + mOverTempSleepAverageOffset.sensValue ) )  ? true : false;
#endif

		// First handle the overtemp cases as they induce sleep:
		// =====================================================
		if ( overtempImmediateSleep )
		{
			PM11_2_RUNTIMELOG("**** %d ***: Thermal Runaway Detected: System Will Sleep\n", ( getCtrlLoopID() ? getCtrlLoopID()->unsigned16BitValue() : 0xFF ));
			PM11_2_RUNTIMELOG("**** %d *** T_cur=%ld >= (T_max:%ld + sleepOffset:%ld)\n", ( getCtrlLoopID() ? getCtrlLoopID()->unsigned16BitValue() : 0xFF ),
					maxTemperature.sensValue >> 16, inputMax.sensValue >> 16, mOverTempSleepImmediateOffset.sensValue >> 16);

			platformPlugin->coreDump();
			platformPlugin->sleepSystem();
		}
		else if ( overtempAverageSleep )
		{
			PM11_2_RUNTIMELOG("**** %d ***: Long Overtemp Detected: System Will Sleep\n", ( getCtrlLoopID() ? getCtrlLoopID()->unsigned16BitValue() : 0xFF ));
			PM11_2_RUNTIMELOG("**** %d *** T_ave=%ld >= (T_max:%ld + sleepOffset:%ld)\n", ( getCtrlLoopID() ? getCtrlLoopID()->unsigned16BitValue() : 0xFF ),
					temperatureAverageOnLastNSamples( mOverTempSleepAverageNumberOfSamples ).sensValue >> 16, inputMax.sensValue >> 16, mOverTempSleepAverageOffset.sensValue >> 16);

			// Otherwise when we wake up we sleep again.
			//bzero( mTemperatureHistory, sizeof(SensorValue) * mTemperatureHistorySize );

			platformPlugin->coreDump();
			platformPlugin->sleepSystem();
		}
		else
		{
			bool didSetEnvVar	=	false;
			bool isSlewing		=	platformPlugin->envArrayCondIsTrueForObject(this, gIOPPluginEnvInternalOvertemp);
			
			PM11_2_RUNTIMELOG("**** %d ***: isSlewing %d (A %d, I %d) %d\n",
				( getCtrlLoopID() ? getCtrlLoopID()->unsigned16BitValue() : 0xFF ),
				isSlewing, overtempSlewingAverage, overtempSlewingImmediate, stopSlewingNow);
			
			// Not Overtemp: Look for the slewing instances:
			if ( (overtempSlewingAverage || overtempSlewingImmediate) )
			{
				if ( ! isSlewing )
				{
					// Handle slewing overtemp case
					platformPlugin->setEnvArray(gIOPPluginEnvInternalOvertemp, this, true);
					didSetEnvVar = true;
				}
			}
			else if ( stopSlewingNow )
			{
				if ( isSlewing )
				{
					// Stops the Slewing
					platformPlugin->setEnvArray(gIOPPluginEnvInternalOvertemp, this, false);
					didSetEnvVar = true;
				}
			}
			
			// now we can call adjustControls:
			if ( didSetEnvVar == false )
				adjustControls();
		}
		
		// Replace the timerCallbackActive with the original value
		timerCallbackActive = oldValueForTimerCallBackActive;
		
		// set the deadline
		if (deadlineAbsolute)
		{
			// this is the first time we're setting the deadline.  In order to better stagger
			// timer callbacks, offset the deadline by 100us * ctrlloopID.
			AbsoluteTime adjustedInterval;
			const OSNumber * id = getCtrlLoopID();

			// 100 * ctrlLoopID -> absolute time format
			clock_interval_to_absolutetime_interval(100 * id->unsigned32BitValue(), NSEC_PER_USEC, &adjustedInterval);

			// Add standard interval to produce adjusted interval
			ADD_ABSOLUTETIME( &adjustedInterval, &interval );

			clock_absolutetime_interval_to_deadline(adjustedInterval, &deadline);
		}
		else
		{
			ADD_ABSOLUTETIME(&deadline, &interval);
		}
	}
}

SensorValue PowerMac11_2_CPUsCtrlLoop::calculateDerivativeTerm( void ) const
{
	int	latest;
	int	previous;
	SensorValue	result;

	latest = tempIndex;
	previous = ( tempIndex == 0 ? ( kTemperatureSensorBufferSize - 1 ) : 0 );

	// Get the change in the error term over the latest interval.
	result.sensValue = tempHistory[ latest ].sensValue - tempHistory[ previous ].sensValue;

	// Divide by the elapsed time to get the slope.
	result.sensValue = result.sensValue / ( SInt32 ) intervalSec;

	return( result );
}

SensorValue PowerMac11_2_CPUsCtrlLoop::calculateIntegralTerm( void ) const
{
	SensorValue integralValue = super::calculateIntegralTerm( );

	// average on the number of samples
	integralValue.sensValue /= (SInt32)historyLen;

	return(integralValue);
}

SensorValue PowerMac11_2_CPUsCtrlLoop::calculateTargetTemp()
{
	// Calculate the adjusted target
	SInt32 rRaw = calculateIntegralTerm().sensValue;
	// this is 12.20 * 16.16 => 28.36
	SInt64 rProd = (SInt64)G_r * (SInt64)rRaw;
	// Calculates the new target temperature:
	SensorValue targetTemp;
	
	targetTemp.sensValue = inputMax.sensValue - (SInt32)((rProd >> 20) & 0xFFFFFFFF);
	
	targetTemp.sensValue = ( inputTarget.sensValue < targetTemp.sensValue ? inputTarget.sensValue : targetTemp.sensValue );
	
	mLastCalculatedDelta.sensValue = tempHistory[tempIndex].sensValue - targetTemp.sensValue ;
	
	PM11_2_RUNTIMELOG("**** %d *** min (tmax, tmax - average * G_r ): min( %ld , %ld - %ld * %ld) = %ld\n",
		( getCtrlLoopID() ? getCtrlLoopID()->unsigned16BitValue() : 0xFF ),
		inputMax.sensValue >> 16, inputMax.sensValue >> 16, rRaw >> 16, G_r >> 20,  targetTemp.sensValue >> 16);

	//IOLog("MarkC-Log1: %ld, %ld, %ld, %ld, %ld,",
	//	tempHistory[tempIndex].sensValue >> 16, historyArray[latestSample].sample.sensValue >> 16, rRaw >> 16, targetTemp.sensValue >> 16, mLastCalculatedDelta.sensValue >>16 );

	return targetTemp;
}

// This is identical to adjustControls, but it is calling calculateNewTargetNonConst instead than
// calculateNewTarget () const and all this because we need a non-const implementation.

void PowerMac11_2_CPUsCtrlLoop::adjustControls( void )
{
	ControlValue newTarget;

	//CTRLLOOP_DLOG("IOPlatformPIDCtrlLoop::adjustControls - entered\n");

	if (ctrlloopState == kIOPCtrlLoopNotReady || !timerCallbackActive)
	{
		//CTRLLOOP_DLOG("IOPlatformPIDCtrlLoop::adjustControls some entities not yet registered\n");
		return;
	}

	// Apply the PID algorithm
	newTarget = calculateNewTargetNonConst();

	// set the target
	sendNewTarget( newTarget );
}

// This is copied and pasted from Q77's one. The only change is the reference to
// the outputControl, which is now the group control
ControlValue PowerMac11_2_CPUsCtrlLoop::calculateNewTargetNonConst()
{
	SInt32 dRaw;
	SInt64 accum, dProd, pProd;
	SInt32 result;
	ControlValue newTarget;
	SensorValue adjInputTarget;

	// if there is an output override, use it
	if (overrideActive)
	{
		newTarget = outputOverride->unsigned32BitValue();
		//IOLog("MarkC-Log1: *** %d *** Override Active new target %lx (T=%ld)\n", ( getCtrlLoopID() ? getCtrlLoopID()->unsigned16BitValue() : 0xFF ), newTarget, tempHistory[tempIndex].sensValue >> 16);
		PM11_2_RUNTIMELOG("*** %d *** Override Active new target %lx\n", ( getCtrlLoopID() ? getCtrlLoopID()->unsigned16BitValue() : 0xFF ), newTarget);
	}

	// apply the PID algorithm to choose a new control target value
	else
	{
		if (ctrlloopState == kIOPCtrlLoopFirstAdjustment)
		{
			result = 0;
		}
		else
		{
			adjInputTarget = calculateTargetTemp();
				
			// do the PID iteration using the result from the last set value:
			//result = mLastTargetValue;  // <-- Uncomment this to feedback the fan value that this control loop "thought" it would set
			if ( mGroupControl ) result = mGroupControl->getTargetValue(); // <-- Uncomment this to feedback the actual fan value

			// calculate the derivative term
			// apply the derivative gain
			// this is 12.20 * 16.16 => 28.36
			dRaw = calculateDerivativeTerm().sensValue;
			accum = dProd = (SInt64)G_d * (SInt64)dRaw;
								
			// calculate the proportional term
			// apply the proportional gain
			// this is 12.20 * 16.16 => 28.36
			pProd = (SInt64)G_p * (SInt64)mLastCalculatedDelta.sensValue;
			accum += pProd;
							
			// truncate the fractional part
			accum >>= 36;
	
			PM11_2_RUNTIMELOG("**** %d *** prevfan + G_d * Dt + Gp * (currentT - Target T) : %ld + %ld * %ld + %ld * (%ld - %ld) = %ld\n",
				( getCtrlLoopID() ? getCtrlLoopID()->unsigned16BitValue() : 0xFF ),
				result, G_d >> 20 , dRaw >> 16 , G_p >> 20 , tempHistory[tempIndex].sensValue >> 16, adjInputTarget.sensValue >> 16, (SInt32)(result + (SInt32)accum));
	
			//result = (UInt32)(accum < 0 ? 0 : (accum & 0xFFFFFFFF));
			result += (SInt32)accum;
		}

		newTarget = ( ControlValue )( ( result > 0 ) ? result : 0 );

		//IOLog(/* "MarkC-Log1:" */ "%ld, %ld\n",
		//	mGroupControl->getTargetValue(), newTarget);

		// apply the hard limits
		if (newTarget < outputMin)
			newTarget = outputMin;
		else if (newTarget > outputMax)
			newTarget = outputMax;
	}

	return(newTarget);
}

void PowerMac11_2_CPUsCtrlLoop::sendNewTarget( ControlValue newTarget )
{
	if ( mGroupControl )
	{
		if ( mOverrideMinOutputValue > newTarget )
			newTarget = mOverrideMinOutputValue;
	
		mLastTargetValue = newTarget;
		
		PM11_2_RUNTIMELOG("**** %d *** (%p)  New Target = %ld currentDelta = %ld\n\n",
					( getCtrlLoopID() ? getCtrlLoopID()->unsigned16BitValue() : 0xFF ), this, newTarget, mLastCalculatedDelta.sensValue >> 16);
					
		mGroupControl->setTargetValue( newTarget, this, ( mLastCalculatedDelta.sensValue * 10 ) >> 16 );  // <- uncomment this to allow a comparison based on Delta T (as Paul Thompson Wished for Q77)
		//mGroupControl->setTargetValue( newTarget, this, newTarget );  // <- uncomment this to allow a comparison based on Fan speed (as Paul Thompson asked for the first version of Q63)
		
		// ensures that from now on a new target will be calculated.
		ctrlloopState = kIOPCtrlLoopAllRegistered;
	}
}

void PowerMac11_2_CPUsCtrlLoop::sensorRegistered( IOPlatformSensor * aSensor )
{
	PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::sensorRegistered %d - entered for %s\n", ( getCtrlLoopID() ? getCtrlLoopID()->unsigned16BitValue() : 0xFF ), aSensor->getSensorDescKey()->getCStringNoCopy());
	
	allControlsAndSensorsRegistredCheck( );
}

void PowerMac11_2_CPUsCtrlLoop::controlRegistered( IOPlatformControl * aControl )
{
	PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::controlRegistered (%p %p) %d - entered for %s\n",
	outputControl, aControl, ( getCtrlLoopID() ? getCtrlLoopID()->unsigned16BitValue() : 0xFF ), aControl->getControlDescKey()->getCStringNoCopy() );

	// If it is the reference control use to add the missing info in the metastates:
	if ( outputControl == aControl )
	{
		OSNumber	*tmpNumber;

		PM11_2_DLOG("PowerMac11_2_CPUsCtrlLoop::controlRegistered UPDATE MIN/MAX Values\n");

		// Updates the metastate array with the new values:
		OSArray* metaStateArray = OSDynamicCast( OSArray, infoDict->getObject( gIOPPluginThermalMetaStatesKey ) );
		OSDictionary* metaStateDict = OSDynamicCast( OSDictionary, metaStateArray->getObject( 0 ) );

		// Min output (RPM/PWM) (16-bit integer).  Get it from outputControl.
		outputMin = outputControl->getControlMinValue();

		tmpNumber = OSNumber::withNumber( outputMin, 16 );
		metaStateDict->setObject( kIOPPIDCtrlLoopOutputMinKey, tmpNumber );
		tmpNumber->release();

		// Max output (RPM/PWM) (16-bit integer).  Get it from outputControl.
		outputMax = outputControl->getControlMaxValue();
		tmpNumber = OSNumber::withNumber( outputMax, 16 );
		metaStateDict->setObject( kIOPPIDCtrlLoopOutputMaxKey, tmpNumber );
		tmpNumber->release();
		
		// Get "Failsafe" meta state dictionary.
		metaStateDict = OSDynamicCast( OSDictionary, metaStateArray->getObject( 1 ) );

		// Set "output-override" to be "output-max".
		tmpNumber = OSNumber::withNumber( outputMax, 16 );
		metaStateDict->setObject( kIOPPIDCtrlLoopOutputOverrideKey, tmpNumber );
		tmpNumber->release();
	}

	allControlsAndSensorsRegistredCheck( );
}
