/*
 * Copyright (c) 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) 2005 Apple Computer, Inc.  All rights reserved.
 *
 *  File: $Id: PowerMac12_1_CPUFanCtrlLoop.cpp,v 1.4 2005/07/22 22:27:13 mpontil Exp $
 *
 */



#include "IOPlatformPluginSymbols.h"
#include "IOPlatformPlugin.h"
#include "PowerMac12_1_CPUFanCtrlLoop.h"
#include "PowerMac12_1VirtualControl.h"

#define super	SMU_Neo2_CPUFanCtrlLoop
OSDefineMetaClassAndStructors( PowerMac12_1_CPUFanCtrlLoop, SMU_Neo2_CPUFanCtrlLoop )

// The follwowing lets me override the SMU settings with the ones in the plist.
// I need it in EVT bringup. Also it may be useful later. Once SMU stores values
// that we feel are "right" we can remove the metastate 0 from the plist
IOReturn PowerMac12_1_CPUFanCtrlLoop::initPlatformCtrlLoop(const OSDictionary *dict)
{
	OSArray			*array = OSDynamicCast(OSArray, dict->getObject(gIOPPluginThermalMetaStatesKey) );
	OSDictionary	*originalMetastate = OSDynamicCast(OSDictionary, array->getObject( 0 ) );
	
	if ( originalMetastate )
	{
		// Makes sure this does not go away:
		originalMetastate->retain();
	}
	
	IOReturn		result = super::initPlatformCtrlLoop(dict);
	
	// Now gets the new metstate:
	OSDictionary	*smuMetastate = OSDynamicCast(OSDictionary, array->getObject( 0 ) );
	
	// And corrects it with the numbers in the plist:
	if ( originalMetastate )
	{
		if ( result == kIOReturnSuccess )
		{
			// Now overrides the smu metaastate with the content of the plist metastate:
			OSDictionary	*smuMetastate = OSDynamicCast(OSDictionary, array->getObject( 0 ) );
			OSCollectionIterator * iter = OSCollectionIterator::withCollection( originalMetastate );
			
			if ( iter != NULL )
			{
				OSSymbol * sym;

				while ( ( sym = (OSSymbol *)iter->getNextObject() ) )
				{
					// Gets the new value
					OSObject *newValue = originalMetastate->getObject( sym );
				
					// And  replaces the SMU one with this:
					smuMetastate->setObject( sym, newValue );
					
					//IOLog("PowerMac12_1_CPUFanCtrlLoop::initPlatformCtrlLoop Set Object %s to %s\n", sym->getCStringNoCopy(), newValue->getMetaClass()->getClassName());
				}
			}
			else
				IOLog("PowerMac12_1_CPUFanCtrlLoop::initPlatformCtrlLoop could not allocate iterator\n");
		}
		else

			IOLog("PowerMac12_1_CPUFanCtrlLoop::initPlatformCtrlLoop result != kIOReturnSuccess\n");

		// Do not need it anymore
		originalMetastate->release();
	}
	else
		IOLog(" PowerMac12_1_CPUFanCtrlLoop::initPlatformCtrlLoop Missing original metastate\n");

	// And finally add the "weighted average" parameter:
	OSNumber *tmpNumber = OSNumber::withNumber( kWeightedAverageDefault, 16 );
	if ( tmpNumber )
	{
		smuMetastate->setObject( kWeightedAverageKey , tmpNumber );
		tmpNumber->release();
	}

	// set the initial meta-state to zero -- subclasses should change as needed
	setMetaState(gIOPPluginZero);
	
	return result;
}

// I need  this method to aallow changes on the "weighted average" parameter:
bool PowerMac12_1_CPUFanCtrlLoop::cacheMetaState( const OSDictionary* metaState )
{
	bool superSuccedes = super::cacheMetaState( metaState );

	if ( superSuccedes )
		{
		const OSNumber*								numWeightedAverage;

		if ( ( numWeightedAverage = OSDynamicCast( OSNumber, metaState->getObject( kWeightedAverageKey ) ) ) == NULL )
			{
			mWeightedAverage = kWeightedAverageDefault;
			}
		else
			{
			mWeightedAverage = numWeightedAverage->unsigned16BitValue();
			}
		}
	
	return superSuccedes;
}

