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