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