/****************************************************************************
 *
 * Module Name: tzpolicy.c -
 *   $Revision: 27 $
 *
 ****************************************************************************/

/*
 *  Copyright (C) 2000, 2001 Andrew Grover
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/*
 * TBD: 1. Move to user-space!
 *	2. Support ACPI 2.0 items (e.g. _TZD, _HOT).
 *      3. Support performance-limit control for non-processor devices
 *         (those listed in _TZD, e.g. graphics).
 */

/* TBD: Linux specific */
#include <linux/proc_fs.h>
#include <linux/sysctl.h>
#include <linux/pm.h>

#include <acpi.h>
#include <bm.h>
#include "tz.h"


#define _COMPONENT		ACPI_THERMAL
	MODULE_NAME		("tzpolicy")


/****************************************************************************
 *                                  Globals
 ****************************************************************************/

extern int TZP;

void
tz_policy_run (
	unsigned long           data);


/****************************************************************************
 *                              Internal Functions
 ****************************************************************************/

ACPI_STATUS
set_performance_limit (
	BM_HANDLE               device_handle,
	u32			flag)
{
	ACPI_STATUS             status;
	BM_REQUEST              request;

	request.status = AE_OK;
	request.handle = device_handle;
	request.command = PR_COMMAND_SET_PERF_LIMIT;
	request.buffer.length = sizeof(u32);
	request.buffer.pointer = &flag;

	status = bm_request(&request);

	if (ACPI_FAILURE(status)) {
		return status;
	}
	else {
		return request.status;
	}
}


/****************************************************************************
 *
 * FUNCTION:    tz_policy_critical
 *
 * PARAMETERS:
 *
 * RETURN:
 *
 * DESCRIPTION:
 *
 ****************************************************************************/

ACPI_STATUS
tz_policy_critical(
	TZ_CONTEXT		*tz)
{
	if (!tz || !tz->policy.critical.threshold) {
		return(AE_BAD_PARAMETER);
	}

	if (tz->policy.temperature >=
		tz->policy.critical.threshold->temperature) {
		/* TBD:	Need method for calling 'halt' - OSL function? */
	}

	return(AE_OK);
}


/****************************************************************************
 *
 * FUNCTION:    tz_policy_passive
 *
 * PARAMETERS:
 *
 * RETURN:
 *
 * DESCRIPTION:
 *
 ****************************************************************************/

ACPI_STATUS
tz_policy_passive(
	TZ_CONTEXT		*tz)
{
	TZ_PASSIVE_POLICY	*passive = NULL;
	static u32		last_temperature = 0;
	s32			trend = 0;
	u32			i = 0;

	if (!tz || !tz->policy.passive.threshold) {
		return(AE_BAD_PARAMETER);
	}

	passive = &(tz->policy.passive);

	if (tz->policy.temperature >= passive->threshold->temperature) {
		/*
		 * Thermal trend?
		 * --------------
		 * Using the passive cooling equation (see the ACPI
		 * Specification), calculate the current thermal trend
		 * (a.k.a. performance delta).
		 */
		trend = passive->tc1 *
			(tz->policy.temperature - last_temperature) +
			passive->tc2 *
			(tz->policy.temperature - passive->threshold->temperature);

		last_temperature = tz->policy.temperature;

		/*
		 * Heating Up?
		 * -----------
		 * Decrease thermal performance limit on all passive
		 * cooling devices (processors).
		 */
		if (trend > 0) {
			for (i=0; i<passive->threshold->cooling_devices.count; i++) {
				set_performance_limit(
					passive->threshold->cooling_devices.handles[i],
					PR_PERF_DEC);
			}
		}
		/*
		 * Cooling Off?
		 * ------------
		 * Increase thermal performance limit on all passive
		 * cooling devices (processors).
		 */
		else if (trend < 0) {
			for (i=0; i<passive->threshold->cooling_devices.count; i++) {
				set_performance_limit(
					passive->threshold->cooling_devices.handles[i],
					PR_PERF_INC);
			}
		}
	}

	return(AE_OK);
}


