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