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:
parent
a17d49511f
commit
06cfbd4159
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
150
drivers/power_domain/power_domain_gpio_monitor.c
Normal file
150
drivers/power_domain/power_domain_gpio_monitor.c
Normal 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)
|
|
@ -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)
|
||||
|
|
22
dts/bindings/power-domain/power-domain-gpio-monitor.yaml
Normal file
22
dts/bindings/power-domain/power-domain-gpio-monitor.yaml
Normal 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.
|
Loading…
Reference in a new issue