The Zephyr power management (PM) subsystem provides a framework for power management that helps reduce overall power consumption by disabling or suspending selected subsystems or devices not currently used.
Many of Zephyr’s components utilize a power management framework. Each layer can operate independently without needing to know about other components. Entering or exiting low power mode is also vendor-specific, and the decision on what to do in these states is up to the author of the device driver.
Zephyr can put the device driver into any of the following power states, see enum pm_device_state
:
PM_DEVICE_STATE_ACTIVE
PM_DEVICE_STATE_SUSPENDED
PM_DEVICE_STATE_SUSPENDING
PM_DEVICE_STATE_OFF
The figure below shows all possible device power states.
As mentioned before, it is the device driver’s responsibility to take action in the current state. Zephyr merely provides a mechanism to notify the driver that it should transition to a specific state.
Let’s take a look at how power management is implemented in the nRF SPI driver., found in zephyr/drivers/spi/spi_nrfx_spim.c
.
static int spim_nrfx_pm_action(const struct device *dev,
enum pm_device_action action)
{
...
switch (action) {
case PM_DEVICE_ACTION_TURN_ON:
ret = 0;
break;
case PM_DEVICE_ACTION_RESUME:
ret = pinctrl_apply_state(dev_config->pcfg,
PINCTRL_STATE_DEFAULT);
....
break;
case PM_DEVICE_ACTION_SUSPEND:
if (dev_data->initialized) {
nrfx_spim_uninit(&dev_config->spim);
dev_data->initialized = false;
}
...
ret = pinctrl_apply_state(dev_config->pcfg,
PINCTRL_STATE_SLEEP);
break;
default:
break;
}
return ret;
}
CThe requested power state is passed as an action
input parameter (see enum pm_device_action
) into the power action callback. The SPI driver then reconfigures the GPIO pins according to the selected state (more info in Pin Control) and uninitializes the driver if needed.
This power management action callback is one of the (optional) parameters in the device driver definition. As this is an optional parameter, the device driver is not obligated to include this callback.
Let’s look into the nRF SPI driver definition to see how this callback is configured.
PM_DEVICE_DT_DEFINE(SPIM(idx), spim_nrfx_pm_action); \
DEVICE_DT_DEFINE(SPIM(idx), \
spi_nrfx_init, \
PM_DEVICE_DT_GET(SPIM(idx)), \
&spi_##idx##_data, \
&spi_##idx##z_config, \
POST_KERNEL, CONFIG_SPI_INIT_PRIORITY, \
&spi_nrfx_driver_api)
CNotice that the callback (spim_nrfx_pm_action()
) is not included directly in the device driver definition. First, the PM_DEVICE_DT_DEFINE()
macro helper is used to link the callback with the selected node from the device tree. Then, in the device driver definition, the macro helper PM_DEVICE_DT_GET()
is used to obtain a reference to the device’s power management resources, using the device tree node.
We have covered the actions a device driver can take when processing a power state request. To fully understand how power management works, we also need to know what trigger these requests and how to define the conditions under which the system decides to put the device into the selected power state.
There are several different methods. For this lesson, we will cover the Device Runtime Power Management strategy.
Other strategies are described in the documentation of the Zephyr power management framework: Power Management
When the device runtime power management strategy is enabled on a device, it will go into the suspend state during the initialization process. Then, when the device is to be used, it is the device driver’s responsibility to notify the power management subsystem about this fact. This can be done using the pm_device_runtime_get()
function, which has the following signature
Consequently, the power management subsystem wakes up the device by requesting the device driver go into the selected power state using the PM action callback described before. The device driver performs all the necessary actions defined by the driver’s author. When the device’s operation is complete and the device does not need to be in operational mode anymore, the device driver needs to notify the PM subsystem to let it put the device into the suspend state by calling pm_device_runtime_put()
, which has the following signature
Similarly, the power management subsystem uses the PM action callback to request that the device driver put the device into the suspend state.
Let’s take a look at the nRF SPI driver code to see this in action.
The driver notifies the PM subsystem using pm_device_runtime_get()
prior to the bus usage.
static int transceive(const struct device *dev,
const struct spi_config *spi_cfg,
const struct spi_buf_set *tx_bufs,
const struct spi_buf_set *rx_bufs,
bool asynchronous,
spi_callback_t cb,
void *userdata)
{
...
if (IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME)) {
pm_device_runtime_get(dev);
}
spi_context_lock(&dev_data->ctx, asynchronous, cb, userdata, spi_cfg);
...
CWhen the operation ends, the nRF SPI driver notifies the PM subsystem about this fact using pm_device_runetime_put()
.
static inline void finalize_spi_transaction(const struct device *dev, bool deactivate_cs)
{
...
}
if (IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME)) {
pm_device_runtime_put(dev);
}
CIn Exercise 2 of this lesson, we will implement the device runtime power management strategy on our BME280 custom driver.