// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2020-2021 The Linux Foundation. All rights reserved. * Copyright (c) 2022-2023, 2025 Qualcomm Innovation Center, Inc. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "battery-profile-loader.h" #include "smblite-lib.h" #include "smblite-reg.h" #include "step-chg-jeita.h" #include "storm-watch.h" #include "schgm-flashlite.h" #include "smb5-iio.h" #define smblite_lib_err(chg, fmt, ...) \ pr_err("%s: %s: " fmt, chg->name, \ __func__, ##__VA_ARGS__) \ #define smblite_lib_dbg(chg, reason, fmt, ...) \ do { \ if (*chg->debug_mask & (reason)) \ pr_info("%s: %s: " fmt, chg->name, \ __func__, ##__VA_ARGS__); \ else \ pr_debug("%s: %s: " fmt, chg->name, \ __func__, ##__VA_ARGS__); \ } while (0) #define typec_rp_med_high(chg, typec_mode) \ ((typec_mode == QTI_POWER_SUPPLY_TYPEC_SOURCE_MEDIUM \ || typec_mode == QTI_POWER_SUPPLY_TYPEC_SOURCE_HIGH) \ && (!chg->typec_legacy || chg->typec_legacy_use_rp_icl)) static void update_sw_icl_max(struct smb_charger *chg, int type); static int smblite_lib_get_prop_typec_mode(struct smb_charger *chg); #define MIN_ADDRESS_RANGE 0x100 int smblite_lib_read(struct smb_charger *chg, u16 addr, u8 *val) { unsigned int value; int rc = 0; /* Ignore address range below MIN_ADDRESS_RANGE */ if (addr < MIN_ADDRESS_RANGE) { pr_debug("Invalid addr = 0x%x\n", addr); return 0; } rc = regmap_read(chg->regmap, addr, &value); if (rc >= 0) *val = (u8)value; return rc; } int smblite_lib_batch_read(struct smb_charger *chg, u16 addr, u8 *val, int count) { /* Ignore address range below MIN_ADDRESS_RANGE */ if (addr < MIN_ADDRESS_RANGE) { pr_debug("Invalid addr = 0x%x\n", addr); return 0; } return regmap_bulk_read(chg->regmap, addr, val, count); } int smblite_lib_write(struct smb_charger *chg, u16 addr, u8 val) { /* Ignore address range below MIN_ADDRESS_RANGE */ if (addr < MIN_ADDRESS_RANGE) { pr_debug("Invalid addr = 0x%x\n", addr); return 0; } return regmap_write(chg->regmap, addr, val); } int smblite_lib_batch_write(struct smb_charger *chg, u16 addr, u8 *val, int count) { /* Ignore address range below MIN_ADDRESS_RANGE */ if (addr < MIN_ADDRESS_RANGE) { pr_debug("Invalid addr = 0x%x\n", addr); return 0; } return regmap_bulk_write(chg->regmap, addr, val, count); } int smblite_lib_masked_write(struct smb_charger *chg, u16 addr, u8 mask, u8 val) { /* Ignore address range below MIN_ADDRESS_RANGE */ if (addr < MIN_ADDRESS_RANGE) { pr_debug("Invalid addr = 0x%x\n", addr); return 0; } return regmap_update_bits(chg->regmap, addr, mask, val); } int smblite_lib_get_iio_channel(struct smb_charger *chg, const char *propname, struct iio_channel **chan) { int rc = 0; rc = of_property_match_string(chg->dev->of_node, "io-channel-names", propname); if (rc < 0) return 0; *chan = iio_channel_get(chg->dev, propname); if (IS_ERR(*chan)) { rc = PTR_ERR(*chan); if (rc != -EPROBE_DEFER) smblite_lib_err(chg, "%s channel unavailable, %d\n", propname, rc); *chan = NULL; } return rc; } #define DIV_FACTOR_MICRO_V_I 1 #define DIV_FACTOR_MILI_V_I 1000 #define DIV_FACTOR_DECIDEGC 100 int smblite_lib_icl_override(struct smb_charger *chg, enum icl_override_mode mode) { int rc; u8 usb51_mode, icl_override; switch (mode) { case SW_OVERRIDE_USB51_MODE: usb51_mode = 0; icl_override = ICL_OVERRIDE_BIT; break; case SW_OVERRIDE_HC_MODE: usb51_mode = USBIN_MODE_CHG_BIT; icl_override = ICL_OVERRIDE_BIT; break; case HW_AUTO_MODE: default: usb51_mode = USBIN_MODE_CHG_BIT; icl_override = 0; break; } rc = smblite_lib_masked_write(chg, USBIN_ICL_OPTIONS_REG(chg->base), USBIN_MODE_CHG_BIT, usb51_mode); if (rc < 0) { smblite_lib_err(chg, "Couldn't set USBIN_ICL_OPTIONS rc=%d\n", rc); return rc; } rc = smblite_lib_masked_write(chg, CMD_ICL_OVERRIDE_REG(chg->base), ICL_OVERRIDE_BIT, icl_override); if (rc < 0) { smblite_lib_err(chg, "Couldn't override ICL rc=%d\n", rc); return rc; } return rc; } static void smblite_lib_notify_extcon_props(struct smb_charger *chg, int id) { union extcon_property_value val; int prop_val; if (chg->connector_type == QTI_POWER_SUPPLY_CONNECTOR_TYPEC) { smblite_lib_get_prop_typec_cc_orientation(chg, &prop_val); val.intval = ((prop_val == 2) ? 1 : 0); extcon_set_property(chg->extcon, id, EXTCON_PROP_USB_TYPEC_POLARITY, val); val.intval = true; extcon_set_property(chg->extcon, id, EXTCON_PROP_USB_SS, val); } else if (chg->connector_type == QTI_POWER_SUPPLY_CONNECTOR_MICRO_USB) { /* * To send extcon notification for SS mode for 10pin * Micro AB 3.0 connector type. */ if (chg->uusb_ss_mode_extcon_enable) val.intval = true; else val.intval = false; extcon_set_property(chg->extcon, id, EXTCON_PROP_USB_SS, val); } } static void smblite_lib_notify_device_mode(struct smb_charger *chg, bool enable) { if (enable) smblite_lib_notify_extcon_props(chg, EXTCON_USB); extcon_set_state_sync(chg->extcon, EXTCON_USB, enable); } #define VBOOST_5P00V 0x03 static void smblite_lib_notify_usb_host(struct smb_charger *chg, bool enable) { int rc = 0; /* LDO mode doesn't support OTG */ if (chg->ldo_mode) return; if (enable) { smblite_lib_dbg(chg, PR_OTG, "enabling VBUS in OTG mode\n"); rc = smblite_lib_masked_write(chg, DCDC_CMD_OTG_REG(chg->base), OTG_EN_BIT, OTG_EN_BIT); if (rc < 0) { smblite_lib_err(chg, "Couldn't enable VBUS in OTG mode rc=%d\n", rc); return; } rc = smblite_lib_masked_write(chg, DCDC_BST_VREG_SEL(chg->base), VBOOST_MASK, VBOOST_5P00V); if (rc < 0) { smblite_lib_err(chg, "Couldn't write BST_VREG_SEL rc=%d\n", rc); return; } smblite_lib_notify_extcon_props(chg, EXTCON_USB_HOST); } else { smblite_lib_dbg(chg, PR_OTG, "disabling VBUS in OTG mode\n"); rc = smblite_lib_masked_write(chg, DCDC_CMD_OTG_REG(chg->base), OTG_EN_BIT, 0); if (rc < 0) { smblite_lib_err(chg, "Couldn't disable VBUS in OTG mode rc=%d\n", rc); return; } } extcon_set_state_sync(chg->extcon, EXTCON_USB_HOST, enable); chg->otg_present = enable; } /******************** * REGISTER GETTERS * ********************/ int smblite_lib_get_charge_param(struct smb_charger *chg, struct smb_chg_param *param, int *val_u) { int rc = 0; u8 val_raw; rc = smblite_lib_read(chg, param->reg, &val_raw); if (rc < 0) { smblite_lib_err(chg, "%s: Couldn't read from 0x%04x rc=%d\n", param->name, param->reg, rc); return rc; } if (param->get_proc) *val_u = param->get_proc(param, val_raw); else *val_u = val_raw * param->step_u + param->min_u; smblite_lib_dbg(chg, PR_REGISTER, "%s = %d (0x%02x)\n", param->name, *val_u, val_raw); return rc; } #define INPUT_NOT_PRESENT 0 #define INPUT_PRESENT_USB BIT(1) static int smblite_lib_is_input_present(struct smb_charger *chg, int *present) { int rc; union power_supply_propval pval = {0, }; *present = INPUT_NOT_PRESENT; rc = smblite_lib_get_prop_usb_present(chg, &pval); if (rc < 0) { pr_err("Couldn't get usb presence status rc=%d\n", rc); return rc; } *present |= pval.intval ? INPUT_PRESENT_USB : INPUT_NOT_PRESENT; return 0; } /******************** * REGISTER SETTERS * ********************/ int smblite_lib_set_charge_param(struct smb_charger *chg, struct smb_chg_param *param, int val_u) { int rc = 0; u8 val_raw; if (param->set_proc) { rc = param->set_proc(param, val_u, &val_raw); if (rc < 0) return -EINVAL; } else { if (val_u > param->max_u || val_u < param->min_u) smblite_lib_dbg(chg, PR_MISC, "%s: %d is out of range [%d, %d]\n", param->name, val_u, param->min_u, param->max_u); if (val_u > param->max_u) val_u = param->max_u; if (val_u < param->min_u) val_u = param->min_u; val_raw = (val_u - param->min_u) / param->step_u; } rc = smblite_lib_write(chg, param->reg, val_raw); if (rc < 0) { smblite_lib_err(chg, "%s: Couldn't write 0x%02x to 0x%04x rc=%d\n", param->name, val_raw, param->reg, rc); return rc; } smblite_lib_dbg(chg, PR_REGISTER, "%s = %d (0x%02x)\n", param->name, val_u, val_raw); return rc; } int smblite_lib_set_usb_suspend(struct smb_charger *chg, bool suspend) { int rc = 0; if (suspend) vote(chg->icl_irq_disable_votable, USB_SUSPEND_VOTER, true, 0); rc = smblite_lib_masked_write(chg, USBIN_INPUT_SUSPEND_REG(chg->base), USBIN_SUSPEND_BIT, suspend ? USBIN_SUSPEND_BIT : 0); if (rc < 0) smblite_lib_err(chg, "Couldn't write %s to USBIN_SUSPEND_BIT rc=%d\n", suspend ? "suspend" : "resume", rc); if (!suspend) vote(chg->icl_irq_disable_votable, USB_SUSPEND_VOTER, false, 0); return rc; } /******************** * HELPER FUNCTIONS * ********************/ /* QG/FG channels */ static const char * const smblite_lib_qg_ext_iio_chan[] = { [SMB5_QG_DEBUG_BATTERY] = "debug_battery", [SMB5_QG_CAPACITY] = "capacity", [SMB5_QG_CURRENT_NOW] = "current_now", [SMB5_QG_VOLTAGE_NOW] = "voltage_now", [SMB5_QG_VOLTAGE_MAX] = "voltage_max", [SMB5_QG_CHARGE_FULL] = "charge_full", [SMB5_QG_RESISTANCE_ID] = "resistance_id", [SMB5_QG_TEMP] = "temp", [SMB5_QG_CHARGE_COUNTER] = "charge_counter", [SMB5_QG_CYCLE_COUNT] = "cycle_count", [SMB5_QG_CHARGE_FULL_DESIGN] = "charge_full_design", [SMB5_QG_TIME_TO_FULL_NOW] = "time_to_full_now", }; int smblite_lib_get_prop_from_bms(struct smb_charger *chg, int channel, int *val) { int rc; if (chg->is_fg_remote) { rc = remote_bms_get_prop(channel, val, BMS_GLINK); if ((rc < 0) && (rc != -EAGAIN)) smblite_lib_err(chg, "Couldn't get prop from remote bms, rc = %d", rc); } else { if (IS_ERR_OR_NULL(chg->iio_chan_list_qg)) return -ENODEV; rc = iio_read_channel_processed(chg->iio_chan_list_qg[channel], val); } return rc < 0 ? rc : 0; } enum chg_type { UNKNOWN, SDP, CDP, DCP, OCP, FLOAT, MAX_TYPES }; static const struct apsd_result smblite_apsd_results[] = { [UNKNOWN] = { .name = "UNKNOWN", .bit = 0, .val = POWER_SUPPLY_TYPE_UNKNOWN }, [SDP] = { .name = "SDP", .bit = SDP_CHARGER_BIT, .val = POWER_SUPPLY_TYPE_USB }, [CDP] = { .name = "CDP", .bit = CDP_CHARGER_BIT, .val = POWER_SUPPLY_TYPE_USB_CDP }, [DCP] = { .name = "DCP", .bit = DCP_CHARGER_BIT, .val = POWER_SUPPLY_TYPE_USB_DCP }, [OCP] = { .name = "OCP", .bit = OCP_CHARGER_BIT, .val = POWER_SUPPLY_TYPE_USB_DCP }, [FLOAT] = { .name = "FLOAT", .bit = FLOAT_CHARGER_BIT, .val = QTI_POWER_SUPPLY_TYPE_USB_FLOAT }, }; const struct apsd_result *smblite_lib_get_apsd_result(struct smb_charger *chg) { int rc, i; u8 apsd_stat; const struct apsd_result *result = &smblite_apsd_results[UNKNOWN]; rc = smblite_lib_read(chg, APSD_STATUS_REG(chg->base), &apsd_stat); if (rc < 0) { smblite_lib_err(chg, "Couldn't read APSD_STATUS rc=%d\n", rc); return result; } smblite_lib_dbg(chg, PR_REGISTER, "APSD_STATUS = 0x%02x\n", apsd_stat); if (!(apsd_stat & APSD_DTC_STATUS_DONE_BIT)) return result; rc = smblite_lib_read(chg, APSD_RESULT_STATUS_REG(chg->base), &apsd_stat); if (rc < 0) { smblite_lib_err(chg, "Couldn't read APSD_RESULT_STATUS rc=%d\n", rc); return result; } apsd_stat &= APSD_RESULT_STATUS_MASK; for (i = 0; i < ARRAY_SIZE(smblite_apsd_results); i++) { if (smblite_apsd_results[i].bit == apsd_stat) result = &smblite_apsd_results[i]; } /* Report HVDCP Adapter as DCP.*/ if (apsd_stat & QC_3P0_BIT) result = &smblite_apsd_results[DCP]; return result; } static const struct apsd_result *smblite_lib_update_usb_type(struct smb_charger *chg, enum power_supply_type type) { const struct apsd_result *apsd_result = smblite_lib_get_apsd_result(chg); if (apsd_result->val == POWER_SUPPLY_TYPE_UNKNOWN) chg->real_charger_type = type; /* * Update real charger type only if its not FLOAT * detected as SDP */ if (apsd_result->val != POWER_SUPPLY_TYPE_UNKNOWN && !(apsd_result->val == QTI_POWER_SUPPLY_TYPE_USB_FLOAT && chg->real_charger_type == POWER_SUPPLY_TYPE_USB)) chg->real_charger_type = apsd_result->val; smblite_update_usb_desc(chg); smblite_lib_dbg(chg, PR_MISC, "APSD=%s, real_charger_type =%d\n", apsd_result->name, chg->real_charger_type); return apsd_result; } static int smblite_lib_notifier_call(struct notifier_block *nb, unsigned long ev, void *v) { struct power_supply *psy = v; struct smb_charger *chg = container_of(nb, struct smb_charger, nb); if (!strcmp(psy->desc->name, "bms")) { if (ev == PSY_EVENT_PROP_CHANGED) schedule_work(&chg->bms_update_work); } if (chg->jeita_configured == JEITA_CFG_NONE) schedule_work(&chg->jeita_update_work); return NOTIFY_OK; } static int smblite_lib_register_notifier(struct smb_charger *chg) { int rc; chg->nb.notifier_call = smblite_lib_notifier_call; rc = power_supply_reg_notifier(&chg->nb); if (rc < 0) { smblite_lib_err(chg, "Couldn't register psy notifier rc = %d\n", rc); return rc; } return 0; } bool is_concurrent_mode_supported(struct smb_charger *chg) { return (chg->concurrent_mode_supported && chg->subtype == PM5100); } static int smblite_lib_concurrent_mode_config(struct smb_charger *chg, bool enable) { int rc; if (!is_concurrent_mode_supported(chg)) return 0; rc = smblite_lib_masked_write(chg, CONCURRENT_MODE_CFG_REG(chg->base), CONCURRENT_MODE_EN_BIT, !!enable); if (rc < 0) smblite_lib_err(chg, "Failed to write CONCURRENT_MODE_CFG_REG rc=%d\n", rc); if (!enable) { /* Remove usb_icl_vote when concurrency mode is disabled */ rc = vote(chg->usb_icl_votable, CONCURRENT_MODE_VOTER, false, 0); if (rc < 0) smblite_lib_err(chg, "Failed to vote on ICL rc=%d\n", rc); /* Remove chg_disable_vote when concurrency mode is disabled */ rc = vote(chg->chg_disable_votable, CONCURRENT_MODE_VOTER, false, 0); if (rc < 0) smblite_lib_err(chg, "Failed to vote on ICL rc=%d\n", rc); } return rc; } static void smblite_lib_uusb_removal(struct smb_charger *chg) { int rc; struct smb_irq_data *data; struct storm_watch *wdata; cancel_delayed_work_sync(&chg->pl_enable_work); if (chg->wa_flags & BOOST_BACK_WA) { data = chg->irq_info[SWITCHER_POWER_OK_IRQ].irq_data; if (data) { wdata = &data->storm_data; update_storm_count(wdata, WEAK_CHG_STORM_COUNT); vote(chg->usb_icl_votable, BOOST_BACK_VOTER, false, 0); vote(chg->usb_icl_votable, WEAK_CHARGER_VOTER, false, 0); } } vote(chg->pl_disable_votable, PL_DELAY_VOTER, true, 0); vote(chg->awake_votable, PL_DELAY_VOTER, false, 0); /* reset both usbin current and voltage votes */ vote(chg->pl_enable_votable_indirect, USBIN_I_VOTER, false, 0); vote(chg->pl_enable_votable_indirect, USBIN_V_VOTER, false, 0); vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, true, is_flashlite_active(chg) ? USBIN_500UA : USBIN_100UA); vote(chg->usb_icl_votable, FLASH_ACTIVE_VOTER, false, 0); vote_override(chg->fcc_main_votable, FCC_STEPPER_VOTER, false, chg->chg_param.fcc_step_start_ua); /* Remove SW thermal regulation votes */ vote(chg->usb_icl_votable, SW_THERM_REGULATION_VOTER, false, 0); vote(chg->awake_votable, SW_THERM_REGULATION_VOTER, false, 0); /* clear USB ICL vote for USB_PSY_VOTER */ rc = vote(chg->usb_icl_votable, USB_PSY_VOTER, false, 0); if (rc < 0) smblite_lib_err(chg, "Couldn't un-vote for USB ICL rc=%d\n", rc); chg->uusb_apsd_rerun_done = false; chg->hvdcp3_detected = false; /* Disable concurrent mode on USB removal. */ smblite_lib_concurrent_mode_config(chg, false); chg->concurrent_mode_status = false; } void smblite_lib_suspend_on_debug_battery(struct smb_charger *chg) { int rc, val; if (!chg->iio_chan_list_qg && !chg->is_fg_remote) return; rc = smblite_lib_get_prop_from_bms(chg, SMB5_QG_DEBUG_BATTERY, &val); if (rc < 0) { smblite_lib_err(chg, "Couldn't get debug battery prop rc=%d\n", rc); return; } chg->is_debug_batt = val; if (chg->suspend_input_on_debug_batt) { vote(chg->usb_icl_votable, DEBUG_BOARD_VOTER, val, 0); if (val) { pr_info("Input suspended: Fake battery\n"); schgm_flashlite_config_usbin_collapse(chg, false); } } else { vote(chg->chg_disable_votable, DEBUG_BOARD_VOTER, val, 0); } } static int set_sdp_current(struct smb_charger *chg, int icl_ua) { int rc; u8 icl_options; enum icl_override_mode icl_override = SW_OVERRIDE_USB51_MODE; /* power source is SDP */ switch (icl_ua) { case USBIN_100UA: case USBIN_150UA: /* USB 2.0 100mA */ icl_options = 0; break; case USBIN_500UA: /* USB 2.0 500mA*/ icl_options = USB51_MODE_BIT; break; default: /* Use ICL configuration register for HC_MODE*/ icl_override = SW_OVERRIDE_HC_MODE; icl_options = USBIN_MODE_CHG_BIT; break; } rc = smblite_lib_set_charge_param(chg, &chg->param.usb_icl, icl_ua); if (rc < 0) { smblite_lib_err(chg, "Couldn't set HC ICL rc=%d\n", rc); return rc; } rc = smblite_lib_masked_write(chg, USBIN_ICL_OPTIONS_REG(chg->base), CFG_USB3P0_SEL_BIT | USB51_MODE_BIT, icl_options); if (rc < 0) { smblite_lib_err(chg, "Couldn't set ICL options rc=%d\n", rc); return rc; } rc = smblite_lib_icl_override(chg, icl_override); if (rc < 0) { smblite_lib_err(chg, "Couldn't set ICL override rc=%d\n", rc); return rc; } return rc; } int smblite_lib_set_icl_current(struct smb_charger *chg, const int icl_ua) { int rc = 0; enum icl_override_mode icl_override = HW_AUTO_MODE; /* suspend if 25mA or less is requested */ bool suspend = (icl_ua <= USBIN_25UA); if (chg->subtype == PM2250) schgm_flashlite_torch_priority(chg, suspend ? TORCH_BOOST_MODE : TORCH_BUCK_MODE); /* Do not configure ICL from SW for DAM */ if (smblite_lib_get_prop_typec_mode(chg) == QTI_POWER_SUPPLY_TYPEC_SINK_DEBUG_ACCESSORY) return 0; if (suspend) return smblite_lib_set_usb_suspend(chg, true); /* No client updated it's vote */ if (icl_ua == INT_MAX) goto set_mode; /* configure current */ if (chg->real_charger_type == POWER_SUPPLY_TYPE_USB && (chg->typec_legacy || chg->typec_mode == QTI_POWER_SUPPLY_TYPEC_SOURCE_DEFAULT || chg->connector_type == QTI_POWER_SUPPLY_CONNECTOR_MICRO_USB)) { rc = set_sdp_current(chg, icl_ua); if (rc < 0) { smblite_lib_err(chg, "Couldn't set SDP ICL rc=%d\n", rc); goto out; } goto unsuspend; } else { /* * Try USB 2.0/3,0 option first on USB path when maximum input * current limit is 500mA or below for better accuracy; in case * of error, proceed to use USB high-current mode. */ if (icl_ua <= USBIN_500UA) { rc = set_sdp_current(chg, icl_ua); if (rc >= 0) goto unsuspend; } rc = smblite_lib_set_charge_param(chg, &chg->param.usb_icl, icl_ua); if (rc < 0) { smblite_lib_err(chg, "Couldn't set HC ICL rc=%d\n", rc); goto out; } icl_override = SW_OVERRIDE_HC_MODE; } set_mode: rc = smblite_lib_icl_override(chg, icl_override); if (rc < 0) { smblite_lib_err(chg, "Couldn't set ICL override rc=%d\n", rc); goto out; } unsuspend: /* unsuspend after configuring current and override */ rc = smblite_lib_set_usb_suspend(chg, false); if (rc < 0) { smblite_lib_err(chg, "Couldn't resume input rc=%d\n", rc); goto out; } /* Re-run AICL */ if (icl_override != SW_OVERRIDE_HC_MODE) rc = smblite_lib_run_aicl(chg, RERUN_AICL); out: return rc; } int smblite_lib_get_icl_current(struct smb_charger *chg, int *icl_ua) { int rc; rc = smblite_lib_get_charge_param(chg, &chg->param.icl_max_stat, icl_ua); if (rc < 0) smblite_lib_err(chg, "Couldn't get HC ICL rc=%d\n", rc); return rc; } #define FCC_STEP_1_SIZE_UA 25000 #define FCC_STEP_2_SIZE_UA 150000 #define FCC_STEP_1_MAX_DATA 60 #define STEP_1_MAX_FCC_UA 1500000 /* * Fast Charge Current = [DATA]x25mA, DATA = 0..60 (0-1500ma) * or [DATA-49]x150mA, DATA = 61..63 (1650mA-1.95A) */ int smblite_lib_get_fcc(struct smb_chg_param *param, u8 val_raw) { if (val_raw > FCC_STEP_1_MAX_DATA) return (((val_raw - FCC_STEP_1_MAX_DATA) * FCC_STEP_2_SIZE_UA) + STEP_1_MAX_FCC_UA); return (val_raw * FCC_STEP_1_SIZE_UA); } int smblite_lib_set_fcc(struct smb_chg_param *param, int val_u, u8 *val_raw) { if (val_u > param->max_u) val_u = param->max_u; else if (val_u < param->min_u) val_u = param->min_u; if (val_u > (FCC_STEP_1_MAX_DATA * FCC_STEP_1_SIZE_UA)) *val_raw = (((val_u - STEP_1_MAX_FCC_UA) / FCC_STEP_2_SIZE_UA) + FCC_STEP_1_MAX_DATA); else *val_raw = (val_u / FCC_STEP_1_SIZE_UA); return 0; } /********************* * VOTABLE CALLBACKS * *********************/ static int smblite_lib_awake_vote_callback(struct votable *votable, void *data, int awake, const char *client) { struct smb_charger *chg = data; if (awake) pm_stay_awake(chg->dev); else pm_relax(chg->dev); return 0; } static int smblite_lib_chg_disable_vote_callback(struct votable *votable, void *data, int chg_disable, const char *client) { struct smb_charger *chg = data; int rc; rc = smblite_lib_masked_write(chg, CHARGING_ENABLE_CMD_REG(chg->base), CHARGING_ENABLE_CMD_BIT, chg_disable ? 0 : CHARGING_ENABLE_CMD_BIT); if (rc < 0) { smblite_lib_err(chg, "Couldn't %s charging rc=%d\n", chg_disable ? "disable" : "enable", rc); return rc; } return 0; } static int smblite_lib_icl_irq_disable_vote_callback(struct votable *votable, void *data, int disable, const char *client) { struct smb_charger *chg = data; if (!chg->irq_info[USBIN_ICL_CHANGE_IRQ].is_requested) return 0; if (chg->irq_info[USBIN_ICL_CHANGE_IRQ].enabled) { if (disable) { irq_set_status_flags(chg->irq_info[USBIN_ICL_CHANGE_IRQ].irq, IRQ_DISABLE_UNLAZY); disable_irq_nosync( chg->irq_info[USBIN_ICL_CHANGE_IRQ].irq); } } else { if (!disable) enable_irq(chg->irq_info[USBIN_ICL_CHANGE_IRQ].irq); } chg->irq_info[USBIN_ICL_CHANGE_IRQ].enabled = !disable; return 0; } static int smblite_lib_temp_change_irq_disable_vote_callback( struct votable *votable, void *data, int disable, const char *client) { struct smb_charger *chg = data; if (!chg->irq_info[TEMP_CHANGE_IRQ].is_requested) return 0; if (chg->irq_info[TEMP_CHANGE_IRQ].enabled && disable) { if (chg->irq_info[TEMP_CHANGE_IRQ].wake) disable_irq_wake(chg->irq_info[TEMP_CHANGE_IRQ].irq); irq_set_status_flags(chg->irq_info[TEMP_CHANGE_IRQ].irq, IRQ_DISABLE_UNLAZY); disable_irq_nosync(chg->irq_info[TEMP_CHANGE_IRQ].irq); } else if (!chg->irq_info[TEMP_CHANGE_IRQ].enabled && !disable) { enable_irq(chg->irq_info[TEMP_CHANGE_IRQ].irq); if (chg->irq_info[TEMP_CHANGE_IRQ].wake) enable_irq_wake(chg->irq_info[TEMP_CHANGE_IRQ].irq); } chg->irq_info[TEMP_CHANGE_IRQ].enabled = !disable; return 0; } /******************** * BATT PSY GETTERS * ********************/ int smblite_lib_get_prop_input_suspend(struct smb_charger *chg, int *val) { *val = (get_client_vote(chg->usb_icl_votable, USER_VOTER) == 0); return 0; } int smblite_lib_get_prop_batt_present(struct smb_charger *chg, union power_supply_propval *val) { int rc; u8 stat; rc = smblite_lib_read(chg, chg->base.batif_base + INT_RT_STS_OFFSET, &stat); if (rc < 0) { smblite_lib_err(chg, "Couldn't read BATIF_INT_RT_STS rc=%d\n", rc); return rc; } val->intval = !(stat & BAT_THERM_OR_ID_MISSING_RT_STS_BIT); return rc; } int smblite_lib_get_prop_batt_capacity(struct smb_charger *chg, union power_supply_propval *val) { int rc = -EINVAL; if (chg->fake_capacity >= 0) { val->intval = chg->fake_capacity; return 0; } rc = smblite_lib_get_prop_from_bms(chg, SMB5_QG_CAPACITY, &val->intval); if ((rc < 0) && (rc != -EAGAIN)) smblite_lib_err(chg, "Couldn't get capacity prop rc=%d\n", rc); return rc; } static bool is_charging_paused(struct smb_charger *chg) { int rc; u8 val; rc = smblite_lib_read(chg, CHARGING_ENABLE_CMD_REG(chg->base), &val); if (rc < 0) { smblite_lib_err(chg, "Couldn't read CHARGING_PAUSE_CMD rc=%d\n", rc); return false; } return val & CHARGING_PAUSE_CMD_BIT; } #define PERCENT_TO_10NANO_RATIO 1000000 static int smblite_lib_read_soc(struct smb_charger *chg) { ssize_t len; u16 *val; int soc; if (!chg->soc_nvmem) return -EINVAL; val = nvmem_cell_read(chg->soc_nvmem, &len); if (IS_ERR(val)) { smblite_lib_err(chg, "Failed to read charger msoc from SDAM\n"); return PTR_ERR(val); } soc = (int)*val; soc = (soc << 16) / PERCENT_TO_10NANO_RATIO; kfree(val); return soc; } #define CUTOFF_COUNT 3 int smblite_lib_get_prop_batt_status(struct smb_charger *chg, union power_supply_propval *val) { union power_supply_propval pval = {0, }; bool usb_online; u8 stat; int rc, input_present = 0, count = 4, data = 0; if (chg->fake_chg_status_on_debug_batt) { rc = smblite_lib_get_prop_from_bms(chg, SMB5_QG_DEBUG_BATTERY, &pval.intval); if (rc < 0) { pr_err_ratelimited("Couldn't get debug battery prop rc=%d\n", rc); } else if (pval.intval == 1) { val->intval = POWER_SUPPLY_STATUS_UNKNOWN; return 0; } } /* * If SOC = 0 and we are discharging with input connected, report * the battery status as DISCHARGING. */ smblite_lib_is_input_present(chg, &input_present); rc = smblite_lib_get_prop_from_bms(chg, SMB5_QG_CAPACITY, &pval.intval); if (!rc && pval.intval == 0 && input_present) { rc = smblite_lib_get_prop_from_bms(chg, SMB5_QG_CURRENT_NOW, &pval.intval); if (!rc && pval.intval > 0) { if (chg->cutoff_count > CUTOFF_COUNT) { val->intval = POWER_SUPPLY_STATUS_DISCHARGING; return 0; } chg->cutoff_count++; } else { chg->cutoff_count = 0; } } else { chg->cutoff_count = 0; } rc = smblite_lib_get_prop_usb_online(chg, &pval); if (rc < 0) { smblite_lib_err(chg, "Couldn't get usb online property rc=%d\n", rc); return rc; } usb_online = (bool)pval.intval; rc = smblite_lib_read(chg, BATTERY_CHARGER_STATUS_1_REG(chg->base), &stat); if (rc < 0) { smblite_lib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_1 rc=%d\n", rc); return rc; } stat = stat & BATTERY_CHARGER_STATUS_MASK; if (!usb_online) { switch (stat) { case TERMINATE_CHARGE: case INHIBIT_CHARGE: val->intval = POWER_SUPPLY_STATUS_FULL; break; default: val->intval = POWER_SUPPLY_STATUS_DISCHARGING; break; } return rc; } switch (stat) { case TRICKLE_CHARGE: case PRE_CHARGE: case FULLON_CHARGE: case TAPER_CHARGE: val->intval = POWER_SUPPLY_STATUS_CHARGING; break; case TERMINATE_CHARGE: val->intval = POWER_SUPPLY_STATUS_FULL; break; case INHIBIT_CHARGE: data = smblite_lib_read_soc(chg); if (data < 0) { smblite_lib_err(chg, "Failed to read msoc rc = %d\n", data); return data; } smblite_lib_dbg(chg, PR_MISC, "Read soc = %d\n", data); /* * Write msoc value to charger SOC_PCT register 4 times after entered * inhibit mode. */ while (count--) { rc = smblite_lib_set_prop_batt_sys_soc(chg, data); if (rc < 0) { smblite_lib_err(chg, "Failed to set battery system soc rc=%d\n", rc); return rc; } } break; case DISABLE_CHARGE: case PAUSE_CHARGE: val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; break; default: val->intval = POWER_SUPPLY_STATUS_UNKNOWN; break; } if (is_charging_paused(chg)) { val->intval = POWER_SUPPLY_STATUS_CHARGING; return 0; } if (val->intval != POWER_SUPPLY_STATUS_CHARGING) return 0; if (!usb_online && chg->fake_batt_status == POWER_SUPPLY_STATUS_FULL) { val->intval = POWER_SUPPLY_STATUS_FULL; return 0; } return 0; } int smblite_lib_get_prop_batt_charge_type(struct smb_charger *chg, union power_supply_propval *val) { int rc; u8 stat; rc = smblite_lib_read(chg, BATTERY_CHARGER_STATUS_1_REG(chg->base), &stat); if (rc < 0) { smblite_lib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_1 rc=%d\n", rc); return rc; } switch (stat & BATTERY_CHARGER_STATUS_MASK) { case TRICKLE_CHARGE: case PRE_CHARGE: val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; break; case FULLON_CHARGE: val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; break; case TAPER_CHARGE: val->intval = POWER_SUPPLY_CHARGE_TYPE_ADAPTIVE; break; default: val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; } return rc; } int smblite_lib_get_prop_batt_health(struct smb_charger *chg, union power_supply_propval *val) { union power_supply_propval pval; int rc; int effective_fv_uv; u8 stat; rc = smblite_lib_read(chg, CHARGER_VBAT_STATUS_REG(chg->base), &stat); if (rc < 0) { smblite_lib_err(chg, "Couldn't read CHARGER_VBAT_STATUS_REG rc=%d\n", rc); return rc; } smblite_lib_dbg(chg, PR_REGISTER, "CHARGER_VBAT_STATUS_REG = 0x%02x\n", stat); if (stat & BAT_OV_BIT) { rc = smblite_lib_get_prop_from_bms(chg, SMB5_QG_VOLTAGE_NOW, &pval.intval); if (!rc) { /* * If Vbatt is within 40mV above Vfloat, then don't * treat it as overvoltage. */ effective_fv_uv = get_effective_result(chg->fv_votable); if (pval.intval >= effective_fv_uv + 40000) { val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; smblite_lib_err(chg, "battery over-voltage vbat_fg = %duV, fv = %duV\n", pval.intval, effective_fv_uv); goto done; } } } rc = smblite_lib_read(chg, BATTERY_TEMP_STATUS_REG(chg->base), &stat); if (rc < 0) { smblite_lib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_2 rc=%d\n", rc); return rc; } if (stat & BAT_TEMP_STATUS_TOO_COLD_BIT) val->intval = POWER_SUPPLY_HEALTH_COLD; else if (stat & BAT_TEMP_STATUS_TOO_HOT_BIT) val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; else if (stat & BAT_TEMP_STATUS_COLD_SOFT_BIT) val->intval = POWER_SUPPLY_HEALTH_COOL; else if (stat & BAT_TEMP_STATUS_HOT_SOFT_BIT) val->intval = POWER_SUPPLY_HEALTH_WARM; else val->intval = POWER_SUPPLY_HEALTH_GOOD; done: return rc; } int smblite_lib_get_prop_system_temp_level(struct smb_charger *chg, union power_supply_propval *val) { val->intval = chg->system_temp_level; return 0; } int smblite_lib_get_prop_system_temp_level_max(struct smb_charger *chg, union power_supply_propval *val) { val->intval = chg->thermal_levels; return 0; } int smblite_lib_get_prop_input_current_limited(struct smb_charger *chg, int *val) { u8 stat; int rc; if (chg->input_current_limited >= 0) { *val = chg->input_current_limited; return 0; } rc = smblite_lib_read(chg, AICL_STATUS_REG(chg->base), &stat); if (rc < 0) { smblite_lib_err(chg, "Couldn't read AICL_STATUS rc=%d\n", rc); return rc; } *val = (stat & SOFT_ILIMIT_BIT); return 0; } int smblite_lib_get_prop_batt_iterm(struct smb_charger *chg, union power_supply_propval *val) { int rc, temp; u8 stat, buf[2]; /* Currently, only ADC comparator-based termination is supported * and validate, hence read only the threshold corresponding to ADC * source. Proceed only if CHGR_ITERM_USE_ANALOG_BIT is 0. */ rc = smblite_lib_read(chg, CHGR_TERM_CFG_REG(chg->base), &stat); if (rc < 0) { smblite_lib_err(chg, "Couldn't read CHGR_TERM_CFG_REG rc=%d\n", rc); return rc; } if (stat & CHGR_ITERM_USE_ANALOG_BIT) { val->intval = -EINVAL; return 0; } rc = smblite_lib_batch_read(chg, CHGR_ADC_ITERM_UP_THD_MSB_REG(chg->base), buf, 2); if (rc < 0) { smblite_lib_err(chg, "Couldn't read CHGR_ADC_ITERM_UP_THD_MSB_REG rc=%d\n", rc); return rc; } temp = buf[1] | (buf[0] << 8); temp = sign_extend32(temp, 15); if (chg->subtype == PM5100) temp = DIV_ROUND_CLOSEST((temp * 1000), PM5100_ADC_CHG_ITERM_MULT); else temp = DIV_ROUND_CLOSEST(temp * ITERM_LIMITS_MA, ADC_CHG_ITERM_MASK); val->intval = temp; return rc; } int smblite_lib_set_prop_batt_iterm(struct smb_charger *chg, int iterm_ma) { int rc; s16 raw_hi_thresh; u8 stat, *buf; if (chg->subtype != PM5100) return -EINVAL; if (iterm_ma < (-1 * PM5100_MAX_LIMITS_MA) || iterm_ma > PM5100_MAX_LIMITS_MA) return -EINVAL; /* Currently, only ADC comparator-based termination is supported * and validate, hence read only the threshold corresponding to ADC * source. Proceed only if CHGR_ITERM_USE_ANALOG_BIT is 0. */ rc = smblite_lib_read(chg, CHGR_TERM_CFG_REG(chg->base), &stat); if (rc < 0) { smblite_lib_err(chg, "Couldn't read CHGR_TERM_CFG_REG rc=%d\n", rc); return rc; } if (stat & CHGR_ITERM_USE_ANALOG_BIT) return -EINVAL; raw_hi_thresh = PM5100_RAW_ITERM(iterm_ma); raw_hi_thresh = sign_extend32(raw_hi_thresh, 15); buf = (u8 *)&raw_hi_thresh; raw_hi_thresh = buf[1] | (buf[0] << 8); rc = smblite_lib_batch_write(chg, CHGR_ADC_ITERM_UP_THD_MSB_REG(chg->base), (u8 *)&raw_hi_thresh, 2); if (rc < 0) { dev_err(chg->dev, "Couldn't configure ITERM threshold HIGH rc=%d\n", rc); return rc; } return rc; } int smblite_lib_get_prop_batt_charge_done(struct smb_charger *chg, int *val) { int rc; u8 stat; rc = smblite_lib_read(chg, BATTERY_CHARGER_STATUS_1_REG(chg->base), &stat); if (rc < 0) { smblite_lib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_1 rc=%d\n", rc); return rc; } stat = stat & BATTERY_CHARGER_STATUS_MASK; *val = (stat == TERMINATE_CHARGE); return 0; } int smblite_lib_get_batt_current_now(struct smb_charger *chg, int *val) { int rc; rc = smblite_lib_get_prop_from_bms(chg, SMB5_QG_CURRENT_NOW, val); if (!rc) *val *= (-1); else smblite_lib_err(chg, "Couldn't get current_now prop rc=%d\n", rc); return rc; } /*********************** * BATTERY PSY SETTERS * ***********************/ int smblite_lib_set_prop_input_suspend(struct smb_charger *chg, const int val) { int rc; /* vote 0mA when suspended */ rc = vote(chg->usb_icl_votable, USER_VOTER, (bool)val, 0); if (rc < 0) { smblite_lib_err(chg, "Couldn't vote to %s USB rc=%d\n", (bool)val ? "suspend" : "resume", rc); return rc; } power_supply_changed(chg->batt_psy); return rc; } int smblite_lib_set_prop_batt_capacity(struct smb_charger *chg, const union power_supply_propval *val) { chg->fake_capacity = val->intval; power_supply_changed(chg->batt_psy); return 0; } int smblite_lib_set_prop_batt_sys_soc(struct smb_charger *chg, int val) { int rc; u8 sys_soc; if (val < 0 || val > 100) { smblite_lib_err(chg, "Invalid system soc = %d\n", val); return -EINVAL; } sys_soc = DIV_ROUND_CLOSEST(val * 255, 100); /* This is used to trigger SOC based auto-recharge */ rc = smblite_lib_write(chg, CHGR_QG_SOC_REG(chg->base), sys_soc); if (rc < 0) { smblite_lib_err(chg, "Couldn't write to CHGR_QG_SOC_REG rc=%d\n", rc); return rc; } rc = smblite_lib_write(chg, CHGR_QG_SOC_UPDATE_REG(chg->base), SOC_UPDATE_PCT_BIT); if (rc < 0) smblite_lib_err(chg, "Couldn't write to CHGR_QG_SOC_UPDATE_REG rc=%d\n", rc); return rc; } int smblite_lib_set_prop_batt_status(struct smb_charger *chg, const union power_supply_propval *val) { /* Faking battery full */ if (val->intval == POWER_SUPPLY_STATUS_FULL) chg->fake_batt_status = val->intval; else chg->fake_batt_status = -EINVAL; power_supply_changed(chg->batt_psy); return 0; } int smblite_lib_set_prop_system_temp_level(struct smb_charger *chg, const union power_supply_propval *val) { if (val->intval < 0) return -EINVAL; if (chg->thermal_levels <= 0) return -EINVAL; if (val->intval > chg->thermal_levels) return -EINVAL; chg->system_temp_level = val->intval; if (chg->system_temp_level == chg->thermal_levels) return vote(chg->chg_disable_votable, THERMAL_DAEMON_VOTER, true, 0); vote(chg->chg_disable_votable, THERMAL_DAEMON_VOTER, false, 0); if (chg->system_temp_level == 0) return vote(chg->fcc_votable, THERMAL_DAEMON_VOTER, false, 0); vote(chg->fcc_votable, THERMAL_DAEMON_VOTER, true, chg->thermal_mitigation[chg->system_temp_level]); return 0; } static int smblite_lib_dp_pulse(struct smb_charger *chg) { int rc; /* QC 3.0 increment */ rc = smblite_lib_masked_write(chg, CMD_HVDCP_REG(chg->base), SINGLE_INCREMENT_BIT, SINGLE_INCREMENT_BIT); if (rc < 0) smblite_lib_err(chg, "Couldn't write to CMD_HVDCP_REG rc=%d\n", rc); return rc; } static int smblite_lib_force_vbus_voltage(struct smb_charger *chg, u8 val) { int rc; rc = smblite_lib_masked_write(chg, CMD_HVDCP_REG(chg->base), val, val); if (rc < 0) smblite_lib_err(chg, "Couldn't write to CMD_HVDCP_2_REG rc=%d\n", rc); return rc; } static bool is_boost_en(struct smb_charger *chg) { int rc; u8 stat = 0; if (chg->subtype != PM5100) return false; rc = smblite_lib_read(chg, BOOST_BST_EN_REG(chg->base), &stat); if (rc < 0) smblite_lib_err(chg, "Couldn't read BOOST_BST_EN_REG rc=%d\n", rc); return (stat & DCIN_BST_EN_BIT); } #define HVDCP3_QUALIFICATION_UV 300000 static int smblite_lib_hvdcp3_force_max_vbus(struct smb_charger *chg) { union power_supply_propval pval = {0, }; int cnt = 0, rc = 0, prev_vbus; bool boost_en; mutex_lock(&chg->dpdm_pulse_lock); boost_en = is_boost_en(chg); if (boost_en || chg->hvdcp3_detected) { smblite_lib_dbg(chg, PR_MISC, "HVDCP3 : Ignore VBUS increment due to boost_en=%s, hvdcp3_detected=%s\n", (boost_en ? "True" : "False"), (chg->hvdcp3_detected ? "True" : "False")); goto failure; } /* Move adapter to IDLE state (continuous mode). */ rc = smblite_lib_force_vbus_voltage(chg, IDLE_BIT); if (rc < 0) smblite_lib_dbg(chg, PR_MISC, "HVDCP3 : Failed to reset adapter to IDLE state\n"); /* Wait for 100ms for adapter to move to idle mode */ msleep(100); rc = smblite_lib_get_prop_usb_voltage_now(chg, &pval); if (rc < 0) { smblite_lib_err(chg, "Couldn't read voltage_now rc=%d\n", rc); goto failure; } prev_vbus = pval.intval; /* * Statically increase voltage till 6V. * ( i.e : 1V / 200mV = 5 pulses ). */ while (cnt++ < PM5100_MAX_HVDCP3_PULSES) { smblite_lib_dp_pulse(chg); /* wait for 100ms for vbus to settle. */ msleep(100); } if (is_boost_en(chg)) { smblite_lib_dbg(chg, PR_MISC, "HVDCP3 : Failed to increase vbus due to boost_en\n"); goto failure; } /* Wait for 200ms for vbus to settle before reading it. */ msleep(200); rc = smblite_lib_get_prop_usb_voltage_now(chg, &pval); if (rc < 0) { smblite_lib_err(chg, "Couldn't read voltage_now rc=%d\n", rc); goto failure; } /* Check if voltage incremented. (i.e if QC3 ) */ if (pval.intval >= (prev_vbus + HVDCP3_QUALIFICATION_UV)) chg->hvdcp3_detected = true; smblite_lib_dbg(chg, PR_MISC, "HVDCP3 : detected=%s, prev_vbus=%d, vbus_now=%d\n", (chg->hvdcp3_detected ? "True" : "False"), prev_vbus, pval.intval); failure: if (!chg->hvdcp3_detected) { /* Incase of failure during QC3 detection force 5V. */ rc = smblite_lib_force_vbus_voltage(chg, FORCE_5V_BIT); if (rc < 0) smblite_lib_dbg(chg, PR_MISC, "HVDCP3 : Failed to move adapter vbus to 5V\n"); } mutex_unlock(&chg->dpdm_pulse_lock); return rc; } int smblite_lib_set_prop_rechg_soc_thresh(struct smb_charger *chg, const int val) { int rc; u8 new_thr = DIV_ROUND_CLOSEST(val * 255, 100); rc = smblite_lib_write(chg, CHARGE_RCHG_SOC_THRESHOLD_CFG_REG(chg->base), new_thr); if (rc < 0) { smblite_lib_err(chg, "Couldn't write to RCHG_SOC_THRESHOLD_CFG_REG rc=%d\n", rc); return rc; } chg->auto_recharge_soc = val; return rc; } int smblite_lib_run_aicl(struct smb_charger *chg, int type) { int rc; u8 stat; rc = smblite_lib_read(chg, POWER_PATH_STATUS_REG(chg->base), &stat); if (rc < 0) { smblite_lib_err(chg, "Couldn't read POWER_PATH_STATUS rc=%d\n", rc); return rc; } /* USB is suspended so skip re-running AICL */ if (stat & USBIN_SUSPEND_STS_BIT) return rc; smblite_lib_dbg(chg, PR_MISC, "re-running AICL\n"); stat = (type == RERUN_AICL) ? RERUN_AICL_BIT : RESTART_AICL_BIT; rc = smblite_lib_masked_write(chg, AICL_CMD_REG(chg->base), stat, stat); if (rc < 0) smblite_lib_err(chg, "Couldn't write to AICL_CMD_REG rc=%d\n", rc); return 0; } #define BOOST_SS_TIMEOUT_COUNT 4 static int smblite_lib_check_boost_ss(struct smb_charger *chg) { int rc, cnt = 0; bool is_ss_done = false; u8 boost_status = 0; /* * POLL on BOOST_SW_DONE bit for 80ms with 20ms interval for * BOOST start-up to be done. */ while (cnt < BOOST_SS_TIMEOUT_COUNT) { rc = smblite_lib_read(chg, BOOST_BST_STATUS_REG(chg->base), &boost_status); if (rc < 0) smblite_lib_err(chg, "Couldn't read BOOST_BST_STATUS_REG rc=%d\n", rc); if (!rc && (boost_status & BOOST_SOFTSTART_DONE_BIT)) { is_ss_done = true; break; } cnt++; msleep(20); } smblite_lib_dbg(chg, PR_MISC, "Concurrent-mode: BOOST_STATUS=%x, cnt=%d\n", boost_status, cnt); /* In case of BOOST failure disable concurrency mode and return failure. */ if (!is_ss_done) { smblite_lib_err(chg, "Boost ss-done failed, failed to enable concurrency\n"); return -ETIME; } return 0; } #define CONCURRENCY_REDUCED_ICL_UA 300000 #define CONCURRENCY_MODE_SUPPORTED_ICL_UA 500000 int smblite_lib_set_concurrent_config(struct smb_charger *chg, bool enable) { int rc = 0, icl_ua = 0, fixed_icl_ua = 0, usb_present = 0; union power_supply_propval pval = {0, }; u8 apsd_status = 0; bool boost_enabled = is_boost_en(chg); if (!is_concurrent_mode_supported(chg)) { smblite_lib_dbg(chg, PR_MISC, "concurrency-mode: support disabled\n"); return -ENXIO; } /* Do not enable concurrency mode when connected to debug batt. */ if (chg->is_debug_batt) { smblite_lib_dbg(chg, PR_MISC, "concurrency-mode: disabled debug board detected\n"); return -EPERM; } /* Exit if there is no change in state */ if (chg->concurrent_mode_status == enable) goto out; rc = smblite_lib_get_prop_usb_present(chg, &pval); if (rc < 0) { smblite_lib_dbg(chg, PR_MISC, "Couldn't get USB preset status rc=%d\n", rc); goto failure; } usb_present = pval.intval; if (enable) { /* Check if USB is connected */ if (!usb_present) { smblite_lib_dbg(chg, PR_MISC, "Failed to enable concurrent mode USB disconnected\n", rc); goto failure; } /* * When boost is enabled and usb is inserted chargering will be disabled causing * AICL to be always zero, Skip calculating ICL for this. */ if (boost_enabled) { smblite_lib_dbg(chg, PR_MISC, "boost is already enabled. Skipping ICL vote.\n"); } else { /* * Incase were AICL is reruning and there is a request from Audio to * enable concurrency mode it may lead us to wait for 1+ secounds which * may in turn compromise with users Audio expirence. Eliminate this * delay by forcing ICL to a fixed value. */ fixed_icl_ua = CONCURRENCY_MODE_SUPPORTED_ICL_UA; if (fixed_icl_ua <= CONCURRENCY_REDUCED_ICL_UA) { /* Return as failure if settled ICL is less than required ICL. */ smblite_lib_err(chg, "fixed_icl_ua=%d less can't enable concurrency-mode\n", fixed_icl_ua); return -EIO; } /* Reduce ICL to go into concurrency mode */ icl_ua = fixed_icl_ua - CONCURRENCY_REDUCED_ICL_UA; rc = vote(chg->usb_icl_votable, CONCURRENT_MODE_VOTER, true, icl_ua); if (rc < 0) { smblite_lib_err(chg, "Failed to vote on ICL rc=%d\n", rc); goto failure; } smblite_lib_dbg(chg, PR_MISC, "concurrent-mode: Reduced ICL to %d for concurrency mode\n", icl_ua); } if (chg->hvdcp3_detected) { /* Force Vbus to 5V. */ rc = smblite_lib_force_vbus_voltage(chg, FORCE_5V_BIT); if (rc < 0) smblite_lib_err(chg, "Failed to force vbus to 5V rc=%d\n", rc); chg->hvdcp3_detected = false; } /* Enable charger if already disabled */ rc = vote(chg->chg_disable_votable, CONCURRENT_MODE_VOTER, false, 0); if (rc < 0) { smblite_lib_err(chg, "Failed to Enable charger rc=%d\n", rc); goto failure; } /* Enable concurrent mode */ rc = smblite_lib_concurrent_mode_config(chg, true); if (rc < 0) goto failure; /* * If Audio playback is in progress and charger is inserted, on enabling * concurrency there is a possibility of it to fail if the BOOST soft-start * is not complete. Avoid this by polling on BOOST_SW_DONE bit which gets * set after concurrency is successfully enabled and then returning back to * the caller. In case of failure disable concurrency-mode and return failure. */ if (boost_enabled) { rc = smblite_lib_check_boost_ss(chg); if (rc < 0) { /* * Disable concurrency mode to move back the switcher to * BOOST-mode and wait for SS_DONE for BOOST to settle. */ boost_enabled = is_boost_en(chg); smblite_lib_dbg(chg, PR_MISC, "Concurrency failed, Disabling concurrency BOOST_EN=%s - going back to BOOST mode\n", (boost_enabled ? "True" : "False")); smblite_lib_concurrent_mode_config(chg, false); if (boost_enabled) { rc = smblite_lib_check_boost_ss(chg); if (rc < 0) { smblite_lib_dbg(chg, PR_MISC, "SS_DONE failed for BOOST-mode rc=%d\n", rc); /* * This is a gross failure where concurrency and * subsequently BOOST failed. */ rc = -EFAULT; } else { /* Concurrency failed, but BOOST came up. */ rc = -ETIME; } } goto boost_ss_failure; } } chg->concurrent_mode_status = true; smblite_lib_dbg(chg, PR_MISC, "Concurrent Mode enabled successfully: fixed_icl_ua=%duA, icl_ua=%duA, is_hvdcp3=%d\n", fixed_icl_ua, icl_ua, chg->hvdcp3_detected); goto out; } else { if (!usb_present) { /* USB removed while concurrency was active */ smblite_lib_dbg(chg, PR_MISC, "USB removed: concurrency mode already disabled\n"); return 0; } /* Disable concurrent mode */ rc = smblite_lib_concurrent_mode_config(chg, false); if (rc < 0) goto failure; rc = smblite_lib_read(chg, APSD_RESULT_STATUS_REG(chg->base), &apsd_status); if (rc < 0) smblite_lib_err(chg, "Couldn't read APSD_RESULT_STATUS rc=%d\n", rc); /* * Try to Restore vbus to MAX(6V) only if: * 1. QC adapter is connected. * 2. USB is present. * 3. Boost is disabled : DPDM request does not take * effect with boost enabled. */ if ((apsd_status & QC_3P0_BIT) && usb_present && !boost_enabled) smblite_lib_hvdcp3_force_max_vbus(chg); rc = smblite_lib_run_aicl(chg, RERUN_AICL); if (rc < 0) smblite_lib_err(chg, "Failed to rerun_aicl rc=%d\n", rc); chg->concurrent_mode_status = false; smblite_lib_dbg(chg, PR_MISC, "Concurrent Mode disabled successfully: is_hvdcp3=%d\n", chg->hvdcp3_detected); return 0; } failure: rc = -EINVAL; boost_ss_failure: smblite_lib_err(chg, "Failed to %s concurrent mode, rc=%d\n", (enable ? "Enable" : "Disable"), rc); out: return rc; } /******************* * USB PSY GETTERS * *******************/ int smblite_lib_get_prop_usb_present(struct smb_charger *chg, union power_supply_propval *val) { int rc; u8 stat; rc = smblite_lib_read(chg, chg->base.usbin_base + INT_RT_STS_OFFSET, &stat); if (rc < 0) { smblite_lib_err(chg, "Couldn't read USBIN_RT_STS rc=%d\n", rc); return rc; } val->intval = (bool)(stat & USBIN_PLUGIN_RT_STS_BIT); return 0; } int smblite_lib_get_prop_usb_online(struct smb_charger *chg, union power_supply_propval *val) { int rc = 0, input_present = 0; u8 stat; /* * Android plays an audio notification on USB insertion when USB_ONLINE = 1, * which on PM5100 will move the charger to BOOST again setting back USB_ONLINE = 0. * Avoid this endless loop by reporting USB_ONLINE = 1 as long as boost is enabled * while the charger is inserted. */ smblite_lib_is_input_present(chg, &input_present); if (is_boost_en(chg) && input_present) { val->intval = true; smblite_lib_dbg(chg, PR_MISC, "USB_ONLINE set due to boost_en and input_present\n"); return 0; } /* * USB_ONLINE is reported as 0 for Debug board + USB present use-case * because USE_USBIN bit is set to 0. Report USB_ONLINE = 1 for * Debug Board + USB present use-case. */ if (input_present && chg->is_debug_batt) { val->intval = true; return 0; } if (get_client_vote_locked(chg->usb_icl_votable, USER_VOTER) == 0) { val->intval = false; return rc; } rc = smblite_lib_read(chg, POWER_PATH_STATUS_REG(chg->base), &stat); if (rc < 0) { smblite_lib_err(chg, "Couldn't read POWER_PATH_STATUS rc=%d\n", rc); return rc; } smblite_lib_dbg(chg, PR_REGISTER, "POWER_PATH_STATUS = 0x%02x\n", stat); val->intval = (stat & USE_USBIN_BIT) && (stat & VALID_INPUT_POWER_SOURCE_STS_BIT); return rc; } int smblite_lib_get_usb_online(struct smb_charger *chg, union power_supply_propval *val) { int rc, input_present = 0; /* * Incase of APSD rerun real_charger_type (i.e APSD_STATUS) * is reset which may cause the USB_ONLINE to always return * zero. Report USB_ONLINE=0 only when real_charger_type is * UNKNOWN and input is not present. */ smblite_lib_is_input_present(chg, &input_present); if ((chg->real_charger_type == POWER_SUPPLY_TYPE_UNKNOWN) && !input_present) { val->intval = 0; return 0; } rc = smblite_lib_get_prop_usb_online(chg, val); if (!val->intval) goto exit; exit: return rc; } static int smblite_lib_read_usbin_voltage_chan(struct smb_charger *chg, union power_supply_propval *val) { int rc; if (!chg->iio.usbin_v_chan) return -ENODATA; rc = iio_read_channel_processed(chg->iio.usbin_v_chan, &val->intval); if (rc < 0) { smblite_lib_err(chg, "Couldn't read USBIN channel rc=%d\n", rc); return rc; } return 0; } int smblite_lib_get_prop_usb_voltage_now(struct smb_charger *chg, union power_supply_propval *val) { union power_supply_propval pval = {0, }; int rc = 0; u8 reg; rc = smblite_lib_get_prop_usb_present(chg, &pval); if (rc < 0) { smblite_lib_err(chg, "Couldn't get usb presence status rc=%d\n", rc); goto out; } /* * Skip reading voltage only if USB is not present and we are not in * OTG mode. */ if (!pval.intval) { rc = smblite_lib_read(chg, DCDC_CMD_OTG_REG(chg->base), ®); if (rc < 0) { smblite_lib_err(chg, "Couldn't read CMD_OTG rc=%d", rc); goto out; } if (!(reg & OTG_EN_BIT)) goto out; } rc = smblite_lib_read_usbin_voltage_chan(chg, val); if (rc < 0) smblite_lib_err(chg, "Couldn't to read USBIN over vadc, rc=%d\n", rc); out: return rc; } int smblite_lib_get_prop_charger_temp(struct smb_charger *chg, int *val) { int temp, rc; int input_present; rc = smblite_lib_is_input_present(chg, &input_present); if (rc < 0) return rc; if (input_present == INPUT_NOT_PRESENT) return -ENODATA; if (chg->iio.temp_chan) { rc = iio_read_channel_processed(chg->iio.temp_chan, &temp); if (rc < 0) { pr_err("Error in reading temp channel, rc=%d\n", rc); return rc; } *val = temp / 100; } else { return -ENODATA; } return rc; } int smblite_lib_get_prop_typec_cc_orientation(struct smb_charger *chg, int *val) { int rc = 0; u8 stat; *val = 0; if (chg->connector_type == QTI_POWER_SUPPLY_CONNECTOR_MICRO_USB) return 0; rc = smblite_lib_read(chg, TYPE_C_MISC_STATUS_REG(chg->base), &stat); if (rc < 0) { smblite_lib_err(chg, "Couldn't read TYPE_C_STATUS_4 rc=%d\n", rc); return rc; } smblite_lib_dbg(chg, PR_REGISTER, "TYPE_C_STATUS_4 = 0x%02x\n", stat); if (stat & CC_ATTACHED_BIT) *val = (bool)(stat & CC_ORIENTATION_BIT) + 1; return rc; } static const char * const smblite_lib_typec_mode_name[] = { [QTI_POWER_SUPPLY_TYPEC_NONE] = "NONE", [QTI_POWER_SUPPLY_TYPEC_SOURCE_DEFAULT] = "SOURCE_DEFAULT", [QTI_POWER_SUPPLY_TYPEC_SOURCE_MEDIUM] = "SOURCE_MEDIUM", [QTI_POWER_SUPPLY_TYPEC_SOURCE_HIGH] = "SOURCE_HIGH", [QTI_POWER_SUPPLY_TYPEC_NON_COMPLIANT] = "NON_COMPLIANT", [QTI_POWER_SUPPLY_TYPEC_SINK] = "SINK", [QTI_POWER_SUPPLY_TYPEC_SINK_POWERED_CABLE] = "SINK_POWERED_CABLE", [QTI_POWER_SUPPLY_TYPEC_SINK_DEBUG_ACCESSORY] = "SINK_DEBUG_ACCESSORY", [QTI_POWER_SUPPLY_TYPEC_SINK_AUDIO_ADAPTER] = "SINK_AUDIO_ADAPTER", [QTI_POWER_SUPPLY_TYPEC_POWERED_CABLE_ONLY] = "POWERED_CABLE_ONLY", }; static int smblite_lib_get_prop_ufp_mode(struct smb_charger *chg) { int rc; u8 stat; rc = smblite_lib_read(chg, TYPE_C_SNK_STATUS_REG(chg->base), &stat); if (rc < 0) { smblite_lib_err(chg, "Couldn't read TYPE_C_STATUS_1 rc=%d\n", rc); return QTI_POWER_SUPPLY_TYPEC_NONE; } smblite_lib_dbg(chg, PR_REGISTER, "TYPE_C_STATUS_1 = 0x%02x\n", stat); switch (stat & DETECTED_SRC_TYPE_MASK) { case SNK_RP_STD_BIT: return QTI_POWER_SUPPLY_TYPEC_SOURCE_DEFAULT; case SNK_RP_1P5_BIT: return QTI_POWER_SUPPLY_TYPEC_SOURCE_MEDIUM; case SNK_RP_3P0_BIT: return QTI_POWER_SUPPLY_TYPEC_SOURCE_HIGH; case SNK_RP_SHORT_BIT: return QTI_POWER_SUPPLY_TYPEC_NON_COMPLIANT; case SNK_DAM_500MA_BIT: case SNK_DAM_1500MA_BIT: case SNK_DAM_3000MA_BIT: return QTI_POWER_SUPPLY_TYPEC_SINK_DEBUG_ACCESSORY; default: break; } return QTI_POWER_SUPPLY_TYPEC_NONE; } static int smblite_lib_get_prop_dfp_mode(struct smb_charger *chg) { int rc; u8 stat; rc = smblite_lib_read(chg, TYPE_C_SRC_STATUS_REG(chg->base), &stat); if (rc < 0) { smblite_lib_err(chg, "Couldn't read TYPE_C_SRC_STATUS_REG rc=%d\n", rc); return QTI_POWER_SUPPLY_TYPEC_NONE; } smblite_lib_dbg(chg, PR_REGISTER, "TYPE_C_SRC_STATUS_REG = 0x%02x\n", stat); switch (stat & DETECTED_SNK_TYPE_MASK) { case AUDIO_ACCESS_RA_RA_BIT: return QTI_POWER_SUPPLY_TYPEC_SINK_AUDIO_ADAPTER; case SRC_DEBUG_ACCESS_BIT: return QTI_POWER_SUPPLY_TYPEC_SINK_DEBUG_ACCESSORY; case SRC_RD_OPEN_BIT: return QTI_POWER_SUPPLY_TYPEC_SINK; default: break; } return QTI_POWER_SUPPLY_TYPEC_NONE; } static int smblite_lib_get_prop_typec_mode(struct smb_charger *chg) { int rc; u8 stat; if (chg->connector_type == QTI_POWER_SUPPLY_CONNECTOR_MICRO_USB) return QTI_POWER_SUPPLY_TYPEC_NONE; rc = smblite_lib_read(chg, TYPE_C_MISC_STATUS_REG(chg->base), &stat); if (rc < 0) { smblite_lib_err(chg, "Couldn't read TYPE_C_MISC_STATUS_REG rc=%d\n", rc); return 0; } smblite_lib_dbg(chg, PR_REGISTER, "TYPE_C_MISC_STATUS_REG = 0x%02x\n", stat); if (stat & SNK_SRC_MODE_BIT) return smblite_lib_get_prop_dfp_mode(chg); else return smblite_lib_get_prop_ufp_mode(chg); } inline int smblite_lib_get_usb_prop_typec_mode(struct smb_charger *chg, int *val) { if (chg->connector_type == QTI_POWER_SUPPLY_CONNECTOR_MICRO_USB) *val = QTI_POWER_SUPPLY_TYPEC_NONE; else *val = chg->typec_mode; return 0; } int smblite_lib_get_prop_typec_power_role(struct smb_charger *chg, int *val) { int rc = 0; u8 ctrl; if (chg->connector_type == QTI_POWER_SUPPLY_CONNECTOR_MICRO_USB) { *val = QTI_POWER_SUPPLY_TYPEC_PR_NONE; return 0; } rc = smblite_lib_read(chg, TYPE_C_MODE_CFG_REG(chg->base), &ctrl); if (rc < 0) { smblite_lib_err(chg, "Couldn't read TYPE_C_MODE_CFG_REG rc=%d\n", rc); return rc; } smblite_lib_dbg(chg, PR_REGISTER, "TYPE_C_MODE_CFG_REG = 0x%02x\n", ctrl); if (ctrl & TYPEC_DISABLE_CMD_BIT) { *val = QTI_POWER_SUPPLY_TYPEC_PR_NONE; return rc; } switch (ctrl & (EN_SRC_ONLY_BIT | EN_SNK_ONLY_BIT)) { case 0: *val = QTI_POWER_SUPPLY_TYPEC_PR_DUAL; break; case EN_SRC_ONLY_BIT: *val = QTI_POWER_SUPPLY_TYPEC_PR_SOURCE; break; case EN_SNK_ONLY_BIT: *val = QTI_POWER_SUPPLY_TYPEC_PR_SINK; break; default: *val = QTI_POWER_SUPPLY_TYPEC_PR_NONE; smblite_lib_err(chg, "unsupported power role 0x%02lx\n", ctrl & (EN_SRC_ONLY_BIT | EN_SNK_ONLY_BIT)); return -EINVAL; } chg->power_role = *val; return rc; } inline int smblite_lib_get_usb_prop_typec_accessory_mode(struct smb_charger *chg, int *val) { if (chg->connector_type == QTI_POWER_SUPPLY_CONNECTOR_MICRO_USB) { *val = TYPEC_ACCESSORY_NONE; return 0; } switch (chg->typec_mode) { case QTI_POWER_SUPPLY_TYPEC_NONE: *val = TYPEC_ACCESSORY_NONE; break; case QTI_POWER_SUPPLY_TYPEC_SINK_AUDIO_ADAPTER: *val = TYPEC_ACCESSORY_AUDIO; break; case QTI_POWER_SUPPLY_TYPEC_SINK_DEBUG_ACCESSORY: *val = TYPEC_ACCESSORY_DEBUG; break; default: *val = -EINVAL; } return 0; } int smblite_lib_get_prop_input_current_settled(struct smb_charger *chg, int *val) { return smblite_lib_get_charge_param(chg, &chg->param.icl_stat, val); } int smblite_lib_get_prop_input_voltage_settled(struct smb_charger *chg, int *val) { union power_supply_propval pval = {0, }; int rc; rc = smblite_lib_get_prop_usb_voltage_now(chg, &pval); *val = pval.intval; return rc; } int smblite_lib_get_prop_die_health(struct smb_charger *chg) { int rc; u8 stat; int input_present; rc = smblite_lib_is_input_present(chg, &input_present); if (rc < 0) return rc; if (input_present == INPUT_NOT_PRESENT) return POWER_SUPPLY_HEALTH_UNKNOWN; rc = smblite_lib_read(chg, DIE_TEMP_STATUS_REG(chg->base), &stat); if (rc < 0) { smblite_lib_err(chg, "Couldn't read DIE_TEMP_STATUS_REG, rc=%d\n", rc); return POWER_SUPPLY_HEALTH_UNKNOWN; } if (stat & DIE_TEMP_RST_BIT) return POWER_SUPPLY_HEALTH_OVERHEAT; if (stat & DIE_TEMP_UB_BIT) return POWER_SUPPLY_HEALTH_HOT; if (stat & DIE_TEMP_LB_BIT) return POWER_SUPPLY_HEALTH_WARM; return POWER_SUPPLY_HEALTH_COOL; } int smblite_lib_get_die_health(struct smb_charger *chg, int *val) { *val = smblite_lib_get_prop_die_health(chg); return 0; } int smblite_lib_get_prop_scope(struct smb_charger *chg, union power_supply_propval *val) { int rc; union power_supply_propval pval; val->intval = POWER_SUPPLY_SCOPE_UNKNOWN; rc = smblite_lib_get_prop_usb_present(chg, &pval); if (rc < 0) return rc; val->intval = pval.intval ? POWER_SUPPLY_SCOPE_DEVICE : chg->otg_present ? POWER_SUPPLY_SCOPE_SYSTEM : POWER_SUPPLY_SCOPE_UNKNOWN; return 0; } static int get_rp_based_dcp_current(struct smb_charger *chg, int typec_mode) { int rp_ua; switch (typec_mode) { case QTI_POWER_SUPPLY_TYPEC_SOURCE_HIGH: rp_ua = TYPEC_HIGH_CURRENT_UA; break; case QTI_POWER_SUPPLY_TYPEC_SOURCE_MEDIUM: case QTI_POWER_SUPPLY_TYPEC_SOURCE_DEFAULT: default: rp_ua = DCP_CURRENT_UA; } return rp_ua; } /******************* * USB PSY SETTERS * * *****************/ int smblite_lib_set_prop_usb_type(struct smb_charger *chg, const int val) { smblite_lib_dbg(chg, PR_MISC, "Charger type request form USB driver type=%d\n", val); /* update real charger type */ smblite_lib_update_usb_type(chg, val); /* For SDP rely on USB enumeration based reported the current */ if ((chg->real_charger_type == POWER_SUPPLY_TYPE_USB) || (chg->real_charger_type == POWER_SUPPLY_TYPE_UNKNOWN)) return 0; vote(chg->usb_icl_votable, USB_PSY_VOTER, false, 0); update_sw_icl_max(chg, chg->real_charger_type); power_supply_changed(chg->usb_psy); return 0; } int smblite_lib_set_prop_current_max(struct smb_charger *chg, const union power_supply_propval *val) { int rc = 0; smblite_lib_dbg(chg, PR_MISC, "Current request from USB driver current=%dmA, charger_type=%d\n", val->intval, chg->real_charger_type); if (chg->real_charger_type == QTI_POWER_SUPPLY_TYPE_USB_FLOAT) { if (val->intval == -ETIMEDOUT) { if ((chg->float_cfg & FLOAT_OPTIONS_MASK) == FORCE_FLOAT_SDP_CFG_BIT) { /* * Confiugure USB500 mode if Float charger is * configured for SDP mode. */ rc = vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, true, USBIN_500UA); if (rc < 0) smblite_lib_err(chg, "Couldn't set SDP ICL rc=%d\n", rc); return rc; } /* Set ICL to 1.5A if its configured for DCP */ rc = vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, true, DCP_CURRENT_UA); if (rc < 0) return rc; } else { /* * FLOAT charger detected as SDP by USB driver, * charge with the requested current and update the * real_charger_type */ chg->real_charger_type = POWER_SUPPLY_TYPE_USB; rc = vote(chg->usb_icl_votable, USB_PSY_VOTER, true, val->intval); if (rc < 0) return rc; rc = vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, false, 0); if (rc < 0) return rc; } } else if (chg->real_charger_type == POWER_SUPPLY_TYPE_USB) { rc = vote(chg->usb_icl_votable, USB_PSY_VOTER, true, val->intval); if (rc < 0) { pr_err("Couldn't vote ICL USB_PSY_VOTER rc=%d\n", rc); return rc; } rc = vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, false, 0); if (rc < 0) { pr_err("Couldn't remove SW_ICL_MAX vote rc=%d\n", rc); return rc; } /* Update TypeC Rp based current */ if (chg->connector_type == QTI_POWER_SUPPLY_CONNECTOR_TYPEC) { update_sw_icl_max(chg, chg->real_charger_type); } else if (is_flashlite_active(chg) && (val->intval >= USBIN_400UA)) { /* For Uusb based SDP port */ vote(chg->usb_icl_votable, FLASH_ACTIVE_VOTER, true, val->intval - USBIN_300UA); smblite_lib_dbg(chg, PR_MISC, "flash_active = 1, ICL set to %d\n", val->intval - USBIN_300UA); } } return 0; } int smblite_lib_set_prop_typec_power_role(struct smb_charger *chg, const int val) { int rc = 0; u8 power_role; if (chg->connector_type == QTI_POWER_SUPPLY_CONNECTOR_MICRO_USB) return -EINVAL; smblite_lib_dbg(chg, PR_MISC, "power role change: %d --> %d!", chg->power_role, val); if (chg->power_role == val) { smblite_lib_dbg(chg, PR_MISC, "power role already in %d, ignore!", chg->power_role); return 0; } switch (val) { case QTI_POWER_SUPPLY_TYPEC_PR_NONE: power_role = TYPEC_DISABLE_CMD_BIT; break; case QTI_POWER_SUPPLY_TYPEC_PR_DUAL: power_role = 0; break; case QTI_POWER_SUPPLY_TYPEC_PR_SINK: power_role = EN_SNK_ONLY_BIT; break; case QTI_POWER_SUPPLY_TYPEC_PR_SOURCE: power_role = EN_SRC_ONLY_BIT; break; default: smblite_lib_err(chg, "power role %d not supported\n", val); return -EINVAL; } rc = smblite_lib_masked_write(chg, TYPE_C_MODE_CFG_REG(chg->base), TYPEC_POWER_ROLE_CMD_MASK | TYPEC_TRY_MODE_MASK, power_role); if (rc < 0) { smblite_lib_err(chg, "Couldn't write 0x%02x to TYPE_C_INTRPT_ENB_SOFTWARE_CTRL rc=%d\n", power_role, rc); return rc; } chg->power_role = val; return rc; } int smblite_lib_set_prop_ship_mode(struct smb_charger *chg, const int val) { int rc; smblite_lib_dbg(chg, PR_MISC, "Set ship mode: %d!!\n", !!val); rc = smblite_lib_masked_write(chg, SHIP_MODE_REG(chg->base), SHIP_MODE_EN_BIT, !!val ? SHIP_MODE_EN_BIT : 0); if (rc < 0) dev_err(chg->dev, "Couldn't %s ship mode, rc=%d\n", !!val ? "enable" : "disable", rc); return rc; } #define JEITA_SOFT 0 #define JEITA_HARD 1 static int smblite_lib_update_jeita(struct smb_charger *chg, u32 *thresholds, int type) { int rc; u16 temp, base_low, base_high; base_low = (type == JEITA_SOFT) ? CHGR_JEITA_COOL_THRESHOLD_REG(chg->base) : CHGR_JEITA_COLD_THRESHOLD_REG(chg->base); base_high = (type == JEITA_SOFT) ? CHGR_JEITA_WARM_THRESHOLD_REG(chg->base) : CHGR_JEITA_HOT_THRESHOLD_REG(chg->base); temp = thresholds[1] & 0xFFFF; temp = ((temp & 0xFF00) >> 8) | ((temp & 0xFF) << 8); rc = smblite_lib_batch_write(chg, base_high, (u8 *)&temp, 2); if (rc < 0) { smblite_lib_err(chg, "Couldn't configure Jeita %s hot threshold rc=%d\n", (type == JEITA_SOFT) ? "Soft" : "Hard", rc); return rc; } temp = thresholds[0] & 0xFFFF; temp = ((temp & 0xFF00) >> 8) | ((temp & 0xFF) << 8); rc = smblite_lib_batch_write(chg, base_low, (u8 *)&temp, 2); if (rc < 0) { smblite_lib_err(chg, "Couldn't configure Jeita %s cold threshold rc=%d\n", (type == JEITA_SOFT) ? "Soft" : "Hard", rc); return rc; } smblite_lib_dbg(chg, PR_MISC, "%s Jeita threshold configured\n", (type == JEITA_SOFT) ? "Soft" : "Hard"); return 0; } static int smblite_lib_charge_inhibit_en(struct smb_charger *chg, bool enable) { int rc; rc = smblite_lib_masked_write(chg, CHGR_INHIBIT_REG(chg->base), CHGR_INHIBIT_BIT, enable ? CHGR_INHIBIT_BIT : 0); return rc; } static int smblite_lib_soft_jeita_arb_wa(struct smb_charger *chg) { union power_supply_propval pval; int rc = 0; bool soft_jeita; rc = smblite_lib_get_prop_batt_health(chg, &pval); if (rc < 0) { smblite_lib_err(chg, "Couldn't get battery health rc=%d\n", rc); return rc; } /* Do nothing on entering hard JEITA condition */ if (pval.intval == POWER_SUPPLY_HEALTH_COLD || pval.intval == POWER_SUPPLY_HEALTH_HOT) return 0; if (chg->jeita_soft_fcc[0] < 0 || chg->jeita_soft_fcc[1] < 0 || chg->jeita_soft_fv[0] < 0 || chg->jeita_soft_fv[1] < 0) return 0; soft_jeita = (pval.intval == POWER_SUPPLY_HEALTH_COOL) || (pval.intval == POWER_SUPPLY_HEALTH_WARM); /* Do nothing on entering soft JEITA from hard JEITA */ if (chg->jeita_arb_flag && soft_jeita) return 0; /* Do nothing, initial to health condition */ if (!chg->jeita_arb_flag && !soft_jeita) return 0; /* Entering soft JEITA from normal state */ if (!chg->jeita_arb_flag && soft_jeita) { vote(chg->chg_disable_votable, JEITA_ARB_VOTER, true, 0); rc = smblite_lib_charge_inhibit_en(chg, true); if (rc < 0) smblite_lib_err(chg, "Couldn't enable charge inhibit rc=%d\n", rc); rc = smblite_lib_update_jeita(chg, chg->jeita_soft_hys_thlds, JEITA_SOFT); if (rc < 0) smblite_lib_err(chg, "Couldn't configure Jeita soft threshold rc=%d\n", rc); if (pval.intval == POWER_SUPPLY_HEALTH_COOL) { vote(chg->fcc_votable, JEITA_ARB_VOTER, true, chg->jeita_soft_fcc[0]); vote(chg->fv_votable, JEITA_ARB_VOTER, true, chg->jeita_soft_fv[0]); } else { vote(chg->fcc_votable, JEITA_ARB_VOTER, true, chg->jeita_soft_fcc[1]); vote(chg->fv_votable, JEITA_ARB_VOTER, true, chg->jeita_soft_fv[1]); } vote(chg->chg_disable_votable, JEITA_ARB_VOTER, false, 0); chg->jeita_arb_flag = true; } else if (chg->jeita_arb_flag && !soft_jeita) { /* Exit to health state from soft JEITA */ vote(chg->chg_disable_votable, JEITA_ARB_VOTER, true, 0); rc = smblite_lib_charge_inhibit_en(chg, false); if (rc < 0) smblite_lib_err(chg, "Couldn't disable charge inhibit rc=%d\n", rc); rc = smblite_lib_update_jeita(chg, chg->jeita_soft_thlds, JEITA_SOFT); if (rc < 0) smblite_lib_err(chg, "Couldn't configure Jeita soft threshold rc=%d\n", rc); vote(chg->fcc_votable, JEITA_ARB_VOTER, false, 0); vote(chg->fv_votable, JEITA_ARB_VOTER, false, 0); vote(chg->chg_disable_votable, JEITA_ARB_VOTER, false, 0); chg->jeita_arb_flag = false; } smblite_lib_dbg(chg, PR_MISC, "JEITA ARB status %d, soft JEITA status %d\n", chg->jeita_arb_flag, soft_jeita); return rc; } /************************ * USB MAIN PSY SETTERS * ************************/ int smblite_lib_get_hw_current_max(struct smb_charger *chg, int *total_current_ua) { union power_supply_propval val = {0, }; int rc = 0, typec_source_rd, current_ua; bool non_compliant; u8 stat; rc = smblite_lib_read(chg, LEGACY_CABLE_STATUS_REG(chg->base), &stat); if (rc < 0) { smblite_lib_err(chg, "Couldn't read TYPE_C_STATUS_5 rc=%d\n", rc); return rc; } non_compliant = stat & TYPEC_NONCOMP_LEGACY_CABLE_STATUS_BIT; /* get settled ICL */ rc = smblite_lib_get_prop_input_current_settled(chg, &val.intval); if (rc < 0) { smblite_lib_err(chg, "Couldn't get settled ICL rc=%d\n", rc); return rc; } typec_source_rd = smblite_lib_get_prop_ufp_mode(chg); if ((chg->connector_type == QTI_POWER_SUPPLY_CONNECTOR_MICRO_USB) || (non_compliant && !chg->typec_legacy_use_rp_icl)) { switch (chg->real_charger_type) { case POWER_SUPPLY_TYPE_USB_CDP: current_ua = CDP_CURRENT_UA; break; case POWER_SUPPLY_TYPE_USB_DCP: current_ua = DCP_CURRENT_UA; break; default: current_ua = 0; break; } *total_current_ua = max(current_ua, val.intval); return 0; } switch (typec_source_rd) { case QTI_POWER_SUPPLY_TYPEC_SOURCE_DEFAULT: switch (chg->real_charger_type) { case POWER_SUPPLY_TYPE_USB_CDP: current_ua = CDP_CURRENT_UA; break; case POWER_SUPPLY_TYPE_USB_DCP: current_ua = DCP_CURRENT_UA; break; default: current_ua = 0; break; } break; case QTI_POWER_SUPPLY_TYPEC_SOURCE_MEDIUM: current_ua = TYPEC_MEDIUM_CURRENT_UA; break; case QTI_POWER_SUPPLY_TYPEC_SOURCE_HIGH: current_ua = TYPEC_HIGH_CURRENT_UA; break; case QTI_POWER_SUPPLY_TYPEC_NON_COMPLIANT: case QTI_POWER_SUPPLY_TYPEC_NONE: default: current_ua = 0; break; } *total_current_ua = max(current_ua, val.intval); return 0; } int smblite_lib_get_charge_current(struct smb_charger *chg, int *total_current_ua) { if (chg->usb_icl_votable) *total_current_ua = get_effective_result(chg->usb_icl_votable); return 0; } /********************** * INTERRUPT HANDLERS * **********************/ irqreturn_t smblite_default_irq_handler(int irq, void *data) { struct smb_irq_data *irq_data = data; struct smb_charger *chg = irq_data->parent_data; smblite_lib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name); return IRQ_HANDLED; } irqreturn_t smblite_chg_state_change_irq_handler(int irq, void *data) { struct smb_irq_data *irq_data = data; struct smb_charger *chg = irq_data->parent_data; u8 stat, boost_en_chgr; int rc; int present; smblite_lib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name); rc = smblite_lib_read(chg, BATTERY_CHARGER_STATUS_1_REG(chg->base), &stat); if (rc < 0) { smblite_lib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_1 rc=%d\n", rc); goto failure; } stat = stat & BATTERY_CHARGER_STATUS_MASK; rc = smblite_lib_is_input_present(chg, &present); if (rc < 0) { smblite_lib_err(chg, "Couldn't read USB_INPUT status rc=%d\n", rc); goto failure; } if ((chg->subtype == PM5100) && !!present) { rc = smblite_lib_read(chg, CHGR_CHG_EN_STATUS_REG(chg->base), &boost_en_chgr); if (rc < 0) { smblite_lib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_1 rc=%d\n", rc); goto failure; } /* * Every time BOOST disables charging, reset the FCC stepper from * fcc_step_start. Enforce this by using the override voter. when * BOOST-disable re-enables charging restart the stepper from * fcc_step_start */ if (boost_en_chgr & CHARGING_DISABLED_FROM_BOOST_BIT) { vote_override(chg->fcc_main_votable, FCC_STEPPER_VOTER, true, chg->chg_param.fcc_step_start_ua); vote(chg->fcc_votable, FCC_STEPPER_VOTER, true, chg->chg_param.fcc_step_start_ua); smblite_lib_dbg(chg, PR_INTERRUPT, "Reset FCC stepper due to boost enabled\n"); } else { vote_override(chg->fcc_main_votable, FCC_STEPPER_VOTER, false, chg->chg_param.fcc_step_start_ua); /* Remove this vote to allow stepper to ramp-up */ vote(chg->fcc_votable, FCC_STEPPER_VOTER, false, 0); } } power_supply_changed(chg->batt_psy); failure: return IRQ_HANDLED; } irqreturn_t smblite_batt_temp_changed_irq_handler(int irq, void *data) { struct smb_irq_data *irq_data = data; struct smb_charger *chg = irq_data->parent_data; int rc; smblite_lib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name); if (chg->jeita_configured != JEITA_CFG_COMPLETE) return IRQ_HANDLED; rc = smblite_lib_soft_jeita_arb_wa(chg); if (rc < 0) { smblite_lib_err(chg, "Couldn't fix soft jeita arb rc=%d\n", rc); return IRQ_HANDLED; } return IRQ_HANDLED; } irqreturn_t smblite_batt_psy_changed_irq_handler(int irq, void *data) { struct smb_irq_data *irq_data = data; struct smb_charger *chg = irq_data->parent_data; smblite_lib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name); power_supply_changed(chg->batt_psy); return IRQ_HANDLED; } #define AICL_STEP_MV 200 #define MAX_AICL_THRESHOLD_MV 4800 irqreturn_t smblite_usbin_uv_irq_handler(int irq, void *data) { struct smb_irq_data *irq_data = data; struct smb_charger *chg = irq_data->parent_data; struct storm_watch *wdata; int rc; smblite_lib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name); if ((chg->wa_flags & WEAK_ADAPTER_WA) && is_storming(&irq_data->storm_data)) { if (chg->aicl_max_reached) { smblite_lib_dbg(chg, PR_MISC, "USBIN_UV storm at max AICL threshold\n"); return IRQ_HANDLED; } smblite_lib_dbg(chg, PR_MISC, "USBIN_UV storm at threshold %d\n", chg->aicl_5v_threshold_mv); /* suspend USBIN before updating AICL threshold */ vote(chg->usb_icl_votable, AICL_THRESHOLD_VOTER, true, 0); /* delay for VASHDN deglitch */ msleep(20); if (chg->aicl_5v_threshold_mv > MAX_AICL_THRESHOLD_MV) { /* reached max AICL threshold */ chg->aicl_max_reached = true; goto unsuspend_input; } /* Increase AICL threshold by 200mV */ rc = smblite_lib_set_charge_param(chg, &chg->param.aicl_5v_threshold, chg->aicl_5v_threshold_mv + AICL_STEP_MV); if (rc < 0) dev_err(chg->dev, "Error in setting AICL threshold rc=%d\n", rc); else chg->aicl_5v_threshold_mv += AICL_STEP_MV; unsuspend_input: /* Force torch in boost mode to ensure it works with low ICL */ if (chg->subtype == PM2250) schgm_flashlite_torch_priority(chg, TORCH_BOOST_MODE); if (chg->aicl_max_reached) { smblite_lib_dbg(chg, PR_MISC, "Reached max AICL threshold resctricting ICL to 100mA\n"); vote(chg->usb_icl_votable, AICL_THRESHOLD_VOTER, true, USBIN_100UA); smblite_lib_run_aicl(chg, RESTART_AICL); } else { smblite_lib_run_aicl(chg, RESTART_AICL); vote(chg->usb_icl_votable, AICL_THRESHOLD_VOTER, false, 0); } wdata = &chg->irq_info[USBIN_UV_IRQ].irq_data->storm_data; reset_storm_count(wdata); } if (!chg->irq_info[SWITCHER_POWER_OK_IRQ].irq_data) return IRQ_HANDLED; wdata = &chg->irq_info[SWITCHER_POWER_OK_IRQ].irq_data->storm_data; reset_storm_count(wdata); return IRQ_HANDLED; } #define USB_WEAK_INPUT_UA 1400000 #define ICL_CHANGE_DELAY_MS 1000 irqreturn_t smblite_icl_change_irq_handler(int irq, void *data) { int rc, settled_ua, delay = ICL_CHANGE_DELAY_MS; struct smb_irq_data *irq_data = data; struct smb_charger *chg = irq_data->parent_data; if (chg->mode == PARALLEL_MASTER) { rc = smblite_lib_get_charge_param(chg, &chg->param.icl_stat, &settled_ua); if (rc < 0) { smblite_lib_err(chg, "Couldn't get ICL status rc=%d\n", rc); return IRQ_HANDLED; } /* If AICL settled then schedule work now */ if (settled_ua == get_effective_result(chg->usb_icl_votable)) delay = 0; cancel_delayed_work_sync(&chg->icl_change_work); schedule_delayed_work(&chg->icl_change_work, msecs_to_jiffies(delay)); } return IRQ_HANDLED; } static int smblite_lib_role_switch_failure(struct smb_charger *chg) { int rc = 0; union power_supply_propval pval = {0, }; if (!chg->use_extcon) return 0; rc = smblite_lib_get_prop_usb_present(chg, &pval); if (rc < 0) { smblite_lib_err(chg, "Couldn't get usb presence status rc=%d\n", rc); return rc; } /* * When role switch fails notify the * current charger state to usb driver. */ if (pval.intval) { smblite_lib_dbg(chg, PR_MISC, "Role reversal failed, notifying device mode to usb driver.\n"); smblite_lib_notify_device_mode(chg, true); } return rc; } static int typec_partner_register(struct smb_charger *chg) { int typec_mode, rc = 0; mutex_lock(&chg->typec_lock); if (!chg->typec_port || chg->pr_swap_in_progress) goto unlock; if (!chg->typec_partner) { if (chg->sink_src_mode == AUDIO_ACCESS_MODE) chg->typec_partner_desc.accessory = TYPEC_ACCESSORY_AUDIO; else chg->typec_partner_desc.accessory = TYPEC_ACCESSORY_NONE; chg->typec_partner = typec_register_partner(chg->typec_port, &chg->typec_partner_desc); if (IS_ERR(chg->typec_partner)) { rc = PTR_ERR(chg->typec_partner); pr_err("Couldn't to register typec_partner rc=%d\n", rc); goto unlock; } } if (chg->connector_type == QTI_POWER_SUPPLY_CONNECTOR_MICRO_USB) goto unlock; typec_mode = smblite_lib_get_prop_typec_mode(chg); if (typec_mode >= QTI_POWER_SUPPLY_TYPEC_SOURCE_DEFAULT || typec_mode == QTI_POWER_SUPPLY_TYPEC_NONE) { if (chg->typec_role_swap_failed) { rc = smblite_lib_role_switch_failure(chg); if (rc < 0) smblite_lib_err(chg, "Failed to role switch rc=%d\n", rc); chg->typec_role_swap_failed = false; } typec_set_data_role(chg->typec_port, TYPEC_DEVICE); typec_set_pwr_role(chg->typec_port, TYPEC_SINK); } else { typec_set_data_role(chg->typec_port, TYPEC_HOST); typec_set_pwr_role(chg->typec_port, TYPEC_SOURCE); } unlock: mutex_unlock(&chg->typec_lock); return rc; } static void typec_partner_unregister(struct smb_charger *chg) { mutex_lock(&chg->typec_lock); if (!chg->typec_port) goto unlock; if (chg->typec_partner && !chg->pr_swap_in_progress) { smblite_lib_dbg(chg, PR_MISC, "Un-registering typeC partner\n"); typec_unregister_partner(chg->typec_partner); chg->typec_partner = NULL; } unlock: mutex_unlock(&chg->typec_lock); } static void smblite_lib_micro_usb_plugin(struct smb_charger *chg, bool vbus_rising) { int rc = 0; u8 stat; if (vbus_rising) { /* * Send extcon notification for only Non-ADSP supported charger. */ if (chg->subtype == PM2250) smblite_lib_notify_device_mode(chg, true); rc = typec_partner_register(chg); if (rc < 0) smblite_lib_err(chg, "Couldn't register partner rc =%d\n", rc); /* * For PM5100 check if concurrent mode support is enabled and * charging is paused in hardware due to boost being enabled, * force charging to be disabled in SW. */ if (is_concurrent_mode_supported(chg)) { rc = smblite_lib_read(chg, CHGR_CHG_EN_STATUS_REG(chg->base), &stat); if (rc < 0) smblite_lib_err(chg, "Couldn't read CHGR_EN_STATUS_REG rc=%d\n", rc); vote(chg->chg_disable_votable, CONCURRENT_MODE_VOTER, (stat & CHARGING_DISABLED_FROM_BOOST_BIT), 0); smblite_lib_dbg(chg, PR_MISC, "charger_en_status=%x, Charging disable by boost\n", stat); } if (chg->wa_flags & HDC_ICL_REDUCTION_WA) { rc = smblite_lib_masked_write(chg, BATIF_PULLDOWN_VPH_CONTROL(chg->base), BATIF_PULLDOWN_VPH_SEL_MASK, (PULLDOWN_VPH_SW_EN_BIT | PULLDOWN_VPH_HW_EN_BIT)); if (rc < 0) smblite_lib_err(chg, "Couldn't set BATIF_PULLDOWN_VPH_CONTROL rc=%d\n", rc); rc = smblite_lib_run_aicl(chg, RERUN_AICL); if (rc < 0) smblite_lib_err(chg, "Couldn't rerun AICL rc=%d\n", rc); } } else { smblite_lib_notify_device_mode(chg, false); smblite_lib_uusb_removal(chg); typec_partner_unregister(chg); if (chg->wa_flags & HDC_ICL_REDUCTION_WA) { rc = smblite_lib_masked_write(chg, BATIF_PULLDOWN_VPH_CONTROL(chg->base), BATIF_PULLDOWN_VPH_SEL_MASK, PULLDOWN_VPH_HW_EN_BIT); if (rc < 0) smblite_lib_err(chg, "Couldn't set BATIF_PULLDOWN_VPH_CONTROL rc=%d\n", rc); } } } static int smblite_lib_request_dpdm(struct smb_charger *chg, bool enable) { int rc = 0; /* Enable dpdm requests only for platform with PM5100 */ if (chg->subtype != PM5100) return 0; /* fetch the DPDM regulator */ if (!chg->dpdm_reg && of_get_property(chg->dev->of_node, "dpdm-supply", NULL)) { chg->dpdm_reg = devm_regulator_get(chg->dev, "dpdm"); if (IS_ERR(chg->dpdm_reg)) { rc = PTR_ERR(chg->dpdm_reg); smblite_lib_err(chg, "Couldn't get dpdm regulator rc=%d\n", rc); chg->dpdm_reg = NULL; return rc; } } mutex_lock(&chg->dpdm_lock); if (enable) { if (chg->dpdm_reg && !chg->dpdm_enabled) { smblite_lib_dbg(chg, PR_MISC, "enabling DPDM regulator\n"); rc = regulator_enable(chg->dpdm_reg); if (rc < 0) smblite_lib_err(chg, "Couldn't enable dpdm regulator rc=%d\n", rc); else chg->dpdm_enabled = true; } } else { if (chg->dpdm_reg && chg->dpdm_enabled) { smblite_lib_dbg(chg, PR_MISC, "disabling DPDM regulator\n"); rc = regulator_disable(chg->dpdm_reg); if (rc < 0) smblite_lib_err(chg, "Couldn't disable dpdm regulator rc=%d\n", rc); else chg->dpdm_enabled = false; } } mutex_unlock(&chg->dpdm_lock); return rc; } #define PL_DELAY_MS 30000 static void smblite_lib_usb_plugin_locked(struct smb_charger *chg) { int rc; u8 stat; bool vbus_rising; struct smb_irq_data *data; struct storm_watch *wdata; rc = smblite_lib_read(chg, chg->base.usbin_base + INT_RT_STS_OFFSET, &stat); if (rc < 0) { smblite_lib_err(chg, "Couldn't read USB_INT_RT_STS rc=%d\n", rc); return; } vbus_rising = (bool)(stat & USBIN_PLUGIN_RT_STS_BIT); if (vbus_rising) { /* Remove FCC_STEPPER 1.5A init vote to allow FCC ramp up */ if (chg->fcc_stepper_enable) vote(chg->fcc_votable, FCC_STEPPER_VOTER, false, 0); rc = smblite_lib_request_dpdm(chg, true); if (rc < 0) smblite_lib_err(chg, "Couldn't to enable DPDM rc=%d\n", rc); /* Schedule work to enable parallel charger */ vote(chg->awake_votable, PL_DELAY_VOTER, true, 0); schedule_delayed_work(&chg->pl_enable_work, msecs_to_jiffies(PL_DELAY_MS)); } else { smblite_lib_update_usb_type(chg, POWER_SUPPLY_TYPE_UNKNOWN); if (chg->wa_flags & BOOST_BACK_WA) { data = chg->irq_info[SWITCHER_POWER_OK_IRQ].irq_data; if (data) { wdata = &data->storm_data; update_storm_count(wdata, WEAK_CHG_STORM_COUNT); vote(chg->usb_icl_votable, BOOST_BACK_VOTER, false, 0); vote(chg->usb_icl_votable, WEAK_CHARGER_VOTER, false, 0); } } if (chg->fcc_stepper_enable) vote(chg->fcc_votable, FCC_STEPPER_VOTER, true, chg->chg_param.fcc_step_start_ua); if (chg->wa_flags & WEAK_ADAPTER_WA) { chg->aicl_5v_threshold_mv = chg->default_aicl_5v_threshold_mv; smblite_lib_set_charge_param(chg, &chg->param.aicl_5v_threshold, chg->aicl_5v_threshold_mv); chg->aicl_max_reached = false; /* * schgm_flash_torch_priority(chg, TORCH_BUCK_MODE); */ data = chg->irq_info[USBIN_UV_IRQ].irq_data; if (data) { wdata = &data->storm_data; reset_storm_count(wdata); } vote(chg->usb_icl_votable, AICL_THRESHOLD_VOTER, false, 0); } rc = smblite_lib_request_dpdm(chg, false); if (rc < 0) smblite_lib_err(chg, "Couldn't to disable DPDM rc=%d\n", rc); } if (chg->connector_type == QTI_POWER_SUPPLY_CONNECTOR_MICRO_USB) smblite_lib_micro_usb_plugin(chg, vbus_rising); vote(chg->temp_change_irq_disable_votable, DEFAULT_VOTER, !vbus_rising, 0); power_supply_changed(chg->usb_psy); smblite_lib_dbg(chg, PR_INTERRUPT, "IRQ: usbin-plugin %s\n", vbus_rising ? "attached" : "detached"); } irqreturn_t smblite_usb_plugin_irq_handler(int irq, void *data) { struct smb_irq_data *irq_data = data; struct smb_charger *chg = irq_data->parent_data; smblite_lib_usb_plugin_locked(chg); return IRQ_HANDLED; } static void update_sw_icl_max(struct smb_charger *chg, int type) { int typec_mode; int rp_ua, icl_ua; if (chg->typec_mode == QTI_POWER_SUPPLY_TYPEC_SINK_AUDIO_ADAPTER) { vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, true, 500000); return; } if (chg->real_charger_type == POWER_SUPPLY_TYPE_UNKNOWN) { vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, true, USBIN_100UA); return; } /* TypeC rp med or high, use rp value */ typec_mode = smblite_lib_get_prop_typec_mode(chg); if (typec_rp_med_high(chg, typec_mode)) { rp_ua = get_rp_based_dcp_current(chg, typec_mode); vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, true, rp_ua); vote(chg->usb_icl_votable, USB_PSY_VOTER, false, 0); return; } /* rp-std or legacy, USB BC 1.2 */ switch (type) { case POWER_SUPPLY_TYPE_USB: /* * USB_PSY will vote to increase the current to 500/900mA once * enumeration is done. */ if (!is_client_vote_enabled(chg->usb_icl_votable, USB_PSY_VOTER)) { /* if flash is active force 500mA */ vote(chg->usb_icl_votable, USB_PSY_VOTER, true, is_flashlite_active(chg) ? USBIN_500UA : USBIN_100UA); } vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, false, 0); break; case POWER_SUPPLY_TYPE_USB_CDP: vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, true, CDP_CURRENT_UA); break; case POWER_SUPPLY_TYPE_USB_DCP: rp_ua = get_rp_based_dcp_current(chg, typec_mode); vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, true, rp_ua); break; case QTI_POWER_SUPPLY_TYPE_USB_FLOAT: /* * limit ICL to 100mA, the USB driver will enumerate to check * if this is a SDP and appropriately set the current */ vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, true, USBIN_100UA); break; case POWER_SUPPLY_TYPE_UNKNOWN: default: vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, true, USBIN_100UA); break; } if (is_flashlite_active(chg)) { icl_ua = get_effective_result(chg->usb_icl_votable); if (icl_ua >= USBIN_400UA) { vote(chg->usb_icl_votable, FLASH_ACTIVE_VOTER, true, icl_ua - USBIN_300UA); smblite_lib_dbg(chg, PR_MISC, "flash_active = 1 ICL is set to %d\n", icl_ua - USBIN_300UA); } } } void smblite_lib_hvdcp_detect_enable(struct smb_charger *chg, bool enable) { int rc; u8 mask; mask = HVDCP_NO_AUTH_QC3_CFG_BIT | HVDCP_EN_BIT; rc = smblite_lib_masked_write(chg, USBIN_QC23_EN_REG(chg->base), mask, enable ? mask : 0); if (rc < 0) smblite_lib_err(chg, "failed to write USBIN_QC23_EN_REG rc=%d\n", rc); } /* triggers when HVDCP is detected */ static void smblite_lib_handle_hvdcp_detect_done(struct smb_charger *chg, bool rising) { smblite_lib_dbg(chg, PR_INTERRUPT, "IRQ: hvdcp-detect-done %s\n", rising ? "rising" : "falling"); } void smblite_lib_rerun_apsd(struct smb_charger *chg) { int rc; smblite_lib_dbg(chg, PR_MISC, "re-running APSD\n"); rc = smblite_lib_masked_write(chg, CMD_APSD_REG(chg->base), APSD_RERUN_BIT, APSD_RERUN_BIT); if (rc < 0) smblite_lib_err(chg, "Couldn't re-run APSD rc=%d\n", rc); } int smblite_lib_rerun_apsd_if_required(struct smb_charger *chg) { union power_supply_propval val; int rc; rc = smblite_lib_get_prop_usb_present(chg, &val); if (rc < 0) { smblite_lib_err(chg, "Couldn't get usb present rc = %d\n", rc); return rc; } if (!val.intval) return 0; rc = smblite_lib_request_dpdm(chg, true); if (rc < 0) smblite_lib_err(chg, "Couldn't to enable DPDM rc=%d\n", rc); chg->uusb_apsd_rerun_done = true; smblite_lib_rerun_apsd(chg); return 0; } static void smblite_lib_handle_apsd_done(struct smb_charger *chg, bool rising) { const struct apsd_result *apsd_result = smblite_lib_get_apsd_result(chg); if (!rising) return; apsd_result = smblite_lib_update_usb_type(chg, apsd_result->val); /* set the ICL based on charger type */ update_sw_icl_max(chg, apsd_result->val); switch (apsd_result->bit) { case SDP_CHARGER_BIT: case CDP_CHARGER_BIT: case FLOAT_CHARGER_BIT: if (chg->use_extcon) smblite_lib_notify_device_mode(chg, true); break; case OCP_CHARGER_BIT: case DCP_CHARGER_BIT: break; default: break; } smblite_lib_dbg(chg, PR_INTERRUPT, "IRQ: apsd-done rising; %s detected\n", apsd_result->name); } static void smblite_lib_handle_hvdcp_check_timeout(struct smb_charger *chg, bool rising, bool qc_charger) { int rc = 0; /* Stay at 5V if BOOST is enabled */ if (is_boost_en(chg)) { smblite_lib_dbg(chg, PR_INTERRUPT, "Ignoring HVDCP3 detect as boost is enabled\n"); return; } if (rising) { if (qc_charger && !chg->hvdcp3_detected) { /* Increase vbus to MAX(6V), if incremented HVDCP_3 is detected */ rc = smblite_lib_hvdcp3_force_max_vbus(chg); if (rc < 0) smblite_lib_err(chg, "HVDCP3 detection failure\n"); } } smblite_lib_dbg(chg, PR_INTERRUPT, "IRQ: %s %s, hvdcp3_detected=%s\n", __func__, (rising ? "rising" : "falling"), (chg->hvdcp3_detected ? "True" : "False")); } static void smblite_lib_handle_sdp_enumeration_done(struct smb_charger *chg, bool rising) { smblite_lib_dbg(chg, PR_INTERRUPT, "IRQ: sdp-enumeration-done %s\n", rising ? "rising" : "falling"); } static void smblite_lib_handle_slow_plugin_timeout(struct smb_charger *chg, bool rising) { smblite_lib_dbg(chg, PR_INTERRUPT, "IRQ: slow-plugin-timeout %s\n", rising ? "rising" : "falling"); } irqreturn_t smblite_usb_source_change_irq_handler(int irq, void *data) { struct smb_irq_data *irq_data = data; struct smb_charger *chg = irq_data->parent_data; int rc = 0; u8 stat; rc = smblite_lib_read(chg, APSD_STATUS_REG(chg->base), &stat); if (rc < 0) { smblite_lib_err(chg, "Couldn't read APSD_STATUS rc=%d\n", rc); return IRQ_HANDLED; } smblite_lib_dbg(chg, PR_INTERRUPT, "APSD_STATUS = 0x%02x\n", stat); if ((chg->connector_type == QTI_POWER_SUPPLY_CONNECTOR_MICRO_USB) && (stat & APSD_DTC_STATUS_DONE_BIT) && !chg->uusb_apsd_rerun_done) { /* * Force re-run APSD to handle slow insertion related * charger-mis-detection. */ chg->uusb_apsd_rerun_done = true; smblite_lib_rerun_apsd_if_required(chg); return IRQ_HANDLED; } smblite_lib_handle_apsd_done(chg, (bool)(stat & APSD_DTC_STATUS_DONE_BIT)); smblite_lib_handle_hvdcp_detect_done(chg, (bool)(stat & QC_CHARGER_BIT)); smblite_lib_handle_hvdcp_check_timeout(chg, (bool)(stat & HVDCP_CHECK_TIMEOUT_BIT), (bool)(stat & QC_CHARGER_BIT)); smblite_lib_handle_sdp_enumeration_done(chg, (bool)(stat & ENUMERATION_DONE_BIT)); smblite_lib_handle_slow_plugin_timeout(chg, (bool)(stat & SLOW_PLUGIN_TIMEOUT_BIT)); power_supply_changed(chg->usb_psy); rc = smblite_lib_read(chg, APSD_STATUS_REG(chg->base), &stat); if (rc < 0) { smblite_lib_err(chg, "Couldn't read APSD_STATUS rc=%d\n", rc); return IRQ_HANDLED; } smblite_lib_dbg(chg, PR_INTERRUPT, "APSD_STATUS = 0x%02x\n", stat); return IRQ_HANDLED; } static void typec_sink_insertion(struct smb_charger *chg) { smblite_lib_notify_usb_host(chg, true); } static void typec_src_insertion(struct smb_charger *chg) { int rc = 0; u8 stat; smblite_lib_notify_device_mode(chg, true); if (chg->pr_swap_in_progress) { vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, false, 0); return; } rc = smblite_lib_read(chg, LEGACY_CABLE_STATUS_REG(chg->base), &stat); if (rc < 0) { smblite_lib_err(chg, "Couldn't read TYPE_C_STATE_MACHINE_STATUS_REG rc=%d\n", rc); return; } chg->typec_legacy = stat & TYPEC_LEGACY_CABLE_STATUS_BIT; } static void typec_ra_ra_insertion(struct smb_charger *chg) { vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, true, 500000); vote(chg->usb_icl_votable, USB_PSY_VOTER, false, 0); } static const char * const dr_mode_text[] = { "ufp", "dfp", "none" }; static int smblite_lib_force_dr_mode(struct smb_charger *chg, int mode) { int rc = 0; switch (mode) { case TYPEC_PORT_SNK: rc = smblite_lib_masked_write(chg, TYPE_C_MODE_CFG_REG(chg->base), TYPEC_POWER_ROLE_CMD_MASK, EN_SNK_ONLY_BIT); if (rc < 0) { smblite_lib_err(chg, "Couldn't enable snk, rc=%d\n", rc); return rc; } break; case TYPEC_PORT_SRC: rc = smblite_lib_masked_write(chg, TYPE_C_MODE_CFG_REG(chg->base), TYPEC_POWER_ROLE_CMD_MASK, EN_SRC_ONLY_BIT); if (rc < 0) { smblite_lib_err(chg, "Couldn't enable src, rc=%d\n", rc); return rc; } break; case TYPEC_PORT_DRP: rc = smblite_lib_masked_write(chg, TYPE_C_MODE_CFG_REG(chg->base), TYPEC_POWER_ROLE_CMD_MASK, 0); if (rc < 0) { smblite_lib_err(chg, "Couldn't enable DRP, rc=%d\n", rc); return rc; } break; default: smblite_lib_err(chg, "Power role %d not supported\n", mode); return -EINVAL; } chg->dr_mode = mode; return rc; } int smblite_lib_typec_port_type_set(const struct typec_capability *cap, enum typec_port_type type) { struct smb_charger *chg = container_of(cap, struct smb_charger, typec_caps); int rc = 0; if (chg->connector_type == QTI_POWER_SUPPLY_CONNECTOR_MICRO_USB) return 0; mutex_lock(&chg->typec_lock); if ((chg->pr_swap_in_progress) || (type == TYPEC_PORT_DRP)) { smblite_lib_dbg(chg, PR_MISC, "Ignoring port type request type = %d swap_in_progress = %d\n", type, chg->pr_swap_in_progress); goto unlock; } chg->pr_swap_in_progress = true; rc = smblite_lib_force_dr_mode(chg, type); if (rc < 0) { chg->pr_swap_in_progress = false; smblite_lib_err(chg, "Couldn't to force mode, rc=%d\n", rc); goto unlock; } smblite_lib_dbg(chg, PR_MISC, "Requested role %s\n", type ? "SINK" : "SOURCE"); /* * As per the hardware requirements, * schedule the work with required delay. */ if (!(delayed_work_pending(&chg->role_reversal_check))) { cancel_delayed_work_sync(&chg->role_reversal_check); schedule_delayed_work(&chg->role_reversal_check, msecs_to_jiffies(ROLE_REVERSAL_DELAY_MS)); vote(chg->awake_votable, TYPEC_SWAP_VOTER, true, 0); } unlock: mutex_unlock(&chg->typec_lock); return rc; } static void smblite_lib_typec_role_check_work(struct work_struct *work) { struct smb_charger *chg = container_of(work, struct smb_charger, role_reversal_check.work); int rc = 0; if (chg->connector_type == QTI_POWER_SUPPLY_CONNECTOR_MICRO_USB) { chg->pr_swap_in_progress = false; vote(chg->awake_votable, TYPEC_SWAP_VOTER, false, 0); return; } mutex_lock(&chg->typec_lock); switch (chg->dr_mode) { case TYPEC_PORT_SNK: if (chg->typec_mode < QTI_POWER_SUPPLY_TYPEC_SOURCE_DEFAULT) { smblite_lib_dbg(chg, PR_MISC, "Role reversal not latched to UFP in %d msecs. Resetting to DRP mode\n", ROLE_REVERSAL_DELAY_MS); rc = smblite_lib_force_dr_mode(chg, TYPEC_PORT_DRP); if (rc < 0) smblite_lib_err(chg, "Couldn't to set DRP mode, rc=%d\n", rc); } else { chg->power_role = QTI_POWER_SUPPLY_TYPEC_PR_SINK; typec_set_pwr_role(chg->typec_port, TYPEC_SINK); typec_set_data_role(chg->typec_port, TYPEC_DEVICE); smblite_lib_dbg(chg, PR_MISC, "Role changed successfully to SINK"); } break; case TYPEC_PORT_SRC: if (chg->typec_mode >= QTI_POWER_SUPPLY_TYPEC_SOURCE_DEFAULT || chg->typec_mode == QTI_POWER_SUPPLY_TYPEC_NONE) { smblite_lib_dbg(chg, PR_MISC, "Role reversal not latched to DFP in %d msecs. Resetting to DRP mode\n", ROLE_REVERSAL_DELAY_MS); chg->pr_swap_in_progress = false; chg->typec_role_swap_failed = true; rc = smblite_lib_force_dr_mode(chg, TYPEC_PORT_DRP); if (rc < 0) smblite_lib_err(chg, "Couldn't to set DRP mode, rc=%d\n", rc); } else { chg->power_role = QTI_POWER_SUPPLY_TYPEC_PR_SOURCE; typec_set_pwr_role(chg->typec_port, TYPEC_SOURCE); typec_set_data_role(chg->typec_port, TYPEC_HOST); smblite_lib_dbg(chg, PR_MISC, "Role changed successfully to SOURCE"); } break; default: pr_debug("Already in DRP mode\n"); break; } chg->pr_swap_in_progress = false; vote(chg->awake_votable, TYPEC_SWAP_VOTER, false, 0); mutex_unlock(&chg->typec_lock); } static void typec_sink_removal(struct smb_charger *chg) { if (chg->otg_present) smblite_lib_notify_usb_host(chg, false); } static void typec_src_removal(struct smb_charger *chg) { struct smb_irq_data *data; struct storm_watch *wdata; if (chg->wa_flags & BOOST_BACK_WA) { data = chg->irq_info[SWITCHER_POWER_OK_IRQ].irq_data; if (data) { wdata = &data->storm_data; update_storm_count(wdata, WEAK_CHG_STORM_COUNT); vote(chg->usb_icl_votable, BOOST_BACK_VOTER, false, 0); vote(chg->usb_icl_votable, WEAK_CHARGER_VOTER, false, 0); } } cancel_delayed_work_sync(&chg->pl_enable_work); /* reset input current limit voters */ vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, true, is_flashlite_active(chg) ? USBIN_500UA : USBIN_100UA); vote(chg->usb_icl_votable, FLASH_ACTIVE_VOTER, false, 0); vote(chg->usb_icl_votable, USB_PSY_VOTER, false, 0); /* reset parallel voters */ vote(chg->pl_disable_votable, PL_DELAY_VOTER, true, 0); vote(chg->pl_enable_votable_indirect, USBIN_I_VOTER, false, 0); vote(chg->pl_enable_votable_indirect, USBIN_V_VOTER, false, 0); vote(chg->awake_votable, PL_DELAY_VOTER, false, 0); /* Remove SW thermal regulation votes */ vote(chg->usb_icl_votable, SW_THERM_REGULATION_VOTER, false, 0); vote(chg->awake_votable, SW_THERM_REGULATION_VOTER, false, 0); smblite_lib_notify_device_mode(chg, false); chg->typec_legacy = false; chg->hvdcp3_detected = false; } static void typec_mode_unattached(struct smb_charger *chg) { vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, true, USBIN_100UA); } static void smblite_lib_handle_rp_change(struct smb_charger *chg, int typec_mode) { /* * if type is not updated or charger current is not set * for SDP ignore Rp change requests. */ if (chg->real_charger_type == POWER_SUPPLY_TYPE_UNKNOWN || (chg->real_charger_type == POWER_SUPPLY_TYPE_USB && !is_client_vote_enabled(chg->usb_icl_votable, USB_PSY_VOTER))) return; update_sw_icl_max(chg, chg->real_charger_type); smblite_lib_dbg(chg, PR_MISC, "CC change old_mode=%d new_mode=%d\n", chg->typec_mode, typec_mode); } irqreturn_t smblite_typec_or_rid_detection_change_irq_handler(int irq, void *data) { struct smb_irq_data *irq_data = data; struct smb_charger *chg = irq_data->parent_data; smblite_lib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name); if (chg->usb_psy) power_supply_changed(chg->usb_psy); return IRQ_HANDLED; } irqreturn_t smblite_typec_state_change_irq_handler(int irq, void *data) { struct smb_irq_data *irq_data = data; struct smb_charger *chg = irq_data->parent_data; int typec_mode; if (chg->connector_type == QTI_POWER_SUPPLY_CONNECTOR_MICRO_USB) { smblite_lib_dbg(chg, PR_INTERRUPT, "Ignoring for micro USB\n"); return IRQ_HANDLED; } typec_mode = smblite_lib_get_prop_typec_mode(chg); if (chg->sink_src_mode != UNATTACHED_MODE && (typec_mode != chg->typec_mode)) smblite_lib_handle_rp_change(chg, typec_mode); chg->typec_mode = typec_mode; smblite_lib_dbg(chg, PR_INTERRUPT, "IRQ: cc-state-change; Type-C %s detected\n", smblite_lib_typec_mode_name[chg->typec_mode]); power_supply_changed(chg->usb_psy); return IRQ_HANDLED; } #define TYPEC_DETACH_DETECT_DELAY_MS 2000 irqreturn_t smblite_typec_attach_detach_irq_handler(int irq, void *data) { struct smb_irq_data *irq_data = data; struct smb_charger *chg = irq_data->parent_data; u8 stat; bool attached = false; int rc; /* IRQ not expected to be executed for uUSB, return */ if (chg->connector_type == QTI_POWER_SUPPLY_CONNECTOR_MICRO_USB) return IRQ_HANDLED; smblite_lib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name); rc = smblite_lib_read(chg, TYPE_C_STATE_MACHINE_STATUS_REG(chg->base), &stat); if (rc < 0) { smblite_lib_err(chg, "Couldn't read TYPE_C_STATE_MACHINE_STATUS_REG rc=%d\n", rc); return IRQ_HANDLED; } attached = !!(stat & TYPEC_ATTACH_DETACH_STATE_BIT); if (attached) { rc = smblite_lib_read(chg, TYPE_C_MISC_STATUS_REG(chg->base), &stat); if (rc < 0) { smblite_lib_err(chg, "Couldn't read TYPE_C_MISC_STATUS_REG rc=%d\n", rc); return IRQ_HANDLED; } if (smblite_lib_get_prop_dfp_mode(chg) == QTI_POWER_SUPPLY_TYPEC_SINK_AUDIO_ADAPTER) { chg->sink_src_mode = AUDIO_ACCESS_MODE; typec_ra_ra_insertion(chg); } else if (stat & SNK_SRC_MODE_BIT) { chg->sink_src_mode = SRC_MODE; typec_sink_insertion(chg); } else { chg->sink_src_mode = SINK_MODE; typec_src_insertion(chg); } rc = typec_partner_register(chg); if (rc < 0) smblite_lib_err(chg, "Couldn't to register partner rc =%d\n", rc); } else { switch (chg->sink_src_mode) { case SRC_MODE: typec_sink_removal(chg); break; case SINK_MODE: case AUDIO_ACCESS_MODE: typec_src_removal(chg); break; case UNATTACHED_MODE: default: typec_mode_unattached(chg); break; } if (!chg->pr_swap_in_progress) chg->sink_src_mode = UNATTACHED_MODE; /* * Restore DRP mode on type-C cable disconnect if role * swap is not in progress, to ensure forced sink or src * mode configuration is reset properly. */ if (chg->typec_port && !chg->pr_swap_in_progress) { /* * Schedule the work to differentiate actual removal * of cable and detach interrupt during role swap, * unregister the partner only during actual cable * removal. */ cancel_delayed_work(&chg->pr_swap_detach_work); vote(chg->awake_votable, DETACH_DETECT_VOTER, true, 0); schedule_delayed_work(&chg->pr_swap_detach_work, msecs_to_jiffies(TYPEC_DETACH_DETECT_DELAY_MS)); smblite_lib_force_dr_mode(chg, TYPEC_PORT_DRP); /* * To handle cable removal during role * swap failure. */ chg->typec_role_swap_failed = false; } } rc = smblite_lib_masked_write(chg, USB_CMD_PULLDOWN_REG(chg->base), EN_PULLDOWN_USB_IN_BIT, attached ? 0 : EN_PULLDOWN_USB_IN_BIT); if (rc < 0) smblite_lib_err(chg, "Couldn't configure pulldown on USB_IN rc=%d\n", rc); power_supply_changed(chg->usb_psy); return IRQ_HANDLED; } static void smblite_lib_bb_removal_work(struct work_struct *work) { struct smb_charger *chg = container_of(work, struct smb_charger, bb_removal_work.work); vote(chg->usb_icl_votable, BOOST_BACK_VOTER, false, 0); vote(chg->awake_votable, BOOST_BACK_VOTER, false, 0); } #define BOOST_BACK_UNVOTE_DELAY_MS 750 #define BOOST_BACK_STORM_COUNT 3 #define WEAK_CHG_STORM_COUNT 8 irqreturn_t smblite_switcher_power_ok_irq_handler(int irq, void *data) { struct smb_irq_data *irq_data = data; struct smb_charger *chg = irq_data->parent_data; struct storm_watch *wdata = &irq_data->storm_data; int rc, usb_icl; u8 stat; if (!(chg->wa_flags & BOOST_BACK_WA)) return IRQ_HANDLED; rc = smblite_lib_read(chg, POWER_PATH_STATUS_REG(chg->base), &stat); if (rc < 0) { smblite_lib_err(chg, "Couldn't read POWER_PATH_STATUS rc=%d\n", rc); return IRQ_HANDLED; } /* skip suspending input if its already suspended by some other voter */ usb_icl = get_effective_result(chg->usb_icl_votable); if ((stat & USE_USBIN_BIT) && usb_icl >= 0 && usb_icl <= USBIN_25UA) return IRQ_HANDLED; if (is_storming(&irq_data->storm_data)) { /* This could be a weak charger reduce ICL */ if (!is_client_vote_enabled(chg->usb_icl_votable, WEAK_CHARGER_VOTER)) { smblite_lib_err(chg, "Weak charger detected: voting %dmA ICL\n", chg->weak_chg_icl_ua / 1000); vote(chg->usb_icl_votable, WEAK_CHARGER_VOTER, true, chg->weak_chg_icl_ua); /* * reset storm data and set the storm threshold * to 3 for reverse boost detection. */ update_storm_count(wdata, BOOST_BACK_STORM_COUNT); } else { smblite_lib_err(chg, "Reverse boost detected: voting 0mA to suspend input\n"); vote(chg->usb_icl_votable, BOOST_BACK_VOTER, true, 0); vote(chg->awake_votable, BOOST_BACK_VOTER, true, 0); /* * Remove the boost-back vote after a delay, to avoid * permanently suspending the input if the boost-back * condition is unintentionally hit. */ schedule_delayed_work(&chg->bb_removal_work, msecs_to_jiffies(BOOST_BACK_UNVOTE_DELAY_MS)); } } return IRQ_HANDLED; } irqreturn_t smblite_wdog_bark_irq_handler(int irq, void *data) { struct smb_irq_data *irq_data = data; struct smb_charger *chg = irq_data->parent_data; int rc; smblite_lib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name); rc = smblite_lib_write(chg, BARK_BITE_WDOG_PET_REG(chg->base), BARK_BITE_WDOG_PET_BIT); if (rc < 0) smblite_lib_err(chg, "Couldn't pet the dog rc=%d\n", rc); power_supply_changed(chg->batt_psy); return IRQ_HANDLED; } /* * triggered when DIE temperature across * either of the _REG_L, _REG_H, _RST, or _SHDN thresholds */ #define THERM_REGULATION_DELAY_MS 1000 #define THERM_REGULATION_STEP_UA 100000 irqreturn_t smblite_temp_change_irq_handler(int irq, void *data) { struct smb_irq_data *irq_data = data; struct smb_charger *chg = irq_data->parent_data; smblite_lib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name); vote(chg->awake_votable, SW_THERM_REGULATION_VOTER, true, 0); cancel_delayed_work_sync(&chg->thermal_regulation_work); schedule_delayed_work(&chg->thermal_regulation_work, msecs_to_jiffies(THERM_REGULATION_DELAY_MS)); return IRQ_HANDLED; } #define USB_OV_DBC_PERIOD_MS 1000 irqreturn_t smblite_usbin_ov_irq_handler(int irq, void *data) { struct smb_irq_data *irq_data = data; struct smb_charger *chg = irq_data->parent_data; smblite_lib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name); return IRQ_HANDLED; } irqreturn_t smblite_usb_id_irq_handler(int irq, void *data) { struct smb_charger *chg = data; bool id_state; id_state = gpio_get_value(chg->usb_id_gpio); smblite_lib_dbg(chg, PR_INTERRUPT, "IRQ: %s, id_state=%d\n", "usb-id-irq", id_state); if (id_state) { /*otg cable removed */ if (chg->otg_present) { if (chg->typec_port) { typec_set_data_role(chg->typec_port, TYPEC_DEVICE); typec_set_pwr_role(chg->typec_port, TYPEC_SINK); typec_partner_unregister(chg); } } } else if (chg->typec_port) { /*otg cable inserted */ typec_partner_register(chg); typec_set_data_role(chg->typec_port, TYPEC_HOST); typec_set_pwr_role(chg->typec_port, TYPEC_SOURCE); } smblite_lib_notify_usb_host(chg, !id_state); return IRQ_HANDLED; } irqreturn_t smblite_boost_mode_sw_en_irq_handler(int irq, void *data) { struct smb_irq_data *irq_data = data; struct smb_charger *chg = irq_data->parent_data; union power_supply_propval pval = {0, }; bool is_qc = false, boost_enabled = is_boost_en(chg); u8 apsd_status = 0; int rc = 0; rc = smblite_lib_get_prop_usb_present(chg, &pval); if (rc < 0) smblite_lib_dbg(chg, PR_MISC, "Couldn't get USB preset status rc=%d\n", rc); /* Try to restore VBUS to MAX(6V) once boost is disabled and USB is present. */ if (!boost_enabled && pval.intval) { rc = smblite_lib_read(chg, APSD_RESULT_STATUS_REG(chg->base), &apsd_status); if (rc < 0) smblite_lib_err(chg, "Couldn't read APSD_RESULT_STATUS rc=%d\n", rc); /* Restore vbus to MAX(6V) only if QC adapter is connected */ if (apsd_status & QC_3P0_BIT) { is_qc = true; /* wait for 100ms to move from boosti -> buck mode. */ msleep(100); smblite_lib_hvdcp3_force_max_vbus(chg); } } smblite_lib_dbg(chg, PR_INTERRUPT, "IRQ: %s, BOOST_EN=%s, usb_present=%d, qc_adapter=%s\n", irq_data->name, (boost_enabled ? "True" : "False"), pval.intval, (is_qc ? "True" : "False")); return IRQ_HANDLED; } /*************** * Work Queues * ***************/ static void smblite_lib_pr_swap_detach_work(struct work_struct *work) { struct smb_charger *chg = container_of(work, struct smb_charger, pr_swap_detach_work.work); int rc; u8 stat; rc = smblite_lib_read(chg, TYPE_C_STATE_MACHINE_STATUS_REG(chg->base), &stat); if (rc < 0) { smblite_lib_err(chg, "Couldn't read STATE_MACHINE_STS rc=%d\n", rc); goto out; } smblite_lib_dbg(chg, PR_REGISTER, "STATE_MACHINE_STS %#x\n", stat); if (!(stat & TYPEC_ATTACH_DETACH_STATE_BIT)) typec_partner_unregister(chg); out: vote(chg->awake_votable, DETACH_DETECT_VOTER, false, 0); } static void bms_update_work(struct work_struct *work) { struct smb_charger *chg = container_of(work, struct smb_charger, bms_update_work); struct iio_channel **qg_list; int rc; if (IS_ERR(chg->iio_chan_list_qg)) return; if (!chg->iio_chan_list_qg) { qg_list = get_ext_channels(chg->dev, smblite_lib_qg_ext_iio_chan, ARRAY_SIZE(smblite_lib_qg_ext_iio_chan)); if (IS_ERR(qg_list)) { rc = PTR_ERR(qg_list); if (rc != -EPROBE_DEFER) { dev_err(chg->dev, "Failed to get channels, %d\n", rc); chg->iio_chan_list_qg = ERR_PTR(-EINVAL); } return; } chg->iio_chan_list_qg = qg_list; } smblite_lib_suspend_on_debug_battery(chg); if (chg->batt_psy) power_supply_changed(chg->batt_psy); } static void smblite_lib_icl_change_work(struct work_struct *work) { struct smb_charger *chg = container_of(work, struct smb_charger, icl_change_work.work); int rc, settled_ua; rc = smblite_lib_get_charge_param(chg, &chg->param.icl_stat, &settled_ua); if (rc < 0) { smblite_lib_err(chg, "Couldn't get ICL status rc=%d\n", rc); return; } power_supply_changed(chg->batt_psy); smblite_lib_dbg(chg, PR_INTERRUPT, "icl_settled=%d\n", settled_ua); } static void smblite_lib_pl_enable_work(struct work_struct *work) { struct smb_charger *chg = container_of(work, struct smb_charger, pl_enable_work.work); smblite_lib_dbg(chg, PR_PARALLEL, "timer expired, enabling parallel\n"); vote(chg->pl_disable_votable, PL_DELAY_VOTER, false, 0); vote(chg->awake_votable, PL_DELAY_VOTER, false, 0); } static void smblite_lib_thermal_regulation_work(struct work_struct *work) { struct smb_charger *chg = container_of(work, struct smb_charger, thermal_regulation_work.work); int rc = 0, icl_ua = 0, input_present = 0; u8 stat = 0; if (!chg->usb_icl_votable) goto exit; rc = smblite_lib_is_input_present(chg, &input_present); if (rc < 0) { smblite_lib_err(chg, "Couldn't read input status rc=%d\n", rc); goto exit; } if (input_present == INPUT_NOT_PRESENT) { vote(chg->usb_icl_votable, SW_THERM_REGULATION_VOTER, false, 0); goto exit; } rc = smblite_lib_read(chg, DIE_TEMP_STATUS_REG(chg->base), &stat); if (rc < 0) { smblite_lib_err(chg, "Couldn't read DIE_TEMP_STATUS_REG, rc=%d\n", rc); goto reschedule; } icl_ua = get_effective_result(chg->usb_icl_votable); if (stat & DIE_TEMP_RST_BIT) { vote(chg->usb_icl_votable, SW_THERM_REGULATION_VOTER, true, USBIN_500UA); icl_ua = USBIN_500UA; goto exit; } if (stat & DIE_TEMP_UB_BIT) { /* Check if we reached minimum ICL limit */ if (icl_ua < USBIN_500UA + THERM_REGULATION_STEP_UA) goto exit; /* Decrement ICL by one step */ icl_ua -= THERM_REGULATION_STEP_UA; vote(chg->usb_icl_votable, SW_THERM_REGULATION_VOTER, true, icl_ua); goto reschedule; } /* check if DIE_TEMP is below LB */ if (!(stat & DIE_TEMP_MASK)) { /* * Check if we need further increments: * If thermal is still effective client then work can continue * with increment otherwise if other voter has voted a lower * ICL then remove vote and exit work. */ if (!strcmp(get_effective_client(chg->usb_icl_votable), SW_THERM_REGULATION_VOTER)) { icl_ua += THERM_REGULATION_STEP_UA; vote(chg->usb_icl_votable, SW_THERM_REGULATION_VOTER, true, icl_ua); goto reschedule; } } exit: smblite_lib_dbg(chg, PR_MISC, "exiting DIE_TEMP regulation work DIE_TEMP_STATUS=%x icl=%duA\n", stat, icl_ua); vote(chg->awake_votable, SW_THERM_REGULATION_VOTER, false, 0); return; reschedule: smblite_lib_dbg(chg, PR_MISC, "rescheduling DIE_TEMP regulation work DIE_TEMP_STATUS=%x icl=%duA\n", stat, icl_ua); schedule_delayed_work(&chg->thermal_regulation_work, msecs_to_jiffies(THERM_REGULATION_DELAY_MS)); } #define SOFT_JEITA_HYSTERESIS_OFFSET 0x200 static void jeita_update_work(struct work_struct *work) { struct smb_charger *chg = container_of(work, struct smb_charger, jeita_update_work); struct device_node *node = chg->dev->of_node; struct device_node *batt_node, *pnode; union power_supply_propval val; int rc, tmp[2], max_fcc_ma, max_fv_uv; u32 jeita_hard_thresholds[2]; u16 addr; u8 buff[2]; batt_node = of_find_node_by_name(node, "qcom,battery-data"); if (!batt_node) { smblite_lib_err(chg, "Batterydata not available\n"); goto out; } /* if BMS is not ready and remote FG does not exist, defer the work */ if ((IS_ERR_OR_NULL(chg->iio_chan_list_qg)) && (!chg->is_fg_remote)) return; rc = smblite_lib_get_prop_from_bms(chg, SMB5_QG_RESISTANCE_ID, &val.intval); if (rc < 0) { smblite_lib_err(chg, "Couldn't to get batt-id rc=%d\n", rc); goto out; } /* if BMS hasn't read out the batt_id yet, defer the work */ if (val.intval <= 0) return; pnode = of_batterydata_get_best_profile(batt_node, val.intval / 1000, NULL); if (IS_ERR(pnode)) { rc = PTR_ERR(pnode); smblite_lib_err(chg, "Couldn't to detect valid battery profile %d\n", rc); goto out; } rc = of_property_read_u32_array(pnode, "qcom,jeita-hard-thresholds", jeita_hard_thresholds, 2); if (!rc) { rc = smblite_lib_update_jeita(chg, jeita_hard_thresholds, JEITA_HARD); if (rc < 0) { smblite_lib_err(chg, "Couldn't configure Hard Jeita rc=%d\n", rc); goto out; } } rc = of_property_read_u32_array(pnode, "qcom,jeita-soft-thresholds", chg->jeita_soft_thlds, 2); if (!rc) { rc = smblite_lib_update_jeita(chg, chg->jeita_soft_thlds, JEITA_SOFT); if (rc < 0) { smblite_lib_err(chg, "Couldn't configure Soft Jeita rc=%d\n", rc); goto out; } rc = of_property_read_u32_array(pnode, "qcom,jeita-soft-hys-thresholds", chg->jeita_soft_hys_thlds, 2); if (rc < 0) { smblite_lib_err(chg, "Couldn't get Soft Jeita hysteresis thresholds rc=%d\n", rc); goto out; } } else { /* Populate the jeita-soft-thresholds */ addr = CHGR_JEITA_COOL_THRESHOLD_REG(chg->base); rc = smblite_lib_batch_read(chg, addr, buff, 2); if (rc < 0) { pr_err("Couldn't to read 0x%4X, rc=%d\n", addr, rc); goto out; } chg->jeita_soft_thlds[0] = buff[1] | buff[0] << 8; rc = smblite_lib_batch_read(chg, addr + 2, buff, 2); if (rc < 0) { pr_err("Couldn't to read 0x%4X, rc=%d\n", addr + 2, rc); goto out; } chg->jeita_soft_thlds[1] = buff[1] | buff[0] << 8; /* * Update the soft jeita hysteresis 2 DegC less for warm and * 2 DegC more for cool than the soft jeita thresholds to avoid * overwriting the registers with invalid values. */ chg->jeita_soft_hys_thlds[1] = chg->jeita_soft_thlds[0] - SOFT_JEITA_HYSTERESIS_OFFSET; chg->jeita_soft_hys_thlds[0] = chg->jeita_soft_thlds[1] + SOFT_JEITA_HYSTERESIS_OFFSET; } chg->jeita_soft_fcc[0] = chg->jeita_soft_fcc[1] = -EINVAL; chg->jeita_soft_fv[0] = chg->jeita_soft_fv[1] = -EINVAL; max_fcc_ma = max_fv_uv = -EINVAL; of_property_read_u32(pnode, "qcom,fastchg-current-ma", &max_fcc_ma); of_property_read_u32(pnode, "qcom,max-voltage-uv", &max_fv_uv); if (max_fcc_ma <= 0 || max_fv_uv <= 0) { smblite_lib_err(chg, "Incorrect fastchg-current-ma or max-voltage-uv\n"); goto out; } rc = of_property_read_u32_array(pnode, "qcom,jeita-soft-fcc-ua", tmp, 2); if (rc < 0) { smblite_lib_err(chg, "Couldn't get fcc values for soft JEITA rc=%d\n", rc); goto out; } max_fcc_ma *= 1000; if (tmp[0] > max_fcc_ma || tmp[1] > max_fcc_ma) { smblite_lib_err(chg, "Incorrect FCC value [%d %d] max: %d\n", tmp[0], tmp[1], max_fcc_ma); goto out; } chg->jeita_soft_fcc[0] = tmp[0]; chg->jeita_soft_fcc[1] = tmp[1]; rc = of_property_read_u32_array(pnode, "qcom,jeita-soft-fv-uv", tmp, 2); if (rc < 0) { smblite_lib_err(chg, "Couldn't get fv values for soft JEITA rc=%d\n", rc); goto out; } if (tmp[0] > max_fv_uv || tmp[1] > max_fv_uv) { smblite_lib_err(chg, "Incorrect FV value [%d %d] max: %d\n", tmp[0], tmp[1], max_fv_uv); goto out; } chg->jeita_soft_fv[0] = tmp[0]; chg->jeita_soft_fv[1] = tmp[1]; rc = smblite_lib_soft_jeita_arb_wa(chg); if (rc < 0) { smblite_lib_err(chg, "Couldn't fix soft jeita arb rc=%d\n", rc); goto out; } chg->jeita_configured = JEITA_CFG_COMPLETE; return; out: chg->jeita_configured = JEITA_CFG_FAILURE; } static int smblite_lib_create_votables(struct smb_charger *chg) { int rc = 0; chg->fcc_votable = find_votable("FCC"); if (chg->fcc_votable == NULL) { rc = -EINVAL; smblite_lib_err(chg, "Couldn't find FCC votable rc=%d\n", rc); return rc; } chg->fcc_main_votable = find_votable("FCC_MAIN"); if (chg->fcc_main_votable == NULL) { rc = -EINVAL; smblite_lib_err(chg, "Couldn't find FCC Main votable rc=%d\n", rc); return rc; } chg->fv_votable = find_votable("FV"); if (chg->fv_votable == NULL) { rc = -EINVAL; smblite_lib_err(chg, "Couldn't find FV votable rc=%d\n", rc); return rc; } chg->usb_icl_votable = find_votable("USB_ICL"); if (chg->usb_icl_votable == NULL) { rc = -EINVAL; smblite_lib_err(chg, "Couldn't find USB_ICL votable rc=%d\n", rc); return rc; } chg->pl_disable_votable = find_votable("PL_DISABLE"); if (chg->pl_disable_votable == NULL) { rc = -EINVAL; smblite_lib_err(chg, "Couldn't find votable PL_DISABLE rc=%d\n", rc); return rc; } chg->pl_enable_votable_indirect = find_votable("PL_ENABLE_INDIRECT"); if (chg->pl_enable_votable_indirect == NULL) { rc = -EINVAL; smblite_lib_err(chg, "Couldn't find votable PL_ENABLE_INDIRECT rc=%d\n", rc); return rc; } vote(chg->pl_disable_votable, PL_DELAY_VOTER, true, 0); chg->awake_votable = create_votable("AWAKE", VOTE_SET_ANY, smblite_lib_awake_vote_callback, chg); if (IS_ERR(chg->awake_votable)) { rc = PTR_ERR(chg->awake_votable); chg->awake_votable = NULL; return rc; } chg->chg_disable_votable = create_votable("CHG_DISABLE", VOTE_SET_ANY, smblite_lib_chg_disable_vote_callback, chg); if (IS_ERR(chg->chg_disable_votable)) { rc = PTR_ERR(chg->chg_disable_votable); chg->chg_disable_votable = NULL; return rc; } chg->icl_irq_disable_votable = create_votable("USB_ICL_IRQ_DISABLE", VOTE_SET_ANY, smblite_lib_icl_irq_disable_vote_callback, chg); if (IS_ERR(chg->icl_irq_disable_votable)) { rc = PTR_ERR(chg->icl_irq_disable_votable); chg->icl_irq_disable_votable = NULL; return rc; } chg->temp_change_irq_disable_votable = create_votable( "TEMP_CHANGE_IRQ_DISABLE", VOTE_SET_ANY, smblite_lib_temp_change_irq_disable_vote_callback, chg); if (IS_ERR(chg->temp_change_irq_disable_votable)) { rc = PTR_ERR(chg->temp_change_irq_disable_votable); chg->temp_change_irq_disable_votable = NULL; return rc; } return rc; } static void smblite_lib_destroy_votables(struct smb_charger *chg) { if (chg->usb_icl_votable) destroy_votable(chg->usb_icl_votable); if (chg->awake_votable) destroy_votable(chg->awake_votable); if (chg->chg_disable_votable) destroy_votable(chg->chg_disable_votable); } static void smblite_lib_iio_deinit(struct smb_charger *chg) { if (!IS_ERR_OR_NULL(chg->iio.usbin_v_chan)) iio_channel_release(chg->iio.usbin_v_chan); if (!IS_ERR_OR_NULL(chg->iio.temp_chan)) iio_channel_release(chg->iio.temp_chan); } int smblite_lib_init(struct smb_charger *chg) { int rc = 0; struct iio_channel **iio_list; struct smblite_remote_bms *remote_bms; mutex_init(&chg->dpdm_lock); mutex_init(&chg->dpdm_pulse_lock); INIT_WORK(&chg->bms_update_work, bms_update_work); INIT_WORK(&chg->jeita_update_work, jeita_update_work); INIT_DELAYED_WORK(&chg->icl_change_work, smblite_lib_icl_change_work); INIT_DELAYED_WORK(&chg->pl_enable_work, smblite_lib_pl_enable_work); INIT_DELAYED_WORK(&chg->bb_removal_work, smblite_lib_bb_removal_work); INIT_DELAYED_WORK(&chg->thermal_regulation_work, smblite_lib_thermal_regulation_work); INIT_DELAYED_WORK(&chg->role_reversal_check, smblite_lib_typec_role_check_work); INIT_DELAYED_WORK(&chg->pr_swap_detach_work, smblite_lib_pr_swap_detach_work); chg->fake_capacity = -EINVAL; chg->fake_batt_status = -EINVAL; chg->sink_src_mode = UNATTACHED_MODE; chg->jeita_configured = false; chg->dr_mode = TYPEC_PORT_DRP; chg->flash_active = false; chg->input_current_limited = -EINVAL; switch (chg->mode) { case PARALLEL_MASTER: rc = qcom_batt_init(chg->dev, &chg->chg_param); if (rc < 0) { smblite_lib_err(chg, "Couldn't init qcom_batt_init rc=%d\n", rc); return rc; } rc = qcom_step_chg_init(chg->dev, true, true, false, chg->iio_chans); if (rc < 0) { smblite_lib_err(chg, "Couldn't init qcom_step_chg_init rc=%d\n", rc); return rc; } rc = smblite_lib_create_votables(chg); if (rc < 0) { smblite_lib_err(chg, "Couldn't create votables rc=%d\n", rc); return rc; } if (chg->is_fg_remote) { remote_bms = &chg->remote_bms; remote_bms->dev = chg->dev; remote_bms->iio_read = chg->chg_param.iio_read; remote_bms->iio_write = chg->chg_param.iio_write; rc = remote_bms_init(remote_bms); if (rc < 0) { smblite_lib_err(chg, "Couldn't initialize remote bms rc=%d\n", rc); return rc; } } else { iio_list = get_ext_channels(chg->dev, smblite_lib_qg_ext_iio_chan, ARRAY_SIZE(smblite_lib_qg_ext_iio_chan)); if (!IS_ERR(iio_list)) chg->iio_chan_list_qg = iio_list; } rc = smblite_lib_register_notifier(chg); if (rc < 0) { smblite_lib_err(chg, "Couldn't register notifier rc=%d\n", rc); return rc; } break; case PARALLEL_SLAVE: break; default: smblite_lib_err(chg, "Unsupported mode %d\n", chg->mode); return -EINVAL; } return rc; } int smblite_lib_deinit(struct smb_charger *chg) { switch (chg->mode) { case PARALLEL_MASTER: cancel_work_sync(&chg->bms_update_work); cancel_work_sync(&chg->jeita_update_work); cancel_delayed_work_sync(&chg->icl_change_work); cancel_delayed_work_sync(&chg->pl_enable_work); cancel_delayed_work_sync(&chg->bb_removal_work); cancel_delayed_work_sync(&chg->thermal_regulation_work); cancel_delayed_work_sync(&chg->role_reversal_check); cancel_delayed_work_sync(&chg->pr_swap_detach_work); power_supply_unreg_notifier(&chg->nb); remote_bms_deinit(); smblite_lib_destroy_votables(chg); qcom_step_chg_deinit(); qcom_batt_deinit(); break; case PARALLEL_SLAVE: break; default: smblite_lib_err(chg, "Unsupported mode %d\n", chg->mode); return -EINVAL; } smblite_lib_iio_deinit(chg); return 0; }