/****************************************************************************
 *
 * FUNCTION:    tz_policy_active
 *
 * PARAMETERS:
 *
 * RETURN:
 *
 * DESCRIPTION:
 *
 ****************************************************************************/

ACPI_STATUS
tz_policy_active(
	TZ_CONTEXT              *tz)
{
	ACPI_STATUS             status = AE_OK;
	TZ_THRESHOLD            *active = NULL;
	u32                     i,j = 0;

	if (!tz || !tz->policy.active.threshold) {
		return(AE_BAD_PARAMETER);
	}

	for (i = 0; i < TZ_MAX_ACTIVE_THRESHOLDS; i++) {

		active = tz->policy.active.threshold[i];
		if (!active) {
			break;
		}

		/*
		 * Above Threshold?
		 * ----------------
		 * If not already enabled, turn ON all cooling devices
		 * associated with this active threshold.
		 */
		if ((tz->policy.temperature >= active->temperature) &&
			(active->cooling_state != TZ_COOLING_ENABLED)) {

			for (j = 0; j < active->cooling_devices.count; j++) {

				status = bm_set_device_power_state(
					active->cooling_devices.handles[j],
					ACPI_STATE_D0);

				if (ACPI_SUCCESS(status)) {
				}
				else {
				}
			}

			active->cooling_state = TZ_COOLING_ENABLED;
		}
		/*
		 * Below Threshold?
		 * ----------------
		 * Turn OFF all cooling devices associated with this
		 * threshold.  Note that by checking "if not disabled" we
		 * turn off all cooling devices for thresholds in the
		 * TZ_COOLING_STATE_UNKNOWN state, useful as a level-set
		 * during the first pass.
		 */
		else if (active->cooling_state != TZ_COOLING_DISABLED) {

			for (j = 0; j < active->cooling_devices.count; j++) {

				status = bm_set_device_power_state(
					active->cooling_devices.handles[j],
					ACPI_STATE_D3);

				if (ACPI_SUCCESS(status)) {
				}
				else {
				}
			}

			active->cooling_state = TZ_COOLING_DISABLED;
		}
	}

	return(AE_OK);
}


/****************************************************************************
 *
 * FUNCTION:    tz_policy_check
 *
 * PARAMETERS:
 *
 * RETURN:
 *
 * DESCRIPTION: Note that this function will get called whenever:
 *                1. A thermal event occurs.
 *                2. The polling/sampling time period expires.
 *
 ****************************************************************************/

