1 /*
2  * Copyright (c) 2023, ithinx GmbH
3  * Copyright (c) 2023, Tonies GmbH
4  *
5  * SPDX-License-Identifier: Apache-2.0
6  */
7 
8 #define DT_DRV_COMPAT ti_bq27z746
9 
10 #include "bq27z746.h"
11 
12 #include <zephyr/kernel.h>
13 #include <zephyr/drivers/fuel_gauge.h>
14 #include <zephyr/drivers/i2c.h>
15 #include <zephyr/sys/byteorder.h>
16 #include <zephyr/logging/log.h>
17 #include <string.h>
18 
19 LOG_MODULE_REGISTER(BQ27Z746);
20 
21 #define BQ27Z746_MAC_DATA_LEN     32
22 #define BQ27Z746_MAC_OVERHEAD_LEN 4 /* 2 cmd bytes, 1 length byte, 1 checksum byte */
23 #define BQ27Z746_MAC_COMPLETE_LEN (BQ27Z746_MAC_DATA_LEN + BQ27Z746_MAC_OVERHEAD_LEN)
24 
bq27z746_read16(const struct device * dev,uint8_t reg,uint16_t * value)25 static int bq27z746_read16(const struct device *dev, uint8_t reg, uint16_t *value)
26 {
27 	uint8_t i2c_data[2];
28 	const struct bq27z746_config *cfg = dev->config;
29 	const int status = i2c_burst_read_dt(&cfg->i2c, reg, i2c_data, sizeof(i2c_data));
30 
31 	if (status < 0) {
32 		LOG_ERR("Unable to read register");
33 		return status;
34 	}
35 	*value = sys_get_le16(i2c_data);
36 
37 	return 0;
38 }
39 
bq27z746_write16(const struct device * dev,uint8_t reg,uint16_t value)40 static int bq27z746_write16(const struct device *dev, uint8_t reg, uint16_t value)
41 {
42 	uint8_t buf[3];
43 	const struct bq27z746_config *cfg = dev->config;
44 
45 	buf[0] = reg;
46 	sys_put_le16(value, &buf[1]);
47 
48 	return i2c_write_dt(&cfg->i2c, buf, sizeof(buf));
49 }
50 
bq27z746_read_mac(const struct device * dev,uint16_t cmd,uint8_t * data,int len)51 static int bq27z746_read_mac(const struct device *dev, uint16_t cmd, uint8_t *data, int len)
52 {
53 	if (len > BQ27Z746_MAC_DATA_LEN) {
54 		return -EINVAL;
55 	}
56 
57 	uint8_t buf[BQ27Z746_MAC_COMPLETE_LEN];
58 	const struct bq27z746_config *cfg = dev->config;
59 
60 	/* Instead of MAC, ALTMAC is used as reccommended in the datasheet */
61 	int ret = bq27z746_write16(dev, BQ27Z746_ALTMANUFACTURERACCESS, cmd);
62 
63 	if (ret != 0) {
64 		return ret;
65 	}
66 
67 	/*
68 	 * The data read from BQ27Z746_ALTMANUFACTURERACCESS is:
69 	 * 0..1: The command (for verification)
70 	 * 2..33: The data
71 	 * 34: Checksum calculated as (uint8_t)(0xFF - (sum of all command and data bytes))
72 	 * 35: Length including command, checksum and length (e.g. data length + 4)
73 	 */
74 
75 	ret = i2c_burst_read_dt(&cfg->i2c, BQ27Z746_ALTMANUFACTURERACCESS, buf,
76 				BQ27Z746_MAC_COMPLETE_LEN);
77 	if (ret != 0) {
78 		return ret;
79 	}
80 
81 	/* The first two bytes read is the command and is used for verification */
82 	const uint16_t read_cmd = sys_get_le16(buf);
83 
84 	if (read_cmd != cmd) {
85 		LOG_ERR("Read command 0x%x != written command 0x%x", read_cmd, cmd);
86 		return -EIO;
87 	}
88 
89 	const uint8_t checksum_actual = buf[34];
90 	uint8_t sum = 0; /* Intentionally 8 bit wide and overflowing */
91 
92 	for (int i = 0; i < BQ27Z746_MAC_COMPLETE_LEN - 2; i++) {
93 		sum += buf[i];
94 	}
95 
96 	const uint8_t checksum_expected = 0xFF - sum;
97 
98 	if (checksum_expected != checksum_actual) {
99 		LOG_ERR("Checksum mismatch");
100 		return -EIO;
101 	}
102 
103 	/* First byte of the user buffer is the length */
104 	data[0] = buf[35] - BQ27Z746_MAC_OVERHEAD_LEN;
105 
106 	/* Copy only the data to the user buffer (= skipping the first two command bytes) */
107 	memcpy(&data[1], &buf[2], len);
108 
109 	return ret;
110 }
111 
bq27z746_get_prop(const struct device * dev,fuel_gauge_prop_t prop,union fuel_gauge_prop_val * val)112 static int bq27z746_get_prop(const struct device *dev, fuel_gauge_prop_t prop,
113 			     union fuel_gauge_prop_val *val)
114 {
115 	int rc = 0;
116 	uint16_t tmp_val = 0;
117 
118 	/*
119 	 * Possibly negative values must be cast from uint16 to int16 first to
120 	 * then correctly end up in the wider datatypes of `prop`.
121 	 */
122 
123 	switch (prop) {
124 	case FUEL_GAUGE_AVG_CURRENT:
125 		rc = bq27z746_read16(dev, BQ27Z746_AVERAGECURRENT, &tmp_val);
126 		val->avg_current = (int16_t)tmp_val * 1000;
127 		break;
128 	case FUEL_GAUGE_CYCLE_COUNT:
129 		rc = bq27z746_read16(dev, BQ27Z746_CYCLECOUNT, &tmp_val);
130 		val->cycle_count = tmp_val * 100;
131 		break;
132 	case FUEL_GAUGE_CURRENT:
133 		rc = bq27z746_read16(dev, BQ27Z746_CURRENT, &tmp_val);
134 		val->current = (int16_t)tmp_val * 1000;
135 		break;
136 	case FUEL_GAUGE_FULL_CHARGE_CAPACITY:
137 		rc = bq27z746_read16(dev, BQ27Z746_FULLCHARGECAPACITY, &tmp_val);
138 		val->full_charge_capacity = tmp_val * 1000;
139 		break;
140 	case FUEL_GAUGE_REMAINING_CAPACITY:
141 		rc = bq27z746_read16(dev, BQ27Z746_REMAININGCAPACITY, &tmp_val);
142 		val->remaining_capacity = tmp_val * 1000;
143 		break;
144 	case FUEL_GAUGE_RUNTIME_TO_EMPTY:
145 		rc = bq27z746_read16(dev, BQ27Z746_AVERAGETIMETOEMPTY, &tmp_val);
146 		val->runtime_to_empty = tmp_val;
147 		break;
148 	case FUEL_GAUGE_RUNTIME_TO_FULL:
149 		rc = bq27z746_read16(dev, BQ27Z746_AVERAGETIMETOFULL, &tmp_val);
150 		val->runtime_to_full = tmp_val;
151 		break;
152 	case FUEL_GAUGE_SBS_MFR_ACCESS:
153 		rc = bq27z746_read16(dev, BQ27Z746_MANUFACTURERACCESS, &tmp_val);
154 		val->sbs_mfr_access_word = tmp_val;
155 		break;
156 	case FUEL_GAUGE_RELATIVE_STATE_OF_CHARGE:
157 		rc = bq27z746_read16(dev, BQ27Z746_RELATIVESTATEOFCHARGE, &tmp_val);
158 		val->relative_state_of_charge = tmp_val;
159 		break;
160 	case FUEL_GAUGE_TEMPERATURE:
161 		rc = bq27z746_read16(dev, BQ27Z746_TEMPERATURE, &tmp_val);
162 		val->temperature = tmp_val;
163 		break;
164 	case FUEL_GAUGE_VOLTAGE:
165 		rc = bq27z746_read16(dev, BQ27Z746_VOLTAGE, &tmp_val);
166 		val->voltage = tmp_val * 1000;
167 		break;
168 	case FUEL_GAUGE_SBS_ATRATE:
169 		rc = bq27z746_read16(dev, BQ27Z746_ATRATE, &tmp_val);
170 		val->sbs_at_rate = (int16_t)tmp_val;
171 		break;
172 	case FUEL_GAUGE_SBS_ATRATE_TIME_TO_EMPTY:
173 		rc = bq27z746_read16(dev, BQ27Z746_ATRATETIMETOEMPTY, &tmp_val);
174 		val->sbs_at_rate_time_to_empty = tmp_val;
175 		break;
176 	case FUEL_GAUGE_CHARGE_VOLTAGE:
177 		rc = bq27z746_read16(dev, BQ27Z746_CHARGINGVOLTAGE, &tmp_val);
178 		val->chg_voltage = tmp_val * 1000;
179 		break;
180 	case FUEL_GAUGE_CHARGE_CURRENT:
181 		rc = bq27z746_read16(dev, BQ27Z746_CHARGINGCURRENT, &tmp_val);
182 		val->chg_current = tmp_val * 1000;
183 		break;
184 	case FUEL_GAUGE_STATUS:
185 		rc = bq27z746_read16(dev, BQ27Z746_BATTERYSTATUS, &tmp_val);
186 		val->fg_status = tmp_val;
187 		break;
188 	case FUEL_GAUGE_DESIGN_CAPACITY:
189 		rc = bq27z746_read16(dev, BQ27Z746_DESIGNCAPACITY, &tmp_val);
190 		val->design_cap = tmp_val;
191 		break;
192 	default:
193 		rc = -ENOTSUP;
194 	}
195 
196 	return rc;
197 }
198 
bq27z746_get_buffer_prop(const struct device * dev,fuel_gauge_prop_t property_type,void * dst,size_t dst_len)199 static int bq27z746_get_buffer_prop(const struct device *dev,
200 				    fuel_gauge_prop_t property_type, void *dst,
201 				    size_t dst_len)
202 {
203 	int rc = 0;
204 
205 	switch (property_type) {
206 	case FUEL_GAUGE_MANUFACTURER_NAME:
207 		if (dst_len == sizeof(struct sbs_gauge_manufacturer_name)) {
208 			rc = bq27z746_read_mac(dev, BQ27Z746_MAC_CMD_MANUFACTURER_NAME,
209 					       (uint8_t *)dst, dst_len - 1);
210 		} else {
211 			rc = -EINVAL;
212 		}
213 		break;
214 	case FUEL_GAUGE_DEVICE_NAME:
215 		if (dst_len == sizeof(struct sbs_gauge_device_name)) {
216 			rc = bq27z746_read_mac(dev, BQ27Z746_MAC_CMD_DEVICE_NAME, (uint8_t *)dst,
217 					       dst_len - 1);
218 		} else {
219 			rc = -EINVAL;
220 		}
221 		break;
222 	case FUEL_GAUGE_DEVICE_CHEMISTRY:
223 		if (dst_len == sizeof(struct sbs_gauge_device_chemistry)) {
224 			rc = bq27z746_read_mac(dev, BQ27Z746_MAC_CMD_DEVICE_CHEM, (uint8_t *)dst,
225 					       dst_len - 1);
226 		} else {
227 			rc = -EINVAL;
228 		}
229 		break;
230 	default:
231 		rc = -ENOTSUP;
232 	}
233 
234 	return rc;
235 }
236 
bq27z746_set_prop(const struct device * dev,fuel_gauge_prop_t prop,union fuel_gauge_prop_val val)237 static int bq27z746_set_prop(const struct device *dev, fuel_gauge_prop_t prop,
238 			     union fuel_gauge_prop_val val)
239 {
240 	int rc = 0;
241 	uint16_t tmp_val = 0;
242 
243 	switch (prop) {
244 	case FUEL_GAUGE_SBS_MFR_ACCESS:
245 		rc = bq27z746_write16(dev, BQ27Z746_MANUFACTURERACCESS, val.sbs_mfr_access_word);
246 		val.sbs_mfr_access_word = tmp_val;
247 		break;
248 	case FUEL_GAUGE_SBS_ATRATE:
249 		rc = bq27z746_write16(dev, BQ27Z746_ATRATE, val.sbs_at_rate);
250 		val.sbs_at_rate = tmp_val;
251 		break;
252 	default:
253 		rc = -ENOTSUP;
254 	}
255 
256 	return rc;
257 }
258 
bq27z746_init(const struct device * dev)259 static int bq27z746_init(const struct device *dev)
260 {
261 	const struct bq27z746_config *cfg;
262 
263 	cfg = dev->config;
264 
265 	if (!device_is_ready(cfg->i2c.bus)) {
266 		LOG_ERR("Bus device is not ready");
267 		return -ENODEV;
268 	}
269 
270 	return 0;
271 }
272 
273 static DEVICE_API(fuel_gauge, bq27z746_driver_api) = {
274 	.get_property = &bq27z746_get_prop,
275 	.set_property = &bq27z746_set_prop,
276 	.get_buffer_property = &bq27z746_get_buffer_prop,
277 };
278 
279 #define BQ27Z746_INIT(index)                                                                       \
280                                                                                                    \
281 	static const struct bq27z746_config bq27z746_config_##index = {                            \
282 		.i2c = I2C_DT_SPEC_INST_GET(index),                                                \
283 	};                                                                                         \
284                                                                                                    \
285 	DEVICE_DT_INST_DEFINE(index, &bq27z746_init, NULL, NULL, &bq27z746_config_##index,         \
286 			      POST_KERNEL, CONFIG_FUEL_GAUGE_INIT_PRIORITY, &bq27z746_driver_api);
287 
288 DT_INST_FOREACH_STATUS_OKAY(BQ27Z746_INIT)
289