// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2018-2021 The Linux Foundation. All rights reserved. * Copyright (c) 2022-2023, Qualcomm Innovation Center, Inc. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "smb5-lib.h" #include "smb5-reg.h" #include "schgm-flash.h" #include "step-chg-jeita.h" #include "storm-watch.h" #include "smb5-iio.h" #include "battery-profile-loader.h" #define smblib_err(chg, fmt, ...) \ pr_err("%s: %s: " fmt, chg->name, \ __func__, ##__VA_ARGS__) \ #define smblib_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 val); static int smblib_get_prop_typec_mode(struct smb_charger *chg); int smblib_read(struct smb_charger *chg, u16 addr, u8 *val) { unsigned int value; int rc = 0; rc = regmap_read(chg->regmap, addr, &value); if (rc >= 0) *val = (u8)value; return rc; } int smblib_batch_read(struct smb_charger *chg, u16 addr, u8 *val, int count) { return regmap_bulk_read(chg->regmap, addr, val, count); } int smblib_write(struct smb_charger *chg, u16 addr, u8 val) { return regmap_write(chg->regmap, addr, val); } int smblib_batch_write(struct smb_charger *chg, u16 addr, u8 *val, int count) { return regmap_bulk_write(chg->regmap, addr, val, count); } int smblib_masked_write(struct smb_charger *chg, u16 addr, u8 mask, u8 val) { return regmap_update_bits(chg->regmap, addr, mask, val); } int smblib_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 = devm_iio_channel_get(chg->dev, propname); if (IS_ERR(*chan)) { rc = PTR_ERR(*chan); if (rc != -EPROBE_DEFER) smblib_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 static int smblib_read_iio_channel(struct smb_charger *chg, struct iio_channel *chan, int div, int *data) { int rc = 0; *data = -ENODATA; if (chan) { rc = iio_read_channel_processed(chan, data); if (rc < 0) { smblib_err(chg, "Error in reading IIO channel data, rc=%d\n", rc); return rc; } if (div != 0) *data /= div; } return rc; } static int smblib_get_jeita_cc_delta(struct smb_charger *chg, int *cc_delta_ua) { int rc, cc_minus_ua; u8 stat; rc = smblib_read(chg, BATTERY_CHARGER_STATUS_7_REG, &stat); if (rc < 0) { smblib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_2 rc=%d\n", rc); return rc; } if (stat & BAT_TEMP_STATUS_HOT_SOFT_BIT) { rc = smblib_get_charge_param(chg, &chg->param.jeita_cc_comp_hot, &cc_minus_ua); if (rc < 0) { smblib_err(chg, "Couldn't get jeita cc minus rc=%d\n", rc); return rc; } } else if (stat & BAT_TEMP_STATUS_COLD_SOFT_BIT) { rc = smblib_get_charge_param(chg, &chg->param.jeita_cc_comp_cold, &cc_minus_ua); if (rc < 0) { smblib_err(chg, "Couldn't get jeita cc minus rc=%d\n", rc); return rc; } } else { cc_minus_ua = 0; } *cc_delta_ua = -cc_minus_ua; return 0; } int smblib_icl_override(struct smb_charger *chg, enum icl_override_mode mode) { int rc; u8 usb51_mode, icl_override, apsd_override; switch (mode) { case SW_OVERRIDE_USB51_MODE: usb51_mode = 0; icl_override = ICL_OVERRIDE_BIT; apsd_override = 0; break; case SW_OVERRIDE_HC_MODE: usb51_mode = USBIN_MODE_CHG_BIT; icl_override = 0; apsd_override = ICL_OVERRIDE_AFTER_APSD_BIT; break; case HW_AUTO_MODE: default: usb51_mode = USBIN_MODE_CHG_BIT; icl_override = 0; apsd_override = 0; break; } rc = smblib_masked_write(chg, USBIN_ICL_OPTIONS_REG, USBIN_MODE_CHG_BIT, usb51_mode); if (rc < 0) { smblib_err(chg, "Couldn't set USBIN_ICL_OPTIONS rc=%d\n", rc); return rc; } rc = smblib_masked_write(chg, CMD_ICL_OVERRIDE_REG, ICL_OVERRIDE_BIT, icl_override); if (rc < 0) { smblib_err(chg, "Couldn't override ICL rc=%d\n", rc); return rc; } rc = smblib_masked_write(chg, USBIN_LOAD_CFG_REG, ICL_OVERRIDE_AFTER_APSD_BIT, apsd_override); if (rc < 0) { smblib_err(chg, "Couldn't override ICL_AFTER_APSD rc=%d\n", rc); return rc; } return rc; } /* * This function does smb_en pin access, which is lock protected. * It should be called with smb_lock held. */ static int smblib_select_sec_charger_locked(struct smb_charger *chg, int sec_chg) { int rc = 0; switch (sec_chg) { case QTI_POWER_SUPPLY_CHARGER_SEC_CP: vote(chg->pl_disable_votable, PL_SMB_EN_VOTER, true, 0); /* select Charge Pump instead of slave charger */ rc = smblib_masked_write(chg, MISC_SMB_CFG_REG, SMB_EN_SEL_BIT, SMB_EN_SEL_BIT); if (rc < 0) { dev_err(chg->dev, "Couldn't select SMB charger rc=%d\n", rc); return rc; } /* Enable Charge Pump, under HW control */ rc = smblib_masked_write(chg, MISC_SMB_EN_CMD_REG, EN_CP_CMD_BIT, EN_CP_CMD_BIT); if (rc < 0) { dev_err(chg->dev, "Couldn't enable SMB charger rc=%d\n", rc); return rc; } vote(chg->smb_override_votable, PL_SMB_EN_VOTER, false, 0); break; case QTI_POWER_SUPPLY_CHARGER_SEC_PL: /* select slave charger instead of Charge Pump */ rc = smblib_masked_write(chg, MISC_SMB_CFG_REG, SMB_EN_SEL_BIT, 0); if (rc < 0) { dev_err(chg->dev, "Couldn't select SMB charger rc=%d\n", rc); return rc; } /* Enable slave charger, under HW control */ rc = smblib_masked_write(chg, MISC_SMB_EN_CMD_REG, EN_STAT_CMD_BIT, EN_STAT_CMD_BIT); if (rc < 0) { dev_err(chg->dev, "Couldn't enable SMB charger rc=%d\n", rc); return rc; } vote(chg->smb_override_votable, PL_SMB_EN_VOTER, false, 0); vote(chg->pl_disable_votable, PL_SMB_EN_VOTER, false, 0); break; case QTI_POWER_SUPPLY_CHARGER_SEC_NONE: default: vote(chg->pl_disable_votable, PL_SMB_EN_VOTER, true, 0); /* SW override, disabling secondary charger(s) */ vote(chg->smb_override_votable, PL_SMB_EN_VOTER, true, 0); break; } return rc; } static int smblib_select_sec_charger(struct smb_charger *chg, int sec_chg, int reason, bool toggle) { int rc; mutex_lock(&chg->smb_lock); if (toggle && sec_chg == QTI_POWER_SUPPLY_CHARGER_SEC_CP) { rc = smblib_select_sec_charger_locked(chg, QTI_POWER_SUPPLY_CHARGER_SEC_NONE); if (rc < 0) { dev_err(chg->dev, "Couldn't disable secondary charger rc=%d\n", rc); goto unlock_out; } /* * A minimum of 20us delay is expected before switching on STAT * pin. */ usleep_range(20, 30); } rc = smblib_select_sec_charger_locked(chg, sec_chg); if (rc < 0) { dev_err(chg->dev, "Couldn't switch secondary charger rc=%d\n", rc); goto unlock_out; } chg->sec_chg_selected = sec_chg; chg->cp_reason = reason; unlock_out: mutex_unlock(&chg->smb_lock); return rc; } static void smblib_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) { smblib_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) { val.intval = false; extcon_set_property(chg->extcon, id, EXTCON_PROP_USB_SS, val); } } static void smblib_notify_device_mode(struct smb_charger *chg, bool enable) { if (enable) smblib_notify_extcon_props(chg, EXTCON_USB); extcon_set_state_sync(chg->extcon, EXTCON_USB, enable); } static void smblib_notify_usb_host(struct smb_charger *chg, bool enable) { int rc = 0; if (enable) { smblib_dbg(chg, PR_OTG, "enabling VBUS in OTG mode\n"); rc = smblib_masked_write(chg, DCDC_CMD_OTG_REG, OTG_EN_BIT, OTG_EN_BIT); if (rc < 0) { smblib_err(chg, "Couldn't enable VBUS in OTG mode rc=%d\n", rc); return; } smblib_notify_extcon_props(chg, EXTCON_USB_HOST); } else { smblib_dbg(chg, PR_OTG, "disabling VBUS in OTG mode\n"); rc = smblib_masked_write(chg, DCDC_CMD_OTG_REG, OTG_EN_BIT, 0); if (rc < 0) { smblib_err(chg, "Couldn't disable VBUS in OTG mode rc=%d\n", rc); return; } } extcon_set_state_sync(chg->extcon, EXTCON_USB_HOST, enable); } /******************** * REGISTER GETTERS * ********************/ int smblib_get_charge_param(struct smb_charger *chg, struct smb_chg_param *param, int *val_u) { int rc = 0; u8 val_raw; rc = smblib_read(chg, param->reg, &val_raw); if (rc < 0) { smblib_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; smblib_dbg(chg, PR_REGISTER, "%s = %d (0x%02x)\n", param->name, *val_u, val_raw); return rc; } static int smblib_get_usb_suspend(struct smb_charger *chg, int *suspend) { int rc = 0; u8 temp; rc = smblib_read(chg, USBIN_CMD_IL_REG, &temp); if (rc < 0) { smblib_err(chg, "Couldn't read USBIN_CMD_IL rc=%d\n", rc); return rc; } *suspend = temp & USBIN_SUSPEND_BIT; return rc; } static const s16 therm_lookup_table[] = { /* Index -30C~85C, ADC raw code */ 0x6C92, 0x6C43, 0x6BF0, 0x6B98, 0x6B3A, 0x6AD8, 0x6A70, 0x6A03, 0x6990, 0x6916, 0x6897, 0x6811, 0x6785, 0x66F2, 0x6658, 0x65B7, 0x650F, 0x6460, 0x63AA, 0x62EC, 0x6226, 0x6159, 0x6084, 0x5FA8, 0x5EC3, 0x5DD8, 0x5CE4, 0x5BE9, 0x5AE7, 0x59DD, 0x58CD, 0x57B5, 0x5696, 0x5571, 0x5446, 0x5314, 0x51DD, 0x50A0, 0x4F5E, 0x4E17, 0x4CCC, 0x4B7D, 0x4A2A, 0x48D4, 0x477C, 0x4621, 0x44C4, 0x4365, 0x4206, 0x40A6, 0x3F45, 0x3DE6, 0x3C86, 0x3B28, 0x39CC, 0x3872, 0x3719, 0x35C4, 0x3471, 0x3322, 0x31D7, 0x308F, 0x2F4C, 0x2E0D, 0x2CD3, 0x2B9E, 0x2A6E, 0x2943, 0x281D, 0x26FE, 0x25E3, 0x24CF, 0x23C0, 0x22B8, 0x21B5, 0x20B8, 0x1FC2, 0x1ED1, 0x1DE6, 0x1D01, 0x1C22, 0x1B49, 0x1A75, 0x19A8, 0x18E0, 0x181D, 0x1761, 0x16A9, 0x15F7, 0x154A, 0x14A2, 0x13FF, 0x1361, 0x12C8, 0x1234, 0x11A4, 0x1119, 0x1091, 0x100F, 0x0F90, 0x0F15, 0x0E9E, 0x0E2B, 0x0DBC, 0x0D50, 0x0CE8, 0x0C83, 0x0C21, 0x0BC3, 0x0B67, 0x0B0F, 0x0AB9, 0x0A66, 0x0A16, 0x09C9, 0x097E, }; int smblib_get_thermal_threshold(struct smb_charger *chg, u16 addr, int *val) { u8 buff[2]; s16 temp; int rc = 0; int i, lower, upper; rc = smblib_batch_read(chg, addr, buff, 2); if (rc < 0) { pr_err("failed to write to 0x%04X, rc=%d\n", addr, rc); return rc; } temp = buff[1] | buff[0] << 8; lower = 0; upper = ARRAY_SIZE(therm_lookup_table) - 1; while (lower <= upper) { i = (upper + lower) / 2; if (therm_lookup_table[i] < temp) upper = i - 1; else if (therm_lookup_table[i] > temp) lower = i + 1; else break; } /* index 0 corresonds to -30C */ *val = (i - 30) * 10; return rc; } struct apsd_result { const char * const name; const u8 bit; const int val; }; enum { UNKNOWN, SDP, CDP, DCP, OCP, FLOAT, HVDCP2, HVDCP3, MAX_TYPES }; static const struct apsd_result smblib_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 }, [HVDCP2] = { .name = "HVDCP2", .bit = DCP_CHARGER_BIT | QC_2P0_BIT, .val = QTI_POWER_SUPPLY_TYPE_USB_HVDCP }, [HVDCP3] = { .name = "HVDCP3", .bit = DCP_CHARGER_BIT | QC_3P0_BIT, .val = QTI_POWER_SUPPLY_TYPE_USB_HVDCP_3, }, }; static const struct apsd_result *smblib_get_apsd_result(struct smb_charger *chg) { int rc, i; u8 apsd_stat, stat; const struct apsd_result *result = &smblib_apsd_results[UNKNOWN]; rc = smblib_read(chg, APSD_STATUS_REG, &apsd_stat); if (rc < 0) { smblib_err(chg, "Couldn't read APSD_STATUS rc=%d\n", rc); return result; } smblib_dbg(chg, PR_REGISTER, "APSD_STATUS = 0x%02x\n", apsd_stat); if (!(apsd_stat & APSD_DTC_STATUS_DONE_BIT)) return result; rc = smblib_read(chg, APSD_RESULT_STATUS_REG, &stat); if (rc < 0) { smblib_err(chg, "Couldn't read APSD_RESULT_STATUS rc=%d\n", rc); return result; } stat &= APSD_RESULT_STATUS_MASK; for (i = 0; i < ARRAY_SIZE(smblib_apsd_results); i++) { if (smblib_apsd_results[i].bit == stat) result = &smblib_apsd_results[i]; } if (apsd_stat & QC_CHARGER_BIT) { /* since its a qc_charger, either return HVDCP3 or HVDCP2 */ if (result != &smblib_apsd_results[HVDCP3]) result = &smblib_apsd_results[HVDCP2]; } return result; } #define INPUT_NOT_PRESENT 0 #define INPUT_PRESENT_USB BIT(1) #define INPUT_PRESENT_DC BIT(2) static int smblib_is_input_present(struct smb_charger *chg, int *present) { int rc; union power_supply_propval pval = {0, }; *present = INPUT_NOT_PRESENT; rc = smblib_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; rc = smblib_get_prop_dc_present(chg, &pval); if (rc < 0) { pr_err("Couldn't get dc presence status rc=%d\n", rc); return rc; } *present |= pval.intval ? INPUT_PRESENT_DC : INPUT_NOT_PRESENT; return 0; } #define AICL_RANGE2_MIN_MV 5600 #define AICL_RANGE2_STEP_DELTA_MV 200 #define AICL_RANGE2_OFFSET 16 int smblib_get_aicl_cont_threshold(struct smb_chg_param *param, u8 val_raw) { int base = param->min_u; u8 reg = val_raw; int step = param->step_u; if (val_raw >= AICL_RANGE2_OFFSET) { reg = val_raw - AICL_RANGE2_OFFSET; base = AICL_RANGE2_MIN_MV; step = AICL_RANGE2_STEP_DELTA_MV; } return base + (reg * step); } /******************** * REGISTER SETTERS * ********************/ static const struct buck_boost_freq chg_freq_list[] = { [0] = { .freq_khz = 2400, .val = 7, }, [1] = { .freq_khz = 2100, .val = 8, }, [2] = { .freq_khz = 1600, .val = 11, }, [3] = { .freq_khz = 1200, .val = 15, }, }; int smblib_set_chg_freq(struct smb_chg_param *param, int val_u, u8 *val_raw) { u8 i; if (val_u > param->max_u || val_u < param->min_u) return -EINVAL; /* Charger FSW is the configured freqency / 2 */ val_u *= 2; for (i = 0; i < ARRAY_SIZE(chg_freq_list); i++) { if (chg_freq_list[i].freq_khz == val_u) break; } if (i == ARRAY_SIZE(chg_freq_list)) { pr_err("Invalid frequency %d Hz\n", val_u / 2); return -EINVAL; } *val_raw = chg_freq_list[i].val; return 0; } static int smblib_set_opt_switcher_freq(struct smb_charger *chg, int fsw_khz) { int rc = 0; rc = smblib_set_charge_param(chg, &chg->param.freq_switcher, fsw_khz); if (rc < 0) dev_err(chg->dev, "Error in setting freq_buck rc=%d\n", rc); return rc; } int smblib_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) smblib_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 = smblib_write(chg, param->reg, val_raw); if (rc < 0) { smblib_err(chg, "%s: Couldn't write 0x%02x to 0x%04x rc=%d\n", param->name, val_raw, param->reg, rc); return rc; } smblib_dbg(chg, PR_REGISTER, "%s = %d (0x%02x)\n", param->name, val_u, val_raw); return rc; } static int smblib_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 = smblib_masked_write(chg, USBIN_CMD_IL_REG, USBIN_SUSPEND_BIT, suspend ? USBIN_SUSPEND_BIT : 0); if (rc < 0) smblib_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; } static int smblib_set_dc_suspend(struct smb_charger *chg, bool suspend) { int rc = 0; rc = smblib_masked_write(chg, DCIN_CMD_IL_REG, DCIN_SUSPEND_BIT, suspend ? DCIN_SUSPEND_BIT : 0); if (rc < 0) smblib_err(chg, "Couldn't write %s to DCIN_SUSPEND_BIT rc=%d\n", suspend ? "suspend" : "resume", rc); return rc; } static int smblib_usb_pd_adapter_allowance_override(struct smb_charger *chg, u8 allowed_voltage) { int rc = 0; if (chg->chg_param.smb_version == PMI632) return 0; rc = smblib_write(chg, USBIN_ADAPTER_ALLOW_OVERRIDE_REG, allowed_voltage); if (rc < 0) smblib_err(chg, "Couldn't write 0x%02x to USBIN_ADAPTER_ALLOW_OVERRIDE_REG rc=%d\n", allowed_voltage, rc); smblib_dbg(chg, PR_MISC, "set USBIN_ALLOW_OVERRIDE: %d\n", allowed_voltage); return rc; } #define MICRO_5V 5000000 #define MICRO_9V 9000000 #define MICRO_12V 12000000 static int smblib_set_usb_pd_fsw(struct smb_charger *chg, int voltage) { int rc = 0; if (voltage == MICRO_5V) rc = smblib_set_opt_switcher_freq(chg, chg->chg_freq.freq_5V); else if (voltage > MICRO_5V && voltage < MICRO_9V) rc = smblib_set_opt_switcher_freq(chg, chg->chg_freq.freq_6V_8V); else if (voltage >= MICRO_9V && voltage < MICRO_12V) rc = smblib_set_opt_switcher_freq(chg, chg->chg_freq.freq_9V); else if (voltage == MICRO_12V) rc = smblib_set_opt_switcher_freq(chg, chg->chg_freq.freq_12V); else { smblib_err(chg, "Couldn't set Fsw: invalid voltage %d\n", voltage); return -EINVAL; } return rc; } #define CONT_AICL_HEADROOM_MV 1000 #define AICL_THRESHOLD_MV_IN_CC 5000 static int smblib_set_usb_pd_allowed_voltage(struct smb_charger *chg, int min_allowed_uv, int max_allowed_uv) { int rc, aicl_threshold; u8 vbus_allowance; if (chg->chg_param.smb_version == PMI632) return 0; if (chg->pd_active == QTI_POWER_SUPPLY_PD_PPS_ACTIVE) { vbus_allowance = CONTINUOUS; } else if (min_allowed_uv == MICRO_5V && max_allowed_uv == MICRO_5V) { vbus_allowance = FORCE_5V; } else if (min_allowed_uv == MICRO_9V && max_allowed_uv == MICRO_9V) { vbus_allowance = FORCE_9V; } else if (min_allowed_uv == MICRO_12V && max_allowed_uv == MICRO_12V) { vbus_allowance = FORCE_12V; } else if (min_allowed_uv < MICRO_12V && max_allowed_uv <= MICRO_12V) { vbus_allowance = CONTINUOUS; } else { smblib_err(chg, "invalid allowed voltage [%d, %d]\n", min_allowed_uv, max_allowed_uv); return -EINVAL; } rc = smblib_usb_pd_adapter_allowance_override(chg, vbus_allowance); if (rc < 0) { smblib_err(chg, "set CONTINUOUS allowance failed, rc=%d\n", rc); return rc; } if (vbus_allowance != CONTINUOUS) return 0; aicl_threshold = min_allowed_uv / 1000 - CONT_AICL_HEADROOM_MV; if (chg->adapter_cc_mode) aicl_threshold = min(aicl_threshold, AICL_THRESHOLD_MV_IN_CC); rc = smblib_set_charge_param(chg, &chg->param.aicl_cont_threshold, aicl_threshold); if (rc < 0) { smblib_err(chg, "set CONT_AICL_THRESHOLD failed, rc=%d\n", rc); return rc; } return rc; } int smblib_set_aicl_cont_threshold(struct smb_chg_param *param, int val_u, u8 *val_raw) { int base = param->min_u; int offset = 0; int step = param->step_u; if (val_u > param->max_u) val_u = param->max_u; if (val_u < param->min_u) val_u = param->min_u; if (val_u >= AICL_RANGE2_MIN_MV) { base = AICL_RANGE2_MIN_MV; step = AICL_RANGE2_STEP_DELTA_MV; offset = AICL_RANGE2_OFFSET; } *val_raw = ((val_u - base) / step) + offset; return 0; } /******************** * HELPER FUNCTIONS * ********************/ /* CP channels */ static const char * const smblib_cp_ext_iio_chan[] = { [CP_PARALLEL_OUTPUT_MODE] = "cp_parallel_output_mode", [CP_MASTER_ENABLE] = "cp_enable", [CP_ILIM] = "cp_ilim", [CP_DIE_TEMP] = "cp_die_temp", }; /* SMB1355 channels */ static const char * const smblib_parallel_ext_iio_chan[] = { [SMB_CHARGER_TEMP] = "pl_charger_temp", [SMB_CHARGER_TEMP_MAX] = "pl_charger_temp_max", [SMB_SET_SHIP_MODE] = "pl_set_ship_mode", }; /* QG/FG channels */ static const char * const smblib_qg_ext_iio_chan[] = { [SMB5_QG_DEBUG_BATTERY] = "debug_battery", [SMB5_QG_CAPACITY] = "capacity", [SMB5_QG_REAL_CAPACITY] = "real_capacity", [SMB5_QG_CC_SOC] = "cc_soc", [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", }; static int smblib_read_iio_prop(struct smb_charger *chg, enum iio_type type, int iio_chan, int *val) { struct iio_channel *iio_chan_list; int rc; switch (type) { case QG: if (IS_ERR_OR_NULL(chg->iio_chan_list_qg)) return -ENODEV; iio_chan_list = chg->iio_chan_list_qg[iio_chan]; break; case CP: if (IS_ERR_OR_NULL(chg->iio_chan_list_cp)) return -ENODEV; iio_chan_list = chg->iio_chan_list_cp[iio_chan]; break; case SMB_PARALLEL: if (IS_ERR_OR_NULL(chg->iio_chan_list_smb_parallel)) return -ENODEV; iio_chan_list = chg->iio_chan_list_smb_parallel[iio_chan]; break; default: pr_err_ratelimited("iio_type %d is not supported\n", type); return -EINVAL; } rc = iio_read_channel_processed(iio_chan_list, val); return rc < 0 ? rc : 0; } static int smblib_write_iio_prop(struct smb_charger *chg, enum iio_type type, int iio_chan, int val) { struct iio_channel *iio_chan_list; switch (type) { case QG: if (IS_ERR_OR_NULL(chg->iio_chan_list_qg)) return -ENODEV; iio_chan_list = chg->iio_chan_list_qg[iio_chan]; break; case CP: if (IS_ERR_OR_NULL(chg->iio_chan_list_cp)) return -ENODEV; iio_chan_list = chg->iio_chan_list_cp[iio_chan]; break; case SMB_PARALLEL: if (IS_ERR_OR_NULL(chg->iio_chan_list_smb_parallel)) return -ENODEV; iio_chan_list = chg->iio_chan_list_smb_parallel[iio_chan]; break; default: pr_err_ratelimited("iio_type %d is not supported\n", type); return -EINVAL; } return iio_write_channel_raw(iio_chan_list, val); } static bool is_cp_available(struct smb_charger *chg) { int rc; struct iio_channel **iio_list; if (IS_ERR(chg->iio_chan_list_cp)) return false; if (!chg->iio_chan_list_cp) { iio_list = get_ext_channels(chg->dev, smblib_cp_ext_iio_chan, ARRAY_SIZE(smblib_cp_ext_iio_chan)); if (IS_ERR(iio_list)) { rc = PTR_ERR(iio_list); if (rc != -EPROBE_DEFER) { dev_err(chg->dev, "Failed to get channels, rc=%d\n", rc); chg->iio_chan_list_cp = ERR_PTR(-EINVAL); } return false; } chg->iio_chan_list_cp = iio_list; } return true; } static bool is_cp_topo_vbatt(struct smb_charger *chg) { int rc = 0, val; bool is_vbatt; if (!is_cp_available(chg)) return false; rc = smblib_read_iio_prop(chg, CP, CP_PARALLEL_OUTPUT_MODE, &val); if (rc < 0) { smblib_err(chg, "Couldn't get CP PARALLEL_OUTPUT_MODE rc=%d\n", rc); return false; } is_vbatt = (val == QTI_POWER_SUPPLY_PL_OUTPUT_VBAT); smblib_dbg(chg, PR_WLS, "%s\n", is_vbatt ? "true" : "false"); return is_vbatt; } #define CP_TO_MAIN_ICL_OFFSET_PC 10 int smblib_get_qc3_main_icl_offset(struct smb_charger *chg, int *offset_ua) { int rc = 0, val; /* * Apply ILIM offset to main charger's FCC if all of the following * conditions are met: * - HVDCP3 adapter with CP as parallel charger * - Output connection topology is VBAT */ if (!is_cp_topo_vbatt(chg) || chg->hvdcp3_standalone_config || ((chg->real_charger_type != QTI_POWER_SUPPLY_TYPE_USB_HVDCP_3) && chg->real_charger_type != QTI_POWER_SUPPLY_TYPE_USB_HVDCP_3P5)) return -EINVAL; rc = smblib_read_iio_prop(chg, CP, CP_MASTER_ENABLE, &val); if (rc < 0) { smblib_err(chg, "Couldn't get CP ENABLE rc=%d\n", rc); return rc; } if (!val) return -EINVAL; rc = smblib_read_iio_prop(chg, CP, CP_ILIM, &val); if (rc < 0) { smblib_err(chg, "Couldn't get CP ILIM rc=%d\n", rc); return rc; } *offset_ua = (val * CP_TO_MAIN_ICL_OFFSET_PC * 2) / 100; return 0; } int smblib_get_prop_from_bms(struct smb_charger *chg, int channel, int *val) { int rc; 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; } void smblib_apsd_enable(struct smb_charger *chg, bool enable) { int rc; rc = smblib_masked_write(chg, USBIN_OPTIONS_1_CFG_REG, BC1P2_SRC_DETECT_BIT, enable ? BC1P2_SRC_DETECT_BIT : 0); if (rc < 0) smblib_err(chg, "failed to write USBIN_OPTIONS_1_CFG rc=%d\n", rc); } void smblib_hvdcp_detect_enable(struct smb_charger *chg, bool enable) { int rc; u8 mask; mask = HVDCP_AUTH_ALG_EN_CFG_BIT | HVDCP_EN_BIT; rc = smblib_masked_write(chg, USBIN_OPTIONS_1_CFG_REG, mask, enable ? mask : 0); if (rc < 0) smblib_err(chg, "failed to write USBIN_OPTIONS_1_CFG rc=%d\n", rc); } static void smblib_hvdcp_detect_try_enable(struct smb_charger *chg, bool enable) { if (chg->hvdcp_disable || chg->pd_not_supported) return; smblib_hvdcp_detect_enable(chg, enable); } void smblib_hvdcp_hw_inov_enable(struct smb_charger *chg, bool enable) { int rc; rc = smblib_masked_write(chg, USBIN_OPTIONS_1_CFG_REG, HVDCP_AUTONOMOUS_MODE_EN_CFG_BIT, enable ? HVDCP_AUTONOMOUS_MODE_EN_CFG_BIT : 0); if (rc < 0) smblib_err(chg, "failed to write USBIN_OPTIONS_1_CFG rc=%d\n", rc); } void smblib_hvdcp_exit_config(struct smb_charger *chg) { u8 stat; int rc; rc = smblib_read(chg, APSD_RESULT_STATUS_REG, &stat); if (rc < 0) return; if (stat & (QC_3P0_BIT | QC_2P0_BIT)) { /* force HVDCP to 5V */ smblib_masked_write(chg, USBIN_OPTIONS_1_CFG_REG, HVDCP_AUTONOMOUS_MODE_EN_CFG_BIT, 0); smblib_write(chg, CMD_HVDCP_2_REG, FORCE_5V_BIT); /* rerun APSD */ smblib_masked_write(chg, CMD_APSD_REG, APSD_RERUN_BIT, APSD_RERUN_BIT); } } static int smblib_request_dpdm(struct smb_charger *chg, bool enable) { int rc = 0; if (chg->pr_swap_in_progress) 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); smblib_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) { smblib_dbg(chg, PR_MISC, "enabling DPDM regulator\n"); rc = regulator_enable(chg->dpdm_reg); if (rc < 0) smblib_err(chg, "Couldn't enable dpdm regulator rc=%d\n", rc); else chg->dpdm_enabled = true; } } else { if (chg->dpdm_reg && chg->dpdm_enabled) { smblib_dbg(chg, PR_MISC, "disabling DPDM regulator\n"); rc = regulator_disable(chg->dpdm_reg); if (rc < 0) smblib_err(chg, "Couldn't disable dpdm regulator rc=%d\n", rc); else chg->dpdm_enabled = false; } } mutex_unlock(&chg->dpdm_lock); return rc; } void smblib_rerun_apsd(struct smb_charger *chg) { int rc; smblib_dbg(chg, PR_MISC, "re-running APSD\n"); rc = smblib_masked_write(chg, CMD_APSD_REG, APSD_RERUN_BIT, APSD_RERUN_BIT); if (rc < 0) smblib_err(chg, "Couldn't re-run APSD rc=%d\n", rc); } static const struct apsd_result *smblib_update_usb_type(struct smb_charger *chg) { const struct apsd_result *apsd_result = smblib_get_apsd_result(chg); /* if PD is active, APSD is disabled so won't have a valid result */ if (chg->pd_active) { chg->real_charger_type = POWER_SUPPLY_TYPE_USB_PD; } else if (chg->qc3p5_detected) { chg->real_charger_type = QTI_POWER_SUPPLY_TYPE_USB_HVDCP_3P5; } else { /* * Update real charger type only if its not FLOAT * detected as SDP */ if (!(apsd_result->val == QTI_POWER_SUPPLY_TYPE_USB_FLOAT && chg->real_charger_type == POWER_SUPPLY_TYPE_USB)) chg->real_charger_type = apsd_result->val; } smblib_dbg(chg, PR_MISC, "APSD=%s PD=%d QC3P5=%d\n", apsd_result->name, chg->pd_active, chg->qc3p5_detected); return apsd_result; } static int smblib_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); if (chg->sec_pl_present && !chg->iio_chan_list_smb_parallel && !strcmp(psy->desc->name, "parallel")) schedule_work(&chg->pl_update_work); if (!strcmp(psy->desc->name, "charge_pump_master")) { pm_stay_awake(chg->dev); schedule_work(&chg->cp_status_change_work); } return NOTIFY_OK; } static int smblib_register_notifier(struct smb_charger *chg) { int rc; chg->nb.notifier_call = smblib_notifier_call; rc = power_supply_reg_notifier(&chg->nb); if (rc < 0) { smblib_err(chg, "Couldn't register psy notifier rc = %d\n", rc); return rc; } return 0; } static void smblib_uusb_removal(struct smb_charger *chg) { int rc; struct smb_irq_data *data; struct storm_watch *wdata; int sec_charger; sec_charger = chg->sec_pl_present ? QTI_POWER_SUPPLY_CHARGER_SEC_PL : QTI_POWER_SUPPLY_CHARGER_SEC_NONE; smblib_select_sec_charger(chg, sec_charger, QTI_POWER_SUPPLY_CP_NONE, false); 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_flash_active(chg) ? SDP_CURRENT_UA : SDP_100_MA); vote(chg->usb_icl_votable, SW_QC3_VOTER, false, 0); vote(chg->usb_icl_votable, HVDCP2_ICL_VOTER, false, 0); vote(chg->usb_icl_votable, CHG_TERMINATION_VOTER, false, 0); vote(chg->usb_icl_votable, THERMAL_THROTTLE_VOTER, false, 0); vote(chg->limited_irq_disable_votable, CHARGER_TYPE_VOTER, true, 0); vote(chg->hdc_irq_disable_votable, CHARGER_TYPE_VOTER, true, 0); vote(chg->hdc_irq_disable_votable, HDC_IRQ_VOTER, false, 0); /* Remove SW thermal regulation WA votes */ vote(chg->usb_icl_votable, SW_THERM_REGULATION_VOTER, false, 0); vote(chg->pl_disable_votable, SW_THERM_REGULATION_VOTER, false, 0); vote(chg->dc_suspend_votable, SW_THERM_REGULATION_VOTER, false, 0); if (chg->cp_disable_votable) vote(chg->cp_disable_votable, SW_THERM_REGULATION_VOTER, false, 0); /* reset USBOV votes and cancel work */ cancel_delayed_work_sync(&chg->usbov_dbc_work); vote(chg->awake_votable, USBOV_DBC_VOTER, false, 0); chg->dbc_usbov = false; chg->voltage_min_uv = MICRO_5V; chg->voltage_max_uv = MICRO_5V; chg->usbin_forced_max_uv = 0; chg->usb_icl_delta_ua = 0; chg->pulse_cnt = 0; chg->uusb_apsd_rerun_done = false; chg->chg_param.forced_main_fcc = 0; del_timer_sync(&chg->apsd_timer); chg->apsd_ext_timeout = false; /* write back the default FLOAT charger configuration */ rc = smblib_masked_write(chg, USBIN_OPTIONS_2_CFG_REG, (u8)FLOAT_OPTIONS_MASK, chg->float_cfg); if (rc < 0) smblib_err(chg, "Couldn't write float charger options rc=%d\n", rc); /* clear USB ICL vote for USB_PSY_VOTER */ rc = vote(chg->usb_icl_votable, USB_PSY_VOTER, false, 0); if (rc < 0) smblib_err(chg, "Couldn't un-vote for USB ICL rc=%d\n", rc); /* clear USB ICL vote for DCP_VOTER */ rc = vote(chg->usb_icl_votable, DCP_VOTER, false, 0); if (rc < 0) smblib_err(chg, "Couldn't un-vote DCP from USB ICL rc=%d\n", rc); /* * if non-compliant charger caused UV, restore original max pulses * and turn SUSPEND_ON_COLLAPSE_USBIN_BIT back on. */ if (chg->qc2_unsupported_voltage) { rc = smblib_masked_write(chg, HVDCP_PULSE_COUNT_MAX_REG, HVDCP_PULSE_COUNT_MAX_QC2_MASK, chg->qc2_max_pulses); if (rc < 0) smblib_err(chg, "Couldn't restore max pulses rc=%d\n", rc); if (!chg->disable_suspend_on_collapse) { rc = smblib_masked_write(chg, USBIN_AICL_OPTIONS_CFG_REG, SUSPEND_ON_COLLAPSE_USBIN_BIT, SUSPEND_ON_COLLAPSE_USBIN_BIT); if (rc < 0) smblib_err(chg, "Couldn't turn on SUSPEND_ON_COLLAPSE_USBIN_BIT rc=%d\n", rc); } chg->qc2_unsupported_voltage = QC2_COMPLIANT; } chg->qc3p5_detected = false; chg->qc3p5_detected_mw = 0; smblib_update_usb_type(chg); } void smblib_config_charger_on_debug_battery(struct smb_charger *chg) { int rc = 0, val; rc = smblib_get_prop_from_bms(chg, SMB5_QG_DEBUG_BATTERY, &val); if (rc < 0) { smblib_err(chg, "Couldn't get debug battery prop rc=%d\n", rc); return; } vote(chg->bat_temp_irq_disable_votable, DEBUG_BOARD_VOTER, val, 0); if (chg->suspend_input_on_debug_batt) { vote(chg->usb_icl_votable, DEBUG_BOARD_VOTER, val, 0); vote(chg->dc_suspend_votable, DEBUG_BOARD_VOTER, val, 0); if (val) pr_info("Input suspended: Fake battery\n"); } else { vote(chg->chg_disable_votable, DEBUG_BOARD_VOTER, val, 0); } } int smblib_rerun_apsd_if_required(struct smb_charger *chg) { union power_supply_propval val; int rc; rc = smblib_get_prop_usb_present(chg, &val); if (rc < 0) { smblib_err(chg, "Couldn't get usb present rc = %d\n", rc); return rc; } if (!val.intval) return 0; rc = smblib_request_dpdm(chg, true); if (rc < 0) smblib_err(chg, "Couldn't to enable DPDM rc=%d\n", rc); chg->uusb_apsd_rerun_done = true; smblib_rerun_apsd(chg); return 0; } static int smblib_get_pulse_cnt(struct smb_charger *chg, int *count) { *count = chg->pulse_cnt; return 0; } #define USBIN_25MA 25000 #define USBIN_100MA 100000 #define USBIN_150MA 150000 #define USBIN_500MA 500000 #define USBIN_900MA 900000 #define USBIN_1000MA 1000000 static int set_sdp_current(struct smb_charger *chg, int icl_ua) { int rc; u8 icl_options; const struct apsd_result *apsd_result = smblib_get_apsd_result(chg); /* power source is SDP */ switch (icl_ua) { case USBIN_100MA: /* USB 2.0 100mA */ icl_options = 0; break; case USBIN_150MA: /* USB 3.0 150mA */ icl_options = CFG_USB3P0_SEL_BIT; break; case USBIN_500MA: /* USB 2.0 500mA */ icl_options = USB51_MODE_BIT; break; case USBIN_900MA: /* USB 3.0 900mA */ icl_options = CFG_USB3P0_SEL_BIT | USB51_MODE_BIT; break; default: return -EINVAL; } if (chg->real_charger_type == POWER_SUPPLY_TYPE_USB && apsd_result->val == QTI_POWER_SUPPLY_TYPE_USB_FLOAT) { /* * change the float charger configuration to SDP, if this * is the case of SDP being detected as FLOAT */ rc = smblib_masked_write(chg, USBIN_OPTIONS_2_CFG_REG, FORCE_FLOAT_SDP_CFG_BIT, FORCE_FLOAT_SDP_CFG_BIT); if (rc < 0) { smblib_err(chg, "Couldn't set float ICL options rc=%d\n", rc); return rc; } } rc = smblib_masked_write(chg, USBIN_ICL_OPTIONS_REG, CFG_USB3P0_SEL_BIT | USB51_MODE_BIT, icl_options); if (rc < 0) { smblib_err(chg, "Couldn't set ICL options rc=%d\n", rc); return rc; } rc = smblib_icl_override(chg, SW_OVERRIDE_USB51_MODE); if (rc < 0) { smblib_err(chg, "Couldn't set ICL override rc=%d\n", rc); return rc; } return rc; } int smblib_set_icl_current(struct smb_charger *chg, 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_25MA); if (chg->chg_param.smb_version == PMI632) schgm_flash_torch_priority(chg, suspend ? TORCH_BOOST_MODE : TORCH_BUCK_MODE); /* Do not configure ICL from SW for DAM cables */ if (smblib_get_prop_typec_mode(chg) == QTI_POWER_SUPPLY_TYPEC_SINK_DEBUG_ACCESSORY) return 0; if (suspend) return smblib_set_usb_suspend(chg, true); 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) { smblib_err(chg, "Couldn't set SDP ICL rc=%d\n", rc); goto out; } } 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_500MA) { rc = set_sdp_current(chg, icl_ua); if (rc >= 0) goto unsuspend; } rc = smblib_set_charge_param(chg, &chg->param.usb_icl, icl_ua); if (rc < 0) { smblib_err(chg, "Couldn't set HC ICL rc=%d\n", rc); goto out; } icl_override = SW_OVERRIDE_HC_MODE; } set_mode: rc = smblib_icl_override(chg, icl_override); if (rc < 0) { smblib_err(chg, "Couldn't set ICL override rc=%d\n", rc); goto out; } unsuspend: /* unsuspend after configuring current and override */ rc = smblib_set_usb_suspend(chg, false); if (rc < 0) { smblib_err(chg, "Couldn't resume input rc=%d\n", rc); goto out; } /* Re-run AICL */ if (icl_override != SW_OVERRIDE_HC_MODE) rc = smblib_run_aicl(chg, RERUN_AICL); out: return rc; } int smblib_get_icl_current(struct smb_charger *chg, int *icl_ua) { int rc; rc = smblib_get_charge_param(chg, &chg->param.icl_max_stat, icl_ua); if (rc < 0) smblib_err(chg, "Couldn't get HC ICL rc=%d\n", rc); return rc; } int smblib_toggle_smb_en(struct smb_charger *chg, int toggle) { int rc = 0; if (!toggle) return rc; rc = smblib_select_sec_charger(chg, chg->sec_chg_selected, chg->cp_reason, true); return rc; } int smblib_get_irq_status(struct smb_charger *chg, int *val) { int rc; u8 reg; if (chg->wa_flags & SKIP_MISC_PBS_IRQ_WA) { *val = 0; return 0; } mutex_lock(&chg->irq_status_lock); /* Report and clear cached status */ *val = chg->irq_status; chg->irq_status = 0; /* get real time status of pulse skip irq */ rc = smblib_read(chg, MISC_PBS_RT_STS_REG, ®); if (rc < 0) smblib_err(chg, "Couldn't read MISC_PBS_RT_STS_REG rc=%d\n", rc); else *val |= (reg & PULSE_SKIP_IRQ_BIT); mutex_unlock(&chg->irq_status_lock); return rc; } /**************************** * uUSB Moisture Protection * ****************************/ #define MICRO_USB_DETECTION_ON_TIME_20_MS 0x08 #define MICRO_USB_DETECTION_PERIOD_X_100 0x03 #define U_USB_STATUS_WATER_PRESENT 0x00 static int smblib_set_moisture_protection(struct smb_charger *chg, bool enable) { int rc = 0; if (chg->moisture_present == enable) { smblib_dbg(chg, PR_MISC, "No change in moisture protection status\n"); return rc; } if (enable) { chg->moisture_present = true; /* Disable uUSB factory mode detection */ rc = smblib_masked_write(chg, TYPEC_U_USB_CFG_REG, EN_MICRO_USB_FACTORY_MODE_BIT, 0); if (rc < 0) { smblib_err(chg, "Couldn't disable uUSB factory mode detection rc=%d\n", rc); return rc; } /* Disable moisture detection and uUSB state change interrupt */ rc = smblib_masked_write(chg, TYPE_C_INTERRUPT_EN_CFG_2_REG, TYPEC_WATER_DETECTION_INT_EN_BIT | MICRO_USB_STATE_CHANGE_INT_EN_BIT, 0); if (rc < 0) { smblib_err(chg, "Couldn't disable moisture detection interrupt rc=%d\n", rc); return rc; } /* Set 1% duty cycle on ID detection */ rc = smblib_masked_write(chg, ((chg->chg_param.smb_version == PMI632) ? PMI632_TYPEC_U_USB_WATER_PROTECTION_CFG_REG : TYPEC_U_USB_WATER_PROTECTION_CFG_REG), EN_MICRO_USB_WATER_PROTECTION_BIT | MICRO_USB_DETECTION_ON_TIME_CFG_MASK | MICRO_USB_DETECTION_PERIOD_CFG_MASK, EN_MICRO_USB_WATER_PROTECTION_BIT | MICRO_USB_DETECTION_ON_TIME_20_MS | MICRO_USB_DETECTION_PERIOD_X_100); if (rc < 0) { smblib_err(chg, "Couldn't set 1 percent CC_ID duty cycle rc=%d\n", rc); return rc; } vote(chg->usb_icl_votable, MOISTURE_VOTER, true, 0); } else { chg->moisture_present = false; vote(chg->usb_icl_votable, MOISTURE_VOTER, false, 0); /* Enable moisture detection and uUSB state change interrupt */ rc = smblib_masked_write(chg, TYPE_C_INTERRUPT_EN_CFG_2_REG, TYPEC_WATER_DETECTION_INT_EN_BIT | MICRO_USB_STATE_CHANGE_INT_EN_BIT, TYPEC_WATER_DETECTION_INT_EN_BIT | MICRO_USB_STATE_CHANGE_INT_EN_BIT); if (rc < 0) { smblib_err(chg, "Couldn't enable moisture detection and uUSB state change interrupt rc=%d\n", rc); return rc; } /* Disable periodic monitoring of CC_ID pin */ rc = smblib_write(chg, ((chg->chg_param.smb_version == PMI632) ? PMI632_TYPEC_U_USB_WATER_PROTECTION_CFG_REG : TYPEC_U_USB_WATER_PROTECTION_CFG_REG), 0); if (rc < 0) { smblib_err(chg, "Couldn't disable 1 percent CC_ID duty cycle rc=%d\n", rc); return rc; } /* Enable uUSB factory mode detection */ rc = smblib_masked_write(chg, TYPEC_U_USB_CFG_REG, EN_MICRO_USB_FACTORY_MODE_BIT, EN_MICRO_USB_FACTORY_MODE_BIT); if (rc < 0) { smblib_err(chg, "Couldn't disable uUSB factory mode detection rc=%d\n", rc); return rc; } } smblib_dbg(chg, PR_MISC, "Moisture protection %s\n", chg->moisture_present ? "enabled" : "disabled"); return rc; } /********************* * VOTABLE CALLBACKS * *********************/ static int smblib_smb_disable_override_vote_callback(struct votable *votable, void *data, int disable_smb, const char *client) { struct smb_charger *chg = data; int rc = 0; /* Enable/disable SMB_EN pin */ rc = smblib_masked_write(chg, MISC_SMB_EN_CMD_REG, SMB_EN_OVERRIDE_BIT | SMB_EN_OVERRIDE_VALUE_BIT, disable_smb ? SMB_EN_OVERRIDE_BIT : 0); if (rc < 0) smblib_err(chg, "Couldn't configure SMB_EN, rc=%d\n", rc); return rc; } static int smblib_dc_suspend_vote_callback(struct votable *votable, void *data, int suspend, const char *client) { struct smb_charger *chg = data; if (chg->chg_param.smb_version == PMI632) return 0; /* resume input if suspend is invalid */ if (suspend < 0) suspend = 0; return smblib_set_dc_suspend(chg, (bool)suspend); } static int smblib_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 smblib_chg_disable_vote_callback(struct votable *votable, void *data, int chg_disable, const char *client) { struct smb_charger *chg = data; int rc; rc = smblib_masked_write(chg, CHARGING_ENABLE_CMD_REG, CHARGING_ENABLE_CMD_BIT, chg_disable ? 0 : CHARGING_ENABLE_CMD_BIT); if (rc < 0) { smblib_err(chg, "Couldn't %s charging rc=%d\n", chg_disable ? "disable" : "enable", rc); return rc; } return 0; } static int smblib_hdc_irq_disable_vote_callback(struct votable *votable, void *data, int disable, const char *client) { struct smb_charger *chg = data; if (!chg->irq_info[HIGH_DUTY_CYCLE_IRQ].irq) return 0; if (chg->irq_info[HIGH_DUTY_CYCLE_IRQ].enabled) { if (disable) disable_irq_nosync( chg->irq_info[HIGH_DUTY_CYCLE_IRQ].irq); } else { if (!disable) enable_irq(chg->irq_info[HIGH_DUTY_CYCLE_IRQ].irq); } chg->irq_info[HIGH_DUTY_CYCLE_IRQ].enabled = !disable; return 0; } static int smblib_limited_irq_disable_vote_callback(struct votable *votable, void *data, int disable, const char *client) { struct smb_charger *chg = data; if (!chg->irq_info[INPUT_CURRENT_LIMITING_IRQ].irq) return 0; if (chg->irq_info[INPUT_CURRENT_LIMITING_IRQ].enabled) { if (disable) disable_irq_nosync( chg->irq_info[INPUT_CURRENT_LIMITING_IRQ].irq); } else { if (!disable) enable_irq( chg->irq_info[INPUT_CURRENT_LIMITING_IRQ].irq); } chg->irq_info[INPUT_CURRENT_LIMITING_IRQ].enabled = !disable; return 0; } static int smblib_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].irq) return 0; if (chg->irq_info[USBIN_ICL_CHANGE_IRQ].enabled) { if (disable) 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 smblib_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].irq) 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); 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; } static int smblib_bat_temp_irq_disable_vote_callback(struct votable *votable, void *data, int disable, const char *client) { struct smb_charger *chg = data; if (!chg->irq_info[BAT_TEMP_IRQ].irq) return 0; if (chg->irq_info[BAT_TEMP_IRQ].enabled && disable) { disable_irq_wake(chg->irq_info[BAT_TEMP_IRQ].irq); disable_irq_nosync(chg->irq_info[BAT_TEMP_IRQ].irq); } else if (!chg->irq_info[BAT_TEMP_IRQ].enabled && !disable) { enable_irq(chg->irq_info[BAT_TEMP_IRQ].irq); enable_irq_wake(chg->irq_info[BAT_TEMP_IRQ].irq); } chg->irq_info[BAT_TEMP_IRQ].enabled = !disable; return 0; } /******************* * VCONN REGULATOR * * *****************/ int smblib_vconn_regulator_enable(struct regulator_dev *rdev) { struct smb_charger *chg = rdev_get_drvdata(rdev); int rc = 0; u8 stat, orientation; smblib_dbg(chg, PR_OTG, "enabling VCONN\n"); rc = smblib_read(chg, TYPE_C_MISC_STATUS_REG, &stat); if (rc < 0) { smblib_err(chg, "Couldn't read TYPE_C_STATUS_4 rc=%d\n", rc); return rc; } /* VCONN orientation is opposite to that of CC */ orientation = stat & TYPEC_CCOUT_VALUE_BIT ? 0 : VCONN_EN_ORIENTATION_BIT; rc = smblib_masked_write(chg, TYPE_C_VCONN_CONTROL_REG, VCONN_EN_VALUE_BIT | VCONN_EN_ORIENTATION_BIT, VCONN_EN_VALUE_BIT | orientation); if (rc < 0) { smblib_err(chg, "Couldn't read TYPE_C_CCOUT_CONTROL_REG rc=%d\n", rc); return rc; } return 0; } int smblib_vconn_regulator_disable(struct regulator_dev *rdev) { struct smb_charger *chg = rdev_get_drvdata(rdev); int rc = 0; smblib_dbg(chg, PR_OTG, "disabling VCONN\n"); rc = smblib_masked_write(chg, TYPE_C_VCONN_CONTROL_REG, VCONN_EN_VALUE_BIT, 0); if (rc < 0) smblib_err(chg, "Couldn't disable vconn regulator rc=%d\n", rc); return 0; } int smblib_vconn_regulator_is_enabled(struct regulator_dev *rdev) { struct smb_charger *chg = rdev_get_drvdata(rdev); int rc; u8 cmd; rc = smblib_read(chg, TYPE_C_VCONN_CONTROL_REG, &cmd); if (rc < 0) { smblib_err(chg, "Couldn't read TYPE_C_INTRPT_ENB_SOFTWARE_CTRL rc=%d\n", rc); return rc; } return (cmd & VCONN_EN_VALUE_BIT) ? 1 : 0; } /***************** * OTG REGULATOR * *****************/ int smblib_vbus_regulator_enable(struct regulator_dev *rdev) { struct smb_charger *chg = rdev_get_drvdata(rdev); int rc; smblib_dbg(chg, PR_OTG, "enabling OTG\n"); rc = smblib_masked_write(chg, DCDC_CMD_OTG_REG, OTG_EN_BIT, OTG_EN_BIT); if (rc < 0) { smblib_err(chg, "Couldn't enable OTG rc=%d\n", rc); return rc; } return 0; } int smblib_vbus_regulator_disable(struct regulator_dev *rdev) { struct smb_charger *chg = rdev_get_drvdata(rdev); int rc; smblib_dbg(chg, PR_OTG, "disabling OTG\n"); rc = smblib_masked_write(chg, DCDC_CMD_OTG_REG, OTG_EN_BIT, 0); if (rc < 0) { smblib_err(chg, "Couldn't disable OTG regulator rc=%d\n", rc); return rc; } return 0; } int smblib_vbus_regulator_is_enabled(struct regulator_dev *rdev) { struct smb_charger *chg = rdev_get_drvdata(rdev); int rc = 0; u8 cmd; rc = smblib_read(chg, DCDC_CMD_OTG_REG, &cmd); if (rc < 0) { smblib_err(chg, "Couldn't read CMD_OTG rc=%d", rc); return rc; } return (cmd & OTG_EN_BIT) ? 1 : 0; } /******************** * BATT PSY GETTERS * ********************/ int smblib_get_prop_input_suspend(struct smb_charger *chg, union power_supply_propval *val) { val->intval = (get_client_vote(chg->usb_icl_votable, USER_VOTER) == 0) && get_client_vote(chg->dc_suspend_votable, USER_VOTER); return 0; } int smblib_get_prop_batt_present(struct smb_charger *chg, union power_supply_propval *val) { int rc; u8 stat; rc = smblib_read(chg, BATIF_BASE + INT_RT_STS_OFFSET, &stat); if (rc < 0) { smblib_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 | BAT_TERMINAL_MISSING_RT_STS_BIT)); return rc; } int smblib_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 = smblib_get_prop_from_bms(chg, SMB5_QG_CAPACITY, &val->intval); if (rc < 0) smblib_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 = smblib_read(chg, CHARGING_PAUSE_CMD_REG, &val); if (rc < 0) { smblib_err(chg, "Couldn't read CHARGING_PAUSE_CMD rc=%d\n", rc); return false; } return val & CHARGING_PAUSE_CMD_BIT; } #define CUTOFF_COUNT 3 int smblib_get_prop_batt_status(struct smb_charger *chg, union power_supply_propval *val) { union power_supply_propval pval = {0, }; bool usb_online, dc_online; u8 stat; int rc, suspend = 0, input_present = 0; if (chg->fake_chg_status_on_debug_batt) { rc = smblib_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; } } rc = smblib_get_prop_batt_health(chg, &pval); if (rc < 0) { smblib_err(chg, "Couldn't get batt health rc=%d\n", rc); return rc; } /* * The charger status register shows charging even though the battery * is discharging when the over voltage condition is hit. Report power * supply state as NOT_CHARGING when the battery health reports * over voltage. */ if (pval.intval == POWER_SUPPLY_HEALTH_OVERVOLTAGE) { val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; return 0; } /* * If SOC = 0 and we are discharging with input connected, report * the battery status as DISCHARGING. */ smblib_is_input_present(chg, &input_present); rc = smblib_get_prop_from_bms(chg, SMB5_QG_CAPACITY, &pval.intval); if (!rc && pval.intval == 0 && input_present) { rc = smblib_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; } if (chg->dbc_usbov) { rc = smblib_get_prop_usb_present(chg, &pval); if (rc < 0) { smblib_err(chg, "Couldn't get usb present prop rc=%d\n", rc); return rc; } rc = smblib_get_usb_suspend(chg, &suspend); if (rc < 0) { smblib_err(chg, "Couldn't get usb suspend rc=%d\n", rc); return rc; } /* * Report charging as long as USBOV is not debounced and * charging path is un-suspended. */ if (pval.intval && !suspend) { val->intval = POWER_SUPPLY_STATUS_CHARGING; return 0; } } rc = smblib_get_prop_usb_online(chg, &pval); if (rc < 0) { smblib_err(chg, "Couldn't get usb online property rc=%d\n", rc); return rc; } usb_online = (bool)pval.intval; rc = smblib_get_prop_dc_online(chg, &pval); if (rc < 0) { smblib_err(chg, "Couldn't get dc online property rc=%d\n", rc); return rc; } dc_online = (bool)pval.intval; rc = smblib_read(chg, BATTERY_CHARGER_STATUS_1_REG, &stat); if (rc < 0) { smblib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_1 rc=%d\n", rc); return rc; } stat = stat & BATTERY_CHARGER_STATUS_MASK; if (!usb_online && !dc_online) { switch (stat) { case TERMINATE_CHARGE: fallthrough; 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: fallthrough; case PRE_CHARGE: fallthrough; case FULLON_CHARGE: fallthrough; case TAPER_CHARGE: val->intval = POWER_SUPPLY_STATUS_CHARGING; break; case TERMINATE_CHARGE: fallthrough; case INHIBIT_CHARGE: val->intval = POWER_SUPPLY_STATUS_FULL; break; case DISABLE_CHARGE: fallthrough; 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 charge termination WA is active and has suspended charging, then * continue reporting charging status as FULL. */ if (is_client_vote_enabled_locked(chg->usb_icl_votable, CHG_TERMINATION_VOTER)) { val->intval = POWER_SUPPLY_STATUS_FULL; return 0; } if (val->intval != POWER_SUPPLY_STATUS_CHARGING) return 0; if (!usb_online && dc_online && chg->fake_batt_status == POWER_SUPPLY_STATUS_FULL) { val->intval = POWER_SUPPLY_STATUS_FULL; return 0; } rc = smblib_read(chg, BATTERY_CHARGER_STATUS_5_REG, &stat); if (rc < 0) { smblib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_2 rc=%d\n", rc); return rc; } stat &= ENABLE_TRICKLE_BIT | ENABLE_PRE_CHARGING_BIT | ENABLE_FULLON_MODE_BIT; if (!stat) val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; return 0; } int smblib_get_prop_batt_charge_type(struct smb_charger *chg, union power_supply_propval *val) { int rc; u8 stat; rc = smblib_read(chg, BATTERY_CHARGER_STATUS_1_REG, &stat); if (rc < 0) { smblib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_1 rc=%d\n", rc); return rc; } switch (stat & BATTERY_CHARGER_STATUS_MASK) { case TRICKLE_CHARGE: fallthrough; 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 smblib_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 = smblib_read(chg, BATTERY_CHARGER_STATUS_2_REG, &stat); if (rc < 0) { smblib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_2 rc=%d\n", rc); return rc; } smblib_dbg(chg, PR_REGISTER, "BATTERY_CHARGER_STATUS_2 = 0x%02x\n", stat); if (stat & CHARGER_ERROR_STATUS_BAT_OV_BIT) { rc = smblib_get_prop_from_bms(chg, SMB5_QG_VOLTAGE_NOW, &pval.intval); if (rc < 0) { smblib_err(chg, "Couldn't get voltage_now prop rc=%d\n", rc); } else { /* * If Vbatt is within 40mV above Vfloat, then don't * treat it as overvoltage. */ effective_fv_uv = get_effective_result_locked( chg->fv_votable); if (pval.intval >= effective_fv_uv + 40000) { val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; smblib_err(chg, "battery over-voltage vbat_fg = %duV, fv = %duV\n", pval.intval, effective_fv_uv); goto done; } } } rc = smblib_read(chg, BATTERY_CHARGER_STATUS_7_REG, &stat); if (rc < 0) { smblib_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 smblib_get_prop_system_temp_level(struct smb_charger *chg, union power_supply_propval *val) { val->intval = chg->system_temp_level; return 0; } int smblib_get_prop_system_temp_level_max(struct smb_charger *chg, union power_supply_propval *val) { val->intval = chg->thermal_levels; return 0; } int smblib_get_prop_input_current_limited(struct smb_charger *chg, int *val) { u8 stat; int rc; if (chg->fake_input_current_limited >= 0) { *val = chg->fake_input_current_limited; return 0; } rc = smblib_read(chg, AICL_STATUS_REG, &stat); if (rc < 0) { smblib_err(chg, "Couldn't read AICL_STATUS rc=%d\n", rc); return rc; } *val = (stat & SOFT_ILIMIT_BIT) || chg->is_hdc; return 0; } int smblib_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, * hence read only the threshold corresponding to ADC source. * Proceed only if CHGR_ITERM_USE_ANALOG_BIT is 0. */ rc = smblib_read(chg, CHGR_ENG_CHARGING_CFG_REG, &stat); if (rc < 0) { smblib_err(chg, "Couldn't read CHGR_ENG_CHARGING_CFG_REG rc=%d\n", rc); return rc; } if (stat & CHGR_ITERM_USE_ANALOG_BIT) { val->intval = -EINVAL; return 0; } rc = smblib_batch_read(chg, CHGR_ADC_ITERM_UP_THD_MSB_REG, buf, 2); if (rc < 0) { smblib_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->chg_param.smb_version == PMI632) temp = DIV_ROUND_CLOSEST(temp * ITERM_LIMITS_PMI632_MA, ADC_CHG_ITERM_MASK); else temp = DIV_ROUND_CLOSEST(temp * ITERM_LIMITS_PM8150B_MA, ADC_CHG_ITERM_MASK); val->intval = temp; return rc; } int smblib_get_prop_batt_charge_done(struct smb_charger *chg, int *val) { int rc; u8 stat; rc = smblib_read(chg, BATTERY_CHARGER_STATUS_1_REG, &stat); if (rc < 0) { smblib_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 smblib_get_batt_current_now(struct smb_charger *chg, union power_supply_propval *val) { int rc; rc = smblib_get_prop_from_bms(chg, SMB5_QG_CURRENT_NOW, &val->intval); if (!rc) val->intval *= (-1); else smblib_err(chg, "Couldn't get current_now prop rc=%d\n", rc); return rc; } /*********************** * BATTERY PSY SETTERS * ***********************/ int smblib_set_prop_input_suspend(struct smb_charger *chg, const union power_supply_propval *val) { int rc; /* vote 0mA when suspended */ rc = vote(chg->usb_icl_votable, USER_VOTER, (bool)val->intval, 0); if (rc < 0) { smblib_err(chg, "Couldn't vote to %s USB rc=%d\n", (bool)val->intval ? "suspend" : "resume", rc); return rc; } rc = vote(chg->dc_suspend_votable, USER_VOTER, (bool)val->intval, 0); if (rc < 0) { smblib_err(chg, "Couldn't vote to %s DC rc=%d\n", (bool)val->intval ? "suspend" : "resume", rc); return rc; } power_supply_changed(chg->batt_psy); return rc; } int smblib_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 smblib_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 smblib_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; } int smblib_set_prop_input_current_limited(struct smb_charger *chg, int val) { chg->fake_input_current_limited = val; return 0; } int smblib_set_prop_rechg_soc_thresh(struct smb_charger *chg, int val) { int rc; u8 new_thr = DIV_ROUND_CLOSEST(val * 255, 100); rc = smblib_write(chg, CHARGE_RCHG_SOC_THRESHOLD_CFG_REG, new_thr); if (rc < 0) { smblib_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 smblib_run_aicl(struct smb_charger *chg, int type) { int rc; u8 stat; rc = smblib_read(chg, POWER_PATH_STATUS_REG, &stat); if (rc < 0) { smblib_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; smblib_dbg(chg, PR_MISC, "re-running AICL\n"); stat = (type == RERUN_AICL) ? RERUN_AICL_BIT : RESTART_AICL_BIT; rc = smblib_masked_write(chg, AICL_CMD_REG, stat, stat); if (rc < 0) smblib_err(chg, "Couldn't write to AICL_CMD_REG rc=%d\n", rc); return 0; } static int smblib_dp_pulse(struct smb_charger *chg) { int rc; /* QC 3.0 increment */ rc = smblib_masked_write(chg, CMD_HVDCP_2_REG, SINGLE_INCREMENT_BIT, SINGLE_INCREMENT_BIT); if (rc < 0) smblib_err(chg, "Couldn't write to CMD_HVDCP_2_REG rc=%d\n", rc); return rc; } static int smblib_dm_pulse(struct smb_charger *chg) { int rc; /* QC 3.0 decrement */ rc = smblib_masked_write(chg, CMD_HVDCP_2_REG, SINGLE_DECREMENT_BIT, SINGLE_DECREMENT_BIT); if (rc < 0) smblib_err(chg, "Couldn't write to CMD_HVDCP_2_REG rc=%d\n", rc); return rc; } int smblib_force_vbus_voltage(struct smb_charger *chg, u8 val) { int rc; rc = smblib_masked_write(chg, CMD_HVDCP_2_REG, val, val); if (rc < 0) smblib_err(chg, "Couldn't write to CMD_HVDCP_2_REG rc=%d\n", rc); return rc; } static void smblib_hvdcp_set_fsw(struct smb_charger *chg, int bit) { switch (bit) { case QC_5V_BIT: smblib_set_opt_switcher_freq(chg, chg->chg_freq.freq_5V); break; case QC_9V_BIT: smblib_set_opt_switcher_freq(chg, chg->chg_freq.freq_9V); break; case QC_12V_BIT: smblib_set_opt_switcher_freq(chg, chg->chg_freq.freq_12V); break; default: smblib_set_opt_switcher_freq(chg, chg->chg_freq.freq_removal); break; } } #define QC3_PULSES_FOR_6V 5 #define QC3_PULSES_FOR_9V 20 #define QC3_PULSES_FOR_12V 35 static int smblib_hvdcp3_set_fsw(struct smb_charger *chg) { int pulse_count, rc; rc = smblib_get_pulse_cnt(chg, &pulse_count); if (rc < 0) { smblib_err(chg, "Couldn't read QC_PULSE_COUNT rc=%d\n", rc); return rc; } if (pulse_count < QC3_PULSES_FOR_6V) smblib_set_opt_switcher_freq(chg, chg->chg_freq.freq_5V); else if (pulse_count < QC3_PULSES_FOR_9V) smblib_set_opt_switcher_freq(chg, chg->chg_freq.freq_6V_8V); else if (pulse_count < QC3_PULSES_FOR_12V) smblib_set_opt_switcher_freq(chg, chg->chg_freq.freq_9V); else smblib_set_opt_switcher_freq(chg, chg->chg_freq.freq_12V); return 0; } static void smblib_hvdcp_adaptive_voltage_change(struct smb_charger *chg) { int rc; u8 stat; if (chg->real_charger_type == QTI_POWER_SUPPLY_TYPE_USB_HVDCP) { rc = smblib_read(chg, QC_CHANGE_STATUS_REG, &stat); if (rc < 0) { smblib_err(chg, "Couldn't read QC_CHANGE_STATUS rc=%d\n", rc); return; } smblib_hvdcp_set_fsw(chg, stat & QC_2P0_STATUS_MASK); vote(chg->usb_icl_votable, HVDCP2_ICL_VOTER, false, 0); } if (chg->real_charger_type == QTI_POWER_SUPPLY_TYPE_USB_HVDCP_3 || chg->real_charger_type == QTI_POWER_SUPPLY_TYPE_USB_HVDCP_3P5) { rc = smblib_hvdcp3_set_fsw(chg); if (rc < 0) smblib_err(chg, "Couldn't set QC3.0 Fsw rc=%d\n", rc); } power_supply_changed(chg->batt_psy); } int smblib_dp_dm(struct smb_charger *chg, int val) { int target_icl_ua, data, rc = 0; u8 stat; switch (val) { case QTI_POWER_SUPPLY_DP_DM_DP_PULSE: /* * Pre-emptively increment pulse count to enable the setting * of FSW prior to increasing voltage. */ chg->pulse_cnt++; rc = smblib_hvdcp3_set_fsw(chg); if (rc < 0) smblib_err(chg, "Couldn't set QC3.0 Fsw rc=%d\n", rc); rc = smblib_dp_pulse(chg); if (rc < 0) { smblib_err(chg, "Couldn't increase pulse count rc=%d\n", rc); /* * Increment pulse count failed; * reset to former value. */ chg->pulse_cnt--; } smblib_dbg(chg, PR_PARALLEL, "DP_DM_DP_PULSE rc=%d cnt=%d\n", rc, chg->pulse_cnt); break; case QTI_POWER_SUPPLY_DP_DM_DM_PULSE: rc = smblib_dm_pulse(chg); if (!rc && chg->pulse_cnt) chg->pulse_cnt--; smblib_dbg(chg, PR_PARALLEL, "DP_DM_DM_PULSE rc=%d cnt=%d\n", rc, chg->pulse_cnt); break; case QTI_POWER_SUPPLY_DP_DM_ICL_DOWN: target_icl_ua = get_effective_result(chg->usb_icl_votable); if (target_icl_ua < 0) { /* no client vote, get the ICL from charger */ rc = smblib_get_charge_current(chg, &data); if (rc < 0) { smblib_err(chg, "Couldn't get max curr rc=%d\n", rc); return rc; } target_icl_ua = data; } /* * Check if any other voter voted on USB_ICL in case of * voter other than SW_QC3_VOTER reset and restart reduction * again. */ if (target_icl_ua != get_client_vote(chg->usb_icl_votable, SW_QC3_VOTER)) chg->usb_icl_delta_ua = 0; chg->usb_icl_delta_ua += 100000; vote(chg->usb_icl_votable, SW_QC3_VOTER, true, target_icl_ua - 100000); smblib_dbg(chg, PR_PARALLEL, "ICL DOWN ICL=%d reduction=%d\n", target_icl_ua, chg->usb_icl_delta_ua); break; case QTI_POWER_SUPPLY_DP_DM_FORCE_5V: rc = smblib_force_vbus_voltage(chg, FORCE_5V_BIT); if (rc < 0) pr_err("Failed to force 5V\n"); break; case QTI_POWER_SUPPLY_DP_DM_FORCE_9V: if (chg->qc2_unsupported_voltage == QC2_NON_COMPLIANT_9V) { smblib_err(chg, "Couldn't set 9V: unsupported\n"); return -EINVAL; } /* If we are increasing voltage to get to 9V, set FSW first */ rc = smblib_read(chg, QC_CHANGE_STATUS_REG, &stat); if (rc < 0) { smblib_err(chg, "Couldn't read QC_CHANGE_STATUS_REG rc=%d\n", rc); break; } if (stat & QC_5V_BIT) { /* Force 1A ICL before requesting higher voltage */ vote(chg->usb_icl_votable, HVDCP2_ICL_VOTER, true, 1000000); smblib_hvdcp_set_fsw(chg, QC_9V_BIT); } rc = smblib_force_vbus_voltage(chg, FORCE_9V_BIT); if (rc < 0) pr_err("Failed to force 9V\n"); break; case QTI_POWER_SUPPLY_DP_DM_FORCE_12V: if (chg->qc2_unsupported_voltage == QC2_NON_COMPLIANT_12V) { smblib_err(chg, "Couldn't set 12V: unsupported\n"); return -EINVAL; } /* If we are increasing voltage to get to 12V, set FSW first */ rc = smblib_read(chg, QC_CHANGE_STATUS_REG, &stat); if (rc < 0) { smblib_err(chg, "Couldn't read QC_CHANGE_STATUS_REG rc=%d\n", rc); break; } if ((stat & QC_9V_BIT) || (stat & QC_5V_BIT)) { /* Force 1A ICL before requesting higher voltage */ vote(chg->usb_icl_votable, HVDCP2_ICL_VOTER, true, 1000000); smblib_hvdcp_set_fsw(chg, QC_12V_BIT); } rc = smblib_force_vbus_voltage(chg, FORCE_12V_BIT); if (rc < 0) pr_err("Failed to force 12V\n"); break; case QTI_POWER_SUPPLY_DP_DM_CONFIRMED_HVDCP3P5: chg->qc3p5_detected = true; smblib_update_usb_type(chg); break; case QTI_POWER_SUPPLY_DP_DM_ICL_UP: default: break; } return rc; } int smblib_disable_hw_jeita(struct smb_charger *chg, bool disable) { int rc; u8 mask; /* * Disable h/w base JEITA compensation if s/w JEITA is enabled */ mask = JEITA_EN_COLD_SL_FCV_BIT | JEITA_EN_HOT_SL_FCV_BIT | JEITA_EN_HOT_SL_CCC_BIT | JEITA_EN_COLD_SL_CCC_BIT, rc = smblib_masked_write(chg, JEITA_EN_CFG_REG, mask, disable ? 0 : mask); if (rc < 0) { dev_err(chg->dev, "Couldn't configure s/w jeita rc=%d\n", rc); return rc; } return 0; } static int smblib_set_sw_thermal_regulation(struct smb_charger *chg, bool enable) { int rc = 0; if (!(chg->wa_flags & SW_THERM_REGULATION_WA)) return rc; if (enable) { /* * Configure min time to quickly address thermal * condition. */ rc = smblib_masked_write(chg, SNARL_BARK_BITE_WD_CFG_REG, SNARL_WDOG_TIMEOUT_MASK, SNARL_WDOG_TMOUT_62P5MS); if (rc < 0) { smblib_err(chg, "Couldn't configure snarl wdog tmout, rc=%d\n", rc); return rc; } /* * Schedule SW_THERM_REGULATION_WORK directly if USB input * is suspended due to SW thermal regulation WA since WDOG * IRQ won't trigger with input suspended. */ if (is_client_vote_enabled(chg->usb_icl_votable, SW_THERM_REGULATION_VOTER)) { vote(chg->awake_votable, SW_THERM_REGULATION_VOTER, true, 0); schedule_delayed_work(&chg->thermal_regulation_work, 0); } } else { cancel_delayed_work_sync(&chg->thermal_regulation_work); vote(chg->awake_votable, SW_THERM_REGULATION_VOTER, false, 0); } smblib_dbg(chg, PR_MISC, "WDOG SNARL INT %s\n", enable ? "Enabled" : "Disabled"); return rc; } static int smblib_update_thermal_readings(struct smb_charger *chg) { int rc = 0, data = 0; struct iio_channel **iio_list; if (!chg->iio_chan_list_smb_parallel) { iio_list = get_ext_channels(chg->dev, smblib_parallel_ext_iio_chan, ARRAY_SIZE(smblib_parallel_ext_iio_chan)); if (IS_ERR(iio_list)) { rc = PTR_ERR(iio_list); if (rc != -EPROBE_DEFER) { dev_err(chg->dev, "Failed to get channels, %d\n", rc); chg->iio_chan_list_smb_parallel = ERR_PTR(-EINVAL); } } else { chg->iio_chan_list_smb_parallel = iio_list; } } rc = smblib_read_iio_channel(chg, chg->iio.die_temp_chan, DIV_FACTOR_DECIDEGC, &chg->die_temp); if (rc < 0) { smblib_err(chg, "Couldn't read DIE TEMP channel, rc=%d\n", rc); return rc; } rc = smblib_read_iio_channel(chg, chg->iio.connector_temp_chan, DIV_FACTOR_DECIDEGC, &chg->connector_temp); if (rc < 0) { smblib_err(chg, "Couldn't read CONN TEMP channel, rc=%d\n", rc); return rc; } rc = smblib_read_iio_channel(chg, chg->iio.skin_temp_chan, DIV_FACTOR_DECIDEGC, &chg->skin_temp); if (rc < 0) { smblib_err(chg, "Couldn't read SKIN TEMP channel, rc=%d\n", rc); return rc; } if (chg->sec_chg_selected == QTI_POWER_SUPPLY_CHARGER_SEC_CP) { if (is_cp_available(chg)) { rc = smblib_read_iio_prop(chg, CP, CP_DIE_TEMP, &data); if (rc < 0) { smblib_err(chg, "Couldn't get smb1390 charger temp, rc=%d\n", rc); return rc; } chg->smb_temp = data; } else { smblib_dbg(chg, PR_MISC, "Coudln't find cp_psy\n"); chg->smb_temp = -ENODATA; } } else if (!IS_ERR_OR_NULL(chg->iio_chan_list_smb_parallel) && chg->sec_chg_selected == QTI_POWER_SUPPLY_CHARGER_SEC_PL) { rc = smblib_read_iio_prop(chg, SMB_PARALLEL, SMB_CHARGER_TEMP, &data); if (rc < 0) { smblib_err(chg, "Couldn't get smb1355 charger temp, rc=%d\n", rc); return rc; } chg->smb_temp = data; } else { chg->smb_temp = -ENODATA; } return rc; } /* SW thermal regulation thresholds in deciDegC */ #define DIE_TEMP_RST_THRESH 1000 #define DIE_TEMP_REG_H_THRESH 800 #define DIE_TEMP_REG_L_THRESH 600 #define CONNECTOR_TEMP_SHDN_THRESH 700 #define CONNECTOR_TEMP_RST_THRESH 600 #define CONNECTOR_TEMP_REG_H_THRESH 550 #define CONNECTOR_TEMP_REG_L_THRESH 500 #define SMB_TEMP_SHDN_THRESH 1400 #define SMB_TEMP_RST_THRESH 900 #define SMB_TEMP_REG_H_THRESH 800 #define SMB_TEMP_REG_L_THRESH 600 #define SKIN_TEMP_SHDN_THRESH 700 #define SKIN_TEMP_RST_THRESH 600 #define SKIN_TEMP_REG_H_THRESH 550 #define SKIN_TEMP_REG_L_THRESH 500 #define THERM_REG_RECHECK_DELAY_1S 1000 /* 1 sec */ #define THERM_REG_RECHECK_DELAY_8S 8000 /* 8 sec */ static int smblib_process_thermal_readings(struct smb_charger *chg) { int rc = 0, wdog_timeout = SNARL_WDOG_TMOUT_8S; u32 thermal_status = TEMP_BELOW_RANGE; bool suspend_input = false, disable_smb = false; /* * Following is the SW thermal regulation flow: * * TEMP_SHUT_DOWN_LEVEL: If either connector temp or skin temp * exceeds their respective SHDN threshold. Need to suspend input * and secondary charger. * * TEMP_SHUT_DOWN_SMB_LEVEL: If smb temp exceed its SHDN threshold * but connector and skin temp are below it. Need to suspend SMB. * * TEMP_ALERT_LEVEL: If die, connector, smb or skin temp exceeds it's * respective RST threshold. Stay put and monitor temperature closely. * * TEMP_ABOVE_RANGE or TEMP_WITHIN_RANGE or TEMP_BELOW_RANGE: If die, * connector, smb or skin temp exceeds it's respective REG_H or REG_L * threshold. Unsuspend input and SMB. */ if (chg->connector_temp > CONNECTOR_TEMP_SHDN_THRESH || chg->skin_temp > SKIN_TEMP_SHDN_THRESH) { thermal_status = TEMP_SHUT_DOWN; wdog_timeout = SNARL_WDOG_TMOUT_1S; suspend_input = true; disable_smb = true; goto out; } if (chg->smb_temp > SMB_TEMP_SHDN_THRESH) { thermal_status = TEMP_SHUT_DOWN_SMB; wdog_timeout = SNARL_WDOG_TMOUT_1S; disable_smb = true; goto out; } if (chg->connector_temp > CONNECTOR_TEMP_RST_THRESH || chg->skin_temp > SKIN_TEMP_RST_THRESH || chg->smb_temp > SMB_TEMP_RST_THRESH || chg->die_temp > DIE_TEMP_RST_THRESH) { thermal_status = TEMP_ALERT_LEVEL; wdog_timeout = SNARL_WDOG_TMOUT_1S; goto out; } if (chg->connector_temp > CONNECTOR_TEMP_REG_H_THRESH || chg->skin_temp > SKIN_TEMP_REG_H_THRESH || chg->smb_temp > SMB_TEMP_REG_H_THRESH || chg->die_temp > DIE_TEMP_REG_H_THRESH) { thermal_status = TEMP_ABOVE_RANGE; wdog_timeout = SNARL_WDOG_TMOUT_1S; goto out; } if (chg->connector_temp > CONNECTOR_TEMP_REG_L_THRESH || chg->skin_temp > SKIN_TEMP_REG_L_THRESH || chg->smb_temp > SMB_TEMP_REG_L_THRESH || chg->die_temp > DIE_TEMP_REG_L_THRESH) { thermal_status = TEMP_WITHIN_RANGE; wdog_timeout = SNARL_WDOG_TMOUT_8S; } out: smblib_dbg(chg, PR_MISC, "Current temperatures: \tDIE_TEMP: %d,\tCONN_TEMP: %d,\tSMB_TEMP: %d,\tSKIN_TEMP: %d\nTHERMAL_STATUS: %d\n", chg->die_temp, chg->connector_temp, chg->smb_temp, chg->skin_temp, thermal_status); if (thermal_status != chg->thermal_status) { chg->thermal_status = thermal_status; /* * If thermal level changes to TEMP ALERT LEVEL, don't * enable/disable main/parallel charging. */ if (chg->thermal_status == TEMP_ALERT_LEVEL) goto exit; vote(chg->smb_override_votable, SW_THERM_REGULATION_VOTER, disable_smb, 0); /* * Enable/disable secondary charger through votables to ensure * that if SMB_EN pin get's toggled somehow, secondary charger * remains enabled/disabled according to SW thermal regulation. */ if (!chg->cp_disable_votable) chg->cp_disable_votable = find_votable("CP_DISABLE"); if (chg->cp_disable_votable) vote(chg->cp_disable_votable, SW_THERM_REGULATION_VOTER, disable_smb, 0); vote(chg->pl_disable_votable, SW_THERM_REGULATION_VOTER, disable_smb, 0); smblib_dbg(chg, PR_MISC, "Parallel %s as per SW thermal regulation\n", disable_smb ? "disabled" : "enabled"); /* * If thermal level changes to TEMP_SHUT_DOWN_SMB, don't * enable/disable main charger. */ if (chg->thermal_status == TEMP_SHUT_DOWN_SMB) goto exit; /* Suspend input if SHDN threshold reached */ vote(chg->dc_suspend_votable, SW_THERM_REGULATION_VOTER, suspend_input, 0); vote(chg->usb_icl_votable, SW_THERM_REGULATION_VOTER, suspend_input, 0); smblib_dbg(chg, PR_MISC, "USB/DC %s as per SW thermal regulation\n", suspend_input ? "suspended" : "unsuspended"); } exit: /* * On USB suspend, WDOG IRQ stops triggering. To continue thermal * monitoring and regulation until USB is plugged out, reschedule * the SW thermal regulation work without releasing the wake lock. */ if (is_client_vote_enabled(chg->usb_icl_votable, SW_THERM_REGULATION_VOTER)) { schedule_delayed_work(&chg->thermal_regulation_work, msecs_to_jiffies(THERM_REG_RECHECK_DELAY_1S)); return 0; } rc = smblib_masked_write(chg, SNARL_BARK_BITE_WD_CFG_REG, SNARL_WDOG_TIMEOUT_MASK, wdog_timeout); if (rc < 0) smblib_err(chg, "Couldn't set WD SNARL timer, rc=%d\n", rc); vote(chg->awake_votable, SW_THERM_REGULATION_VOTER, false, 0); return rc; } /******************* * DC PSY GETTERS * *******************/ int smblib_get_prop_voltage_wls_output(struct smb_charger *chg, union power_supply_propval *val) { int rc = 0; if (!chg->wls_psy) { chg->wls_psy = power_supply_get_by_name("wireless"); if (!chg->wls_psy) { val->intval = -EINVAL; return 0; } } rc = power_supply_get_property(chg->wls_psy, POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT, val); return rc; } int smblib_get_prop_dc_present(struct smb_charger *chg, union power_supply_propval *val) { int rc; u8 stat; if (chg->chg_param.smb_version == PMI632) { val->intval = 0; return 0; } rc = smblib_read(chg, DCIN_BASE + INT_RT_STS_OFFSET, &stat); if (rc < 0) { smblib_err(chg, "Couldn't read DCIN_RT_STS rc=%d\n", rc); return rc; } val->intval = (bool)(stat & DCIN_PLUGIN_RT_STS_BIT); return 0; } int smblib_get_prop_dc_online(struct smb_charger *chg, union power_supply_propval *val) { int rc = 0; u8 stat; if (chg->chg_param.smb_version == PMI632) { val->intval = 0; return 0; } if (get_client_vote(chg->dc_suspend_votable, USER_VOTER)) { val->intval = false; return rc; } if (is_client_vote_enabled(chg->dc_suspend_votable, CHG_TERMINATION_VOTER)) { rc = smblib_get_prop_dc_present(chg, val); return rc; } rc = smblib_read(chg, POWER_PATH_STATUS_REG, &stat); if (rc < 0) { smblib_err(chg, "Couldn't read POWER_PATH_STATUS rc=%d\n", rc); return rc; } smblib_dbg(chg, PR_REGISTER, "POWER_PATH_STATUS = 0x%02x\n", stat); val->intval = (stat & USE_DCIN_BIT) && (stat & VALID_INPUT_POWER_SOURCE_STS_BIT); return rc; } int smblib_get_prop_dc_current_max(struct smb_charger *chg, union power_supply_propval *val) { return smblib_get_charge_param(chg, &chg->param.dc_icl, &val->intval); } int smblib_get_prop_dc_voltage_max(struct smb_charger *chg, union power_supply_propval *val) { int rc; val->intval = MICRO_12V; if (!chg->wls_psy) chg->wls_psy = power_supply_get_by_name("wireless"); if (chg->wls_psy) { rc = power_supply_get_property(chg->wls_psy, POWER_SUPPLY_PROP_VOLTAGE_MAX, val); if (rc < 0) { dev_err(chg->dev, "Couldn't get VOLTAGE_MAX, rc=%d\n", rc); return rc; } } return 0; } int smblib_get_prop_dc_voltage_now(struct smb_charger *chg, union power_supply_propval *val) { int rc = 0; if (!chg->wls_psy) { chg->wls_psy = power_supply_get_by_name("wireless"); if (!chg->wls_psy) { val->intval = -EINVAL; return 0; } } rc = power_supply_get_property(chg->wls_psy, POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT, val); if (rc < 0) { dev_err(chg->dev, "Couldn't get POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT, rc=%d\n", rc); return rc; } return rc; } /******************* * DC PSY SETTERS * *******************/ int smblib_set_prop_dc_current_max(struct smb_charger *chg, const union power_supply_propval *val) { chg->dcin_icl_user_set = true; return smblib_set_charge_param(chg, &chg->param.dc_icl, val->intval); } #define DCIN_AICL_RERUN_DELAY_MS 5000 int smblib_set_prop_voltage_wls_output(struct smb_charger *chg, union power_supply_propval *val) { int rc = 0; if (!chg->wls_psy) { chg->wls_psy = power_supply_get_by_name("wireless"); if (!chg->wls_psy) return -ENODEV; } rc = power_supply_set_property(chg->wls_psy, POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT, val); if (rc < 0) dev_err(chg->dev, "Couldn't set POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT, rc=%d\n", rc); smblib_dbg(chg, PR_WLS, "%d\n", val->intval); /* * When WLS VOUT goes down, the power-constrained adaptor may be able * to supply more current, so allow it to do so - unless userspace has * changed DCIN ICL value already due to thermal considerations. */ if (!chg->dcin_icl_user_set && (val->intval > 0) && (val->intval < chg->last_wls_vout)) { alarm_start_relative(&chg->dcin_aicl_alarm, ms_to_ktime(DCIN_AICL_RERUN_DELAY_MS)); } chg->last_wls_vout = val->intval; return rc; } int smblib_set_prop_dc_reset(struct smb_charger *chg) { int rc; rc = vote(chg->dc_suspend_votable, VOUT_VOTER, true, 0); if (rc < 0) { smblib_err(chg, "Couldn't suspend DC rc=%d\n", rc); return rc; } rc = smblib_masked_write(chg, DCIN_CMD_IL_REG, DCIN_EN_MASK, DCIN_EN_OVERRIDE_BIT); if (rc < 0) { smblib_err(chg, "Couldn't set DCIN_EN_OVERRIDE_BIT rc=%d\n", rc); return rc; } rc = smblib_write(chg, DCIN_CMD_PON_REG, DCIN_PON_BIT | MID_CHG_BIT); if (rc < 0) { smblib_err(chg, "Couldn't write %d to DCIN_CMD_PON_REG rc=%d\n", DCIN_PON_BIT | MID_CHG_BIT, rc); return rc; } /* Wait for 10ms to allow the charge to get drained */ usleep_range(10000, 10010); rc = smblib_write(chg, DCIN_CMD_PON_REG, 0); if (rc < 0) { smblib_err(chg, "Couldn't clear DCIN_CMD_PON_REG rc=%d\n", rc); return rc; } rc = smblib_masked_write(chg, DCIN_CMD_IL_REG, DCIN_EN_MASK, 0); if (rc < 0) { smblib_err(chg, "Couldn't clear DCIN_EN_OVERRIDE_BIT rc=%d\n", rc); return rc; } rc = vote(chg->dc_suspend_votable, VOUT_VOTER, false, 0); if (rc < 0) { smblib_err(chg, "Couldn't unsuspend DC rc=%d\n", rc); return rc; } smblib_dbg(chg, PR_MISC, "Wireless charger removal detection successful\n"); return rc; } /******************* * USB PSY GETTERS * *******************/ int smblib_get_prop_usb_present(struct smb_charger *chg, union power_supply_propval *val) { int rc; u8 stat; rc = smblib_read(chg, USBIN_BASE + INT_RT_STS_OFFSET, &stat); if (rc < 0) { smblib_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 smblib_get_prop_usb_online(struct smb_charger *chg, union power_supply_propval *val) { int rc = 0; u8 stat; if (get_client_vote_locked(chg->usb_icl_votable, USER_VOTER) == 0) { val->intval = false; return rc; } if (is_client_vote_enabled_locked(chg->usb_icl_votable, CHG_TERMINATION_VOTER)) { rc = smblib_get_prop_usb_present(chg, val); return rc; } rc = smblib_read(chg, POWER_PATH_STATUS_REG, &stat); if (rc < 0) { smblib_err(chg, "Couldn't read POWER_PATH_STATUS rc=%d\n", rc); return rc; } smblib_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 smblib_get_usb_online(struct smb_charger *chg, union power_supply_propval *val) { int rc; rc = smblib_get_prop_usb_online(chg, val); if (!val->intval) goto exit; if (((chg->typec_mode == QTI_POWER_SUPPLY_TYPEC_SOURCE_DEFAULT) || (chg->connector_type == QTI_POWER_SUPPLY_CONNECTOR_MICRO_USB)) && (chg->real_charger_type == POWER_SUPPLY_TYPE_USB)) val->intval = 0; else val->intval = 1; if (chg->real_charger_type == POWER_SUPPLY_TYPE_UNKNOWN) val->intval = 0; exit: return rc; } int smblib_get_prop_usb_voltage_max_design(struct smb_charger *chg, union power_supply_propval *val) { switch (chg->real_charger_type) { case QTI_POWER_SUPPLY_TYPE_USB_HVDCP: if (chg->qc2_unsupported_voltage == QC2_NON_COMPLIANT_9V) { val->intval = MICRO_5V; break; } else if (chg->qc2_unsupported_voltage == QC2_NON_COMPLIANT_12V) { val->intval = MICRO_9V; break; } fallthrough; case QTI_POWER_SUPPLY_TYPE_USB_HVDCP_3P5: case QTI_POWER_SUPPLY_TYPE_USB_HVDCP_3: case POWER_SUPPLY_TYPE_USB_PD: if (chg->chg_param.smb_version == PMI632) val->intval = MICRO_9V; else val->intval = MICRO_12V; break; default: val->intval = MICRO_5V; break; } return 0; } int smblib_get_prop_usb_voltage_max(struct smb_charger *chg, union power_supply_propval *val) { switch (chg->real_charger_type) { case QTI_POWER_SUPPLY_TYPE_USB_HVDCP: if (chg->qc2_unsupported_voltage == QC2_NON_COMPLIANT_9V) { val->intval = MICRO_5V; break; } else if (chg->qc2_unsupported_voltage == QC2_NON_COMPLIANT_12V) { val->intval = MICRO_9V; break; } fallthrough; case QTI_POWER_SUPPLY_TYPE_USB_HVDCP_3P5: case QTI_POWER_SUPPLY_TYPE_USB_HVDCP_3: if (chg->chg_param.smb_version == PMI632) val->intval = MICRO_9V; else val->intval = MICRO_12V; break; case POWER_SUPPLY_TYPE_USB_PD: val->intval = chg->voltage_max_uv; break; default: val->intval = MICRO_5V; break; } return 0; } #define HVDCP3_STEP_UV 200000 #define HVDCP3P5_STEP_UV 20000 static int smblib_estimate_adaptor_voltage(struct smb_charger *chg, union power_supply_propval *val) { int step_uv = HVDCP3_STEP_UV; switch (chg->real_charger_type) { case QTI_POWER_SUPPLY_TYPE_USB_HVDCP: val->intval = MICRO_12V; break; case QTI_POWER_SUPPLY_TYPE_USB_HVDCP_3P5: step_uv = HVDCP3P5_STEP_UV; fallthrough; case QTI_POWER_SUPPLY_TYPE_USB_HVDCP_3: val->intval = MICRO_5V + (step_uv * chg->pulse_cnt); break; case POWER_SUPPLY_TYPE_USB_PD: /* Take the average of min and max values */ val->intval = chg->voltage_min_uv + ((chg->voltage_max_uv - chg->voltage_min_uv) / 2); break; default: val->intval = MICRO_5V; break; } return 0; } static int smblib_read_mid_voltage_chan(struct smb_charger *chg, union power_supply_propval *val) { int rc; if (!chg->iio.mid_chan) return -ENODATA; rc = iio_read_channel_processed(chg->iio.mid_chan, &val->intval); if (rc < 0) { smblib_err(chg, "Couldn't read MID channel rc=%d\n", rc); return rc; } /* * If MID voltage < 1V, it is unreliable. * Figure out voltage from registers and calculations. */ if (val->intval < 1000000) return smblib_estimate_adaptor_voltage(chg, val); return 0; } static int smblib_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) { smblib_err(chg, "Couldn't read USBIN channel rc=%d\n", rc); return rc; } return 0; } int smblib_get_prop_usb_voltage_now(struct smb_charger *chg, union power_supply_propval *val) { union power_supply_propval pval = {0, }; int rc, ret = 0; u8 reg, adc_ch_reg; mutex_lock(&chg->adc_lock); if (chg->wa_flags & USBIN_ADC_WA) { /* Store ADC channel config in order to restore later */ rc = smblib_read(chg, BATIF_ADC_CHANNEL_EN_REG, &adc_ch_reg); if (rc < 0) { smblib_err(chg, "Couldn't read ADC config rc=%d\n", rc); ret = rc; goto unlock; } /* Disable all ADC channels except IBAT channel */ rc = smblib_write(chg, BATIF_ADC_CHANNEL_EN_REG, IBATT_CHANNEL_EN_BIT); if (rc < 0) { smblib_err(chg, "Couldn't disable ADC channels rc=%d\n", rc); ret = rc; goto unlock; } } rc = smblib_get_prop_usb_present(chg, &pval); if (rc < 0) { smblib_err(chg, "Couldn't get usb presence status rc=%d\n", rc); ret = -ENODATA; goto restore_adc_config; } /* * Skip reading voltage only if USB is not present and we are not in * OTG mode. */ if (!pval.intval) { rc = smblib_read(chg, DCDC_CMD_OTG_REG, ®); if (rc < 0) { smblib_err(chg, "Couldn't read CMD_OTG rc=%d", rc); goto restore_adc_config; } if (!(reg & OTG_EN_BIT)) goto restore_adc_config; } /* * For PM8150B, use MID_CHG ADC channel because overvoltage is observed * to occur randomly in the USBIN channel, particularly at high * voltages. */ if (chg->chg_param.smb_version == PM8150B) rc = smblib_read_mid_voltage_chan(chg, val); else rc = smblib_read_usbin_voltage_chan(chg, val); if (rc < 0) { smblib_err(chg, "Failed to read USBIN over vadc, rc=%d\n", rc); ret = rc; } restore_adc_config: /* Restore ADC channel config */ if (chg->wa_flags & USBIN_ADC_WA) { rc = smblib_write(chg, BATIF_ADC_CHANNEL_EN_REG, adc_ch_reg); if (rc < 0) smblib_err(chg, "Couldn't write ADC config rc=%d\n", rc); } unlock: mutex_unlock(&chg->adc_lock); return ret; } int smblib_get_prop_vph_voltage_now(struct smb_charger *chg, int *val) { int rc; if (!chg->iio.vph_v_chan) return -ENODATA; rc = iio_read_channel_processed(chg->iio.vph_v_chan, val); if (rc < 0) { smblib_err(chg, "Couldn't read vph channel rc=%d\n", rc); return rc; } return 0; } static bool smblib_rsbux_low(struct smb_charger *chg, int r_thr) { int r_sbu1, r_sbu2; bool ret = false; int rc; if (!chg->iio.sbux_chan) return false; /* disable crude sensors */ rc = smblib_masked_write(chg, TYPE_C_CRUDE_SENSOR_CFG_REG, EN_SRC_CRUDE_SENSOR_BIT | EN_SNK_CRUDE_SENSOR_BIT, 0); if (rc < 0) { smblib_err(chg, "Couldn't disable crude sensor rc=%d\n", rc); return false; } /* select SBU1 as current source */ rc = smblib_write(chg, TYPE_C_SBU_CFG_REG, SEL_SBU1_ISRC_VAL); if (rc < 0) { smblib_err(chg, "Couldn't select SBU1 rc=%d\n", rc); goto cleanup; } rc = iio_read_channel_processed(chg->iio.sbux_chan, &r_sbu1); if (rc < 0) { smblib_err(chg, "Couldn't read SBU1 rc=%d\n", rc); goto cleanup; } if (r_sbu1 < r_thr) { ret = true; goto cleanup; } /* select SBU2 as current source */ rc = smblib_write(chg, TYPE_C_SBU_CFG_REG, SEL_SBU2_ISRC_VAL); if (rc < 0) { smblib_err(chg, "Couldn't select SBU1 rc=%d\n", rc); goto cleanup; } rc = iio_read_channel_processed(chg->iio.sbux_chan, &r_sbu2); if (rc < 0) { smblib_err(chg, "Couldn't read SBU1 rc=%d\n", rc); goto cleanup; } if (r_sbu2 < r_thr) ret = true; cleanup: /* enable crude sensors */ rc = smblib_masked_write(chg, TYPE_C_CRUDE_SENSOR_CFG_REG, EN_SRC_CRUDE_SENSOR_BIT | EN_SNK_CRUDE_SENSOR_BIT, EN_SRC_CRUDE_SENSOR_BIT | EN_SNK_CRUDE_SENSOR_BIT); if (rc < 0) smblib_err(chg, "Couldn't enable crude sensor rc=%d\n", rc); /* disable current source */ rc = smblib_write(chg, TYPE_C_SBU_CFG_REG, 0); if (rc < 0) smblib_err(chg, "Couldn't select SBU1 rc=%d\n", rc); return ret; } int smblib_get_prop_charger_temp(struct smb_charger *chg, int *val) { int temp, rc; int input_present; rc = smblib_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 smblib_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 = smblib_read(chg, TYPE_C_MISC_STATUS_REG, &stat); if (rc < 0) { smblib_err(chg, "Couldn't read TYPE_C_STATUS_4 rc=%d\n", rc); return rc; } smblib_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 smblib_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 smblib_get_prop_ufp_mode(struct smb_charger *chg) { int rc; u8 stat; rc = smblib_read(chg, TYPE_C_SNK_STATUS_REG, &stat); if (rc < 0) { smblib_err(chg, "Couldn't read TYPE_C_STATUS_1 rc=%d\n", rc); return QTI_POWER_SUPPLY_TYPEC_NONE; } smblib_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 smblib_get_prop_dfp_mode(struct smb_charger *chg) { int rc; u8 stat; if (chg->lpd_stage == LPD_STAGE_COMMIT) return QTI_POWER_SUPPLY_TYPEC_NONE; rc = smblib_read(chg, TYPE_C_SRC_STATUS_REG, &stat); if (rc < 0) { smblib_err(chg, "Couldn't read TYPE_C_SRC_STATUS_REG rc=%d\n", rc); return QTI_POWER_SUPPLY_TYPEC_NONE; } smblib_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_RA_VCONN_BIT: return QTI_POWER_SUPPLY_TYPEC_SINK_POWERED_CABLE; case SRC_RD_OPEN_BIT: return QTI_POWER_SUPPLY_TYPEC_SINK; default: break; } return QTI_POWER_SUPPLY_TYPEC_NONE; } static int smblib_get_prop_typec_mode(struct smb_charger *chg) { int rc; u8 stat; rc = smblib_read(chg, TYPE_C_MISC_STATUS_REG, &stat); if (rc < 0) { smblib_err(chg, "Couldn't read TYPE_C_MISC_STATUS_REG rc=%d\n", rc); return 0; } smblib_dbg(chg, PR_REGISTER, "TYPE_C_MISC_STATUS_REG = 0x%02x\n", stat); if (stat & SNK_SRC_MODE_BIT) return smblib_get_prop_dfp_mode(chg); else return smblib_get_prop_ufp_mode(chg); } inline int smblib_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 { chg->typec_mode = smblib_get_prop_typec_mode(chg); *val = chg->typec_mode; } return 0; } inline int smblib_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 smblib_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; } spin_lock(&chg->typec_pr_lock); rc = smblib_read(chg, TYPE_C_MODE_CFG_REG, &ctrl); if (rc < 0) { smblib_err(chg, "Couldn't read TYPE_C_MODE_CFG_REG rc=%d\n", rc); goto unlock; } smblib_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; goto unlock; } 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; smblib_err(chg, "unsupported power role 0x%02lx\n", ctrl & (EN_SRC_ONLY_BIT | EN_SNK_ONLY_BIT)); rc = -EINVAL; goto unlock; } chg->power_role = *val; unlock: spin_unlock(&chg->typec_pr_lock); return rc; } static inline bool typec_in_src_mode(struct smb_charger *chg) { return (chg->typec_mode > QTI_POWER_SUPPLY_TYPEC_NONE && chg->typec_mode < QTI_POWER_SUPPLY_TYPEC_SOURCE_DEFAULT); } int smblib_get_prop_typec_select_rp(struct smb_charger *chg, int *val) { int rc, rp; u8 stat; if (!typec_in_src_mode(chg)) { *val = -EINVAL; return 0; } rc = smblib_read(chg, TYPE_C_CURRSRC_CFG_REG, &stat); if (rc < 0) { smblib_err(chg, "Couldn't read TYPE_C_CURRSRC_CFG_REG rc=%d\n", rc); return rc; } switch (stat & TYPEC_SRC_RP_SEL_MASK) { case TYPEC_SRC_RP_STD: rp = QTI_POWER_SUPPLY_TYPEC_SRC_RP_STD; break; case TYPEC_SRC_RP_1P5A: rp = QTI_POWER_SUPPLY_TYPEC_SRC_RP_1P5A; break; case TYPEC_SRC_RP_3A: case TYPEC_SRC_RP_3A_DUPLICATE: rp = QTI_POWER_SUPPLY_TYPEC_SRC_RP_3A; break; default: return -EINVAL; } *val = rp; return 0; } int smblib_get_prop_usb_current_now(struct smb_charger *chg, union power_supply_propval *val) { union power_supply_propval pval = {0, }; int rc = 0, buck_scale = 1, boost_scale = 1; if (chg->iio.usbin_i_chan) { rc = iio_read_channel_processed(chg->iio.usbin_i_chan, &val->intval); if (rc < 0) { pr_err("Error in reading USBIN_I channel, rc=%d\n", rc); return rc; } /* * For PM8150B, scaling factor = reciprocal of * 0.2V/A in Buck mode, 0.4V/A in Boost mode. * For PMI632, scaling factor = reciprocal of * 0.4V/A in Buck mode, 0.8V/A in Boost mode. */ switch (chg->chg_param.smb_version) { case PMI632: buck_scale = 40; boost_scale = 80; break; default: buck_scale = 20; boost_scale = 40; break; } if (chg->otg_present || smblib_get_prop_dfp_mode(chg) != QTI_POWER_SUPPLY_TYPEC_NONE) { val->intval = DIV_ROUND_CLOSEST(val->intval * 100, boost_scale); return rc; } rc = smblib_get_prop_usb_present(chg, &pval); if (rc < 0) { smblib_err(chg, "Couldn't get usb present status,rc=%d\n", rc); return -ENODATA; } /* If USB is not present, return 0 */ if (!pval.intval) val->intval = 0; else val->intval = DIV_ROUND_CLOSEST(val->intval * 100, buck_scale); } else { val->intval = 0; rc = -ENODATA; } return rc; } int smblib_get_prop_low_power(struct smb_charger *chg, union power_supply_propval *val) { int rc; u8 stat; if (chg->sink_src_mode != SRC_MODE) return -ENODATA; rc = smblib_read(chg, TYPE_C_SRC_STATUS_REG, &stat); if (rc < 0) { smblib_err(chg, "Couldn't read TYPE_C_SRC_STATUS_REG rc=%d\n", rc); return rc; } val->intval = !(stat & SRC_HIGH_BATT_BIT); return 0; } int smblib_get_prop_input_current_settled(struct smb_charger *chg, union power_supply_propval *val) { return smblib_get_charge_param(chg, &chg->param.icl_stat, &val->intval); } int smblib_get_prop_input_current_max(struct smb_charger *chg, union power_supply_propval *val) { int icl_ua = 0, rc; rc = smblib_get_charge_param(chg, &chg->param.usb_icl, &icl_ua); if (rc < 0) return rc; if (is_override_vote_enabled_locked(chg->usb_icl_votable) && icl_ua < USBIN_1000MA) { val->intval = USBIN_1000MA; return 0; } return smblib_get_charge_param(chg, &chg->param.icl_stat, &val->intval); } int smblib_get_prop_input_voltage_settled(struct smb_charger *chg, int *val) { int rc, pulses; int step_uv = HVDCP3_STEP_UV; switch (chg->real_charger_type) { case QTI_POWER_SUPPLY_TYPE_USB_HVDCP_3P5: step_uv = HVDCP3P5_STEP_UV; fallthrough; case QTI_POWER_SUPPLY_TYPE_USB_HVDCP_3: rc = smblib_get_pulse_cnt(chg, &pulses); if (rc < 0) { smblib_err(chg, "Couldn't read QC_PULSE_COUNT rc=%d\n", rc); return 0; } *val = MICRO_5V + step_uv * pulses; break; case POWER_SUPPLY_TYPE_USB_PD: *val = chg->voltage_min_uv; break; default: *val = MICRO_5V; break; } return 0; } int smblib_get_prop_pd_in_hard_reset(struct smb_charger *chg, int *val) { *val = chg->pd_hard_reset; return 0; } int smblib_get_pe_start(struct smb_charger *chg, int *val) { *val = chg->ok_to_pd; return 0; } int smblib_get_prop_smb_health(struct smb_charger *chg) { int rc, val; int input_present; rc = smblib_is_input_present(chg, &input_present); if (rc < 0) return rc; if ((input_present == INPUT_NOT_PRESENT) || (!is_cp_available(chg))) return POWER_SUPPLY_HEALTH_UNKNOWN; rc = smblib_read_iio_prop(chg, CP, CP_DIE_TEMP, &val); if (rc < 0) { smblib_err(chg, "Couldn't get CP DIE_TEMP rc=%d\n", rc); return rc; } if (val > SMB_TEMP_RST_THRESH) return POWER_SUPPLY_HEALTH_OVERHEAT; if (val > SMB_TEMP_REG_H_THRESH) return POWER_SUPPLY_HEALTH_HOT; if (val > SMB_TEMP_REG_L_THRESH) return POWER_SUPPLY_HEALTH_WARM; return POWER_SUPPLY_HEALTH_COOL; } static int smblib_get_prop_die_health(struct smb_charger *chg) { int rc; u8 stat; int input_present; rc = smblib_is_input_present(chg, &input_present); if (rc < 0) return rc; if (input_present == INPUT_NOT_PRESENT) return POWER_SUPPLY_HEALTH_UNKNOWN; if (chg->wa_flags & SW_THERM_REGULATION_WA) { if (chg->die_temp == -ENODATA) return POWER_SUPPLY_HEALTH_UNKNOWN; if (chg->die_temp > DIE_TEMP_RST_THRESH) return POWER_SUPPLY_HEALTH_OVERHEAT; if (chg->die_temp > DIE_TEMP_REG_H_THRESH) return POWER_SUPPLY_HEALTH_HOT; if (chg->die_temp > DIE_TEMP_REG_L_THRESH) return POWER_SUPPLY_HEALTH_WARM; return POWER_SUPPLY_HEALTH_COOL; } rc = smblib_read(chg, DIE_TEMP_STATUS_REG, &stat); if (rc < 0) { smblib_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 smblib_get_die_health(struct smb_charger *chg, int *val) { if (chg->die_health == -EINVAL) *val = smblib_get_prop_die_health(chg); else *val = chg->die_health; return 0; } int smblib_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 = smblib_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 smblib_get_typec_connector_temp_status(struct smb_charger *chg) { int rc; u8 stat; if (chg->connector_health != -EINVAL) return chg->connector_health; if (chg->wa_flags & SW_THERM_REGULATION_WA) { if (chg->connector_temp == -ENODATA) return POWER_SUPPLY_HEALTH_UNKNOWN; if (chg->connector_temp > CONNECTOR_TEMP_RST_THRESH) return POWER_SUPPLY_HEALTH_OVERHEAT; if (chg->connector_temp > CONNECTOR_TEMP_REG_H_THRESH) return POWER_SUPPLY_HEALTH_HOT; if (chg->connector_temp > CONNECTOR_TEMP_REG_L_THRESH) return POWER_SUPPLY_HEALTH_WARM; return POWER_SUPPLY_HEALTH_COOL; } rc = smblib_read(chg, CONNECTOR_TEMP_STATUS_REG, &stat); if (rc < 0) { smblib_err(chg, "Couldn't read CONNECTOR_TEMP_STATUS_REG, rc=%d\n", rc); return POWER_SUPPLY_HEALTH_UNKNOWN; } if (stat & CONNECTOR_TEMP_RST_BIT) return POWER_SUPPLY_HEALTH_OVERHEAT; if (stat & CONNECTOR_TEMP_UB_BIT) return POWER_SUPPLY_HEALTH_HOT; if (stat & CONNECTOR_TEMP_LB_BIT) return POWER_SUPPLY_HEALTH_WARM; return POWER_SUPPLY_HEALTH_COOL; } int smblib_get_skin_temp_status(struct smb_charger *chg) { int rc; u8 stat; if (!chg->en_skin_therm_mitigation) return POWER_SUPPLY_HEALTH_UNKNOWN; rc = smblib_read(chg, SKIN_TEMP_STATUS_REG, &stat); if (rc < 0) { smblib_err(chg, "Couldn't read SKIN_TEMP_STATUS_REG, rc=%d\n", rc); return POWER_SUPPLY_HEALTH_UNKNOWN; } if (stat & SKIN_TEMP_RST_BIT) return POWER_SUPPLY_HEALTH_OVERHEAT; if (stat & SKIN_TEMP_UB_BIT) return POWER_SUPPLY_HEALTH_HOT; if (stat & SKIN_TEMP_LB_BIT) return POWER_SUPPLY_HEALTH_WARM; return POWER_SUPPLY_HEALTH_COOL; } int smblib_get_prop_connector_health(struct smb_charger *chg) { bool dc_present, usb_present; int input_present; int rc; rc = smblib_is_input_present(chg, &input_present); if (rc < 0) return POWER_SUPPLY_HEALTH_UNKNOWN; dc_present = input_present & INPUT_PRESENT_DC; usb_present = input_present & INPUT_PRESENT_USB; if (usb_present) return smblib_get_typec_connector_temp_status(chg); /* * In PM8150B, SKIN channel measures Wireless charger receiver * temp, used to regulate DC ICL. */ if (chg->chg_param.smb_version == PM8150B && dc_present) return smblib_get_skin_temp_status(chg); return POWER_SUPPLY_HEALTH_COOL; } 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: fallthrough; case QTI_POWER_SUPPLY_TYPEC_SOURCE_DEFAULT: fallthrough; default: rp_ua = DCP_CURRENT_UA; } return rp_ua; } /******************* * USB PSY SETTERS * * *****************/ int smblib_set_prop_pd_current_max(struct smb_charger *chg, int val) { int rc, icl; if (chg->pd_active) { icl = get_client_vote(chg->usb_icl_votable, PD_VOTER); rc = vote(chg->usb_icl_votable, PD_VOTER, true, val); if (val != icl) power_supply_changed(chg->usb_psy); } else { rc = -EPERM; } return rc; } static int smblib_handle_usb_current(struct smb_charger *chg, int usb_current) { int rc = 0, rp_ua, typec_mode; union power_supply_propval val = {0, }; if (chg->real_charger_type == QTI_POWER_SUPPLY_TYPE_USB_FLOAT) { if (usb_current == -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_500MA); if (rc < 0) smblib_err(chg, "Couldn't set SDP ICL rc=%d\n", rc); return rc; } if (chg->connector_type == QTI_POWER_SUPPLY_CONNECTOR_TYPEC) { /* * Valid FLOAT charger, report the current * based of Rp. */ typec_mode = smblib_get_prop_typec_mode(chg); rp_ua = get_rp_based_dcp_current(chg, typec_mode); rc = vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, true, rp_ua); if (rc < 0) return rc; } else { 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, usb_current); if (rc < 0) return rc; rc = vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, false, 0); if (rc < 0) return rc; } } else { rc = smblib_get_prop_usb_present(chg, &val); if (!rc && !val.intval) return 0; /* if flash is active force 500mA */ if ((usb_current < SDP_CURRENT_UA) && is_flash_active(chg)) usb_current = SDP_CURRENT_UA; rc = vote(chg->usb_icl_votable, USB_PSY_VOTER, true, usb_current); 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; } } return 0; } int smblib_set_prop_sdp_current_max(struct smb_charger *chg, int val) { union power_supply_propval pval; int rc = 0; if (!chg->pd_active) { rc = smblib_get_prop_usb_present(chg, &pval); if (rc < 0) { smblib_err(chg, "Couldn't get usb present rc = %d\n", rc); return rc; } /* handle the request only when USB is present */ if (pval.intval) rc = smblib_handle_usb_current(chg, val); } else if (chg->system_suspend_supported) { if (val <= USBIN_25MA) rc = vote(chg->usb_icl_votable, PD_SUSPEND_SUPPORTED_VOTER, true, val); else rc = vote(chg->usb_icl_votable, PD_SUSPEND_SUPPORTED_VOTER, false, 0); } return rc; } int smblib_set_prop_usb_voltage_max_limit(struct smb_charger *chg, int val) { union power_supply_propval pval = {0, }; /* Exit if same value is re-configured */ if (val == chg->usbin_forced_max_uv) return 0; smblib_get_prop_usb_voltage_max_design(chg, &pval); if (val >= MICRO_5V && val <= pval.intval) { chg->usbin_forced_max_uv = val; smblib_dbg(chg, PR_MISC, "Max VBUS limit changed to: %d\n", val); } else if (chg->usbin_forced_max_uv) { chg->usbin_forced_max_uv = 0; } else { return 0; } power_supply_changed(chg->usb_psy); return 0; } static void smblib_typec_irq_config(struct smb_charger *chg, bool en) { if (en == chg->typec_irq_en) return; if (en) { enable_irq( chg->irq_info[TYPEC_ATTACH_DETACH_IRQ].irq); enable_irq( chg->irq_info[TYPEC_CC_STATE_CHANGE_IRQ].irq); enable_irq( chg->irq_info[TYPEC_OR_RID_DETECTION_CHANGE_IRQ].irq); } else { disable_irq_nosync( chg->irq_info[TYPEC_ATTACH_DETACH_IRQ].irq); disable_irq_nosync( chg->irq_info[TYPEC_CC_STATE_CHANGE_IRQ].irq); disable_irq_nosync( chg->irq_info[TYPEC_OR_RID_DETECTION_CHANGE_IRQ].irq); } chg->typec_irq_en = en; } #define PR_LOCK_TIMEOUT_MS 1000 int smblib_set_prop_typec_power_role(struct smb_charger *chg, int val) { int rc = 0; u8 power_role; enum power_supply_typec_mode typec_mode; bool snk_attached = false, src_attached = false, is_pr_lock = false; if (chg->connector_type == QTI_POWER_SUPPLY_CONNECTOR_MICRO_USB) return 0; spin_lock(&chg->typec_pr_lock); smblib_dbg(chg, PR_MISC, "power role change: %d --> %d!", chg->power_role, val); /* Force the power-role if the initial value is NONE, for the legacy cable detection WA. */ if (chg->power_role == val && chg->power_role != QTI_POWER_SUPPLY_TYPEC_PR_NONE) { smblib_dbg(chg, PR_MISC, "power role already in %d, ignore!", chg->power_role); goto unlock; } typec_mode = smblib_get_prop_typec_mode(chg); if (typec_mode >= QTI_POWER_SUPPLY_TYPEC_SINK && typec_mode <= QTI_POWER_SUPPLY_TYPEC_SINK_AUDIO_ADAPTER) snk_attached = true; else if (typec_mode >= QTI_POWER_SUPPLY_TYPEC_SOURCE_DEFAULT && typec_mode <= QTI_POWER_SUPPLY_TYPEC_SOURCE_HIGH) src_attached = true; /* * If current power role is in DRP, and type-c is already in the * mode (source or sink) that's being requested, it means this is * a power role locking request from USBPD driver. Disable type-c * related interrupts for locking power role to avoid the redundant * notifications. */ if ((chg->power_role == QTI_POWER_SUPPLY_TYPEC_PR_DUAL) && ((src_attached && val == QTI_POWER_SUPPLY_TYPEC_PR_SINK) || (snk_attached && val == QTI_POWER_SUPPLY_TYPEC_PR_SOURCE))) is_pr_lock = true; smblib_dbg(chg, PR_MISC, "snk_attached = %d, src_attached = %d, is_pr_lock = %d\n", snk_attached, src_attached, is_pr_lock); cancel_delayed_work(&chg->pr_lock_clear_work); if (!chg->pr_lock_in_progress && is_pr_lock) { smblib_dbg(chg, PR_MISC, "disable type-c interrupts for power role locking\n"); smblib_typec_irq_config(chg, false); schedule_delayed_work(&chg->pr_lock_clear_work, msecs_to_jiffies(PR_LOCK_TIMEOUT_MS)); } else if (chg->pr_lock_in_progress && !is_pr_lock) { smblib_dbg(chg, PR_MISC, "restore type-c interrupts after exit power role locking\n"); smblib_typec_irq_config(chg, true); } chg->pr_lock_in_progress = is_pr_lock; 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 = chg->typec_try_mode; 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: smblib_err(chg, "power role %d not supported\n", val); rc = -EINVAL; goto unlock; } rc = smblib_masked_write(chg, TYPE_C_MODE_CFG_REG, TYPEC_POWER_ROLE_CMD_MASK | TYPEC_TRY_MODE_MASK, power_role); if (rc < 0) { smblib_err(chg, "Couldn't write 0x%02x to TYPE_C_INTRPT_ENB_SOFTWARE_CTRL rc=%d\n", power_role, rc); goto unlock; } chg->power_role = val; unlock: spin_unlock(&chg->typec_pr_lock); return rc; } int smblib_set_prop_typec_select_rp(struct smb_charger *chg, int val) { int rc; if (!typec_in_src_mode(chg)) { smblib_err(chg, "Couldn't set curr src: not in SRC mode\n"); return -EINVAL; } if (val < TYPEC_SRC_RP_MAX_ELEMENTS) { rc = smblib_masked_write(chg, TYPE_C_CURRSRC_CFG_REG, TYPEC_SRC_RP_SEL_MASK, val); if (rc < 0) smblib_err(chg, "Couldn't write to TYPE_C_CURRSRC_CFG rc=%d\n", rc); return rc; } return -EINVAL; } int smblib_set_prop_pd_voltage_min(struct smb_charger *chg, int val) { int rc, min_uv; min_uv = min(val, chg->voltage_max_uv); if (chg->voltage_min_uv == min_uv) return 0; rc = smblib_set_usb_pd_allowed_voltage(chg, min_uv, chg->voltage_max_uv); if (rc < 0) { smblib_err(chg, "invalid min voltage %duV rc=%d\n", val, rc); return rc; } chg->voltage_min_uv = min_uv; power_supply_changed(chg->batt_psy); return rc; } int smblib_set_prop_pd_voltage_max(struct smb_charger *chg, int val) { int rc, max_uv; max_uv = max(val, chg->voltage_min_uv); if (chg->voltage_max_uv == max_uv) return 0; rc = smblib_set_usb_pd_fsw(chg, max_uv); if (rc < 0) { smblib_err(chg, "Couldn't set FSW for voltage %duV rc=%d\n", val, rc); return rc; } rc = smblib_set_usb_pd_allowed_voltage(chg, chg->voltage_min_uv, max_uv); if (rc < 0) { smblib_err(chg, "invalid max voltage %duV rc=%d\n", val, rc); return rc; } chg->voltage_max_uv = max_uv; power_supply_changed(chg->batt_psy); return rc; } int smblib_set_prop_pd_active(struct smb_charger *chg, int val) { const struct apsd_result *apsd = smblib_get_apsd_result(chg); int rc = 0; int sec_charger, typec_mode; /* * Ignore repetitive notification while PD is active, which * is caused by hard reset. */ if (chg->pd_active && chg->pd_active == val) return 0; chg->pd_active = val; smblib_apsd_enable(chg, !chg->pd_active); update_sw_icl_max(chg, apsd->val); if (chg->pd_active) { vote(chg->limited_irq_disable_votable, CHARGER_TYPE_VOTER, false, 0); vote(chg->hdc_irq_disable_votable, CHARGER_TYPE_VOTER, false, 0); /* * Enforce 100mA for PD until the real vote comes in later. * It is guaranteed that pd_active is set prior to * pd_current_max */ vote(chg->usb_icl_votable, PD_VOTER, true, USBIN_100MA); vote(chg->usb_icl_votable, USB_PSY_VOTER, false, 0); vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, false, 0); /* * For PPS, Charge Pump is preferred over parallel charger if * present. */ if (chg->pd_active == QTI_POWER_SUPPLY_PD_PPS_ACTIVE && chg->sec_cp_present) { rc = smblib_select_sec_charger(chg, QTI_POWER_SUPPLY_CHARGER_SEC_CP, QTI_POWER_SUPPLY_CP_PPS, false); if (rc < 0) dev_err(chg->dev, "Couldn't enable secondary charger rc=%d\n", rc); } } else { vote(chg->usb_icl_votable, PD_VOTER, false, 0); vote(chg->limited_irq_disable_votable, CHARGER_TYPE_VOTER, true, 0); vote(chg->hdc_irq_disable_votable, CHARGER_TYPE_VOTER, true, 0); sec_charger = chg->sec_pl_present ? QTI_POWER_SUPPLY_CHARGER_SEC_PL : QTI_POWER_SUPPLY_CHARGER_SEC_NONE; rc = smblib_select_sec_charger(chg, sec_charger, QTI_POWER_SUPPLY_CP_NONE, false); if (rc < 0) dev_err(chg->dev, "Couldn't enable secondary charger rc=%d\n", rc); /* PD hard resets failed, proceed to detect QC2/3 */ if (chg->ok_to_pd) { chg->ok_to_pd = false; smblib_hvdcp_detect_try_enable(chg, true); } } smblib_usb_pd_adapter_allowance_override(chg, !!chg->pd_active ? FORCE_5V : FORCE_NULL); smblib_update_usb_type(chg); if (chg->real_charger_type == POWER_SUPPLY_TYPE_USB && !chg->ok_to_pd) { typec_mode = smblib_get_prop_typec_mode(chg); if (typec_rp_med_high(chg, typec_mode)) vote(chg->usb_icl_votable, USB_PSY_VOTER, false, 0); } power_supply_changed(chg->usb_psy); return rc; } int smblib_set_prop_ship_mode(struct smb_charger *chg, int val) { int rc; smblib_dbg(chg, PR_MISC, "Set ship mode: %d!!\n", !!val); rc = smblib_masked_write(chg, SHIP_MODE_REG, 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; } int smblib_set_prop_pd_in_hard_reset(struct smb_charger *chg, int val) { int rc = 0; if (chg->pd_hard_reset == val) return rc; chg->pd_hard_reset = val; rc = smblib_masked_write(chg, TYPE_C_EXIT_STATE_CFG_REG, EXIT_SNK_BASED_ON_CC_BIT, (chg->pd_hard_reset) ? EXIT_SNK_BASED_ON_CC_BIT : 0); if (rc < 0) smblib_err(chg, "Couldn't set EXIT_SNK_BASED_ON_CC rc=%d\n", rc); return rc; } #define JEITA_SOFT 0 #define JEITA_HARD 1 static int smblib_update_jeita(struct smb_charger *chg, u32 *thresholds, int type) { int rc; u16 temp, base; base = CHGR_JEITA_THRESHOLD_BASE_REG(type); temp = thresholds[1] & 0xFFFF; temp = ((temp & 0xFF00) >> 8) | ((temp & 0xFF) << 8); rc = smblib_batch_write(chg, base, (u8 *)&temp, 2); if (rc < 0) { smblib_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 = smblib_batch_write(chg, base + 2, (u8 *)&temp, 2); if (rc < 0) { smblib_err(chg, "Couldn't configure Jeita %s cold threshold rc=%d\n", (type == JEITA_SOFT) ? "Soft" : "Hard", rc); return rc; } smblib_dbg(chg, PR_MISC, "%s Jeita threshold configured\n", (type == JEITA_SOFT) ? "Soft" : "Hard"); return 0; } static int smblib_charge_inhibit_en(struct smb_charger *chg, bool enable) { int rc; rc = smblib_masked_write(chg, CHGR_CFG2_REG, CHARGER_INHIBIT_BIT, enable ? CHARGER_INHIBIT_BIT : 0); return rc; } static int smblib_soft_jeita_arb_wa(struct smb_charger *chg) { union power_supply_propval pval; int rc = 0; bool soft_jeita; rc = smblib_get_prop_batt_health(chg, &pval); if (rc < 0) { smblib_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 = smblib_charge_inhibit_en(chg, true); if (rc < 0) smblib_err(chg, "Couldn't enable charge inhibit rc=%d\n", rc); rc = smblib_update_jeita(chg, chg->jeita_soft_hys_thlds, JEITA_SOFT); if (rc < 0) smblib_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 = smblib_charge_inhibit_en(chg, false); if (rc < 0) smblib_err(chg, "Couldn't disable charge inhibit rc=%d\n", rc); rc = smblib_update_jeita(chg, chg->jeita_soft_thlds, JEITA_SOFT); if (rc < 0) smblib_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; } smblib_dbg(chg, PR_MISC, "JEITA ARB status %d, soft JEITA status %d\n", chg->jeita_arb_flag, soft_jeita); return rc; } /************************ * USB MAIN PSY GETTERS * ************************/ int smblib_get_prop_fcc_delta(struct smb_charger *chg, int *val) { int rc, jeita_cc_delta_ua = 0; if (chg->sw_jeita_enabled) { *val = 0; return 0; } rc = smblib_get_jeita_cc_delta(chg, &jeita_cc_delta_ua); if (rc < 0) { smblib_err(chg, "Couldn't get jeita cc delta rc=%d\n", rc); jeita_cc_delta_ua = 0; } *val = jeita_cc_delta_ua; return 0; } /************************ * USB MAIN PSY SETTERS * ************************/ int smblib_get_charge_current(struct smb_charger *chg, int *total_current_ua) { const struct apsd_result *apsd_result = smblib_get_apsd_result(chg); union power_supply_propval val = {0, }; int rc = 0, typec_source_rd, current_ua; bool non_compliant; u8 stat; if (chg->pd_active) { *total_current_ua = get_client_vote_locked(chg->usb_icl_votable, PD_VOTER); return rc; } rc = smblib_read(chg, LEGACY_CABLE_STATUS_REG, &stat); if (rc < 0) { smblib_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 = smblib_get_prop_input_current_settled(chg, &val); if (rc < 0) { smblib_err(chg, "Couldn't get settled ICL rc=%d\n", rc); return rc; } typec_source_rd = smblib_get_prop_ufp_mode(chg); /* QC 2.0/3.0 adapter */ if (apsd_result->bit & (QC_3P0_BIT | QC_2P0_BIT)) { *total_current_ua = HVDCP_CURRENT_UA; return 0; } if (non_compliant && !chg->typec_legacy_use_rp_icl) { switch (apsd_result->bit) { case CDP_CHARGER_BIT: current_ua = CDP_CURRENT_UA; break; case DCP_CHARGER_BIT: case OCP_CHARGER_BIT: case FLOAT_CHARGER_BIT: 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 (apsd_result->bit) { case CDP_CHARGER_BIT: current_ua = CDP_CURRENT_UA; break; case DCP_CHARGER_BIT: case OCP_CHARGER_BIT: case FLOAT_CHARGER_BIT: current_ua = chg->default_icl_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; } #define IADP_OVERHEAT_UA 500000 int smblib_set_prop_thermal_overheat(struct smb_charger *chg, int therm_overheat) { int icl_ua = 0; if (chg->thermal_overheat == !!therm_overheat) return 0; /* Configure ICL to 500mA in case system health is Overheat */ if (therm_overheat) icl_ua = IADP_OVERHEAT_UA; if (!chg->cp_disable_votable) chg->cp_disable_votable = find_votable("CP_DISABLE"); if (chg->cp_disable_votable) { vote(chg->cp_disable_votable, OVERHEAT_LIMIT_VOTER, therm_overheat, 0); vote(chg->usb_icl_votable, OVERHEAT_LIMIT_VOTER, therm_overheat, icl_ua); } chg->thermal_overheat = !!therm_overheat; return 0; } /********************** * INTERRUPT HANDLERS * **********************/ irqreturn_t smb5_default_irq_handler(int irq, void *data) { struct smb_irq_data *irq_data = data; struct smb_charger *chg = irq_data->parent_data; smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name); return IRQ_HANDLED; } irqreturn_t smb5_smb_en_irq_handler(int irq, void *data) { struct smb_irq_data *irq_data = data; struct smb_charger *chg = irq_data->parent_data; int rc, input_present; if (!chg->cp_disable_votable) { chg->cp_disable_votable = find_votable("CP_DISABLE"); if (!chg->cp_disable_votable) return IRQ_HANDLED; } if (chg->pd_hard_reset) { vote(chg->cp_disable_votable, BOOST_BACK_VOTER, true, 0); return IRQ_HANDLED; } rc = smblib_is_input_present(chg, &input_present); if (rc < 0) { pr_err("Couldn't get usb presence status rc=%d\n", rc); return IRQ_HANDLED; } if (input_present) { /* * Add some delay to enable SMB1390 switcher after SMB_EN * pin goes high */ usleep_range(1000, 1100); vote(chg->cp_disable_votable, BOOST_BACK_VOTER, false, 0); } return IRQ_HANDLED; } irqreturn_t smb5_sdam_sts_change_irq_handler(int irq, void *data) { struct smb_irq_data *irq_data = data; struct smb_charger *chg = irq_data->parent_data; smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name); mutex_lock(&chg->irq_status_lock); chg->irq_status |= PULSE_SKIP_IRQ_BIT; mutex_unlock(&chg->irq_status_lock); power_supply_changed(chg->batt_psy); return IRQ_HANDLED; } #define CHG_TERM_WA_ENTRY_DELAY_MS 300000 /* 5 min */ #define CHG_TERM_WA_EXIT_DELAY_MS 60000 /* 1 min */ static void smblib_eval_chg_termination(struct smb_charger *chg, u8 batt_status) { union power_supply_propval pval = {0, }; int rc = 0; rc = smblib_get_prop_from_bms(chg, SMB5_QG_REAL_CAPACITY, &pval.intval); if (rc < 0) { smblib_err(chg, "Couldn't read SOC value, rc=%d\n", rc); return; } /* * Post charge termination, switch to BSM mode triggers the risk of * over charging as BATFET opening may take some time post the necessity * of staying in supplemental mode, leading to unintended charging of * battery. Trigger the charge termination WA once charging is completed * to prevent overcharing. */ if ((batt_status == TERMINATE_CHARGE) && (pval.intval == 100) && (ktime_to_ms(alarm_expires_remaining(/* alarm not pending */ &chg->chg_termination_alarm)) <= 0)) { chg->cc_soc_ref = 0; chg->last_cc_soc = 0; chg->term_vbat_uv = 0; alarm_start_relative(&chg->chg_termination_alarm, ms_to_ktime(CHG_TERM_WA_ENTRY_DELAY_MS)); } else if (pval.intval < 100) { /* * Reset CC_SOC reference value for charge termination WA once * we exit the TERMINATE_CHARGE state and soc drops below 100% */ chg->cc_soc_ref = 0; chg->last_cc_soc = 0; chg->term_vbat_uv = 0; } } irqreturn_t smb5_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; int rc; smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name); rc = smblib_read(chg, BATTERY_CHARGER_STATUS_1_REG, &stat); if (rc < 0) { smblib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_1 rc=%d\n", rc); return IRQ_HANDLED; } stat = stat & BATTERY_CHARGER_STATUS_MASK; if (chg->wa_flags & CHG_TERMINATION_WA) smblib_eval_chg_termination(chg, stat); power_supply_changed(chg->batt_psy); return IRQ_HANDLED; } irqreturn_t smb5_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; smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name); if (chg->jeita_configured != JEITA_CFG_COMPLETE) return IRQ_HANDLED; rc = smblib_soft_jeita_arb_wa(chg); if (rc < 0) { smblib_err(chg, "Couldn't fix soft jeita arb rc=%d\n", rc); return IRQ_HANDLED; } return IRQ_HANDLED; } irqreturn_t smb5_batt_psy_changed_irq_handler(int irq, void *data) { struct smb_irq_data *irq_data = data; struct smb_charger *chg = irq_data->parent_data; smblib_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 smb5_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; const struct apsd_result *apsd = smblib_get_apsd_result(chg); int rc; u8 stat = 0, max_pulses = 0; smblib_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) { smblib_dbg(chg, PR_MISC, "USBIN_UV storm at max AICL threshold\n"); return IRQ_HANDLED; } smblib_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 = smblib_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; rc = smblib_set_charge_param(chg, &chg->param.aicl_cont_threshold, chg->aicl_cont_threshold_mv + AICL_STEP_MV); if (rc < 0) dev_err(chg->dev, "Error in setting AICL threshold rc=%d\n", rc); else chg->aicl_cont_threshold_mv += AICL_STEP_MV; unsuspend_input: /* Force torch in boost mode to ensure it works with low ICL */ if (chg->chg_param.smb_version == PMI632) schgm_flash_torch_priority(chg, TORCH_BOOST_MODE); if (chg->aicl_max_reached) { smblib_dbg(chg, PR_MISC, "Reached max AICL threshold resctricting ICL to 100mA\n"); vote(chg->usb_icl_votable, AICL_THRESHOLD_VOTER, true, USBIN_100MA); smblib_run_aicl(chg, RESTART_AICL); } else { smblib_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); /* Workaround for non-QC2.0-compliant chargers follows */ if (!chg->qc2_unsupported_voltage && apsd->val == QTI_POWER_SUPPLY_TYPE_USB_HVDCP) { rc = smblib_read(chg, QC_CHANGE_STATUS_REG, &stat); if (rc < 0) smblib_err(chg, "Couldn't read CHANGE_STATUS_REG rc=%d\n", rc); if (stat & QC_5V_BIT) return IRQ_HANDLED; rc = smblib_read(chg, HVDCP_PULSE_COUNT_MAX_REG, &max_pulses); if (rc < 0) smblib_err(chg, "Couldn't read QC2 max pulses rc=%d\n", rc); chg->qc2_max_pulses = (max_pulses & HVDCP_PULSE_COUNT_MAX_QC2_MASK); if (stat & QC_12V_BIT) { chg->qc2_unsupported_voltage = QC2_NON_COMPLIANT_12V; rc = smblib_masked_write(chg, HVDCP_PULSE_COUNT_MAX_REG, HVDCP_PULSE_COUNT_MAX_QC2_MASK, HVDCP_PULSE_COUNT_MAX_QC2_9V); if (rc < 0) smblib_err(chg, "Couldn't force max pulses to 9V rc=%d\n", rc); } else if (stat & QC_9V_BIT) { chg->qc2_unsupported_voltage = QC2_NON_COMPLIANT_9V; rc = smblib_masked_write(chg, HVDCP_PULSE_COUNT_MAX_REG, HVDCP_PULSE_COUNT_MAX_QC2_MASK, HVDCP_PULSE_COUNT_MAX_QC2_5V); if (rc < 0) smblib_err(chg, "Couldn't force max pulses to 5V rc=%d\n", rc); } rc = smblib_masked_write(chg, USBIN_AICL_OPTIONS_CFG_REG, SUSPEND_ON_COLLAPSE_USBIN_BIT, 0); if (rc < 0) smblib_err(chg, "Couldn't turn off SUSPEND_ON_COLLAPSE_USBIN_BIT rc=%d\n", rc); smblib_rerun_apsd(chg); } return IRQ_HANDLED; } #define USB_WEAK_INPUT_UA 1400000 #define ICL_CHANGE_DELAY_MS 1000 irqreturn_t smb5_icl_change_irq_handler(int irq, void *data) { u8 stat; 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) { /* * Ignore if change in ICL is due to DIE temp mitigation. * This is to prevent any further ICL split. */ if (chg->hw_die_temp_mitigation) { rc = smblib_read(chg, DIE_TEMP_STATUS_REG, &stat); if (rc < 0) { smblib_err(chg, "Couldn't read DIE_TEMP rc=%d\n", rc); return IRQ_HANDLED; } if (stat & (DIE_TEMP_UB_BIT | DIE_TEMP_LB_BIT)) { smblib_dbg(chg, PR_PARALLEL, "skip ICL change DIE_TEMP %x\n", stat); return IRQ_HANDLED; } } rc = smblib_read(chg, AICL_STATUS_REG, &stat); if (rc < 0) { smblib_err(chg, "Couldn't read AICL_STATUS rc=%d\n", rc); return IRQ_HANDLED; } rc = smblib_get_charge_param(chg, &chg->param.icl_stat, &settled_ua); if (rc < 0) { smblib_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 smblib_role_switch_failure(struct smb_charger *chg) { int rc = 0; union power_supply_propval pval = {0, }; if (!chg->use_extcon) return 0; rc = smblib_get_prop_usb_present(chg, &pval); if (rc < 0) { smblib_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) { smblib_dbg(chg, PR_MISC, " Role reversal failed, notifying device mode to usb driver.\n"); smblib_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("failed to register typec_partner rc=%d\n", rc); goto unlock; } } typec_mode = smblib_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 = smblib_role_switch_failure(chg); if (rc < 0) smblib_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) { smblib_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 smblib_micro_usb_plugin(struct smb_charger *chg, bool vbus_rising) { int rc = 0; if (!vbus_rising) { smblib_update_usb_type(chg); smblib_notify_device_mode(chg, false); smblib_uusb_removal(chg); typec_partner_unregister(chg); } else { rc = typec_partner_register(chg); if (rc < 0) smblib_err(chg, "Couldn't register partner rc =%d\n", rc); } } static void smblib_usb_plugin_hard_reset_locked(struct smb_charger *chg) { int rc; u8 stat; bool vbus_rising; struct smb_irq_data *data; struct storm_watch *wdata; rc = smblib_read(chg, USBIN_BASE + INT_RT_STS_OFFSET, &stat); if (rc < 0) { smblib_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); } else { 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); } } /* Force 1500mA FCC on USB removal if fcc stepper is enabled */ if (chg->fcc_stepper_enable) vote(chg->fcc_votable, FCC_STEPPER_VOTER, true, 1500000); } power_supply_changed(chg->usb_psy); smblib_dbg(chg, PR_INTERRUPT, "IRQ: usbin-plugin %s\n", vbus_rising ? "attached" : "detached"); } #define PL_DELAY_MS 30000 static void smblib_usb_plugin_locked(struct smb_charger *chg) { int rc; u8 stat; bool vbus_rising; struct smb_irq_data *data; struct storm_watch *wdata; rc = smblib_read(chg, USBIN_BASE + INT_RT_STS_OFFSET, &stat); if (rc < 0) { smblib_err(chg, "Couldn't read USB_INT_RT_STS rc=%d\n", rc); return; } vbus_rising = (bool)(stat & USBIN_PLUGIN_RT_STS_BIT); smblib_set_opt_switcher_freq(chg, vbus_rising ? chg->chg_freq.freq_5V : chg->chg_freq.freq_removal); if (vbus_rising) { cancel_delayed_work_sync(&chg->pr_swap_detach_work); vote(chg->awake_votable, DETACH_DETECT_VOTER, false, 0); rc = smblib_request_dpdm(chg, true); if (rc < 0) smblib_err(chg, "Couldn't to enable DPDM rc=%d\n", rc); /* Enable SW Thermal regulation */ rc = smblib_set_sw_thermal_regulation(chg, true); if (rc < 0) smblib_err(chg, "Couldn't start SW thermal regulation WA, rc=%d\n", rc); /* 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); /* 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 { /* Disable SW Thermal Regulation */ rc = smblib_set_sw_thermal_regulation(chg, false); if (rc < 0) smblib_err(chg, "Couldn't stop SW thermal regulation WA, rc=%d\n", rc); 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); } } /* Force 1500mA FCC on removal if fcc stepper is enabled */ if (chg->fcc_stepper_enable) vote(chg->fcc_votable, FCC_STEPPER_VOTER, true, 1500000); if (chg->wa_flags & WEAK_ADAPTER_WA) { chg->aicl_5v_threshold_mv = chg->default_aicl_5v_threshold_mv; chg->aicl_cont_threshold_mv = chg->default_aicl_cont_threshold_mv; smblib_set_charge_param(chg, &chg->param.aicl_5v_threshold, chg->aicl_5v_threshold_mv); smblib_set_charge_param(chg, &chg->param.aicl_cont_threshold, chg->aicl_cont_threshold_mv); chg->aicl_max_reached = false; if (chg->chg_param.smb_version == PMI632) 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 = smblib_request_dpdm(chg, false); if (rc < 0) smblib_err(chg, "Couldn't disable DPDM rc=%d\n", rc); smblib_update_usb_type(chg); } if (chg->connector_type == QTI_POWER_SUPPLY_CONNECTOR_MICRO_USB) smblib_micro_usb_plugin(chg, vbus_rising); vote(chg->temp_change_irq_disable_votable, DEFAULT_VOTER, !vbus_rising, 0); power_supply_changed(chg->usb_psy); smblib_dbg(chg, PR_INTERRUPT, "IRQ: usbin-plugin %s\n", vbus_rising ? "attached" : "detached"); } irqreturn_t smb5_usb_plugin_irq_handler(int irq, void *data) { struct smb_irq_data *irq_data = data; struct smb_charger *chg = irq_data->parent_data; if (chg->pd_hard_reset) smblib_usb_plugin_hard_reset_locked(chg); else smblib_usb_plugin_locked(chg); return IRQ_HANDLED; } static void smblib_handle_slow_plugin_timeout(struct smb_charger *chg, bool rising) { smblib_dbg(chg, PR_INTERRUPT, "IRQ: slow-plugin-timeout %s\n", rising ? "rising" : "falling"); } static void smblib_handle_sdp_enumeration_done(struct smb_charger *chg, bool rising) { smblib_dbg(chg, PR_INTERRUPT, "IRQ: sdp-enumeration-done %s\n", rising ? "rising" : "falling"); } #define APSD_EXTENDED_TIMEOUT_MS 400 /* triggers when HVDCP 3.0 authentication has finished */ static void smblib_handle_hvdcp_3p0_auth_done(struct smb_charger *chg, bool rising) { const struct apsd_result *apsd_result; int rc; if (!rising) return; if (chg->mode == PARALLEL_MASTER) vote(chg->pl_enable_votable_indirect, USBIN_V_VOTER, true, 0); /* the APSD done handler will set the USB supply type */ apsd_result = smblib_get_apsd_result(chg); if (apsd_result->bit & QC_3P0_BIT) { /* for QC3, switch to CP if present */ if (chg->sec_cp_present) { rc = smblib_select_sec_charger(chg, QTI_POWER_SUPPLY_CHARGER_SEC_CP, QTI_POWER_SUPPLY_CP_HVDCP3, false); if (rc < 0) dev_err(chg->dev, "Couldn't enable secondary chargers rc=%d\n", rc); } /* QC3.5 detection timeout */ if (!chg->apsd_ext_timeout && !timer_pending(&chg->apsd_timer)) { smblib_dbg(chg, PR_MISC, "APSD Extented timer started at %lld\n", jiffies_to_msecs(jiffies)); mod_timer(&chg->apsd_timer, msecs_to_jiffies(APSD_EXTENDED_TIMEOUT_MS) + jiffies); } } smblib_dbg(chg, PR_INTERRUPT, "IRQ: hvdcp-3p0-auth-done rising; %s detected\n", apsd_result->name); } static void smblib_handle_hvdcp_check_timeout(struct smb_charger *chg, bool rising, bool qc_charger) { u32 hvdcp_ua = 0; if (rising) { if (qc_charger) { hvdcp_ua = (chg->real_charger_type == QTI_POWER_SUPPLY_TYPE_USB_HVDCP) ? chg->chg_param.hvdcp2_max_icl_ua : HVDCP_CURRENT_UA; /* enable HDC and ICL irq for QC2/3 charger */ vote(chg->limited_irq_disable_votable, CHARGER_TYPE_VOTER, false, 0); vote(chg->hdc_irq_disable_votable, CHARGER_TYPE_VOTER, false, 0); vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, true, hvdcp_ua); } else { /* A plain DCP, enforce DCP ICL if specified */ vote(chg->usb_icl_votable, DCP_VOTER, chg->dcp_icl_ua != -EINVAL, chg->dcp_icl_ua); } } smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s %s\n", __func__, rising ? "rising" : "falling"); } /* triggers when HVDCP is detected */ static void smblib_handle_hvdcp_detect_done(struct smb_charger *chg, bool rising) { smblib_dbg(chg, PR_INTERRUPT, "IRQ: hvdcp-detect-done %s\n", rising ? "rising" : "falling"); } static void update_sw_icl_max(struct smb_charger *chg, int val) { int typec_mode; int rp_ua; /* while PD is active it should have complete ICL control */ if (chg->pd_active) return; if (chg->typec_mode == QTI_POWER_SUPPLY_TYPEC_SINK_AUDIO_ADAPTER) { vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, true, 500000); return; } /* * HVDCP 2/3, handled separately */ if (val == QTI_POWER_SUPPLY_TYPE_USB_HVDCP || val == QTI_POWER_SUPPLY_TYPE_USB_HVDCP_3) return; /* TypeC rp med or high, use rp value */ typec_mode = smblib_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); return; } /* rp-std or legacy, USB BC 1.2 */ switch (val) { 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_flash_active(chg) ? SDP_CURRENT_UA : SDP_100_MA); } 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, SDP_100_MA); break; case POWER_SUPPLY_TYPE_UNKNOWN: default: vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, true, SDP_100_MA); break; } } static void smblib_handle_apsd_done(struct smb_charger *chg, bool rising) { const struct apsd_result *apsd_result; if (!rising) return; apsd_result = smblib_update_usb_type(chg); 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) smblib_notify_device_mode(chg, true); break; case OCP_CHARGER_BIT: case DCP_CHARGER_BIT: break; default: break; } smblib_dbg(chg, PR_INTERRUPT, "IRQ: apsd-done rising; %s detected\n", apsd_result->name); } irqreturn_t smb5_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; /* PD session is ongoing, ignore BC1.2 and QC detection */ if (chg->pd_active) return IRQ_HANDLED; rc = smblib_read(chg, APSD_STATUS_REG, &stat); if (rc < 0) { smblib_err(chg, "Couldn't read APSD_STATUS rc=%d\n", rc); return IRQ_HANDLED; } smblib_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; smblib_rerun_apsd_if_required(chg); return IRQ_HANDLED; } smblib_handle_apsd_done(chg, (bool)(stat & APSD_DTC_STATUS_DONE_BIT)); smblib_handle_hvdcp_detect_done(chg, (bool)(stat & QC_CHARGER_BIT)); smblib_handle_hvdcp_check_timeout(chg, (bool)(stat & HVDCP_CHECK_TIMEOUT_BIT), (bool)(stat & QC_CHARGER_BIT)); smblib_handle_hvdcp_3p0_auth_done(chg, (bool)(stat & QC_AUTH_DONE_STATUS_BIT)); smblib_handle_sdp_enumeration_done(chg, (bool)(stat & ENUMERATION_DONE_BIT)); smblib_handle_slow_plugin_timeout(chg, (bool)(stat & SLOW_PLUGIN_TIMEOUT_BIT)); smblib_hvdcp_adaptive_voltage_change(chg); power_supply_changed(chg->usb_psy); rc = smblib_read(chg, APSD_STATUS_REG, &stat); if (rc < 0) { smblib_err(chg, "Couldn't read APSD_STATUS rc=%d\n", rc); return IRQ_HANDLED; } smblib_dbg(chg, PR_INTERRUPT, "APSD_STATUS = 0x%02x\n", stat); return IRQ_HANDLED; } enum alarmtimer_restart smblib_lpd_recheck_timer(struct alarm *alarm, ktime_t time) { int val; struct smb_charger *chg = container_of(alarm, struct smb_charger, lpd_recheck_timer); int rc; if (chg->lpd_reason == LPD_MOISTURE_DETECTED) { val = QTI_POWER_SUPPLY_TYPEC_PR_DUAL; rc = smblib_set_prop_typec_power_role(chg, val); if (rc < 0) { smblib_err(chg, "Couldn't write 0x%02x to TYPE_C_INTRPT_ENB_SOFTWARE_CTRL rc=%d\n", val, rc); return ALARMTIMER_NORESTART; } chg->moisture_present = false; power_supply_changed(chg->usb_psy); } else { rc = smblib_masked_write(chg, TYPE_C_INTERRUPT_EN_CFG_2_REG, TYPEC_WATER_DETECTION_INT_EN_BIT, TYPEC_WATER_DETECTION_INT_EN_BIT); if (rc < 0) { smblib_err(chg, "Couldn't set TYPE_C_INTERRUPT_EN_CFG_2_REG rc=%d\n", rc); return ALARMTIMER_NORESTART; } } chg->lpd_stage = LPD_STAGE_NONE; chg->lpd_reason = LPD_NONE; return ALARMTIMER_NORESTART; } #define RSBU_K_300K_UV 3000000 static bool smblib_src_lpd(struct smb_charger *chg) { bool lpd_flag = false; u8 stat; int rc, val; if (chg->lpd_disabled) return false; rc = smblib_read(chg, TYPE_C_SRC_STATUS_REG, &stat); if (rc < 0) { smblib_err(chg, "Couldn't read TYPE_C_SRC_STATUS_REG rc=%d\n", rc); return false; } switch (stat & DETECTED_SNK_TYPE_MASK) { case SRC_DEBUG_ACCESS_BIT: if (smblib_rsbux_low(chg, RSBU_K_300K_UV)) lpd_flag = true; break; case SRC_RD_RA_VCONN_BIT: case SRC_RD_OPEN_BIT: case AUDIO_ACCESS_RA_RA_BIT: default: break; } if (lpd_flag) { chg->lpd_stage = LPD_STAGE_COMMIT; val = QTI_POWER_SUPPLY_TYPEC_PR_SINK; rc = smblib_set_prop_typec_power_role(chg, val); if (rc < 0) smblib_err(chg, "Couldn't write 0x%02x to TYPE_C_INTRPT_ENB_SOFTWARE_CTRL rc=%d\n", val, rc); chg->lpd_reason = LPD_MOISTURE_DETECTED; chg->moisture_present = true; vote(chg->usb_icl_votable, LPD_VOTER, true, 0); alarm_start_relative(&chg->lpd_recheck_timer, ms_to_ktime(60000)); power_supply_changed(chg->usb_psy); } else { chg->lpd_reason = LPD_NONE; chg->typec_mode = smblib_get_prop_typec_mode(chg); } return lpd_flag; } static void typec_src_fault_condition_cfg(struct smb_charger *chg, bool src) { int rc; u8 mask = USBIN_MID_COMP_FAULT_EN_BIT | USBIN_COLLAPSE_FAULT_EN_BIT; rc = smblib_masked_write(chg, OTG_FAULT_CONDITION_CFG_REG, mask, src ? 0 : mask); if (rc < 0) smblib_err(chg, "Couldn't write OTG_FAULT_CONDITION_CFG_REG rc=%d\n", rc); } static void typec_sink_insertion(struct smb_charger *chg) { int rc; typec_src_fault_condition_cfg(chg, true); rc = smblib_set_charge_param(chg, &chg->param.freq_switcher, chg->chg_freq.freq_above_otg_threshold); if (rc < 0) dev_err(chg->dev, "Error in setting freq_boost rc=%d\n", rc); if (chg->use_extcon) { smblib_notify_usb_host(chg, true); chg->otg_present = true; } if (!chg->pr_swap_in_progress) chg->ok_to_pd = (!(chg->pd_disabled) || chg->early_usb_attach) && !chg->pd_not_supported; } static void typec_src_insertion(struct smb_charger *chg) { int rc = 0; u8 stat; if (chg->pr_swap_in_progress) { vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, false, 0); return; } rc = smblib_read(chg, LEGACY_CABLE_STATUS_REG, &stat); if (rc < 0) { smblib_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; chg->ok_to_pd = (!(chg->typec_legacy || chg->pd_disabled) || chg->early_usb_attach) && !chg->pd_not_supported; /* allow apsd proceed to detect QC2/3 */ if (!chg->ok_to_pd) smblib_hvdcp_detect_try_enable(chg, true); } 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); chg->ok_to_pd = false; smblib_hvdcp_detect_enable(chg, true); } static const char * const dr_mode_text[] = { "ufp", "dfp", "none" }; static int smblib_force_dr_mode(struct smb_charger *chg, int mode) { int rc = 0; switch (mode) { case TYPEC_PORT_SNK: rc = smblib_masked_write(chg, TYPE_C_MODE_CFG_REG, TYPEC_POWER_ROLE_CMD_MASK, EN_SNK_ONLY_BIT); if (rc < 0) { smblib_err(chg, "Couldn't enable snk, rc=%d\n", rc); return rc; } break; case TYPEC_PORT_SRC: rc = smblib_masked_write(chg, TYPE_C_MODE_CFG_REG, TYPEC_POWER_ROLE_CMD_MASK, EN_SRC_ONLY_BIT); if (rc < 0) { smblib_err(chg, "Couldn't enable src, rc=%d\n", rc); return rc; } break; case TYPEC_PORT_DRP: rc = smblib_masked_write(chg, TYPE_C_MODE_CFG_REG, TYPEC_POWER_ROLE_CMD_MASK, 0); if (rc < 0) { smblib_err(chg, "Couldn't enable DRP, rc=%d\n", rc); return rc; } break; default: smblib_err(chg, "Power role %d not supported\n", mode); return -EINVAL; } chg->dr_mode = mode; return rc; } int smblib_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; mutex_lock(&chg->typec_lock); if ((chg->pr_swap_in_progress) || (type == TYPEC_PORT_DRP)) { smblib_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 = smblib_force_dr_mode(chg, type); if (rc < 0) { chg->pr_swap_in_progress = false; smblib_err(chg, "Failed to force mode, rc=%d\n", rc); goto unlock; } smblib_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 smblib_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; mutex_lock(&chg->typec_lock); switch (chg->dr_mode) { case TYPEC_PORT_SNK: if (chg->typec_mode < QTI_POWER_SUPPLY_TYPEC_SOURCE_DEFAULT) { smblib_dbg(chg, PR_MISC, "Role reversal not latched to UFP in %d msecs. Resetting to DRP mode\n", ROLE_REVERSAL_DELAY_MS); rc = smblib_force_dr_mode(chg, TYPEC_PORT_DRP); if (rc < 0) smblib_err(chg, "Failed 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); smblib_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) { smblib_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 = smblib_force_dr_mode(chg, TYPEC_PORT_DRP); if (rc < 0) smblib_err(chg, "Failed 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); smblib_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) { int rc; typec_src_fault_condition_cfg(chg, false); rc = smblib_set_charge_param(chg, &chg->param.freq_switcher, chg->chg_freq.freq_removal); if (rc < 0) dev_err(chg->dev, "Error in setting freq_removal rc=%d\n", rc); if (chg->use_extcon) { if (chg->otg_present) smblib_notify_usb_host(chg, false); chg->otg_present = false; } } static void typec_src_removal(struct smb_charger *chg) { int rc; struct smb_irq_data *data; struct storm_watch *wdata; int sec_charger; u8 val[2] = {0}; sec_charger = chg->sec_pl_present ? QTI_POWER_SUPPLY_CHARGER_SEC_PL : QTI_POWER_SUPPLY_CHARGER_SEC_NONE; rc = smblib_select_sec_charger(chg, sec_charger, QTI_POWER_SUPPLY_CP_NONE, false); if (rc < 0) dev_err(chg->dev, "Couldn't disable secondary charger rc=%d\n", rc); chg->qc3p5_detected = false; chg->qc3p5_detected_mw = 0; typec_src_fault_condition_cfg(chg, false); smblib_hvdcp_detect_try_enable(chg, false); smblib_update_usb_type(chg); 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_flash_active(chg) ? SDP_CURRENT_UA : SDP_100_MA); vote(chg->usb_icl_votable, PD_VOTER, false, 0); vote(chg->usb_icl_votable, USB_PSY_VOTER, false, 0); vote(chg->usb_icl_votable, DCP_VOTER, false, 0); vote(chg->usb_icl_votable, SW_QC3_VOTER, false, 0); vote(chg->usb_icl_votable, CTM_VOTER, false, 0); vote(chg->usb_icl_votable, HVDCP2_ICL_VOTER, false, 0); vote(chg->usb_icl_votable, CHG_TERMINATION_VOTER, false, 0); vote(chg->usb_icl_votable, THERMAL_THROTTLE_VOTER, false, 0); vote(chg->usb_icl_votable, LPD_VOTER, false, 0); /* reset usb irq voters */ vote(chg->limited_irq_disable_votable, CHARGER_TYPE_VOTER, true, 0); vote(chg->hdc_irq_disable_votable, CHARGER_TYPE_VOTER, true, 0); vote(chg->hdc_irq_disable_votable, HDC_IRQ_VOTER, false, 0); /* reset parallel voters */ vote(chg->pl_disable_votable, PL_DELAY_VOTER, true, 0); vote(chg->pl_disable_votable, PL_FCC_LOW_VOTER, false, 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 WA votes */ vote(chg->usb_icl_votable, SW_THERM_REGULATION_VOTER, false, 0); vote(chg->pl_disable_votable, SW_THERM_REGULATION_VOTER, false, 0); vote(chg->dc_suspend_votable, SW_THERM_REGULATION_VOTER, false, 0); if (chg->cp_disable_votable) vote(chg->cp_disable_votable, SW_THERM_REGULATION_VOTER, false, 0); /* reset USBOV votes and cancel work */ cancel_delayed_work_sync(&chg->usbov_dbc_work); vote(chg->awake_votable, USBOV_DBC_VOTER, false, 0); chg->dbc_usbov = false; chg->pulse_cnt = 0; chg->usb_icl_delta_ua = 0; chg->voltage_min_uv = MICRO_5V; chg->voltage_max_uv = MICRO_5V; chg->usbin_forced_max_uv = 0; chg->chg_param.forced_main_fcc = 0; /* Reset all CC mode votes */ vote(chg->fcc_main_votable, MAIN_FCC_VOTER, false, 0); chg->adapter_cc_mode = 0; chg->thermal_overheat = 0; vote_override(chg->fcc_votable, CC_MODE_VOTER, false, 0); vote_override(chg->usb_icl_votable, CC_MODE_VOTER, false, 0); vote(chg->cp_disable_votable, OVERHEAT_LIMIT_VOTER, false, 0); vote(chg->usb_icl_votable, OVERHEAT_LIMIT_VOTER, false, 0); /* write back the default FLOAT charger configuration */ rc = smblib_masked_write(chg, USBIN_OPTIONS_2_CFG_REG, (u8)FLOAT_OPTIONS_MASK, chg->float_cfg); if (rc < 0) smblib_err(chg, "Couldn't write float charger options rc=%d\n", rc); if (chg->sdam_base) { rc = smblib_write(chg, chg->sdam_base + SDAM_QC_DET_STATUS_REG, 0); if (rc < 0) pr_err("Couldn't clear SDAM QC status rc=%d\n", rc); rc = smblib_batch_write(chg, chg->sdam_base + SDAM_QC_ADC_LSB_REG, val, 2); if (rc < 0) pr_err("Couldn't clear SDAM ADC status rc=%d\n", rc); } if (!chg->pr_swap_in_progress) { rc = smblib_usb_pd_adapter_allowance_override(chg, FORCE_NULL); if (rc < 0) smblib_err(chg, "Couldn't set FORCE_NULL rc=%d\n", rc); rc = smblib_set_charge_param(chg, &chg->param.aicl_cont_threshold, chg->default_aicl_cont_threshold_mv); if (rc < 0) smblib_err(chg, "Couldn't restore aicl_cont_threshold, rc=%d", rc); } /* * if non-compliant charger caused UV, restore original max pulses * and turn SUSPEND_ON_COLLAPSE_USBIN_BIT back on. */ if (chg->qc2_unsupported_voltage) { rc = smblib_masked_write(chg, HVDCP_PULSE_COUNT_MAX_REG, HVDCP_PULSE_COUNT_MAX_QC2_MASK, chg->qc2_max_pulses); if (rc < 0) smblib_err(chg, "Couldn't restore max pulses rc=%d\n", rc); if (!chg->disable_suspend_on_collapse) { rc = smblib_masked_write(chg, USBIN_AICL_OPTIONS_CFG_REG, SUSPEND_ON_COLLAPSE_USBIN_BIT, SUSPEND_ON_COLLAPSE_USBIN_BIT); if (rc < 0) smblib_err(chg, "Couldn't turn on SUSPEND_ON_COLLAPSE_USBIN_BIT rc=%d\n", rc); } chg->qc2_unsupported_voltage = QC2_COMPLIANT; } if (chg->use_extcon) smblib_notify_device_mode(chg, false); chg->typec_legacy = false; del_timer_sync(&chg->apsd_timer); chg->apsd_ext_timeout = false; } static void typec_mode_unattached(struct smb_charger *chg) { vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, true, USBIN_100MA); } static void smblib_handle_rp_change(struct smb_charger *chg, int typec_mode) { const struct apsd_result *apsd = smblib_get_apsd_result(chg); /* * We want the ICL vote @ 100mA for a FLOAT charger * until the detection by the USB stack is complete. * Ignore the Rp changes unless there is a * pre-existing valid vote or FLOAT is configured for * SDP current. */ if (apsd->val == QTI_POWER_SUPPLY_TYPE_USB_FLOAT) { if (get_client_vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER) <= USBIN_100MA || (chg->float_cfg & FLOAT_OPTIONS_MASK) == FORCE_FLOAT_SDP_CFG_BIT) return; } update_sw_icl_max(chg, apsd->val); smblib_dbg(chg, PR_MISC, "CC change old_mode=%d new_mode=%d\n", chg->typec_mode, typec_mode); } static void smblib_lpd_launch_ra_open_work(struct smb_charger *chg) { u8 stat; int rc; if (chg->lpd_disabled) return; rc = smblib_read(chg, TYPE_C_MISC_STATUS_REG, &stat); if (rc < 0) { smblib_err(chg, "Couldn't read TYPE_C_MISC_STATUS_REG rc=%d\n", rc); return; } if (!(stat & TYPEC_TCCDEBOUNCE_DONE_STATUS_BIT) && chg->lpd_stage == LPD_STAGE_NONE) { chg->lpd_stage = LPD_STAGE_FLOAT; cancel_delayed_work_sync(&chg->lpd_ra_open_work); vote(chg->awake_votable, LPD_VOTER, true, 0); schedule_delayed_work(&chg->lpd_ra_open_work, msecs_to_jiffies(300)); } } irqreturn_t smb5_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; smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name); if (chg->connector_type == QTI_POWER_SUPPLY_CONNECTOR_MICRO_USB) { if (chg->uusb_moisture_protection_enabled) { /* * Adding pm_stay_awake as because pm_relax is called * on exit path from the work routine. */ pm_stay_awake(chg->dev); schedule_work(&chg->moisture_protection_work); } cancel_delayed_work_sync(&chg->uusb_otg_work); /* * Skip OTG enablement if RID interrupt triggers with moisture * protection still enabled. */ if (!chg->moisture_present) { vote(chg->awake_votable, OTG_DELAY_VOTER, true, 0); smblib_dbg(chg, PR_INTERRUPT, "Scheduling OTG work\n"); schedule_delayed_work(&chg->uusb_otg_work, msecs_to_jiffies(chg->otg_delay_ms)); } goto out; } if (chg->pr_swap_in_progress || chg->pd_hard_reset) goto out; smblib_lpd_launch_ra_open_work(chg); if (chg->usb_psy) power_supply_changed(chg->usb_psy); out: return IRQ_HANDLED; } irqreturn_t smb5_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) { smblib_dbg(chg, PR_INTERRUPT, "Ignoring for micro USB\n"); return IRQ_HANDLED; } typec_mode = smblib_get_prop_typec_mode(chg); if (chg->sink_src_mode != UNATTACHED_MODE && (typec_mode != chg->typec_mode)) smblib_handle_rp_change(chg, typec_mode); chg->typec_mode = typec_mode; smblib_dbg(chg, PR_INTERRUPT, "IRQ: cc-state-change; Type-C %s detected\n", smblib_typec_mode_name[chg->typec_mode]); power_supply_changed(chg->usb_psy); return IRQ_HANDLED; } static void smblib_lpd_clear_ra_open_work(struct smb_charger *chg) { if (chg->lpd_disabled) return; cancel_delayed_work_sync(&chg->lpd_detach_work); chg->lpd_stage = LPD_STAGE_FLOAT_CANCEL; cancel_delayed_work_sync(&chg->lpd_ra_open_work); vote(chg->awake_votable, LPD_VOTER, false, 0); } #define TYPEC_DETACH_DETECT_DELAY_MS 2000 irqreturn_t smb5_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; smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name); rc = smblib_read(chg, TYPE_C_STATE_MACHINE_STATUS_REG, &stat); if (rc < 0) { smblib_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) { smblib_lpd_clear_ra_open_work(chg); rc = smblib_read(chg, TYPE_C_MISC_STATUS_REG, &stat); if (rc < 0) { smblib_err(chg, "Couldn't read TYPE_C_MISC_STATUS_REG rc=%d\n", rc); return IRQ_HANDLED; } if (smblib_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) { if (smblib_src_lpd(chg)) return IRQ_HANDLED; 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) smblib_err(chg, "failed 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->ok_to_pd = false; chg->sink_src_mode = UNATTACHED_MODE; chg->early_usb_attach = false; smblib_apsd_enable(chg, true); } /* * 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. */ mutex_lock(&chg->typec_lock); 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)); smblib_force_dr_mode(chg, TYPEC_PORT_DRP); /* * To handle cable removal during role * swap failure. */ chg->typec_role_swap_failed = false; } mutex_unlock(&chg->typec_lock); if (chg->lpd_stage == LPD_STAGE_FLOAT_CANCEL) schedule_delayed_work(&chg->lpd_detach_work, msecs_to_jiffies(1000)); } rc = smblib_masked_write(chg, USB_CMD_PULLDOWN_REG, EN_PULLDOWN_USB_IN_BIT, attached ? 0 : EN_PULLDOWN_USB_IN_BIT); if (rc < 0) smblib_err(chg, "Couldn't configure pulldown on USB_IN rc=%d\n", rc); power_supply_changed(chg->usb_psy); return IRQ_HANDLED; } static void dcin_aicl(struct smb_charger *chg) { int rc, icl, icl_save; int input_present; bool aicl_done = true; /* * Hold awake votable to prevent pm_relax being called prior to * completion of this work. */ vote(chg->awake_votable, DCIN_AICL_VOTER, true, 0); increment: mutex_lock(&chg->dcin_aicl_lock); rc = smblib_get_charge_param(chg, &chg->param.dc_icl, &icl); if (rc < 0) goto err; if (icl == chg->wls_icl_ua) { /* Upper limit reached; do nothing */ smblib_dbg(chg, PR_WLS, "hit max ICL: stop\n"); rc = smblib_is_input_present(chg, &input_present); if (rc < 0 || !(input_present & INPUT_PRESENT_DC)) aicl_done = false; goto unlock; } icl = min(chg->wls_icl_ua, icl + DCIN_ICL_STEP_UA); icl_save = icl; rc = smblib_set_charge_param(chg, &chg->param.dc_icl, icl); if (rc < 0) goto err; mutex_unlock(&chg->dcin_aicl_lock); smblib_dbg(chg, PR_WLS, "icl: %d mA\n", (icl / 1000)); /* Check to see if DC is still present before and after sleep */ rc = smblib_is_input_present(chg, &input_present); if (rc < 0 || !(input_present & INPUT_PRESENT_DC)) { aicl_done = false; goto unvote; } /* * Wait awhile to check for any DCIN_UVs (the UV handler reduces the * ICL). If the adaptor collapses, the ICL read after waking up will be * lesser, indicating that the AICL process is complete. */ msleep(500); rc = smblib_is_input_present(chg, &input_present); if (rc < 0 || !(input_present & INPUT_PRESENT_DC)) { aicl_done = false; goto unvote; } mutex_lock(&chg->dcin_aicl_lock); rc = smblib_get_charge_param(chg, &chg->param.dc_icl, &icl); if (rc < 0) goto err; if (icl < icl_save) { smblib_dbg(chg, PR_WLS, "done: icl: %d mA\n", (icl / 1000)); goto unlock; } mutex_unlock(&chg->dcin_aicl_lock); goto increment; err: aicl_done = false; unlock: mutex_unlock(&chg->dcin_aicl_lock); unvote: vote(chg->awake_votable, DCIN_AICL_VOTER, false, 0); chg->dcin_aicl_done = aicl_done; } static void dcin_aicl_work(struct work_struct *work) { struct smb_charger *chg = container_of(work, struct smb_charger, dcin_aicl_work); dcin_aicl(chg); } static enum alarmtimer_restart dcin_aicl_alarm_cb(struct alarm *alarm, ktime_t now) { struct smb_charger *chg = container_of(alarm, struct smb_charger, dcin_aicl_alarm); smblib_dbg(chg, PR_WLS, "rerunning DCIN AICL\n"); pm_stay_awake(chg->dev); schedule_work(&chg->dcin_aicl_work); return ALARMTIMER_NORESTART; } static void dcin_icl_decrement(struct smb_charger *chg) { int rc, icl; ktime_t now = ktime_get(); rc = smblib_get_charge_param(chg, &chg->param.dc_icl, &icl); if (rc < 0) { smblib_err(chg, "reading DCIN ICL failed: %d\n", rc); return; } if (icl == DCIN_ICL_MIN_UA) { /* Cannot possibly decrease ICL any further - do nothing */ smblib_dbg(chg, PR_WLS, "hit min ICL: stop\n"); return; } /* Reduce ICL by 100 mA if 3 UVs happen in a row */ if (ktime_us_delta(now, chg->dcin_uv_last_time) > (200 * 1000)) { chg->dcin_uv_count = 0; } else if (chg->dcin_uv_count >= 3) { icl -= DCIN_ICL_STEP_UA; smblib_dbg(chg, PR_WLS, "icl: %d mA\n", (icl / 1000)); rc = smblib_set_charge_param(chg, &chg->param.dc_icl, icl); if (rc < 0) { smblib_err(chg, "setting DCIN ICL failed: %d\n", rc); return; } chg->dcin_uv_count = 0; } chg->dcin_uv_last_time = now; } irqreturn_t smb5_dcin_uv_irq_handler(int irq, void *data) { struct smb_irq_data *irq_data = data; struct smb_charger *chg = irq_data->parent_data; mutex_lock(&chg->dcin_aicl_lock); chg->dcin_uv_count++; smblib_dbg(chg, (PR_WLS | PR_INTERRUPT), "DCIN UV count: %d\n", chg->dcin_uv_count); dcin_icl_decrement(chg); mutex_unlock(&chg->dcin_aicl_lock); return IRQ_HANDLED; } irqreturn_t smb5_dc_plugin_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; int input_present; bool dcin_present, vbus_present; int rc, wireless_vout = 0, wls_set = 0; int sec_charger, val; rc = smblib_get_prop_vph_voltage_now(chg, &val); if (rc < 0) return IRQ_HANDLED; /* 2*VPH, with a granularity of 100mV */ wireless_vout = ((val * 2) / 100000) * 100000; rc = smblib_is_input_present(chg, &input_present); if (rc < 0) return IRQ_HANDLED; dcin_present = input_present & INPUT_PRESENT_DC; vbus_present = input_present & INPUT_PRESENT_USB; if (!chg->cp_ilim_votable) chg->cp_ilim_votable = find_votable("CP_ILIM"); if (dcin_present && !vbus_present) { cancel_work_sync(&chg->dcin_aicl_work); /* Reset DCIN ICL to 100 mA */ mutex_lock(&chg->dcin_aicl_lock); rc = smblib_set_charge_param(chg, &chg->param.dc_icl, DCIN_ICL_MIN_UA); mutex_unlock(&chg->dcin_aicl_lock); if (rc < 0) return IRQ_HANDLED; smblib_dbg(chg, (PR_WLS | PR_INTERRUPT), "reset: icl: 100 mA\n"); /* * Remove USB's CP ILIM vote - inapplicable for wireless * parallel charging. */ if (chg->cp_ilim_votable) vote(chg->cp_ilim_votable, ICL_CHANGE_VOTER, false, 0); if (chg->sec_cp_present) { /* * If CP output topology is VBATT, limit main charger's * FCC share and let the CPs handle the rest. */ if (is_cp_topo_vbatt(chg)) vote(chg->fcc_main_votable, WLS_PL_CHARGING_VOTER, true, 800000); rc = smblib_get_prop_batt_status(chg, &pval); if (rc < 0) smblib_err(chg, "Couldn't read batt status rc=%d\n", rc); wls_set = (pval.intval == POWER_SUPPLY_STATUS_FULL) ? MICRO_5V : wireless_vout; pval.intval = wls_set; rc = smblib_set_prop_voltage_wls_output(chg, &pval); if (rc < 0) dev_err(chg->dev, "Couldn't set dc voltage to 2*vph rc=%d\n", rc); rc = smblib_select_sec_charger(chg, QTI_POWER_SUPPLY_CHARGER_SEC_CP, QTI_POWER_SUPPLY_CP_WIRELESS, false); if (rc < 0) dev_err(chg->dev, "Couldn't enable secondary chargers rc=%d\n", rc); } else { /* * If no secondary charger is present, commence * wireless charging at 5 V by default. */ pval.intval = 5000000; rc = smblib_set_prop_voltage_wls_output(chg, &pval); if (rc < 0) dev_err(chg->dev, "Couldn't set dc voltage to 5 V rc=%d\n", rc); } schedule_work(&chg->dcin_aicl_work); } else { if (chg->cp_reason == QTI_POWER_SUPPLY_CP_WIRELESS) { sec_charger = chg->sec_pl_present ? QTI_POWER_SUPPLY_CHARGER_SEC_PL : QTI_POWER_SUPPLY_CHARGER_SEC_NONE; rc = smblib_select_sec_charger(chg, sec_charger, QTI_POWER_SUPPLY_CP_NONE, false); if (rc < 0) dev_err(chg->dev, "Couldn't disable secondary charger rc=%d\n", rc); } vote(chg->dc_suspend_votable, CHG_TERMINATION_VOTER, false, 0); vote(chg->fcc_main_votable, WLS_PL_CHARGING_VOTER, false, 0); chg->last_wls_vout = 0; chg->dcin_aicl_done = false; chg->dcin_icl_user_set = false; } /* * Vote for 1500mA FCC upon WLS detach and remove vote upon attach if * FCC stepper is enabled. */ if (chg->fcc_stepper_enable && !vbus_present) vote(chg->fcc_votable, FCC_STEPPER_VOTER, !dcin_present, dcin_present ? 0 : 1500000); if (chg->dc_psy) power_supply_changed(chg->dc_psy); smblib_dbg(chg, (PR_WLS | PR_INTERRUPT), "dcin_present= %d, usbin_present= %d, cp_reason = %d\n", dcin_present, vbus_present, chg->cp_reason); return IRQ_HANDLED; } irqreturn_t smb5_high_duty_cycle_irq_handler(int irq, void *data) { struct smb_irq_data *irq_data = data; struct smb_charger *chg = irq_data->parent_data; chg->is_hdc = true; /* * Disable usb IRQs after the flag set and re-enable IRQs after * the flag cleared in the delayed work queue, to avoid any IRQ * storming during the delays */ vote(chg->hdc_irq_disable_votable, HDC_IRQ_VOTER, true, 0); schedule_delayed_work(&chg->clear_hdc_work, msecs_to_jiffies(60)); return IRQ_HANDLED; } static void smblib_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 smb5_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 = smblib_read(chg, POWER_PATH_STATUS_REG, &stat); if (rc < 0) { smblib_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_25MA) return IRQ_HANDLED; if (stat & USE_DCIN_BIT) 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)) { smblib_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 { smblib_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 smb5_wdog_snarl_irq_handler(int irq, void *data) { struct smb_irq_data *irq_data = data; struct smb_charger *chg = irq_data->parent_data; smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name); if (chg->wa_flags & SW_THERM_REGULATION_WA) { cancel_delayed_work_sync(&chg->thermal_regulation_work); vote(chg->awake_votable, SW_THERM_REGULATION_VOTER, true, 0); schedule_delayed_work(&chg->thermal_regulation_work, 0); } power_supply_changed(chg->batt_psy); return IRQ_HANDLED; } irqreturn_t smb5_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; smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name); rc = smblib_write(chg, BARK_BITE_WDOG_PET_REG, BARK_BITE_WDOG_PET_BIT); if (rc < 0) smblib_err(chg, "Couldn't pet the dog rc=%d\n", rc); return IRQ_HANDLED; } static void smblib_die_rst_icl_regulate(struct smb_charger *chg) { int rc; u8 temp; rc = smblib_read(chg, DIE_TEMP_STATUS_REG, &temp); if (rc < 0) { smblib_err(chg, "Couldn't read DIE_TEMP_STATUS_REG rc=%d\n", rc); return; } /* Regulate ICL on die temp crossing DIE_RST threshold */ vote(chg->usb_icl_votable, DIE_TEMP_VOTER, temp & DIE_TEMP_RST_BIT, 500000); } /* * triggered when DIE or SKIN or CONNECTOR temperature across * either of the _REG_L, _REG_H, _RST, or _SHDN thresholds */ irqreturn_t smb5_temp_change_irq_handler(int irq, void *data) { struct smb_irq_data *irq_data = data; struct smb_charger *chg = irq_data->parent_data; smblib_die_rst_icl_regulate(chg); return IRQ_HANDLED; } static void smblib_usbov_dbc_work(struct work_struct *work) { struct smb_charger *chg = container_of(work, struct smb_charger, usbov_dbc_work.work); smblib_dbg(chg, PR_MISC, "Resetting USBOV debounce\n"); chg->dbc_usbov = false; vote(chg->awake_votable, USBOV_DBC_VOTER, false, 0); } #define USB_OV_DBC_PERIOD_MS 1000 irqreturn_t smb5_usbin_ov_irq_handler(int irq, void *data) { struct smb_irq_data *irq_data = data; struct smb_charger *chg = irq_data->parent_data; u8 stat; int rc; smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name); if (!(chg->wa_flags & USBIN_OV_WA)) return IRQ_HANDLED; rc = smblib_read(chg, USBIN_BASE + INT_RT_STS_OFFSET, &stat); if (rc < 0) { smblib_err(chg, "Couldn't read USB_INT_RT_STS rc=%d\n", rc); return IRQ_HANDLED; } /* * On specific PMICs, OV IRQ triggers for very small duration in * interim periods affecting charging status reflection. In order to * differentiate between OV IRQ glitch and real OV_IRQ, add a debounce * period for evaluation. */ if (stat & USBIN_OV_RT_STS_BIT) { chg->dbc_usbov = true; vote(chg->awake_votable, USBOV_DBC_VOTER, true, 0); schedule_delayed_work(&chg->usbov_dbc_work, msecs_to_jiffies(USB_OV_DBC_PERIOD_MS)); } else { cancel_delayed_work_sync(&chg->usbov_dbc_work); chg->dbc_usbov = false; vote(chg->awake_votable, USBOV_DBC_VOTER, false, 0); } smblib_dbg(chg, PR_MISC, "USBOV debounce status %d\n", chg->dbc_usbov); return IRQ_HANDLED; } /************** * Additional USB PSY getters/setters * that call interrupt functions ***************/ int smblib_get_prop_pr_swap_in_progress(struct smb_charger *chg, int *val) { *val = chg->pr_swap_in_progress; return 0; } #define DETACH_DETECT_DELAY_MS 20 int smblib_set_prop_pr_swap_in_progress(struct smb_charger *chg, int val) { int rc; u8 stat = 0, orientation; smblib_dbg(chg, PR_MISC, "Requested PR_SWAP %d\n", val); chg->pr_swap_in_progress = val; /* check for cable removal during pr_swap */ if (!chg->pr_swap_in_progress) { cancel_delayed_work_sync(&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(DETACH_DETECT_DELAY_MS)); } rc = smblib_masked_write(chg, TYPE_C_DEBOUNCE_OPTION_REG, REDUCE_TCCDEBOUNCE_TO_2MS_BIT, val ? REDUCE_TCCDEBOUNCE_TO_2MS_BIT : 0); if (rc < 0) smblib_err(chg, "Couldn't set tCC debounce rc=%d\n", rc); rc = smblib_masked_write(chg, TYPE_C_EXIT_STATE_CFG_REG, BYPASS_VSAFE0V_DURING_ROLE_SWAP_BIT, val ? BYPASS_VSAFE0V_DURING_ROLE_SWAP_BIT : 0); if (rc < 0) smblib_err(chg, "Couldn't set exit state cfg rc=%d\n", rc); if (chg->pr_swap_in_progress) { rc = smblib_read(chg, TYPE_C_MISC_STATUS_REG, &stat); if (rc < 0) { smblib_err(chg, "Couldn't read TYPE_C_STATUS_4 rc=%d\n", rc); } orientation = stat & CC_ORIENTATION_BIT ? TYPEC_CCOUT_VALUE_BIT : 0; rc = smblib_masked_write(chg, TYPE_C_CCOUT_CONTROL_REG, TYPEC_CCOUT_SRC_BIT | TYPEC_CCOUT_BUFFER_EN_BIT | TYPEC_CCOUT_VALUE_BIT, TYPEC_CCOUT_SRC_BIT | TYPEC_CCOUT_BUFFER_EN_BIT | orientation); if (rc < 0) { smblib_err(chg, "Couldn't read TYPE_C_CCOUT_CONTROL_REG rc=%d\n", rc); } } else { rc = smblib_masked_write(chg, TYPE_C_CCOUT_CONTROL_REG, TYPEC_CCOUT_SRC_BIT, 0); if (rc < 0) { smblib_err(chg, "Couldn't read TYPE_C_CCOUT_CONTROL_REG rc=%d\n", rc); return rc; } /* enable DRP */ rc = smblib_masked_write(chg, TYPE_C_MODE_CFG_REG, TYPEC_POWER_ROLE_CMD_MASK, 0); if (rc < 0) { smblib_err(chg, "Couldn't enable DRP rc=%d\n", rc); return rc; } chg->power_role = QTI_POWER_SUPPLY_TYPEC_PR_DUAL; smblib_dbg(chg, PR_MISC, "restore power role: %d\n", chg->power_role); } return 0; } /*************** * Work Queues * ***************/ static void smblib_pr_lock_clear_work(struct work_struct *work) { struct smb_charger *chg = container_of(work, struct smb_charger, pr_lock_clear_work.work); spin_lock(&chg->typec_pr_lock); if (chg->pr_lock_in_progress) { smblib_dbg(chg, PR_MISC, "restore type-c interrupts\n"); smblib_typec_irq_config(chg, true); chg->pr_lock_in_progress = false; } spin_unlock(&chg->typec_pr_lock); } static void smblib_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 = smblib_read(chg, TYPE_C_STATE_MACHINE_STATUS_REG, &stat); if (rc < 0) { smblib_err(chg, "Couldn't read STATE_MACHINE_STS rc=%d\n", rc); goto out; } smblib_dbg(chg, PR_REGISTER, "STATE_MACHINE_STS %x\n", stat); if (!(stat & TYPEC_ATTACH_DETACH_STATE_BIT)) { rc = smblib_request_dpdm(chg, false); if (rc < 0) smblib_err(chg, "Couldn't disable DPDM rc=%d\n", rc); if (chg->typec_port) typec_partner_unregister(chg); } out: vote(chg->awake_votable, DETACH_DETECT_VOTER, false, 0); } static void smblib_uusb_otg_work(struct work_struct *work) { struct smb_charger *chg = container_of(work, struct smb_charger, uusb_otg_work.work); int rc; u8 stat; bool otg; rc = smblib_read(chg, TYPEC_U_USB_STATUS_REG, &stat); if (rc < 0) { smblib_err(chg, "Couldn't read TYPE_C_STATUS_3 rc=%d\n", rc); goto out; } otg = !!(stat & U_USB_GROUND_NOVBUS_BIT); if (chg->otg_present != otg) { if (otg) { /* otg cable inserted */ if (chg->typec_port) { typec_partner_register(chg); typec_set_data_role(chg->typec_port, TYPEC_HOST); typec_set_pwr_role(chg->typec_port, TYPEC_SOURCE); } } else if (chg->typec_port) { /* otg cable removed */ typec_set_data_role(chg->typec_port, TYPEC_DEVICE); typec_set_pwr_role(chg->typec_port, TYPEC_SINK); typec_partner_unregister(chg); } smblib_notify_usb_host(chg, otg); } else { goto out; } chg->otg_present = otg; if (!otg) chg->boost_current_ua = 0; rc = smblib_set_charge_param(chg, &chg->param.freq_switcher, otg ? chg->chg_freq.freq_below_otg_threshold : chg->chg_freq.freq_removal); if (rc < 0) dev_err(chg->dev, "Error in setting freq_boost rc=%d\n", rc); smblib_dbg(chg, PR_REGISTER, "TYPE_C_U_USB_STATUS = 0x%02x OTG=%d\n", stat, otg); power_supply_changed(chg->usb_psy); out: vote(chg->awake_votable, OTG_DELAY_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, smblib_qg_ext_iio_chan, ARRAY_SIZE(smblib_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; } smblib_config_charger_on_debug_battery(chg); if (chg->batt_psy) power_supply_changed(chg->batt_psy); } static void pl_update_work(struct work_struct *work) { int val, rc; struct smb_charger *chg = container_of(work, struct smb_charger, pl_update_work); struct iio_channel **iio_list; iio_list = get_ext_channels(chg->dev, smblib_parallel_ext_iio_chan, ARRAY_SIZE(smblib_parallel_ext_iio_chan)); if (IS_ERR(iio_list)) { rc = PTR_ERR(iio_list); if (rc != -EPROBE_DEFER) { dev_err(chg->dev, "Failed to get channels, %d\n", rc); chg->iio_chan_list_smb_parallel = ERR_PTR(-EINVAL); } return; } chg->iio_chan_list_smb_parallel = iio_list; if (chg->smb_temp_max == -EINVAL) { rc = smblib_get_thermal_threshold(chg, SMB_REG_H_THRESHOLD_MSB_REG, &chg->smb_temp_max); if (rc < 0) { dev_err(chg->dev, "Couldn't get charger_temp_max rc=%d\n", rc); return; } } val = chg->smb_temp_max; rc = smblib_write_iio_prop(chg, SMB_PARALLEL, SMB_CHARGER_TEMP_MAX, val); if (rc < 0) { dev_err(chg->dev, "Couldn't set POWER_SUPPLY_PROP_CHARGER_TEMP_MAX rc=%d\n", rc); return; } if (chg->sec_chg_selected == QTI_POWER_SUPPLY_CHARGER_SEC_CP) return; smblib_select_sec_charger(chg, QTI_POWER_SUPPLY_CHARGER_SEC_PL, QTI_POWER_SUPPLY_CP_NONE, false); } static void clear_hdc_work(struct work_struct *work) { struct smb_charger *chg = container_of(work, struct smb_charger, clear_hdc_work.work); chg->is_hdc = false; vote(chg->hdc_irq_disable_votable, HDC_IRQ_VOTER, false, 0); } static void smblib_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 = smblib_get_charge_param(chg, &chg->param.icl_stat, &settled_ua); if (rc < 0) { smblib_err(chg, "Couldn't get ICL status rc=%d\n", rc); return; } power_supply_changed(chg->batt_psy); smblib_dbg(chg, PR_INTERRUPT, "icl_settled=%d\n", settled_ua); } static void smblib_pl_enable_work(struct work_struct *work) { struct smb_charger *chg = container_of(work, struct smb_charger, pl_enable_work.work); smblib_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 smblib_thermal_regulation_work(struct work_struct *work) { struct smb_charger *chg = container_of(work, struct smb_charger, thermal_regulation_work.work); int rc; rc = smblib_update_thermal_readings(chg); if (rc < 0) smblib_err(chg, "Couldn't read current thermal values %d\n", rc); rc = smblib_process_thermal_readings(chg); if (rc < 0) smblib_err(chg, "Couldn't run sw thermal regulation %d\n", rc); } #define MOISTURE_PROTECTION_CHECK_DELAY_MS 300000 /* 5 mins */ static void smblib_moisture_protection_work(struct work_struct *work) { struct smb_charger *chg = container_of(work, struct smb_charger, moisture_protection_work); int rc; bool usb_plugged_in; u8 stat; /* * Hold awake votable to prevent pm_relax being called prior to * completion of this work. */ vote(chg->awake_votable, MOISTURE_VOTER, true, 0); /* * Disable 1% duty cycle on CC_ID pin and enable uUSB factory mode * detection to track any change on RID, as interrupts are disable. */ rc = smblib_write(chg, ((chg->chg_param.smb_version == PMI632) ? PMI632_TYPEC_U_USB_WATER_PROTECTION_CFG_REG : TYPEC_U_USB_WATER_PROTECTION_CFG_REG), 0); if (rc < 0) { smblib_err(chg, "Couldn't disable periodic monitoring of CC_ID rc=%d\n", rc); goto out; } rc = smblib_masked_write(chg, TYPEC_U_USB_CFG_REG, EN_MICRO_USB_FACTORY_MODE_BIT, EN_MICRO_USB_FACTORY_MODE_BIT); if (rc < 0) { smblib_err(chg, "Couldn't enable uUSB factory mode detection rc=%d\n", rc); goto out; } /* * Add a delay of 100ms to allow change in rid to reflect on * status registers. */ msleep(100); rc = smblib_read(chg, USBIN_BASE + INT_RT_STS_OFFSET, &stat); if (rc < 0) { smblib_err(chg, "Couldn't read USB_INT_RT_STS rc=%d\n", rc); goto out; } usb_plugged_in = (bool)(stat & USBIN_PLUGIN_RT_STS_BIT); /* Check uUSB status for moisture presence */ rc = smblib_read(chg, TYPEC_U_USB_STATUS_REG, &stat); if (rc < 0) { smblib_err(chg, "Couldn't read TYPE_C_U_USB_STATUS_REG rc=%d\n", rc); goto out; } /* * Factory mode detection happens in case of USB plugged-in by using * a different current source of 2uA which can hamper moisture * detection. Since factory mode is not supported in kernel, factory * mode detection can be considered as equivalent to presence of * moisture. */ if (stat == U_USB_STATUS_WATER_PRESENT || stat == U_USB_FMB1_BIT || stat == U_USB_FMB2_BIT || (usb_plugged_in && stat == U_USB_FLOAT1_BIT)) { smblib_set_moisture_protection(chg, true); alarm_start_relative(&chg->moisture_protection_alarm, ms_to_ktime(MOISTURE_PROTECTION_CHECK_DELAY_MS)); } else { smblib_set_moisture_protection(chg, false); rc = alarm_cancel(&chg->moisture_protection_alarm); if (rc < 0) smblib_err(chg, "Couldn't cancel moisture protection alarm\n"); } out: vote(chg->awake_votable, MOISTURE_VOTER, false, 0); } static enum alarmtimer_restart moisture_protection_alarm_cb(struct alarm *alarm, ktime_t now) { struct smb_charger *chg = container_of(alarm, struct smb_charger, moisture_protection_alarm); smblib_dbg(chg, PR_MISC, "moisture Protection Alarm Triggered %lld\n", ktime_to_ms(now)); /* Atomic context, cannot use voter */ pm_stay_awake(chg->dev); schedule_work(&chg->moisture_protection_work); return ALARMTIMER_NORESTART; } static void smblib_chg_termination_work(struct work_struct *work) { int val; struct smb_charger *chg = container_of(work, struct smb_charger, chg_termination_work); union power_supply_propval pval = {0, }; int rc, input_present, delay = CHG_TERM_WA_ENTRY_DELAY_MS; int vbat_now_uv, max_fv_uv; u8 stat = 0; /* * Hold awake votable to prevent pm_relax being called prior to * completion of this work. */ vote(chg->awake_votable, CHG_TERMINATION_VOTER, true, 0); rc = smblib_is_input_present(chg, &input_present); if ((rc < 0) || !input_present) goto out; rc = smblib_get_prop_from_bms(chg, SMB5_QG_REAL_CAPACITY, &val); if ((rc < 0) || (val < 100)) { vote(chg->usb_icl_votable, CHG_TERMINATION_VOTER, false, 0); vote(chg->dc_suspend_votable, CHG_TERMINATION_VOTER, false, 0); smblib_err(chg, "Couldn't read SOC value, rc=%d\n", rc); goto out; } if ((rc < 0) || (pval.intval < 100)) { rc = smblib_read(chg, BATTERY_CHARGER_STATUS_1_REG, &stat); if (rc < 0) goto out; /* check we are not in termination to exit the WA */ if ((stat & BATTERY_CHARGER_STATUS_MASK) != TERMINATE_CHARGE) { vote(chg->usb_icl_votable, CHG_TERMINATION_VOTER, false, 0); vote(chg->dc_suspend_votable, CHG_TERMINATION_VOTER, false, 0); goto out; } } /* Get the battery float voltage */ rc = smblib_get_prop_from_bms(chg, SMB5_QG_VOLTAGE_MAX, &val); if (rc < 0) { smblib_err(chg, "Couldn't read voltage_max prop, rc=%d\n", rc); goto out; } max_fv_uv = val; rc = smblib_get_prop_from_bms(chg, SMB5_QG_CHARGE_FULL, &val); if (rc < 0) { smblib_err(chg, "Couldn't read charge_full prop, rc=%d\n", rc); goto out; } /* * On change in the value of learned capacity, re-initialize the * reference cc_soc value due to change in cc_soc characteristic value * at full capacity. Also, in case cc_soc_ref value is reset, * re-initialize it. */ if (val != chg->charge_full_cc || !chg->cc_soc_ref) { chg->charge_full_cc = val; rc = smblib_get_prop_from_bms(chg, SMB5_QG_VOLTAGE_NOW, &val); if (rc < 0) { smblib_err(chg, "Couldn't read voltage_now prop, rc=%d\n", rc); goto out; } /* * Store the Vbat at the charge termination to compare with * the current voltage to see if the Vbat is increasing after * charge termination in BSM. */ chg->term_vbat_uv = val; vbat_now_uv = val; rc = smblib_get_prop_from_bms(chg, SMB5_QG_CC_SOC, &val); if (rc < 0) { smblib_err(chg, "Couldn't read cc_soc prop, rc=%d\n", rc); goto out; } chg->cc_soc_ref = val; } else { rc = smblib_get_prop_from_bms(chg, SMB5_QG_VOLTAGE_NOW, &val); if (rc < 0) { smblib_err(chg, "Couldn't read voltage_now prop, rc=%d\n", rc); goto out; } vbat_now_uv = val; rc = smblib_get_prop_from_bms(chg, SMB5_QG_CC_SOC, &val); if (rc < 0) { smblib_err(chg, "Couldn't read cc_soc prop, rc=%d\n", rc); goto out; } } /* * In BSM a sudden jump in CC_SOC is not expected. If seen, its a * good_ocv or updated capacity, reject it. */ if (chg->last_cc_soc && val > (chg->last_cc_soc + 100)) { /* CC_SOC has increased by 1% from last time */ chg->cc_soc_ref = val; smblib_dbg(chg, PR_MISC, "cc_soc jumped(%d->%d), reset cc_soc_ref\n", chg->last_cc_soc, val); } chg->last_cc_soc = val; /* * Suspend/Unsuspend USB input to keep cc_soc within the 0.5% to 0.75% * overshoot range of the cc_soc value at termination and make sure that * vbat is indeed rising above vfloat. */ if (val < DIV_ROUND_CLOSEST(chg->cc_soc_ref * 10050, 10000)) { vote(chg->usb_icl_votable, CHG_TERMINATION_VOTER, false, 0); vote(chg->dc_suspend_votable, CHG_TERMINATION_VOTER, false, 0); delay = CHG_TERM_WA_ENTRY_DELAY_MS; } else if ((val > DIV_ROUND_CLOSEST(chg->cc_soc_ref * 10075, 10000)) && ((vbat_now_uv > chg->term_vbat_uv) && (vbat_now_uv > max_fv_uv))) { if (input_present & INPUT_PRESENT_USB) vote(chg->usb_icl_votable, CHG_TERMINATION_VOTER, true, 0); if (input_present & INPUT_PRESENT_DC) vote(chg->dc_suspend_votable, CHG_TERMINATION_VOTER, true, 0); delay = CHG_TERM_WA_EXIT_DELAY_MS; } smblib_dbg(chg, PR_MISC, "Chg Term WA readings: cc_soc: %d, cc_soc_ref: %d, delay: %d vbat_now %d term_vbat %d\n", val, chg->cc_soc_ref, delay, vbat_now_uv, chg->term_vbat_uv); alarm_start_relative(&chg->chg_termination_alarm, ms_to_ktime(delay)); out: vote(chg->awake_votable, CHG_TERMINATION_VOTER, false, 0); } static enum alarmtimer_restart chg_termination_alarm_cb(struct alarm *alarm, ktime_t now) { struct smb_charger *chg = container_of(alarm, struct smb_charger, chg_termination_alarm); smblib_dbg(chg, PR_MISC, "Charge termination WA alarm triggered %lld\n", ktime_to_ms(now)); /* Atomic context, cannot use voter */ pm_stay_awake(chg->dev); schedule_work(&chg->chg_termination_work); return ALARMTIMER_NORESTART; } static void apsd_timer_cb(struct timer_list *tm) { struct smb_charger *chg = container_of(tm, struct smb_charger, apsd_timer); smblib_dbg(chg, PR_MISC, "APSD Extented timer timeout at %lld\n", jiffies_to_msecs(jiffies)); chg->apsd_ext_timeout = true; } #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) { smblib_err(chg, "Batterydata not available\n"); goto out; } /* if BMS is not ready, defer the work */ if (IS_ERR_OR_NULL(chg->iio_chan_list_qg)) return; rc = smblib_get_prop_from_bms(chg, SMB5_QG_RESISTANCE_ID, &val.intval); if (rc < 0) { smblib_err(chg, "Failed 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); smblib_err(chg, "Failed 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 = smblib_update_jeita(chg, jeita_hard_thresholds, JEITA_HARD); if (rc < 0) { smblib_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 = smblib_update_jeita(chg, chg->jeita_soft_thlds, JEITA_SOFT); if (rc < 0) { smblib_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) { smblib_err(chg, "Couldn't get Soft Jeita hysteresis thresholds rc=%d\n", rc); goto out; } } else { /* Populate the jeita-soft-thresholds */ addr = CHGR_JEITA_THRESHOLD_BASE_REG(JEITA_SOFT); rc = smblib_batch_read(chg, addr, buff, 2); if (rc < 0) { pr_err("failed to read 0x%4X, rc=%d\n", addr, rc); goto out; } chg->jeita_soft_thlds[1] = buff[1] | buff[0] << 8; rc = smblib_batch_read(chg, addr + 2, buff, 2); if (rc < 0) { pr_err("failed to read 0x%4X, rc=%d\n", addr + 2, rc); goto out; } chg->jeita_soft_thlds[0] = 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[0] = chg->jeita_soft_thlds[0] - SOFT_JEITA_HYSTERESIS_OFFSET; chg->jeita_soft_hys_thlds[1] = 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) { smblib_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) { smblib_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) { smblib_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) { smblib_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) { smblib_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 = smblib_soft_jeita_arb_wa(chg); if (rc < 0) { smblib_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; } void smblib_moisture_detection_enable(struct smb_charger *chg, int pval) { int rc, input_present, val; if (chg->pd_disabled) return; smblib_is_input_present(chg, &input_present); if (pval) { chg->lpd_disabled = false; pr_debug("Moisture detection enabled\n"); if (input_present) schedule_delayed_work(&chg->lpd_ra_open_work, msecs_to_jiffies(300)); return; } chg->lpd_disabled = true; if (!is_client_vote_enabled(chg->usb_icl_votable, LPD_VOTER)) goto done; cancel_delayed_work_sync(&chg->lpd_ra_open_work); alarm_cancel(&chg->lpd_recheck_timer); vote(chg->usb_icl_votable, LPD_VOTER, false, 0); /* restore DRP mode */ val = QTI_POWER_SUPPLY_TYPEC_PR_DUAL; rc = smblib_set_prop_typec_power_role(chg, val); if (rc < 0) { smblib_err(chg, "Failed to set power-role to DRP rc=%d\n", rc); return; } chg->lpd_reason = LPD_NONE; chg->lpd_stage = LPD_STAGE_NONE; done: pr_debug("Moisture detection disabled\n"); } static void smblib_lpd_ra_open_work(struct work_struct *work) { struct smb_charger *chg = container_of(work, struct smb_charger, lpd_ra_open_work.work); u8 stat; int rc, val; if (chg->pr_swap_in_progress || chg->pd_hard_reset || chg->lpd_disabled) { chg->lpd_stage = LPD_STAGE_NONE; goto out; } if (chg->lpd_stage != LPD_STAGE_FLOAT) goto out; rc = smblib_read(chg, TYPE_C_MISC_STATUS_REG, &stat); if (rc < 0) { smblib_err(chg, "Couldn't read TYPE_C_MISC_STATUS_REG rc=%d\n", rc); goto out; } /* quit if moisture status is gone or in attached state */ if (!(stat & TYPEC_WATER_DETECTION_STATUS_BIT) || (stat & TYPEC_TCCDEBOUNCE_DONE_STATUS_BIT)) { chg->lpd_stage = LPD_STAGE_NONE; goto out; } chg->lpd_stage = LPD_STAGE_COMMIT; /* Enable source only mode */ val = QTI_POWER_SUPPLY_TYPEC_PR_SOURCE; rc = smblib_set_prop_typec_power_role(chg, val); if (rc < 0) { smblib_err(chg, "Couldn't set typec source only mode rc=%d\n", rc); goto out; } /* Wait 1.5ms to get SBUx ready */ usleep_range(1500, 1510); if (smblib_rsbux_low(chg, RSBU_K_300K_UV)) { /* Moisture detected, enable sink only mode */ val = QTI_POWER_SUPPLY_TYPEC_PR_SINK; rc = smblib_set_prop_typec_power_role(chg, val); if (rc < 0) { smblib_err(chg, "Couldn't set typec sink only rc=%d\n", rc); goto out; } chg->lpd_reason = LPD_MOISTURE_DETECTED; chg->moisture_present = true; vote(chg->usb_icl_votable, LPD_VOTER, true, 0); } else { /* Floating cable, disable water detection irq temporarily */ rc = smblib_masked_write(chg, TYPE_C_INTERRUPT_EN_CFG_2_REG, TYPEC_WATER_DETECTION_INT_EN_BIT, 0); if (rc < 0) { smblib_err(chg, "Couldn't set TYPE_C_INTERRUPT_EN_CFG_2_REG rc=%d\n", rc); goto out; } /* restore DRP mode */ val = QTI_POWER_SUPPLY_TYPEC_PR_DUAL; rc = smblib_set_prop_typec_power_role(chg, val); if (rc < 0) { smblib_err(chg, "Couldn't write 0x%02x to TYPE_C_INTRPT_ENB_SOFTWARE_CTRL rc=%d\n", val, rc); goto out; } chg->lpd_reason = LPD_FLOATING_CABLE; } /* recheck in 60 seconds */ alarm_start_relative(&chg->lpd_recheck_timer, ms_to_ktime(60000)); out: vote(chg->awake_votable, LPD_VOTER, false, 0); } static void smblib_lpd_detach_work(struct work_struct *work) { struct smb_charger *chg = container_of(work, struct smb_charger, lpd_detach_work.work); if (chg->lpd_stage == LPD_STAGE_FLOAT_CANCEL) chg->lpd_stage = LPD_STAGE_NONE; } static void smblib_cp_status_change_work(struct work_struct *work) { int rc = 0, val; struct smb_charger *chg = container_of(work, struct smb_charger, cp_status_change_work); if (!is_cp_available(chg)) goto relax; if (chg->cp_topo == -EINVAL) { rc = smblib_read_iio_prop(chg, CP, CP_PARALLEL_OUTPUT_MODE, &val); if (rc < 0) { smblib_err(chg, "Couldn't read cp topo rc=%d\n", rc); goto relax; } chg->cp_topo = val; if (chg->cp_topo == QTI_POWER_SUPPLY_PL_OUTPUT_VBAT && chg->cp_reason == QTI_POWER_SUPPLY_CP_WIRELESS) vote(chg->fcc_main_votable, WLS_PL_CHARGING_VOTER, true, 800000); } relax: pm_relax(chg->dev); } static int smblib_create_votables(struct smb_charger *chg) { int rc = 0; chg->fcc_votable = find_votable("FCC"); if (chg->fcc_votable == NULL) { rc = -EINVAL; smblib_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; smblib_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; smblib_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; smblib_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; smblib_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; smblib_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->smb_override_votable = create_votable("SMB_EN_OVERRIDE", VOTE_SET_ANY, smblib_smb_disable_override_vote_callback, chg); if (IS_ERR(chg->smb_override_votable)) { rc = PTR_ERR(chg->smb_override_votable); chg->smb_override_votable = NULL; return rc; } chg->dc_suspend_votable = create_votable("DC_SUSPEND", VOTE_SET_ANY, smblib_dc_suspend_vote_callback, chg); if (IS_ERR(chg->dc_suspend_votable)) { rc = PTR_ERR(chg->dc_suspend_votable); chg->dc_suspend_votable = NULL; return rc; } chg->awake_votable = create_votable("AWAKE", VOTE_SET_ANY, smblib_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, smblib_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->limited_irq_disable_votable = create_votable( "USB_LIMITED_IRQ_DISABLE", VOTE_SET_ANY, smblib_limited_irq_disable_vote_callback, chg); if (IS_ERR(chg->limited_irq_disable_votable)) { rc = PTR_ERR(chg->limited_irq_disable_votable); chg->limited_irq_disable_votable = NULL; return rc; } chg->hdc_irq_disable_votable = create_votable("USB_HDC_IRQ_DISABLE", VOTE_SET_ANY, smblib_hdc_irq_disable_vote_callback, chg); if (IS_ERR(chg->hdc_irq_disable_votable)) { rc = PTR_ERR(chg->hdc_irq_disable_votable); chg->hdc_irq_disable_votable = NULL; return rc; } chg->icl_irq_disable_votable = create_votable("USB_ICL_IRQ_DISABLE", VOTE_SET_ANY, smblib_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, smblib_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; } chg->bat_temp_irq_disable_votable = create_votable( "BAT_TEMP_IRQ_DISABLE", VOTE_SET_ANY, smblib_bat_temp_irq_disable_vote_callback, chg); if (IS_ERR(chg->bat_temp_irq_disable_votable)) { rc = PTR_ERR(chg->bat_temp_irq_disable_votable); chg->bat_temp_irq_disable_votable = NULL; return rc; } return rc; } static void smblib_destroy_votables(struct smb_charger *chg) { if (chg->dc_suspend_votable) destroy_votable(chg->dc_suspend_votable); 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); if (chg->bat_temp_irq_disable_votable) destroy_votable(chg->bat_temp_irq_disable_votable); } int smblib_init(struct smb_charger *chg) { int rc = 0, val; struct iio_channel **iio_list; mutex_init(&chg->smb_lock); mutex_init(&chg->irq_status_lock); mutex_init(&chg->dcin_aicl_lock); mutex_init(&chg->dpdm_lock); spin_lock_init(&chg->typec_pr_lock); INIT_WORK(&chg->bms_update_work, bms_update_work); INIT_WORK(&chg->pl_update_work, pl_update_work); INIT_WORK(&chg->jeita_update_work, jeita_update_work); INIT_WORK(&chg->dcin_aicl_work, dcin_aicl_work); INIT_WORK(&chg->cp_status_change_work, smblib_cp_status_change_work); INIT_DELAYED_WORK(&chg->clear_hdc_work, clear_hdc_work); INIT_DELAYED_WORK(&chg->icl_change_work, smblib_icl_change_work); INIT_DELAYED_WORK(&chg->pl_enable_work, smblib_pl_enable_work); INIT_DELAYED_WORK(&chg->uusb_otg_work, smblib_uusb_otg_work); INIT_DELAYED_WORK(&chg->bb_removal_work, smblib_bb_removal_work); INIT_DELAYED_WORK(&chg->lpd_ra_open_work, smblib_lpd_ra_open_work); INIT_DELAYED_WORK(&chg->lpd_detach_work, smblib_lpd_detach_work); INIT_DELAYED_WORK(&chg->thermal_regulation_work, smblib_thermal_regulation_work); INIT_DELAYED_WORK(&chg->usbov_dbc_work, smblib_usbov_dbc_work); INIT_DELAYED_WORK(&chg->pr_swap_detach_work, smblib_pr_swap_detach_work); INIT_DELAYED_WORK(&chg->pr_lock_clear_work, smblib_pr_lock_clear_work); timer_setup(&chg->apsd_timer, apsd_timer_cb, 0); INIT_DELAYED_WORK(&chg->role_reversal_check, smblib_typec_role_check_work); if (chg->wa_flags & CHG_TERMINATION_WA) { INIT_WORK(&chg->chg_termination_work, smblib_chg_termination_work); if (alarmtimer_get_rtcdev()) { alarm_init(&chg->chg_termination_alarm, ALARM_BOOTTIME, chg_termination_alarm_cb); } else { smblib_err(chg, "Couldn't get rtc device\n"); return -ENODEV; } } if (chg->uusb_moisture_protection_enabled) { INIT_WORK(&chg->moisture_protection_work, smblib_moisture_protection_work); if (alarmtimer_get_rtcdev()) { alarm_init(&chg->moisture_protection_alarm, ALARM_BOOTTIME, moisture_protection_alarm_cb); } else { smblib_err(chg, "Failed to initialize moisture protection alarm\n"); return -ENODEV; } } if (alarmtimer_get_rtcdev()) { alarm_init(&chg->dcin_aicl_alarm, ALARM_REALTIME, dcin_aicl_alarm_cb); } else { smblib_err(chg, "Failed to initialize dcin aicl alarm\n"); return -ENODEV; } chg->fake_capacity = -EINVAL; chg->fake_input_current_limited = -EINVAL; chg->fake_batt_status = -EINVAL; chg->sink_src_mode = UNATTACHED_MODE; chg->jeita_configured = false; chg->sec_chg_selected = QTI_POWER_SUPPLY_CHARGER_SEC_NONE; chg->cp_reason = QTI_POWER_SUPPLY_CP_NONE; chg->thermal_status = TEMP_BELOW_RANGE; chg->typec_irq_en = true; chg->cp_topo = -EINVAL; chg->dr_mode = TYPEC_PORT_DRP; switch (chg->mode) { case PARALLEL_MASTER: rc = qcom_batt_init(chg->dev, &chg->chg_param); if (rc < 0) { smblib_err(chg, "Couldn't init qcom_batt_init rc=%d\n", rc); return rc; } rc = qcom_step_chg_init(chg->dev, chg->step_chg_enabled, chg->sw_jeita_enabled, chg->jeita_arb_enable, chg->iio_chans); if (rc < 0) { smblib_err(chg, "Couldn't init qcom_step_chg_init rc=%d\n", rc); return rc; } rc = smblib_create_votables(chg); if (rc < 0) { smblib_err(chg, "Couldn't create votables rc=%d\n", rc); return rc; } iio_list = get_ext_channels(chg->dev, smblib_qg_ext_iio_chan, ARRAY_SIZE(smblib_qg_ext_iio_chan)); if (!IS_ERR(iio_list)) chg->iio_chan_list_qg = iio_list; if (chg->sec_pl_present) { iio_list = get_ext_channels( chg->dev, smblib_parallel_ext_iio_chan, ARRAY_SIZE(smblib_parallel_ext_iio_chan)); if (!IS_ERR(iio_list)) chg->iio_chan_list_smb_parallel = iio_list; if (chg->iio_chan_list_smb_parallel) { if (chg->sec_chg_selected != QTI_POWER_SUPPLY_CHARGER_SEC_CP) { rc = smblib_select_sec_charger(chg, QTI_POWER_SUPPLY_CHARGER_SEC_PL, QTI_POWER_SUPPLY_CP_NONE, false); if (rc < 0) smblib_err(chg, "Couldn't config pl charger rc=%d\n", rc); } if (chg->smb_temp_max == -EINVAL) { rc = smblib_get_thermal_threshold(chg, SMB_REG_H_THRESHOLD_MSB_REG, &chg->smb_temp_max); if (rc < 0) { dev_err(chg->dev, "Couldn't get charger_temp_max rc=%d\n", rc); return rc; } } val = chg->smb_temp_max; rc = smblib_write_iio_prop(chg, SMB_PARALLEL, SMB_CHARGER_TEMP_MAX, val); if (rc < 0) { dev_err(chg->dev, "Couldn't set POWER_SUPPLY_PROP_CHARGER_TEMP_MAX rc=%d\n", rc); return rc; } } } rc = smblib_register_notifier(chg); if (rc < 0) { smblib_err(chg, "Couldn't register notifier rc=%d\n", rc); return rc; } break; case PARALLEL_SLAVE: break; default: smblib_err(chg, "Unsupported mode %d\n", chg->mode); return -EINVAL; } return rc; } int smblib_deinit(struct smb_charger *chg) { switch (chg->mode) { case PARALLEL_MASTER: if (chg->uusb_moisture_protection_enabled) { alarm_cancel(&chg->moisture_protection_alarm); cancel_work_sync(&chg->moisture_protection_work); } if (chg->wa_flags & CHG_TERMINATION_WA) { alarm_cancel(&chg->chg_termination_alarm); cancel_work_sync(&chg->chg_termination_work); } del_timer_sync(&chg->apsd_timer); cancel_work_sync(&chg->bms_update_work); cancel_work_sync(&chg->jeita_update_work); cancel_work_sync(&chg->pl_update_work); cancel_work_sync(&chg->dcin_aicl_work); cancel_work_sync(&chg->cp_status_change_work); cancel_delayed_work_sync(&chg->clear_hdc_work); cancel_delayed_work_sync(&chg->icl_change_work); cancel_delayed_work_sync(&chg->pl_enable_work); cancel_delayed_work_sync(&chg->uusb_otg_work); cancel_delayed_work_sync(&chg->bb_removal_work); cancel_delayed_work_sync(&chg->lpd_ra_open_work); cancel_delayed_work_sync(&chg->lpd_detach_work); cancel_delayed_work_sync(&chg->thermal_regulation_work); cancel_delayed_work_sync(&chg->usbov_dbc_work); cancel_delayed_work_sync(&chg->role_reversal_check); cancel_delayed_work_sync(&chg->pr_swap_detach_work); power_supply_unreg_notifier(&chg->nb); smblib_destroy_votables(chg); qcom_step_chg_deinit(); qcom_batt_deinit(); break; case PARALLEL_SLAVE: break; default: smblib_err(chg, "Unsupported mode %d\n", chg->mode); return -EINVAL; } return 0; }