06cfbd4159
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>
142 lines
4.1 KiB
C
142 lines
4.1 KiB
C
/*
|
|
* Copyright (c) 2022, Commonwealth Scientific and Industrial Research
|
|
* Organisation (CSIRO) ABN 41 687 119 230.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT power_domain_gpio
|
|
|
|
#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, CONFIG_POWER_DOMAIN_LOG_LEVEL);
|
|
|
|
struct pd_gpio_config {
|
|
struct gpio_dt_spec enable;
|
|
uint32_t startup_delay_us;
|
|
uint32_t off_on_delay_us;
|
|
};
|
|
|
|
struct pd_gpio_data {
|
|
k_timeout_t next_boot;
|
|
};
|
|
|
|
struct pd_visitor_context {
|
|
const struct device *domain;
|
|
enum pm_device_action action;
|
|
};
|
|
|
|
#ifdef CONFIG_PM_DEVICE_POWER_DOMAIN
|
|
|
|
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;
|
|
}
|
|
|
|
(void)pm_device_action_run(dev, visitor_context->action);
|
|
return 0;
|
|
}
|
|
|
|
#endif
|
|
|
|
static int pd_gpio_pm_action(const struct device *dev,
|
|
enum pm_device_action action)
|
|
{
|
|
#ifdef CONFIG_PM_DEVICE_POWER_DOMAIN
|
|
struct pd_visitor_context context = {.domain = dev};
|
|
#endif
|
|
const struct pd_gpio_config *cfg = dev->config;
|
|
struct pd_gpio_data *data = dev->data;
|
|
int64_t next_boot_ticks;
|
|
int rc = 0;
|
|
|
|
/* Validate that blocking API's can be used */
|
|
if (!k_can_yield()) {
|
|
LOG_ERR("Blocking actions cannot run in this context");
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
switch (action) {
|
|
case PM_DEVICE_ACTION_RESUME:
|
|
/* Wait until we can boot again */
|
|
k_sleep(data->next_boot);
|
|
/* Switch power on */
|
|
gpio_pin_set_dt(&cfg->enable, 1);
|
|
LOG_INF("%s is now ON", dev->name);
|
|
/* Wait for domain to come up */
|
|
k_sleep(K_USEC(cfg->startup_delay_us));
|
|
#ifdef CONFIG_PM_DEVICE_POWER_DOMAIN
|
|
/* Notify devices on the domain they are now powered */
|
|
context.action = PM_DEVICE_ACTION_TURN_ON;
|
|
(void)device_supported_foreach(dev, pd_on_domain_visitor, &context);
|
|
#endif
|
|
break;
|
|
case PM_DEVICE_ACTION_SUSPEND:
|
|
#ifdef CONFIG_PM_DEVICE_POWER_DOMAIN
|
|
/* Notify devices on the domain that power is going down */
|
|
context.action = PM_DEVICE_ACTION_TURN_OFF;
|
|
(void)device_supported_foreach(dev, pd_on_domain_visitor, &context);
|
|
#endif
|
|
/* Switch power off */
|
|
gpio_pin_set_dt(&cfg->enable, 0);
|
|
LOG_INF("%s is now OFF", dev->name);
|
|
/* Store next time we can boot */
|
|
next_boot_ticks = k_uptime_ticks() + k_us_to_ticks_ceil32(cfg->off_on_delay_us);
|
|
data->next_boot = K_TIMEOUT_ABS_TICKS(next_boot_ticks);
|
|
break;
|
|
case PM_DEVICE_ACTION_TURN_ON:
|
|
/* Actively control the enable pin now that the device is powered */
|
|
gpio_pin_configure_dt(&cfg->enable, GPIO_OUTPUT_INACTIVE);
|
|
LOG_DBG("%s is OFF and powered", dev->name);
|
|
break;
|
|
case PM_DEVICE_ACTION_TURN_OFF:
|
|
/* Let the enable pin float while device is not powered */
|
|
gpio_pin_configure_dt(&cfg->enable, GPIO_DISCONNECTED);
|
|
LOG_DBG("%s is OFF and not powered", dev->name);
|
|
break;
|
|
default:
|
|
rc = -ENOTSUP;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int pd_gpio_init(const struct device *dev)
|
|
{
|
|
const struct pd_gpio_config *cfg = dev->config;
|
|
struct pd_gpio_data *data = dev->data;
|
|
|
|
if (!gpio_is_ready_dt(&cfg->enable)) {
|
|
LOG_ERR("GPIO port %s is not ready", cfg->enable.port->name);
|
|
return -ENODEV;
|
|
}
|
|
/* We can't know how long the domain has been off for before boot */
|
|
data->next_boot = K_TIMEOUT_ABS_US(cfg->off_on_delay_us);
|
|
|
|
/* Boot according to state */
|
|
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, CONFIG_POWER_DOMAIN_INIT_PRIORITY, \
|
|
NULL);
|
|
|
|
DT_INST_FOREACH_STATUS_OKAY(POWER_DOMAIN_DEVICE)
|