// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2021 The Linux Foundation. All rights reserved. * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. */ #define pr_fmt(fmt) "REMOTE-FG: %s: " fmt, __func__ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "battery-profile-loader.h" #include "smb5-iio.h" #include "smblite-remote-bms.h" static struct smblite_remote_bms *the_bms; #define DEBUG_BATT_ID_LOW 6000 #define DEBUG_BATT_ID_HIGH 8500 static bool is_debug_batt_id(struct smblite_remote_bms *bms) { if (is_between(DEBUG_BATT_ID_LOW, DEBUG_BATT_ID_HIGH, bms->batt_id_ohm)) return true; return false; } static int remote_bms_read_prop_from_sdam(struct smblite_remote_bms *bms, int offset, int *val) { if (!bms->nvmem) return -EINVAL; return nvmem_device_read(bms->nvmem, offset, 1, val); } static int get_bms_param_from_smb5_param(int smb_param, int *offset) { int rc = 0; switch (smb_param) { case SMB5_QG_CHARGE_FULL: *offset = CHARGE_FULL; break; case SMB5_QG_CHARGE_FULL_DESIGN: *offset = CHARGE_FULL_DESIGN; break; case SMB5_QG_CURRENT_NOW: *offset = CURRENT_NOW; break; case SMB5_QG_CAPACITY: *offset = CAPACITY; break; case SMB5_QG_TIME_TO_FULL_NOW: *offset = TIME_TO_FULL; break; case SMB5_QG_CYCLE_COUNT: *offset = CYCLE_COUNT; break; case SMB5_QG_CHARGE_COUNTER: *offset = CHARGE_COUNTER; break; default: pr_err_ratelimited("Unsupported SMB param\n"); rc = -EINVAL; break; } return rc; } static int bms_get_buffered_data(int channel, int *val, int src) { int rc = 0, offset; rc = get_bms_param_from_smb5_param(channel, &offset); if (rc < 0) { pr_err("Unsupported remote bms property %d\n", channel); return rc; } if (src == BMS_SDAM) { rc = remote_bms_read_prop_from_sdam(the_bms, offset, val); if (rc < 0) { pr_err("SDAM read failed for property %d, rc=%d\n", channel, rc); return rc; } } else { *val = the_bms->rx_params[offset].data; rc = 0; } pr_debug("remote_bms_param:%u src:%d value:%d\n", offset, src, *val); return rc; } int remote_bms_get_prop(int channel, int *val, int src) { int rc = 0; if (!the_bms) return -EINVAL; switch (channel) { case SMB5_QG_CHARGE_FULL: case SMB5_QG_CHARGE_FULL_DESIGN: case SMB5_QG_CURRENT_NOW: case SMB5_QG_TIME_TO_FULL_NOW: case SMB5_QG_CYCLE_COUNT: case SMB5_QG_CHARGE_COUNTER: rc = bms_get_buffered_data(channel, val, src); break; case SMB5_QG_CAPACITY: if (is_debug_batt_id(the_bms)) { *val = REMOTE_FG_DEBUG_BATT_SOC; } else if (!the_bms->is_seb_up || !the_bms->received_first_data) { pr_debug("Waiting for Co-Proc Up: %d / QBG Data : %d\n", the_bms->is_seb_up, the_bms->received_first_data); return -EAGAIN; } else rc = bms_get_buffered_data(channel, val, src); break; case SMB5_QG_VOLTAGE_NOW: rc = iio_read_channel_processed(the_bms->batt_volt_chan, val); break; case SMB5_QG_DEBUG_BATTERY: *val = is_debug_batt_id(the_bms); break; case SMB5_QG_TEMP: rc = iio_read_channel_processed(the_bms->batt_temp_chan, val); break; case SMB5_QG_RESISTANCE_ID: *val = the_bms->batt_id_ohm; break; default: pr_err("Invalid channel read for remote-fg\n"); rc = -EINVAL; break; } if ((rc < 0) && (rc != -EAGAIN)) pr_err("Failed to read remote bms property %d, rc=%d\n", channel, rc); else pr_debug("Read Remote bms param:%u value:%d\n", channel, *val); return rc; } static int remote_bms_get_data(struct smblite_remote_bms *bms) { int rc, buf_ptr = 0; char *tx_buf; unsigned int tx_buf_size; if (!bms->seb_handle) { pr_err("Not registered with Slate event bridge, cannot request data\n"); return -EINVAL; } mutex_lock(&bms->tx_lock); tx_buf_size = SEB_BUF_HEADER_SIZE + SEB_EACH_OPCODE_SIZE; tx_buf = kzalloc(tx_buf_size, GFP_KERNEL); if (!tx_buf) { mutex_unlock(&bms->tx_lock); return -ENOMEM; } tx_buf[buf_ptr++] = BMS_READ; /* One opcode REQUEST_DATA to request data from remote-fg */ tx_buf[buf_ptr++] = 1; tx_buf[buf_ptr++] = REQUEST_DATA; rc = seb_send_event_to_slate(bms->seb_handle, GMI_SLATE_EVENT_QBG, tx_buf, tx_buf_size); if (rc < 0) { pr_err("Failed to send REQUEST_DATA event to remote-fg, rc=%d\n", rc); goto free_tx_buf; } pr_debug("Send REQUEST_DATA event to remote-fg successful\n"); free_tx_buf: kfree(tx_buf); mutex_unlock(&bms->tx_lock); return rc; } static void periodic_fg_work(struct work_struct *work) { struct smblite_remote_bms *bms = container_of(work, struct smblite_remote_bms, periodic_fg_work.work); int rc = 0; pr_debug("Read runtime data work expired, requesting remote-fg for data\n"); rc = remote_bms_get_data(bms); if (rc < 0) { pr_err("Couldn't request data from remote-fg, rc=%d\n", rc); return; } schedule_delayed_work(&bms->periodic_fg_work, msecs_to_jiffies(BMS_READ_INTERVAL_MS)); } static int remote_bms_send_data(struct smblite_remote_bms *bms) { int rc; char *tx_buf; unsigned int tx_buf_size, buf_ptr = 0; mutex_lock(&bms->tx_lock); tx_buf_size = SEB_BUF_HEADER_SIZE + (TX_MAX * SEB_EACH_PARAM_SIZE); tx_buf = kzalloc(tx_buf_size, GFP_KERNEL); if (!tx_buf) { mutex_unlock(&bms->tx_lock); return -ENOMEM; } tx_buf[buf_ptr++] = BMS_WRITE; tx_buf[buf_ptr++] = TX_MAX; tx_buf[buf_ptr++] = CHARGE_STATUS; *(unsigned int *)(tx_buf + buf_ptr) = (unsigned int)bms->charge_status; buf_ptr += 4; tx_buf[buf_ptr++] = CHARGE_TYPE; *(unsigned int *)(tx_buf + buf_ptr) = (unsigned int)bms->charge_type; buf_ptr += 4; tx_buf[buf_ptr++] = CHARGER_PRESENT; *(unsigned int *)(tx_buf + buf_ptr) = (unsigned int)bms->charger_present; buf_ptr += 4; rc = seb_send_event_to_slate(bms->seb_handle, GMI_SLATE_EVENT_QBG, tx_buf, tx_buf_size); if (rc < 0) { pr_err("Failed to send event to remote-fg, rc=%d\n", rc); goto free_tx_buf; } pr_debug("Send event to remote-fg successful, charge_status:%d charger_present:%d chg_type:%d\n", bms->charge_status, bms->charger_present, bms->charge_type); free_tx_buf: kfree(tx_buf); mutex_unlock(&bms->tx_lock); return rc; } static int remote_bms_handle_recharge(struct smblite_remote_bms *bms) { int rc = 0; union power_supply_propval prop = {0, }; int force_recharge = bms->rx_params[RECHARGE_TRIGGER].data; int recharge_fv = bms->rx_params[RECHARGE_FV].data; int recharge_iterm = bms->rx_params[RECHARGE_ITERM].data; if (force_recharge != bms->force_recharge) { bms->force_recharge = force_recharge; if (!force_recharge) return 0; pr_debug("Recharge configuration requested with FV:%duV Iterm:%dmA\n", recharge_fv, recharge_iterm); if ((recharge_fv >= 0) && (recharge_fv != bms->recharge_float_voltage)) { prop.intval = bms->recharge_float_voltage = recharge_fv; rc = power_supply_set_property(bms->batt_psy, POWER_SUPPLY_PROP_VOLTAGE_MAX, &prop); if (rc < 0) { pr_err("Failed to set recharge voltage_max property on batt_psy, rc=%d\n", rc); return rc; } } /* Configure charger iterm only if digital termination is used */ if (bms->default_iterm_ma && (bms->default_iterm_ma != -EINVAL) && (recharge_iterm != bms->recharge_iterm)) { prop.intval = bms->recharge_iterm = recharge_iterm; /* smblite charger expects termination current with -ve sign */ prop.intval = (-1 * prop.intval); rc = power_supply_set_property(bms->batt_psy, POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, &prop); if (rc < 0) { pr_err("Failed to set recharge charge_term_current property on batt_psy, rc=%d\n", rc); return rc; } } rc = bms->iio_write(bms->dev, PSY_IIO_FORCE_RECHARGE, bms->force_recharge); if (rc < 0) pr_err("Failed to set force recharge, rc=%d\n", rc); pr_debug("Recharge configuration completed with FV:%duV Iterm:%dmA\n", bms->recharge_float_voltage, bms->recharge_iterm); } return rc; } static void rx_data_work(struct work_struct *work) { struct smblite_remote_bms *bms = container_of(work, struct smblite_remote_bms, rx_data_work); int rc = -EINVAL, i; unsigned int buf_ptr = 0, param; unsigned char num_params; char *rx_buf; vote(bms->awake_votable, REMOTE_FG_VOTER, true, 0); mutex_lock(&bms->rx_lock); num_params = *(unsigned char *)(bms->rx_buf + 1); pr_debug("Processing %d parameteres\n", num_params); rx_buf = (char *)(bms->rx_buf + SEB_BUF_HEADER_SIZE); if (num_params > SEB_MAX_RX_PARAMS) num_params = SEB_MAX_RX_PARAMS; for (i = 0, buf_ptr = 0; i < num_params; i++) { param = rx_buf[buf_ptr++]; switch (param) { case CAPACITY: case CURRENT_NOW: case VOLTAGE_OCV: case CYCLE_COUNT: case CHARGE_COUNTER: case CHARGE_FULL: case CHARGE_FULL_DESIGN: case TIME_TO_FULL: case TIME_TO_EMPTY: case SOH: case RECHARGE_TRIGGER: case RECHARGE_FV: case RECHARGE_ITERM: case REQUEST_CHG_DATA: bms->rx_params[param].data = *(unsigned int *)(rx_buf + buf_ptr); buf_ptr += 4; pr_debug("param:%u data:%u\n", param, bms->rx_params[param].data); break; default: pr_debug("Unsupported remote-fg parameter %d with value %d\n", param, *(unsigned int *)(rx_buf + buf_ptr)); buf_ptr += 4; break; } } if (!bms->received_first_data) { pr_debug("First SOC reported from Co-Proc : %d\n", bms->rx_params[CAPACITY].data); bms->received_first_data = true; } rc = remote_bms_handle_recharge(bms); if (rc < 0) { pr_err("Failed to handle recharge, rc=%d\n", rc); goto out; } if (bms->rx_params[REQUEST_CHG_DATA].data) { pr_debug("Charger data requested by remote-fg\n"); rc = remote_bms_send_data(bms); if (rc < 0) { pr_err("Failed to send data to remote-fg, rc=%d\n", rc); goto out; } } if (!rc) pr_debug("Finished processing rx buf from remote-fg\n"); /* Notify Clients so that they pick-up latest data from QBG */ if (bms->batt_psy) power_supply_changed(bms->batt_psy); out: mutex_unlock(&bms->rx_lock); vote(bms->awake_votable, REMOTE_FG_VOTER, false, 0); } static int seb_notifier_cb(struct notifier_block *nb, unsigned long event, void *data) { struct smblite_remote_bms *bms = container_of(nb, struct smblite_remote_bms, seb_nb); int rc, buf_size; u8 command, num_params; if (is_debug_batt_id(bms)) return 0; if (event == GLINK_CHANNEL_STATE_UP) { bms->is_seb_up = true; rc = remote_bms_get_data(bms); if (rc < 0) { pr_err("Couldn't request runtime data from QBG, rc=%d\n"); return rc; } pr_debug("Slate-UP, requested first data from QBG\n"); /* send the first charger status */ if (!work_pending(&bms->psy_status_change_work)) schedule_work(&bms->psy_status_change_work); schedule_delayed_work(&bms->periodic_fg_work, msecs_to_jiffies(BMS_READ_INTERVAL_MS)); return 0; } else if (event != GMI_SLATE_EVENT_QBG) { pr_debug("SEB event is not for GMI_SLATE_EVENT_QBG\n"); return 0; } /* Always process latest data from remote-fg */ cancel_work_sync(&bms->rx_data_work); if (!data) { pr_err("Invalid buffer received from remote-fg\n"); return -EINVAL; } command = *(u8 *)data; if (command != BMS_READ) { pr_err("Packet with invalid command %d received, cannot process\n", command); return -EINVAL; } num_params = *(u8 *)(data + 1); if (!num_params) { pr_err("No params received from remote-fg\n"); return -EINVAL; } pr_debug("Remote-fg data received: event:%d command:%d num_params:%d\n", event, command, num_params); buf_size = SEB_BUF_HEADER_SIZE + (num_params * SEB_EACH_PARAM_SIZE); if (buf_size > SEB_RX_BUF_SIZE) { pr_err("Received more parameters from remote-fg, processing only till %d bytes\n", SEB_RX_BUF_SIZE); } memcpy(bms->rx_buf, data, SEB_RX_BUF_SIZE); /* Process QBG data */ schedule_work(&bms->rx_data_work); /* Cancel and re-schedule periodic work to read QBG data */ cancel_delayed_work(&bms->periodic_fg_work); schedule_delayed_work(&bms->periodic_fg_work, msecs_to_jiffies(BMS_READ_INTERVAL_MS)); return 0; } static int remote_bms_set_battery_params(struct smblite_remote_bms *bms) { int rc; union power_supply_propval prop = {0, }; prop.intval = bms->float_volt_uv; rc = power_supply_set_property(bms->batt_psy, POWER_SUPPLY_PROP_VOLTAGE_MAX, &prop); if (rc < 0) { pr_err("Failed to set voltage_max property on batt_psy, rc=%d\n", rc); return rc; } prop.intval = bms->fastchg_curr_ma * 1000; rc = power_supply_set_property(bms->batt_psy, POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, &prop); if (rc < 0) { pr_err("Failed to set constant_charge_current_max property on batt_psy, rc=%d\n", rc); return rc; } if (bms->default_iterm_ma != -EINVAL) { prop.intval = bms->default_iterm_ma; rc = power_supply_set_property(bms->batt_psy, POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, &prop); if (rc < 0) { pr_err("Failed to set charge_current_max property on batt_psy, rc=%d\n", rc); return rc; } } bms->recharge_float_voltage = bms->recharge_iterm = 0; pr_debug("Set iterm:%dmA max voltage:%duV and max_current:%dmA to charger\n", bms->default_iterm_ma, bms->float_volt_uv, bms->fastchg_curr_ma); return rc; } static bool is_batt_available(struct smblite_remote_bms *bms) { int rc; union power_supply_propval prop = {0, }; if (!bms->batt_psy) { bms->batt_psy = power_supply_get_by_name("battery"); if (!bms->batt_psy) return false; /* * Read termination current from charger, * Use it to configure iterm after recharge upon USB removal. */ rc = power_supply_get_property(bms->batt_psy, POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, &prop); if (!rc) bms->default_iterm_ma = prop.intval; rc = remote_bms_set_battery_params(bms); return (rc < 0) ? false : true; } return true; } static bool is_usb_available(struct smblite_remote_bms *bms) { if (!bms->usb_psy) bms->usb_psy = power_supply_get_by_name("usb"); if (!bms->usb_psy) return false; return true; } static void psy_status_change_work(struct work_struct *work) { struct smblite_remote_bms *bms = container_of(work, struct smblite_remote_bms, psy_status_change_work); int rc; int charge_status, charge_type, charger_present; union power_supply_propval prop = {0, }; vote(bms->awake_votable, REMOTE_FG_VOTER, true, 0); if (!bms->seb_handle) { pr_err("Not registered with Slate event bridge, exiting\n"); goto out; } if (!is_batt_available(bms)) goto out; if (!is_usb_available(bms)) goto out; rc = power_supply_get_property(bms->batt_psy, POWER_SUPPLY_PROP_STATUS, &prop); if (rc < 0) { pr_err("Failed to get battery charge status, rc=%d\n", rc); goto out; } charge_status = prop.intval; rc = power_supply_get_property(bms->usb_psy, POWER_SUPPLY_PROP_PRESENT, &prop); if (rc < 0) { pr_err("Failed to get USB present, rc=%d\n", rc); goto out; } charger_present = prop.intval; rc = power_supply_get_property(bms->batt_psy, POWER_SUPPLY_PROP_CHARGE_TYPE, &prop); if (rc < 0) { pr_err("Failed to get charge type, rc=%d\n", rc); goto out; } charge_type = prop.intval; /* * Set FCC and iterm back to default value on charger removal * as they could have been updated during recharge. */ if (bms->charger_present && !charger_present) { rc = remote_bms_set_battery_params(bms); if (rc < 0) { pr_err("Failed to set battery FCC and FV on charger removal\n"); goto out; } } if ((charge_status != bms->charge_status) || (charger_present != bms->charger_present) || (charge_type != bms->charge_type)) { bms->charge_status = charge_status; bms->charger_present = charger_present; bms->charge_type = charge_type; pr_debug("Charger update: charger_status:%d charger_present:%d charger_type:%d\n", bms->charge_status, bms->charger_present, bms->charge_type); rc = remote_bms_send_data(bms); if (rc < 0) pr_err("Failed to send data to remote-fg, rc=%d\n", rc); } out: vote(bms->awake_votable, REMOTE_FG_VOTER, false, 0); } static int bms_psy_notifier_cb(struct notifier_block *nb, unsigned long event, void *data) { struct power_supply *psy = data; struct smblite_remote_bms *bms = container_of(nb, struct smblite_remote_bms, psy_nb); if (is_debug_batt_id(bms)) return NOTIFY_OK; if (event != PSY_EVENT_PROP_CHANGED) return NOTIFY_OK; if (work_pending(&bms->psy_status_change_work)) return NOTIFY_OK; if ((strcmp(psy->desc->name, "battery") == 0) || (strcmp(psy->desc->name, "usb") == 0)) { schedule_work(&bms->psy_status_change_work); } return NOTIFY_OK; } static int remote_bms_get_adc_props(struct smblite_remote_bms *bms) { int rc = 0; bms->batt_id_chan = iio_channel_get(bms->dev, "batt-id"); if (IS_ERR(bms->batt_id_chan)) { rc = PTR_ERR(bms->batt_id_chan); if (rc != -EPROBE_DEFER) pr_err("batt-id channel unavailable, rc=%d\n", rc); bms->batt_id_chan = NULL; return rc; } rc = iio_read_channel_processed(bms->batt_id_chan, &bms->batt_id_ohm); if (rc < 0) { pr_err("Couldn't read battery ID channel rc=%d\n", rc); return rc; } bms->batt_temp_chan = iio_channel_get(bms->dev, "batt-temp"); if (IS_ERR(bms->batt_temp_chan)) { rc = PTR_ERR(bms->batt_temp_chan); if (rc != -EPROBE_DEFER) pr_err("batt-temp channel unavailable, rc=%d\n", rc); bms->batt_temp_chan = NULL; return rc; } bms->batt_volt_chan = iio_channel_get(bms->dev, "batt-volt"); if (IS_ERR(bms->batt_volt_chan)) { rc = PTR_ERR(bms->batt_volt_chan); if (rc != -EPROBE_DEFER) pr_err("batt-volt channel unavailable, rc=%d\n", rc); bms->batt_volt_chan = NULL; return rc; } return rc; } static int remote_bms_parse_profile(struct smblite_remote_bms *bms) { struct device_node *profile_node; int rc; bms->batt_node = of_find_node_by_name(bms->dev->of_node, "qcom,battery-data"); if (!bms->batt_node) { pr_err("Batterydata not available\n"); return -ENODEV; } profile_node = of_batterydata_get_best_profile(bms->batt_node, bms->batt_id_ohm / 1000, NULL); if (IS_ERR_OR_NULL(profile_node)) { rc = profile_node ? PTR_ERR(profile_node) : -EINVAL; pr_err("Failed to detect valid battery profile %d\n", rc); return 0; } rc = of_property_read_string(profile_node, "qcom,battery-type", &bms->batt_profile_name); if (rc < 0) { pr_err("Failed to get battery profile name, rc=%d\n", rc); return rc; } rc = of_property_read_u32(profile_node, "qcom,max-voltage-uv", &bms->float_volt_uv); if (rc < 0) { pr_err("Failed to read battery float-voltage rc:%d\n", rc); bms->float_volt_uv = -EINVAL; } rc = of_property_read_u32(profile_node, "qcom,fastchg-current-ma", &bms->fastchg_curr_ma); if (rc < 0) { pr_err("Failed to read battery fastcharge current rc:%d\n", rc); bms->fastchg_curr_ma = -EINVAL; } pr_debug("batt_id=%d Ohm profile=%s, FV=%d uV FCC=%d mA\n", bms->batt_id_ohm, bms->batt_profile_name, bms->float_volt_uv, bms->fastchg_curr_ma); return rc; } int remote_bms_init(struct smblite_remote_bms *bms) { int rc; struct seb_notif_info *seb_handle; if (!bms->iio_read || !bms->iio_write) { pr_err("Invalid iio read/write pointers\n"); return -EINVAL; } rc = remote_bms_get_adc_props(bms); if (rc < 0) { pr_err("Couldn't get adc props, rc=%d\n", rc); return rc; } rc = remote_bms_parse_profile(bms); if (rc < 0) { pr_err("Couldn't parse battery profile, rc=%d\n", rc); return rc; } bms->awake_votable = find_votable("AWAKE"); if (bms->awake_votable == NULL) { rc = -EINVAL; pr_err("Couldn't find AWAKE votable rc=%d\n", rc); return rc; } bms->is_seb_up = false; bms->received_first_data = false; mutex_init(&bms->data_lock); mutex_init(&bms->tx_lock); mutex_init(&bms->rx_lock); INIT_DELAYED_WORK(&bms->periodic_fg_work, periodic_fg_work); INIT_WORK(&bms->psy_status_change_work, psy_status_change_work); INIT_WORK(&bms->rx_data_work, rx_data_work); bms->seb_nb.notifier_call = seb_notifier_cb; seb_handle = seb_register_for_slate_event(GMI_SLATE_EVENT_QBG, &bms->seb_nb); if (IS_ERR_OR_NULL(seb_handle)) { rc = seb_handle ? PTR_ERR(seb_handle) : -EINVAL; pr_err("Failed to register with Slate event bridge, rc=%d\n", rc); return rc; } bms->seb_handle = seb_handle; bms->psy_nb.notifier_call = bms_psy_notifier_cb; rc = power_supply_reg_notifier(&bms->psy_nb); if (rc < 0) pr_err("Failed to register psy notifier,rc = %d\n", rc); the_bms = bms; pr_info("smblite remote-fg registered - battery detected:%s\n", is_debug_batt_id(bms) ? "debug_board" : bms->batt_profile_name); return rc; } int remote_bms_deinit(void) { int rc = 0; if (!the_bms) return -ENODEV; cancel_delayed_work_sync(&the_bms->periodic_fg_work); cancel_work_sync(&the_bms->psy_status_change_work); cancel_work_sync(&the_bms->rx_data_work); rc = seb_unregister_for_slate_event(the_bms->seb_handle, &the_bms->seb_nb); if (rc < 0) { pr_err("Couldn't unregister with slate event bridge, rc=%d\n", rc); return rc; } mutex_destroy(&the_bms->data_lock); mutex_destroy(&the_bms->tx_lock); mutex_destroy(&the_bms->rx_lock); the_bms = NULL; return rc; } int remote_bms_resume(void) { if (!the_bms) return -ENODEV; if (is_debug_batt_id(the_bms)) return 0; schedule_delayed_work(&the_bms->periodic_fg_work, 0); return 0; } int remote_bms_suspend(void) { if (!the_bms) return -ENODEV; cancel_delayed_work(&the_bms->periodic_fg_work); cancel_work_sync(&the_bms->psy_status_change_work); cancel_work_sync(&the_bms->rx_data_work); return 0; }