From 77829b42d01a05057a615f71e6c2cb8a36aa390a Mon Sep 17 00:00:00 2001 From: Bjarki Arge Andreasen Date: Tue, 3 Jun 2025 17:45:33 +0200 Subject: [PATCH 1/5] pm: device: extend pm_device_driver_init() description Rewrite the docstring for pm_device_driver_init() to align with documentation, especially covering the case where CONFIG_PM_DEVICE=n Signed-off-by: Bjarki Arge Andreasen --- include/zephyr/pm/device.h | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/include/zephyr/pm/device.h b/include/zephyr/pm/device.h index 5badda9fa68f3..af47b1a61e9d1 100644 --- a/include/zephyr/pm/device.h +++ b/include/zephyr/pm/device.h @@ -622,13 +622,22 @@ int pm_device_power_domain_remove(const struct device *dev, bool pm_device_is_powered(const struct device *dev); /** - * @brief Setup a device driver into the lowest valid power mode + * @brief Move a device driver into its initial device power state. * - * This helper function is intended to be called at the end of a driver - * init function to automatically setup the device into the lowest power - * mode. It assumes that the device has been configured as if it is in - * @ref PM_DEVICE_STATE_OFF, or @ref PM_DEVICE_STATE_SUSPENDED if device can - * never be powered off. + * @details This function uses the device driver's internal PM hook to + * move the device from the OFF state to the initial power state expected + * by the system. + * + * The initial power state expected by the system is: + * + * - ACTIVE if CONFIG_PM_DEVICE=n or (CONFIG_PM_DEVICE=y and + * CONFIG_PM_DEVICE_RUNTIME=n) or (CONFIG_PM_DEVICE_RUNTIME=y and + * !pm_device_runtime_is_enabled(dev)). + * - SUSPENDED if CONFIG_PM_DEVICE_RUNTIME=y and device's parent power domain is ACTIVE. + * - OFF if CONFIG_PM_DEVICE_RUNTIME=y and device's parent power domain is SUSPENDED. + * + * @note This function must be called at the end of a driver's init + * function. * * @param dev Device instance. * @param action_cb Device PM control callback function. From 6bf881c66434cffef1ea40e66c64ace840d432be Mon Sep 17 00:00:00 2001 From: Bjarki Arge Andreasen Date: Tue, 20 May 2025 12:51:33 +0200 Subject: [PATCH 2/5] pm: device: introduce pm_device_driver_deinit() Introduce pm_device_driver_deinit() which is required to fully support device_deinit(). This function shall be called by drivers when device_deinit() is called. If PM_DEVICE is enabled, it will simply check whether the device is either SUSPENDED or OFF, this will prevent device_deinit() on a device which is currently in use, and ensure the device is in the correct state if device_init() is called later. If PM_DEVICE is not enabled, it will invoke the SUSPEND action which mirrors the pm_device_driver_init() behavior. Note that TURN_OFF action is not called since the device is deinitialized after this call. Signed-off-by: Bjarki Arge Andreasen --- include/zephyr/pm/device.h | 24 ++++++++++++++++++++++++ subsys/pm/device.c | 11 +++++++++++ 2 files changed, 35 insertions(+) diff --git a/include/zephyr/pm/device.h b/include/zephyr/pm/device.h index af47b1a61e9d1..9100d0e93b3a3 100644 --- a/include/zephyr/pm/device.h +++ b/include/zephyr/pm/device.h @@ -646,6 +646,25 @@ bool pm_device_is_powered(const struct device *dev); */ int pm_device_driver_init(const struct device *dev, pm_device_action_cb_t action_cb); +/** + * @brief Prepare PM device for device driver deinit + * + * @details Ensures device is either SUSPENDED or OFF. If CONFIG_PM_DEVICE=y, + * the function checks whether power management has moved the device to + * either the SUSPENDED or OFF states. If CONFIG_PM_DEVICE=n, the function + * uses the device driver's internal PM hook to move the device to the + * SUSPENDED state. + * + * @note This function must be called at the beginning of a driver's deinit + * function. + * + * @param dev Device instance. + * @param action_cb Device PM control callback function. + * @retval 0 if success. + * @retval -EBUSY Device is not SUSPENDED nor OFF + * @retval -errno code if failure. + */ +int pm_device_driver_deinit(const struct device *dev, pm_device_action_cb_t action_cb); #else static inline int pm_device_state_get(const struct device *dev, enum pm_device_state *state) @@ -742,6 +761,11 @@ static inline int pm_device_driver_init(const struct device *dev, pm_device_acti return 0; } +static inline int pm_device_driver_deinit(const struct device *dev, pm_device_action_cb_t action_cb) +{ + return action_cb(dev, PM_DEVICE_ACTION_SUSPEND); +} + #endif /* CONFIG_PM_DEVICE */ /** @} */ diff --git a/subsys/pm/device.c b/subsys/pm/device.c index a1a7e92ee9258..2d7bb3176c367 100644 --- a/subsys/pm/device.c +++ b/subsys/pm/device.c @@ -401,3 +401,14 @@ int pm_device_driver_init(const struct device *dev, /* Return the PM_DEVICE_ACTION_RESUME result */ return rc; } + +int pm_device_driver_deinit(const struct device *dev, + pm_device_action_cb_t action_cb) +{ + struct pm_device_base *pm = dev->pm_base; + + return pm->state == PM_DEVICE_STATE_SUSPENDED || + pm->state == PM_DEVICE_STATE_OFF ? + 0 : + -EBUSY; +} From 08556014a578b1efd556b41265f2d15c0a03493b Mon Sep 17 00:00:00 2001 From: Bjarki Arge Andreasen Date: Tue, 20 May 2025 13:50:44 +0200 Subject: [PATCH 3/5] tests: pm: device_driver_init: extend to support device_deinit() Extend test suite to cover pm_device_driver_deinit() resulting from call to device_deinit(). Signed-off-by: Bjarki Arge Andreasen --- tests/subsys/pm/device_driver_init/src/main.c | 55 ++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/tests/subsys/pm/device_driver_init/src/main.c b/tests/subsys/pm/device_driver_init/src/main.c index d38d6ed1429b3..15253633078af 100644 --- a/tests/subsys/pm/device_driver_init/src/main.c +++ b/tests/subsys/pm/device_driver_init/src/main.c @@ -73,6 +73,8 @@ ZTEST(device_driver_init, test_device_driver_init) struct pm_transition_test_dev_data { enum pm_device_state state_turn_on; enum pm_device_state state_resume; + enum pm_device_state state_suspend; + enum pm_device_state state_turn_off; bool state_other; }; @@ -88,6 +90,12 @@ static int pm_transition_test_dev_pm_action(const struct device *dev, enum pm_de case PM_DEVICE_ACTION_RESUME: pm_device_state_get(dev, &data->state_resume); break; + case PM_DEVICE_ACTION_SUSPEND: + pm_device_state_get(dev, &data->state_suspend); + break; + case PM_DEVICE_ACTION_TURN_OFF: + pm_device_state_get(dev, &data->state_turn_off); + break; default: data->state_other = true; } @@ -100,14 +108,30 @@ static int pm_transition_test_dev_init(const struct device *dev) data->state_turn_on = UINT8_MAX; data->state_resume = UINT8_MAX; + data->state_suspend = UINT8_MAX; + data->state_turn_off = UINT8_MAX; data->state_other = false; return pm_device_driver_init(dev, pm_transition_test_dev_pm_action); } +static int pm_transition_test_dev_deinit(const struct device *dev) +{ + struct pm_transition_test_dev_data *data = dev->data; + + data->state_turn_on = UINT8_MAX; + data->state_resume = UINT8_MAX; + data->state_suspend = UINT8_MAX; + data->state_turn_off = UINT8_MAX; + data->state_other = false; + + return pm_device_driver_deinit(dev, pm_transition_test_dev_pm_action); +} + static struct pm_transition_test_dev_data dev_data; PM_DEVICE_DEFINE(pm_transition_test_dev_pm, pm_transition_test_dev_pm_action); -DEVICE_DEFINE(pm_transition_test_dev, "test_dev", pm_transition_test_dev_init, +DEVICE_DEINIT_DEFINE(pm_transition_test_dev, "test_dev", + pm_transition_test_dev_init, pm_transition_test_dev_deinit, PM_DEVICE_GET(pm_transition_test_dev_pm), &dev_data, NULL, POST_KERNEL, 0, NULL); @@ -116,11 +140,40 @@ ZTEST(device_driver_init, test_device_driver_init_pm_state) #ifdef CONFIG_PM_DEVICE zassert_equal(PM_DEVICE_STATE_OFF, dev_data.state_turn_on); zassert_equal(PM_DEVICE_STATE_SUSPENDED, dev_data.state_resume); + zassert_equal(UINT8_MAX, dev_data.state_suspend); + zassert_equal(UINT8_MAX, dev_data.state_turn_off); zassert_false(dev_data.state_other); #else /* pm_device_state_get always returns PM_DEVICE_STATE_ACTIVE */ zassert_equal(PM_DEVICE_STATE_ACTIVE, dev_data.state_turn_on); zassert_equal(PM_DEVICE_STATE_ACTIVE, dev_data.state_resume); + zassert_equal(UINT8_MAX, dev_data.state_suspend); + zassert_equal(UINT8_MAX, dev_data.state_turn_off); + zassert_false(dev_data.state_other); +#endif /* CONFIG_PM */ + +#ifdef CONFIG_PM_DEVICE + /* device_deinit() blocked if device is not suspended or off */ + zassert_not_ok(device_deinit(DEVICE_GET(pm_transition_test_dev))); + zassert_ok(pm_device_action_run(DEVICE_GET(pm_transition_test_dev), + PM_DEVICE_ACTION_SUSPEND)); +#endif + + zassert_ok(device_deinit(DEVICE_GET(pm_transition_test_dev))); + +#ifdef CONFIG_PM_DEVICE + /* no action called as device is already suspended or off */ + zassert_equal(UINT8_MAX, dev_data.state_turn_on); + zassert_equal(UINT8_MAX, dev_data.state_resume); + zassert_equal(UINT8_MAX, dev_data.state_suspend); + zassert_equal(UINT8_MAX, dev_data.state_turn_off); + zassert_false(dev_data.state_other); +#else + /* pm_device_state_get always returns PM_DEVICE_STATE_ACTIVE */ + zassert_equal(UINT8_MAX, dev_data.state_turn_on); + zassert_equal(UINT8_MAX, dev_data.state_resume); + zassert_equal(PM_DEVICE_STATE_ACTIVE, dev_data.state_suspend); + zassert_equal(UINT8_MAX, dev_data.state_turn_off); zassert_false(dev_data.state_other); #endif /* CONFIG_PM */ } From 3feb4a981c8a8bea5ca6085982302c801488d847 Mon Sep 17 00:00:00 2001 From: Bjarki Arge Andreasen Date: Tue, 10 Jun 2025 14:39:17 +0200 Subject: [PATCH 4/5] doc: services: pm: update pm device init/deinit/states - Specify "Device" Power Management Support in sections covering CONFIG_PM_DEVICE=y or n - Extend the enum pm_device_state docstrings to define and detail expectations of devices in each enum pm_device_state state. - Extend pm device driver code example with more concise comments and include pm_device_driver_init, pm_device_driver_deinit, and DEVICE_DT_INST_DEINIT_DEFINE() (device deinit feature) - Describe the device driver PM states used if PM_DEVICE is not enabled. - Note which states a device driver is initialized from and can be deinitialized in and why. Signed-off-by: Bjarki Arge Andreasen --- doc/services/pm/device.rst | 369 ++++++++++++++++++++++++++++++++----- include/zephyr/pm/device.h | 70 ++++++- 2 files changed, 381 insertions(+), 58 deletions(-) diff --git a/doc/services/pm/device.rst b/doc/services/pm/device.rst index 379ebfbef74fb..a61187358caa2 100644 --- a/doc/services/pm/device.rst +++ b/doc/services/pm/device.rst @@ -4,11 +4,11 @@ Device Power Management Introduction ************ -Device power management (PM) on Zephyr is a feature that enables devices to -save energy when they are not being used. This feature can be enabled by -setting :kconfig:option:`CONFIG_PM_DEVICE` to ``y``. When this option is -selected, device drivers implementing power management will be able to take -advantage of the device power management subsystem. +Device Power Management in Zephyr is a feature which presents mechanisms +to coherently affect the control of power management actions to be taken +by device drivers. This control is based on unambiguous expectations +which could be set by any component of the system, and on power-related +dependencies that devices may have on each other. Zephyr supports two methods of device power management: @@ -158,8 +158,14 @@ a particular device. It is important to emphasize that, although the state is tracked by the subsystem, it is the responsibility of each device driver to handle device actions(:c:enum:`pm_device_action`) which change device state. -Each :c:enum:`pm_device_action` have a direct an unambiguous relationship with -a :c:enum:`pm_device_state`. +Device drivers implement the :c:func:`pm_device_action_cb_t` hook internally +which receives the :c:enum:`pm_device_action` for the device driver to handle. +If the :kconfig:option:`CONFIG_PM_DEVICE` option is selected, the device +drivers implementations of the hooks are exposed to the PM subsystem, enabling +runtime power management of the devices. + +:c:enum:`pm_device_action` actions have direct and unambiguous relationships with +:c:enum:`pm_device_state` states: .. graphviz:: :caption: Device actions x states @@ -174,8 +180,10 @@ a :c:enum:`pm_device_state`. ACTIVE [label=PM_DEVICE_STATE_ACTIVE]; OFF [label=PM_DEVICE_STATE_OFF]; + ACTIVE -> SUSPENDING; + SUSPENDING -> ACTIVE; + SUSPENDING -> SUSPENDED ["label"="PM_DEVICE_ACTION_SUSPEND"]; - ACTIVE -> SUSPENDING -> SUSPENDED; ACTIVE -> SUSPENDED ["label"="PM_DEVICE_ACTION_SUSPEND"]; SUSPENDED -> ACTIVE ["label"="PM_DEVICE_ACTION_RESUME"]; @@ -192,8 +200,8 @@ This is entirely done by the power management subsystem. Instead, drivers are responsible for implementing any hardware-specific tasks needed to handle state changes. -Device Model with Power Management Support -****************************************** +Device Model with Device Power Management Support +************************************************* Drivers initialize devices using macros. See :ref:`device_model_api` for details on how these macros are used. A driver which implements device power @@ -202,58 +210,319 @@ power management implementation. Use :c:macro:`PM_DEVICE_DEFINE` or :c:macro:`PM_DEVICE_DT_DEFINE` to define the power management resources required by a driver. These macros allocate the -driver-specific state which is required by the power management subsystem. +driver-specific context which is required by the power management subsystem. Drivers can use :c:macro:`PM_DEVICE_GET` or -:c:macro:`PM_DEVICE_DT_GET` to get a pointer to this state. These +:c:macro:`PM_DEVICE_DT_GET` to get a pointer to this context. These pointers should be passed to ``DEVICE_DEFINE`` or ``DEVICE_DT_DEFINE`` to initialize the power management field in each :c:struct:`device`. -Here is some example code showing how to implement device power management -support in a device driver. +The following example code shows how to implement device power management +support in a device driver. Note that return values are explicitly ignored +for brevity, in real drivers they must be handled. .. code-block:: c - #define DT_DRV_COMPAT dummy_device + #include + #include - static int dummy_driver_pm_action(const struct device *dev, - enum pm_device_action action) - { - switch (action) { - case PM_DEVICE_ACTION_SUSPEND: - /* suspend the device */ - ... - break; - case PM_DEVICE_ACTION_RESUME: - /* resume the device */ - ... - break; - case PM_DEVICE_ACTION_TURN_ON: - /* - * powered on the device, used when the power - * domain this device belongs is resumed. - */ - ... - break; - case PM_DEVICE_ACTION_TURN_OFF: - /* - * power off the device, used when the power - * domain this device belongs is suspended. - */ - ... - break; - default: - return -ENOTSUP; - } + #define DT_DRV_COMPAT dummy_device - return 0; - } + struct dummy_driver_data { + struct gpio_callback int_pin_callback; + const struct device *dev; + }; + + struct dummy_driver_config { + const struct device *bus; + const struct gpio_dt_spec int_pin; + const struct gpio_dt_spec enable_pin; + }; + + static void dummy_driver_int_pin_handler(const struct device *dev, + struct gpio_callback *cb, + uint32_t pins) + { + struct dummy_driver_data *dev_data = + CONTAINER_OF(cb, struct dummy_driver_data, int_pin_callback); + const struct device *dev = dev_data->dev; + const struct dummy_driver_config *dev_config = dev->config; + + /* ... */ + } + + static int dummy_driver_pm_suspend(const struct device *dev) + { + struct dummy_driver_data *dev_data = dev->data; + const struct dummy_driver_config *dev_config = dev->config; + + /* Request devices needed by device */ + (void)pm_device_runtime_get(config->enable_pin.port); + + /* Disable and remove interrupt pin interrupt */ + (void)gpio_pin_interrupt_configure_dt(&config->int_gpio, GPIO_INT_DISABLED); + (void)gpio_remove_callback(config->int_pin.port, &data->int_pin_callback); + + /* Disable the device. In this case, we use the enable pin */ + (void)gpio_pin_set_dt(&config->enable_pin, 0); + + /* Release devices currenty not needed by device */ + (void)pm_device_runtime_put(config->enable_pin.port); + (void)pm_device_runtime_put(config->int_pin.port); + + /* + * Note that we now have suspended the device and released all the + * devices this device depends on. We are ready for the power + * domain being suspended, the device being resumed again, or the + * device driver being deinitialized. + */ + + return 0; + } + + static int dummy_driver_pm_resume(const struct device *dev) + { + struct dummy_driver_data *dev_data = dev->data; + const struct dummy_driver_config *dev_config = dev->config; + + /* Request devices needed by device */ + (void)pm_device_runtime_get(config->enable_pin.port); + (void)pm_device_runtime_get(config->int_pin.port); + (void)pm_device_runtime_get(config->bus); + + /* Enable the device. In this case, we use the enable pin */ + (void)gpio_pin_set_dt(&config->enable_pin, 1); + + /* + * Write initial commands to device, in this case configuring + * the device's interrupt output pin using the bus + */ + + /* ... */ + + /* Add and enable interrupt pin interrupt */ + (void)gpio_add_callback(config->int_pin.port, &data->int_pin_callback); + (void)gpio_pin_interrupt_configure_dt(&config->int_gpio, GPIO_INT_EDGE_TO_ACTIVE); + + /* + * Release devices currenty not needed by device. In this case, we + * are releasing the bus and the enable pin. + * + * The device driver would keep the bus ACTIVE while the device is + * ACTIVE in cases of high throughput or unsolicitet data on the + * bus, to avoid inefficient RESUME/SUSPEND cycles of the bus + * for every transaction, and allowing reception of unsolicitet + * data on busses like UART. + */ + (void)pm_device_runtime_put(config->bus); + (void)pm_device_runtime_put(config->enable_pin.port); + + /* + * Note that the interrupt pin's port is kept resumed as it + * it needs to service the GPIO interrupt we enabled. + */ - PM_DEVICE_DT_INST_DEFINE(0, dummy_driver_pm_action); + return 0; + } + + static int dummy_driver_pm_turn_off(const struct device *dev) + { + const struct dummy_driver_config *dev_config = dev->config; + + /* Request devices needed for configuring device */ + (void)pm_device_runtime_get(config->enable_pin.port); + + /* + * We prepare the device for being powered off. In this case, we + * have an active low enable pin, which could back power the device + * once the power domain is suspended, so we configure it as + * disconnected if supported, input otherwise. + */ + if (gpio_pin_configure_dt(&config->enable_pin, GPIO_DISCONNECTED)) { + (void)gpio_pin_configure_dt(&config->enable_pin, GPIO_INPUT); + } + + /* Release devices needed for configuring device */ + (void)pm_device_runtime_put(config->enable_pin.port); + + /* + * We have now prepared the device for being powered off and have + * released all the devices this device depends on. We assume that + * the enable pin will retain its configuration, even as we have + * released the enable pin's port. + */ + + return 0; + } + + static int dummy_driver_pm_turn_on(const struct device *dev) + { + const struct dummy_driver_config *dev_config = dev->config; + + /* Request devices needed for configuring device */ + (void)pm_device_runtime_get(config->enable_pin.port); + (void)pm_device_runtime_get(config->int_gpio.port); + + /* + * We ensure the device is suspended, and if possible in its reset + * state. In this case we are using an enable pin, for other devices + * we may need to reset them by toggling a reset pin, using an SoC + * reset controller, or writing a reset command to them using their + * bus. + */ + (void)gpio_pin_configure_dt(&config->enable_pin, GPIO_OUTPUT_INACTIVE); + + /* We configure pins for suspended */ + (void)gpio_pin_configure_dt(&config->int_gpio, GPIO_INPUT); + + /* Release devices needed for configuring device */ + (void)pm_device_runtime_put(config->int_gpio.port); + (void)pm_device_runtime_put(config->enable_pin.port); + + return 0; + } + + static int dummy_driver_pm_action(const struct device *dev, + enum pm_device_action action) + { + int ret; + + switch (action) { + case PM_DEVICE_ACTION_SUSPEND: + ret = dummy_driver_pm_suspend(dev); + break; + case PM_DEVICE_ACTION_RESUME: + ret = dummy_driver_pm_resume(dev); + break; + case PM_DEVICE_ACTION_TURN_OFF: + ret = dummy_driver_pm_turn_off(dev); + break; + case PM_DEVICE_ACTION_TURN_ON: + ret = dummy_driver_pm_turn_on(dev); + break; + default: + ret = -EINVAL; + break; + } + + return ret; + } + + static int dummy_init(const struct device *dev) + { + struct dummy_driver_data *dev_data = dev->data; + const struct dummy_driver_config *dev_config = dev->config; + + /* + * We must ensure all devices we depend on, excluding a potential + * power domain, are initialized. + * + * If CONFIG_PM_DEVICE=n, this also ensures the devices are ACTIVE. + */ + if (!device_is_ready(dev_config->bus) || + !gpio_is_ready_dt(&dev_config->int_pin) || + !gpio_is_ready_dt(&dev_config->enable_pin)) { + return -ENODEV; + } + + /* We then initialize the device driver data structure */ + gpio_init_callback(&dev_data->int_pin_callback, + dummy_driver_int_pin_handler, + BIT(dev_config->int_pin.pin)); + + dev_data->dev = dev; + + /* + * This call must be the last call of the device init function. + * It will initialize the device's PM_DEVICE context and use the + * dummy_driver_pm_action callback to initialize the device into + * the appropriate state. + */ + return pm_device_driver_init(dev, dummy_driver_pm_action); + } + + static int dummy_deinit(const struct device *dev) + { + int ret; + + /* + * This call must be the first call of the device deinit function. + * It will use the dummy_driver_pm_action callback to move the + * device into, or verify the device is already in, an appropriate + * state for deinitialization, and deinitialize the device's + * PM_DEVICE context. + */ + ret = pm_device_driver_deinit(dev, dummy_driver_pm_action); + if (ret) { + return ret; + } + + /* + * The device is now either SUSPENDED or OFF, all the devices this + * device depends on have been released, and devices with persistent + * configurations like GPIO pins have been configured to match the + * device state. + * + * The device will be left in this state until a new "owner" takes + * over. + */ + + /* + * If we had allocated memory, DMA channels or other resources, we would + * release them here. + */ + + return ret; + } + + static struct dummy_driver_data data0; + + static struct dummy_driver_config config0 = { + .bus = DEVICE_DT_GET(DT_INST_PARENT(0)), + .int_pin = GPIO_DT_SPEC_INST_GET(0, int_gpios), + .enable_pin = GPIO_DT_SPEC_INST_GET(0, enable_gpios), + }; - DEVICE_DT_INST_DEFINE(0, &dummy_init, - PM_DEVICE_DT_INST_GET(0), NULL, NULL, POST_KERNEL, - CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, NULL); + /* Define the device's PM DEVICE context */ + PM_DEVICE_DT_INST_DEFINE(0, dummy_driver_pm_action); + + /* Define the device, pointing to the device's PM DEVICE context */ + DEVICE_DT_INST_DEINIT_DEFINE( + 0, + &dummy_init, + &dummy_deinit, + PM_DEVICE_DT_INST_GET(0), + &data0, + &config0, + POST_KERNEL, + CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, + NULL + ); + +Device Model with Partial Device Power Management Support +********************************************************* + +If :kconfig:option:`CONFIG_PM_DEVICE` is not enabled, The device +power state is tied to the devices initialization state. + +Once a device is initialized, the device driver PM action hook is +used to move the device to the ``ACTIVE`` state through calling +:c:func:`pm_device_driver_init`. Following the +``Device actions x states`` graph and the definition of the ``OFF`` +state, this results in a call to ``PM_DEVICE_ACTION_TURN_ON`` +followed by ``PM_DEVICE_ACTION_RESUME``. + +Given power domains and busses are "just devices", every power +domain and bus will be resumed before its child devices as they +are initialized according to the devicetree dependency ordinals. +Every device is assumed to be powered, and the devices a device +depends on are assumed to be ``ACTIVE``, when device is initialized. + +Once a device is deinitialized, the device driver PM action hook +is used to move the device to the ``SUSPENDED`` state through +calling :c:func:`pm_device_driver_deinit`. Following the +``Device actions x states``, and assuming power domains are "always +on" this results in a call to ``PM_DEVICE_ACTION_SUSPEND``. .. _pm-device-shell: diff --git a/include/zephyr/pm/device.h b/include/zephyr/pm/device.h index 9100d0e93b3a3..a63bb88c53556 100644 --- a/include/zephyr/pm/device.h +++ b/include/zephyr/pm/device.h @@ -66,22 +66,76 @@ enum pm_device_flag { /** @brief Device power states. */ enum pm_device_state { - /** Device is in active or regular state. */ + /** + * @brief Device hardware is powered, and the device is needed by the system. + * + * @details The device should be enabled in this state. Any device driver API + * may be called in this state. + */ PM_DEVICE_STATE_ACTIVE, /** - * Device is suspended. + * @brief Device hardware is powered, but the device is not needed by the + * system. * - * @note - * Device context may be lost. + * @details The device should be put into its lowest internal power state, + * commonly named "disabled" or "stopped". + * + * If a device has been specified as this device's power domain, and said + * device is no longer needed by the system, this device will be + * transitioned into the @ref PM_DEVICE_STATE_OFF state, followed by the + * power domain device being transitioned to the + * @ref PM_DEVICE_STATE_SUSPENDED state. + * + * A device driver may be deinitialized in this state. Once the device + * driver has been deinitialized, we implicitly move to the + * @ref PM_DEVICE_STATE_OFF state as the device hardware may lose power, + * with no device driver to respond to the corresponding + * @ref PM_DEVICE_ACTION_TURN_OFF action. + * + * @note This state is NOT a "low-power"/"partially operable" state, + * those are configured using device driver specific APIs, and apply only + * while the device is in the @ref PM_DEVICE_STATE_ACTIVE state. */ PM_DEVICE_STATE_SUSPENDED, - /** Device is being suspended. */ + /** + * @brief Device hardware is powered, but the device has been scheduled to + * be suspended, as it is no longer needed by the system. + * + * @details This state is used when delegating suspension of a device to + * the PM subsystem, optionally with residency to avoid unnecessary + * suspend/resume cycles, resulting from a call to + * @ref pm_device_runtime_put_async. The device will be unscheduled in case + * the device becomes needed by the system. + * + * No device driver API calls must occur in this state. + * + * @note that this state is opaque to the device driver (no + * @ref pm_device_action is called as this state is entered) and is used + * solely by PM_DEVICE_RUNTIME. + */ PM_DEVICE_STATE_SUSPENDING, /** - * Device is turned off (power removed). + * @brief Device hardware is not powered. This is the initial state from + * which a device driver is initialized. * - * @note - * Device context is lost. + * @details When a device driver is initialized, we do not know the state + * of the device. As a result, the @ref PM_DEVICE_ACTION_TURN_ON action + * should be able to transition the device from any internal state into + * @ref PM_DEVICE_STATE_SUSPENDED, since no guarantees can be made across + * resets. This is typically achieved through toggling a reset pin or + * triggering a software reset through a register write before performing + * any additional configuration needed to meet the requirements of + * @ref PM_DEVICE_STATE_SUSPENDED. For devices where this is not possible, + * the device driver must presume the device is in either the + * @ref PM_DEVICE_STATE_OFF or @ref PM_DEVICE_STATE_SUSPENDED state at + * time of initialization, as these are the states within which device + * drivers may be deinitialized. + * + * If a device has been specified as this device's power domain, and said + * device becomes needed by the system, the power domain device will be + * transitioned into the @ref PM_DEVICE_STATE_ACTIVE state, followed by this + * device being transitioned to the + * @ref PM_DEVICE_STATE_SUSPENDED state. */ PM_DEVICE_STATE_OFF }; From 5e0d6a28c707e92a558db8cf5bc88a65ad16913e Mon Sep 17 00:00:00 2001 From: Bjarki Arge Andreasen Date: Fri, 27 Jun 2025 10:49:40 +0200 Subject: [PATCH 5/5] doc: releases: 4.2: mention pm_device_driver_deinit() Mention introduction of pm_device_driver_deinit() in the 4.2 release notes. Signed-off-by: Bjarki Arge Andreasen --- doc/releases/release-notes-4.2.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/releases/release-notes-4.2.rst b/doc/releases/release-notes-4.2.rst index 41d9b8ea330ed..d247f4f1c09a9 100644 --- a/doc/releases/release-notes-4.2.rst +++ b/doc/releases/release-notes-4.2.rst @@ -221,6 +221,7 @@ New APIs and options * :kconfig:option:`CONFIG_PM_DEVICE_RUNTIME_DEDICATED_WQ_PRIO` * :kconfig:option:`CONFIG_PM_DEVICE_RUNTIME_DEDICATED_WQ_INIT_PRIO` * :kconfig:option:`CONFIG_PM_DEVICE_RUNTIME_ASYNC` + * :c:func:`pm_device_driver_deinit` * Sockets