/*
	IMPORTANT NOTE:
	the following PowerMac12_1_CPUFanCtrlLoop::calculateNewTarget is a cut & paste
	of the SMU_Neo2_CPUFanCtrlLoop::calculateNewTarget(), no code changed except for:
	
	result = ( SInt32 ) targetValue;
	
	which became 
	
	result = ( SInt32 ) outputControl->getTargetValue();
*/

ControlValue PowerMac12_1_CPUFanCtrlLoop::calculateNewTarget( void ) const
	{
	SInt64											accum;
	SInt64											dProd;
	SInt64											rProd;
	SInt64											pProd;
	SInt32											pRaw;
	SInt32											dRaw;
	SInt32											rRaw;
	SInt32											result;
	SensorValue										adjInputTarget;
	ControlValue									newTarget;
	SensorValue										lastIntegralTerm;

	// If there is an output override, use it.

	if ( overrideActive )
		{
		CTRLLOOP_DLOG( "*** PID *** Override Active\n" );
		newTarget = outputOverride->unsigned32BitValue();

		return( newTarget );
		}

	// Apply the PID algorithm to choose a new control target value.

	if ( ctrlloopState == kIOPCtrlLoopFirstAdjustment )
		{
		result = 0;
		}
	else
		{
		SensorValue										sVal;

		// Calculate the adjusted target.

		lastIntegralTerm.sensValue = rRaw = calculateIntegralTerm().sensValue;
		
		// This is 12.20 * 16.16 => 28.36

		rProd = ( SInt64 ) G_r * ( SInt64 ) rRaw;

		sVal.sensValue = inputMax.sensValue - ( SInt32 ) ( ( rProd >> 20 ) & 0xFFFFFFFF );

		adjInputTarget.sensValue = min( inputTarget.sensValue, sVal.sensValue );

		// Do the PID iteration.

		//result = ( SInt32 ) targetValue; /// BEFORE
		result = ( SInt32 ) outputControl->getTargetValue(); // AFTER
		
		// Calculate the derivative term and apply the derivative gain.
		// 12.20 * 16.16 => 28.36

		dRaw = calculateDerivativeTerm().sensValue;
		accum = dProd = ( SInt64 ) G_d * ( SInt64 ) dRaw;

		// Calculate the proportional term and apply the proportional gain.
		// 12.20 * 16.16 => 28.36

		pRaw = tempHistory[ tempIndex ].sensValue - adjInputTarget.sensValue;
		pProd = ( SInt64 ) G_p * ( SInt64 ) pRaw;
		accum += pProd;

		// Truncate the fractional part.

		accum >>= 36;

		result += ( SInt32 ) accum;
		}

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

	newTarget = min( newTarget, maxMaximum );
	newTarget = max( newTarget, minMinimum );

#if 0
#if 1 // #ifdef CTRLLOOP_DEBUG
	{
		const OSString*				tempDesc;
		
		IOLog("OldValue %ld\n", outputControl->getTargetValue());

		IOLog( "*** TARGET *** "
					"%s"
					" powerSum=%08lX (%ld)"
					" G_power=%08lX"
					" powerProd=%016llX (%lld)"
					" Ttgt=%08lX (%ld)"
					" Tmax=%08lX (%ld)"
					" TtgtAdj=%08lX (%ld)"
					" T_cur=%08lX (%ld)"
					" T_err=%08lX (%ld)"
					" G_p=%08lX"
					" pProd=%016llX (%lld)"
					" dRaw=%08lX (%ld)"
					" G_d=%08lX"
					" dProd=%016llX (%lld)"
					" Accum=%016llX"
					" Res=%lu"
					" Out=%lu (%ld %ld)\n",
					( tempDesc = OSDynamicCast( OSString, infoDict->getObject( kIOPPluginThermalGenericDescKey ) ) ) != NULL ? tempDesc->getCStringNoCopy() : "Unknown CtrlLoop",
					rRaw, rRaw >> 16,
					G_r,
					rProd, rProd >> 36,
					inputTarget.sensValue, inputTarget.sensValue >> 16,
					inputMax.sensValue, inputMax.sensValue >> 16,
					adjInputTarget.sensValue, adjInputTarget.sensValue >> 16,
					tempHistory[ tempIndex ].sensValue, tempHistory[ tempIndex ].sensValue >> 16,
					pRaw, pRaw >> 16,
					G_p,
					pProd, pProd >> 36,
					dRaw, dRaw >> 16,
					G_d,
					dProd, dProd >> 36,
					accum,
					result,
					newTarget , minMinimum, maxMaximum);
		}
#endif // CTRLLOOP_DEBUG
#endif // 0

	return( newTarget );
	}


