zephyr/drivers/power_domain/power_domain_gpio_monitor.c
Krzysztof Chruściński 25173f71cd pm: device_runtime: Extend with synchronous runtime PM
In many cases suspending or resuming of a device is limited to
just a few register writes. Current solution assumes that those
operations may be blocking, asynchronous and take a lot of time.
Due to this assumption runtime PM API cannot be effectively used
from the interrupt context. Zephyr has few driver APIs which
can be used from an interrupt context and now use of runtime PM
is limited in those cases.

Patch introduces a new type of PM device - synchronous PM. If
device is specified as capable of synchronous PM operations then
device runtime getting and putting is executed in the critical
section. In that case, runtime API can be used from an interrupt
context. Additionally, this approach reduces RAM needed for
PM device (104 -> 20 bytes of RAM on ARM Cortex-M).

Signed-off-by: Krzysztof Chruściński <krzysztof.chruscinski@nordicsemi.no>
2024-02-01 15:03:42 +01:00

151 lines
4.2 KiB
C

/*
* Copyright (c) 2023 Google LLC
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT power_domain_gpio_monitor
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/pm/device.h>
#include <zephyr/pm/device_runtime.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(power_domain_gpio_monitor, CONFIG_POWER_DOMAIN_LOG_LEVEL);
struct pd_gpio_monitor_config {
struct gpio_dt_spec power_good_gpio;
};
struct pd_gpio_monitor_data {
struct gpio_callback callback;
const struct device *dev;
bool is_powered;
};
struct pd_visitor_context {
const struct device *domain;
enum pm_device_action action;
};
static int pd_on_domain_visitor(const struct device *dev, void *context)
{
struct pd_visitor_context *visitor_context = context;
/* Only run action if the device is on the specified domain */
if (!dev->pm || (dev->pm_base->domain != visitor_context->domain)) {
return 0;
}
dev->pm->usage = 0;
(void)pm_device_action_run(dev, visitor_context->action);
return 0;
}
static void pd_gpio_monitor_callback(const struct device *port,
struct gpio_callback *cb, gpio_port_pins_t pins)
{
struct pd_gpio_monitor_data *data = CONTAINER_OF(cb, struct pd_gpio_monitor_data, callback);
const struct pd_gpio_monitor_config *config = data->dev->config;
const struct device *dev = data->dev;
struct pd_visitor_context context = {.domain = dev};
int rc;
rc = gpio_pin_get_dt(&config->power_good_gpio);
if (rc < 0) {
LOG_WRN("Failed to read gpio logic level");
return;
}
data->is_powered = rc;
if (rc == 0) {
context.action = PM_DEVICE_ACTION_SUSPEND;
(void)device_supported_foreach(dev, pd_on_domain_visitor, &context);
context.action = PM_DEVICE_ACTION_TURN_OFF;
(void)device_supported_foreach(dev, pd_on_domain_visitor, &context);
return;
}
pm_device_children_action_run(data->dev, PM_DEVICE_ACTION_TURN_ON, NULL);
}
static int pd_gpio_monitor_pm_action(const struct device *dev, enum pm_device_action action)
{
struct pd_gpio_monitor_data *data = dev->data;
switch (action) {
case PM_DEVICE_ACTION_TURN_ON:
case PM_DEVICE_ACTION_TURN_OFF:
return -ENOTSUP;
case PM_DEVICE_ACTION_RESUME:
if (!data->is_powered) {
return -EAGAIN;
}
break;
default:
break;
}
return 0;
}
static int pd_gpio_monitor_init(const struct device *dev)
{
const struct pd_gpio_monitor_config *config = dev->config;
struct pd_gpio_monitor_data *data = dev->data;
int rc;
data->dev = dev;
if (!gpio_is_ready_dt(&config->power_good_gpio)) {
LOG_ERR("GPIO port %s is not ready", config->power_good_gpio.port->name);
return -ENODEV;
}
rc = gpio_pin_configure_dt(&config->power_good_gpio, GPIO_INPUT);
if (rc) {
LOG_ERR("Failed to configure GPIO");
goto unconfigure_pin;
}
rc = gpio_pin_interrupt_configure_dt(&config->power_good_gpio, GPIO_INT_EDGE_BOTH);
if (rc) {
gpio_pin_configure_dt(&config->power_good_gpio, GPIO_DISCONNECTED);
LOG_ERR("Failed to configure GPIO interrupt");
goto unconfigure_interrupt;
}
gpio_init_callback(&data->callback, pd_gpio_monitor_callback,
BIT(config->power_good_gpio.pin));
rc = gpio_add_callback_dt(&config->power_good_gpio, &data->callback);
if (rc) {
LOG_ERR("Failed to add GPIO callback");
goto remove_callback;
}
pm_device_init_suspended(dev);
return pm_device_runtime_enable(dev);
remove_callback:
gpio_remove_callback(config->power_good_gpio.port, &data->callback);
unconfigure_interrupt:
gpio_pin_interrupt_configure_dt(&config->power_good_gpio, GPIO_INT_DISABLE);
unconfigure_pin:
gpio_pin_configure_dt(&config->power_good_gpio, GPIO_DISCONNECTED);
return rc;
}
#define POWER_DOMAIN_DEVICE(inst) \
static const struct pd_gpio_monitor_config pd_gpio_monitor_config_##inst = { \
.power_good_gpio = GPIO_DT_SPEC_INST_GET(inst, gpios), \
}; \
static struct pd_gpio_monitor_data pd_gpio_monitor_data_##inst; \
PM_DEVICE_DT_INST_DEFINE(inst, pd_gpio_monitor_pm_action); \
DEVICE_DT_INST_DEFINE(inst, pd_gpio_monitor_init, \
PM_DEVICE_DT_INST_GET(inst), &pd_gpio_monitor_data_##inst, \
&pd_gpio_monitor_config_##inst, POST_KERNEL, \
CONFIG_POWER_DOMAIN_GPIO_MONITOR_INIT_PRIORITY, NULL);
DT_INST_FOREACH_STATUS_OKAY(POWER_DOMAIN_DEVICE)