1 /*
2 * Copyright (c) 2023, ithinx GmbH
3 * Copyright (c) 2023, Tonies GmbH
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 *
7 * Emulator for bq27z746 fuel gauge
8 */
9
10 #include <string.h>
11 #define DT_DRV_COMPAT ti_bq27z746
12
13 #include <zephyr/logging/log.h>
14 LOG_MODULE_REGISTER(EMUL_BQ27Z746);
15
16 #include <zephyr/device.h>
17 #include <zephyr/drivers/emul.h>
18 #include <zephyr/drivers/i2c.h>
19 #include <zephyr/drivers/i2c_emul.h>
20 #include <zephyr/sys/byteorder.h>
21
22 #include "bq27z746.h"
23
24 #define BQ27Z746_MAC_DATA_LEN 32
25 #define BQ27Z746_MAC_OVERHEAD_LEN 4 /* 2 cmd bytes, 1 length byte, 1 checksum byte */
26 #define BQ27Z746_MAC_COMPLETE_LEN (BQ27Z746_MAC_DATA_LEN + BQ27Z746_MAC_OVERHEAD_LEN)
27
28 struct bq27z746_emul_data {
29 uint16_t mac_cmd;
30 };
31
32 /** Static configuration for the emulator */
33 struct bq27z746_emul_cfg {
34 /** I2C address of emulator */
35 uint16_t addr;
36 };
37
emul_bq27z746_read_altmac(const struct emul * target,uint8_t * buf,size_t len)38 static int emul_bq27z746_read_altmac(const struct emul *target, uint8_t *buf, size_t len)
39 {
40 const uint8_t manufacturer_name[] = "Texas Instruments";
41 const uint8_t device_name[] = "BQ27Z746";
42 const uint8_t device_chemistry[] = "LION";
43 const struct bq27z746_emul_data *data = target->data;
44
45 if (len < BQ27Z746_MAC_COMPLETE_LEN) {
46 LOG_ERR("When reading the ALTMAC, one must read the full %u byte",
47 BQ27Z746_MAC_COMPLETE_LEN);
48 return -EIO;
49 }
50
51 memset(buf, 0, len);
52
53 /*
54 * The data read from BQ27Z746_ALTMANUFACTURERACCESS is:
55 * 0..1: The command (for verification)
56 * 2..33: The data
57 * 34: Checksum calculated as (uint8_t)(0xFF - (sum of all command and data bytes))
58 * 35: Length including command, checksum and length (e.g. data length + 4)
59 */
60
61 /* Put the command in the first two byte */
62 sys_put_le16(data->mac_cmd, buf);
63
64 /* Based on the command, put some data and the length into the buffer. */
65 /* In all of the operations, don't consider the zero-terminator. */
66 switch (data->mac_cmd) {
67 case BQ27Z746_MAC_CMD_MANUFACTURER_NAME:
68 memcpy(&buf[2], manufacturer_name, sizeof(manufacturer_name) - 1);
69 buf[35] = sizeof(manufacturer_name) - 1 + BQ27Z746_MAC_OVERHEAD_LEN;
70 break;
71 case BQ27Z746_MAC_CMD_DEVICE_NAME:
72 memcpy(&buf[2], device_name, sizeof(device_name) - 1);
73 buf[35] = sizeof(device_name) - 1 + BQ27Z746_MAC_OVERHEAD_LEN;
74 break;
75 case BQ27Z746_MAC_CMD_DEVICE_CHEM:
76 memcpy(&buf[2], device_chemistry, sizeof(device_chemistry) - 1);
77 buf[35] = sizeof(device_chemistry) - 1 + BQ27Z746_MAC_OVERHEAD_LEN;
78 break;
79 default:
80 LOG_ERR("ALTMAC command 0x%x is not supported", data->mac_cmd);
81 return -EIO;
82 }
83
84 /* Calculate the checksum */
85 uint8_t sum = 0; /* Intentionally 8 bit wide and overflowing */
86
87 for (int i = 0; i < BQ27Z746_MAC_COMPLETE_LEN - 2; i++) {
88 sum += buf[i];
89 }
90 buf[34] = 0xFF - sum;
91
92 return 0;
93 }
94
emul_bq27z746_write(const struct emul * target,uint8_t * buf,size_t len)95 static int emul_bq27z746_write(const struct emul *target, uint8_t *buf, size_t len)
96 {
97 struct bq27z746_emul_data *data = target->data;
98 const uint8_t reg = buf[0];
99
100 switch (reg) {
101 case BQ27Z746_ALTMANUFACTURERACCESS:
102 data->mac_cmd = sys_get_le16(&buf[1]);
103 return 0;
104 default:
105 LOG_ERR("Writing is only supported to ALTMAC currently");
106 return -EIO;
107 }
108 }
109
emul_bq27z746_reg_read(const struct emul * target,int reg,int * val)110 static int emul_bq27z746_reg_read(const struct emul *target, int reg, int *val)
111 {
112 switch (reg) {
113 case BQ27Z746_MANUFACTURERACCESS:
114 *val = 1;
115 break;
116 case BQ27Z746_ATRATE:
117 *val = -2;
118 break;
119 case BQ27Z746_ATRATETIMETOEMPTY:
120 *val = 1;
121 break;
122 case BQ27Z746_TEMPERATURE:
123 *val = 1;
124 break;
125 case BQ27Z746_VOLTAGE:
126 *val = 1;
127 break;
128 case BQ27Z746_BATTERYSTATUS:
129 *val = 1;
130 break;
131 case BQ27Z746_CURRENT:
132 *val = -2;
133 break;
134 case BQ27Z746_REMAININGCAPACITY:
135 *val = 1;
136 break;
137 case BQ27Z746_FULLCHARGECAPACITY:
138 *val = 1;
139 break;
140 case BQ27Z746_AVERAGECURRENT:
141 *val = -2;
142 break;
143 case BQ27Z746_AVERAGETIMETOEMPTY:
144 *val = 1;
145 break;
146 case BQ27Z746_AVERAGETIMETOFULL:
147 *val = 1;
148 break;
149 case BQ27Z746_MAXLOADCURRENT:
150 *val = 1;
151 break;
152 case BQ27Z746_MAXLOADTIMETOEMPTY:
153 *val = 1;
154 break;
155 case BQ27Z746_AVERAGEPOWER:
156 *val = 1;
157 break;
158 case BQ27Z746_BTPDISCHARGESET:
159 *val = 1;
160 break;
161 case BQ27Z746_BTPCHARGESET:
162 *val = 1;
163 break;
164 case BQ27Z746_INTERNALTEMPERATURE:
165 *val = 1;
166 break;
167 case BQ27Z746_CYCLECOUNT:
168 *val = 1;
169 break;
170 case BQ27Z746_RELATIVESTATEOFCHARGE:
171 *val = 1;
172 break;
173 case BQ27Z746_STATEOFHEALTH:
174 *val = 1;
175 break;
176 case BQ27Z746_CHARGINGVOLTAGE:
177 *val = 1;
178 break;
179 case BQ27Z746_CHARGINGCURRENT:
180 *val = 1;
181 break;
182 case BQ27Z746_TERMINATEVOLTAGE:
183 *val = 1;
184 break;
185 case BQ27Z746_TIMESTAMPUPPER:
186 *val = 1;
187 break;
188 case BQ27Z746_TIMESTAMPLOWER:
189 *val = 1;
190 break;
191 case BQ27Z746_QMAXCYCLES:
192 *val = 1;
193 break;
194 case BQ27Z746_DESIGNCAPACITY:
195 *val = 1;
196 break;
197 case BQ27Z746_ALTMANUFACTURERACCESS:
198 *val = 1;
199 break;
200 case BQ27Z746_MACDATA:
201 *val = 1;
202 break;
203 case BQ27Z746_MACDATASUM:
204 *val = 1;
205 break;
206 case BQ27Z746_MACDATALEN:
207 *val = 1;
208 break;
209 case BQ27Z746_VOLTHISETTHRESHOLD:
210 *val = 1;
211 break;
212 case BQ27Z746_VOLTHICLEARTHRESHOLD:
213 *val = 1;
214 break;
215 case BQ27Z746_VOLTLOSETTHRESHOLD:
216 *val = 1;
217 break;
218 case BQ27Z746_VOLTLOCLEARTHRESHOLD:
219 *val = 1;
220 break;
221 case BQ27Z746_TEMPHISETTHRESHOLD:
222 *val = 1;
223 break;
224 case BQ27Z746_TEMPHICLEARTHRESHOLD:
225 *val = 1;
226 break;
227 case BQ27Z746_TEMPLOSETTHRESHOLD:
228 *val = 1;
229 break;
230 case BQ27Z746_TEMPLOCLEARTHRESHOLD:
231 *val = 1;
232 break;
233 case BQ27Z746_INTERRUPTSTATUS:
234 *val = 1;
235 break;
236 case BQ27Z746_SOCDELTASETTHRESHOLD:
237 *val = 1;
238 break;
239 default:
240 LOG_ERR("Unknown register 0x%x read", reg);
241 return -EIO;
242 }
243 LOG_INF("read 0x%x = 0x%x", reg, *val);
244
245 return 0;
246 }
247
emul_bq27z746_read(const struct emul * target,int reg,uint8_t * buf,size_t len)248 static int emul_bq27z746_read(const struct emul *target, int reg, uint8_t *buf, size_t len)
249 {
250 if (len == 2) {
251 unsigned int val;
252 int rc = emul_bq27z746_reg_read(target, reg, &val);
253
254 if (rc) {
255 return rc;
256 }
257
258 sys_put_le16(val, buf);
259 } else {
260 switch (reg) {
261 case BQ27Z746_ALTMANUFACTURERACCESS:
262 LOG_DBG("Reading %u byte from ALTMAC", len);
263 emul_bq27z746_read_altmac(target, buf, len);
264 break;
265 default:
266 LOG_ERR("Reading is only supported from ALTMAC currently");
267 return -EIO;
268 }
269 }
270
271 return 0;
272 }
273
bq27z746_emul_transfer_i2c(const struct emul * target,struct i2c_msg * msgs,int num_msgs,int addr)274 static int bq27z746_emul_transfer_i2c(const struct emul *target, struct i2c_msg *msgs, int num_msgs,
275 int addr)
276 {
277 int reg;
278 int rc;
279
280 __ASSERT_NO_MSG(msgs && num_msgs);
281
282 i2c_dump_msgs_rw(target->dev, msgs, num_msgs, addr, false);
283 switch (num_msgs) {
284 case 1:
285 if (msgs->flags & I2C_MSG_READ) {
286 LOG_ERR("Unexpected read");
287 return -EIO;
288 }
289
290 return emul_bq27z746_write(target, msgs->buf, msgs->len);
291 case 2:
292 if (msgs->flags & I2C_MSG_READ) {
293 LOG_ERR("Unexpected read");
294 return -EIO;
295 }
296 if (msgs->len != 1) {
297 LOG_ERR("Unexpected msg0 length %d", msgs->len);
298 return -EIO;
299 }
300 reg = msgs->buf[0];
301
302 /* Now process the 'read' part of the message */
303 msgs++;
304 if (msgs->flags & I2C_MSG_READ) {
305 rc = emul_bq27z746_read(target, reg, msgs->buf, msgs->len);
306 if (rc) {
307 return rc;
308 }
309 } else {
310 LOG_ERR("Second message must be an I2C write");
311 return -EIO;
312 }
313 return rc;
314 default:
315 LOG_ERR("Invalid number of messages: %d", num_msgs);
316 return -EIO;
317 }
318
319 return 0;
320 }
321
322 static const struct i2c_emul_api bq27z746_emul_api_i2c = {
323 .transfer = bq27z746_emul_transfer_i2c,
324 };
325
326 /**
327 * Set up a new emulator (I2C)
328 *
329 * @param emul Emulation information
330 * @param parent Device to emulate
331 * @return 0 indicating success (always)
332 */
emul_bq27z746_init(const struct emul * target,const struct device * parent)333 static int emul_bq27z746_init(const struct emul *target, const struct device *parent)
334 {
335 ARG_UNUSED(target);
336 ARG_UNUSED(parent);
337
338 return 0;
339 }
340
341 /*
342 * Main instantiation macro.
343 */
344 #define BQ27Z746_EMUL(n) \
345 static struct bq27z746_emul_data bq27z746_emul_data_##n; \
346 static const struct bq27z746_emul_cfg bq27z746_emul_cfg_##n = { \
347 .addr = DT_INST_REG_ADDR(n), \
348 }; \
349 EMUL_DT_INST_DEFINE(n, emul_bq27z746_init, &bq27z746_emul_data_##n, \
350 &bq27z746_emul_cfg_##n, &bq27z746_emul_api_i2c, NULL)
351
352 DT_INST_FOREACH_STATUS_OKAY(BQ27Z746_EMUL)
353