/*****************************************************************************
 *
 * Module Name: bmpm.c
 *   $Revision: 10 $
 *
 *****************************************************************************/

/*
 *  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
 */


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


#define _COMPONENT		ACPI_BUS
	MODULE_NAME		("bmpm")


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

/****************************************************************************
 *
 * FUNCTION:    bm_get_inferred_power_state
 *
 * PARAMETERS:  
 *
 * RETURN:      
 *
 * DESCRIPTION: 
 *
 ****************************************************************************/

ACPI_STATUS
bm_get_inferred_power_state (
	BM_DEVICE               *device)
{
	ACPI_STATUS             status = AE_OK;
	BM_HANDLE_LIST          pr_list;
	BM_POWER_STATE          list_state = ACPI_STATE_UNKNOWN;
	char                    object_name[5] = {'_','P','R','0','\0'};
	u32                     i = 0;

	FUNCTION_TRACE("bm_get_inferred_power_state");

	if (!device) {
		return_ACPI_STATUS(AE_BAD_PARAMETER);
	}

	MEMSET(&pr_list, 0, sizeof(BM_HANDLE_LIST));

	device->power.state = ACPI_STATE_D3;

	/*
	 * Calculate Power State:
	 * ----------------------
	 * Try to infer the devices's power state by checking the state of 
	 * the devices's power resources.  We start by evaluating _PR0
	 * (resource requirements at D0) and work through _PR1 and _PR2.
	 * We know the current devices power state when all resources (for 
	 * a give Dx state) are ON.  If no power resources are on then the 
	 * device is assumed to be off (D3).
	 */
	for (i=ACPI_STATE_D0; i<ACPI_STATE_D3; i++) {

		object_name[3] = '0' + i;

		status = bm_evaluate_reference_list(device->acpi_handle, 
			object_name, &pr_list);

		if (ACPI_SUCCESS(status)) {

			status = bm_pr_list_get_state(&pr_list, &list_state);

			if (ACPI_SUCCESS(status)) {

				if (list_state == ACPI_STATE_D0) {
					device->power.state = i;
					break;
				}
			}
		}
	}

	return_ACPI_STATUS(AE_OK);
}


/****************************************************************************
 *                             External Functions
 ****************************************************************************/

/****************************************************************************
 *
 * FUNCTION:    bm_get_power_state
 *
 * PARAMETERS:  
 *
 * RETURN:      
 *
 * DESCRIPTION: 
 *
 ****************************************************************************/

ACPI_STATUS
bm_get_power_state (
	BM_NODE			*node)
{
	ACPI_STATUS             status = AE_OK;
	BM_DEVICE               *device = NULL;

	FUNCTION_TRACE("bm_get_power_state");

	if (!node || !node->parent) {
		return_ACPI_STATUS(AE_BAD_PARAMETER);
	}

	device = &(node->device);

	device->power.state = ACPI_STATE_UNKNOWN;

	/*
	 * Power Control?
	 * --------------
	 * If this device isn't directly power manageable (e.g. doesn't 
	 * include _PR0/_PS0) then there's nothing to do (state is static).
	 */
	if (!BM_IS_POWER_CONTROL(device)) {
		return_ACPI_STATUS(AE_OK);
	}

	/*
	 * Parent Present?
	 * ---------------
	 * Make sure the parent is present before mucking with the child.
	 */
	if (!BM_NODE_PRESENT(node->parent)) {
		return_ACPI_STATUS(AE_NOT_EXIST);
	}
	
	/*
	 * Get Power State:
	 * ----------------
	 * Either directly (via _PSC) or inferred (via power resource
	 * dependencies).
	 */
	if (BM_IS_POWER_STATE(device)) {
		status = bm_evaluate_simple_integer(device->acpi_handle, 
			"_PSC", &(device->power.state));
	}
	else {
		status = bm_get_inferred_power_state(device);
	}

	if (ACPI_SUCCESS(status)) {
		DEBUG_PRINT(ACPI_INFO, ("Device [%02x] is at power state [D%d].\n", device->handle, device->power.state));
	}
	else {
		DEBUG_PRINT(ACPI_INFO, ("Error getting power state for device [%02x]\n", device->handle));
	}

	return_ACPI_STATUS(status);
}


