1 /*
2  * Copyright (c) 2023 North River Systems Ltd
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  *
6  * Emulator for the TI INA230 I2C power monitor
7  */
8 
9 #include <zephyr/device.h>
10 #include <zephyr/drivers/emul.h>
11 #include <zephyr/drivers/i2c_emul.h>
12 #include <zephyr/drivers/i2c.h>
13 #include <zephyr/logging/log.h>
14 #include <zephyr/sys/byteorder.h>
15 #include <ina230.h>
16 #include <ina230_emul.h>
17 
18 LOG_MODULE_REGISTER(INA230_EMUL, CONFIG_SENSOR_LOG_LEVEL);
19 
20 /* Register ID, size, and value table */
21 struct ina230_reg {
22 	uint8_t id;
23 	uint8_t bytes;
24 	uint32_t value;
25 };
26 
27 /* Emulator configuration passed into driver instance */
28 struct ina230_emul_cfg {
29 	uint16_t addr;
30 };
31 
32 #define MAX_REGS 0xff
33 
34 struct ina230_emul_data {
35 	struct ina230_reg *regs;
36 };
37 
get_register(struct ina230_emul_data * data,int reg)38 static struct ina230_reg *get_register(struct ina230_emul_data *data, int reg)
39 {
40 	struct ina230_reg *c_reg = data->regs;
41 
42 	while (c_reg->bytes) {
43 		if (c_reg->id == reg) {
44 			return c_reg;
45 		}
46 		c_reg++;
47 	}
48 
49 	return NULL;
50 }
51 
ina230_mock_set_register(void * data_ptr,int reg,uint32_t value)52 int ina230_mock_set_register(void *data_ptr, int reg, uint32_t value)
53 {
54 	struct ina230_reg *reg_ptr = get_register(data_ptr, reg);
55 
56 	if (reg_ptr == NULL) {
57 		return -EINVAL;
58 	}
59 
60 	reg_ptr->value = value;
61 
62 	if (reg == INA230_REG_CONFIG) {
63 		/* bit 14 is always set in hardware */
64 		value |= 1 << 14;
65 	}
66 
67 	return 0;
68 }
69 
ina230_mock_get_register(void * data_ptr,int reg,uint32_t * value_ptr)70 int ina230_mock_get_register(void *data_ptr, int reg, uint32_t *value_ptr)
71 {
72 	struct ina230_reg *reg_ptr = get_register(data_ptr, reg);
73 
74 	if (reg_ptr == NULL || value_ptr == NULL) {
75 		return -EINVAL;
76 	}
77 
78 	*value_ptr = reg_ptr->value;
79 	return 0;
80 }
81 
ina230_emul_transfer_i2c(const struct emul * target,struct i2c_msg msgs[],int num_msgs,int addr)82 static int ina230_emul_transfer_i2c(const struct emul *target, struct i2c_msg msgs[], int num_msgs,
83 				    int addr)
84 {
85 	struct ina230_emul_data *data = (struct ina230_emul_data *)target->data;
86 
87 	/* The INA230 uses big-endian read 16, read 24, and write 16 transactions */
88 	if (!msgs || num_msgs < 1 || num_msgs > 2) {
89 		LOG_ERR("Invalid number of messages: %d", num_msgs);
90 		return -EIO;
91 	}
92 
93 	if (msgs[0].flags & I2C_MSG_READ) {
94 		LOG_ERR("Expected write");
95 		return -EIO;
96 	}
97 
98 	if (num_msgs == 1) {
99 		/* Write16 Transaction */
100 		if (msgs[0].len != 3) {
101 			LOG_ERR("Expected 3 bytes");
102 			return -EIO;
103 		}
104 
105 		/* Write 2 bytes */
106 		uint8_t reg = msgs[0].buf[0];
107 		uint16_t val = sys_get_be16(&msgs[0].buf[1]);
108 
109 		struct ina230_reg *reg_ptr = get_register(data, reg);
110 
111 		if (!reg_ptr) {
112 			LOG_ERR("Invalid register: %02x", reg);
113 			return -EIO;
114 		}
115 		reg_ptr->value = val;
116 		LOG_DBG("Write reg %02x: %04x", reg, val);
117 	} else {
118 		/* Read 2 or 3 bytes */
119 		if ((msgs[1].flags & I2C_MSG_READ) == I2C_MSG_WRITE) {
120 			LOG_ERR("Expected read");
121 			return -EIO;
122 		}
123 		uint8_t reg = msgs[0].buf[0];
124 
125 		struct ina230_reg *reg_ptr = get_register(data, reg);
126 
127 		if (!reg_ptr) {
128 			LOG_ERR("Invalid register: %02x", reg);
129 			return -EIO;
130 		}
131 
132 		if (msgs[1].len == 2) {
133 			sys_put_be16(reg_ptr->value, msgs[1].buf);
134 			LOG_DBG("Read16 reg %02x: %04x", reg, reg_ptr->value);
135 		} else if (msgs[1].len == 3) {
136 			sys_put_be24(reg_ptr->value, msgs[1].buf);
137 			LOG_DBG("Read24 reg %02x: %06x", reg, reg_ptr->value);
138 		} else {
139 			LOG_ERR("Invalid read length: %d", msgs[1].len);
140 			return -EIO;
141 		}
142 	}
143 
144 	return 0;
145 }
146 
ina230_emul_init(const struct emul * target,const struct device * parent)147 static int ina230_emul_init(const struct emul *target, const struct device *parent)
148 {
149 	ARG_UNUSED(target);
150 	ARG_UNUSED(parent);
151 
152 	return 0;
153 }
154 
155 static const struct i2c_emul_api ina230_emul_api_i2c = {
156 	.transfer = ina230_emul_transfer_i2c,
157 };
158 
159 /* clang-format off */
160 
161 #define CREATE_INA230_REGS                                                                         \
162 	{INA230_REG_CONFIG, 2, 0x4127},                                                            \
163 	{INA230_REG_SHUNT_VOLT, 2, 0},                                                             \
164 	{INA230_REG_BUS_VOLT, 2, 0},                                                               \
165 	{INA230_REG_POWER, 2, 0},                                                                  \
166 	{INA230_REG_CURRENT, 2, 0},                                                                \
167 	{INA230_REG_CALIB, 2, 0},                                                                  \
168 	{INA230_REG_MASK, 2, 0},                                                                   \
169 	{INA230_REG_ALERT, 2, 0}
170 
171 #define CREATE_INA236_REGS                                                                         \
172 	CREATE_INA230_REGS,                                                                        \
173 	{INA236_REG_MANUFACTURER_ID, 2, 0x449},                                                    \
174 	{INA236_REG_DEVICE_ID, 2, 0xa080}
175 
176 /* clang-format on */
177 
178 #define INA230_EMUL(n, v)                                                                          \
179 	static const struct ina230_emul_cfg ina23##v##_emul_cfg_##n = {                            \
180 		.addr = DT_INST_REG_ADDR(n),                                                       \
181 	};                                                                                         \
182 	static struct ina230_reg ina23##v##_regs_##n[] = {CREATE_INA23##v##_REGS, {}};             \
183 	static struct ina230_emul_data ina23##v##_emul_data_##n = {                                \
184 		.regs = (struct ina230_reg *)ina23##v##_regs_##n,                                  \
185 	};                                                                                         \
186 	EMUL_DT_INST_DEFINE(n, ina230_emul_init, &ina23##v##_emul_data_##n,                        \
187 			    &ina23##v##_emul_cfg_##n, &ina230_emul_api_i2c, NULL)
188 
189 #undef DT_DRV_COMPAT
190 #define DT_DRV_COMPAT ti_ina230
191 DT_INST_FOREACH_STATUS_OKAY_VARGS(INA230_EMUL, 0)
192 
193 #undef DT_DRV_COMPAT
194 #define DT_DRV_COMPAT ti_ina236
195 DT_INST_FOREACH_STATUS_OKAY_VARGS(INA230_EMUL, 6)
196