1 /*
2  * Copyright 2023 Cirrus Logic, Inc.
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #define DT_DRV_COMPAT ti_bq24190
8 
9 #include <errno.h>
10 
11 #include "bq24190.h"
12 
13 #include "zephyr/device.h"
14 #include "zephyr/drivers/charger.h"
15 #include "zephyr/drivers/i2c.h"
16 #include "zephyr/kernel.h"
17 #include "zephyr/sys/util.h"
18 #include "zephyr/logging/log.h"
19 #include <zephyr/drivers/gpio.h>
20 
21 LOG_MODULE_REGISTER(ti_bq24190);
22 
23 struct bq24190_config {
24 	struct i2c_dt_spec i2c;
25 	struct gpio_dt_spec ce_gpio;
26 };
27 
28 struct bq24190_data {
29 	uint8_t ss_reg;
30 	unsigned int ichg_ua;
31 	unsigned int vreg_uv;
32 	enum charger_status state;
33 	enum charger_online online;
34 };
35 
bq24190_register_reset(const struct device * dev)36 static int bq24190_register_reset(const struct device *dev)
37 {
38 	const struct bq24190_config *const config = dev->config;
39 	int ret, limit = BQ24190_RESET_MAX_TRIES;
40 	uint8_t val;
41 
42 	ret = i2c_reg_update_byte_dt(&config->i2c, BQ24190_REG_POC, BQ24190_REG_POC_RESET_MASK,
43 				     BQ24190_REG_POC_RESET_MASK);
44 	if (ret) {
45 		return ret;
46 	}
47 
48 	/*
49 	 * No explicit reset timing characteristcs are provided in the datasheet.
50 	 * Instead, poll every 100µs for 100 attempts to see if the reset request
51 	 * bit has cleared.
52 	 */
53 	do {
54 		ret = i2c_reg_read_byte_dt(&config->i2c, BQ24190_REG_POC, &val);
55 		if (ret) {
56 			return ret;
57 		}
58 
59 		if (!(val & BQ24190_REG_POC_RESET_MASK)) {
60 			return 0;
61 		}
62 
63 		k_usleep(100);
64 	} while (--limit);
65 
66 	return -EIO;
67 }
68 
bq24190_charger_get_charge_type(const struct device * dev,enum charger_charge_type * charge_type)69 static int bq24190_charger_get_charge_type(const struct device *dev,
70 					   enum charger_charge_type *charge_type)
71 {
72 	const struct bq24190_config *const config = dev->config;
73 	uint8_t v;
74 	int ret;
75 
76 	*charge_type = CHARGER_CHARGE_TYPE_UNKNOWN;
77 
78 	ret = i2c_reg_read_byte_dt(&config->i2c, BQ24190_REG_POC, &v);
79 	if (ret) {
80 		return ret;
81 	}
82 
83 	v = FIELD_GET(BQ24190_REG_POC_CHG_CONFIG_MASK, v);
84 
85 	if (!v) {
86 		*charge_type = CHARGER_CHARGE_TYPE_NONE;
87 	} else {
88 		ret = i2c_reg_read_byte_dt(&config->i2c, BQ24190_REG_CCC, &v);
89 		if (ret) {
90 			return ret;
91 		}
92 
93 		v = FIELD_GET(BQ24190_REG_CCC_FORCE_20PCT_MASK, v);
94 
95 		if (v) {
96 			*charge_type = CHARGER_CHARGE_TYPE_TRICKLE;
97 		} else {
98 			*charge_type = CHARGER_CHARGE_TYPE_FAST;
99 		}
100 	}
101 
102 	return 0;
103 }
104 
bq24190_charger_get_health(const struct device * dev,enum charger_health * health)105 static int bq24190_charger_get_health(const struct device *dev, enum charger_health *health)
106 {
107 	const struct bq24190_config *const config = dev->config;
108 	uint8_t v;
109 	int ret;
110 
111 	ret = i2c_reg_read_byte_dt(&config->i2c, BQ24190_REG_F, &v);
112 	if (ret) {
113 		return ret;
114 	}
115 
116 	if (v & BQ24190_REG_F_NTC_FAULT_MASK) {
117 		switch (v >> BQ24190_REG_F_NTC_FAULT_SHIFT & 0x7) {
118 		case BQ24190_NTC_FAULT_TS1_COLD:
119 		case BQ24190_NTC_FAULT_TS2_COLD:
120 		case BQ24190_NTC_FAULT_TS1_TS2_COLD:
121 			*health = CHARGER_HEALTH_COLD;
122 			break;
123 		case BQ24190_NTC_FAULT_TS1_HOT:
124 		case BQ24190_NTC_FAULT_TS2_HOT:
125 		case BQ24190_NTC_FAULT_TS1_TS2_HOT:
126 			*health = CHARGER_HEALTH_HOT;
127 			break;
128 		default:
129 			*health = CHARGER_HEALTH_UNKNOWN;
130 		}
131 	} else if (v & BQ24190_REG_F_BAT_FAULT_MASK) {
132 		*health = CHARGER_HEALTH_OVERVOLTAGE;
133 	} else if (v & BQ24190_REG_F_CHRG_FAULT_MASK) {
134 		switch (v >> BQ24190_REG_F_CHRG_FAULT_SHIFT & 0x3) {
135 		case BQ24190_CHRG_FAULT_INPUT_FAULT:
136 			/*
137 			 * This could be over-voltage or under-voltage
138 			 * and there's no way to tell which.  Instead
139 			 * of looking foolish and returning 'OVERVOLTAGE'
140 			 * when its really under-voltage, just return
141 			 * 'UNSPEC_FAILURE'.
142 			 */
143 			*health = CHARGER_HEALTH_UNSPEC_FAILURE;
144 			break;
145 		case BQ24190_CHRG_FAULT_TSHUT:
146 			*health = CHARGER_HEALTH_OVERHEAT;
147 			break;
148 		case BQ24190_CHRG_SAFETY_TIMER:
149 			*health = CHARGER_HEALTH_SAFETY_TIMER_EXPIRE;
150 			break;
151 		default: /* prevent compiler warning */
152 			*health = CHARGER_HEALTH_UNKNOWN;
153 		}
154 	} else if (v & BQ24190_REG_F_BOOST_FAULT_MASK) {
155 		/*
156 		 * This could be over-current or over-voltage but there's
157 		 * no way to tell which.  Return 'OVERVOLTAGE' since there
158 		 * isn't an 'OVERCURRENT' value defined that we can return
159 		 * even if it was over-current.
160 		 */
161 		*health = CHARGER_HEALTH_OVERVOLTAGE;
162 	} else {
163 		*health = CHARGER_HEALTH_GOOD;
164 	}
165 
166 	return 0;
167 }
168 
bq24190_charger_get_online(const struct device * dev,enum charger_online * online)169 static int bq24190_charger_get_online(const struct device *dev, enum charger_online *online)
170 {
171 	const struct bq24190_config *const config = dev->config;
172 	uint8_t pg_stat, batfet_disable;
173 	int ret;
174 
175 	ret = i2c_reg_read_byte_dt(&config->i2c, BQ24190_REG_SS, &pg_stat);
176 	if (ret) {
177 		return ret;
178 	}
179 
180 	pg_stat = FIELD_GET(BQ24190_REG_SS_PG_STAT_MASK, pg_stat);
181 
182 	ret = i2c_reg_read_byte_dt(&config->i2c, BQ24190_REG_MOC, &batfet_disable);
183 	if (ret) {
184 		return ret;
185 	}
186 
187 	batfet_disable = FIELD_GET(BQ24190_REG_MOC_BATFET_DISABLE_MASK, batfet_disable);
188 
189 	if (pg_stat && !batfet_disable) {
190 		*online = CHARGER_ONLINE_FIXED;
191 	} else {
192 		*online = CHARGER_ONLINE_OFFLINE;
193 	}
194 
195 	return 0;
196 }
197 
bq24190_charger_get_status(const struct device * dev,enum charger_status * status)198 static int bq24190_charger_get_status(const struct device *dev, enum charger_status *status)
199 {
200 	const struct bq24190_config *const config = dev->config;
201 	uint8_t ss_reg, chrg_fault;
202 	int ret;
203 
204 	ret = i2c_reg_read_byte_dt(&config->i2c, BQ24190_REG_F, &chrg_fault);
205 	if (ret) {
206 		return ret;
207 	}
208 
209 	chrg_fault = FIELD_GET(BQ24190_REG_F_CHRG_FAULT_MASK, chrg_fault);
210 
211 	ret = i2c_reg_read_byte_dt(&config->i2c, BQ24190_REG_SS, &ss_reg);
212 	if (ret) {
213 		return ret;
214 	}
215 
216 	/*
217 	 * The battery must be discharging when any of these are true:
218 	 * - there is no good power source;
219 	 * - there is a charge fault.
220 	 * Could also be discharging when in "supplement mode" but
221 	 * there is no way to tell when its in that mode.
222 	 */
223 	if (!(ss_reg & BQ24190_REG_SS_PG_STAT_MASK) || chrg_fault) {
224 		*status = CHARGER_STATUS_DISCHARGING;
225 	} else {
226 		ss_reg = FIELD_GET(BQ24190_REG_SS_CHRG_STAT_MASK, ss_reg);
227 
228 		switch (ss_reg) {
229 		case BQ24190_CHRG_STAT_NOT_CHRGING:
230 			*status = CHARGER_STATUS_NOT_CHARGING;
231 			break;
232 		case BQ24190_CHRG_STAT_PRECHRG:
233 		case BQ24190_CHRG_STAT_FAST_CHRG:
234 			*status = CHARGER_STATUS_CHARGING;
235 			break;
236 		case BQ24190_CHRG_STAT_CHRG_TERM:
237 			*status = CHARGER_STATUS_FULL;
238 			break;
239 		default:
240 			return -EIO;
241 		}
242 	}
243 
244 	return 0;
245 }
246 
bq24190_charger_get_constant_charge_current(const struct device * dev,uint32_t * current_ua)247 static int bq24190_charger_get_constant_charge_current(const struct device *dev,
248 						       uint32_t *current_ua)
249 {
250 	const struct bq24190_config *const config = dev->config;
251 	bool frc_20pct;
252 	uint8_t v;
253 	int ret;
254 
255 	ret = i2c_reg_read_byte_dt(&config->i2c, BQ24190_REG_CCC, &v);
256 	if (ret) {
257 		return ret;
258 	}
259 
260 	frc_20pct = v & BQ24190_REG_CCC_FORCE_20PCT_MASK;
261 
262 	v = FIELD_GET(BQ24190_REG_CCC_ICHG_MASK, v);
263 
264 	*current_ua = (v * BQ24190_REG_CCC_ICHG_STEP_UA) + BQ24190_REG_CCC_ICHG_OFFSET_UA;
265 
266 	if (frc_20pct) {
267 		*current_ua /= 5;
268 	}
269 
270 	return 0;
271 }
272 
bq24190_charger_get_precharge_current(const struct device * dev,uint32_t * current_ua)273 static int bq24190_charger_get_precharge_current(const struct device *dev, uint32_t *current_ua)
274 {
275 	const struct bq24190_config *const config = dev->config;
276 	bool frc_20pct;
277 	uint8_t v;
278 	int ret;
279 
280 	ret = i2c_reg_read_byte_dt(&config->i2c, BQ24190_REG_CCC, &v);
281 	if (ret) {
282 		return ret;
283 	}
284 
285 	frc_20pct = v & BQ24190_REG_CCC_FORCE_20PCT_MASK;
286 
287 	ret = i2c_reg_read_byte_dt(&config->i2c, BQ24190_REG_PCTCC, &v);
288 	if (ret) {
289 		return ret;
290 	}
291 
292 	v = FIELD_GET(BQ24190_REG_PCTCC_IPRECHG_MASK, v);
293 
294 	*current_ua = (v * BQ24190_REG_PCTCC_IPRECHG_STEP_UA) + BQ24190_REG_PCTCC_IPRECHG_OFFSET_UA;
295 
296 	if (frc_20pct) {
297 		*current_ua /= 2;
298 	}
299 
300 	return 0;
301 }
302 
bq24190_charger_get_charge_term_current(const struct device * dev,uint32_t * current_ua)303 static int bq24190_charger_get_charge_term_current(const struct device *dev, uint32_t *current_ua)
304 {
305 	const struct bq24190_config *const config = dev->config;
306 	uint8_t v;
307 	int ret;
308 
309 	ret = i2c_reg_read_byte_dt(&config->i2c, BQ24190_REG_PCTCC, &v);
310 	if (ret) {
311 		return ret;
312 	}
313 
314 	v = FIELD_GET(BQ24190_REG_PCTCC_ITERM_MASK, v);
315 
316 	*current_ua = (v * BQ24190_REG_PCTCC_ITERM_STEP_UA) + BQ24190_REG_PCTCC_ITERM_OFFSET_UA;
317 
318 	return 0;
319 }
320 
bq24190_get_constant_charge_voltage(const struct device * dev,uint32_t * voltage_uv)321 static int bq24190_get_constant_charge_voltage(const struct device *dev, uint32_t *voltage_uv)
322 {
323 	const struct bq24190_config *const config = dev->config;
324 	uint8_t v;
325 	int ret;
326 
327 	ret = i2c_reg_read_byte_dt(&config->i2c, BQ24190_REG_CVC, &v);
328 	if (ret < 0) {
329 		return ret;
330 	}
331 
332 	v = FIELD_GET(BQ24190_REG_CVC_VREG_MASK, v);
333 
334 	*voltage_uv = (v * BQ24190_REG_CVC_VREG_STEP_UV) + BQ24190_REG_CVC_VREG_OFFSET_UV;
335 
336 	return 0;
337 }
338 
bq24190_set_constant_charge_current(const struct device * dev,uint32_t current_ua)339 static int bq24190_set_constant_charge_current(const struct device *dev, uint32_t current_ua)
340 {
341 	const struct bq24190_config *const config = dev->config;
342 	uint8_t v;
343 	int ret;
344 
345 	ret = i2c_reg_read_byte_dt(&config->i2c, BQ24190_REG_CCC, &v);
346 	if (ret < 0) {
347 		return ret;
348 	}
349 
350 	v &= BQ24190_REG_CCC_FORCE_20PCT_MASK;
351 
352 	if (v) {
353 		current_ua *= 5;
354 	}
355 
356 	current_ua = CLAMP(current_ua, BQ24190_REG_CCC_ICHG_MIN_UA, BQ24190_REG_CCC_ICHG_MAX_UA);
357 
358 	v = (current_ua - BQ24190_REG_CCC_ICHG_OFFSET_UA) / BQ24190_REG_CCC_ICHG_STEP_UA;
359 
360 	v = FIELD_PREP(BQ24190_REG_CCC_ICHG_MASK, v);
361 
362 	return i2c_reg_update_byte_dt(&config->i2c, BQ24190_REG_CCC, BQ24190_REG_CCC_ICHG_MASK, v);
363 }
364 
bq24190_set_constant_charge_voltage(const struct device * dev,uint32_t voltage_uv)365 static int bq24190_set_constant_charge_voltage(const struct device *dev, uint32_t voltage_uv)
366 {
367 	const struct bq24190_config *const config = dev->config;
368 	uint8_t v;
369 
370 	voltage_uv = CLAMP(voltage_uv, BQ24190_REG_CVC_VREG_MIN_UV, BQ24190_REG_CVC_VREG_MAX_UV);
371 
372 	v = (voltage_uv - BQ24190_REG_CVC_VREG_OFFSET_UV) / BQ24190_REG_CVC_VREG_STEP_UV;
373 
374 	v = FIELD_PREP(BQ24190_REG_CVC_VREG_MASK, v);
375 
376 	return i2c_reg_update_byte_dt(&config->i2c, BQ24190_REG_CVC, BQ24190_REG_CVC_VREG_MASK, v);
377 }
378 
bq24190_set_config(const struct device * dev)379 static int bq24190_set_config(const struct device *dev)
380 {
381 	struct bq24190_data *data = dev->data;
382 	union charger_propval val;
383 	int ret;
384 
385 	val.const_charge_current_ua = data->ichg_ua;
386 
387 	ret = bq24190_set_constant_charge_current(dev, val.const_charge_current_ua);
388 	if (ret < 0) {
389 		return ret;
390 	}
391 
392 	val.const_charge_voltage_uv = data->vreg_uv;
393 
394 	return bq24190_set_constant_charge_voltage(dev, val.const_charge_voltage_uv);
395 }
396 
bq24190_get_prop(const struct device * dev,charger_prop_t prop,union charger_propval * val)397 static int bq24190_get_prop(const struct device *dev, charger_prop_t prop,
398 			    union charger_propval *val)
399 {
400 	switch (prop) {
401 	case CHARGER_PROP_ONLINE:
402 		return bq24190_charger_get_online(dev, &val->online);
403 	case CHARGER_PROP_CHARGE_TYPE:
404 		return bq24190_charger_get_charge_type(dev, &val->charge_type);
405 	case CHARGER_PROP_HEALTH:
406 		return bq24190_charger_get_health(dev, &val->health);
407 	case CHARGER_PROP_STATUS:
408 		return bq24190_charger_get_status(dev, &val->status);
409 	case CHARGER_PROP_CONSTANT_CHARGE_CURRENT_UA:
410 		return bq24190_charger_get_constant_charge_current(dev,
411 								   &val->const_charge_current_ua);
412 	case CHARGER_PROP_CONSTANT_CHARGE_VOLTAGE_UV:
413 		return bq24190_get_constant_charge_voltage(dev, &val->const_charge_voltage_uv);
414 	case CHARGER_PROP_PRECHARGE_CURRENT_UA:
415 		return bq24190_charger_get_precharge_current(dev, &val->precharge_current_ua);
416 	case CHARGER_PROP_CHARGE_TERM_CURRENT_UA:
417 		return bq24190_charger_get_charge_term_current(dev, &val->charge_term_current_ua);
418 	default:
419 		return -ENOTSUP;
420 	}
421 }
422 
bq24190_set_prop(const struct device * dev,charger_prop_t prop,const union charger_propval * val)423 static int bq24190_set_prop(const struct device *dev, charger_prop_t prop,
424 			    const union charger_propval *val)
425 {
426 	switch (prop) {
427 	case CHARGER_PROP_CONSTANT_CHARGE_CURRENT_UA:
428 		return bq24190_set_constant_charge_current(dev, val->const_charge_current_ua);
429 	case CHARGER_PROP_CONSTANT_CHARGE_VOLTAGE_UV:
430 		return bq24190_set_constant_charge_voltage(dev, val->const_charge_voltage_uv);
431 	default:
432 		return -ENOTSUP;
433 	}
434 }
435 
bq24190_charge_enable(const struct device * dev,const bool enable)436 static int bq24190_charge_enable(const struct device *dev, const bool enable)
437 {
438 	const struct bq24190_config *const config = dev->config;
439 
440 	if (config->ce_gpio.port != NULL) {
441 		if (enable == true) {
442 			return gpio_pin_set_dt(&config->ce_gpio, 1);
443 		} else {
444 			return gpio_pin_set_dt(&config->ce_gpio, 0);
445 		}
446 	} else {
447 		return -ENOTSUP;
448 	}
449 }
450 
bq24190_init(const struct device * dev)451 static int bq24190_init(const struct device *dev)
452 {
453 	const struct bq24190_config *const config = dev->config;
454 	struct bq24190_data *data = dev->data;
455 	uint8_t val;
456 	int ret;
457 
458 	ret = i2c_reg_read_byte_dt(&config->i2c, BQ24190_REG_VPRS, &val);
459 	if (ret) {
460 		return ret;
461 	}
462 
463 	val = FIELD_GET(BQ24190_REG_VPRS_PN_MASK, val);
464 
465 	switch (val) {
466 	case BQ24190_REG_VPRS_PN_24190:
467 	case BQ24190_REG_VPRS_PN_24192:
468 	case BQ24190_REG_VPRS_PN_24192I:
469 		break;
470 	default:
471 		LOG_ERR("Error unknown model: 0x%02x\n", val);
472 		return -ENODEV;
473 	}
474 
475 	if (config->ce_gpio.port != NULL) {
476 		if (!gpio_is_ready_dt(&config->ce_gpio)) {
477 			return -ENODEV;
478 		}
479 
480 		ret = gpio_pin_configure_dt(&config->ce_gpio, GPIO_OUTPUT_INACTIVE);
481 		if (ret < 0) {
482 			return ret;
483 		}
484 	} else {
485 		LOG_DBG("Assuming charge enable pin is pulled low");
486 	}
487 
488 	ret = bq24190_register_reset(dev);
489 	if (ret) {
490 		return ret;
491 	}
492 
493 	ret = bq24190_set_config(dev);
494 	if (ret) {
495 		return ret;
496 	}
497 
498 	return i2c_reg_read_byte_dt(&config->i2c, BQ24190_REG_SS, &data->ss_reg);
499 }
500 
501 static DEVICE_API(charger, bq24190_driver_api) = {
502 	.get_property = bq24190_get_prop,
503 	.set_property = bq24190_set_prop,
504 	.charge_enable = bq24190_charge_enable,
505 };
506 
507 #define BQ24190_INIT(inst)                                                                         \
508                                                                                                    \
509 	static const struct bq24190_config bq24190_config_##inst = {                               \
510 		.i2c = I2C_DT_SPEC_INST_GET(inst),                                                 \
511 		.ce_gpio = GPIO_DT_SPEC_INST_GET_OR(inst, ce_gpios, {}),                           \
512 	};                                                                                         \
513                                                                                                    \
514 	static struct bq24190_data bq24190_data_##inst = {                                         \
515 		.ichg_ua = DT_INST_PROP(inst, constant_charge_current_max_microamp),               \
516 		.vreg_uv = DT_INST_PROP(inst, constant_charge_voltage_max_microvolt),              \
517 	};                                                                                         \
518                                                                                                    \
519 	DEVICE_DT_INST_DEFINE(inst, bq24190_init, NULL, &bq24190_data_##inst,                      \
520 			      &bq24190_config_##inst, POST_KERNEL, CONFIG_CHARGER_INIT_PRIORITY,   \
521 			      &bq24190_driver_api);
522 
523 DT_INST_FOREACH_STATUS_OKAY(BQ24190_INIT)
524