pm: device: De-couple device pm from system pm

PM_DEVICE is not attached to system managed device power management.
It is a very common use case targets with device runtime power
management that don't want system device power management enabled.

We introduce a new symbol (PM_DEVICE_SYSTEM_MANAGED) to explicit
control whether or not system device power management should be
globally enabled.

Signed-off-by: Flavio Ceolin <flavio.ceolin@intel.com>
diff --git a/subsys/pm/CMakeLists.txt b/subsys/pm/CMakeLists.txt
index a9aba72..0e961ab 100644
--- a/subsys/pm/CMakeLists.txt
+++ b/subsys/pm/CMakeLists.txt
@@ -8,3 +8,4 @@
 zephyr_sources_ifdef(CONFIG_PM_DEVICE device.c)
 zephyr_sources_ifdef(CONFIG_PM_DEVICE_RUNTIME device_runtime.c)
 zephyr_sources_ifdef(CONFIG_PM_DEVICE_SHELL pm_shell.c)
+zephyr_sources_ifdef(CONFIG_PM_DEVICE_SYSTEM_MANAGED device_system_managed.c)
diff --git a/subsys/pm/Kconfig b/subsys/pm/Kconfig
index 5caf01f..2b7ef99 100644
--- a/subsys/pm/Kconfig
+++ b/subsys/pm/Kconfig
@@ -78,12 +78,11 @@
 	bool "Device Power Management"
 	help
 	  This option enables the device power management interface.  The
-	  interface consists of hook functions implemented by device drivers
-	  that get called by the power manager application when the system
-	  is going to suspend state or resuming from suspend state. This allows
-	  device drivers to do any necessary power management operations
-	  like turning off device clocks and peripherals. The device drivers
-	  may also save and restore states in these hook functions.
+	  interface implemented by device drivers are called by the power
+	  management subsystem. This allows device drivers to do any
+	  necessary power management operations like turning off
+	  device clocks and peripherals. Device drivers may also save
+	  and restore states in these hook functions.
 
 if PM_DEVICE
 
@@ -137,6 +136,18 @@
 	  Enable the device power management shell, for triggering device power
 	  management events through the shell interface.
 
+config PM_DEVICE_SYSTEM_MANAGED
+	bool "System-Managed Device Power Management"
+	default y if !PM_DEVICE_RUNTIME_EXCLUSIVE
+	default y if !PM_DEVICE_RUNTIME
+	help
+	  This option enables the system-managed device power
+	  management.  The power management subsystem will suspend
+	  devices before entering a low power state. Conversely, after
+	  the core wakes up from low power mode all suspended devices
+	  are resumed.
+
+
 endif # PM_DEVICE
 
 endmenu
diff --git a/subsys/pm/device_system_managed.c b/subsys/pm/device_system_managed.c
new file mode 100644
index 0000000..6507267
--- /dev/null
+++ b/subsys/pm/device_system_managed.c
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2024 Intel Corporation.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <zephyr/kernel.h>
+#include <zephyr/pm/device.h>
+#include <zephyr/pm/device_runtime.h>
+
+#include <zephyr/logging/log.h>
+LOG_MODULE_DECLARE(pm_device, CONFIG_PM_DEVICE_LOG_LEVEL);
+
+#define DT_PM_DEVICE_ENABLED(node_id)					\
+	COND_CODE_1(DT_PROP(node_id, zephyr_pm_device_disabled),	\
+		(), (1 +))
+
+#define DT_PM_DEVICE_NEEDED			\
+	(DT_FOREACH_STATUS_OKAY(zephyr_power_state, DT_PM_DEVICE_ENABLED) 0)
+
+#if DT_PM_DEVICE_NEEDED
+TYPE_SECTION_START_EXTERN(const struct device *, pm_device_slots);
+
+/* Number of devices successfully suspended. */
+static size_t num_susp;
+
+bool pm_suspend_devices(void)
+{
+	const struct device *devs;
+	size_t devc;
+
+	devc = z_device_get_all_static(&devs);
+
+	num_susp = 0;
+
+	for (const struct device *dev = devs + devc - 1; dev >= devs; dev--) {
+		int ret;
+
+		/*
+		 * Ignore uninitialized devices, busy devices, wake up sources, and
+		 * devices with runtime PM enabled.
+		 */
+		if (!device_is_ready(dev) || pm_device_is_busy(dev) ||
+		    pm_device_wakeup_is_enabled(dev) ||
+		    pm_device_runtime_is_enabled(dev)) {
+			continue;
+		}
+
+		ret = pm_device_action_run(dev, PM_DEVICE_ACTION_SUSPEND);
+		/* ignore devices not supporting or already at the given state */
+		if ((ret == -ENOSYS) || (ret == -ENOTSUP) || (ret == -EALREADY)) {
+			continue;
+		} else if (ret < 0) {
+			LOG_ERR("Device %s did not enter %s state (%d)",
+				dev->name,
+				pm_device_state_str(PM_DEVICE_STATE_SUSPENDED),
+				ret);
+			return false;
+		}
+
+		TYPE_SECTION_START(pm_device_slots)[num_susp] = dev;
+		num_susp++;
+	}
+
+	return true;
+}
+
+void pm_resume_devices(void)
+{
+	for (int i = (num_susp - 1); i >= 0; i--) {
+		pm_device_action_run(TYPE_SECTION_START(pm_device_slots)[i],
+				    PM_DEVICE_ACTION_RESUME);
+	}
+
+	num_susp = 0;
+}
+
+#else /* !DT_PM_DEVICE_NEEDED */
+
+void pm_resume_devices(void)
+{
+}
+
+bool pm_suspend_devices(void)
+{
+	return true;
+}
+
+#endif
diff --git a/subsys/pm/device_system_managed.h b/subsys/pm/device_system_managed.h
new file mode 100644
index 0000000..6c2e9b9
--- /dev/null
+++ b/subsys/pm/device_system_managed.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2024 Intel Corporation.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#ifndef ZEPHYR_SUBSYS_PM_DEVICE_SYSTEM_MANAGED_H_
+#define ZEPHYR_SUBSYS_PM_DEVICE_SYSTEM_MANAGED_H_
+
+#ifdef CONFIG_PM_DEVICE_SYSTEM_MANAGED
+
+bool pm_suspend_devices(void);
+void pm_resume_devices(void);
+
+#else
+
+bool pm_resume_devices(void) { return true; }
+void pm_suspend_devices(void) {}
+
+#endif /* CONFIG_PM_DEVICE_SYSTEM_MANAGED */
+
+#endif /* ZEPHYR_SUBSYS_PM_DEVICE_SYSTEM_MANAGED_H_ */
diff --git a/subsys/pm/pm.c b/subsys/pm/pm.c
index 643a47b..25e60b6 100644
--- a/subsys/pm/pm.c
+++ b/subsys/pm/pm.c
@@ -18,6 +18,7 @@
 #include <zephyr/tracing/tracing.h>
 
 #include "pm_stats.h"