void
tz_policy_check (
	void                    *context)
{
	ACPI_STATUS             status = AE_OK;
	TZ_CONTEXT              *tz = NULL;
	u32                     previous_temperature = 0;
	u32                     previous_state = 0;
	u32                     active_index = 0;
	u32                     i = 0;
	u32                     sleep_time = 0;

	if (!context) {
		return;
	}

	tz = (TZ_CONTEXT*)context;

	/*
	 * Preserve Previous State:
	 * ------------------------
	 */
	previous_temperature = tz->policy.temperature;
	previous_state = tz->policy.state;

	/*
	 * Get Temperature:
	 * ----------------
	 */
	status = tz_get_temperature(tz, &(tz->policy.temperature));
	if (ACPI_FAILURE(status)) {
		return;
	}

	/*
	 * Calculate State:
	 * ----------------
	 */
	tz->policy.state = TZ_STATE_OK;

	/* Critical? */
	if ((tz->policy.critical.threshold) &&
		(tz->policy.temperature >= tz->policy.critical.threshold->temperature)) {
		tz->policy.state |= TZ_STATE_CRITICAL;
	}

	/* Passive? */
	if ((tz->policy.passive.threshold) &&
		(tz->policy.temperature >= tz->policy.passive.threshold->temperature)) {
		tz->policy.state |= TZ_STATE_PASSIVE;
	}

	/* Active? */
	if (tz->policy.active.threshold[0]) {
		for (i=0; i<tz->policy.active.threshold_count; i++) {
			if ((tz->policy.active.threshold[i]) &&
				(tz->policy.temperature >= tz->policy.active.threshold[i]->temperature)) {
			    tz->policy.state |= TZ_STATE_ACTIVE;
			    if (tz->policy.active.threshold[i]->index > active_index) {
				    active_index = tz->policy.active.threshold[i]->index;
			    }
			}
		}
		tz->policy.state |= active_index;
	}

	/*
	 * Invoke Policy:
	 * --------------
	 * Note that policy must be invoked both when 'going into' a
	 * policy state (e.g. to allow fans to be turned on) and 'going
	 * out of' a policy state (e.g. to allow fans to be turned off);
	 * thus we must preserve the previous state.
	 */
	if (tz->policy.state & TZ_STATE_CRITICAL) {
		tz_policy_critical(tz);
	}
	if ((tz->policy.state & TZ_STATE_PASSIVE) ||
		(previous_state & TZ_STATE_PASSIVE)) {
		tz_policy_passive(tz);
	}
	if ((tz->policy.state & TZ_STATE_ACTIVE) ||
		(previous_state & TZ_STATE_ACTIVE)) {
		tz_policy_active(tz);
	}

	/*
	 * Calculate Sleep Time:
	 * ---------------------
	 * If we're in the passive state, use _TSP's value.  Otherwise
	 * use _TZP or the OS's default polling frequency.  If no polling
	 * frequency is specified then we'll wait forever (that is, until
	 * a thermal event occurs -- e.g. never poll).  Note that _TSP
	 * and _TZD values are given in 1/10th seconds.
	 */
	if (tz->policy.state & TZ_STATE_PASSIVE) {
		sleep_time = tz->policy.passive.tsp * 100;
	}
	else if (tz->policy.polling_freq > 0) {
		sleep_time = tz->policy.polling_freq * 100;
	}
	else {
		sleep_time = WAIT_FOREVER;
	}


	/*
	 * Schedule Next Poll:
	 * -------------------
	 */
	if (sleep_time < WAIT_FOREVER) {
		if (timer_pending(&(tz->policy.timer))) {
			mod_timer(&(tz->policy.timer),
				(HZ*sleep_time)/1000);
		}
		else {
			tz->policy.timer.data = (u32)tz;
			tz->policy.timer.function = tz_policy_run;
			tz->policy.timer.expires =
				jiffies + (HZ*sleep_time)/1000;
			add_timer(&(tz->policy.timer));
		}
	}
	else {
		if (timer_pending(&(tz->policy.timer))) {
			del_timer(&(tz->policy.timer));
		}
	}

	return;
}


/****************************************************************************
 *
 * FUNCTION:    tz_policy_run
 *
 * PARAMETERS:
 *
 * RETURN:
 *
 * DESCRIPTION:
 *
 ****************************************************************************/

void
tz_policy_run (
	unsigned long           data)
{
	ACPI_STATUS             status = AE_OK;

	if (!data) {
		return;
	}

	/*
	 * Defer to Non-Interrupt Level:
	 * -----------------------------
	 * Note that all Linux kernel timers run at interrupt-level (ack!).
	 */
	status = acpi_os_queue_for_execution(OSD_PRIORITY_GPE,
		tz_policy_check, (void*)data);
	if (ACPI_FAILURE(status)) {
	}

	return;
}


/****************************************************************************
 *
 * FUNCTION:    tz_policy_add_device
 *
 * PARAMETERS:
 *
 * RETURN:	
 *
 * DESCRIPTION:
 *
 ****************************************************************************/