void PowerMac12_1_CPUFanCtrlLoop::sendNewTarget( ControlValue newTarget )
	{
	UInt32						mainNewTarget = newTarget;
	bool						outputAtMax = false;
	PowerMac12_1VirtualControl *tmpControl = OSDynamicCast( PowerMac12_1VirtualControl, outputControl);
	
	targetValue = newTarget;

	// const OSString*tempDesc; IOLog("%s sendNewTarget( %ld ) - %ld \n", ( tempDesc = OSDynamicCast( OSString, infoDict->getObject( kIOPPluginThermalGenericDescKey ) ) ) != NULL ? tempDesc->getCStringNoCopy() : "Unknown CtrlLoop", newTarget , outputControl->getTargetValue() );

	// Apply any hard limits.

	mainNewTarget = min( ( SInt16 ) mainNewTarget, ( SInt16 ) outputMax );
	mainNewTarget = max( ( SInt16 ) mainNewTarget, ( SInt16 ) outputMin );
	
	if ( mainNewTarget == outputMax )
		{
		outputAtMax |= true;
		}

	tmpControl->setTargetValue( mainNewTarget , this,  mainNewTarget);
	( void ) platformPlugin->setEnvArray( gIOPPluginEnvCtrlLoopOutputAtMax, this, outputAtMax );
	ctrlloopState = kIOPCtrlLoopAllRegistered;
	}
	
void PowerMac12_1_CPUFanCtrlLoop::controlRegistered( IOPlatformControl* aControl )
	{
	PowerMac12_1VirtualControl *tmpControl = OSDynamicCast( PowerMac12_1VirtualControl, outputControl);
	
	if  ( ( tmpControl != NULL ) && ( aControl == tmpControl->targetControl() ) )
		super::controlRegistered( outputControl );
	else
		super::controlRegistered( aControl );
	}
	

SensorValue PowerMac12_1_CPUFanCtrlLoop::averagePower()
{
	// NOTE: the specs need the average power of the CPU,the integal term however
	// uses the error so we need to redo our math here:
	SensorValue accum = { 0 };
	
	if ( ctrlloopState != kIOPCtrlLoopNotReady )
	{
		SensorValue lastPowerSensorReading = { historyArray[ latestSample ].sample.sensValue };

		// Bob's Magic Formula : new average = old average - (old average / 45) + (current power / 45)
		accum.sensValue = mPreviousWeightedAverage.sensValue - ( mPreviousWeightedAverage.sensValue / mWeightedAverage ) + ( lastPowerSensorReading.sensValue / mWeightedAverage );

		CTRLLOOP_DLOG("PowerReading %ld.%2ld (%ld) OldValue %ld.%2ld (%ld) NewValue %ld.%2ld (%ld) (Parameter %d)\n",
		lastPowerSensorReading.sensValue >> 16 , lastPowerSensorReading.sensValue & 0x0000FFFF , lastPowerSensorReading.sensValue,
		mPreviousWeightedAverage.sensValue >> 16 , mPreviousWeightedAverage.sensValue & 0x0000FFFF , mPreviousWeightedAverage.sensValue,
		accum.sensValue >> 16, accum.sensValue & 0x0000FFFF, accum.sensValue,
		mWeightedAverage);

		// Record the new value for the next iteration:
		mPreviousWeightedAverage.sensValue = accum.sensValue;
	}
	
	return(accum);
}