/****************************************************************************
 *
 * FUNCTION:    bm_set_power_state
 *
 * PARAMETERS:  
 *
 * RETURN:      
 *
 * DESCRIPTION: 
 *
 ****************************************************************************/

ACPI_STATUS
bm_set_power_state (
	BM_NODE			*node,
	BM_POWER_STATE          state)
{
	ACPI_STATUS             status = AE_OK;
	BM_DEVICE		*device = NULL;
	BM_DEVICE		*parent_device = NULL;
	BM_HANDLE_LIST          current_list;
	BM_HANDLE_LIST          target_list;
	char                    object_name[5] = {'_','P','R','0','\0'};

	FUNCTION_TRACE("bm_set_power_state");

	if (!node || !node->parent || (state > ACPI_STATE_D3)) {
		return_ACPI_STATUS(AE_BAD_PARAMETER);
	}

	MEMSET(&current_list, 0, sizeof(BM_HANDLE_LIST));
	MEMSET(&target_list, 0, sizeof(BM_HANDLE_LIST));

	device = &(node->device);
	parent_device = &(node->parent->device);

	/*
	 * Power Control?
	 * --------------
	 * If this device isn't directly power manageable (e.g. doesn't 
	 * include _PR0/_PS0) then return an error (can't set state).
	 */
	if (!BM_IS_POWER_CONTROL(device)) {
		return_ACPI_STATUS(AE_ERROR);
	}

	/*
	 * Parent Present?
	 * ---------------
	 * Make sure the parent is present before mucking with the child.
	 */
	if (!BM_NODE_PRESENT(node->parent)) {
		return_ACPI_STATUS(AE_NOT_EXIST);
	}
	
	/*
	 * Check Parent's Power State:
	 * ---------------------------
	 * Can't be in a higher power state (lower Dx value) than parent.
	 */
	if (state < parent_device->power.state) {
		DEBUG_PRINT(ACPI_WARN, ("Cannot set device [%02x] to a higher-powered state than parent_device.\n", device->handle));
		return_ACPI_STATUS(AE_ERROR);
	}

	/*
	 * Get Resources:
	 * --------------
	 * Get the power resources associated with the device's current 
	 * and target power states.
	 */
	if (device->power.state != ACPI_STATE_UNKNOWN) {
		object_name[3] = '0' + device->power.state;
		bm_evaluate_reference_list(device->acpi_handle, 
			object_name, &current_list);
	}

	object_name[3] = '0' + state;
	bm_evaluate_reference_list(device->acpi_handle, object_name, 
		&target_list);

	/*
	 * Transition Resources:
	 * ---------------------
	 * Transition all power resources referenced by this device to 
	 * the correct power state (taking into consideration sequencing 
	 * and dependencies to other devices).
	 */
	if (current_list.count || target_list.count) {
		status = bm_pr_list_transition(&current_list, &target_list);
	}
	if (ACPI_FAILURE(status)) {
		return_ACPI_STATUS(status);
	}

	/*
	 * Execute _PSx:
	 * -------------
	 * Execute the _PSx method corresponding to the target Dx state, 
	 * if it exists.
	 */
	object_name[2] = 'S';
	object_name[3] = '0' + state;
	bm_evaluate_object(device->acpi_handle, object_name, NULL, NULL);

	if (ACPI_SUCCESS(status)) {
		DEBUG_PRINT(ACPI_INFO, ("Device [%02x] is now at [D%d].\n", device->handle, state));
		device->power.state = state;
	}

	return_ACPI_STATUS(status);
}


/****************************************************************************
 *
 * FUNCTION:    bm_get_pm_capabilities
 *
 * PARAMETERS:  
 *
 * RETURN:      
 *
 * DESCRIPTION: 
 *
 ****************************************************************************/

