#include #include #include #include #define MODULE battery_module #include #include #include #include #include #include #include #include #include #include #include "bat_state_event.h" #include "module_lifecycle.h" LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF); #define VBATT_NODE DT_PATH(vbatt) #define IP5306_NODE DT_NODELABEL(ip5306) #define BATTERY_SAMPLE_INTERVAL K_SECONDS(1) #define BATTERY_SOC_MIN_MV 3300 #define BATTERY_SOC_MAX_MV 4200 BUILD_ASSERT(DT_NODE_HAS_STATUS(VBATT_NODE, okay), "Missing /vbatt voltage-divider node in devicetree"); BUILD_ASSERT(DT_NODE_HAS_STATUS(IP5306_NODE, okay), "Missing ip5306 node in devicetree"); struct battery_module_ctx { struct module_lifecycle_ctx lc; const struct device *vbatt_dev; const struct device *ip5306_dev; struct k_work_delayable battery_sample_work; struct { bool valid; uint8_t soc; bool charging; bool full; } last_bat_state; }; static int do_init(void); static int do_start(void); static int do_stop(void); static const struct module_lifecycle_cfg lifecycle_cfg = { .mode = ML_MODE_POWER, .stopped_state = MODULE_STATE_STANDBY, }; static const struct module_lifecycle_ops lifecycle_ops = { .do_init = do_init, .do_start = do_start, .do_stop = do_stop, }; static struct battery_module_ctx ctx = { .lc = { .state = LC_UNINIT, .cfg = &lifecycle_cfg, .ops = &lifecycle_ops, }, .vbatt_dev = DEVICE_DT_GET(VBATT_NODE), .ip5306_dev = DEVICE_DT_GET(IP5306_NODE), }; static int sensor_value_to_mv(const struct sensor_value *value) { return (value->val1 * 1000) + (value->val2 / 1000); } static int measurement_enable(bool enable) { enum pm_device_action action = enable ? PM_DEVICE_ACTION_RESUME : PM_DEVICE_ACTION_SUSPEND; int err = pm_device_action_run(ctx.vbatt_dev, action); if (err && (err != -EALREADY) && (err != -ENOTSUP)) { LOG_ERR("Cannot %s vbatt sensor (%d)", enable ? "resume" : "suspend", err); return err; } return 0; } static uint8_t battery_soc_from_mv(int voltage_mv) { const int span_mv = BATTERY_SOC_MAX_MV - BATTERY_SOC_MIN_MV; int bucket; if (voltage_mv <= BATTERY_SOC_MIN_MV) { return 0U; } if (voltage_mv >= BATTERY_SOC_MAX_MV) { return 100U; } bucket = ((voltage_mv - BATTERY_SOC_MIN_MV) * 10 + (span_mv / 2)) / span_mv; return (uint8_t)(bucket * 10); } static void battery_sample_fn(struct k_work *work) { struct ip5306_status pmic_status; struct sensor_value voltage; int voltage_mv; int err; ARG_UNUSED(work); if (!module_lifecycle_is_running(&ctx.lc)) { return; } err = sensor_sample_fetch(ctx.vbatt_dev); if (err) { LOG_WRN("Battery sample fetch failed (%d)", err); goto reschedule; } err = sensor_channel_get(ctx.vbatt_dev, SENSOR_CHAN_VOLTAGE, &voltage); if (err) { LOG_WRN("Battery channel get failed (%d)", err); goto reschedule; } err = ip5306_get_status(ctx.ip5306_dev, &pmic_status); if (err) { LOG_WRN("IP5306 status read failed (%d)", err); goto reschedule; } voltage_mv = sensor_value_to_mv(&voltage); uint8_t soc = battery_soc_from_mv(voltage_mv); if (!ctx.last_bat_state.valid || (ctx.last_bat_state.soc != soc) || (ctx.last_bat_state.charging != pmic_status.charging) || (ctx.last_bat_state.full != pmic_status.full)) { ctx.last_bat_state.valid = true; ctx.last_bat_state.soc = soc; ctx.last_bat_state.charging = pmic_status.charging; ctx.last_bat_state.full = pmic_status.full; submit_bat_state_event(soc, pmic_status.charging, pmic_status.full); } reschedule: if (module_lifecycle_is_running(&ctx.lc)) { k_work_reschedule(&ctx.battery_sample_work, BATTERY_SAMPLE_INTERVAL); } } static int do_init(void) { if (!device_is_ready(ctx.vbatt_dev)) { LOG_ERR("vbatt device not ready"); return -ENODEV; } if (!device_is_ready(ctx.ip5306_dev)) { LOG_ERR("ip5306 device not ready"); return -ENODEV; } int err = ip5306_init(ctx.ip5306_dev); if (err) { LOG_ERR("ip5306 init failed (%d)", err); return err; } k_work_init_delayable(&ctx.battery_sample_work, battery_sample_fn); memset(&ctx.last_bat_state, 0, sizeof(ctx.last_bat_state)); power_manager_restrict(MODULE_IDX(MODULE), POWER_MANAGER_LEVEL_SUSPENDED); return 0; } static int do_start(void) { int err; if (module_lifecycle_is_running(&ctx.lc)) { return 0; } err = measurement_enable(true); if (err) { return err; } k_work_reschedule(&ctx.battery_sample_work, K_NO_WAIT); return 0; } static int do_stop(void) { if (!module_lifecycle_is_running(&ctx.lc)) { return 0; } (void)k_work_cancel_delayable(&ctx.battery_sample_work); (void)measurement_enable(false); return 0; } static bool app_event_handler(const struct app_event_header *aeh) { if (is_module_state_event(aeh)) { const struct module_state_event *event = cast_module_state_event(aeh); if (check_state(event, MODULE_ID(main), MODULE_STATE_READY)) { (void)module_set_lifecycle(&ctx.lc, LC_RUNNING); } return false; } if (is_power_down_event(aeh)) { if (module_lifecycle_is_initialized(&ctx.lc)) { (void)module_set_lifecycle(&ctx.lc, LC_STOPPED); } return false; } if (is_wake_up_event(aeh)) { if (module_lifecycle_is_initialized(&ctx.lc)) { (void)module_set_lifecycle(&ctx.lc, LC_RUNNING); } return false; } __ASSERT_NO_MSG(false); return false; } APP_EVENT_LISTENER(MODULE, app_event_handler); APP_EVENT_SUBSCRIBE(MODULE, module_state_event); APP_EVENT_SUBSCRIBE_EARLY(MODULE, power_down_event); APP_EVENT_SUBSCRIBE(MODULE, wake_up_event);