diff --git a/include/zephyr/acpi/acpi.h b/include/zephyr/acpi/acpi.h index 87be59ce9f3e9..9b952c3e6b01f 100644 --- a/include/zephyr/acpi/acpi.h +++ b/include/zephyr/acpi/acpi.h @@ -299,4 +299,19 @@ ACPI_MADT_LOCAL_APIC *acpi_local_apic_get(int cpu_num); */ int acpi_invoke_method(char *path, ACPI_OBJECT_LIST *arg_list, ACPI_OBJECT *ret_obj); -#endif +#if defined(CONFIG_ACPI_POWEROFF) || defined(__DOXYGEN__) +/** + * @brief system level poweroff by setting it to soft off. + * Requires @kconfig{CONFIG_ACPI_POWEROFF} to be enabled. + * + * @return -EINVAL if the pm1_cnt address is not available. + */ +int acpi_poweroff(void); +#else +static inline int acpi_poweroff(void) +{ + return -ENOTSUP; +} +#endif /* CONFIG_ACPI_POWEROFF */ + +#endif /* ZEPHYR_INCLUDE_DRIVERS_ACPI_H_ */ diff --git a/lib/acpi/Kconfig b/lib/acpi/Kconfig index 34710aad92cfe..e4fa24a83eaa7 100644 --- a/lib/acpi/Kconfig +++ b/lib/acpi/Kconfig @@ -34,6 +34,11 @@ config ACPI_SHELL help Enable commands for debugging ACPI using the built-in shell. +config ACPI_POWEROFF + bool "Power off functionality by setting system to S5 power state" + help + Enable support for system power off through ACPI. + config ACPI_DEV_MAX int "maximum child devices" default 1000 diff --git a/lib/acpi/acpi.c b/lib/acpi/acpi.c index cde781c478e86..63d37ce973f7a 100644 --- a/lib/acpi/acpi.c +++ b/lib/acpi/acpi.c @@ -14,6 +14,23 @@ #include LOG_MODULE_REGISTER(ACPI, CONFIG_ACPI_LOG_LEVEL); +#if defined(CONFIG_ACPI_POWEROFF) + +/* PM1_CNT register */ +#if (defined(CONFIG_BOARD_QEMU_X86_64) || defined(CONFIG_BOARD_QEMU_X86)) +#define PM1_CNT_SLP_TYP_S5 0x00 /* S5 SLP_TYP is 0 in QEMU */ +#elif defined(CONFIG_ACRN_COMMON) +#define PM1_CNT_SLP_TYP_S5 0x05 /* S5 SLP_TYP is 5 in ACRN */ +#else +#define PM1_CNT_SLP_TYP_S5 0x07 /* S5 SLP_TYP is 7 in other platforms*/ +#endif + +#define PM1_CNT_SLP_TYP_SHFT 0x0A /* SLP_TYP bits(10-12) in PM1_CNT */ + +#define PM1_CNT_SLP_EN BIT(13) /* Sets SLP_EN bit13 */ + +#endif /* CONFIG_ACPI_POWEROFF */ + static struct { struct acpi_dev child_dev[CONFIG_ACPI_DEV_MAX]; int num_dev; @@ -974,3 +991,34 @@ static int acpi_init(void) exit: return status; } + +#if defined(CONFIG_ACPI_POWEROFF) + +int acpi_poweroff(void) +{ + ACPI_STATUS status; + uintptr_t pm1_cnt_addr; + uint32_t pm1_cnt; + + if (!acpi.early_init) { + status = acpi_early_init(); + if (status) { + LOG_ERR("ACPI early init failed"); + return -ENODEV; + } + } + + if (!AcpiGbl_FADT.Pm1aControlBlock) { + return -EINVAL; + } + + pm1_cnt_addr = AcpiGbl_FADT.Pm1aControlBlock; + + pm1_cnt = sys_in16(pm1_cnt_addr); + pm1_cnt |= ((PM1_CNT_SLP_TYP_S5 << PM1_CNT_SLP_TYP_SHFT) | PM1_CNT_SLP_EN); + sys_out16(pm1_cnt, pm1_cnt_addr); + + return 0; +} + +#endif /* CONFIG_ACPI_POWEROFF */ diff --git a/lib/acpi/acpi_shell.c b/lib/acpi/acpi_shell.c index e8981e2c4a3c3..a2aca6aeeed14 100644 --- a/lib/acpi/acpi_shell.c +++ b/lib/acpi/acpi_shell.c @@ -346,6 +346,16 @@ static int read_table(const struct shell *sh, size_t argc, char **argv) return 0; } +#if defined(CONFIG_ACPI_POWEROFF) + +static void cmd_acpi_poweroff(const struct shell *sh) +{ + if (acpi_poweroff()) { + shell_print(sh, "ACPI poweroff failed due to invalid PM1_CNT address"); + } +} +#endif + SHELL_STATIC_SUBCMD_SET_CREATE( sub_acpi, SHELL_CMD(crs, NULL, @@ -366,6 +376,9 @@ SHELL_STATIC_SUBCMD_SET_CREATE( get_acpi_dev_resource), SHELL_CMD(rd_table, NULL, "read ACPI table (eg: acpi read_table APIC)", read_table), +#if defined(CONFIG_ACPI_POWEROFF) + SHELL_CMD(poweroff, NULL, "poweroff the platform", cmd_acpi_poweroff), +#endif SHELL_SUBCMD_SET_END /* Array terminated. */ ); diff --git a/soc/intel/alder_lake/CMakeLists.txt b/soc/intel/alder_lake/CMakeLists.txt index 918ee2f3e91a1..a33156edec888 100644 --- a/soc/intel/alder_lake/CMakeLists.txt +++ b/soc/intel/alder_lake/CMakeLists.txt @@ -10,4 +10,6 @@ zephyr_cc_option(-march=goldmont) zephyr_library_sources(cpu.c) zephyr_library_sources(../common/soc_gpio.c) +zephyr_library_sources_ifdef(CONFIG_POWEROFF ../common/power.c) + set(SOC_LINKER_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/linker.ld CACHE INTERNAL "") diff --git a/soc/intel/alder_lake/Kconfig b/soc/intel/alder_lake/Kconfig index 1e8808cae13eb..42b44c1011245 100644 --- a/soc/intel/alder_lake/Kconfig +++ b/soc/intel/alder_lake/Kconfig @@ -9,3 +9,4 @@ config SOC_ALDER_LAKE select PCIE_MSI select DYNAMIC_INTERRUPTS select X86_MMU + select HAS_POWEROFF if ACPI_POWEROFF diff --git a/soc/intel/atom/CMakeLists.txt b/soc/intel/atom/CMakeLists.txt index 9bb60341f1bf9..c49e5c0203b00 100644 --- a/soc/intel/atom/CMakeLists.txt +++ b/soc/intel/atom/CMakeLists.txt @@ -3,4 +3,5 @@ zephyr_include_directories(.) +zephyr_library_sources_ifdef(CONFIG_POWEROFF ../common/power.c) set(SOC_LINKER_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/linker.ld CACHE INTERNAL "") diff --git a/soc/intel/atom/Kconfig b/soc/intel/atom/Kconfig index bc0d509eb709d..bb4a764d8ca8f 100644 --- a/soc/intel/atom/Kconfig +++ b/soc/intel/atom/Kconfig @@ -4,5 +4,6 @@ config SOC_ATOM select X86 select CPU_ATOM + select HAS_POWEROFF if ACPI_POWEROFF imply X86_MMU select ARCH_HAS_RESERVED_PAGE_FRAMES diff --git a/soc/intel/common/power.c b/soc/intel/common/power.c new file mode 100644 index 0000000000000..35e5d88213202 --- /dev/null +++ b/soc/intel/common/power.c @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2025 Intel Corporation. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +void z_sys_poweroff(void) +{ +#if defined(CONFIG_ACPI_POWEROFF) + acpi_poweroff(); +#endif + CODE_UNREACHABLE; +}