ACPI_STATUS
bm_get_pm_capabilities (
	BM_NODE			*node)
{
	ACPI_STATUS             status = AE_OK;
	BM_DEVICE		*device = NULL;
	BM_DEVICE		*parent_device = NULL;
	ACPI_HANDLE             acpi_handle = NULL;
	BM_POWER_STATE          dx_supported = ACPI_STATE_UNKNOWN;
	char                    object_name[5] = {'_','S','0','D','\0'};
	u32                     i = 0;

	FUNCTION_TRACE("bm_get_pm_capabilities");

	if (!node || !node->parent) {
		return_ACPI_STATUS(AE_BAD_PARAMETER);
	}

	device = &(node->device);
	parent_device = &(node->parent->device);

	/*
	 * Power Management Flags:
	 * -----------------------
	 */
	if (ACPI_SUCCESS(acpi_get_handle(device->acpi_handle, "_PSC", 
		&acpi_handle))) {
		device->power.flags |= BM_FLAGS_POWER_STATE;
	}

	if (ACPI_SUCCESS(acpi_get_handle(device->acpi_handle, "_IRC", 
		&acpi_handle))) {
		device->power.flags |= BM_FLAGS_INRUSH_CURRENT;
	}

	if (ACPI_SUCCESS(acpi_get_handle(device->acpi_handle, "_PRW", 
		&acpi_handle))) {
		device->power.flags |= BM_FLAGS_WAKE_CAPABLE;
	}

	/*
	 * Device Power State:
	 * -------------------
	 * Note that we can't get the device's power state until we've
	 * initialized all power resources, so for now we just set to
	 * unknown.
	 */
	device->power.state = ACPI_STATE_UNKNOWN;

	/*
	 * Dx Supported in S0:
	 * -------------------
	 * Figure out which Dx states are supported by this device for the
	 * S0 (working) state.  Note that D0 and D3 are required (assumed).
	 */
	device->power.dx_supported[ACPI_STATE_S0] = BM_FLAGS_D0_SUPPORT | 
		BM_FLAGS_D3_SUPPORT;

	if ((ACPI_SUCCESS(acpi_get_handle(device->acpi_handle, "_PR1", 
		&acpi_handle))) || 
		(ACPI_SUCCESS(acpi_get_handle(device->acpi_handle, "_PS1", 
		&acpi_handle)))) {
		device->power.dx_supported[ACPI_STATE_S0] |= 
			BM_FLAGS_D1_SUPPORT;
	}

	if ((ACPI_SUCCESS(acpi_get_handle(device->acpi_handle, "_PR2", 
		&acpi_handle))) || 
		(ACPI_SUCCESS(acpi_get_handle(device->acpi_handle, "_PS2", 
		&acpi_handle)))) {
		device->power.dx_supported[ACPI_STATE_S0] |= 
			BM_FLAGS_D2_SUPPORT;
	}

	/*
	 * Dx Supported in S1-S5:
	 * ----------------------
	 * Figure out which Dx states are supported by this device for
	 * all other Sx states.
	 */
	for (i = ACPI_STATE_S1; i <= ACPI_STATE_S5; i++) {

		/*
		 * D3 support is assumed (off is always possible!).
		 */
		device->power.dx_supported[i] = BM_FLAGS_D3_SUPPORT;

		/*
		 * Evalute _SxD:
		 * -------------
		 * Which returns the highest (power) Dx state supported in 
		 * this system (Sx) state.  We convert this value to a bit 
		 * mask of supported states (conceptually simpler).
		 */
		status = bm_evaluate_simple_integer(device->acpi_handle, 
			object_name, &dx_supported);
		if (ACPI_SUCCESS(status)) {
			switch (dx_supported) {
			case 0:
				device->power.dx_supported[i] |= 
					BM_FLAGS_D0_SUPPORT;
				/* fall through */
			case 1:
				device->power.dx_supported[i] |= 
					BM_FLAGS_D1_SUPPORT;
				/* fall through */
			case 2:
				device->power.dx_supported[i] |= 
					BM_FLAGS_D2_SUPPORT;
				/* fall through */
			case 3:
				device->power.dx_supported[i] |= 
					BM_FLAGS_D3_SUPPORT;
				break;
			}

			/*
			 * Validate:
			 * ---------
			 * Mask of any states that _Sx_d falsely advertises 
			 * (e.g.claims D1 support but neither _PR2 or _PS2 
			 * exist).  In other words, S1-S5 can't offer a Dx 
			 * state that isn't supported by S0.
			 */
			device->power.dx_supported[i] &= 
				device->power.dx_supported[ACPI_STATE_S0];
		}

		object_name[2]++;
	}

	return_ACPI_STATUS(AE_OK);
}
