1 /*
2  * Copyright (c) 2022 Leica Geosystems AG
3  *
4  * Copyright 2022 Google LLC
5  * Copyright 2023 Microsoft Corporation
6  *
7  * SPDX-License-Identifier: Apache-2.0
8  */
9 
10 #define DT_DRV_COMPAT sbs_sbs_gauge_new_api
11 
12 #include "sbs_gauge.h"
13 
14 #include <stdbool.h>
15 #include <stdint.h>
16 #include <zephyr/devicetree.h>
17 #include <zephyr/drivers/fuel_gauge.h>
18 #include <zephyr/drivers/i2c.h>
19 #include <zephyr/logging/log.h>
20 #include <zephyr/sys/byteorder.h>
21 #include <zephyr/sys/util.h>
22 
23 LOG_MODULE_REGISTER(sbs_gauge);
24 
sbs_cmd_reg_read(const struct device * dev,uint8_t reg_addr,uint16_t * val)25 static int sbs_cmd_reg_read(const struct device *dev, uint8_t reg_addr, uint16_t *val)
26 {
27 	const struct sbs_gauge_config *cfg;
28 	uint8_t i2c_data[2];
29 	int status;
30 
31 	cfg = dev->config;
32 	status = i2c_burst_read_dt(&cfg->i2c, reg_addr, i2c_data, ARRAY_SIZE(i2c_data));
33 	if (status < 0) {
34 		LOG_ERR("Unable to read register");
35 		return status;
36 	}
37 
38 	*val = sys_get_le16(i2c_data);
39 
40 	return 0;
41 }
42 
sbs_cmd_reg_write(const struct device * dev,uint8_t reg_addr,uint16_t val)43 static int sbs_cmd_reg_write(const struct device *dev, uint8_t reg_addr, uint16_t val)
44 {
45 	const struct sbs_gauge_config *config = dev->config;
46 	uint8_t buf[2];
47 
48 	sys_put_le16(val, buf);
49 
50 	return i2c_burst_write_dt(&config->i2c, reg_addr, buf, sizeof(buf));
51 }
52 
sbs_cmd_buffer_read(const struct device * dev,uint8_t reg_addr,char * buffer,const uint8_t buffer_size)53 static int sbs_cmd_buffer_read(const struct device *dev, uint8_t reg_addr, char *buffer,
54 			      const uint8_t buffer_size)
55 {
56 	const struct sbs_gauge_config *cfg;
57 	int status;
58 
59 	cfg = dev->config;
60 	status = i2c_burst_read_dt(&cfg->i2c, reg_addr, buffer, buffer_size);
61 	if (status < 0) {
62 		LOG_ERR("Unable to read register");
63 		return status;
64 	}
65 
66 	return 0;
67 }
68 
sbs_gauge_get_prop(const struct device * dev,fuel_gauge_prop_t prop,union fuel_gauge_prop_val * val)69 static int sbs_gauge_get_prop(const struct device *dev, fuel_gauge_prop_t prop,
70 			      union fuel_gauge_prop_val *val)
71 {
72 	int rc = 0;
73 	uint16_t tmp_val = 0;
74 
75 	switch (prop) {
76 	case FUEL_GAUGE_AVG_CURRENT:
77 		rc = sbs_cmd_reg_read(dev, SBS_GAUGE_CMD_AVG_CURRENT, &tmp_val);
78 		val->avg_current = tmp_val * 1000;
79 		break;
80 	case FUEL_GAUGE_CYCLE_COUNT:
81 		rc = sbs_cmd_reg_read(dev, SBS_GAUGE_CMD_CYCLE_COUNT, &tmp_val);
82 		val->cycle_count = tmp_val;
83 		break;
84 	case FUEL_GAUGE_CURRENT:
85 		rc = sbs_cmd_reg_read(dev, SBS_GAUGE_CMD_CURRENT, &tmp_val);
86 		val->current = (int16_t)tmp_val * 1000;
87 		break;
88 	case FUEL_GAUGE_FULL_CHARGE_CAPACITY:
89 		rc = sbs_cmd_reg_read(dev, SBS_GAUGE_CMD_FULL_CAPACITY, &tmp_val);
90 		val->full_charge_capacity = tmp_val * 1000;
91 		break;
92 	case FUEL_GAUGE_REMAINING_CAPACITY:
93 		rc = sbs_cmd_reg_read(dev, SBS_GAUGE_CMD_REM_CAPACITY, &tmp_val);
94 		val->remaining_capacity = tmp_val * 1000;
95 		break;
96 	case FUEL_GAUGE_RUNTIME_TO_EMPTY:
97 		rc = sbs_cmd_reg_read(dev, SBS_GAUGE_CMD_RUNTIME2EMPTY, &tmp_val);
98 		val->runtime_to_empty = tmp_val;
99 		break;
100 	case FUEL_GAUGE_RUNTIME_TO_FULL:
101 		rc = sbs_cmd_reg_read(dev, SBS_GAUGE_CMD_AVG_TIME2FULL, &tmp_val);
102 		val->runtime_to_full = tmp_val;
103 		break;
104 	case FUEL_GAUGE_SBS_MFR_ACCESS:
105 		rc = sbs_cmd_reg_read(dev, SBS_GAUGE_CMD_MANUFACTURER_ACCESS, &tmp_val);
106 		val->sbs_mfr_access_word = tmp_val;
107 		break;
108 	case FUEL_GAUGE_ABSOLUTE_STATE_OF_CHARGE:
109 		rc = sbs_cmd_reg_read(dev, SBS_GAUGE_CMD_ASOC, &tmp_val);
110 		val->absolute_state_of_charge = tmp_val;
111 		break;
112 	case FUEL_GAUGE_RELATIVE_STATE_OF_CHARGE:
113 		rc = sbs_cmd_reg_read(dev, SBS_GAUGE_CMD_RSOC, &tmp_val);
114 		val->relative_state_of_charge = tmp_val;
115 		break;
116 	case FUEL_GAUGE_TEMPERATURE:
117 		rc = sbs_cmd_reg_read(dev, SBS_GAUGE_CMD_TEMP, &tmp_val);
118 		val->temperature = tmp_val;
119 		break;
120 	case FUEL_GAUGE_VOLTAGE:
121 		rc = sbs_cmd_reg_read(dev, SBS_GAUGE_CMD_VOLTAGE, &tmp_val);
122 		val->voltage = tmp_val * 1000;
123 		break;
124 	case FUEL_GAUGE_SBS_MODE:
125 		rc = sbs_cmd_reg_read(dev, SBS_GAUGE_CMD_BATTERY_MODE, &tmp_val);
126 		val->sbs_mode = tmp_val;
127 		break;
128 	case FUEL_GAUGE_CHARGE_CURRENT:
129 		rc = sbs_cmd_reg_read(dev, SBS_GAUGE_CMD_CHG_CURRENT, &tmp_val);
130 		val->chg_current = tmp_val * 1000;
131 		break;
132 	case FUEL_GAUGE_CHARGE_VOLTAGE:
133 		rc = sbs_cmd_reg_read(dev, SBS_GAUGE_CMD_CHG_VOLTAGE, &tmp_val);
134 		val->chg_voltage = tmp_val * 1000;
135 		break;
136 	case FUEL_GAUGE_STATUS:
137 		rc = sbs_cmd_reg_read(dev, SBS_GAUGE_CMD_FLAGS, &tmp_val);
138 		val->fg_status = tmp_val;
139 		break;
140 	case FUEL_GAUGE_DESIGN_CAPACITY:
141 		rc = sbs_cmd_reg_read(dev, SBS_GAUGE_CMD_NOM_CAPACITY, &tmp_val);
142 		val->design_cap = tmp_val;
143 		break;
144 	case FUEL_GAUGE_DESIGN_VOLTAGE:
145 		rc = sbs_cmd_reg_read(dev, SBS_GAUGE_CMD_DESIGN_VOLTAGE, &tmp_val);
146 		val->design_volt = tmp_val;
147 		break;
148 	case FUEL_GAUGE_SBS_ATRATE:
149 		rc = sbs_cmd_reg_read(dev, SBS_GAUGE_CMD_AR, &tmp_val);
150 		val->sbs_at_rate = tmp_val;
151 		break;
152 	case FUEL_GAUGE_SBS_ATRATE_TIME_TO_FULL:
153 		rc = sbs_cmd_reg_read(dev, SBS_GAUGE_CMD_ARTTF, &tmp_val);
154 		val->sbs_at_rate_time_to_full = tmp_val;
155 		break;
156 	case FUEL_GAUGE_SBS_ATRATE_TIME_TO_EMPTY:
157 		rc = sbs_cmd_reg_read(dev, SBS_GAUGE_CMD_ARTTE, &tmp_val);
158 		val->sbs_at_rate_time_to_empty = tmp_val;
159 		break;
160 	case FUEL_GAUGE_SBS_ATRATE_OK:
161 		rc = sbs_cmd_reg_read(dev, SBS_GAUGE_CMD_AROK, &tmp_val);
162 		val->sbs_at_rate_ok = tmp_val;
163 		break;
164 	case FUEL_GAUGE_SBS_REMAINING_CAPACITY_ALARM:
165 		rc = sbs_cmd_reg_read(dev, SBS_GAUGE_CMD_REM_CAPACITY_ALARM, &tmp_val);
166 		val->sbs_remaining_capacity_alarm = tmp_val;
167 		break;
168 	case FUEL_GAUGE_SBS_REMAINING_TIME_ALARM:
169 		rc = sbs_cmd_reg_read(dev, SBS_GAUGE_CMD_REM_TIME_ALARM, &tmp_val);
170 		val->sbs_remaining_time_alarm = tmp_val;
171 		break;
172 	default:
173 		rc = -ENOTSUP;
174 	}
175 
176 	return rc;
177 }
178 
sbs_gauge_do_battery_cutoff(const struct device * dev)179 static int sbs_gauge_do_battery_cutoff(const struct device *dev)
180 {
181 	int rc = -ENOTSUP;
182 	const struct sbs_gauge_config *cfg = dev->config;
183 
184 	if (cfg->cutoff_cfg == NULL) {
185 		return -ENOTSUP;
186 	}
187 
188 	for (int i = 0; i < cfg->cutoff_cfg->payload_size; i++) {
189 		rc = sbs_cmd_reg_write(dev, cfg->cutoff_cfg->reg, cfg->cutoff_cfg->payload[i]);
190 		if (rc != 0) {
191 			return rc;
192 		}
193 	}
194 
195 	return rc;
196 }
197 
sbs_gauge_set_prop(const struct device * dev,fuel_gauge_prop_t prop,union fuel_gauge_prop_val val)198 static int sbs_gauge_set_prop(const struct device *dev, fuel_gauge_prop_t prop,
199 			      union fuel_gauge_prop_val val)
200 {
201 	int rc = 0;
202 	uint16_t tmp_val = 0;
203 
204 	switch (prop) {
205 
206 	case FUEL_GAUGE_SBS_MFR_ACCESS:
207 		rc = sbs_cmd_reg_write(dev, SBS_GAUGE_CMD_MANUFACTURER_ACCESS,
208 				       val.sbs_mfr_access_word);
209 		val.sbs_mfr_access_word = tmp_val;
210 		break;
211 	case FUEL_GAUGE_SBS_REMAINING_CAPACITY_ALARM:
212 		rc = sbs_cmd_reg_write(dev, SBS_GAUGE_CMD_REM_CAPACITY_ALARM,
213 				       val.sbs_remaining_capacity_alarm);
214 		val.sbs_remaining_capacity_alarm = tmp_val;
215 		break;
216 	case FUEL_GAUGE_SBS_REMAINING_TIME_ALARM:
217 		rc = sbs_cmd_reg_write(dev, SBS_GAUGE_CMD_REM_TIME_ALARM,
218 				       val.sbs_remaining_time_alarm);
219 		val.sbs_remaining_time_alarm = tmp_val;
220 		break;
221 	case FUEL_GAUGE_SBS_MODE:
222 		rc = sbs_cmd_reg_write(dev, SBS_GAUGE_CMD_BATTERY_MODE, val.sbs_mode);
223 		val.sbs_mode = tmp_val;
224 		break;
225 	case FUEL_GAUGE_SBS_ATRATE:
226 		rc = sbs_cmd_reg_write(dev, SBS_GAUGE_CMD_AR, val.sbs_at_rate);
227 		val.sbs_at_rate = tmp_val;
228 		break;
229 	default:
230 		rc = -ENOTSUP;
231 	}
232 
233 	return rc;
234 }
235 
sbs_gauge_get_buffer_prop(const struct device * dev,fuel_gauge_prop_t prop_type,void * dst,size_t dst_len)236 static int sbs_gauge_get_buffer_prop(const struct device *dev,
237 				    fuel_gauge_prop_t prop_type, void *dst,
238 				    size_t dst_len)
239 {
240 	int rc = 0;
241 
242 	switch (prop_type) {
243 	case FUEL_GAUGE_MANUFACTURER_NAME:
244 		if (dst_len == sizeof(struct sbs_gauge_manufacturer_name)) {
245 			rc = sbs_cmd_buffer_read(dev, SBS_GAUGE_CMD_MANUFACTURER_NAME, (char *)dst,
246 						dst_len);
247 		} else {
248 			rc = -EINVAL;
249 		}
250 		break;
251 	case FUEL_GAUGE_DEVICE_NAME:
252 		if (dst_len == sizeof(struct sbs_gauge_device_name)) {
253 			rc = sbs_cmd_buffer_read(dev, SBS_GAUGE_CMD_DEVICE_NAME, (char *)dst,
254 						dst_len);
255 		} else {
256 			rc = -EINVAL;
257 		}
258 		break;
259 	case FUEL_GAUGE_DEVICE_CHEMISTRY:
260 		if (dst_len == sizeof(struct sbs_gauge_device_chemistry)) {
261 			rc = sbs_cmd_buffer_read(dev, SBS_GAUGE_CMD_DEVICE_CHEMISTRY, (char *)dst,
262 						dst_len);
263 		} else {
264 			rc = -EINVAL;
265 		}
266 		break;
267 	default:
268 		rc = -ENOTSUP;
269 	}
270 
271 	return rc;
272 }
273 
274 /**
275  * @brief initialize the fuel gauge
276  *
277  * @return 0 for success
278  */
sbs_gauge_init(const struct device * dev)279 static int sbs_gauge_init(const struct device *dev)
280 {
281 	const struct sbs_gauge_config *cfg;
282 
283 	cfg = dev->config;
284 
285 	if (!device_is_ready(cfg->i2c.bus)) {
286 		LOG_ERR("Bus device is not ready");
287 		return -ENODEV;
288 	}
289 
290 	return 0;
291 }
292 
293 static DEVICE_API(fuel_gauge, sbs_gauge_driver_api) = {
294 	.get_property = &sbs_gauge_get_prop,
295 	.set_property = &sbs_gauge_set_prop,
296 	.get_buffer_property = &sbs_gauge_get_buffer_prop,
297 	.battery_cutoff = &sbs_gauge_do_battery_cutoff,
298 };
299 
300 /* Concatenates index to battery config to create unique cfg variable name per instance. */
301 #define _SBS_GAUGE_BATT_CUTOFF_CFG_VAR_NAME(index) sbs_gauge_batt_cutoff_cfg_##index
302 
303 /* Declare and define the battery config struct */
304 #define _SBS_GAUGE_CONFIG_DEFINE(index)                                                            \
305 	static const struct sbs_gauge_battery_cutoff_config _SBS_GAUGE_BATT_CUTOFF_CFG_VAR_NAME(   \
306 		index) = {                                                                         \
307 		.reg = DT_INST_PROP(index, battery_cutoff_reg_addr),                               \
308 		.payload = DT_INST_PROP(index, battery_cutoff_payload),                            \
309 		.payload_size = DT_INST_PROP_LEN(index, battery_cutoff_payload),                   \
310 	};
311 
312 /* Conditionally defined battery config based on battery cutoff support */
313 #define SBS_GAUGE_CONFIG_DEFINE(index)                                                             \
314 	COND_CODE_1(DT_INST_PROP(index, battery_cutoff_support),                                   \
315 		    (_SBS_GAUGE_CONFIG_DEFINE(index)), (;))
316 
317 /* Conditionally get the battery config variable name or NULL based on battery cutoff support */
318 #define SBS_GAUGE_GET_BATTERY_CONFIG_NAME(index)                                                   \
319 	COND_CODE_1(DT_INST_PROP(index, battery_cutoff_support),                                   \
320 		    (&_SBS_GAUGE_BATT_CUTOFF_CFG_VAR_NAME(index)), (NULL))
321 
322 #define SBS_GAUGE_INIT(index)                                                                      \
323 	SBS_GAUGE_CONFIG_DEFINE(index);                                                            \
324 	static const struct sbs_gauge_config sbs_gauge_config_##index = {                          \
325 		.i2c = I2C_DT_SPEC_INST_GET(index),                                                \
326 		.cutoff_cfg = SBS_GAUGE_GET_BATTERY_CONFIG_NAME(index)};                           \
327                                                                                                    \
328 	DEVICE_DT_INST_DEFINE(index, &sbs_gauge_init, NULL, NULL, &sbs_gauge_config_##index,       \
329 			      POST_KERNEL, CONFIG_FUEL_GAUGE_INIT_PRIORITY,                        \
330 			      &sbs_gauge_driver_api);
331 
332 DT_INST_FOREACH_STATUS_OKAY(SBS_GAUGE_INIT)
333 
334 #define CUTOFF_PAYLOAD_SIZE_ASSERT(inst)                                                           \
335 	BUILD_ASSERT(DT_INST_PROP_LEN_OR(inst, battery_cutoff_payload, 0) <=                       \
336 		     SBS_GAUGE_CUTOFF_PAYLOAD_MAX_SIZE);
337 DT_INST_FOREACH_STATUS_OKAY(CUTOFF_PAYLOAD_SIZE_ASSERT)
338