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