1 /*
2 * Copyright (c) 2022, Commonwealth Scientific and Industrial Research
3 * Organisation (CSIRO) ABN 41 687 119 230.
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 */
7
8 #define DT_DRV_COMPAT power_domain_gpio
9
10 #include <zephyr/kernel.h>
11 #include <zephyr/drivers/gpio.h>
12 #include <zephyr/pm/device.h>
13 #include <zephyr/pm/device_runtime.h>
14
15 #include <zephyr/logging/log.h>
16 LOG_MODULE_REGISTER(power_domain_gpio, CONFIG_POWER_DOMAIN_LOG_LEVEL);
17
18 struct pd_gpio_config {
19 struct gpio_dt_spec enable;
20 uint32_t startup_delay_us;
21 uint32_t off_on_delay_us;
22 };
23
24 struct pd_gpio_data {
25 k_timeout_t next_boot;
26 };
27
28 struct pd_visitor_context {
29 const struct device *domain;
30 enum pm_device_action action;
31 };
32
33 #ifdef CONFIG_PM_DEVICE_POWER_DOMAIN
34
pd_on_domain_visitor(const struct device * dev,void * context)35 static int pd_on_domain_visitor(const struct device *dev, void *context)
36 {
37 struct pd_visitor_context *visitor_context = context;
38
39 /* Only run action if the device is on the specified domain */
40 if (!dev->pm || (dev->pm_base->domain != visitor_context->domain)) {
41 return 0;
42 }
43
44 (void)pm_device_action_run(dev, visitor_context->action);
45 return 0;
46 }
47
48 #endif
49
pd_gpio_pm_action(const struct device * dev,enum pm_device_action action)50 static int pd_gpio_pm_action(const struct device *dev,
51 enum pm_device_action action)
52 {
53 #ifdef CONFIG_PM_DEVICE_POWER_DOMAIN
54 struct pd_visitor_context context = {.domain = dev};
55 #endif
56 const struct pd_gpio_config *cfg = dev->config;
57 struct pd_gpio_data *data = dev->data;
58 int64_t next_boot_ticks;
59 int rc = 0;
60
61 /* Validate that blocking API's can be used */
62 if (!k_can_yield()) {
63 LOG_ERR("Blocking actions cannot run in this context");
64 return -ENOTSUP;
65 }
66
67 switch (action) {
68 case PM_DEVICE_ACTION_RESUME:
69 /* Wait until we can boot again */
70 k_sleep(data->next_boot);
71 /* Switch power on */
72 gpio_pin_set_dt(&cfg->enable, 1);
73 LOG_INF("%s is now ON", dev->name);
74 /* Wait for domain to come up */
75 k_sleep(K_USEC(cfg->startup_delay_us));
76 #ifdef CONFIG_PM_DEVICE_POWER_DOMAIN
77 /* Notify devices on the domain they are now powered */
78 context.action = PM_DEVICE_ACTION_TURN_ON;
79 (void)device_supported_foreach(dev, pd_on_domain_visitor, &context);
80 #endif
81 break;
82 case PM_DEVICE_ACTION_SUSPEND:
83 #ifdef CONFIG_PM_DEVICE_POWER_DOMAIN
84 /* Notify devices on the domain that power is going down */
85 context.action = PM_DEVICE_ACTION_TURN_OFF;
86 (void)device_supported_foreach(dev, pd_on_domain_visitor, &context);
87 #endif
88 /* Switch power off */
89 gpio_pin_set_dt(&cfg->enable, 0);
90 LOG_INF("%s is now OFF", dev->name);
91 /* Store next time we can boot */
92 next_boot_ticks = k_uptime_ticks() + k_us_to_ticks_ceil32(cfg->off_on_delay_us);
93 data->next_boot = K_TIMEOUT_ABS_TICKS(next_boot_ticks);
94 break;
95 case PM_DEVICE_ACTION_TURN_ON:
96 /* Actively control the enable pin now that the device is powered */
97 gpio_pin_configure_dt(&cfg->enable, GPIO_OUTPUT_INACTIVE);
98 LOG_DBG("%s is OFF and powered", dev->name);
99 break;
100 case PM_DEVICE_ACTION_TURN_OFF:
101 /* Let the enable pin float while device is not powered */
102 gpio_pin_configure_dt(&cfg->enable, GPIO_DISCONNECTED);
103 LOG_DBG("%s is OFF and not powered", dev->name);
104 break;
105 default:
106 rc = -ENOTSUP;
107 }
108
109 return rc;
110 }
111
pd_gpio_init(const struct device * dev)112 static int pd_gpio_init(const struct device *dev)
113 {
114 const struct pd_gpio_config *cfg = dev->config;
115 struct pd_gpio_data *data = dev->data;
116
117 if (!gpio_is_ready_dt(&cfg->enable)) {
118 LOG_ERR("GPIO port %s is not ready", cfg->enable.port->name);
119 return -ENODEV;
120 }
121 /* We can't know how long the domain has been off for before boot */
122 data->next_boot = K_TIMEOUT_ABS_US(cfg->off_on_delay_us);
123
124 /* Boot according to state */
125 return pm_device_driver_init(dev, pd_gpio_pm_action);
126 }
127
128 #define POWER_DOMAIN_DEVICE(id) \
129 static const struct pd_gpio_config pd_gpio_##id##_cfg = { \
130 .enable = GPIO_DT_SPEC_INST_GET(id, enable_gpios), \
131 .startup_delay_us = DT_INST_PROP(id, startup_delay_us), \
132 .off_on_delay_us = DT_INST_PROP(id, off_on_delay_us), \
133 }; \
134 static struct pd_gpio_data pd_gpio_##id##_data; \
135 PM_DEVICE_DT_INST_DEFINE(id, pd_gpio_pm_action); \
136 DEVICE_DT_INST_DEFINE(id, pd_gpio_init, PM_DEVICE_DT_INST_GET(id), \
137 &pd_gpio_##id##_data, &pd_gpio_##id##_cfg, \
138 POST_KERNEL, CONFIG_POWER_DOMAIN_GPIO_INIT_PRIORITY, \
139 NULL);
140
141 DT_INST_FOREACH_STATUS_OKAY(POWER_DOMAIN_DEVICE)
142