/*
 * Copyright (c) 2003-2004 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * The contents of this file constitute Original Code as defined in and
 * are subject to the Apple Public Source License Version 1.1 (the
 * "License").  You may not use this file except in compliance with the
 * License.  Please obtain a copy of the License at
 * http://www.apple.com/publicsource and read it before using this file.
 * 
 * This 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 OR NON-INFRINGEMENT.  Please see the
 * License for the specific language governing rights and limitations
 * under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */
/*
 * Copyright (c) 2003-2004 Apple Computer, Inc.  All rights reserved.
 *
 *
 */


#include "IOPlatformPluginDefs.h"
#include "IOPlatformPluginSymbols.h"
#include "IOPlatformPlugin.h"
#include "PowerMac7_2_SlotFanCtrlLoop.h"

#define super IOPlatformPIDCtrlLoop
OSDefineMetaClassAndStructors(PowerMac7_2_SlotFanCtrlLoop, IOPlatformPIDCtrlLoop)

extern const OSSymbol * gPM72EnvSystemUncalibrated;

bool PowerMac7_2_SlotFanCtrlLoop::init( void )
{
	if (!super::init()) return(false);

	activeAGPControl = false;

	agpSensor = NULL;
	agpControl = NULL;

	return(true);
}

bool PowerMac7_2_SlotFanCtrlLoop::updateMetaState( void )
{
	const OSArray * metaStateArray;
	const OSDictionary * metaStateDict;
	const OSNumber * newMetaState;

	// else if there is an overtemp condition, use meta-state 1 (normal) or 3 (agp card control)
	// else if there is a forced meta state, use it
	// else, use meta-state 0 (normal) or 2 (agp card control)

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

	if ((platformPlugin->envArrayCondIsTrue(gIOPPluginEnvExternalOvertemp)) ||
	    (platformPlugin->getEnv(gPM72EnvSystemUncalibrated)) != NULL)
	{
		CTRLLOOP_DLOG("PowerMac7_2_SlotFanCtrlLoop::updateMetaState Entering Overtemp Mode\n");

		// Choose the correct failsafe meta state
		int failsafeIndex = activeAGPControl ? 3 : 1;
		const OSNumber* failsafeIndexNum = activeAGPControl ? gIOPPluginThree : gIOPPluginOne;

		CTRLLOOP_DLOG("PowerMac7_2_SlotFanCtrlLoop::updateMetaState Entering Overtemp Mode (%d)\n", failsafeIndex );

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

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

	// Choose the correct failsafe meta state
	int normalIndex = activeAGPControl ? 2 : 0;
	const OSNumber* normalIndexNum = activeAGPControl ? gIOPPluginTwo : gIOPPluginZero;

	CTRLLOOP_DLOG("PowerMac7_2_SlotFanCtrlLoop::updateMetaState Trying for Normal Meta State (%d)\n", normalIndex );

	// Use default "Normal" meta state
	if ((metaStateDict = OSDynamicCast(OSDictionary, metaStateArray->getObject( normalIndex ))) != NULL &&
	    (cacheMetaState( metaStateDict ) == true))
	{

		setMetaState( normalIndexNum );
		return(true);
	}
	else
	{
		// can't find a valid meta state, nothing we can really do except log an error
		CTRLLOOP_DLOG("PowerMac7_2_SlotFanCtrlLoop::updateMetaState no valid meta states!\n");
		return(false);
	}
}

SensorValue PowerMac7_2_SlotFanCtrlLoop::getAggregateSensorValue( void )
{
	SensorValue powerValue, agpValue;

	powerValue = inputSensor->forceAndFetchCurrentValue();
	inputSensor->setCurrentValue( powerValue );

	if ( agpSensor )
	{
		agpValue = agpSensor->forceAndFetchCurrentValue();
		agpSensor->setCurrentValue( agpValue );
	}

	return( activeAGPControl ? agpValue : powerValue );
}

ControlValue PowerMac7_2_SlotFanCtrlLoop::calculateNewTarget( void ) const
{
	SInt32 dRaw, rRaw;
	SInt64 accum, dProd, rProd, pProd;
	//UInt32 result, prevResult, scratch;
	SInt32 result;
	UInt32 newTarget;
	samplePoint * latest;

	// if there is an output override, use it
	if (overrideActive)
	{
		CTRLLOOP_DLOG("*** PID *** Override Active\n");
		newTarget = outputOverride->unsigned32BitValue();
	}

	// apply the PID algorithm to choose a new control target value
	else
	{
		if (ctrlloopState == kIOPCtrlLoopFirstAdjustment)
		{
			result = 0;
		}
		else
		{
			// 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;
			//CTRLLOOP_DLOG("CPU%u dProd=0x%016llX G_d=0x%08lX dRaw=0x%08lX\n", cpuID, dProd, G_d, dRaw);
	
			// calculate the reset term
			// apply the reset gain
			// this is 12.20 * 16.16 => 28.36
			rRaw = calculateIntegralTerm().sensValue;
			rProd = (SInt64)G_r * (SInt64)rRaw; 
			accum += rProd;
	
			// calculate the proportional term
			// apply the proportional gain
			// this is 12.20 * 16.16 => 28.36
			latest = sampleAtIndex(0);
			pProd = (SInt64)G_p * (SInt64)latest->error.sensValue;
			accum += pProd;
			
			// truncate the fractional part
			accum >>= 36;

			// if we're doing AGP Card Control, we use the PID result as a delta with respect to the
			// previous target. For traditional PCI power control, the result is calculated independent
			// of the previous result.
			if ( activeAGPControl )
			{
				result = (SInt32)accum + (SInt32)agpControl->getTargetValue();
			}
			else
			{
				result = (SInt32)accum;
			}
		}

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

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

#if 0
#ifdef CTRLLOOP_DEBUG
	if (timerCallbackActive)
	{
		const OSString * tempDesc;
#endif
		CTRLLOOP_DLOG("%s"
		              " G_p=%08lX"
					  " G_d=%08lX"
					  " G_r=%08lX"
					  " T_cur=%08lX"
					  " accum=%016llX"
					  " result=%ld"
		              " T_err=%08lX"
					  " pProd=%016llX"
		              " dRaw=%08lX"
					  " dProd=%016llX"
					  " rRaw=%08lX"
					  " rProd=%016llX\n",
						(tempDesc = OSDynamicCast( OSString, infoDict->getObject(kIOPPluginThermalGenericDescKey))) != NULL ?
								tempDesc->getCStringNoCopy() : "Unknown CtrlLoop",
					  G_p,
					  G_d,
					  G_r,
					  latest->sample.sensValue,
					  accum,
					  result,
					  (latest->error.sensValue),
					  (pProd),
					  (dRaw),
					  (dProd),
					  (rRaw),
					  (rProd) );
#ifdef CTRLLOOP_DEBUG
	}
#endif
#endif
	}

	return(newTarget);
}

void PowerMac7_2_SlotFanCtrlLoop::sendNewTarget( ControlValue newTarget )
{
	if ( activeAGPControl )
	{
		if (ctrlloopState == kIOPCtrlLoopFirstAdjustment ||
			ctrlloopState == kIOPCtrlLoopDidWake ||
			newTarget != agpControl->getTargetValue() )
		{
			if (agpControl->sendTargetValue( newTarget ))
			{
				agpControl->setTargetValue(newTarget);
				ctrlloopState = kIOPCtrlLoopAllRegistered;
			}
			else
			{
				CTRLLOOP_DLOG("PowerMac7_2_SlotFanCtrlLoop::sendNewTarget failed to send AGP target value\n");
			}
		}

		if ( outputControl && outputControl->isRegistered() == kOSBooleanTrue )
		{
			// If we're in AGP Card Control mode, newTarget is an AGP fan speed. Map it
			// onto the PCI fan.
			ControlValue pciFanTarget;
	
			// the following equation makes the assumption that the min/max for both fans
			// is greater than or equal to zero:
			//
			// ( pci_target - pci_min ) / ( pci_max - pci_min) = ( agp_target - agp_min ) / ( agp_max - agp_min )
	
			pciFanTarget = ( ( pciFanMax - pciFanMin) * ( newTarget - outputMin ) ) / ( outputMax - outputMin ) + pciFanMin;
	
			CTRLLOOP_DLOG( "NV40 FAN TARGET: %lu PCI FAN TARGET: %lu\n", newTarget, pciFanTarget );
	
			if (ctrlloopState == kIOPCtrlLoopFirstAdjustment ||
				ctrlloopState == kIOPCtrlLoopDidWake ||
				pciFanTarget != outputControl->getTargetValue() )
			{
				if (outputControl->sendTargetValue( pciFanTarget ))
				{
					outputControl->setTargetValue( pciFanTarget );
					ctrlloopState = kIOPCtrlLoopAllRegistered;
				}
				else
				{
					CTRLLOOP_DLOG("PowerMac7_2_SlotFanCtrlLoop::sendNewTarget failed to send PCI target value\n");
				}
			}
		}
	}
	else
	{	
		if (ctrlloopState == kIOPCtrlLoopFirstAdjustment ||
			ctrlloopState == kIOPCtrlLoopDidWake ||
			newTarget != outputControl->getTargetValue() )
		{
			if (outputControl->sendTargetValue( newTarget ))
			{
				outputControl->setTargetValue(newTarget);
				ctrlloopState = kIOPCtrlLoopAllRegistered;
			}
			else
			{
				CTRLLOOP_DLOG("PowerMac7_2_SlotFanCtrlLoop::sendNewTarget failed to send target value\n");
			}
		}
	}
}

void PowerMac7_2_SlotFanCtrlLoop::sensorRegistered( IOPlatformSensor * aSensor )
{
	const OSData * zone;

	// see if this is a dynamically-provided AGP sensor, such as a video card temperature sensor
	if ( aSensor != inputSensor &&
	     ( ( zone = aSensor->getSensorZone() ) != NULL ) &&
		 ( *(UInt32 *)zone->getBytesNoCopy() == kIOPPluginAGPThermalZone ) )
	{
		CTRLLOOP_DLOG( "PowerMac7_2_SlotFanCtrlLoop::sensorRegistered got AGP sensor 0x%08lX\n",
				aSensor->getSensorID()->unsigned32BitValue() );

		// This is an AGP sensor. First, disable polling.
		aSensor->setPollingPeriod( (UInt32) kIOPPluginNoPolling );
		aSensor->setPollingPeriodNS( (UInt32) kIOPPluginNoPolling );
		aSensor->sendPollingPeriod();

		// Now, look for the input-target property. If found, save it in the meta state dictionary
		// and use this sensor to drive the AGP/PCI control loop.
		IOService* agpSensorDriver;
		IOService* agpSensorNub;
		OSData* target;

		if ( ( ( agpSensorDriver = aSensor->getSensorDriver() ) != NULL ) &&
		     ( ( agpSensorNub = agpSensorDriver->getProvider() ) != NULL ) &&
			 ( ( target = OSDynamicCast( OSData, agpSensorNub->getProperty( "input-target" ) ) ) != NULL ) )
		{
			CTRLLOOP_DLOG( "PowerMac7_2_SlotFanCtrlLoop::sensorRegistered AGP Sensor 0x%08lX has input-target 0x%08lX\n",
					aSensor->getSensorID()->unsigned32BitValue(), *(UInt32 *)target->getBytesNoCopy() );

			// place the input-target value in the "AGP control" meta state dict

			OSNumber* numTarget;
			OSArray* metaStateArray;
			OSDictionary* metaStateDict;

			// grab the meta state array
			metaStateArray = OSDynamicCast( OSArray, infoDict->getObject( gIOPPluginThermalMetaStatesKey ) );

			// "AGP Control" meta state is at index 2
			if ( ( metaStateDict = OSDynamicCast( OSDictionary, metaStateArray->getObject( 2 ) ) ) == NULL )
			{
				CTRLLOOP_DLOG( "PowerMac7_2_SlotFanCtrlLoop::sensorRegistered Failed to fetch meta state 2\n" );
				goto AGP_Sensor_Init_Failed;
			}

			// encapsulate the input-target as an OSNumber and insert it
			numTarget = OSNumber::withNumber( *(UInt32 *)target->getBytesNoCopy(), 32 );
			metaStateDict->setObject( kIOPPIDCtrlLoopInputTargetKey, numTarget );
			numTarget->release();

			// designate this as the AGP sensor to use for input

			agpSensor = aSensor;
		}
	}

AGP_Sensor_Init_Failed:

	// if we've found both an agp sensor and an agp control, then switch to AGP control mode
	if ( !activeAGPControl && agpSensor && agpControl )
	{
		CTRLLOOP_DLOG( "PowerMac7_2_SlotFanCtrlLoop::sensorRegistered AGP Card Control active\n" );
		activeAGPControl = true;
		updateMetaState();
	}

	/*
	 * Decide whether or not to kick off the control algorithm by invoking deadlinePassed().
	 *
	 * - if the deadline is already non-zero, the ball is already rolling, so do nothing
	 *
	 * - if activeAGPControl is true, we have all the required components for AGP Card Control,
	 *   so begin the control algorithm.
	 *
	 * - if inputSensor and outputControl are registered, we can do tradition PCI fan control,
	 *   so kick it off.
	 */

	if ( activeAGPControl ||
	     ( inputSensor && ( inputSensor->isRegistered() == kOSBooleanTrue ) &&
	       outputControl && ( outputControl->isRegistered() == kOSBooleanTrue ) ) )
	{
		CTRLLOOP_DLOG("PowerMac7_2_SlotFanCtrlLoop::sensorRegistered starting control algorithm, AGP Control = %s\n",
				activeAGPControl ? "TRUE" : "FALSE" );

		ctrlloopState = kIOPCtrlLoopFirstAdjustment;

		// set the deadline
		deadlinePassed();
	}
}

void PowerMac7_2_SlotFanCtrlLoop::controlRegistered( IOPlatformControl * aControl )
{
	const OSData * zone;

	// see if this is a dynamically-provided AGP control, such as a video card fan
	if ( aControl != outputControl &&
	     ( ( zone = aControl->getControlZone() ) != NULL ) &&
		 ( *(UInt32 *)zone->getBytesNoCopy() == kIOPPluginAGPThermalZone ) )
	{
		CTRLLOOP_DLOG( "PowerMac7_2_SlotFanCtrlLoop::controlRegistered got AGP control 0x%08lX\n",
				aControl->getControlID()->unsigned32BitValue() );

		OSNumber* tmpNumber;
		OSArray* metaStateArray;
		OSDictionary* metaStateDict;

		// grab the meta state array
		metaStateArray = OSDynamicCast( OSArray, infoDict->getObject( gIOPPluginThermalMetaStatesKey ) );

		// "AGP Control" meta state is at index 2
		if ( ( metaStateDict = OSDynamicCast( OSDictionary, metaStateArray->getObject( 2 ) ) ) == NULL )
		{
			CTRLLOOP_DLOG( "PowerMac7_2_SlotFanCtrlLoop::controlRegistered Failed to fetch meta state 2\n" );
			goto AGP_Control_Init_Failed;
		}

		// When we're in AGP Card Control mode, we use the PID result to drive the AGP
		// fan, and then we map that fan speed onto the PCI fan. The PCI fan max and min
		// are specified by two properties in the meta state 2 entry, pci-fan-min and
		// pci-fan-max

		if ( ( tmpNumber = OSDynamicCast( OSNumber, metaStateDict->getObject( "pci-fan-min" ) ) ) == NULL )
		{
			CTRLLOOP_DLOG( "PowerMac7_2_SlotFanCtrlLoop::controlRegistered Failed to fetch pci-fan-min\n" );
			goto AGP_Control_Init_Failed;
		}
		
		pciFanMin = tmpNumber->unsigned32BitValue();

		if ( ( tmpNumber = OSDynamicCast( OSNumber, metaStateDict->getObject( "pci-fan-max" ) ) ) == NULL )
		{
			CTRLLOOP_DLOG( "PowerMac7_2_SlotFanCtrlLoop::controlRegistered Failed to fetch pci-fan-max\n" );
			goto AGP_Control_Init_Failed;
		}

		pciFanMax = tmpNumber->unsigned32BitValue();

		CTRLLOOP_DLOG( "PowerMac7_2_SlotFanCtrlLoop::controlRegistered PCI FAN Min: %lu Max: %lu\n",
				pciFanMin, pciFanMax );

		// Min output (RPM/PWM)

		if ( ( tmpNumber = OSNumber::withNumber( aControl->getControlMinValue(), 16 ) ) == NULL )
		{
			CTRLLOOP_DLOG( "PowerMac7_2_SlotFanCtrlLoop::controlRegistered Failed to fetch control's min-value\n" );
			goto AGP_Control_Init_Failed;
		}

		CTRLLOOP_DLOG( "PowerMac7_2_SlotFanCtrlLoop::controlRegistered output-min: %lu\n", tmpNumber->unsigned32BitValue() );

		metaStateDict->setObject( kIOPPIDCtrlLoopOutputMinKey, tmpNumber );
		tmpNumber->release();

		// Max output (RPM/PWM)

		if ( ( tmpNumber = OSNumber::withNumber( aControl->getControlMaxValue(), 16 ) ) == NULL )
		{
			CTRLLOOP_DLOG( "PowerMac7_2_SlotFanCtrlLoop::controlRegistered Failed to fetch control's max-value\n" );
			goto AGP_Control_Init_Failed;
		}

		CTRLLOOP_DLOG( "PowerMac7_2_SlotFanCtrlLoop::controlRegistered output-max: %lu\n", tmpNumber->unsigned32BitValue() );

		metaStateDict->setObject( kIOPPIDCtrlLoopOutputMaxKey, tmpNumber );

		// "AGP Control Failsafe" meta state is at index 3
		if ( ( metaStateDict = OSDynamicCast( OSDictionary, metaStateArray->getObject( 3 ) ) ) == NULL )
		{
			CTRLLOOP_DLOG( "PowerMac7_2_SlotFanCtrlLoop::controlRegistered Failed to fetch meta state 3\n" );
			tmpNumber->release();
			goto AGP_Control_Init_Failed;
		}

		// Set "output-override" to be "output-max".

		metaStateDict->setObject( kIOPPIDCtrlLoopOutputOverrideKey, tmpNumber );
		tmpNumber->release();

		// designate this as the AGP sensor to use for input

		agpControl = aControl;
	}

AGP_Control_Init_Failed:

	// if we've found both an agp sensor and an agp control, then switch to AGP control mode
	if ( !activeAGPControl && agpSensor && agpControl )
	{
		CTRLLOOP_DLOG( "PowerMac7_2_SlotFanCtrlLoop::controlRegistered AGP Card Control active\n" );
		activeAGPControl = true;
		updateMetaState();
	}

	/*
	 * Decide whether or not to kick off the control algorithm by invoking deadlinePassed().
	 *
	 * - if activeAGPControl is true, we have all the required components for AGP Card Control,
	 *   so begin the control algorithm.
	 *
	 * - if inputSensor and outputControl are registered, we can do tradition PCI fan control,
	 *   so kick it off.
	 */

	if ( activeAGPControl ||
	     ( inputSensor && ( inputSensor->isRegistered() == kOSBooleanTrue ) &&
	       outputControl && ( outputControl->isRegistered() == kOSBooleanTrue ) ) )
	{
		CTRLLOOP_DLOG("PowerMac7_2_SlotFanCtrlLoop::controlRegistered starting control algorithm, AGP Control = %s\n",
				activeAGPControl ? "TRUE" : "FALSE" );

		ctrlloopState = kIOPCtrlLoopFirstAdjustment;

		// set the deadline
		deadlinePassed();
	}
}
