/* * 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 #include #include #include #include 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_base->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_GPIO_INIT_PRIORITY, \ NULL); DT_INST_FOREACH_STATUS_OKAY(POWER_DOMAIN_DEVICE)