+#include "device_system_managed.h"
 
 #include <zephyr/logging/log.h>
 LOG_MODULE_REGISTER(pm, CONFIG_PM_LOG_LEVEL);
@@ -42,72 +43,6 @@
 static struct k_spinlock pm_forced_state_lock;
 static struct k_spinlock pm_notifier_lock;
 
-#define DT_PM_DEVICE_ENABLED(node_id)					\
-	COND_CODE_1(DT_PROP_OR(node_id, zephyr_pm_device_disabled, 0),	\
-		(), (1 +))
-
-#define DT_PM_DEVICE_NEEDED			\
-	(DT_FOREACH_STATUS_OKAY(zephyr_power_state, DT_PM_DEVICE_ENABLED) 0)
-
-#if defined(CONFIG_PM_DEVICE) && DT_PM_DEVICE_NEEDED
-TYPE_SECTION_START_EXTERN(const struct device *, pm_device_slots);
-
-#if !defined(CONFIG_PM_DEVICE_RUNTIME_EXCLUSIVE)
-/* Number of devices successfully suspended. */
-static size_t num_susp;
-
-static int pm_suspend_devices(void)
-{
-	const struct device *devs;
-	size_t devc;
-
-	devc = z_device_get_all_static(&devs);
-
-	num_susp = 0;
-
-	for (const struct device *dev = devs + devc - 1; dev >= devs; dev--) {
-		int ret;
-
-		/*
-		 * Ignore uninitialized devices, busy devices, wake up sources, and
-		 * devices with runtime PM enabled.
-		 */
-		if (!device_is_ready(dev) || pm_device_is_busy(dev) ||
-		    pm_device_wakeup_is_enabled(dev) ||
-		    pm_device_runtime_is_enabled(dev)) {
-			continue;
-		}
-
-		ret = pm_device_action_run(dev, PM_DEVICE_ACTION_SUSPEND);
-		/* ignore devices not supporting or already at the given state */
-		if ((ret == -ENOSYS) || (ret == -ENOTSUP) || (ret == -EALREADY)) {
-			continue;
-		} else if (ret < 0) {
-			LOG_ERR("Device %s did not enter %s state (%d)",
-				dev->name,
-				pm_device_state_str(PM_DEVICE_STATE_SUSPENDED),
-				ret);
-			return ret;
-		}
-
-		TYPE_SECTION_START(pm_device_slots)[num_susp] = dev;
-		num_susp++;
-	}
-
-	return 0;
-}
-
-static void pm_resume_devices(void)
-{
-	for (int i = (num_susp - 1); i >= 0; i--) {
-		pm_device_action_run(TYPE_SECTION_START(pm_device_slots)[i],
-				    PM_DEVICE_ACTION_RESUME);
-	}
-
-	num_susp = 0;
-}
-#endif	/* defined(CONFIG_PM_DEVICE) && DT_PM_DEVICE_NEEDED */
-
 /*
  * Function called to notify when the system is entering / exiting a
  * power state
@@ -150,9 +85,12 @@
 	 * and it may schedule another thread.
 	 */
 	if (atomic_test_and_clear_bit(z_post_ops_required, id)) {
-#if defined(CONFIG_PM_DEVICE) && DT_PM_DEVICE_NEEDED
+#ifdef CONFIG_PM_DEVICE_SYSTEM_MANAGED
 		if (atomic_add(&_cpus_active, 1) == 0) {
-			pm_resume_devices();
+			if ((z_cpus_pm_state[id].state != PM_STATE_RUNTIME_IDLE) &&
+					!z_cpus_pm_state[id].pm_device_disabled) {
+				pm_resume_devices();
+			}
 		}
 #endif
 		pm_state_exit_post_ops(z_cpus_pm_state[id].state, z_cpus_pm_state[id].substate_id);
@@ -209,11 +147,11 @@
 		return false;
 	}
 
-#if defined(CONFIG_PM_DEVICE) && DT_PM_DEVICE_NEEDED
+#ifdef CONFIG_PM_DEVICE_SYSTEM_MANAGED
 	if (atomic_sub(&_cpus_active, 1) == 1) {
 		if ((z_cpus_pm_state[id].state != PM_STATE_RUNTIME_IDLE) &&
 		    !z_cpus_pm_state[id].pm_device_disabled) {
-			if (pm_suspend_devices()) {
+			if (!pm_suspend_devices()) {
 				pm_resume_devices();
 				z_cpus_pm_state[id].state = PM_STATE_ACTIVE;
 				(void)atomic_add(&_cpus_active, 1);