1 /*
2  * Copyright (c) 2023 North River Systems Ltd
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  *
6  * Emulator for the TI INA237 I2C power monitor
7  */
8 #define DT_DRV_COMPAT ti_ina237
9 
10 #include <zephyr/device.h>
11 #include <zephyr/drivers/emul.h>
12 #include <zephyr/drivers/i2c_emul.h>
13 #include <zephyr/drivers/i2c.h>
14 #include <zephyr/logging/log.h>
15 #include <zephyr/sys/byteorder.h>
16 #include <ina237.h>
17 #include <ina237_emul.h>
18 
19 LOG_MODULE_REGISTER(INA237_EMUL, CONFIG_SENSOR_LOG_LEVEL);
20 
21 /* Register ID, size, and value table */
22 struct ina237_reg {
23 	uint8_t id;
24 	uint8_t bytes;
25 	uint32_t value;
26 };
27 
28 /* Emulator configuration passed into driver instance */
29 struct ina237_emul_cfg {
30 	uint16_t addr;
31 };
32 
33 struct ina237_emul_data {
34 	struct ina237_reg ina237_regs[INA237_REGISTER_COUNT];
35 };
36 
get_register(struct ina237_emul_data * data,int reg)37 static struct ina237_reg *get_register(struct ina237_emul_data *data, int reg)
38 {
39 	for (int i = 0; i < INA237_REGISTER_COUNT; i++) {
40 		if (data->ina237_regs[i].id == reg) {
41 			return &data->ina237_regs[i];
42 		}
43 	}
44 	return NULL;
45 }
46 
ina237_mock_set_register(void * data_ptr,int reg,uint32_t value)47 int ina237_mock_set_register(void *data_ptr, int reg, uint32_t value)
48 {
49 	struct ina237_reg *reg_ptr = get_register(data_ptr, reg);
50 
51 	if (reg_ptr == NULL) {
52 		return -EINVAL;
53 	}
54 
55 	reg_ptr->value = value;
56 	return 0;
57 }
58 
ina237_mock_get_register(void * data_ptr,int reg,uint32_t * value_ptr)59 int ina237_mock_get_register(void *data_ptr, int reg, uint32_t *value_ptr)
60 {
61 	struct ina237_reg *reg_ptr = get_register(data_ptr, reg);
62 
63 	if (reg_ptr == NULL || value_ptr == NULL) {
64 		return -EINVAL;
65 	}
66 
67 	*value_ptr = reg_ptr->value;
68 	return 0;
69 }
70 
ina237_emul_transfer_i2c(const struct emul * target,struct i2c_msg msgs[],int num_msgs,int addr)71 static int ina237_emul_transfer_i2c(const struct emul *target, struct i2c_msg msgs[], int num_msgs,
72 				    int addr)
73 {
74 	struct ina237_emul_data *data = (struct ina237_emul_data *)target->data;
75 
76 	/* The INA237 uses big-endian read 16, read 24, and write 16 transactions */
77 	if (!msgs || num_msgs < 1 || num_msgs > 2) {
78 		LOG_ERR("Invalid number of messages: %d", num_msgs);
79 		return -EIO;
80 	}
81 
82 	if (msgs[0].flags & I2C_MSG_READ) {
83 		LOG_ERR("Expected write");
84 		return -EIO;
85 	}
86 
87 	if (num_msgs == 1) {
88 		/* Write16 Transaction */
89 		if (msgs[0].len != 3) {
90 			LOG_ERR("Expected 3 bytes");
91 			return -EIO;
92 		}
93 
94 		/* Write 2 bytes */
95 		uint8_t reg = msgs[0].buf[0];
96 		uint16_t val = sys_get_be16(&msgs[0].buf[1]);
97 
98 		struct ina237_reg *reg_ptr = get_register(data, reg);
99 
100 		if (!reg_ptr) {
101 			LOG_ERR("Invalid register: %02x", reg);
102 			return -EIO;
103 		}
104 		reg_ptr->value = val;
105 		LOG_DBG("Write reg %02x: %04x", reg, val);
106 	} else {
107 		/* Read 2 or 3 bytes */
108 		if ((msgs[1].flags & I2C_MSG_READ) == I2C_MSG_WRITE) {
109 			LOG_ERR("Expected read");
110 			return -EIO;
111 		}
112 		uint8_t reg = msgs[0].buf[0];
113 
114 		struct ina237_reg *reg_ptr = get_register(data, reg);
115 
116 		if (!reg_ptr) {
117 			LOG_ERR("Invalid register: %02x", reg);
118 			return -EIO;
119 		}
120 
121 		if (msgs[1].len == 2) {
122 			sys_put_be16(reg_ptr->value, msgs[1].buf);
123 			LOG_DBG("Read16 reg %02x: %04x", reg, reg_ptr->value);
124 		} else if (msgs[1].len == 3) {
125 			sys_put_be24(reg_ptr->value, msgs[1].buf);
126 			LOG_DBG("Read24 reg %02x: %06x", reg, reg_ptr->value);
127 		} else {
128 			LOG_ERR("Invalid read length: %d", msgs[1].len);
129 			return -EIO;
130 		}
131 	}
132 
133 	return 0;
134 }
135 
ina237_emul_init(const struct emul * target,const struct device * parent)136 static int ina237_emul_init(const struct emul *target, const struct device *parent)
137 {
138 	ARG_UNUSED(target);
139 	ARG_UNUSED(parent);
140 
141 	return 0;
142 }
143 
144 static const struct i2c_emul_api ina237_emul_api_i2c = {
145 	.transfer = ina237_emul_transfer_i2c,
146 };
147 
148 #define INA237_EMUL(n)									\
149 	static const struct ina237_emul_cfg ina237_emul_cfg_##n = {			\
150 		.addr = DT_INST_REG_ADDR(n)						\
151 	};										\
152 	static struct ina237_emul_data ina237_emul_data_##n = {				\
153 		.ina237_regs = {							\
154 			{INA237_REG_CONFIG, 2, 0},					\
155 			{INA237_REG_ADC_CONFIG, 2, 0xFB68},				\
156 			{INA237_REG_CALIB, 2, 0x1000},					\
157 			{INA237_REG_SHUNT_VOLT, 2, 0},					\
158 			{INA237_REG_BUS_VOLT, 2, 0},					\
159 			{INA237_REG_DIETEMP, 2, 0},					\
160 			{INA237_REG_CURRENT, 2, 0},					\
161 			{INA237_REG_POWER, 3, 0},					\
162 			{INA237_REG_ALERT, 2, 0x0001},					\
163 			{INA237_REG_SOVL, 2, 0x7FFF},					\
164 			{INA237_REG_SUVL, 2, 0x8000},					\
165 			{INA237_REG_BOVL, 2, 0x7FFF},					\
166 			{INA237_REG_BUVL, 2, 0},					\
167 			{INA237_REG_TEMP_LIMIT, 2, 0x7FFF},				\
168 			{INA237_REG_PWR_LIMIT, 2, 0xFFFF},				\
169 			{INA237_REG_MANUFACTURER_ID, 2, INA237_MANUFACTURER_ID},	\
170 		}};									\
171 	EMUL_DT_INST_DEFINE(n, ina237_emul_init, &ina237_emul_data_##n, &ina237_emul_cfg_##n, \
172 			    &ina237_emul_api_i2c, NULL)
173 
174 DT_INST_FOREACH_STATUS_OKAY(INA237_EMUL)
175