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