#include #include #include #include #include #include #include #include #include "batt_mgmt.h" #include "usb_mgmt.h" LOG_MODULE_REGISTER(batt_mgmt, CONFIG_BATT_MGMT_LOG_LEVEL); #define BATT_VDDH_DIVIDER_FACTOR 5 #define BATT_VDDH_ADC_NODE DT_PATH(zephyr_user) static const struct adc_dt_spec batt_vddh_adc = ADC_DT_SPEC_GET_BY_NAME(BATT_VDDH_ADC_NODE, vddh); #define BATT_CHG_STATUS_NODE DT_ALIAS(chg_status) static const struct gpio_dt_spec batt_chg_status = GPIO_DT_SPEC_GET(BATT_CHG_STATUS_NODE, gpios); static bool batt_mgmt_ready; static int64_t batt_mgmt_last_chg_change_ms; static bool batt_mgmt_chg_interrupt_enabled; static K_THREAD_STACK_DEFINE(batt_monitor_stack, CONFIG_BATT_MGMT_MONITOR_THREAD_STACK_SIZE); static struct k_thread batt_monitor_thread_data; static int batt_mgmt_to_adc_oversampling(uint8_t oversampling, uint8_t *adc_oversampling) { if (adc_oversampling == NULL) { return -EINVAL; } switch (oversampling) { case BATT_MGMT_OVERSAMPLING_8X: *adc_oversampling = 3U; return 0; case BATT_MGMT_OVERSAMPLING_16X: *adc_oversampling = 4U; return 0; default: return -EINVAL; } } int batt_mgmt_measure_vddh_mv(uint8_t oversampling, int32_t *vddh_mv) { int rc; uint8_t adc_oversampling; int16_t raw_sample; int32_t input_mv; struct adc_sequence sequence = { .buffer = &raw_sample, .buffer_size = sizeof(raw_sample), }; if (vddh_mv == NULL) { return -EINVAL; } if (!batt_mgmt_ready) { return -EAGAIN; } rc = batt_mgmt_to_adc_oversampling(oversampling, &adc_oversampling); if (rc < 0) { return rc; } rc = adc_sequence_init_dt(&batt_vddh_adc, &sequence); if (rc < 0) { return rc; } sequence.oversampling = adc_oversampling; rc = adc_read_dt(&batt_vddh_adc, &sequence); if (rc < 0) { return rc; } input_mv = raw_sample; rc = adc_raw_to_millivolts_dt(&batt_vddh_adc, &input_mv); if (rc < 0) { return rc; } *vddh_mv = input_mv * BATT_VDDH_DIVIDER_FACTOR; LOG_DBG("Battery VDDH: %d mV (raw=%d, os=%ux)", *vddh_mv, raw_sample, oversampling); return 0; } static void batt_mgmt_chg_interrupt_handler(const struct device *dev, struct gpio_callback *cb, uint32_t pins) { int64_t now_ms = k_uptime_get(); int64_t delta_ms = now_ms - batt_mgmt_last_chg_change_ms; #ifdef CONFIG_BATT_MGMT_BLINK_INTERVAL_LOGGING LOG_DBG("CHG interrupt: delta=%lld ms", delta_ms); #endif batt_mgmt_last_chg_change_ms = now_ms; } static int batt_mgmt_enable_chg_interrupt(void) { int rc; static struct gpio_callback batt_chg_cb; if (batt_mgmt_chg_interrupt_enabled) { return 0; } if (!gpio_is_ready_dt(&batt_chg_status)) { LOG_ERR("CHG GPIO device not ready"); return -ENODEV; } rc = gpio_pin_configure_dt(&batt_chg_status, GPIO_INPUT); if (rc < 0) { LOG_ERR("Failed to configure CHG GPIO: %d", rc); return rc; } gpio_init_callback(&batt_chg_cb, batt_mgmt_chg_interrupt_handler, BIT(batt_chg_status.pin)); rc = gpio_add_callback(batt_chg_status.port, &batt_chg_cb); if (rc < 0) { LOG_ERR("Failed to add CHG callback: %d", rc); return rc; } rc = gpio_pin_interrupt_configure_dt(&batt_chg_status, GPIO_INT_EDGE_BOTH); if (rc < 0) { LOG_ERR("Failed to configure CHG interrupt: %d", rc); return rc; } batt_mgmt_chg_interrupt_enabled = true; batt_mgmt_last_chg_change_ms = k_uptime_get(); LOG_INF("CHG interrupt enabled"); return 0; } static int batt_mgmt_disable_chg_interrupt(void) { int rc; if (!batt_mgmt_chg_interrupt_enabled) { return 0; } rc = gpio_pin_interrupt_configure_dt(&batt_chg_status, GPIO_INT_DISABLE); if (rc < 0) { LOG_WRN("Failed to disable CHG interrupt: %d", rc); return rc; } rc = gpio_pin_configure(batt_chg_status.port, batt_chg_status.pin, GPIO_DISCONNECTED); if (rc < 0) { LOG_WRN("Failed to disconnect CHG GPIO: %d", rc); return rc; } batt_mgmt_chg_interrupt_enabled = false; LOG_DBG("CHG interrupt disabled"); return 0; } bool batt_mgmt_get_charger_status(void) { if (!batt_mgmt_chg_interrupt_enabled) { return false; } return gpio_pin_get_dt(&batt_chg_status); } batt_mgmt_state_t batt_mgmt_get_battery_state(void) { int64_t now_ms = k_uptime_get(); int64_t delta_ms = now_ms - batt_mgmt_last_chg_change_ms; bool chg_high = batt_mgmt_get_charger_status(); bool usb_present = usb_mgmt_is_vbus_present(); /* Blinking detection: state change within window */ if (delta_ms < CONFIG_BATT_MGMT_CHG_BLINKING_WINDOW_MS) { LOG_DBG("Blinking detected (delta=%lld ms)", delta_ms); return BATT_STATE_ERROR; } /* Stable states */ if (chg_high) { return BATT_STATE_CHARGING; } if (usb_present) { return BATT_STATE_FULL; } return BATT_STATE_DISCHARGING; } static void batt_mgmt_monitor_thread(void *unused1, void *unused2, void *unused3) { (void)unused1; (void)unused2; (void)unused3; if (!usb_mgmt_wait_ready(K_SECONDS(2))) { LOG_WRN("usb_mgmt not ready after timeout; using current VBUS state"); } /* Sync initial state in case VBUS was already present before this thread started. */ bool vbus_present = usb_mgmt_is_vbus_present(); if (vbus_present) { (void)batt_mgmt_enable_chg_interrupt(); } else { (void)batt_mgmt_disable_chg_interrupt(); } while (1) { uint32_t events = k_event_wait(&usb_mgmt_events, USB_MGMT_VBUS_CONNECTED | USB_MGMT_VBUS_DISCONNECTED, false, K_FOREVER); if (events & USB_MGMT_VBUS_CONNECTED) { (void)batt_mgmt_enable_chg_interrupt(); } if (events & USB_MGMT_VBUS_DISCONNECTED) { (void)batt_mgmt_disable_chg_interrupt(); } k_event_clear(&usb_mgmt_events, USB_MGMT_VBUS_CONNECTED | USB_MGMT_VBUS_DISCONNECTED); } } static int batt_mgmt_init(void) { int32_t boot_vddh_mv; int rc; if (!adc_is_ready_dt(&batt_vddh_adc)) { LOG_ERR("VDDH ADC device not ready"); return -ENODEV; } rc = adc_channel_setup_dt(&batt_vddh_adc); if (rc < 0) { LOG_ERR("VDDH ADC channel setup failed: %d", rc); return rc; } batt_mgmt_ready = true; rc = batt_mgmt_measure_vddh_mv(BATT_MGMT_OVERSAMPLING_16X, &boot_vddh_mv); if (rc < 0) { LOG_WRN("Initial battery measurement failed: %d", rc); return 0; } LOG_DBG("Initial battery VDDH: %d mV", boot_vddh_mv); k_thread_create(&batt_monitor_thread_data, batt_monitor_stack, K_THREAD_STACK_SIZEOF(batt_monitor_stack), batt_mgmt_monitor_thread, NULL, NULL, NULL, K_PRIO_COOP(CONFIG_BATT_MGMT_MONITOR_THREAD_PRIORITY), 0, K_NO_WAIT); return 0; } SYS_INIT(batt_mgmt_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY);