1 /*
2  * Copyright 2024 NXP
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 /* TODO: Add functionality for Trigger. */
8 
9 #define DT_DRV_COMPAT ti_ina226
10 
11 #include <zephyr/logging/log.h>
12 #include <zephyr/drivers/sensor.h>
13 #include <zephyr/dt-bindings/sensor/ina226.h>
14 #include <zephyr/sys/byteorder.h>
15 
16 #include <zephyr/sys/util_macro.h>
17 #include <stdint.h>
18 #include <zephyr/device.h>
19 #include <zephyr/drivers/gpio.h>
20 #include <zephyr/drivers/i2c.h>
21 
22 /* Device register addresses. */
23 #define INA226_REG_CONFIG		0x00
24 #define INA226_REG_SHUNT_VOLT		0x01
25 #define INA226_REG_BUS_VOLT		0x02
26 #define INA226_REG_POWER		0x03
27 #define INA226_REG_CURRENT		0x04
28 #define INA226_REG_CALIB		0x05
29 #define INA226_REG_MASK			0x06
30 #define INA226_REG_ALERT		0x07
31 #define INA226_REG_MANUFACTURER_ID	0xFE
32 #define INA226_REG_DEVICE_ID		0xFF
33 
34 /* Device register values. */
35 #define INA226_MANUFACTURER_ID		0x5449
36 #define INA226_DEVICE_ID		0x2260
37 
38 struct ina226_data {
39 	const struct device *dev;
40 	int16_t current;
41 	uint16_t bus_voltage;
42 	uint16_t power;
43 #ifdef CONFIG_INA226_VSHUNT
44 	int16_t shunt_voltage;
45 #endif
46 	enum sensor_channel chan;
47 };
48 
49 struct ina226_config {
50 	const struct i2c_dt_spec bus;
51 	uint16_t config;
52 	uint32_t current_lsb;
53 	uint16_t cal;
54 };
55 
56 LOG_MODULE_REGISTER(INA226, CONFIG_SENSOR_LOG_LEVEL);
57 
58 /** @brief Calibration scaling value (scaled by 10^-5) */
59 #define INA226_CAL_SCALING		512ULL
60 
61 /** @brief The LSB value for the bus voltage register, in microvolts/LSB. */
62 #define INA226_BUS_VOLTAGE_TO_uV(x)	((x) * 1250U)
63 
64 /** @brief The LSB value for the shunt voltage register, in microvolts/LSB. */
65 #define INA226_SHUNT_VOLTAGE_TO_uV(x)	((x) * 2500U / 1000U)
66 
67 /** @brief Power scaling (need factor of 0.2) */
68 #define INA226_POWER_TO_uW(x)		((x) * 25ULL)
69 
70 
ina226_reg_read_16(const struct i2c_dt_spec * bus,uint8_t reg,uint16_t * val)71 int ina226_reg_read_16(const struct i2c_dt_spec *bus, uint8_t reg, uint16_t *val)
72 {
73 	uint8_t data[2];
74 	int ret;
75 
76 	ret = i2c_burst_read_dt(bus, reg, data, sizeof(data));
77 	if (ret < 0) {
78 		return ret;
79 	}
80 
81 	*val = sys_get_be16(data);
82 
83 	return ret;
84 }
85 
ina226_reg_write(const struct i2c_dt_spec * bus,uint8_t reg,uint16_t val)86 int ina226_reg_write(const struct i2c_dt_spec *bus, uint8_t reg, uint16_t val)
87 {
88 	uint8_t tx_buf[3];
89 
90 	tx_buf[0] = reg;
91 	sys_put_be16(val, &tx_buf[1]);
92 
93 	return i2c_write_dt(bus, tx_buf, sizeof(tx_buf));
94 }
95 
micro_s32_to_sensor_value(struct sensor_value * val,int32_t value_microX)96 static void micro_s32_to_sensor_value(struct sensor_value *val, int32_t value_microX)
97 {
98 	val->val1 = value_microX / 1000000L;
99 	val->val2 = value_microX % 1000000L;
100 }
101 
ina226_channel_get(const struct device * dev,enum sensor_channel chan,struct sensor_value * val)102 static int ina226_channel_get(const struct device *dev, enum sensor_channel chan,
103 			      struct sensor_value *val)
104 {
105 	const struct ina226_data *data = dev->data;
106 	const struct ina226_config *config = dev->config;
107 
108 	switch (chan) {
109 	case SENSOR_CHAN_VOLTAGE:
110 		micro_s32_to_sensor_value(val, INA226_BUS_VOLTAGE_TO_uV(data->bus_voltage));
111 		break;
112 	case SENSOR_CHAN_CURRENT:
113 		/* see datasheet "Current and Power calculations" section */
114 		micro_s32_to_sensor_value(val, data->current * config->current_lsb);
115 		break;
116 	case SENSOR_CHAN_POWER:
117 		/* power in uW is power_reg * current_lsb * 0.2 */
118 		micro_s32_to_sensor_value(val,
119 			INA226_POWER_TO_uW((uint32_t)data->power * config->current_lsb));
120 		break;
121 #ifdef CONFIG_INA226_VSHUNT
122 	case SENSOR_CHAN_VSHUNT:
123 		micro_s32_to_sensor_value(val, INA226_SHUNT_VOLTAGE_TO_uV(data->shunt_voltage));
124 		break;
125 #endif /* CONFIG_INA226_VSHUNT */
126 	default:
127 		return -ENOTSUP;
128 	}
129 
130 	return 0;
131 }
132 
ina226_read_data(const struct device * dev)133 static int ina226_read_data(const struct device *dev)
134 {
135 	struct ina226_data *data = dev->data;
136 	const struct ina226_config *config = dev->config;
137 	int ret;
138 
139 	if ((data->chan == SENSOR_CHAN_ALL) || (data->chan == SENSOR_CHAN_VOLTAGE)) {
140 		ret = ina226_reg_read_16(&config->bus, INA226_REG_BUS_VOLT, &data->bus_voltage);
141 		if (ret < 0) {
142 			LOG_ERR("Failed to read bus voltage");
143 			return ret;
144 		}
145 	}
146 
147 	if ((data->chan == SENSOR_CHAN_ALL) || (data->chan == SENSOR_CHAN_CURRENT)) {
148 		ret = ina226_reg_read_16(&config->bus, INA226_REG_CURRENT, &data->current);
149 		if (ret < 0) {
150 			LOG_ERR("Failed to read current");
151 			return ret;
152 		}
153 	}
154 
155 	if ((data->chan == SENSOR_CHAN_ALL) || (data->chan == SENSOR_CHAN_POWER)) {
156 		ret = ina226_reg_read_16(&config->bus, INA226_REG_POWER, &data->power);
157 		if (ret < 0) {
158 			LOG_ERR("Failed to read power");
159 			return ret;
160 		}
161 	}
162 
163 #ifdef CONFIG_INA226_VSHUNT
164 	if ((data->chan == SENSOR_CHAN_ALL) || (data->chan == SENSOR_CHAN_VSHUNT)) {
165 		ret = ina226_reg_read_16(&config->bus, INA226_REG_SHUNT_VOLT, &data->shunt_voltage);
166 		if (ret < 0) {
167 			LOG_ERR("Failed to read shunt voltage");
168 			return ret;
169 		}
170 	}
171 #endif /* CONFIG_INA226_VSHUNT */
172 
173 	return 0;
174 }
175 
ina226_sample_fetch(const struct device * dev,enum sensor_channel chan)176 static int ina226_sample_fetch(const struct device *dev, enum sensor_channel chan)
177 {
178 	struct ina226_data *data = dev->data;
179 
180 	if (chan != SENSOR_CHAN_ALL && chan != SENSOR_CHAN_VOLTAGE
181 	    && chan != SENSOR_CHAN_CURRENT && chan != SENSOR_CHAN_POWER
182 #ifdef CONFIG_INA226_VSHUNT
183 	    && chan != SENSOR_CHAN_VSHUNT
184 #endif /* CONFIG_INA226_VSHUNT */
185 	    ) {
186 		return -ENOTSUP;
187 	}
188 
189 	data->chan = chan;
190 
191 	return ina226_read_data(dev);
192 }
193 
ina226_attr_set(const struct device * dev,enum sensor_channel chan,enum sensor_attribute attr,const struct sensor_value * val)194 static int ina226_attr_set(const struct device *dev, enum sensor_channel chan,
195 			   enum sensor_attribute attr, const struct sensor_value *val)
196 {
197 	const struct ina226_config *config = dev->config;
198 	uint16_t data = val->val1;
199 
200 	switch (attr) {
201 	case SENSOR_ATTR_CONFIGURATION:
202 		return ina226_reg_write(&config->bus, INA226_REG_CONFIG, data);
203 	case SENSOR_ATTR_CALIBRATION:
204 		return ina226_reg_write(&config->bus, INA226_REG_CALIB, data);
205 	default:
206 		LOG_ERR("INA226 attribute not supported.");
207 		return -ENOTSUP;
208 	}
209 }
210 
ina226_attr_get(const struct device * dev,enum sensor_channel chan,enum sensor_attribute attr,struct sensor_value * val)211 static int ina226_attr_get(const struct device *dev, enum sensor_channel chan,
212 			   enum sensor_attribute attr, struct sensor_value *val)
213 {
214 	const struct ina226_config *config = dev->config;
215 	uint16_t data;
216 	int ret;
217 
218 	switch (attr) {
219 	case SENSOR_ATTR_CONFIGURATION:
220 		ret = ina226_reg_read_16(&config->bus, INA226_REG_CONFIG, &data);
221 		if (ret < 0) {
222 			return ret;
223 		}
224 		break;
225 	case SENSOR_ATTR_CALIBRATION:
226 		ret = ina226_reg_read_16(&config->bus, INA226_REG_CALIB, &data);
227 		if (ret < 0) {
228 			return ret;
229 		}
230 		break;
231 	default:
232 		LOG_ERR("INA226 attribute not supported.");
233 		return -ENOTSUP;
234 	}
235 
236 	val->val1 = data;
237 	val->val2 = 0;
238 
239 	return 0;
240 }
241 
ina226_calibrate(const struct device * dev)242 static int ina226_calibrate(const struct device *dev)
243 {
244 	const struct ina226_config *config = dev->config;
245 	int ret;
246 
247 	ret = ina226_reg_write(&config->bus, INA226_REG_CALIB, config->cal);
248 	if (ret < 0) {
249 		return ret;
250 	}
251 
252 	return 0;
253 }
254 
ina226_init(const struct device * dev)255 static int ina226_init(const struct device *dev)
256 {
257 	struct ina226_data *data = dev->data;
258 	const struct ina226_config *config = dev->config;
259 	uint16_t id;
260 	int ret;
261 
262 	if (!i2c_is_ready_dt(&config->bus)) {
263 		LOG_ERR("I2C bus %s is not ready", config->bus.bus->name);
264 		return -ENODEV;
265 	}
266 
267 	data->dev = dev;
268 
269 	ret = ina226_reg_read_16(&config->bus, INA226_REG_MANUFACTURER_ID, &id);
270 	if (ret < 0) {
271 		LOG_ERR("Failed to read manufacturer register.");
272 		return ret;
273 	}
274 
275 	if (id != INA226_MANUFACTURER_ID) {
276 		LOG_ERR("Manufacturer ID doesn't match.");
277 		return -ENODEV;
278 	}
279 
280 	ret = ina226_reg_read_16(&config->bus, INA226_REG_DEVICE_ID, &id);
281 	if (ret < 0) {
282 		LOG_ERR("Failed to read device register.");
283 		return ret;
284 	}
285 
286 	if (id != INA226_DEVICE_ID) {
287 		LOG_ERR("Device ID doesn't match.");
288 		return -ENODEV;
289 	}
290 
291 	ret = ina226_reg_write(&config->bus, INA226_REG_CONFIG, config->config);
292 	if (ret < 0) {
293 		LOG_ERR("Failed to write configuration register.");
294 		return ret;
295 	}
296 
297 	ret = ina226_calibrate(dev);
298 	if (ret < 0) {
299 		LOG_ERR("Failed to write calibration register.");
300 		return ret;
301 	}
302 
303 	return 0;
304 }
305 
306 static DEVICE_API(sensor, ina226_driver_api) = {
307 	.attr_set = ina226_attr_set,
308 	.attr_get = ina226_attr_get,
309 	.sample_fetch = ina226_sample_fetch,
310 	.channel_get = ina226_channel_get,
311 };
312 
313 #define INA226_DRIVER_INIT(inst)							\
314 	static struct ina226_data ina226_data_##inst;					\
315 	static const struct ina226_config ina226_config_##inst = {			\
316 		.bus = I2C_DT_SPEC_INST_GET(inst),					\
317 		.current_lsb = DT_INST_PROP(inst, current_lsb_microamps),		\
318 		.cal = INA226_CAL_SCALING * 10000000ULL /				\
319 			(DT_INST_PROP(inst, current_lsb_microamps) *			\
320 			DT_INST_PROP(inst, rshunt_micro_ohms)),				\
321 		.config = (DT_INST_ENUM_IDX(inst, avg_count) << 9) |			\
322 			(DT_INST_ENUM_IDX(inst, vbus_conversion_time_us) << 6) |	\
323 			(DT_INST_ENUM_IDX(inst, vshunt_conversion_time_us) << 3) |	\
324 			DT_INST_ENUM_IDX(inst, operating_mode),				\
325 	};										\
326 	SENSOR_DEVICE_DT_INST_DEFINE(inst,						\
327 				     &ina226_init,					\
328 				     NULL,						\
329 				     &ina226_data_##inst,				\
330 				     &ina226_config_##inst,				\
331 				     POST_KERNEL,					\
332 				     CONFIG_SENSOR_INIT_PRIORITY,			\
333 				     &ina226_driver_api);
334 
335 DT_INST_FOREACH_STATUS_OKAY(INA226_DRIVER_INIT)
336