1 /*
2 * Copyright 2021 The Chromium OS Authors
3 * Copyright 2021 Grinn
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 */
7
8 #include "ina230.h"
9 #include "ina23x_common.h"
10
11 #include <zephyr/logging/log.h>
12 #include <zephyr/drivers/sensor.h>
13
14 LOG_MODULE_REGISTER(INA230, CONFIG_SENSOR_LOG_LEVEL);
15
16 /** @brief Calibration scaling value (value scaled by 100000) */
17 #define INA230_CAL_SCALING 512ULL
18
19 /** @brief The LSB value for the bus voltage register, in microvolts/LSB. */
20 #define INA230_BUS_VOLTAGE_UV_LSB 1250U
21 #define INA236_BUS_VOLTAGE_UV_LSB 1600U
22
23 /** @brief The scaling for the power register. */
24 #define INA230_POWER_SCALING 25
25 #define INA236_POWER_SCALING 32
26
ina230_channel_get(const struct device * dev,enum sensor_channel chan,struct sensor_value * val)27 static int ina230_channel_get(const struct device *dev, enum sensor_channel chan,
28 struct sensor_value *val)
29 {
30 struct ina230_data *data = dev->data;
31 const struct ina230_config *const config = dev->config;
32 uint32_t bus_uv, power_uw;
33 int32_t current_ua;
34
35 switch (chan) {
36 case SENSOR_CHAN_VOLTAGE:
37 bus_uv = data->bus_voltage * config->uv_lsb;
38
39 /* convert to fractional volts (units for voltage channel) */
40 val->val1 = bus_uv / 1000000U;
41 val->val2 = bus_uv % 1000000U;
42 break;
43
44 case SENSOR_CHAN_CURRENT:
45 /* see datasheet "Programming" section for reference */
46 current_ua = data->current * config->current_lsb;
47
48 /* convert to fractional amperes */
49 val->val1 = current_ua / 1000000L;
50 val->val2 = current_ua % 1000000L;
51 break;
52
53 case SENSOR_CHAN_POWER:
54 power_uw = data->power * config->power_scale * config->current_lsb;
55
56 /* convert to fractional watts */
57 val->val1 = (int32_t)(power_uw / 1000000U);
58 val->val2 = (int32_t)(power_uw % 1000000U);
59
60 break;
61
62 default:
63 return -ENOTSUP;
64 }
65
66 return 0;
67 }
68
ina230_sample_fetch(const struct device * dev,enum sensor_channel chan)69 static int ina230_sample_fetch(const struct device *dev, enum sensor_channel chan)
70 {
71 struct ina230_data *data = dev->data;
72 const struct ina230_config *config = dev->config;
73 int ret;
74
75 if (chan != SENSOR_CHAN_ALL && chan != SENSOR_CHAN_VOLTAGE && chan != SENSOR_CHAN_CURRENT &&
76 chan != SENSOR_CHAN_POWER) {
77 return -ENOTSUP;
78 }
79
80 if ((chan == SENSOR_CHAN_ALL) || (chan == SENSOR_CHAN_VOLTAGE)) {
81 ret = ina23x_reg_read_16(&config->bus, INA230_REG_BUS_VOLT, &data->bus_voltage);
82 if (ret < 0) {
83 LOG_ERR("Failed to read bus voltage");
84 return ret;
85 }
86 }
87
88 if ((chan == SENSOR_CHAN_ALL) || (chan == SENSOR_CHAN_CURRENT)) {
89 ret = ina23x_reg_read_16(&config->bus, INA230_REG_CURRENT, &data->current);
90 if (ret < 0) {
91 LOG_ERR("Failed to read current");
92 return ret;
93 }
94 }
95
96 if ((chan == SENSOR_CHAN_ALL) || (chan == SENSOR_CHAN_POWER)) {
97 ret = ina23x_reg_read_16(&config->bus, INA230_REG_POWER, &data->power);
98 if (ret < 0) {
99 LOG_ERR("Failed to read power");
100 return ret;
101 }
102 }
103
104 return 0;
105 }
106
ina230_attr_set(const struct device * dev,enum sensor_channel chan,enum sensor_attribute attr,const struct sensor_value * val)107 static int ina230_attr_set(const struct device *dev, enum sensor_channel chan,
108 enum sensor_attribute attr, const struct sensor_value *val)
109 {
110 const struct ina230_config *config = dev->config;
111 uint16_t data = val->val1;
112
113 switch (attr) {
114 case SENSOR_ATTR_CONFIGURATION:
115 return ina23x_reg_write(&config->bus, INA230_REG_CONFIG, data);
116 case SENSOR_ATTR_CALIBRATION:
117 return ina23x_reg_write(&config->bus, INA230_REG_CALIB, data);
118 case SENSOR_ATTR_FEATURE_MASK:
119 return ina23x_reg_write(&config->bus, INA230_REG_MASK, data);
120 case SENSOR_ATTR_ALERT:
121 return ina23x_reg_write(&config->bus, INA230_REG_ALERT, data);
122 default:
123 LOG_ERR("INA230 attribute not supported.");
124 return -ENOTSUP;
125 }
126 }
127
ina230_attr_get(const struct device * dev,enum sensor_channel chan,enum sensor_attribute attr,struct sensor_value * val)128 static int ina230_attr_get(const struct device *dev, enum sensor_channel chan,
129 enum sensor_attribute attr, struct sensor_value *val)
130 {
131 const struct ina230_config *config = dev->config;
132 uint16_t data;
133 int ret;
134
135 switch (attr) {
136 case SENSOR_ATTR_CONFIGURATION:
137 ret = ina23x_reg_read_16(&config->bus, INA230_REG_CONFIG, &data);
138 if (ret < 0) {
139 return ret;
140 }
141 break;
142 case SENSOR_ATTR_CALIBRATION:
143 ret = ina23x_reg_read_16(&config->bus, INA230_REG_CALIB, &data);
144 if (ret < 0) {
145 return ret;
146 }
147 break;
148 case SENSOR_ATTR_FEATURE_MASK:
149 ret = ina23x_reg_read_16(&config->bus, INA230_REG_MASK, &data);
150 if (ret < 0) {
151 return ret;
152 }
153 break;
154 case SENSOR_ATTR_ALERT:
155 ret = ina23x_reg_read_16(&config->bus, INA230_REG_ALERT, &data);
156 if (ret < 0) {
157 return ret;
158 }
159 break;
160 default:
161 LOG_ERR("INA230 attribute not supported.");
162 return -ENOTSUP;
163 }
164
165 val->val1 = data;
166 val->val2 = 0;
167
168 return 0;
169 }
170
ina230_calibrate(const struct device * dev)171 static int ina230_calibrate(const struct device *dev)
172 {
173 const struct ina230_config *config = dev->config;
174 int ret;
175
176 /* See datasheet "Programming" section */
177 ret = ina23x_reg_write(&config->bus, INA230_REG_CALIB, config->cal);
178 if (ret < 0) {
179 return ret;
180 }
181
182 return 0;
183 }
184
ina230_init(const struct device * dev)185 static int ina230_init(const struct device *dev)
186 {
187 const struct ina230_config *const config = dev->config;
188 int ret;
189
190 if (!device_is_ready(config->bus.bus)) {
191 LOG_ERR("I2C bus %s is not ready", config->bus.bus->name);
192 return -ENODEV;
193 }
194
195 ret = ina23x_reg_write(&config->bus, INA230_REG_CONFIG, config->config);
196 if (ret < 0) {
197 LOG_ERR("Failed to write configuration register!");
198 return ret;
199 }
200
201 ret = ina230_calibrate(dev);
202 if (ret < 0) {
203 LOG_ERR("Failed to write calibration register!");
204 return ret;
205 }
206
207 #ifdef CONFIG_INA230_TRIGGER
208 if (config->trig_enabled) {
209 ret = ina230_trigger_mode_init(dev);
210 if (ret < 0) {
211 LOG_ERR("Failed to init trigger mode\n");
212 return ret;
213 }
214
215 ret = ina23x_reg_write(&config->bus, INA230_REG_ALERT, config->alert_limit);
216 if (ret < 0) {
217 LOG_ERR("Failed to write alert register!");
218 return ret;
219 }
220
221 ret = ina23x_reg_write(&config->bus, INA230_REG_MASK, config->mask);
222 if (ret < 0) {
223 LOG_ERR("Failed to write mask register!");
224 return ret;
225 }
226 }
227 #endif /* CONFIG_INA230_TRIGGER */
228
229 return 0;
230 }
231
232 static DEVICE_API(sensor, ina230_driver_api) = {
233 .attr_set = ina230_attr_set,
234 .attr_get = ina230_attr_get,
235 #ifdef CONFIG_INA230_TRIGGER
236 .trigger_set = ina230_trigger_set,
237 #endif
238 .sample_fetch = ina230_sample_fetch,
239 .channel_get = ina230_channel_get,
240 };
241
242 #ifdef CONFIG_INA230_TRIGGER
243 #define INA230_CFG_IRQ(inst) \
244 .trig_enabled = true, .mask = DT_INST_PROP(inst, mask), \
245 .alert_limit = DT_INST_PROP(inst, alert_limit), \
246 .alert_gpio = GPIO_DT_SPEC_INST_GET(inst, alert_gpios)
247 #else
248 #define INA230_CFG_IRQ(inst)
249 #endif /* CONFIG_INA230_TRIGGER */
250
251 #define INA230_DRIVER_INIT(inst, type) \
252 static struct ina230_data drv_data_##type##inst; \
253 static const struct ina230_config drv_config_##type##inst = { \
254 .bus = I2C_DT_SPEC_INST_GET(inst), \
255 .config = (DT_INST_PROP_OR(inst, high_precision, 0) << 12) | \
256 DT_INST_PROP(inst, config) | (DT_INST_ENUM_IDX(inst, avg_count) << 9) | \
257 (DT_INST_ENUM_IDX(inst, vbus_conversion_time_us) << 6) | \
258 (DT_INST_ENUM_IDX(inst, vshunt_conversion_time_us) << 3) | \
259 DT_INST_ENUM_IDX(inst, adc_mode), \
260 .current_lsb = DT_INST_PROP(inst, current_lsb_microamps), \
261 .uv_lsb = type##_BUS_VOLTAGE_UV_LSB, \
262 .power_scale = type##_POWER_SCALING, \
263 .cal = (uint16_t)(((INA230_CAL_SCALING * 10000000ULL) / \
264 ((uint64_t)DT_INST_PROP(inst, current_lsb_microamps) * \
265 DT_INST_PROP(inst, rshunt_micro_ohms))) >> \
266 (DT_INST_PROP_OR(inst, high_precision, 0) << 1)), \
267 COND_CODE_1(DT_INST_NODE_HAS_PROP(inst, alert_gpios), (INA230_CFG_IRQ(inst)), \
268 ())}; \
269 SENSOR_DEVICE_DT_INST_DEFINE(inst, &ina230_init, NULL, &drv_data_##type##inst, \
270 &drv_config_##type##inst, POST_KERNEL, \
271 CONFIG_SENSOR_INIT_PRIORITY, &ina230_driver_api);
272
273 #undef DT_DRV_COMPAT
274 #define DT_DRV_COMPAT ti_ina230
275 DT_INST_FOREACH_STATUS_OKAY_VARGS(INA230_DRIVER_INIT, INA230)
276
277 #undef DT_DRV_COMPAT
278 #define DT_DRV_COMPAT ti_ina236
279 DT_INST_FOREACH_STATUS_OKAY_VARGS(INA230_DRIVER_INIT, INA236)
280