diff options
Diffstat (limited to 'drivers/acpi/acpica/hwsleep.c')
-rw-r--r-- | drivers/acpi/acpica/hwsleep.c | 318 |
1 files changed, 318 insertions, 0 deletions
diff --git a/drivers/acpi/acpica/hwsleep.c b/drivers/acpi/acpica/hwsleep.c new file mode 100644 index 0000000000..e0921f08b7 --- /dev/null +++ b/drivers/acpi/acpica/hwsleep.c @@ -0,0 +1,318 @@ +// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 +/****************************************************************************** + * + * Name: hwsleep.c - ACPI Hardware Sleep/Wake Support functions for the + * original/legacy sleep/PM registers. + * + * Copyright (C) 2000 - 2023, Intel Corp. + * + *****************************************************************************/ + +#include <acpi/acpi.h> +#include "accommon.h" + +#define _COMPONENT ACPI_HARDWARE +ACPI_MODULE_NAME("hwsleep") + +#if (!ACPI_REDUCED_HARDWARE) /* Entire module */ +/******************************************************************************* + * + * FUNCTION: acpi_hw_legacy_sleep + * + * PARAMETERS: sleep_state - Which sleep state to enter + * + * RETURN: Status + * + * DESCRIPTION: Enter a system sleep state via the legacy FADT PM registers + * THIS FUNCTION MUST BE CALLED WITH INTERRUPTS DISABLED + * + ******************************************************************************/ +acpi_status acpi_hw_legacy_sleep(u8 sleep_state) +{ + struct acpi_bit_register_info *sleep_type_reg_info; + struct acpi_bit_register_info *sleep_enable_reg_info; + u32 pm1a_control; + u32 pm1b_control; + u32 in_value; + acpi_status status; + + ACPI_FUNCTION_TRACE(hw_legacy_sleep); + + sleep_type_reg_info = + acpi_hw_get_bit_register_info(ACPI_BITREG_SLEEP_TYPE); + sleep_enable_reg_info = + acpi_hw_get_bit_register_info(ACPI_BITREG_SLEEP_ENABLE); + + /* Clear wake status */ + + status = acpi_write_bit_register(ACPI_BITREG_WAKE_STATUS, + ACPI_CLEAR_STATUS); + if (ACPI_FAILURE(status)) { + return_ACPI_STATUS(status); + } + + /* Disable all GPEs */ + status = acpi_hw_disable_all_gpes(); + if (ACPI_FAILURE(status)) { + return_ACPI_STATUS(status); + } + status = acpi_hw_clear_acpi_status(); + if (ACPI_FAILURE(status)) { + return_ACPI_STATUS(status); + } + acpi_gbl_system_awake_and_running = FALSE; + + /* Enable all wakeup GPEs */ + status = acpi_hw_enable_all_wakeup_gpes(); + if (ACPI_FAILURE(status)) { + return_ACPI_STATUS(status); + } + + /* Get current value of PM1A control */ + + status = acpi_hw_register_read(ACPI_REGISTER_PM1_CONTROL, + &pm1a_control); + if (ACPI_FAILURE(status)) { + return_ACPI_STATUS(status); + } + ACPI_DEBUG_PRINT((ACPI_DB_INIT, + "Entering sleep state [S%u]\n", sleep_state)); + + /* Clear the SLP_EN and SLP_TYP fields */ + + pm1a_control &= ~(sleep_type_reg_info->access_bit_mask | + sleep_enable_reg_info->access_bit_mask); + pm1b_control = pm1a_control; + + /* Insert the SLP_TYP bits */ + + pm1a_control |= + (acpi_gbl_sleep_type_a << sleep_type_reg_info->bit_position); + pm1b_control |= + (acpi_gbl_sleep_type_b << sleep_type_reg_info->bit_position); + + /* + * We split the writes of SLP_TYP and SLP_EN to workaround + * poorly implemented hardware. + */ + + /* Write #1: write the SLP_TYP data to the PM1 Control registers */ + + status = acpi_hw_write_pm1_control(pm1a_control, pm1b_control); + if (ACPI_FAILURE(status)) { + return_ACPI_STATUS(status); + } + + /* Insert the sleep enable (SLP_EN) bit */ + + pm1a_control |= sleep_enable_reg_info->access_bit_mask; + pm1b_control |= sleep_enable_reg_info->access_bit_mask; + + /* Flush caches, as per ACPI specification */ + + if (sleep_state < ACPI_STATE_S4) { + ACPI_FLUSH_CPU_CACHE(); + } + + status = acpi_os_enter_sleep(sleep_state, pm1a_control, pm1b_control); + if (status == AE_CTRL_TERMINATE) { + return_ACPI_STATUS(AE_OK); + } + if (ACPI_FAILURE(status)) { + return_ACPI_STATUS(status); + } + + /* Write #2: Write both SLP_TYP + SLP_EN */ + + status = acpi_hw_write_pm1_control(pm1a_control, pm1b_control); + if (ACPI_FAILURE(status)) { + return_ACPI_STATUS(status); + } + + if (sleep_state > ACPI_STATE_S3) { + /* + * We wanted to sleep > S3, but it didn't happen (by virtue of the + * fact that we are still executing!) + * + * Wait ten seconds, then try again. This is to get S4/S5 to work on + * all machines. + * + * We wait so long to allow chipsets that poll this reg very slowly + * to still read the right value. Ideally, this block would go + * away entirely. + */ + acpi_os_stall(10 * ACPI_USEC_PER_SEC); + + status = acpi_hw_register_write(ACPI_REGISTER_PM1_CONTROL, + sleep_enable_reg_info-> + access_bit_mask); + if (ACPI_FAILURE(status)) { + return_ACPI_STATUS(status); + } + } + + /* Wait for transition back to Working State */ + + do { + status = + acpi_read_bit_register(ACPI_BITREG_WAKE_STATUS, &in_value); + if (ACPI_FAILURE(status)) { + return_ACPI_STATUS(status); + } + + } while (!in_value); + + return_ACPI_STATUS(AE_OK); +} + +/******************************************************************************* + * + * FUNCTION: acpi_hw_legacy_wake_prep + * + * PARAMETERS: sleep_state - Which sleep state we just exited + * + * RETURN: Status + * + * DESCRIPTION: Perform the first state of OS-independent ACPI cleanup after a + * sleep. + * Called with interrupts ENABLED. + * + ******************************************************************************/ + +acpi_status acpi_hw_legacy_wake_prep(u8 sleep_state) +{ + acpi_status status = AE_OK; + struct acpi_bit_register_info *sleep_type_reg_info; + struct acpi_bit_register_info *sleep_enable_reg_info; + u32 pm1a_control; + u32 pm1b_control; + + ACPI_FUNCTION_TRACE(hw_legacy_wake_prep); + + /* + * Set SLP_TYPE and SLP_EN to state S0. + * This is unclear from the ACPI Spec, but it is required + * by some machines. + */ + if (acpi_gbl_sleep_type_a_s0 != ACPI_SLEEP_TYPE_INVALID) { + sleep_type_reg_info = + acpi_hw_get_bit_register_info(ACPI_BITREG_SLEEP_TYPE); + sleep_enable_reg_info = + acpi_hw_get_bit_register_info(ACPI_BITREG_SLEEP_ENABLE); + + /* Get current value of PM1A control */ + + status = acpi_hw_register_read(ACPI_REGISTER_PM1_CONTROL, + &pm1a_control); + if (ACPI_SUCCESS(status)) { + + /* Clear the SLP_EN and SLP_TYP fields */ + + pm1a_control &= ~(sleep_type_reg_info->access_bit_mask | + sleep_enable_reg_info-> + access_bit_mask); + pm1b_control = pm1a_control; + + /* Insert the SLP_TYP bits */ + + pm1a_control |= (acpi_gbl_sleep_type_a_s0 << + sleep_type_reg_info->bit_position); + pm1b_control |= (acpi_gbl_sleep_type_b_s0 << + sleep_type_reg_info->bit_position); + + /* Write the control registers and ignore any errors */ + + (void)acpi_hw_write_pm1_control(pm1a_control, + pm1b_control); + } + } + + return_ACPI_STATUS(status); +} + +/******************************************************************************* + * + * FUNCTION: acpi_hw_legacy_wake + * + * PARAMETERS: sleep_state - Which sleep state we just exited + * + * RETURN: Status + * + * DESCRIPTION: Perform OS-independent ACPI cleanup after a sleep + * Called with interrupts ENABLED. + * + ******************************************************************************/ + +acpi_status acpi_hw_legacy_wake(u8 sleep_state) +{ + acpi_status status; + + ACPI_FUNCTION_TRACE(hw_legacy_wake); + + /* Ensure enter_sleep_state_prep -> enter_sleep_state ordering */ + + acpi_gbl_sleep_type_a = ACPI_SLEEP_TYPE_INVALID; + acpi_hw_execute_sleep_method(METHOD_PATHNAME__SST, ACPI_SST_WAKING); + + /* + * GPEs must be enabled before _WAK is called as GPEs + * might get fired there + * + * Restore the GPEs: + * 1) Disable all GPEs + * 2) Enable all runtime GPEs + */ + status = acpi_hw_disable_all_gpes(); + if (ACPI_FAILURE(status)) { + return_ACPI_STATUS(status); + } + + status = acpi_hw_enable_all_runtime_gpes(); + if (ACPI_FAILURE(status)) { + return_ACPI_STATUS(status); + } + + /* + * Now we can execute _WAK, etc. Some machines require that the GPEs + * are enabled before the wake methods are executed. + */ + acpi_hw_execute_sleep_method(METHOD_PATHNAME__WAK, sleep_state); + + /* + * Some BIOS code assumes that WAK_STS will be cleared on resume + * and use it to determine whether the system is rebooting or + * resuming. Clear WAK_STS for compatibility. + */ + (void)acpi_write_bit_register(ACPI_BITREG_WAKE_STATUS, + ACPI_CLEAR_STATUS); + acpi_gbl_system_awake_and_running = TRUE; + + /* Enable power button */ + + (void) + acpi_write_bit_register(acpi_gbl_fixed_event_info + [ACPI_EVENT_POWER_BUTTON]. + enable_register_id, ACPI_ENABLE_EVENT); + + (void) + acpi_write_bit_register(acpi_gbl_fixed_event_info + [ACPI_EVENT_POWER_BUTTON]. + status_register_id, ACPI_CLEAR_STATUS); + + /* Enable sleep button */ + + (void) + acpi_write_bit_register(acpi_gbl_fixed_event_info + [ACPI_EVENT_SLEEP_BUTTON]. + enable_register_id, ACPI_ENABLE_EVENT); + + (void) + acpi_write_bit_register(acpi_gbl_fixed_event_info + [ACPI_EVENT_SLEEP_BUTTON]. + status_register_id, ACPI_CLEAR_STATUS); + + acpi_hw_execute_sleep_method(METHOD_PATHNAME__SST, ACPI_SST_WORKING); + return_ACPI_STATUS(status); +} + +#endif /* !ACPI_REDUCED_HARDWARE */ |