drivers: power_domain: Introduce a gpio monitor driver

Power rails of some peripherals are controlled externally.
This is a case in embedded controllers, where the power of
some I2C devices are managed by the main application
processor.

To ensure that zephyr drivers access the devices where is
powered on, introduce a "monitoring" power domain. It works
by registering interrupt handler with gpio a pin, so that
when power state changes, it will notify relevant drivers.

Additionaly add CONFIG_POWER_DOMAIN_INIT_PRIORITY to replace
harcoded init priority.

Fixes: #51349

Signed-off-by: Albert Jakieła <jakiela@google.com>
This commit is contained in:
Albert Jakieła 2023-07-18 13:57:26 +00:00 committed by Fabio Baltieri
parent a17d49511f
commit 06cfbd4159
6 changed files with 206 additions and 20 deletions

View file

@ -4,4 +4,5 @@
zephyr_library()
zephyr_library_sources_ifdef(CONFIG_POWER_DOMAIN_GPIO power_domain_gpio.c)
zephyr_library_sources_ifdef(CONFIG_POWER_DOMAIN_GPIO_MONITOR power_domain_gpio_monitor.c)
zephyr_library_sources_ifdef(CONFIG_POWER_DOMAIN_INTEL_ADSP power_domain_intel_adsp.c)

View file

@ -12,6 +12,12 @@ module = POWER_DOMAIN
module-str = power_domain
source "subsys/logging/Kconfig.template.log_config"
config POWER_DOMAIN_INIT_PRIORITY
int "Power domain init priority"
default 75
help
Power domain initialization priority.
config POWER_DOMAIN_GPIO
bool "GPIO controlled power domain"
default y
@ -28,4 +34,11 @@ config POWER_DOMAIN_INTEL_ADSP
help
Include Intel ADSP power domain control mechanisms
config POWER_DOMAIN_GPIO_MONITOR
bool "GPIO monitor for sensing power on rail"
default y
depends on DT_HAS_POWER_DOMAIN_GPIO_MONITOR_ENABLED
depends on GPIO
select DEVICE_DEPS
endif

View file

@ -125,17 +125,17 @@ static int pd_gpio_init(const struct device *dev)
return pm_device_driver_init(dev, pd_gpio_pm_action);
}
#define POWER_DOMAIN_DEVICE(id) \
static const struct pd_gpio_config pd_gpio_##id##_cfg = { \
.enable = GPIO_DT_SPEC_INST_GET(id, enable_gpios), \
.startup_delay_us = DT_INST_PROP(id, startup_delay_us), \
.off_on_delay_us = DT_INST_PROP(id, off_on_delay_us), \
}; \
static struct pd_gpio_data pd_gpio_##id##_data; \
PM_DEVICE_DT_INST_DEFINE(id, pd_gpio_pm_action); \
DEVICE_DT_INST_DEFINE(id, pd_gpio_init, PM_DEVICE_DT_INST_GET(id), \
&pd_gpio_##id##_data, &pd_gpio_##id##_cfg, \
POST_KERNEL, 75, \
#define POWER_DOMAIN_DEVICE(id) \
static const struct pd_gpio_config pd_gpio_##id##_cfg = { \
.enable = GPIO_DT_SPEC_INST_GET(id, enable_gpios), \
.startup_delay_us = DT_INST_PROP(id, startup_delay_us), \
.off_on_delay_us = DT_INST_PROP(id, off_on_delay_us), \
}; \
static struct pd_gpio_data pd_gpio_##id##_data; \
PM_DEVICE_DT_INST_DEFINE(id, pd_gpio_pm_action); \
DEVICE_DT_INST_DEFINE(id, pd_gpio_init, PM_DEVICE_DT_INST_GET(id), \
&pd_gpio_##id##_data, &pd_gpio_##id##_cfg, \
POST_KERNEL, CONFIG_POWER_DOMAIN_INIT_PRIORITY, \
NULL);
DT_INST_FOREACH_STATUS_OKAY(POWER_DOMAIN_DEVICE)

View file

@ -0,0 +1,150 @@
/*
* 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->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_INIT_PRIORITY, NULL);
DT_INST_FOREACH_STATUS_OKAY(POWER_DOMAIN_DEVICE)

View file

@ -76,14 +76,14 @@ static int pd_intel_adsp_init(const struct device *dev)
#define DT_DRV_COMPAT intel_adsp_power_domain
#define POWER_DOMAIN_DEVICE(id) \
static struct pg_bits pd_pg_reg##id = { \
.SPA_bit = DT_INST_PROP(id, bit_position), \
.CPA_bit = DT_INST_PROP(id, bit_position), \
}; \
PM_DEVICE_DT_INST_DEFINE(id, pd_intel_adsp_pm_action); \
DEVICE_DT_INST_DEFINE(id, pd_intel_adsp_init, PM_DEVICE_DT_INST_GET(id),\
&pd_pg_reg##id, NULL, POST_KERNEL, \
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, NULL);
#define POWER_DOMAIN_DEVICE(id) \
static struct pg_bits pd_pg_reg##id = { \
.SPA_bit = DT_INST_PROP(id, bit_position), \
.CPA_bit = DT_INST_PROP(id, bit_position), \
}; \
PM_DEVICE_DT_INST_DEFINE(id, pd_intel_adsp_pm_action); \
DEVICE_DT_INST_DEFINE(id, pd_intel_adsp_init, PM_DEVICE_DT_INST_GET(id), \
&pd_pg_reg##id, NULL, POST_KERNEL, \
CONFIG_POWER_DOMAIN_INIT_PRIORITY, NULL);
DT_INST_FOREACH_STATUS_OKAY(POWER_DOMAIN_DEVICE)

View file

@ -0,0 +1,22 @@
# Copyright (c) 2023 Google LLC
# SPDX-License-Identifier: Apache-2.0
description: |
Simple monitorig power domain
This power domain monitors the state of a GPIO pin to detect whether a power
rail is on/off. Therefore, performing resume/suspend on power domain won't
change physical state of power rails and those action won't be triggerd on
child nodes. Additionally, due to the asynchronous nature of monitoring a
pending transaction won't be interrupted by power state change.
compatible: "power-domain-gpio-monitor"
include: power-domain.yaml
properties:
gpios:
type: phandle-array
required: true
description: |
GPIO to use to sense if rail is powered on.