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, ¤t_init, PM_DEVICE_DT_INST_GET(inst), \
225 ¤t_amp_##inst##_data, ¤t_amp_##inst##_config, \
226 POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY, ¤t_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