1 /*
2  * Copyright (c) 2023 FTP Technologies
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #define DT_DRV_COMPAT current_sense_amplifier
8 
9 #include <stdlib.h>
10 
11 #include <zephyr/drivers/adc.h>
12 #include <zephyr/drivers/adc/current_sense_amplifier.h>
13 #include <zephyr/drivers/gpio.h>
14 #include <zephyr/drivers/sensor.h>
15 #include <zephyr/pm/device.h>
16 #include <zephyr/sys/__assert.h>
17 
18 #include <zephyr/logging/log.h>
19 LOG_MODULE_REGISTER(current_amp, CONFIG_SENSOR_LOG_LEVEL);
20 
21 #if DT_ANY_INST_HAS_PROP_STATUS_OKAY(gain_extended_range)
22 #define INST_HAS_EXTENDED_RANGE 1
23 #endif
24 
25 struct current_sense_amplifier_data {
26 #ifdef INST_HAS_EXTENDED_RANGE
27 	const struct adc_channel_cfg *sample_channel_cfg;
28 	struct adc_channel_cfg channel_cfg_extended_range;
29 	int16_t adc_max;
30 #endif /* INST_HAS_EXTENDED_RANGE */
31 	struct adc_sequence sequence;
32 	int16_t raw;
33 };
34 
fetch(const struct device * dev,enum sensor_channel chan)35 static int fetch(const struct device *dev, enum sensor_channel chan)
36 {
37 	const struct current_sense_amplifier_dt_spec *config = dev->config;
38 	struct current_sense_amplifier_data *data = dev->data;
39 	int ret;
40 
41 	if ((chan != SENSOR_CHAN_CURRENT) && (chan != SENSOR_CHAN_ALL)) {
42 		return -ENOTSUP;
43 	}
44 
45 	ret = adc_read_dt(&config->port, &data->sequence);
46 	if (ret != 0) {
47 		LOG_ERR("adc_read: %d", ret);
48 	}
49 #ifdef INST_HAS_EXTENDED_RANGE
50 	data->sample_channel_cfg = &config->port.channel_cfg;
51 
52 	/* Initial measurement hit the limits, and an alternate gain has been defined */
53 	if ((data->raw == data->adc_max) && (config->gain_extended_range != 0xFF)) {
54 		int ret2;
55 
56 		/* Reconfigure channel for the extended range setup.
57 		 * Updating configurations should not fail since they were validated in `init`.
58 		 */
59 		ret = adc_channel_setup(config->port.dev, &data->channel_cfg_extended_range);
60 		__ASSERT_NO_MSG(ret == 0);
61 		/* Sample again at the higher range/lower resolution */
62 		ret = adc_read_dt(&config->port, &data->sequence);
63 		if (ret != 0) {
64 			LOG_ERR("adc_read: %d", ret);
65 		}
66 
67 		/* Put channel back to original configuration */
68 		ret2 = adc_channel_setup_dt(&config->port);
69 		__ASSERT_NO_MSG(ret2 == 0);
70 
71 		/* Sample was measured with the extended range configuration */
72 		data->sample_channel_cfg = &data->channel_cfg_extended_range;
73 	}
74 #endif /* INST_HAS_EXTENDED_RANGE */
75 
76 	return ret;
77 }
78 
get(const struct device * dev,enum sensor_channel chan,struct sensor_value * val)79 static int get(const struct device *dev, enum sensor_channel chan, struct sensor_value *val)
80 {
81 	const struct current_sense_amplifier_dt_spec *config = dev->config;
82 	struct current_sense_amplifier_data *data = dev->data;
83 	int32_t v_uv = data->raw;
84 	int32_t i_ua;
85 	int ret;
86 
87 	__ASSERT_NO_MSG(val != NULL);
88 
89 	if (chan != SENSOR_CHAN_CURRENT) {
90 		return -ENOTSUP;
91 	}
92 
93 	if (abs(data->raw) < config->noise_threshold) {
94 		return sensor_value_from_micro(val, 0);
95 	}
96 
97 #ifdef INST_HAS_EXTENDED_RANGE
98 	ret = adc_raw_to_x_dt_chan(adc_raw_to_microvolts, &config->port, data->sample_channel_cfg,
99 				   &v_uv);
100 #else
101 	ret = adc_raw_to_microvolts_dt(&config->port, &v_uv);
102 #endif
103 	if (ret != 0) {
104 		LOG_ERR("raw_to_mv: %d", ret);
105 		return ret;
106 	}
107 
108 	i_ua = current_sense_amplifier_scale_ua_dt(config, v_uv);
109 	LOG_DBG("%d/%d, %d uV, current:%d uA", data->raw, (1 << data->sequence.resolution) - 1,
110 		v_uv, i_ua);
111 
112 	return sensor_value_from_micro(val, i_ua);
113 }
114 
115 static DEVICE_API(sensor, current_api) = {
116 	.sample_fetch = fetch,
117 	.channel_get = get,
118 };
119 
120 #ifdef CONFIG_PM_DEVICE
pm_action(const struct device * dev,enum pm_device_action action)121 static int pm_action(const struct device *dev, enum pm_device_action action)
122 {
123 	const struct current_sense_amplifier_dt_spec *config = dev->config;
124 	int ret;
125 
126 	if (config->power_gpio.port == NULL) {
127 		return 0;
128 	}
129 
130 	switch (action) {
131 	case PM_DEVICE_ACTION_RESUME:
132 		ret = gpio_pin_set_dt(&config->power_gpio, 1);
133 		if (ret != 0) {
134 			LOG_ERR("failed to set GPIO for PM resume");
135 			return ret;
136 		}
137 		break;
138 	case PM_DEVICE_ACTION_SUSPEND:
139 		ret = gpio_pin_set_dt(&config->power_gpio, 0);
140 		if (ret != 0) {
141 			LOG_ERR("failed to set GPIO for PM suspend");
142 			return ret;
143 		}
144 		break;
145 	default:
146 		return -ENOTSUP;
147 	}
148 
149 	return 0;
150 }
151 #endif
152 
current_init(const struct device * dev)153 static int current_init(const struct device *dev)
154 {
155 	const struct current_sense_amplifier_dt_spec *config = dev->config;
156 	struct current_sense_amplifier_data *data = dev->data;
157 	int ret;
158 
159 	__ASSERT(config->sense_milli_ohms != 0, "Milli-ohms must not be 0");
160 
161 	if (!adc_is_ready_dt(&config->port)) {
162 		LOG_ERR("ADC is not ready");
163 		return -ENODEV;
164 	}
165 
166 #ifdef CONFIG_PM_DEVICE
167 	if (config->power_gpio.port != NULL) {
168 		if (!gpio_is_ready_dt(&config->power_gpio)) {
169 			LOG_ERR("Power GPIO is not ready");
170 			return -ENODEV;
171 		}
172 
173 		ret = gpio_pin_configure_dt(&config->power_gpio, GPIO_OUTPUT_ACTIVE);
174 		if (ret != 0) {
175 			LOG_ERR("failed to config GPIO: %d", ret);
176 			return ret;
177 		}
178 	}
179 #endif
180 
181 #ifdef INST_HAS_EXTENDED_RANGE
182 	if (config->gain_extended_range != 0xFF) {
183 		memcpy(&data->channel_cfg_extended_range, &config->port.channel_cfg,
184 		       sizeof(data->channel_cfg_extended_range));
185 		data->channel_cfg_extended_range.gain = config->gain_extended_range;
186 		data->adc_max = (1 << config->port.resolution) - 1;
187 
188 		/* Test setup of configuration to catch invalid values */
189 		ret = adc_channel_setup(config->port.dev, &data->channel_cfg_extended_range);
190 		if (ret != 0) {
191 			LOG_ERR("ext_setup: %d", ret);
192 			return ret;
193 		}
194 	}
195 #endif /* INST_HAS_EXTENDED_RANGE */
196 
197 	ret = adc_channel_setup_dt(&config->port);
198 	if (ret != 0) {
199 		LOG_ERR("setup: %d", ret);
200 		return ret;
201 	}
202 
203 	ret = adc_sequence_init_dt(&config->port, &data->sequence);
204 	if (ret != 0) {
205 		LOG_ERR("sequence init: %d", ret);
206 		return ret;
207 	}
208 
209 	data->sequence.buffer = &data->raw;
210 	data->sequence.buffer_size = sizeof(data->raw);
211 	data->sequence.calibrate = config->enable_calibration;
212 
213 	return 0;
214 }
215 
216 #define CURRENT_SENSE_AMPLIFIER_INIT(inst)                                                         \
217 	static struct current_sense_amplifier_data current_amp_##inst##_data;                      \
218                                                                                                    \
219 	static const struct current_sense_amplifier_dt_spec current_amp_##inst##_config =          \
220 		CURRENT_SENSE_AMPLIFIER_DT_SPEC_GET(DT_DRV_INST(inst));                            \
221                                                                                                    \
222 	PM_DEVICE_DT_INST_DEFINE(inst, pm_action);                                                 \
223                                                                                                    \
224 	SENSOR_DEVICE_DT_INST_DEFINE(inst, &current_init, PM_DEVICE_DT_INST_GET(inst),             \
225 				     &current_amp_##inst##_data, &current_amp_##inst##_config,     \
226 				     POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY, &current_api);      \
227                                                                                                    \
228 	BUILD_ASSERT((DT_INST_PROP(inst, zero_current_voltage_mv) == 0) ||                         \
229 			     (DT_INST_PROP(inst, sense_resistor_milli_ohms) == 1),                 \
230 		     "zero_current_voltage_mv requires sense_resistor_milli_ohms == 1");
231 
232 DT_INST_FOREACH_STATUS_OKAY(CURRENT_SENSE_AMPLIFIER_INIT)
233