CDC Badge OS
Firmware for the CDC Badge v1.0 hardware security key
Loading...
Searching...
No Matches
BQ25895Power.cpp
Go to the documentation of this file.
1
8
10#include "cdc_hal/II2cBus.h"
11#include "cdc_hal/hw_config.h"
12#include "cdc_log.h"
13#include "esp_attr.h"
14#include "esp_timer.h"
15#include "driver/gpio.h"
16#include "freertos/FreeRTOS.h"
17#include "freertos/task.h"
18
19static const char* TAG = "BQ25895";
20
21namespace cdc::hal {
22
26static constexpr uint8_t BQ_REG_INPUT_CTRL = 0x00; // Input source control
27static constexpr uint8_t BQ_REG_ADC_CTRL = 0x02; // ADC control
28static constexpr uint8_t BQ_REG_CHG_CTRL = 0x03; // Charge control (SYS_MIN, OTG)
29static constexpr uint8_t BQ_REG_FAST_CHG = 0x04; // Fast charge current
30static constexpr uint8_t BQ_REG_TIMER = 0x07; // Charge timer
31static constexpr uint8_t BQ_REG_MISC = 0x09; // Misc (BATFET_DIS)
32static constexpr uint8_t BQ_REG_SYS_STATUS = 0x0B; // System status
33static constexpr uint8_t BQ_REG_FAULT = 0x0C; // Fault status
34static constexpr uint8_t BQ_REG_BATV = 0x0E; // Battery voltage ADC
35static constexpr uint8_t BQ_REG_SYSV = 0x0F; // System voltage ADC
36static constexpr uint8_t BQ_REG_TS = 0x10; // TS ADC
37static constexpr uint8_t BQ_REG_VBUS = 0x11; // VBUS voltage ADC
38static constexpr uint8_t BQ_REG_ICHG = 0x12; // Charge current ADC
39static constexpr uint8_t BQ_REG_VINDPM = 0x13; // VINDPM threshold
40static constexpr uint8_t BQ_REG_VENDOR = 0x14; // Vendor/Part info
41
45static constexpr uint8_t BQ_ICHG_STEP_MA = 64; // REG04[6:0] step size
46static constexpr uint16_t BQ_SYS_MIN_MV = 3300; // Minimum system voltage
47static constexpr uint16_t CHARGE_CURRENT_SLOW = 512; // Slow charge: 512mA
48static constexpr uint16_t CHARGE_CURRENT_FAST = 1000; // Fast charge: 1000mA
49static constexpr uint16_t CHARGE_CURRENT_MIN = 64;
50static constexpr uint16_t CHARGE_CURRENT_MAX = 1024; // Critical: max for 1200mAh LiPo
51
56static constexpr uint16_t BATTERY_MIN_MV = 2800; // Minimum usable voltage
57static constexpr uint16_t BATTERY_EMPTY_MV = 3200; // 0% calculation point
58static constexpr uint16_t BATTERY_FULL_MV = 4200; // 100% calculation point
59static constexpr uint16_t BATTERY_MAX_MV = 4250; // Maximum safe voltage
60static constexpr uint16_t BATTERY_USB_PASSTHRU_MIN_MV = 4000; // No-battery USB-passthrough range
61static constexpr uint16_t BATTERY_USB_PASSTHRU_MAX_MV = BATTERY_MAX_MV;
62
66static constexpr uint32_t kPowerButtonLongPressMs = 3000;
67
71static volatile bool charger_irq_pending = false;
72
78static void IRAM_ATTR charger_isr(void* arg) {
79 (void)arg;
81}
82
87public:
88 BQ25895Power() = default;
89
94 bool init() override;
95 bool start() override;
96 void stop() override;
97 core::ServiceState getState() const override { return state_; }
98 const char* getName() const override { return "power"; }
100
105 uint16_t getBatteryVoltage() const override;
106 uint8_t getBatteryPercent() const override;
107 bool isUsbConnected() const override;
108 PowerSource getPowerSource() const override;
109 ChargeStatus getChargeStatus() const override;
110 bool isBatteryLow() const override;
111 bool isBatteryCritical() const override;
112 bool isBatteryPresent() const override;
113 void setChargingEnabled(bool enabled) override;
114 void enterShipMode() override;
115 void update() override;
116 void refresh() override;
118
119private:
121 bool readReg(uint8_t reg, uint8_t* value) const;
122 bool writeReg(uint8_t reg, uint8_t value);
123 bool updateRegBits(uint8_t reg, uint8_t mask, uint8_t value, const char* label);
124
126 void readChargerStatus();
127 bool setChargeCurrentMa(uint16_t currentMa);
128
130
132 II2cBus* bus_ = nullptr;
133 I2cDeviceHandle device_ = nullptr;
134
136 void kickWatchdog();
137
139 uint16_t currentChargeMa_ = CHARGE_CURRENT_SLOW;
140 bool fastChargeEnabled_ = false;
141 uint32_t lastWdtKickMs_ = 0;
142
144 bool powerButtonHeld_ = false;
145 uint32_t powerButtonHoldStartMs_ = 0;
146
148 mutable uint16_t cachedBatteryMv_ = 0;
149 mutable ChargeStatus cachedChargeStatus_ = ChargeStatus::NOT_CHARGING;
150 mutable bool cachedUsbConnected_ = false;
151 mutable bool cachedBatteryPresent_ = false;
152
154 ChargeStatus prevChargeStatus_ = ChargeStatus::NOT_CHARGING;
155 bool prevUsbConnected_ = false;
156 bool prevBatteryPresent_ = false;
157};
158
165bool BQ25895Power::readReg(uint8_t reg, uint8_t* value) const {
166 if (!device_ || !value) return false;
167 return bus_->readReg(device_, reg, value, 1) == ESP_OK;
168}
169
176bool BQ25895Power::writeReg(uint8_t reg, uint8_t value) {
177 if (!device_) return false;
178 return bus_->writeReg(device_, reg, &value, 1) == ESP_OK;
179}
180
189bool BQ25895Power::updateRegBits(uint8_t reg, uint8_t mask, uint8_t value, const char* label) {
190 uint8_t current = 0;
191 if (!readReg(reg, &current)) {
192 LOG_E(TAG, "Read failed: %s", label);
193 return false;
194 }
195
196 uint8_t newVal = (current & ~mask) | (value & mask);
197 if (newVal == current) {
198 LOG_D(TAG, "%s already set (0x%02X)", label, current);
199 return true;
200 }
201
202 if (!writeReg(reg, newVal)) {
203 LOG_E(TAG, "Write failed: %s", label);
204 return false;
205 }
206
207 LOG_D(TAG, "%s: 0x%02X -> 0x%02X", label, current, newVal);
208 return true;
209}
210
216 if (state_ != core::ServiceState::UNINITIALIZED) {
217 return state_ == core::ServiceState::INITIALIZED ||
219 }
220
221 LOG_I(TAG, "Initializing BQ25895 power management");
222
223 // Get I2C bus
224 bus_ = getI2cBus0();
225 if (!bus_ || bus_->getState() != core::ServiceState::INITIALIZED) {
226 LOG_E(TAG, "I2C bus not initialized");
228 return false;
229 }
230
231 // Add BQ25895 to I2C bus
232 if (bus_->addDevice(BQ25895_ADDR, &device_) != ESP_OK) {
233 LOG_E(TAG, "Failed to add BQ25895 to I2C0");
235 return false;
236 }
237
238 // Verify chip ID (REG14[5:3] should be 0b111 for BQ25895)
239 uint8_t vendor = 0;
240 if (!readReg(BQ_REG_VENDOR, &vendor)) {
241 LOG_E(TAG, "Failed to read vendor register");
243 return false;
244 }
245 uint8_t partNumber = (vendor >> 3) & 0x07;
246 if (partNumber != 7) {
247 LOG_E(TAG, "BQ25895 not detected (got part=%d)", partNumber);
249 return false;
250 }
251 LOG_I(TAG, "BQ25895 detected (REG14=0x%02X)", vendor);
252
253 // Apply safe defaults (charger has NO persistent storage!)
254
255 // 1) Disable ILIM pin (REG00[6]=0) - use internal limit
256 if (!updateRegBits(BQ_REG_INPUT_CTRL, (1 << 6), 0x00, "ILIM disable")) {
257 return false;
258 }
259
260 // 2) Set minimum system voltage to 3.3V (REG03[3:1])
261 // Formula: SYS_MIN = 3.0V + code*0.1V -> code = 3 for 3.3V
262 uint8_t sysMinCode = (BQ_SYS_MIN_MV - 3000) / 100;
263 if (!updateRegBits(BQ_REG_CHG_CTRL, 0x0E, (uint8_t)(sysMinCode << 1), "SYS_MIN=3.3V")) {
264 return false;
265 }
266
267 // 3) Disable OTG boost (REG03[5]=0)
268 if (!updateRegBits(BQ_REG_CHG_CTRL, (1 << 5), 0x00, "OTG disable")) {
269 return false;
270 }
271
272 // 4) Set initial charge current (default: slow charge 512mA)
273 if (!setChargeCurrentMa(CHARGE_CURRENT_SLOW)) {
274 return false;
275 }
276
277 // 5) Disable USB D+/D- detection (REG02[0]=0)
278 // This prevents the charger from pulling D+/D- during detection.
279 if (!updateRegBits(BQ_REG_ADC_CTRL, (1 << 0), 0x00, "DPDM disable")) {
280 return false;
281 }
282
283 // Configure charger IRQ (active-low, open-drain)
284 gpio_config_t io_conf = {};
285 io_conf.pin_bit_mask = (1ULL << CHG_IRQ_PIN);
286 io_conf.mode = GPIO_MODE_INPUT;
287 io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
288 io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
289 io_conf.intr_type = GPIO_INTR_NEGEDGE;
290 gpio_config(&io_conf);
291
292 // Install ISR service (may already be installed by another driver)
293 esp_err_t err = gpio_install_isr_service(0);
294 if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
295 LOG_E(TAG, "Failed to install ISR service: %s", esp_err_to_name(err));
297 return false;
298 }
299 gpio_isr_handler_add(CHG_IRQ_PIN, charger_isr, nullptr);
300
301 // Configure power/flash button as input (active-low), polled in update()
302 // for the ship-mode long-press.
303 gpio_config_t btn_conf = {};
304 btn_conf.pin_bit_mask = (1ULL << FLASH_BTN_PIN);
305 btn_conf.mode = GPIO_MODE_INPUT;
306 btn_conf.pull_up_en = GPIO_PULLUP_ENABLE;
307 btn_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
308 btn_conf.intr_type = GPIO_INTR_DISABLE;
309 gpio_config(&btn_conf);
310
311 // Read initial status (clears any stale IRQ flags)
312 readChargerStatus();
313
315 LOG_I(TAG, "BQ25895 initialized");
316 return true;
317}
318
324 if (state_ == core::ServiceState::INITIALIZED ||
325 state_ == core::ServiceState::STOPPED) {
327 return true;
328 }
329 return state_ == core::ServiceState::STARTED;
330}
331
336 if (state_ == core::ServiceState::STARTED) {
338 }
339}
340
344void BQ25895Power::readChargerStatus() {
345 uint8_t reg0b = 0, reg0c = 0;
346 uint8_t chrgStat = 0;
347
348 if (readReg(BQ_REG_SYS_STATUS, &reg0b)) {
349 chrgStat = (reg0b >> 3) & 0x03;
350 bool pgStat = (reg0b >> 2) & 0x01;
351 // REG0B[0] VSYS_STAT: 1 = chip in VSYSMIN regulation (battery missing or
352 // below VSYSMIN), 0 = battery is supplying VBAT >= VSYSMIN.
353 bool vsysMinRegulation = (reg0b & 0x01) != 0;
354
355 // Update cached status
356 cachedUsbConnected_ = pgStat;
357
358 switch (chrgStat) {
359 case 0: cachedChargeStatus_ = ChargeStatus::NOT_CHARGING; break;
360 case 1: cachedChargeStatus_ = ChargeStatus::PRE_CHARGE; break;
361 case 2: cachedChargeStatus_ = ChargeStatus::FAST_CHARGE; break;
362 case 3: cachedChargeStatus_ = ChargeStatus::CHARGE_DONE; break;
363 }
364
365 uint16_t vbat = getBatteryVoltage();
366 uint8_t ichgr = 0;
367 readReg(BQ_REG_ICHG, &ichgr);
368 // ICHGR = (REG12[6:0]) * 50mA
369
370 // Primary battery presence signal: VSYS_STAT bit.
371 // Active charging (pre/fast) also implies a battery (overrides VSYS_STAT
372 // during a charge cycle).
373 if (cachedChargeStatus_ == ChargeStatus::FAST_CHARGE ||
374 cachedChargeStatus_ == ChargeStatus::PRE_CHARGE) {
375 cachedBatteryPresent_ = true;
376 } else if (cachedUsbConnected_) {
377 // With USB powering the system the battery is present iff the chip
378 // is NOT in VSYSMIN regulation. CHARGE_DONE without battery keeps
379 // VSYS_STAT=1; CHARGE_DONE with full battery keeps VSYS_STAT=0.
380 cachedBatteryPresent_ = !vsysMinRegulation;
381 } else {
382 // On battery only (no USB): trust VBAT range and VSYS_STAT together.
383 cachedBatteryPresent_ = !vsysMinRegulation &&
384 (vbat >= BATTERY_MIN_MV && vbat <= BATTERY_MAX_MV);
385 }
386
387 // Correct "charge done" to "not charging" if no battery connected
388 if (cachedChargeStatus_ == ChargeStatus::CHARGE_DONE && !cachedBatteryPresent_) {
389 cachedChargeStatus_ = ChargeStatus::NOT_CHARGING;
390 }
391
392 // Only log on status change to avoid spam
393 bool statusChanged = (cachedChargeStatus_ != prevChargeStatus_) ||
394 (cachedUsbConnected_ != prevUsbConnected_) ||
395 (cachedBatteryPresent_ != prevBatteryPresent_);
396
397 if (statusChanged) {
398 const char* chrgText[] = {"Not charging", "Pre-charge", "Fast charging", "Charge done"};
399 uint16_t chargeMa = (ichgr & 0x7F) * 50;
400 if (!cachedBatteryPresent_ && cachedUsbConnected_) {
401 LOG_I(TAG, "USB connected, no battery (VBAT=%dmV, ICHG=%dmA)", vbat, chargeMa);
402 } else {
403 LOG_I(TAG, "Status: %s, USB=%d, VBAT=%dmV, ICHG=%dmA",
404 chrgText[static_cast<int>(cachedChargeStatus_)],
405 cachedUsbConnected_, vbat, chargeMa);
406 }
407
408 prevChargeStatus_ = cachedChargeStatus_;
409 prevUsbConnected_ = cachedUsbConnected_;
410 prevBatteryPresent_ = cachedBatteryPresent_;
411 }
412 }
413
414 if (readReg(BQ_REG_FAULT, &reg0c) && reg0c != 0x00) {
415 // Watchdog fault (0x80) when battery is full is expected behavior
416 if (reg0c == 0x80 && chrgStat == 3 && cachedBatteryPresent_) {
417 LOG_I(TAG, "Battery full");
418 } else if (reg0c == 0x80) {
419 // Watchdog expired after sleep - kick to resume charging
420 LOG_D(TAG, "WDT expired (post-sleep), kicking");
421 kickWatchdog();
422 } else {
423 // Real fault
424 LOG_W(TAG, "Fault: 0x%02X", reg0c);
425 cachedChargeStatus_ = ChargeStatus::FAULT;
426 }
427 }
428}
429
435bool BQ25895Power::setChargeCurrentMa(uint16_t currentMa) {
436 // Clamp to valid range
437 if (currentMa < CHARGE_CURRENT_MIN) currentMa = CHARGE_CURRENT_MIN;
438 if (currentMa > CHARGE_CURRENT_MAX) currentMa = CHARGE_CURRENT_MAX;
439
440 // Calculate register code: ICHG = code * 64mA
441 uint8_t code = (uint8_t)(currentMa / BQ_ICHG_STEP_MA);
442
443 // REG04[6:0] = charge current code
444 if (!updateRegBits(BQ_REG_FAST_CHG, 0x7F, code, "ICHG")) {
445 return false;
446 }
447
448 currentChargeMa_ = code * BQ_ICHG_STEP_MA;
449 LOG_I(TAG, "Charge current set to %dmA (code=%d)", currentChargeMa_, code);
450 return true;
451}
452
458 // Start ADC conversion if not running (REG02[7]=CONV_START)
459 uint8_t reg02 = 0;
460 if (readReg(BQ_REG_ADC_CTRL, &reg02) && !(reg02 & 0x80)) {
461 const_cast<BQ25895Power*>(this)->writeReg(BQ_REG_ADC_CTRL, reg02 | 0x80);
462 vTaskDelay(pdMS_TO_TICKS(10)); // Wait for ADC conversion
463 }
464
465 uint8_t batv = 0;
466 if (!readReg(BQ_REG_BATV, &batv)) {
467 return 0;
468 }
469
470 // Formula: VBAT = 2304mV + (BATV[6:0] * 20mV)
471 uint8_t code = batv & 0x7F;
472 cachedBatteryMv_ = 2304 + (code * 20);
473 return cachedBatteryMv_;
474}
475
481 uint16_t mv = getBatteryVoltage();
482 if (mv == 0) return 0;
483
484 // Linear approximation: BATTERY_EMPTY_MV=0%, BATTERY_FULL_MV=100%
485 if (mv <= BATTERY_EMPTY_MV) return 0;
486 if (mv >= BATTERY_FULL_MV) return 100;
487
488 return (uint8_t)(((uint32_t)(mv - BATTERY_EMPTY_MV) * 100) /
490}
491
497 // Use cached value (updated in update())
498 return cachedUsbConnected_;
499}
500
506 return cachedUsbConnected_ ? PowerSource::USB : PowerSource::BATTERY;
507}
508
514 return cachedChargeStatus_;
515}
516
522 return getBatteryPercent() < 20;
523}
524
530 return getBatteryPercent() < 5;
531}
532
538 return cachedBatteryPresent_;
539}
540
546 if (enabled) {
547 setChargeCurrentMa(fastChargeEnabled_ ? CHARGE_CURRENT_FAST : CHARGE_CURRENT_SLOW);
548 } else {
549 // Set minimum current to effectively disable
550 setChargeCurrentMa(CHARGE_CURRENT_MIN);
551 }
552 LOG_I(TAG, "Charging %s", enabled ? "enabled" : "disabled");
553}
554
559 // Set BATFET_DIS (REG09[5]=1) to disconnect battery
560 // System will only run from USB after this.
561 // User must press PW ON / RESET to wake.
562 uint8_t current = 0;
563 if (!readReg(BQ_REG_MISC, &current)) {
564 LOG_E(TAG, "Failed to read REG09");
565 return;
566 }
567
568 if (!writeReg(BQ_REG_MISC, current | (1 << 5))) {
569 LOG_E(TAG, "Failed to set BATFET_DIS");
570 return;
571 }
572
573 LOG_I(TAG, "Entered shipping mode");
574}
575
579void BQ25895Power::kickWatchdog() {
580 // REG03[6] = 1 resets the I2C watchdog timer
581 // Silently kick without debug log (runs every 30s)
582 uint8_t current = 0;
583 if (readReg(BQ_REG_CHG_CTRL, &current)) {
584 writeReg(BQ_REG_CHG_CTRL, current | (1 << 6));
585 }
586}
587
592 // Proactive watchdog kick every 30s (WDT timeout is 40s)
593 uint32_t nowMs = (uint32_t)(esp_timer_get_time() / 1000);
594 if ((nowMs - lastWdtKickMs_) >= 30000) {
595 lastWdtKickMs_ = nowMs;
596 kickWatchdog();
597 }
598
599 // Power-button long-press enters ship mode (FLASH_BTN is active-low)
600 if (gpio_get_level(FLASH_BTN_PIN) == 0) {
601 if (!powerButtonHeld_) {
602 powerButtonHeld_ = true;
603 powerButtonHoldStartMs_ = nowMs;
604 } else if ((nowMs - powerButtonHoldStartMs_) >= kPowerButtonLongPressMs) {
605 powerButtonHeld_ = false;
606 LOG_W(TAG, "Power button long-press, entering ship mode");
608 }
609 } else {
610 powerButtonHeld_ = false;
611 }
612
613 // Handle charger IRQ
615 charger_irq_pending = false;
616 readChargerStatus();
617 }
618}
619
624 readChargerStatus();
625}
626
631
639
640} // namespace cdc::hal
static const char * TAG
CDC Log: logging over TinyUSB CDC and UART.
#define LOG_W(tag, fmt,...)
Definition cdc_log.h:146
#define LOG_D(tag, fmt,...)
Definition cdc_log.h:148
#define LOG_I(tag, fmt,...)
Definition cdc_log.h:147
#define LOG_E(tag, fmt,...)
Definition cdc_log.h:145
PowerSource getPowerSource() const override
Returns current active power source.
ChargeStatus getChargeStatus() const override
Returns cached charge-state machine value.
void stop() override
Stops power manager service state.
uint8_t getBatteryPercent() const override
Estimates battery percentage from measured voltage.
bool isBatteryPresent() const override
Returns whether a battery is considered present.
bool init() override
Initializes charger hardware and applies safe boot defaults.
void refresh() override
Forces a synchronous re-read of charger status registers.
bool isBatteryLow() const override
Indicates low-battery threshold state.
void update() override
Periodic power-manager update handling watchdog and IRQ-driven refresh.
uint16_t getBatteryVoltage() const override
Returns measured battery voltage in millivolts.
bool isUsbConnected() const override
Returns cached USB power presence.
const char * getName() const override
bool isBatteryCritical() const override
Indicates critical-battery threshold state.
void setChargingEnabled(bool enabled) override
Enables or effectively disables charging current.
void enterShipMode() override
Requests battery ship mode via BATFET disconnect.
bool start() override
Starts power manager service state.
core::ServiceState getState() const override
virtual esp_err_t readReg(I2cDeviceHandle dev, uint8_t reg, uint8_t *data, size_t len)=0
virtual esp_err_t writeReg(I2cDeviceHandle dev, uint8_t reg, const uint8_t *data, size_t len)=0
#define CHG_IRQ_PIN
Definition hw_config.h:24
#define BQ25895_ADDR
Definition hw_config.h:22
#define FLASH_BTN_PIN
Definition hw_config.h:43
static constexpr uint8_t BQ_REG_VBUS
static constexpr uint8_t BQ_REG_ADC_CTRL
static constexpr uint32_t kPowerButtonLongPressMs
Hold duration on the power/flash button that triggers ship mode.
static BQ25895Power g_powerManager
Singleton power manager instance.
II2cBus * getI2cBus0()
Returns singleton instance of I2C bus 0.
Definition I2cBus.cpp:275
static constexpr uint16_t CHARGE_CURRENT_MIN
static constexpr uint8_t BQ_REG_SYSV
static constexpr uint16_t BATTERY_MIN_MV
Battery voltage thresholds for state estimation. LiPo battery characteristics (3.7V nominal,...
static constexpr uint8_t BQ_REG_FAULT
void * I2cDeviceHandle
Definition II2cBus.h:10
static constexpr uint8_t BQ_REG_SYS_STATUS
static constexpr uint16_t BATTERY_FULL_MV
static constexpr uint16_t BATTERY_USB_PASSTHRU_MIN_MV
static constexpr uint8_t BQ_ICHG_STEP_MA
Charge current and safety threshold constants.
static constexpr uint8_t BQ_REG_TS
static constexpr uint8_t BQ_REG_CHG_CTRL
static constexpr uint8_t BQ_REG_MISC
IPowerManager * getPowerManagerInstance()
Returns the singleton power manager instance.
static constexpr uint16_t BATTERY_MAX_MV
static constexpr uint16_t CHARGE_CURRENT_FAST
static constexpr uint16_t CHARGE_CURRENT_MAX
static constexpr uint8_t BQ_REG_INPUT_CTRL
BQ25895 register map constants.
static constexpr uint8_t BQ_REG_FAST_CHG
static constexpr uint16_t BATTERY_EMPTY_MV
static void charger_isr(void *arg)
GPIO interrupt handler for the charger IRQ pin.
static volatile bool charger_irq_pending
Charger IRQ flag set by ISR and consumed in update().
static constexpr uint16_t CHARGE_CURRENT_SLOW
static constexpr uint8_t BQ_REG_TIMER
static constexpr uint16_t BQ_SYS_MIN_MV
static constexpr uint8_t BQ_REG_ICHG
static constexpr uint8_t BQ_REG_VINDPM
static constexpr uint8_t BQ_REG_VENDOR
static constexpr uint16_t BATTERY_USB_PASSTHRU_MAX_MV
static constexpr uint8_t BQ_REG_BATV