1 /*
2  * Copyright (c) 2023 FTP Technologies
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #define DT_DRV_COMPAT voltage_divider
8 
9 #include <zephyr/drivers/adc.h>
10 #include <zephyr/drivers/adc/voltage_divider.h>
11 #include <zephyr/drivers/gpio.h>
12 #include <zephyr/drivers/sensor.h>
13 #include <zephyr/pm/device.h>
14 #include <zephyr/pm/device_runtime.h>
15 
16 #include <zephyr/logging/log.h>
17 LOG_MODULE_REGISTER(voltage, CONFIG_SENSOR_LOG_LEVEL);
18 
19 struct voltage_config {
20 	struct voltage_divider_dt_spec voltage;
21 	struct gpio_dt_spec gpio_power;
22 	uint32_t sample_delay_us;
23 };
24 
25 struct voltage_data {
26 	struct adc_sequence sequence;
27 	k_timeout_t earliest_sample;
28 	uint16_t raw;
29 };
30 
fetch(const struct device * dev,enum sensor_channel chan)31 static int fetch(const struct device *dev, enum sensor_channel chan)
32 {
33 	const struct voltage_config *config = dev->config;
34 	struct voltage_data *data = dev->data;
35 	int ret;
36 
37 	if ((chan != SENSOR_CHAN_VOLTAGE) && (chan != SENSOR_CHAN_ALL)) {
38 		return -ENOTSUP;
39 	}
40 
41 	/* Wait until sampling is valid */
42 	k_sleep(data->earliest_sample);
43 
44 	/* configure the active channel to be converted */
45 	ret = adc_channel_setup_dt(&config->voltage.port);
46 	if (ret != 0) {
47 		LOG_ERR("adc_setup failed: %d", ret);
48 		return ret;
49 	}
50 
51 	/* start conversion */
52 	ret = adc_read(config->voltage.port.dev, &data->sequence);
53 	if (ret != 0) {
54 		LOG_ERR("adc_read: %d", ret);
55 	}
56 
57 	return ret;
58 }
59 
get(const struct device * dev,enum sensor_channel chan,struct sensor_value * val)60 static int get(const struct device *dev, enum sensor_channel chan, struct sensor_value *val)
61 {
62 	const struct voltage_config *config = dev->config;
63 	struct voltage_data *data = dev->data;
64 	int32_t raw_val;
65 	int32_t v_mv;
66 	int ret;
67 
68 	__ASSERT_NO_MSG(val != NULL);
69 
70 	if (chan != SENSOR_CHAN_VOLTAGE) {
71 		return -ENOTSUP;
72 	}
73 
74 	if (config->voltage.port.channel_cfg.differential) {
75 		raw_val = (int16_t)data->raw;
76 	} else {
77 		raw_val = data->raw;
78 	}
79 
80 	ret = adc_raw_to_millivolts_dt(&config->voltage.port, &raw_val);
81 	if (ret != 0) {
82 		LOG_ERR("raw_to_mv: %d", ret);
83 		return ret;
84 	}
85 
86 	v_mv = raw_val;
87 
88 	/* Note if full_ohms is not specified then unscaled voltage is returned */
89 	(void)voltage_divider_scale_dt(&config->voltage, &v_mv);
90 
91 	LOG_DBG("%d of %d, %dmV, voltage:%dmV", data->raw,
92 		(1 << data->sequence.resolution) - 1, raw_val, v_mv);
93 	val->val1 = v_mv / 1000;
94 	val->val2 = (v_mv * 1000) % 1000000;
95 
96 	return ret;
97 }
98 
99 static DEVICE_API(sensor, voltage_api) = {
100 	.sample_fetch = fetch,
101 	.channel_get = get,
102 };
103 
pm_action(const struct device * dev,enum pm_device_action action)104 static int pm_action(const struct device *dev, enum pm_device_action action)
105 {
106 	const struct voltage_config *config = dev->config;
107 	struct voltage_data *data = dev->data;
108 	int ret = 0;
109 
110 	if (config->gpio_power.port == NULL) {
111 		/* No work to do */
112 		return 0;
113 	}
114 
115 	switch (action) {
116 	case PM_DEVICE_ACTION_TURN_ON:
117 		ret = gpio_pin_configure_dt(&config->gpio_power, GPIO_OUTPUT_INACTIVE);
118 		if (ret != 0) {
119 			LOG_ERR("failed to configure GPIO for PM on");
120 		}
121 		break;
122 	case PM_DEVICE_ACTION_RESUME:
123 		ret = gpio_pin_set_dt(&config->gpio_power, 1);
124 		if (ret != 0) {
125 			LOG_ERR("failed to set GPIO for PM resume");
126 		}
127 		data->earliest_sample = K_TIMEOUT_ABS_TICKS(
128 			k_uptime_ticks() + k_us_to_ticks_ceil32(config->sample_delay_us));
129 		/* Power up ADC */
130 		ret = pm_device_runtime_get(config->voltage.port.dev);
131 		if (ret != 0) {
132 			LOG_ERR("failed to power up ADC (%d)", ret);
133 			return ret;
134 		}
135 		break;
136 #ifdef CONFIG_PM_DEVICE
137 	case PM_DEVICE_ACTION_SUSPEND:
138 		ret = gpio_pin_set_dt(&config->gpio_power, 0);
139 		if (ret != 0) {
140 			LOG_ERR("failed to set GPIO for PM suspend");
141 		}
142 		/* Power down ADC */
143 		ret = pm_device_runtime_put(config->voltage.port.dev);
144 		if (ret != 0) {
145 			LOG_ERR("failed to Power down ADC (%d)", ret);
146 			return ret;
147 		}
148 		break;
149 	case PM_DEVICE_ACTION_TURN_OFF:
150 		break;
151 #endif /* CONFIG_PM_DEVICE */
152 	default:
153 		return -ENOTSUP;
154 	}
155 
156 	return ret;
157 }
158 
voltage_init(const struct device * dev)159 static int voltage_init(const struct device *dev)
160 {
161 	const struct voltage_config *config = dev->config;
162 	struct voltage_data *data = dev->data;
163 	int ret;
164 
165 	/* Default value to use if `power-gpios` does not exist */
166 	data->earliest_sample = K_TIMEOUT_ABS_TICKS(0);
167 
168 	if (!adc_is_ready_dt(&config->voltage.port)) {
169 		LOG_ERR("ADC is not ready");
170 		return -ENODEV;
171 	}
172 
173 	if (config->gpio_power.port != NULL) {
174 		if (!gpio_is_ready_dt(&config->gpio_power)) {
175 			LOG_ERR("Power GPIO is not ready");
176 			return -ENODEV;
177 		}
178 	}
179 
180 	ret = adc_channel_setup_dt(&config->voltage.port);
181 	if (ret != 0) {
182 		LOG_ERR("setup: %d", ret);
183 		return ret;
184 	}
185 
186 	ret = adc_sequence_init_dt(&config->voltage.port, &data->sequence);
187 	if (ret != 0) {
188 		LOG_ERR("sequence init: %d", ret);
189 		return ret;
190 	}
191 
192 	data->sequence.buffer = &data->raw;
193 	data->sequence.buffer_size = sizeof(data->raw);
194 
195 	return pm_device_driver_init(dev, pm_action);
196 }
197 
198 #define VOLTAGE_INIT(inst)                                                                         \
199 	static struct voltage_data voltage_##inst##_data;                                          \
200                                                                                                    \
201 	static const struct voltage_config voltage_##inst##_config = {                             \
202 		.voltage = VOLTAGE_DIVIDER_DT_SPEC_GET(DT_DRV_INST(inst)),                         \
203 		.gpio_power = GPIO_DT_SPEC_INST_GET_OR(inst, power_gpios, {0}),                    \
204 		.sample_delay_us = DT_INST_PROP(inst, power_on_sample_delay_us),                   \
205 	};                                                                                         \
206                                                                                                    \
207 	PM_DEVICE_DT_INST_DEFINE(inst, pm_action);                                                 \
208                                                                                                    \
209 	SENSOR_DEVICE_DT_INST_DEFINE(inst, &voltage_init, PM_DEVICE_DT_INST_GET(inst),             \
210 			      &voltage_##inst##_data, &voltage_##inst##_config, POST_KERNEL,       \
211 			      CONFIG_SENSOR_INIT_PRIORITY, &voltage_api);
212 
213 DT_INST_FOREACH_STATUS_OKAY(VOLTAGE_INIT)
214