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