1 /* max17048.c - Driver for max17048 battery fuel gauge */
2 
3 /*
4  * Copyright (c) 2023 Alvaro Garcia Gomez <maxpowel@gmail.com>
5  *
6  * SPDX-License-Identifier: Apache-2.0
7  */
8 
9 #define DT_DRV_COMPAT maxim_max17048
10 
11 #include "max17048.h"
12 
13 #include <zephyr/drivers/fuel_gauge.h>
14 #include <zephyr/kernel.h>
15 #include <zephyr/init.h>
16 #include <zephyr/drivers/gpio.h>
17 #include <zephyr/pm/device.h>
18 #include <zephyr/sys/byteorder.h>
19 #include <zephyr/sys/__assert.h>
20 #include <zephyr/logging/log.h>
21 
22 LOG_MODULE_REGISTER(MAX17048);
23 
24 #if DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT) == 0
25 #warning "MAX17048 driver enabled without any devices"
26 #endif
27 
28 /**
29  * Storage for the fuel gauge basic information
30  */
31 struct max17048_data {
32 	/* Charge as percentage */
33 	uint8_t charge;
34 	/* Voltage as uV */
35 	uint32_t voltage;
36 
37 	/* Time in minutes */
38 	uint16_t time_to_full;
39 	uint16_t time_to_empty;
40 	/* True if battery chargin, false if discharging */
41 	bool charging;
42 };
43 
44 /**
45  * I2C communication
46  * The way we read a value is first writing the address we want to read and then
47  * wait for 2 bytes containing the data.
48  */
max17048_read_register(const struct device * dev,uint8_t registerId,uint16_t * response)49 int max17048_read_register(const struct device *dev, uint8_t registerId, uint16_t *response)
50 {
51 	uint8_t max17048_buffer[2];
52 	const struct max17048_config *cfg = dev->config;
53 	int rc = i2c_write_read_dt(&cfg->i2c, &registerId, sizeof(registerId), max17048_buffer,
54 				   sizeof(max17048_buffer));
55 	if (rc != 0) {
56 		LOG_ERR("Unable to read register, error %d", rc);
57 		return rc;
58 	}
59 
60 	*response = sys_get_be16(max17048_buffer);
61 	return 0;
62 }
63 
64 /**
65  * Raw value from the internal ADC
66  */
max17048_adc(const struct device * i2c_dev,uint16_t * response)67 int max17048_adc(const struct device *i2c_dev, uint16_t *response)
68 {
69 	return max17048_read_register(i2c_dev, REGISTER_VCELL, response);
70 }
71 
72 /**
73  * Battery voltage
74  */
max17048_voltage(const struct device * i2c_dev,uint32_t * response)75 int max17048_voltage(const struct device *i2c_dev, uint32_t *response)
76 {
77 	uint16_t raw_voltage;
78 	int rc = max17048_adc(i2c_dev, &raw_voltage);
79 
80 	if (rc < 0) {
81 		return rc;
82 	}
83 	/**
84 	 * Once the value is read, it has to be converted to volts. The datasheet
85 	 * https://www.analog.com/media/en/technical-documentation/data-sheets/
86 	 * MAX17048-MAX17049.pdf
87 	 * Page 10, Table 2. Register Summary: 78.125µV/cell
88 	 * Max17048 only supports one cell so we just have to multiply the value by 78.125 to
89 	 * obtain µV
90 	 */
91 
92 	*response = (uint32_t)raw_voltage * 78.125;
93 	return 0;
94 }
95 
96 /**
97  * Battery percentage still available
98  */
max17048_percent(const struct device * i2c_dev,uint8_t * response)99 int max17048_percent(const struct device *i2c_dev, uint8_t *response)
100 {
101 	uint16_t data;
102 	int rc = max17048_read_register(i2c_dev, REGISTER_SOC, &data);
103 
104 	if (rc < 0) {
105 		return rc;
106 	}
107 	/**
108 	 * Once the value is read, it has to be converted to percentage. The datasheet
109 	 * https://www.analog.com/media/en/technical-documentation/data-she4ets/
110 	 * MAX17048-MAX17049.pdf
111 	 * Page 10, Table 2. Register Summary: 1%/256
112 	 * So to obtain the total percentaje we just divide the read value by 256
113 	 */
114 	*response = data / 256;
115 	return 0;
116 }
117 
118 /**
119  * Percentage of the total battery capacity per hour, positive is charging or
120  * negative if discharging
121  */
max17048_crate(const struct device * i2c_dev,int16_t * response)122 int max17048_crate(const struct device *i2c_dev, int16_t *response)
123 {
124 	int rc = max17048_read_register(i2c_dev, REGISTER_CRATE, response);
125 
126 	if (rc < 0) {
127 		return rc;
128 	}
129 
130 	/**
131 	 * Once the value is read, it has to be converted to something useful. The datasheet
132 	 * https://www.analog.com/media/en/technical-documentation/data-sheets/
133 	 * MAX17048-MAX17049.pdf
134 	 * Page 11, Table 2. Register Summary (continued): 0.208%/hr
135 	 * To avoid floats, the value will be multiplied by 208 instead of 0.208, taking into
136 	 * account that the value will be 1000 times higher
137 	 */
138 	*response = *response * 208;
139 	return 0;
140 }
141 
142 /**
143  * Initialize and verify the chip. The datasheet says that the version register
144  * should be 0x10. If not, or the chip is malfunctioning or it is not a MAX17048 at all
145  */
max17048_init(const struct device * dev)146 static int max17048_init(const struct device *dev)
147 {
148 	const struct max17048_config *cfg = dev->config;
149 	uint16_t version;
150 	int rc = max17048_read_register(dev, REGISTER_VERSION, &version);
151 
152 	if (!device_is_ready(cfg->i2c.bus)) {
153 		LOG_ERR("Bus device is not ready");
154 		return -ENODEV;
155 	}
156 
157 	if (rc < 0) {
158 		LOG_ERR("Cannot read from I2C");
159 		return rc;
160 	}
161 
162 	version = version & 0xFFF0;
163 	if (version != 0x10) {
164 		LOG_ERR("Something found at the provided I2C address, but it is not a MAX17048");
165 		LOG_ERR("The version registers should be 0x10 but got %x. Maybe your wiring is "
166 			"wrong or it is a fake chip\n",
167 			version);
168 		return -ENODEV;
169 	}
170 
171 	return 0;
172 }
173 
174 /**
175  * Get a single property from the fuel gauge
176  */
max17048_get_single_prop_impl(const struct device * dev,fuel_gauge_prop_t prop,union fuel_gauge_prop_val * val)177 static int max17048_get_single_prop_impl(const struct device *dev, fuel_gauge_prop_t prop,
178 					 union fuel_gauge_prop_val *val)
179 {
180 	struct max17048_data *data = dev->data;
181 	int rc = 0;
182 
183 	switch (prop) {
184 	case FUEL_GAUGE_RUNTIME_TO_EMPTY:
185 		val->runtime_to_empty = data->time_to_empty;
186 		break;
187 	case FUEL_GAUGE_RUNTIME_TO_FULL:
188 		val->runtime_to_full = data->time_to_full;
189 		break;
190 	case FUEL_GAUGE_RELATIVE_STATE_OF_CHARGE:
191 		val->relative_state_of_charge = data->charge;
192 		break;
193 	case FUEL_GAUGE_VOLTAGE:
194 		val->voltage = data->voltage;
195 		break;
196 	default:
197 		rc = -ENOTSUP;
198 	}
199 
200 	return rc;
201 }
202 
203 /**
204  * Get properties from the fuel gauge
205  */
max17048_get_prop(const struct device * dev,fuel_gauge_prop_t prop,union fuel_gauge_prop_val * val)206 static int max17048_get_prop(const struct device *dev, fuel_gauge_prop_t prop,
207 			     union fuel_gauge_prop_val *val)
208 {
209 	struct max17048_data *data = dev->data;
210 	int rc = max17048_percent(dev, &data->charge);
211 	int16_t crate;
212 	int ret;
213 
214 	if (rc < 0) {
215 		LOG_ERR("Error while reading battery percentage");
216 		return rc;
217 	}
218 
219 	rc = max17048_voltage(dev, &data->voltage);
220 	if (rc < 0) {
221 		LOG_ERR("Error while reading battery voltage");
222 		return rc;
223 	}
224 
225 	/**
226 	 * Crate (current rate) is the current percentage of the battery charged or drained
227 	 * per hour
228 	 */
229 	rc = max17048_crate(dev, &crate);
230 	if (rc < 0) {
231 		LOG_ERR("Error while reading battery current rate");
232 		return rc;
233 	}
234 
235 	if (crate != 0) {
236 
237 		/**
238 		 * May take some time until the chip detects the change between discharging to
239 		 * charging (and vice versa) especially if your device consumes little power
240 		 */
241 		data->charging = crate > 0;
242 
243 		/**
244 		 * In the following code, we multiply by 1000 the charge to increase the
245 		 * precision. If we just truncate the division without this multiplier,
246 		 * the precision lost is very significant when converting it into minutes
247 		 * (the value given is in hours)
248 		 *
249 		 * The value coming from crate is already 1000 times higher (check the
250 		 * function max17048_crate to see the reason) so the multiplier for the
251 		 * charge will be 1000000
252 		 */
253 		if (data->charging) {
254 			uint8_t percentage_pending = 100 - data->charge;
255 			uint32_t hours_pending = percentage_pending * 1000000 / crate;
256 
257 			data->time_to_empty = 0;
258 			data->time_to_full = hours_pending * 60 / 1000;
259 		} else {
260 			/* Discharging */
261 			uint32_t hours_pending = data->charge * 1000000 / -crate;
262 
263 			data->time_to_empty = hours_pending * 60 / 1000;
264 			data->time_to_full = 0;
265 		}
266 	} else {
267 		/**
268 		 * This case is to avoid a division by 0 when the charge rate is the same
269 		 * than consumption rate. It could also happen when the sensor is still
270 		 * calibrating the battery
271 		 */
272 		data->charging = false;
273 		data->time_to_full = 0;
274 		data->time_to_empty = 0;
275 	}
276 
277 	ret = max17048_get_single_prop_impl(dev, prop, val);
278 
279 	return ret;
280 }
281 
282 static DEVICE_API(fuel_gauge, max17048_driver_api) = {
283 	.get_property = &max17048_get_prop,
284 };
285 
286 #define MAX17048_DEFINE(inst)                                                                      \
287 	static struct max17048_data max17048_data_##inst;                                          \
288                                                                                                    \
289 	static const struct max17048_config max17048_config_##inst = {                             \
290 		.i2c = I2C_DT_SPEC_INST_GET(inst)};                                                \
291                                                                                                    \
292 	DEVICE_DT_INST_DEFINE(inst, &max17048_init, NULL, &max17048_data_##inst,                   \
293 			&max17048_config_##inst, POST_KERNEL,                                \
294 			CONFIG_FUEL_GAUGE_INIT_PRIORITY, &max17048_driver_api);
295 
296 DT_INST_FOREACH_STATUS_OKAY(MAX17048_DEFINE)
297