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

/*
 *  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)) {
		ACPI_DEBUG_PRINT ((ACPI_DB_INFO, "Device [%02x] is at power state [D%d].\n", device->handle, device->power.state));
	}
	else {
		ACPI_DEBUG_PRINT ((ACPI_DB_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) {
		ACPI_DEBUG_PRINT ((ACPI_DB_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)) {
		ACPI_DEBUG_PRINT ((ACPI_DB_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 _Sx_d:
		 * -------------
		 * 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);
}
