238 lines
9.1 KiB
ReStructuredText
238 lines
9.1 KiB
ReStructuredText
.. _pm-device-runtime:
|
|
|
|
Device Runtime Power Management
|
|
###############################
|
|
|
|
Introduction
|
|
************
|
|
|
|
The device runtime power management (PM) framework is an active power management
|
|
mechanism which reduces the overall system power consumption by suspending the
|
|
devices which are idle or not used independently of the system state. It can be
|
|
enabled by setting :kconfig:option:`CONFIG_PM_DEVICE_RUNTIME`. In this model the device
|
|
driver is responsible to indicate when it needs the device and when it does not.
|
|
This information is used to determine when to suspend or resume a device based
|
|
on usage count.
|
|
|
|
When device runtime power management is enabled on a device, its state will be
|
|
initially set to a :c:enumerator:`PM_DEVICE_STATE_SUSPENDED` indicating it is
|
|
not used. On the first device request, it will be resumed and so put into the
|
|
:c:enumerator:`PM_DEVICE_STATE_ACTIVE` state. The device will remain in this
|
|
state until it is no longer used. At this point, the device will be suspended
|
|
until the next device request. If the suspension is performed synchronously the
|
|
device will be immediately put into the
|
|
:c:enumerator:`PM_DEVICE_STATE_SUSPENDED` state, whereas if it is performed
|
|
asynchronously, it will be put into the
|
|
:c:enumerator:`PM_DEVICE_STATE_SUSPENDING` state first and then into the
|
|
:c:enumerator:`PM_DEVICE_STATE_SUSPENDED` state when the action is run.
|
|
|
|
For devices on a power domain (via the devicetree 'power-domains' property), device runtime
|
|
power management automatically attempts to request and release the dependent domain
|
|
in response to :c:func:`pm_device_runtime_get` and :c:func:`pm_device_runtime_put`
|
|
calls on the child device.
|
|
|
|
For the previous to automatically control the power domain state, device runtime PM must be enabled
|
|
on the power domain device (either through the ``zephyr,pm-device-runtime-auto`` devicetree property
|
|
or :c:func:`pm_device_runtime_enable`).
|
|
|
|
.. graphviz::
|
|
:caption: Device states and transitions
|
|
|
|
digraph {
|
|
node [shape=box];
|
|
init [shape=point];
|
|
|
|
SUSPENDED [label=PM_DEVICE_STATE_SUSPENDED];
|
|
ACTIVE [label=PM_DEVICE_STATE_ACTIVE];
|
|
SUSPENDING [label=PM_DEVICE_STATE_SUSPENDING];
|
|
|
|
init -> SUSPENDED;
|
|
SUSPENDED -> ACTIVE;
|
|
ACTIVE -> SUSPENDED;
|
|
ACTIVE -> SUSPENDING [constraint=false]
|
|
SUSPENDING -> SUSPENDED [constraint=false];
|
|
SUSPENDED -> SUSPENDING [style=invis];
|
|
SUSPENDING -> ACTIVE [style=invis];
|
|
}
|
|
|
|
The device runtime power management framework has been designed to minimize
|
|
devices power consumption with minimal application work. Device drivers are
|
|
responsible for indicating when they need the device to be operational and
|
|
when they do not. Therefore, applications can not manually suspend or resume a
|
|
device. An application can, however, decide when to disable or enable runtime
|
|
power management for a device. This can be useful, for example, if an
|
|
application wants a particular device to be always active.
|
|
|
|
Design principles
|
|
*****************
|
|
|
|
When runtime PM is enabled on a device it will no longer be resumed or suspended
|
|
during system power transitions. Instead, the device is fully responsible to
|
|
indicate when it needs a device and when it does not. The device runtime PM API
|
|
uses reference counting to keep track of device's usage. This allows the API to
|
|
determine when a device needs to be resumed or suspended. The API uses the *get*
|
|
and *put* terminology to indicate when a device is needed or not, respectively.
|
|
This mechanism plays a key role when we account for device dependencies. For
|
|
example, if a bus device is used by multiple sensors, we can keep the bus active
|
|
until the last sensor has finished using it.
|
|
|
|
.. note::
|
|
|
|
As of today, the device runtime power management API does not manage device
|
|
dependencies. This effectively means that, if a device depends on other
|
|
devices to operate (e.g. a sensor may depend on a bus device), the bus will
|
|
be resumed and suspended on every transaction. In general, it is more
|
|
efficient to keep parent devices active when their children are used, since
|
|
the children may perform multiple transactions in a short period of time.
|
|
Until this feature is added, devices can manually *get* or *put* their
|
|
dependencies.
|
|
|
|
The :c:func:`pm_device_runtime_get` function can be used by a device driver to
|
|
indicate it *needs* the device to be active or operational. This function will
|
|
increase device usage count and resume the device if necessary. Similarly, the
|
|
:c:func:`pm_device_runtime_put` function can be used to indicate that the device
|
|
is no longer needed. This function will decrease the device usage count and
|
|
suspend the device if necessary. It is worth to note that in both cases, the
|
|
operation is carried out synchronously. The sequence diagram shown below
|
|
illustrates how a device can use this API and the expected sequence of events.
|
|
|
|
.. figure:: images/devr-sync-ops.svg
|
|
|
|
Synchronous operation on a single device
|
|
|
|
The synchronous model is as simple as it gets. However, it may introduce
|
|
unnecessary delays since the application will not get the operation result until
|
|
the device is suspended (in case device is no longer used). It will likely not
|
|
be a problem if the operation is fast, e.g. a register toggle. However, the
|
|
situation will not be the same if suspension involves sending packets through a
|
|
slow bus. For this reason the device drivers can also make use of the
|
|
:c:func:`pm_device_runtime_put_async` function. This function will schedule
|
|
the suspend operation, again, if device is no longer used. The suspension will
|
|
then be carried out when the system work queue gets the chance to run. The
|
|
sequence diagram shown below illustrates this scenario.
|
|
|
|
.. figure:: images/devr-async-ops.svg
|
|
|
|
Asynchronous operation on a single device
|
|
|
|
Implementation guidelines
|
|
*************************
|
|
|
|
In a first place, a device driver needs to implement the PM action callback used
|
|
by the PM subsystem to suspend or resume devices.
|
|
|
|
.. code-block:: c
|
|
|
|
static int mydev_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;
|
|
default:
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
The PM action callback calls are serialized by the PM subsystem, therefore, no
|
|
special synchronization is required.
|
|
|
|
To enable device runtime power management on a device, the driver needs to call
|
|
:c:func:`pm_device_runtime_enable` at initialization time. Note that this
|
|
function will suspend the device if its state is
|
|
:c:enumerator:`PM_DEVICE_STATE_ACTIVE`. In case the device is physically
|
|
suspended, the init function should call
|
|
:c:func:`pm_device_init_suspended` before calling
|
|
:c:func:`pm_device_runtime_enable`.
|
|
|
|
.. code-block:: c
|
|
|
|
/* device driver initialization function */
|
|
static int mydev_init(const struct device *dev)
|
|
{
|
|
int ret;
|
|
...
|
|
|
|
/* OPTIONAL: mark device as suspended if it is physically suspended */
|
|
pm_device_init_suspended(dev);
|
|
|
|
/* enable device runtime power management */
|
|
ret = pm_device_runtime_enable(dev);
|
|
if ((ret < 0) && (ret != -ENOSYS)) {
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
Device runtime power management can also be automatically enabled on a device
|
|
instance by adding the ``zephyr,pm-device-runtime-auto`` flag onto the corresponding
|
|
devicetree node. If enabled, :c:func:`pm_device_runtime_enable` is called immediately
|
|
after the ``init`` function of the device runs and returns successfully.
|
|
|
|
.. code-block:: dts
|
|
|
|
foo {
|
|
/* ... */
|
|
zephyr,pm-device-runtime-auto;
|
|
};
|
|
|
|
Assuming an example device driver that implements an ``operation`` API call, the
|
|
*get* and *put* operations could be carried out as follows:
|
|
|
|
.. code-block:: c
|
|
|
|
static int mydev_operation(const struct device *dev)
|
|
{
|
|
int ret;
|
|
|
|
/* "get" device (increases usage count, resumes device if suspended) */
|
|
ret = pm_device_runtime_get(dev);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
/* do something with the device */
|
|
...
|
|
|
|
/* "put" device (decreases usage count, suspends device if no more users) */
|
|
return pm_device_runtime_put(dev);
|
|
}
|
|
|
|
In case the suspend operation is *slow*, the device driver can use the
|
|
asynchronous API:
|
|
|
|
.. code-block:: c
|
|
|
|
static int mydev_operation(const struct device *dev)
|
|
{
|
|
int ret;
|
|
|
|
/* "get" device (increases usage count, resumes device if suspended) */
|
|
ret = pm_device_runtime_get(dev);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
/* do something with the device */
|
|
...
|
|
|
|
/* "put" device (decreases usage count, schedule suspend if no more users) */
|
|
return pm_device_runtime_put_async(dev, K_NO_WAIT);
|
|
}
|
|
|
|
Examples
|
|
********
|
|
|
|
Some helpful examples showing device runtime power management features:
|
|
|
|
* :zephyr_file:`tests/subsys/pm/device_runtime_api/`
|
|
* :zephyr_file:`tests/subsys/pm/device_power_domains/`
|
|
* :zephyr_file:`tests/subsys/pm/power_domain/`
|