ACPI_STATUS
tz_policy_add_device (
	TZ_CONTEXT		*tz)
{
	ACPI_STATUS             status = AE_OK;
	TZ_THRESHOLD            *threshold = NULL;
	u32                     i,j = 0;

	if (!tz) {
		return(AE_BAD_PARAMETER);
	}

	/*
	 * Temperature:
	 * ------------
	 * Make sure we can read the zone's current temperature (_TMP).
	 * If we can't, there's no use in doing any policy (abort).
	 */
	status = tz_get_temperature(tz, &(tz->policy.temperature));
	if (ACPI_FAILURE(status)) {
		return(status);
	}

	/*
	 * Polling Frequency:
	 * ------------------
	 * If a _TZP object doesn't exist, use the OS default polling
	 * frequency.
	 */
	status = bm_evaluate_simple_integer(tz->acpi_handle, "_TZP",
		&(tz->policy.polling_freq));
	if (ACPI_FAILURE(status)) {
		tz->policy.polling_freq = TZP;
	}
	status = AE_OK;

	/*
	 * Get Thresholds:
	 * ---------------
	 * Get all of the zone's thresholds, parse, and organize for
	 * later use.
	 */
	status = tz_get_thresholds(tz, &(tz->policy.threshold_list));
	if (ACPI_FAILURE(status)) {
		return(status);
	}

	/*
	 * Initialize Policies:
	 * --------------------
	 */
	for (i = 0; i < tz->policy.threshold_list.count; i++) {

		threshold = &(tz->policy.threshold_list.thresholds[i]);

		switch (threshold->type) {

		case TZ_THRESHOLD_CRITICAL:
			tz->policy.critical.threshold = threshold;
			break;

		case TZ_THRESHOLD_PASSIVE:

			/*
			 * Set thermal performance limit on all processors
			 * to max.
			 */
			for (j=0; j<threshold->cooling_devices.count; j++) {
				set_performance_limit(
					threshold->cooling_devices.handles[j],
					PR_PERF_MAX);
			}

			/*
			 * Get passive cooling constants.
			 */
			status = bm_evaluate_simple_integer(tz->acpi_handle,
				"_TC1", &(tz->policy.passive.tc1));
			if (ACPI_FAILURE(status)) {
				break;
			}

			status = bm_evaluate_simple_integer(tz->acpi_handle,
				"_TC2", &(tz->policy.passive.tc2));
			if (ACPI_FAILURE(status)) {
				break;
			}

			status = bm_evaluate_simple_integer(tz->acpi_handle,
				"_TSP", &(tz->policy.passive.tsp));
			if (ACPI_FAILURE(status)) {
				break;
			}

			tz->policy.passive.threshold = threshold;

			tz_policy_passive(tz);

			break;

		case TZ_THRESHOLD_ACTIVE:
			tz->policy.active.threshold[threshold->index] = threshold;
			tz_policy_active(tz);
			break;
		}
	}

	if (ACPI_FAILURE(status)) {
		return(status);
	}

	/*
	 * Initialize Policy Timer:
	 * ------------------------
	 * TBD: Linux-specific - remove when policy moves to user-space.
	 */
	init_timer(&(tz->policy.timer));

	/*
	 * Start Policy:
	 * -------------
	 * Run an initial check using this zone's policy.
	 */
	tz_policy_check(tz);

	return(status);
}


/*****************************************************************************
 *
 * FUNCTION:    tz_policy_remove_device
 *
 * PARAMETERS:
 *
 * RETURN:	
 *
 * DESCRIPTION:
 *
 ****************************************************************************/

ACPI_STATUS
tz_policy_remove_device(
	TZ_CONTEXT		*tz)
{
	u32			i = 0;

	if (!tz) {
		return(AE_BAD_PARAMETER);
	}

	/*
	 * Delete the thermal zone policy timer entry, if exists.
	 */
	if (timer_pending(&(tz->policy.timer))) {
		del_timer(&(tz->policy.timer));
	}

	/*
	 * Reset thermal performance limit on all processors back to max.
	 */
	if (tz->policy.passive.threshold) {
		for (i=0; i<tz->policy.passive.threshold->cooling_devices.count; i++) {
			set_performance_limit(
				tz->policy.passive.threshold->cooling_devices.handles[i],
				PR_PERF_MAX);
		}
	}

	return(AE_OK);
}
