1 /*
2 * Copyright (c) 2023 Google LLC
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #define DT_DRV_COMPAT power_domain_gpio_monitor
8
9 #include <zephyr/kernel.h>
10 #include <zephyr/drivers/gpio.h>
11 #include <zephyr/pm/device.h>
12 #include <zephyr/pm/device_runtime.h>
13
14 #include <zephyr/logging/log.h>
15
16 LOG_MODULE_REGISTER(power_domain_gpio_monitor, CONFIG_POWER_DOMAIN_LOG_LEVEL);
17
18 struct pd_gpio_monitor_config {
19 struct gpio_dt_spec power_good_gpio;
20 };
21
22 struct pd_gpio_monitor_data {
23 struct gpio_callback callback;
24 const struct device *dev;
25 bool is_powered;
26 };
27
28 struct pd_visitor_context {
29 const struct device *domain;
30 enum pm_device_action action;
31 };
32
pd_on_domain_visitor(const struct device * dev,void * context)33 static int pd_on_domain_visitor(const struct device *dev, void *context)
34 {
35 struct pd_visitor_context *visitor_context = context;
36
37 /* Only run action if the device is on the specified domain */
38 if (!dev->pm || (dev->pm_base->domain != visitor_context->domain)) {
39 return 0;
40 }
41
42 dev->pm->usage = 0;
43 (void)pm_device_action_run(dev, visitor_context->action);
44 return 0;
45 }
46
pd_gpio_monitor_callback(const struct device * port,struct gpio_callback * cb,gpio_port_pins_t pins)47 static void pd_gpio_monitor_callback(const struct device *port,
48 struct gpio_callback *cb, gpio_port_pins_t pins)
49 {
50 struct pd_gpio_monitor_data *data = CONTAINER_OF(cb, struct pd_gpio_monitor_data, callback);
51 const struct pd_gpio_monitor_config *config = data->dev->config;
52 const struct device *dev = data->dev;
53 struct pd_visitor_context context = {.domain = dev};
54 int rc;
55
56 rc = gpio_pin_get_dt(&config->power_good_gpio);
57 if (rc < 0) {
58 LOG_WRN("Failed to read gpio logic level");
59 return;
60 }
61
62 data->is_powered = rc;
63 if (rc == 0) {
64 context.action = PM_DEVICE_ACTION_SUSPEND;
65 (void)device_supported_foreach(dev, pd_on_domain_visitor, &context);
66 context.action = PM_DEVICE_ACTION_TURN_OFF;
67 (void)device_supported_foreach(dev, pd_on_domain_visitor, &context);
68 return;
69 }
70
71 pm_device_children_action_run(data->dev, PM_DEVICE_ACTION_TURN_ON, NULL);
72 }
73
pd_gpio_monitor_pm_action(const struct device * dev,enum pm_device_action action)74 static int pd_gpio_monitor_pm_action(const struct device *dev, enum pm_device_action action)
75 {
76 struct pd_gpio_monitor_data *data = dev->data;
77
78 switch (action) {
79 case PM_DEVICE_ACTION_TURN_ON:
80 case PM_DEVICE_ACTION_TURN_OFF:
81 return -ENOTSUP;
82 case PM_DEVICE_ACTION_RESUME:
83 if (!data->is_powered) {
84 return -EAGAIN;
85 }
86 break;
87 default:
88 break;
89 }
90
91 return 0;
92 }
93
pd_gpio_monitor_init(const struct device * dev)94 static int pd_gpio_monitor_init(const struct device *dev)
95 {
96 const struct pd_gpio_monitor_config *config = dev->config;
97 struct pd_gpio_monitor_data *data = dev->data;
98 int rc;
99
100 data->dev = dev;
101
102 if (!gpio_is_ready_dt(&config->power_good_gpio)) {
103 LOG_ERR("GPIO port %s is not ready", config->power_good_gpio.port->name);
104 return -ENODEV;
105 }
106
107 rc = gpio_pin_configure_dt(&config->power_good_gpio, GPIO_INPUT);
108 if (rc) {
109 LOG_ERR("Failed to configure GPIO");
110 goto unconfigure_pin;
111 }
112
113 rc = gpio_pin_interrupt_configure_dt(&config->power_good_gpio, GPIO_INT_EDGE_BOTH);
114 if (rc) {
115 gpio_pin_configure_dt(&config->power_good_gpio, GPIO_DISCONNECTED);
116 LOG_ERR("Failed to configure GPIO interrupt");
117 goto unconfigure_interrupt;
118 }
119
120 gpio_init_callback(&data->callback, pd_gpio_monitor_callback,
121 BIT(config->power_good_gpio.pin));
122 rc = gpio_add_callback_dt(&config->power_good_gpio, &data->callback);
123 if (rc) {
124 LOG_ERR("Failed to add GPIO callback");
125 goto remove_callback;
126 }
127
128 pm_device_init_suspended(dev);
129 return pm_device_runtime_enable(dev);
130 remove_callback:
131 gpio_remove_callback(config->power_good_gpio.port, &data->callback);
132 unconfigure_interrupt:
133 gpio_pin_interrupt_configure_dt(&config->power_good_gpio, GPIO_INT_DISABLE);
134 unconfigure_pin:
135 gpio_pin_configure_dt(&config->power_good_gpio, GPIO_DISCONNECTED);
136 return rc;
137 }
138
139 #define POWER_DOMAIN_DEVICE(inst) \
140 static const struct pd_gpio_monitor_config pd_gpio_monitor_config_##inst = { \
141 .power_good_gpio = GPIO_DT_SPEC_INST_GET(inst, gpios), \
142 }; \
143 static struct pd_gpio_monitor_data pd_gpio_monitor_data_##inst; \
144 PM_DEVICE_DT_INST_DEFINE(inst, pd_gpio_monitor_pm_action); \
145 DEVICE_DT_INST_DEFINE(inst, pd_gpio_monitor_init, \
146 PM_DEVICE_DT_INST_GET(inst), &pd_gpio_monitor_data_##inst, \
147 &pd_gpio_monitor_config_##inst, POST_KERNEL, \
148 CONFIG_POWER_DOMAIN_GPIO_MONITOR_INIT_PRIORITY, NULL);
149
150 DT_INST_FOREACH_STATUS_OKAY(POWER_DOMAIN_DEVICE)
151