/* Copyright (c) 2016 The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #include #include #include #include #include "smb-lib.h" #include "smb-reg.h" #include "storm-watch.h" #include "pmic-voter.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) static bool is_secure(struct smb_charger *chg, int addr) { /* assume everything above 0xA0 is secure */ return (bool)((addr & 0xFF) >= 0xA0); } int smblib_read(struct smb_charger *chg, u16 addr, u8 *val) { unsigned int temp; int rc = 0; rc = regmap_read(chg->regmap, addr, &temp); if (rc >= 0) *val = (u8)temp; return rc; } int smblib_masked_write(struct smb_charger *chg, u16 addr, u8 mask, u8 val) { int rc = 0; mutex_lock(&chg->write_lock); if (is_secure(chg, addr)) { rc = regmap_write(chg->regmap, (addr & 0xFF00) | 0xD0, 0xA5); if (rc < 0) goto unlock; } rc = regmap_update_bits(chg->regmap, addr, mask, val); unlock: mutex_unlock(&chg->write_lock); return rc; } int smblib_write(struct smb_charger *chg, u16 addr, u8 val) { int rc = 0; mutex_lock(&chg->write_lock); if (is_secure(chg, addr)) { rc = regmap_write(chg->regmap, (addr & ~(0xFF)) | 0xD0, 0xA5); if (rc < 0) goto unlock; } rc = regmap_write(chg->regmap, addr, val); unlock: mutex_unlock(&chg->write_lock); return rc; } static int smblib_get_step_cc_delta(struct smb_charger *chg, int *cc_delta_ua) { int rc, step_state; u8 stat; if (!chg->step_chg_enabled) { *cc_delta_ua = 0; return 0; } 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; } step_state = (stat & STEP_CHARGING_STATUS_MASK) >> STEP_CHARGING_STATUS_SHIFT; rc = smblib_get_charge_param(chg, &chg->param.step_cc_delta[step_state], cc_delta_ua); if (rc < 0) { smblib_err(chg, "Couldn't get step cc delta rc=%d\n", rc); return rc; } return 0; } 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_2_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_SOFT_LIMIT_MASK)) { *cc_delta_ua = 0; return 0; } rc = smblib_get_charge_param(chg, &chg->param.jeita_cc_comp, &cc_minus_ua); if (rc < 0) { smblib_err(chg, "Couldn't get jeita cc minus rc=%d\n", rc); return rc; } *cc_delta_ua = -cc_minus_ua; return 0; } static void smblib_split_fcc(struct smb_charger *chg, int total_ua, int *master_ua, int *slave_ua) { int rc, jeita_cc_delta_ua, step_cc_delta_ua, effective_total_ua, slave_limited_ua, hw_cc_delta_ua = 0; rc = smblib_get_step_cc_delta(chg, &step_cc_delta_ua); if (rc < 0) { smblib_err(chg, "Couldn't get step cc delta rc=%d\n", rc); step_cc_delta_ua = 0; } else { hw_cc_delta_ua = step_cc_delta_ua; } 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; } else if (jeita_cc_delta_ua < 0) { /* HW will take the min between JEITA and step charge */ hw_cc_delta_ua = min(hw_cc_delta_ua, jeita_cc_delta_ua); } effective_total_ua = max(0, total_ua + hw_cc_delta_ua); slave_limited_ua = min(effective_total_ua, chg->input_limited_fcc_ua); *slave_ua = (slave_limited_ua * chg->pl.slave_pct) / 100; *slave_ua = (*slave_ua * chg->pl.taper_pct) / 100; *master_ua = max(0, total_ua - *slave_ua); } /******************** * 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; } 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; } #define FSW_600HZ_FOR_5V 600 #define FSW_800HZ_FOR_6V_8V 800 #define FSW_1MHZ_FOR_REMOVAL 1000 #define FSW_1MHZ_FOR_9V 1000 #define FSW_1P2MHZ_FOR_12V 1200 static int smblib_set_opt_freq_buck(struct smb_charger *chg, int fsw_khz) { union power_supply_propval pval = {0, }; int rc = 0; rc = smblib_set_charge_param(chg, &chg->param.freq_buck, fsw_khz); if (rc < 0) dev_err(chg->dev, "Error in setting freq_buck rc=%d\n", rc); if (chg->mode == PARALLEL_MASTER && chg->pl.psy) { pval.intval = fsw_khz; rc = power_supply_set_property(chg->pl.psy, POWER_SUPPLY_PROP_BUCK_FREQ, &pval); if (rc < 0) { dev_err(chg->dev, "Could not set parallel buck_freq rc=%d\n", rc); return rc; } } return rc; } struct apsd_result { const char * const name; const u8 bit; const enum power_supply_type pst; }; enum { UNKNOWN, SDP, CDP, DCP, OCP, FLOAT, HVDCP2, HVDCP3, MAX_TYPES }; static const struct apsd_result const smblib_apsd_results[] = { [UNKNOWN] = { .name = "UNKNOWN", .bit = 0, .pst = POWER_SUPPLY_TYPE_UNKNOWN }, [SDP] = { .name = "SDP", .bit = SDP_CHARGER_BIT, .pst = POWER_SUPPLY_TYPE_USB }, [CDP] = { .name = "CDP", .bit = CDP_CHARGER_BIT, .pst = POWER_SUPPLY_TYPE_USB_CDP }, [DCP] = { .name = "DCP", .bit = DCP_CHARGER_BIT, .pst = POWER_SUPPLY_TYPE_USB_DCP }, [OCP] = { .name = "OCP", .bit = OCP_CHARGER_BIT, .pst = POWER_SUPPLY_TYPE_USB_DCP }, [FLOAT] = { .name = "FLOAT", .bit = FLOAT_CHARGER_BIT, .pst = POWER_SUPPLY_TYPE_USB_DCP }, [HVDCP2] = { .name = "HVDCP2", .bit = DCP_CHARGER_BIT | QC_2P0_BIT, .pst = POWER_SUPPLY_TYPE_USB_HVDCP }, [HVDCP3] = { .name = "HVDCP3", .bit = DCP_CHARGER_BIT | QC_3P0_BIT, .pst = 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; } /******************** * REGISTER SETTERS * ********************/ 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_err(chg, "%s: %d is out of range [%d, %d]\n", param->name, val_u, param->min_u, param->max_u); return -EINVAL; } 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 step_charge_soc_update(struct smb_charger *chg, int capacity) { int rc = 0; rc = smblib_set_charge_param(chg, &chg->param.step_soc, capacity); if (rc < 0) { smblib_err(chg, "Error in updating soc, rc=%d\n", rc); return rc; } rc = smblib_write(chg, STEP_CHG_SOC_VBATT_V_UPDATE_REG, STEP_CHG_SOC_VBATT_V_UPDATE_BIT); if (rc < 0) { smblib_err(chg, "Couldn't set STEP_CHG_SOC_VBATT_V_UPDATE_REG rc=%d\n", rc); return rc; } return rc; } int smblib_set_usb_suspend(struct smb_charger *chg, bool suspend) { int rc = 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); return rc; } 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; } #define MICRO_5V 5000000 #define MICRO_9V 9000000 #define MICRO_12V 12000000 static int smblib_set_usb_pd_allowed_voltage(struct smb_charger *chg, int min_allowed_uv, int max_allowed_uv) { int rc; u8 allowed_voltage; if (min_allowed_uv == MICRO_5V && max_allowed_uv == MICRO_5V) { allowed_voltage = USBIN_ADAPTER_ALLOW_5V; smblib_set_opt_freq_buck(chg, FSW_600HZ_FOR_5V); } else if (min_allowed_uv == MICRO_9V && max_allowed_uv == MICRO_9V) { allowed_voltage = USBIN_ADAPTER_ALLOW_9V; smblib_set_opt_freq_buck(chg, FSW_1MHZ_FOR_9V); } else if (min_allowed_uv == MICRO_12V && max_allowed_uv == MICRO_12V) { allowed_voltage = USBIN_ADAPTER_ALLOW_12V; smblib_set_opt_freq_buck(chg, FSW_1P2MHZ_FOR_12V); } else if (min_allowed_uv < MICRO_9V && max_allowed_uv <= MICRO_9V) { allowed_voltage = USBIN_ADAPTER_ALLOW_5V_TO_9V; } else if (min_allowed_uv < MICRO_9V && max_allowed_uv <= MICRO_12V) { allowed_voltage = USBIN_ADAPTER_ALLOW_5V_TO_12V; } else if (min_allowed_uv < MICRO_12V && max_allowed_uv <= MICRO_12V) { allowed_voltage = USBIN_ADAPTER_ALLOW_9V_TO_12V; } else { smblib_err(chg, "invalid allowed voltage [%d, %d]\n", min_allowed_uv, max_allowed_uv); return -EINVAL; } rc = smblib_write(chg, USBIN_ADAPTER_ALLOW_CFG_REG, allowed_voltage); if (rc < 0) { smblib_err(chg, "Couldn't write 0x%02x to USBIN_ADAPTER_ALLOW_CFG rc=%d\n", allowed_voltage, rc); return rc; } return rc; } /******************** * HELPER FUNCTIONS * ********************/ static int try_rerun_apsd_for_hvdcp(struct smb_charger *chg) { const struct apsd_result *apsd_result; /* * PD_INACTIVE_VOTER on hvdcp_disable_votable indicates whether * apsd rerun was tried earlier */ if (get_client_vote(chg->hvdcp_disable_votable, PD_INACTIVE_VOTER)) { vote(chg->hvdcp_disable_votable, PD_INACTIVE_VOTER, false, 0); /* ensure hvdcp is enabled */ if (!get_effective_result(chg->hvdcp_disable_votable)) { apsd_result = smblib_get_apsd_result(chg); if (apsd_result->bit & (QC_2P0_BIT | QC_3P0_BIT)) { /* rerun APSD */ smblib_dbg(chg, PR_MISC, "rerun APSD\n"); smblib_masked_write(chg, CMD_APSD_REG, APSD_RERUN_BIT, APSD_RERUN_BIT); } } } return 0; } static const struct apsd_result *smblib_update_usb_type(struct smb_charger *chg) { const struct apsd_result *apsd_result; /* if PD is active, APSD is disabled so won't have a valid result */ if (chg->pd_active) { chg->usb_psy_desc.type = POWER_SUPPLY_TYPE_USB_PD; return 0; } apsd_result = smblib_get_apsd_result(chg); chg->usb_psy_desc.type = apsd_result->pst; 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 (!chg->bms_psy) chg->bms_psy = psy; if (ev == PSY_EVENT_PROP_CHANGED && chg->batt_psy) schedule_work(&chg->bms_update_work); } if (!chg->pl.psy && !strcmp(psy->desc->name, "parallel")) { chg->pl.psy = psy; schedule_work(&chg->pl_detect_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; } int smblib_mapping_soc_from_field_value(struct smb_chg_param *param, int val_u, u8 *val_raw) { if (val_u > param->max_u || val_u < param->min_u) return -EINVAL; *val_raw = val_u << 1; return 0; } int smblib_mapping_cc_delta_to_field_value(struct smb_chg_param *param, u8 val_raw) { int val_u = val_raw * param->step_u + param->min_u; if (val_u > param->max_u) val_u -= param->max_u * 2; return val_u; } int smblib_mapping_cc_delta_from_field_value(struct smb_chg_param *param, int val_u, u8 *val_raw) { if (val_u > param->max_u || val_u < param->min_u - param->max_u) return -EINVAL; val_u += param->max_u * 2 - param->min_u; val_u %= param->max_u * 2; *val_raw = val_u / param->step_u; return 0; } /********************* * VOTABLE CALLBACKS * *********************/ static int smblib_usb_suspend_vote_callback(struct votable *votable, void *data, int suspend, const char *client) { struct smb_charger *chg = data; /* resume input if suspend is invalid */ if (suspend < 0) suspend = 0; return smblib_set_usb_suspend(chg, (bool)suspend); } static int smblib_dc_suspend_vote_callback(struct votable *votable, void *data, int suspend, const char *client) { struct smb_charger *chg = data; /* resume input if suspend is invalid */ if (suspend < 0) suspend = 0; return smblib_set_dc_suspend(chg, (bool)suspend); } static int smblib_fcc_max_vote_callback(struct votable *votable, void *data, int fcc_ua, const char *client) { struct smb_charger *chg = data; return vote(chg->fcc_votable, FCC_MAX_RESULT_VOTER, true, fcc_ua); } static int smblib_fcc_vote_callback(struct votable *votable, void *data, int total_fcc_ua, const char *client) { struct smb_charger *chg = data; union power_supply_propval pval = {0, }; int rc, master_fcc_ua = total_fcc_ua, slave_fcc_ua = 0; if (total_fcc_ua < 0) return 0; if (chg->mode == PARALLEL_MASTER && !get_effective_result_locked(chg->pl_disable_votable)) { smblib_split_fcc(chg, total_fcc_ua, &master_fcc_ua, &slave_fcc_ua); /* * parallel charger is not disabled, implying that * chg->pl.psy exists */ pval.intval = slave_fcc_ua; rc = power_supply_set_property(chg->pl.psy, POWER_SUPPLY_PROP_CURRENT_MAX, &pval); if (rc < 0) { smblib_err(chg, "Could not set parallel fcc, rc=%d\n", rc); return rc; } chg->pl.slave_fcc_ua = slave_fcc_ua; } rc = smblib_set_charge_param(chg, &chg->param.fcc, master_fcc_ua); if (rc < 0) { smblib_err(chg, "Couldn't set master fcc rc=%d\n", rc); return rc; } smblib_dbg(chg, PR_PARALLEL, "master_fcc=%d slave_fcc=%d distribution=(%d/%d)\n", master_fcc_ua, slave_fcc_ua, (master_fcc_ua * 100) / total_fcc_ua, (slave_fcc_ua * 100) / total_fcc_ua); return 0; } #define PARALLEL_FLOAT_VOLTAGE_DELTA_UV 50000 static int smblib_fv_vote_callback(struct votable *votable, void *data, int fv_uv, const char *client) { struct smb_charger *chg = data; union power_supply_propval pval = {0, }; int rc = 0; if (fv_uv < 0) { smblib_dbg(chg, PR_MISC, "No Voter\n"); return 0; } rc = smblib_set_charge_param(chg, &chg->param.fv, fv_uv); if (rc < 0) { smblib_err(chg, "Couldn't set floating voltage rc=%d\n", rc); return rc; } if (chg->mode == PARALLEL_MASTER && chg->pl.psy) { pval.intval = fv_uv + PARALLEL_FLOAT_VOLTAGE_DELTA_UV; rc = power_supply_set_property(chg->pl.psy, POWER_SUPPLY_PROP_VOLTAGE_MAX, &pval); if (rc < 0) { smblib_err(chg, "Couldn't set float on parallel rc=%d\n", rc); return rc; } } return 0; } #define USBIN_25MA 25000 #define USBIN_100MA 100000 #define USBIN_150MA 150000 #define USBIN_500MA 500000 #define USBIN_900MA 900000 static int smblib_usb_icl_vote_callback(struct votable *votable, void *data, int icl_ua, const char *client) { struct smb_charger *chg = data; int rc = 0; bool suspend = (icl_ua < USBIN_25MA); u8 icl_options = 0; if (suspend) goto out; if (chg->usb_psy_desc.type != POWER_SUPPLY_TYPE_USB) { 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); return rc; } goto out; } /* 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: smblib_err(chg, "ICL %duA isn't supported for SDP\n", icl_ua); icl_options = 0; break; } out: 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 opetions rc=%d\n", rc); return rc; } rc = vote(chg->usb_suspend_votable, PD_VOTER, suspend, 0); if (rc < 0) { smblib_err(chg, "Couldn't %s input rc=%d\n", suspend ? "suspend" : "resume", rc); return rc; } return rc; } #define MICRO_250MA 250000 static int smblib_otg_cl_config(struct smb_charger *chg, int otg_cl_ua) { int rc = 0; rc = smblib_set_charge_param(chg, &chg->param.otg_cl, otg_cl_ua); if (rc < 0) { smblib_err(chg, "Couldn't set otg current limit rc=%d\n", rc); return rc; } /* configure PFM/PWM mode for OTG regulator */ rc = smblib_masked_write(chg, DC_ENG_SSUPPLY_CFG3_REG, ENG_SSUPPLY_CFG_SKIP_TH_V0P2_BIT, otg_cl_ua > MICRO_250MA ? 1 : 0); if (rc < 0) { smblib_err(chg, "Couldn't write DC_ENG_SSUPPLY_CFG3_REG rc=%d\n", rc); return rc; } return rc; } static int smblib_dc_icl_vote_callback(struct votable *votable, void *data, int icl_ua, const char *client) { struct smb_charger *chg = data; int rc = 0; bool suspend; if (icl_ua < 0) { smblib_dbg(chg, PR_MISC, "No Voter hence suspending\n"); icl_ua = 0; } suspend = (icl_ua < USBIN_25MA); if (suspend) goto suspend; rc = smblib_set_charge_param(chg, &chg->param.dc_icl, icl_ua); if (rc < 0) { smblib_err(chg, "Couldn't set DC input current limit rc=%d\n", rc); return rc; } suspend: rc = vote(chg->dc_suspend_votable, USER_VOTER, suspend, 0); if (rc < 0) { smblib_err(chg, "Couldn't vote to %s DC rc=%d\n", suspend ? "suspend" : "resume", rc); return rc; } return rc; } static int smblib_pd_disallowed_votable_indirect_callback( struct votable *votable, void *data, int disallowed, const char *client) { struct smb_charger *chg = data; int rc; rc = vote(chg->pd_allowed_votable, PD_DISALLOWED_INDIRECT_VOTER, !disallowed, 0); return rc; } 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_pl_disable_vote_callback(struct votable *votable, void *data, int pl_disable, const char *client) { struct smb_charger *chg = data; union power_supply_propval pval = {0, }; int rc; if (chg->mode != PARALLEL_MASTER || !chg->pl.psy) return 0; chg->pl.taper_pct = 100; rerun_election(chg->fv_votable); rerun_election(chg->fcc_votable); pval.intval = pl_disable; rc = power_supply_set_property(chg->pl.psy, POWER_SUPPLY_PROP_INPUT_SUSPEND, &pval); if (rc < 0) { smblib_err(chg, "Couldn't change slave suspend state rc=%d\n", rc); return rc; } smblib_dbg(chg, PR_PARALLEL, "parallel charging %s\n", pl_disable ? "disabled" : "enabled"); 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_pl_enable_indirect_vote_callback(struct votable *votable, void *data, int chg_enable, const char *client) { struct smb_charger *chg = data; vote(chg->pl_disable_votable, PL_INDIRECT_VOTER, !chg_enable, 0); return 0; } static int smblib_hvdcp_disable_vote_callback(struct votable *votable, void *data, int hvdcp_disable, const char *client) { struct smb_charger *chg = data; int rc; u8 val = HVDCP_AUTH_ALG_EN_CFG_BIT | HVDCP_AUTONOMOUS_MODE_EN_CFG_BIT | HVDCP_EN_BIT; /* * Disable the autonomous bit and auth bit for disabling hvdcp. * This ensures only qc 2.0 detection runs but no vbus * negotiation happens. */ if (hvdcp_disable) val = HVDCP_EN_BIT; rc = smblib_masked_write(chg, USBIN_OPTIONS_1_CFG_REG, HVDCP_EN_BIT | HVDCP_AUTONOMOUS_MODE_EN_CFG_BIT | HVDCP_AUTH_ALG_EN_CFG_BIT, val); if (rc < 0) { smblib_err(chg, "Couldn't %s hvdcp rc=%d\n", hvdcp_disable ? "disable" : "enable", rc); return rc; } return 0; } static int smblib_apsd_disable_vote_callback(struct votable *votable, void *data, int apsd_disable, const char *client) { struct smb_charger *chg = data; int rc; rc = smblib_masked_write(chg, USBIN_OPTIONS_1_CFG_REG, AUTO_SRC_DETECT_BIT, apsd_disable ? 0 : AUTO_SRC_DETECT_BIT); if (rc < 0) { smblib_err(chg, "Couldn't %s APSD rc=%d\n", apsd_disable ? "disable" : "enable", rc); return rc; } return 0; } /***************** * OTG REGULATOR * *****************/ #define MAX_SOFTSTART_TRIES 2 int smblib_vbus_regulator_enable(struct regulator_dev *rdev) { struct smb_charger *chg = rdev_get_drvdata(rdev); u8 stat; int rc = 0; int tries = MAX_SOFTSTART_TRIES; rc = smblib_masked_write(chg, OTG_ENG_OTG_CFG_REG, ENG_BUCKBOOST_HALT1_8_MODE_BIT, ENG_BUCKBOOST_HALT1_8_MODE_BIT); if (rc < 0) { smblib_err(chg, "Couldn't set OTG_ENG_OTG_CFG_REG rc=%d\n", rc); return rc; } rc = smblib_write(chg, CMD_OTG_REG, OTG_EN_BIT); if (rc < 0) { smblib_err(chg, "Couldn't enable OTG regulator rc=%d\n", rc); return rc; } /* waiting for boost readiness, usually ~1ms, 2ms in worst case */ do { usleep_range(1000, 1100); rc = smblib_read(chg, OTG_STATUS_REG, &stat); if (rc < 0) { smblib_err(chg, "Couldn't read OTG_STATUS_REG rc=%d\n", rc); return rc; } if (stat & BOOST_SOFTSTART_DONE_BIT) { smblib_otg_cl_config(chg, chg->otg_cl_ua); break; } } while (--tries); if (tries == 0) smblib_err(chg, "Timeout waiting for boost softstart rc=%d\n", rc); return rc; } int smblib_vbus_regulator_disable(struct regulator_dev *rdev) { struct smb_charger *chg = rdev_get_drvdata(rdev); int rc = 0; rc = smblib_write(chg, CMD_OTG_REG, 0); if (rc < 0) { smblib_err(chg, "Couldn't disable OTG regulator rc=%d\n", rc); return rc; } smblib_otg_cl_config(chg, MICRO_250MA); rc = smblib_masked_write(chg, OTG_ENG_OTG_CFG_REG, ENG_BUCKBOOST_HALT1_8_MODE_BIT, 0); if (rc < 0) { smblib_err(chg, "Couldn't set OTG_ENG_OTG_CFG_REG rc=%d\n", rc); return rc; } return rc; } 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, 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; } /******************* * VCONN REGULATOR * * *****************/ int smblib_vconn_regulator_enable(struct regulator_dev *rdev) { struct smb_charger *chg = rdev_get_drvdata(rdev); u8 stat; int rc = 0; /* * VCONN_EN_ORIENTATION is overloaded with overriding the CC pin used * for Vconn, and it should be set with reverse polarity of CC_OUT. */ rc = smblib_read(chg, TYPE_C_STATUS_4_REG, &stat); if (rc < 0) { smblib_err(chg, "Couldn't read TYPE_C_STATUS_4 rc=%d\n", rc); return rc; } stat = stat & CC_ORIENTATION_BIT ? 0 : VCONN_EN_ORIENTATION_BIT; rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG, VCONN_EN_VALUE_BIT | VCONN_EN_ORIENTATION_BIT, VCONN_EN_VALUE_BIT | stat); if (rc < 0) smblib_err(chg, "Couldn't enable vconn setting rc=%d\n", rc); return rc; } int smblib_vconn_regulator_disable(struct regulator_dev *rdev) { struct smb_charger *chg = rdev_get_drvdata(rdev); int rc = 0; rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG, VCONN_EN_VALUE_BIT, 0); if (rc < 0) smblib_err(chg, "Couldn't disable vconn regulator rc=%d\n", rc); return rc; } int smblib_vconn_regulator_is_enabled(struct regulator_dev *rdev) { struct smb_charger *chg = rdev_get_drvdata(rdev); int rc = 0; u8 cmd; rc = smblib_read(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_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; } /******************** * 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_suspend_votable, USER_VOTER) && 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; } if (chg->bms_psy) rc = power_supply_get_property(chg->bms_psy, POWER_SUPPLY_PROP_CAPACITY, val); return rc; } 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; 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; if (!usb_online && !dc_online) { val->intval = POWER_SUPPLY_STATUS_DISCHARGING; return rc; } 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; switch (stat) { case TRICKLE_CHARGE: case PRE_CHARGE: case FAST_CHARGE: case FULLON_CHARGE: case TAPER_CHARGE: val->intval = POWER_SUPPLY_STATUS_CHARGING; break; case TERMINATE_CHARGE: case INHIBIT_CHARGE: val->intval = POWER_SUPPLY_STATUS_FULL; break; case DISABLE_CHARGE: val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; break; default: val->intval = POWER_SUPPLY_STATUS_UNKNOWN; break; } 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: case PRE_CHARGE: val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; break; case FAST_CHARGE: case FULLON_CHARGE: val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; break; case TAPER_CHARGE: val->intval = POWER_SUPPLY_CHARGE_TYPE_TAPER; 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; 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_batt_voltage_now(chg, &pval); if (!rc) { /* * If Vbatt is within 40mV above Vfloat, then don't * treat it as overvoltage. */ if (pval.intval >= get_effective_result(chg->fv_votable) + 40000) { val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; smblib_err(chg, "battery over-voltage\n"); goto done; } } } 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_LIMIT_BIT) val->intval = POWER_SUPPLY_HEALTH_COOL; else if (stat & BAT_TEMP_STATUS_HOT_SOFT_LIMIT_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_input_current_limited(struct smb_charger *chg, union power_supply_propval *val) { u8 stat; int rc; 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->intval = (stat & SOFT_ILIMIT_BIT) || chg->is_hdc; return 0; } int smblib_get_prop_batt_voltage_now(struct smb_charger *chg, union power_supply_propval *val) { int rc; if (!chg->bms_psy) return -EINVAL; rc = power_supply_get_property(chg->bms_psy, POWER_SUPPLY_PROP_VOLTAGE_NOW, val); return rc; } int smblib_get_prop_batt_current_now(struct smb_charger *chg, union power_supply_propval *val) { int rc; if (!chg->bms_psy) return -EINVAL; rc = power_supply_get_property(chg->bms_psy, POWER_SUPPLY_PROP_CURRENT_NOW, val); return rc; } int smblib_get_prop_batt_temp(struct smb_charger *chg, union power_supply_propval *val) { int rc; if (!chg->bms_psy) return -EINVAL; rc = power_supply_get_property(chg->bms_psy, POWER_SUPPLY_PROP_TEMP, val); return rc; } int smblib_get_prop_step_chg_step(struct smb_charger *chg, union power_supply_propval *val) { int rc; u8 stat; if (!chg->step_chg_enabled) { val->intval = -1; return 0; } 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; } val->intval = (stat & STEP_CHARGING_STATUS_MASK) >> STEP_CHARGING_STATUS_SHIFT; return rc; } int smblib_get_prop_batt_charge_done(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; } stat = stat & BATTERY_CHARGER_STATUS_MASK; val->intval = (stat == TERMINATE_CHARGE); return 0; } /*********************** * BATTERY PSY SETTERS * ***********************/ int smblib_set_prop_input_suspend(struct smb_charger *chg, const union power_supply_propval *val) { int rc; rc = vote(chg->usb_suspend_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_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; } /******************* * DC PSY GETTERS * *******************/ int smblib_get_prop_dc_present(struct smb_charger *chg, union power_supply_propval *val) { int rc; u8 stat; 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 (get_client_vote(chg->dc_suspend_votable, USER_VOTER)) { val->intval = false; 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_BIT); return rc; } int smblib_get_prop_dc_current_max(struct smb_charger *chg, union power_supply_propval *val) { val->intval = get_effective_result_locked(chg->dc_icl_votable); return 0; } /******************* * USB PSY SETTERS * * *****************/ int smblib_set_prop_dc_current_max(struct smb_charger *chg, const union power_supply_propval *val) { int rc; rc = vote(chg->dc_icl_votable, USER_VOTER, true, val->intval); 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(chg->usb_suspend_votable, USER_VOTER)) { val->intval = false; 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_BIT); return rc; } int smblib_get_prop_usb_voltage_now(struct smb_charger *chg, union power_supply_propval *val) { int rc = 0; rc = smblib_get_prop_usb_present(chg, val); if (rc < 0 || !val->intval) return rc; if (!chg->iio.usbin_v_chan || PTR_ERR(chg->iio.usbin_v_chan) == -EPROBE_DEFER) chg->iio.usbin_v_chan = iio_channel_get(chg->dev, "usbin_v"); if (IS_ERR(chg->iio.usbin_v_chan)) return PTR_ERR(chg->iio.usbin_v_chan); return iio_read_channel_processed(chg->iio.usbin_v_chan, &val->intval); } int smblib_get_prop_pd_current_max(struct smb_charger *chg, union power_supply_propval *val) { val->intval = get_client_vote_locked(chg->usb_icl_votable, PD_VOTER); return 0; } int smblib_get_prop_usb_current_max(struct smb_charger *chg, union power_supply_propval *val) { val->intval = get_client_vote_locked(chg->usb_icl_votable, USB_PSY_VOTER); return 0; } int smblib_get_prop_usb_current_now(struct smb_charger *chg, union power_supply_propval *val) { int rc = 0; rc = smblib_get_prop_usb_present(chg, val); if (rc < 0 || !val->intval) return rc; if (!chg->iio.usbin_i_chan || PTR_ERR(chg->iio.usbin_i_chan) == -EPROBE_DEFER) chg->iio.usbin_i_chan = iio_channel_get(chg->dev, "usbin_i"); if (IS_ERR(chg->iio.usbin_i_chan)) return PTR_ERR(chg->iio.usbin_i_chan); return iio_read_channel_processed(chg->iio.usbin_i_chan, &val->intval); } int smblib_get_prop_charger_temp(struct smb_charger *chg, union power_supply_propval *val) { int rc; if (!chg->iio.temp_chan || PTR_ERR(chg->iio.temp_chan) == -EPROBE_DEFER) chg->iio.temp_chan = iio_channel_get(chg->dev, "charger_temp"); if (IS_ERR(chg->iio.temp_chan)) return PTR_ERR(chg->iio.temp_chan); rc = iio_read_channel_processed(chg->iio.temp_chan, &val->intval); val->intval /= 100; return rc; } int smblib_get_prop_charger_temp_max(struct smb_charger *chg, union power_supply_propval *val) { int rc; if (!chg->iio.temp_max_chan || PTR_ERR(chg->iio.temp_max_chan) == -EPROBE_DEFER) chg->iio.temp_max_chan = iio_channel_get(chg->dev, "charger_temp_max"); if (IS_ERR(chg->iio.temp_max_chan)) return PTR_ERR(chg->iio.temp_max_chan); rc = iio_read_channel_processed(chg->iio.temp_max_chan, &val->intval); val->intval /= 100; return rc; } int smblib_get_prop_typec_cc_orientation(struct smb_charger *chg, union power_supply_propval *val) { int rc = 0; u8 stat; rc = smblib_read(chg, TYPE_C_STATUS_4_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->intval = (bool)(stat & CC_ORIENTATION_BIT) + 1; else val->intval = 0; return rc; } static const char * const smblib_typec_mode_name[] = { [POWER_SUPPLY_TYPEC_NONE] = "NONE", [POWER_SUPPLY_TYPEC_SOURCE_DEFAULT] = "SOURCE_DEFAULT", [POWER_SUPPLY_TYPEC_SOURCE_MEDIUM] = "SOURCE_MEDIUM", [POWER_SUPPLY_TYPEC_SOURCE_HIGH] = "SOURCE_HIGH", [POWER_SUPPLY_TYPEC_NON_COMPLIANT] = "NON_COMPLIANT", [POWER_SUPPLY_TYPEC_SINK] = "SINK", [POWER_SUPPLY_TYPEC_SINK_POWERED_CABLE] = "SINK_POWERED_CABLE", [POWER_SUPPLY_TYPEC_SINK_DEBUG_ACCESSORY] = "SINK_DEBUG_ACCESSORY", [POWER_SUPPLY_TYPEC_SINK_AUDIO_ADAPTER] = "SINK_AUDIO_ADAPTER", [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_STATUS_1_REG, &stat); if (rc < 0) { smblib_err(chg, "Couldn't read TYPE_C_STATUS_1 rc=%d\n", rc); return POWER_SUPPLY_TYPEC_NONE; } smblib_dbg(chg, PR_REGISTER, "TYPE_C_STATUS_1 = 0x%02x\n", stat); switch (stat) { case 0: return POWER_SUPPLY_TYPEC_NONE; case UFP_TYPEC_RDSTD_BIT: return POWER_SUPPLY_TYPEC_SOURCE_DEFAULT; case UFP_TYPEC_RD1P5_BIT: return POWER_SUPPLY_TYPEC_SOURCE_MEDIUM; case UFP_TYPEC_RD3P0_BIT: return POWER_SUPPLY_TYPEC_SOURCE_HIGH; default: break; } return POWER_SUPPLY_TYPEC_NON_COMPLIANT; } static int smblib_get_prop_dfp_mode(struct smb_charger *chg) { int rc; u8 stat; rc = smblib_read(chg, TYPE_C_STATUS_2_REG, &stat); if (rc < 0) { smblib_err(chg, "Couldn't read TYPE_C_STATUS_2 rc=%d\n", rc); return POWER_SUPPLY_TYPEC_NONE; } smblib_dbg(chg, PR_REGISTER, "TYPE_C_STATUS_2 = 0x%02x\n", stat); switch (stat & DFP_TYPEC_MASK) { case DFP_RA_RA_BIT: return POWER_SUPPLY_TYPEC_SINK_AUDIO_ADAPTER; case DFP_RD_RD_BIT: return POWER_SUPPLY_TYPEC_SINK_DEBUG_ACCESSORY; case DFP_RD_RA_VCONN_BIT: return POWER_SUPPLY_TYPEC_SINK_POWERED_CABLE; case DFP_RD_OPEN_BIT: return POWER_SUPPLY_TYPEC_SINK; case DFP_RA_OPEN_BIT: return POWER_SUPPLY_TYPEC_POWERED_CABLE_ONLY; default: break; } return POWER_SUPPLY_TYPEC_NONE; } int smblib_get_prop_typec_mode(struct smb_charger *chg, union power_supply_propval *val) { int rc; u8 stat; rc = smblib_read(chg, TYPE_C_STATUS_4_REG, &stat); if (rc < 0) { smblib_err(chg, "Couldn't read TYPE_C_STATUS_4 rc=%d\n", rc); val->intval = POWER_SUPPLY_TYPEC_NONE; return rc; } smblib_dbg(chg, PR_REGISTER, "TYPE_C_STATUS_4 = 0x%02x\n", stat); if (!(stat & TYPEC_DEBOUNCE_DONE_STATUS_BIT)) { val->intval = POWER_SUPPLY_TYPEC_NONE; return rc; } if (stat & UFP_DFP_MODE_STATUS_BIT) val->intval = smblib_get_prop_dfp_mode(chg); else val->intval = smblib_get_prop_ufp_mode(chg); return rc; } int smblib_get_prop_typec_power_role(struct smb_charger *chg, union power_supply_propval *val) { int rc = 0; u8 ctrl; rc = smblib_read(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG, &ctrl); if (rc < 0) { smblib_err(chg, "Couldn't read TYPE_C_INTRPT_ENB_SOFTWARE_CTRL rc=%d\n", rc); return rc; } smblib_dbg(chg, PR_REGISTER, "TYPE_C_INTRPT_ENB_SOFTWARE_CTRL = 0x%02x\n", ctrl); if (ctrl & TYPEC_DISABLE_CMD_BIT) { val->intval = POWER_SUPPLY_TYPEC_PR_NONE; return rc; } switch (ctrl & (DFP_EN_CMD_BIT | UFP_EN_CMD_BIT)) { case 0: val->intval = POWER_SUPPLY_TYPEC_PR_DUAL; break; case DFP_EN_CMD_BIT: val->intval = POWER_SUPPLY_TYPEC_PR_SOURCE; break; case UFP_EN_CMD_BIT: val->intval = POWER_SUPPLY_TYPEC_PR_SINK; break; default: val->intval = POWER_SUPPLY_TYPEC_PR_NONE; smblib_err(chg, "unsupported power role 0x%02lx\n", ctrl & (DFP_EN_CMD_BIT | UFP_EN_CMD_BIT)); return -EINVAL; } return rc; } int smblib_get_prop_pd_allowed(struct smb_charger *chg, union power_supply_propval *val) { val->intval = get_effective_result(chg->pd_allowed_votable); 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_pd_in_hard_reset(struct smb_charger *chg, union power_supply_propval *val) { int rc; u8 ctrl; rc = smblib_read(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG, &ctrl); if (rc < 0) { smblib_err(chg, "Couldn't read TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG rc=%d\n", rc); return rc; } val->intval = ctrl & EXIT_SNK_BASED_ON_CC_BIT; return 0; } int smblib_get_pe_start(struct smb_charger *chg, union power_supply_propval *val) { /* * hvdcp timeout voter is the last one to allow pd. Use its vote * to indicate start of pe engine */ val->intval = !get_client_vote_locked(chg->pd_disallowed_votable_indirect, HVDCP_TIMEOUT_VOTER); return 0; } /******************* * USB PSY SETTERS * * *****************/ int smblib_set_prop_pd_current_max(struct smb_charger *chg, const union power_supply_propval *val) { int rc; if (chg->pd_active) rc = vote(chg->usb_icl_votable, PD_VOTER, true, val->intval); else rc = -EPERM; return rc; } int smblib_set_prop_usb_current_max(struct smb_charger *chg, const union power_supply_propval *val) { int rc; if (!chg->pd_active) { rc = vote(chg->usb_icl_votable, USB_PSY_VOTER, true, val->intval); } else if (chg->system_suspend_supported) { if (val->intval <= USBIN_25MA) rc = vote(chg->usb_icl_votable, USB_PSY_VOTER, true, val->intval); else rc = vote(chg->usb_icl_votable, USB_PSY_VOTER, false, 0); } return rc; } int smblib_set_prop_typec_power_role(struct smb_charger *chg, const union power_supply_propval *val) { int rc = 0; u8 power_role; switch (val->intval) { case POWER_SUPPLY_TYPEC_PR_NONE: power_role = TYPEC_DISABLE_CMD_BIT; break; case POWER_SUPPLY_TYPEC_PR_DUAL: power_role = 0; break; case POWER_SUPPLY_TYPEC_PR_SINK: power_role = UFP_EN_CMD_BIT; break; case POWER_SUPPLY_TYPEC_PR_SOURCE: power_role = DFP_EN_CMD_BIT; break; default: smblib_err(chg, "power role %d not supported\n", val->intval); return -EINVAL; } rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG, TYPEC_POWER_ROLE_CMD_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); return rc; } return rc; } int smblib_set_prop_usb_voltage_min(struct smb_charger *chg, const union power_supply_propval *val) { int rc, min_uv; min_uv = min(val->intval, chg->voltage_max_uv); rc = smblib_set_usb_pd_allowed_voltage(chg, min_uv, chg->voltage_max_uv); if (rc < 0) { smblib_err(chg, "invalid max voltage %duV rc=%d\n", val->intval, rc); return rc; } if (chg->mode == PARALLEL_MASTER) vote(chg->pl_enable_votable_indirect, USBIN_V_VOTER, min_uv > MICRO_5V, 0); chg->voltage_min_uv = min_uv; return rc; } int smblib_set_prop_usb_voltage_max(struct smb_charger *chg, const union power_supply_propval *val) { int rc, max_uv; max_uv = max(val->intval, chg->voltage_min_uv); rc = smblib_set_usb_pd_allowed_voltage(chg, chg->voltage_min_uv, max_uv); if (rc < 0) { smblib_err(chg, "invalid min voltage %duV rc=%d\n", val->intval, rc); return rc; } chg->voltage_max_uv = max_uv; return rc; } int smblib_set_prop_pd_active(struct smb_charger *chg, const union power_supply_propval *val) { int rc; u8 stat = 0; bool cc_debounced; bool orientation; bool pd_active = val->intval; if (!get_effective_result(chg->pd_allowed_votable)) { smblib_err(chg, "PD is not allowed\n"); return -EINVAL; } vote(chg->apsd_disable_votable, PD_VOTER, pd_active, 0); vote(chg->pd_allowed_votable, PD_VOTER, pd_active, 0); /* * VCONN_EN_ORIENTATION_BIT controls whether to use CC1 or CC2 line * when TYPEC_SPARE_CFG_BIT (CC pin selection s/w override) is set * or when VCONN_EN_VALUE_BIT is set. */ rc = smblib_read(chg, TYPE_C_STATUS_4_REG, &stat); if (rc < 0) { smblib_err(chg, "Couldn't read TYPE_C_STATUS_4 rc=%d\n", rc); return rc; } if (pd_active) { orientation = stat & CC_ORIENTATION_BIT; rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG, VCONN_EN_ORIENTATION_BIT, orientation ? 0 : VCONN_EN_ORIENTATION_BIT); if (rc < 0) { smblib_err(chg, "Couldn't enable vconn on CC line rc=%d\n", rc); return rc; } rc = vote(chg->usb_icl_votable, PD_VOTER, true, USBIN_500MA); if (rc < 0) { smblib_err(chg, "Couldn't vote for USB ICL rc=%d\n", rc); return rc; } rc = smblib_masked_write(chg, USBIN_ICL_OPTIONS_REG, USBIN_MODE_CHG_BIT, USBIN_MODE_CHG_BIT); if (rc < 0) { smblib_err(chg, "Couldn't change USB mode rc=%d\n", rc); return rc; } rc = smblib_masked_write(chg, CMD_APSD_REG, ICL_OVERRIDE_BIT, ICL_OVERRIDE_BIT); if (rc < 0) { smblib_err(chg, "Couldn't override APSD rc=%d\n", rc); return rc; } } else { rc = smblib_masked_write(chg, CMD_APSD_REG, ICL_OVERRIDE_BIT, 0); if (rc < 0) { smblib_err(chg, "Couldn't override APSD rc=%d\n", rc); return rc; } rc = smblib_masked_write(chg, USBIN_ICL_OPTIONS_REG, USBIN_MODE_CHG_BIT, 0); if (rc < 0) { smblib_err(chg, "Couldn't change USB mode rc=%d\n", rc); return rc; } } /* CC pin selection s/w override in PD session; h/w otherwise. */ rc = smblib_masked_write(chg, TAPER_TIMER_SEL_CFG_REG, TYPEC_SPARE_CFG_BIT, pd_active ? TYPEC_SPARE_CFG_BIT : 0); if (rc < 0) { smblib_err(chg, "Couldn't change cc_out ctrl to %s rc=%d\n", pd_active ? "SW" : "HW", rc); return rc; } cc_debounced = (bool)(stat & TYPEC_DEBOUNCE_DONE_STATUS_BIT); if (!pd_active && cc_debounced) try_rerun_apsd_for_hvdcp(chg); chg->pd_active = pd_active; smblib_update_usb_type(chg); power_supply_changed(chg->usb_psy); rc = smblib_masked_write(chg, TYPE_C_CFG_3_REG, EN_TRYSINK_MODE_BIT, chg->pd_active ? 0 : EN_TRYSINK_MODE_BIT); if (rc < 0) { dev_err(chg->dev, "Couldn't set TRYSINK_MODE rc=%d\n", rc); return rc; } return rc; } int smblib_reg_block_update(struct smb_charger *chg, struct reg_info *entry) { int rc = 0; while (entry && entry->reg) { rc = smblib_read(chg, entry->reg, &entry->bak); if (rc < 0) { dev_err(chg->dev, "Error in reading %s rc=%d\n", entry->desc, rc); break; } entry->bak &= entry->mask; rc = smblib_masked_write(chg, entry->reg, entry->mask, entry->val); if (rc < 0) { dev_err(chg->dev, "Error in writing %s rc=%d\n", entry->desc, rc); break; } entry++; } return rc; } int smblib_reg_block_restore(struct smb_charger *chg, struct reg_info *entry) { int rc = 0; while (entry && entry->reg) { rc = smblib_masked_write(chg, entry->reg, entry->mask, entry->bak); if (rc < 0) { dev_err(chg->dev, "Error in writing %s rc=%d\n", entry->desc, rc); break; } entry++; } return rc; } static struct reg_info cc2_detach_settings[] = { { .reg = TYPE_C_CFG_2_REG, .mask = TYPE_C_UFP_MODE_BIT | EN_TRY_SOURCE_MODE_BIT, .val = TYPE_C_UFP_MODE_BIT, .desc = "TYPE_C_CFG_2_REG", }, { .reg = TYPE_C_CFG_3_REG, .mask = EN_TRYSINK_MODE_BIT, .val = 0, .desc = "TYPE_C_CFG_3_REG", }, { .reg = TAPER_TIMER_SEL_CFG_REG, .mask = TYPEC_SPARE_CFG_BIT, .val = TYPEC_SPARE_CFG_BIT, .desc = "TAPER_TIMER_SEL_CFG_REG", }, { .reg = TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG, .mask = VCONN_EN_ORIENTATION_BIT, .val = 0, .desc = "TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG", }, { .reg = MISC_CFG_REG, .mask = TCC_DEBOUNCE_20MS_BIT, .val = TCC_DEBOUNCE_20MS_BIT, .desc = "Tccdebounce time" }, { }, }; static int smblib_cc2_sink_removal_enter(struct smb_charger *chg) { int rc = 0; union power_supply_propval cc2_val = {0, }; if ((chg->wa_flags & TYPEC_CC2_REMOVAL_WA_BIT) == 0) return rc; if (chg->cc2_sink_detach_flag != CC2_SINK_NONE) return rc; rc = smblib_get_prop_typec_cc_orientation(chg, &cc2_val); if (rc < 0) { smblib_err(chg, "Couldn't get cc orientation rc=%d\n", rc); return rc; } if (cc2_val.intval == 1) return rc; rc = smblib_get_prop_typec_mode(chg, &cc2_val); if (rc < 0) { smblib_err(chg, "Couldn't get prop typec mode rc=%d\n", rc); return rc; } switch (cc2_val.intval) { case POWER_SUPPLY_TYPEC_SOURCE_DEFAULT: smblib_reg_block_update(chg, cc2_detach_settings); chg->cc2_sink_detach_flag = CC2_SINK_STD; schedule_work(&chg->rdstd_cc2_detach_work); break; case POWER_SUPPLY_TYPEC_SOURCE_MEDIUM: case POWER_SUPPLY_TYPEC_SOURCE_HIGH: chg->cc2_sink_detach_flag = CC2_SINK_MEDIUM_HIGH; break; default: break; } return rc; } static int smblib_cc2_sink_removal_exit(struct smb_charger *chg) { int rc = 0; if ((chg->wa_flags & TYPEC_CC2_REMOVAL_WA_BIT) == 0) return rc; if (chg->cc2_sink_detach_flag == CC2_SINK_STD) { cancel_work_sync(&chg->rdstd_cc2_detach_work); smblib_reg_block_restore(chg, cc2_detach_settings); } chg->cc2_sink_detach_flag = CC2_SINK_NONE; return rc; } int smblib_set_prop_pd_in_hard_reset(struct smb_charger *chg, const union power_supply_propval *val) { int rc; rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG, EXIT_SNK_BASED_ON_CC_BIT, (val->intval) ? EXIT_SNK_BASED_ON_CC_BIT : 0); if (rc < 0) { smblib_err(chg, "Could not set EXIT_SNK_BASED_ON_CC rc=%d\n", rc); return rc; } vote(chg->apsd_disable_votable, PD_HARD_RESET_VOTER, val->intval, 0); if (val->intval) rc = smblib_cc2_sink_removal_enter(chg); else rc = smblib_cc2_sink_removal_exit(chg); if (rc < 0) { smblib_err(chg, "Could not detect cc2 removal rc=%d\n", rc); return rc; } return rc; } /************************ * PARALLEL PSY GETTERS * ************************/ int smblib_get_prop_slave_current_now(struct smb_charger *chg, union power_supply_propval *pval) { if (IS_ERR_OR_NULL(chg->iio.batt_i_chan)) chg->iio.batt_i_chan = iio_channel_get(chg->dev, "batt_i"); if (IS_ERR(chg->iio.batt_i_chan)) return PTR_ERR(chg->iio.batt_i_chan); return iio_read_channel_processed(chg->iio.batt_i_chan, &pval->intval); } /********************** * INTERRUPT HANDLERS * **********************/ irqreturn_t smblib_handle_debug(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; } static void smblib_pl_handle_chg_state_change(struct smb_charger *chg, u8 stat) { bool pl_enabled; if (chg->mode != PARALLEL_MASTER) return; pl_enabled = !get_effective_result_locked(chg->pl_disable_votable); switch (stat) { case FAST_CHARGE: case FULLON_CHARGE: vote(chg->pl_disable_votable, CHG_STATE_VOTER, false, 0); break; case TAPER_CHARGE: if (pl_enabled) { cancel_delayed_work_sync(&chg->pl_taper_work); schedule_delayed_work(&chg->pl_taper_work, 0); } break; case TERMINATE_CHARGE: case INHIBIT_CHARGE: case DISABLE_CHARGE: vote(chg->pl_disable_votable, TAPER_END_VOTER, false, 0); break; default: break; } } irqreturn_t smblib_handle_chg_state_change(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; smblib_pl_handle_chg_state_change(chg, stat); power_supply_changed(chg->batt_psy); return IRQ_HANDLED; } irqreturn_t smblib_handle_step_chg_state_change(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->step_chg_enabled) rerun_election(chg->fcc_votable); return IRQ_HANDLED; } irqreturn_t smblib_handle_step_chg_soc_update_fail(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->step_chg_enabled) rerun_election(chg->fcc_votable); return IRQ_HANDLED; } #define STEP_SOC_REQ_MS 3000 irqreturn_t smblib_handle_step_chg_soc_update_request(int irq, void *data) { struct smb_irq_data *irq_data = data; struct smb_charger *chg = irq_data->parent_data; int rc; union power_supply_propval pval = {0, }; smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name); if (!chg->bms_psy) { schedule_delayed_work(&chg->step_soc_req_work, msecs_to_jiffies(STEP_SOC_REQ_MS)); return IRQ_HANDLED; } rc = smblib_get_prop_batt_capacity(chg, &pval); if (rc < 0) smblib_err(chg, "Couldn't get batt capacity rc=%d\n", rc); else step_charge_soc_update(chg, pval.intval); return IRQ_HANDLED; } irqreturn_t smblib_handle_batt_temp_changed(int irq, void *data) { struct smb_irq_data *irq_data = data; struct smb_charger *chg = irq_data->parent_data; rerun_election(chg->fcc_votable); power_supply_changed(chg->batt_psy); return IRQ_HANDLED; } irqreturn_t smblib_handle_batt_psy_changed(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; } irqreturn_t smblib_handle_usb_psy_changed(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->usb_psy); return IRQ_HANDLED; } irqreturn_t smblib_handle_usb_plugin(int irq, void *data) { struct smb_irq_data *irq_data = data; struct smb_charger *chg = irq_data->parent_data; int rc; u8 stat; bool vbus_rising; rc = smblib_read(chg, USBIN_BASE + INT_RT_STS_OFFSET, &stat); if (rc < 0) { dev_err(chg->dev, "Couldn't read USB_INT_RT_STS rc=%d\n", rc); return IRQ_HANDLED; } vbus_rising = (bool)(stat & USBIN_PLUGIN_RT_STS_BIT); smblib_set_opt_freq_buck(chg, vbus_rising ? FSW_600HZ_FOR_5V : FSW_1MHZ_FOR_REMOVAL); /* 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)) { smblib_err(chg, "Couldn't get dpdm regulator rc=%ld\n", PTR_ERR(chg->dpdm_reg)); chg->dpdm_reg = NULL; } } if (vbus_rising) { if (chg->dpdm_reg && !regulator_is_enabled(chg->dpdm_reg)) { 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 { if (chg->wa_flags & BOOST_BACK_WA) { vote(chg->usb_suspend_votable, BOOST_BACK_VOTER, false, 0); vote(chg->dc_suspend_votable, BOOST_BACK_VOTER, false, 0); } if (chg->dpdm_reg && regulator_is_enabled(chg->dpdm_reg)) { 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); } } power_supply_changed(chg->usb_psy); smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s %s\n", irq_data->name, vbus_rising ? "attached" : "detached"); return IRQ_HANDLED; } #define USB_WEAK_INPUT_UA 1400000 #define EFFICIENCY_PCT 80 irqreturn_t smblib_handle_icl_change(int irq, void *data) { struct smb_irq_data *irq_data = data; struct smb_charger *chg = irq_data->parent_data; int rc, icl_ua; smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name); rc = smblib_get_charge_param(chg, &chg->param.icl_stat, &icl_ua); if (rc < 0) { smblib_err(chg, "Couldn't get ICL status rc=%d\n", rc); return IRQ_HANDLED; } if (chg->mode != PARALLEL_MASTER) return IRQ_HANDLED; chg->input_limited_fcc_ua = div64_s64( (s64)icl_ua * MICRO_5V * EFFICIENCY_PCT, (s64)get_effective_result(chg->fv_votable) * 100); if (!get_effective_result(chg->pl_disable_votable)) rerun_election(chg->fcc_votable); vote(chg->pl_enable_votable_indirect, USBIN_I_VOTER, icl_ua >= USB_WEAK_INPUT_UA, 0); 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 QC3_PULSES_FOR_6V 5 #define QC3_PULSES_FOR_9V 20 #define QC3_PULSES_FOR_12V 35 static void smblib_hvdcp_adaptive_voltage_change(struct smb_charger *chg) { int rc; u8 stat; int pulses; if (chg->usb_psy_desc.type == 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; } switch (stat & QC_2P0_STATUS_MASK) { case QC_5V_BIT: smblib_set_opt_freq_buck(chg, FSW_600HZ_FOR_5V); break; case QC_9V_BIT: smblib_set_opt_freq_buck(chg, FSW_1MHZ_FOR_9V); break; case QC_12V_BIT: smblib_set_opt_freq_buck(chg, FSW_1P2MHZ_FOR_12V); break; default: smblib_set_opt_freq_buck(chg, FSW_1MHZ_FOR_REMOVAL); break; } } if (chg->usb_psy_desc.type == POWER_SUPPLY_TYPE_USB_HVDCP_3) { rc = smblib_read(chg, QC_PULSE_COUNT_STATUS_REG, &stat); if (rc < 0) { smblib_err(chg, "Couldn't read QC_PULSE_COUNT rc=%d\n", rc); return; } pulses = (stat & QC_PULSE_COUNT_MASK); if (pulses < QC3_PULSES_FOR_6V) smblib_set_opt_freq_buck(chg, FSW_600HZ_FOR_5V); else if (pulses < QC3_PULSES_FOR_9V) smblib_set_opt_freq_buck(chg, FSW_800HZ_FOR_6V_8V); else if (pulses < QC3_PULSES_FOR_12V) smblib_set_opt_freq_buck(chg, FSW_1MHZ_FOR_9V); else smblib_set_opt_freq_buck(chg, FSW_1P2MHZ_FOR_12V); } } static void smblib_handle_adaptive_voltage_done(struct smb_charger *chg, bool rising) { smblib_dbg(chg, PR_INTERRUPT, "IRQ: adaptive-voltage-done %s\n", rising ? "rising" : "falling"); } /* 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; /* * Disable AUTH_IRQ_EN_CFG_BIT to receive adapter voltage * change interrupt. */ rc = smblib_masked_write(chg, USBIN_SOURCE_CHANGE_INTRPT_ENB_REG, AUTH_IRQ_EN_CFG_BIT, 0); if (rc < 0) smblib_err(chg, "Couldn't enable QC auth setting rc=%d\n", rc); 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); 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) { /* Hold off PD only until hvdcp 2.0 detection timeout */ if (rising) { vote(chg->pd_disallowed_votable_indirect, HVDCP_TIMEOUT_VOTER, false, 0); if (get_effective_result(chg->pd_disallowed_votable_indirect)) /* could be a legacy cable, try doing hvdcp */ try_rerun_apsd_for_hvdcp(chg); } smblib_dbg(chg, PR_INTERRUPT, "IRQ: smblib_handle_hvdcp_check_timeout %s\n", rising ? "rising" : "falling"); } /* triggers when HVDCP is detected */ static void smblib_handle_hvdcp_detect_done(struct smb_charger *chg, bool rising) { if (!rising) return; /* the APSD done handler will set the USB supply type */ cancel_delayed_work_sync(&chg->hvdcp_detect_work); smblib_dbg(chg, PR_INTERRUPT, "IRQ: hvdcp-detect-done %s\n", rising ? "rising" : "falling"); } #define HVDCP_DET_MS 2500 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); switch (apsd_result->bit) { case SDP_CHARGER_BIT: case CDP_CHARGER_BIT: case OCP_CHARGER_BIT: case FLOAT_CHARGER_BIT: /* if not DCP then no hvdcp timeout happens. Enable pd here */ vote(chg->pd_disallowed_votable_indirect, HVDCP_TIMEOUT_VOTER, false, 0); break; case DCP_CHARGER_BIT: if (chg->wa_flags & QC_CHARGER_DETECTION_WA_BIT) schedule_delayed_work(&chg->hvdcp_detect_work, msecs_to_jiffies(HVDCP_DET_MS)); break; default: break; } smblib_dbg(chg, PR_INTERRUPT, "IRQ: apsd-done rising; %s detected\n", apsd_result->name); } irqreturn_t smblib_handle_usb_source_change(int irq, void *data) { struct smb_irq_data *irq_data = data; struct smb_charger *chg = irq_data->parent_data; int rc = 0; u8 stat; rc = 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_REGISTER, "APSD_STATUS = 0x%02x\n", stat); 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_adaptive_voltage_done(chg, (bool)(stat & VADP_CHANGE_DONE_AFTER_AUTH_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); return IRQ_HANDLED; } static void typec_source_removal(struct smb_charger *chg) { int rc; vote(chg->pl_disable_votable, TYPEC_SRC_VOTER, true, 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); /* reset taper_end voter here */ vote(chg->pl_disable_votable, TAPER_END_VOTER, false, 0); cancel_delayed_work_sync(&chg->hvdcp_detect_work); /* reset AUTH_IRQ_EN_CFG_BIT */ rc = smblib_masked_write(chg, USBIN_SOURCE_CHANGE_INTRPT_ENB_REG, AUTH_IRQ_EN_CFG_BIT, AUTH_IRQ_EN_CFG_BIT); if (rc < 0) smblib_err(chg, "Couldn't enable QC auth setting rc=%d\n", rc); /* reconfigure allowed voltage for HVDCP */ rc = smblib_write(chg, USBIN_ADAPTER_ALLOW_CFG_REG, USBIN_ADAPTER_ALLOW_5V_OR_9V_TO_12V); if (rc < 0) smblib_err(chg, "Couldn't set USBIN_ADAPTER_ALLOW_5V_OR_9V_TO_12V rc=%d\n", rc); chg->voltage_min_uv = MICRO_5V; chg->voltage_max_uv = MICRO_5V; /* clear USB ICL vote for PD_VOTER */ rc = vote(chg->usb_icl_votable, PD_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 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); } static void typec_source_insertion(struct smb_charger *chg) { vote(chg->pl_disable_votable, TYPEC_SRC_VOTER, false, 0); } static void typec_sink_insertion(struct smb_charger *chg) { /* when a sink is inserted we should not wait on hvdcp timeout to * enable pd */ vote(chg->pd_disallowed_votable_indirect, HVDCP_TIMEOUT_VOTER, false, 0); } static void smblib_handle_typec_removal(struct smb_charger *chg) { vote(chg->pd_disallowed_votable_indirect, CC_DETACHED_VOTER, true, 0); vote(chg->pd_disallowed_votable_indirect, HVDCP_TIMEOUT_VOTER, true, 0); vote(chg->pd_disallowed_votable_indirect, LEGACY_CABLE_VOTER, true, 0); vote(chg->pd_disallowed_votable_indirect, VBUS_CC_SHORT_VOTER, true, 0); /* reset votes from vbus_cc_short */ vote(chg->hvdcp_disable_votable, VBUS_CC_SHORT_VOTER, true, 0); vote(chg->hvdcp_disable_votable, PD_INACTIVE_VOTER, true, 0); /* * cable could be removed during hard reset, remove its vote to * disable apsd */ vote(chg->apsd_disable_votable, PD_HARD_RESET_VOTER, false, 0); typec_source_removal(chg); smblib_update_usb_type(chg); } static void smblib_handle_typec_insertion(struct smb_charger *chg, bool sink_attached, bool legacy_cable) { int rp; bool vbus_cc_short = false; vote(chg->pd_disallowed_votable_indirect, CC_DETACHED_VOTER, false, 0); if (sink_attached) { typec_source_removal(chg); typec_sink_insertion(chg); } else { typec_source_insertion(chg); } vote(chg->pd_disallowed_votable_indirect, LEGACY_CABLE_VOTER, legacy_cable, 0); if (legacy_cable) { rp = smblib_get_prop_ufp_mode(chg); if (rp == POWER_SUPPLY_TYPEC_SOURCE_HIGH || rp == POWER_SUPPLY_TYPEC_NON_COMPLIANT) { vbus_cc_short = true; smblib_err(chg, "Disabling PD and HVDCP, VBUS-CC shorted, rp = %d found\n", rp); } } vote(chg->hvdcp_disable_votable, VBUS_CC_SHORT_VOTER, vbus_cc_short, 0); vote(chg->pd_disallowed_votable_indirect, VBUS_CC_SHORT_VOTER, vbus_cc_short, 0); } static void smblib_handle_typec_debounce_done(struct smb_charger *chg, bool rising, bool sink_attached, bool legacy_cable) { int rc; union power_supply_propval pval = {0, }; if (rising) smblib_handle_typec_insertion(chg, sink_attached, legacy_cable); else smblib_handle_typec_removal(chg); rc = smblib_get_prop_typec_mode(chg, &pval); if (rc < 0) smblib_err(chg, "Couldn't get prop typec mode rc=%d\n", rc); /* * HW BUG - after cable is removed, medium or high rd reading * falls to std. Use it for signal of typec cc detachment in * software WA. */ if (chg->cc2_sink_detach_flag == CC2_SINK_MEDIUM_HIGH && pval.intval == POWER_SUPPLY_TYPEC_SOURCE_DEFAULT) { chg->cc2_sink_detach_flag = CC2_SINK_WA_DONE; rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG, EXIT_SNK_BASED_ON_CC_BIT, 0); if (rc < 0) smblib_err(chg, "Couldn't get prop typec mode rc=%d\n", rc); } smblib_dbg(chg, PR_INTERRUPT, "IRQ: debounce-done %s; Type-C %s detected\n", rising ? "rising" : "falling", smblib_typec_mode_name[pval.intval]); } irqreturn_t smblib_handle_usb_typec_change(int irq, void *data) { struct smb_irq_data *irq_data = data; struct smb_charger *chg = irq_data->parent_data; int rc; u8 stat; bool debounce_done, sink_attached, legacy_cable; /* WA - not when PD hard_reset WIP on cc2 in sink mode */ if (chg->cc2_sink_detach_flag == CC2_SINK_STD) return IRQ_HANDLED; rc = smblib_read(chg, TYPE_C_STATUS_4_REG, &stat); if (rc < 0) { smblib_err(chg, "Couldn't read TYPE_C_STATUS_4 rc=%d\n", rc); return IRQ_HANDLED; } smblib_dbg(chg, PR_REGISTER, "TYPE_C_STATUS_4 = 0x%02x\n", stat); debounce_done = (bool)(stat & TYPEC_DEBOUNCE_DONE_STATUS_BIT); sink_attached = (bool)(stat & UFP_DFP_MODE_STATUS_BIT); rc = smblib_read(chg, TYPE_C_STATUS_5_REG, &stat); if (rc < 0) { smblib_err(chg, "Couldn't read TYPE_C_STATUS_5 rc=%d\n", rc); return IRQ_HANDLED; } smblib_dbg(chg, PR_REGISTER, "TYPE_C_STATUS_5 = 0x%02x\n", stat); legacy_cable = (bool)(stat & TYPEC_LEGACY_CABLE_STATUS_BIT); smblib_handle_typec_debounce_done(chg, debounce_done, sink_attached, legacy_cable); power_supply_changed(chg->usb_psy); if (stat & TYPEC_VBUS_ERROR_STATUS_BIT) smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s vbus-error\n", irq_data->name); return IRQ_HANDLED; } irqreturn_t smblib_handle_dc_plugin(int irq, void *data) { struct smb_irq_data *irq_data = data; struct smb_charger *chg = irq_data->parent_data; power_supply_changed(chg->dc_psy); return IRQ_HANDLED; } irqreturn_t smblib_handle_high_duty_cycle(int irq, void *data) { struct smb_irq_data *irq_data = data; struct smb_charger *chg = irq_data->parent_data; chg->is_hdc = true; schedule_delayed_work(&chg->clear_hdc_work, msecs_to_jiffies(60)); return IRQ_HANDLED; } irqreturn_t smblib_handle_switcher_power_ok(int irq, void *data) { struct smb_irq_data *irq_data = data; struct smb_charger *chg = irq_data->parent_data; int rc; 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; } if ((stat & USE_USBIN_BIT) && get_effective_result(chg->usb_suspend_votable)) return IRQ_HANDLED; if ((stat & USE_DCIN_BIT) && get_effective_result(chg->dc_suspend_votable)) return IRQ_HANDLED; if (is_storming(&irq_data->storm_data)) { smblib_dbg(chg, PR_MISC, "reverse boost detected; suspending input\n"); vote(chg->usb_suspend_votable, BOOST_BACK_VOTER, true, 0); vote(chg->dc_suspend_votable, BOOST_BACK_VOTER, true, 0); } return IRQ_HANDLED; } /*************** * Work Queues * ***************/ static void smblib_hvdcp_detect_work(struct work_struct *work) { struct smb_charger *chg = container_of(work, struct smb_charger, hvdcp_detect_work.work); vote(chg->pd_disallowed_votable_indirect, HVDCP_TIMEOUT_VOTER, false, 0); if (get_effective_result(chg->pd_disallowed_votable_indirect)) /* pd is still disabled, try hvdcp */ try_rerun_apsd_for_hvdcp(chg); else /* notify pd now that pd is allowed */ power_supply_changed(chg->usb_psy); } static void bms_update_work(struct work_struct *work) { struct smb_charger *chg = container_of(work, struct smb_charger, bms_update_work); power_supply_changed(chg->batt_psy); } static void step_soc_req_work(struct work_struct *work) { struct smb_charger *chg = container_of(work, struct smb_charger, step_soc_req_work.work); union power_supply_propval pval = {0, }; int rc; rc = smblib_get_prop_batt_capacity(chg, &pval); if (rc < 0) { smblib_err(chg, "Couldn't get batt capacity rc=%d\n", rc); return; } step_charge_soc_update(chg, pval.intval); } static void smblib_pl_detect_work(struct work_struct *work) { struct smb_charger *chg = container_of(work, struct smb_charger, pl_detect_work); vote(chg->pl_disable_votable, PARALLEL_PSY_VOTER, false, 0); } #define MINIMUM_PARALLEL_FCC_UA 500000 #define PL_TAPER_WORK_DELAY_MS 100 #define TAPER_RESIDUAL_PCT 75 static void smblib_pl_taper_work(struct work_struct *work) { struct smb_charger *chg = container_of(work, struct smb_charger, pl_taper_work.work); union power_supply_propval pval = {0, }; int rc; smblib_dbg(chg, PR_PARALLEL, "starting parallel taper work\n"); if (chg->pl.slave_fcc_ua < MINIMUM_PARALLEL_FCC_UA) { smblib_dbg(chg, PR_PARALLEL, "parallel taper is done\n"); vote(chg->pl_disable_votable, TAPER_END_VOTER, true, 0); goto done; } rc = smblib_get_prop_batt_charge_type(chg, &pval); if (rc < 0) { smblib_err(chg, "Couldn't get batt charge type rc=%d\n", rc); goto done; } if (pval.intval == POWER_SUPPLY_CHARGE_TYPE_TAPER) { smblib_dbg(chg, PR_PARALLEL, "master is taper charging; reducing slave FCC\n"); vote(chg->awake_votable, PL_TAPER_WORK_RUNNING_VOTER, true, 0); /* Reduce the taper percent by 25 percent */ chg->pl.taper_pct = chg->pl.taper_pct * TAPER_RESIDUAL_PCT / 100; rerun_election(chg->fcc_votable); schedule_delayed_work(&chg->pl_taper_work, msecs_to_jiffies(PL_TAPER_WORK_DELAY_MS)); return; } /* * Master back to Fast Charge, get out of this round of taper reduction */ smblib_dbg(chg, PR_PARALLEL, "master is fast charging; waiting for next taper\n"); done: vote(chg->awake_votable, PL_TAPER_WORK_RUNNING_VOTER, false, 0); } 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 = 0; } static void rdstd_cc2_detach_work(struct work_struct *work) { int rc; u8 stat; struct smb_irq_data irq_data = {NULL, "cc2-removal-workaround"}; struct smb_charger *chg = container_of(work, struct smb_charger, rdstd_cc2_detach_work); /* * WA steps - * 1. Enable both UFP and DFP, wait for 10ms. * 2. Disable DFP, wait for 30ms. * 3. Removal detected if both TYPEC_DEBOUNCE_DONE_STATUS * and TIMER_STAGE bits are gone, otherwise repeat all by * work rescheduling. * Note, work will be cancelled when pd_hard_reset is 0. */ rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG, UFP_EN_CMD_BIT | DFP_EN_CMD_BIT, UFP_EN_CMD_BIT | DFP_EN_CMD_BIT); if (rc < 0) { smblib_err(chg, "Couldn't write TYPE_C_CTRL_REG rc=%d\n", rc); return; } usleep_range(10000, 11000); rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG, UFP_EN_CMD_BIT | DFP_EN_CMD_BIT, UFP_EN_CMD_BIT); if (rc < 0) { smblib_err(chg, "Couldn't write TYPE_C_CTRL_REG rc=%d\n", rc); return; } usleep_range(30000, 31000); rc = smblib_read(chg, TYPE_C_STATUS_4_REG, &stat); if (rc < 0) { smblib_err(chg, "Couldn't read TYPE_C_STATUS_4 rc=%d\n", rc); return; } if (stat & TYPEC_DEBOUNCE_DONE_STATUS_BIT) goto rerun; rc = smblib_read(chg, TYPE_C_STATUS_5_REG, &stat); if (rc < 0) { smblib_err(chg, "Couldn't read TYPE_C_STATUS_5_REG rc=%d\n", rc); return; } if (stat & TIMER_STAGE_2_BIT) goto rerun; /* Bingo, cc2 removal detected */ smblib_reg_block_restore(chg, cc2_detach_settings); chg->cc2_sink_detach_flag = CC2_SINK_WA_DONE; irq_data.parent_data = chg; smblib_handle_usb_typec_change(0, &irq_data); return; rerun: schedule_work(&chg->rdstd_cc2_detach_work); } static int smblib_create_votables(struct smb_charger *chg) { int rc = 0; chg->usb_suspend_votable = create_votable("USB_SUSPEND", VOTE_SET_ANY, smblib_usb_suspend_vote_callback, chg); if (IS_ERR(chg->usb_suspend_votable)) { rc = PTR_ERR(chg->usb_suspend_votable); 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); return rc; } chg->fcc_max_votable = create_votable("FCC_MAX", VOTE_MAX, smblib_fcc_max_vote_callback, chg); if (IS_ERR(chg->fcc_max_votable)) { rc = PTR_ERR(chg->fcc_max_votable); return rc; } chg->fcc_votable = create_votable("FCC", VOTE_MIN, smblib_fcc_vote_callback, chg); if (IS_ERR(chg->fcc_votable)) { rc = PTR_ERR(chg->fcc_votable); return rc; } chg->fv_votable = create_votable("FV", VOTE_MAX, smblib_fv_vote_callback, chg); if (IS_ERR(chg->fv_votable)) { rc = PTR_ERR(chg->fv_votable); return rc; } chg->usb_icl_votable = create_votable("USB_ICL", VOTE_MIN, smblib_usb_icl_vote_callback, chg); if (IS_ERR(chg->usb_icl_votable)) { rc = PTR_ERR(chg->usb_icl_votable); return rc; } chg->dc_icl_votable = create_votable("DC_ICL", VOTE_MIN, smblib_dc_icl_vote_callback, chg); if (IS_ERR(chg->dc_icl_votable)) { rc = PTR_ERR(chg->dc_icl_votable); return rc; } chg->pd_disallowed_votable_indirect = create_votable("PD_DISALLOWED_INDIRECT", VOTE_SET_ANY, smblib_pd_disallowed_votable_indirect_callback, chg); if (IS_ERR(chg->pd_disallowed_votable_indirect)) { rc = PTR_ERR(chg->pd_disallowed_votable_indirect); return rc; } chg->pd_allowed_votable = create_votable("PD_ALLOWED", VOTE_SET_ANY, NULL, NULL); if (IS_ERR(chg->pd_allowed_votable)) { rc = PTR_ERR(chg->pd_allowed_votable); 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); return rc; } chg->pl_disable_votable = create_votable("PL_DISABLE", VOTE_SET_ANY, smblib_pl_disable_vote_callback, chg); if (IS_ERR(chg->pl_disable_votable)) { rc = PTR_ERR(chg->pl_disable_votable); 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); return rc; } chg->pl_enable_votable_indirect = create_votable("PL_ENABLE_INDIRECT", VOTE_SET_ANY, smblib_pl_enable_indirect_vote_callback, chg); if (IS_ERR(chg->pl_enable_votable_indirect)) { rc = PTR_ERR(chg->pl_enable_votable_indirect); return rc; } chg->hvdcp_disable_votable = create_votable("HVDCP_DISABLE", VOTE_SET_ANY, smblib_hvdcp_disable_vote_callback, chg); if (IS_ERR(chg->hvdcp_disable_votable)) { rc = PTR_ERR(chg->hvdcp_disable_votable); return rc; } chg->apsd_disable_votable = create_votable("APSD_DISABLE", VOTE_SET_ANY, smblib_apsd_disable_vote_callback, chg); if (IS_ERR(chg->apsd_disable_votable)) { rc = PTR_ERR(chg->apsd_disable_votable); return rc; } return rc; } static void smblib_destroy_votables(struct smb_charger *chg) { if (chg->usb_suspend_votable) destroy_votable(chg->usb_suspend_votable); if (chg->dc_suspend_votable) destroy_votable(chg->dc_suspend_votable); if (chg->fcc_max_votable) destroy_votable(chg->fcc_max_votable); if (chg->fcc_votable) destroy_votable(chg->fcc_votable); if (chg->fv_votable) destroy_votable(chg->fv_votable); if (chg->usb_icl_votable) destroy_votable(chg->usb_icl_votable); if (chg->dc_icl_votable) destroy_votable(chg->dc_icl_votable); if (chg->pd_disallowed_votable_indirect) destroy_votable(chg->pd_disallowed_votable_indirect); if (chg->pd_allowed_votable) destroy_votable(chg->pd_allowed_votable); if (chg->awake_votable) destroy_votable(chg->awake_votable); if (chg->pl_disable_votable) destroy_votable(chg->pl_disable_votable); if (chg->chg_disable_votable) destroy_votable(chg->chg_disable_votable); if (chg->pl_enable_votable_indirect) destroy_votable(chg->pl_enable_votable_indirect); if (chg->apsd_disable_votable) destroy_votable(chg->apsd_disable_votable); } static void smblib_iio_deinit(struct smb_charger *chg) { if (!IS_ERR_OR_NULL(chg->iio.temp_chan)) iio_channel_release(chg->iio.temp_chan); if (!IS_ERR_OR_NULL(chg->iio.temp_max_chan)) iio_channel_release(chg->iio.temp_max_chan); if (!IS_ERR_OR_NULL(chg->iio.usbin_i_chan)) iio_channel_release(chg->iio.usbin_i_chan); if (!IS_ERR_OR_NULL(chg->iio.usbin_v_chan)) iio_channel_release(chg->iio.usbin_v_chan); if (!IS_ERR_OR_NULL(chg->iio.batt_i_chan)) iio_channel_release(chg->iio.batt_i_chan); } int smblib_init(struct smb_charger *chg) { int rc = 0; mutex_init(&chg->write_lock); INIT_WORK(&chg->bms_update_work, bms_update_work); INIT_WORK(&chg->pl_detect_work, smblib_pl_detect_work); INIT_WORK(&chg->rdstd_cc2_detach_work, rdstd_cc2_detach_work); INIT_DELAYED_WORK(&chg->hvdcp_detect_work, smblib_hvdcp_detect_work); INIT_DELAYED_WORK(&chg->pl_taper_work, smblib_pl_taper_work); INIT_DELAYED_WORK(&chg->step_soc_req_work, step_soc_req_work); INIT_DELAYED_WORK(&chg->clear_hdc_work, clear_hdc_work); chg->fake_capacity = -EINVAL; switch (chg->mode) { case PARALLEL_MASTER: rc = smblib_create_votables(chg); if (rc < 0) { smblib_err(chg, "Couldn't create votables 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; } chg->bms_psy = power_supply_get_by_name("bms"); chg->pl.psy = power_supply_get_by_name("parallel"); if (chg->pl.psy) vote(chg->pl_disable_votable, PARALLEL_PSY_VOTER, false, 0); 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: power_supply_unreg_notifier(&chg->nb); smblib_destroy_votables(chg); break; case PARALLEL_SLAVE: break; default: smblib_err(chg, "Unsupported mode %d\n", chg->mode); return -EINVAL; } smblib_iio_deinit(chg); return 0; } int smblib_validate_initial_typec_legacy_status(struct smb_charger *chg) { int rc; u8 stat; if (qpnp_pon_is_warm_reset()) return 0; rc = smblib_read(chg, TYPE_C_STATUS_5_REG, &stat); if (rc < 0) { smblib_err(chg, "Couldn't read TYPE_C_STATUS_5 rc=%d\n", rc); return rc; } if ((stat & TYPEC_LEGACY_CABLE_STATUS_BIT) == 0) return 0; rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG, TYPEC_DISABLE_CMD_BIT, TYPEC_DISABLE_CMD_BIT); if (rc < 0) { smblib_err(chg, "Couldn't disable typec rc=%d\n", rc); return rc; } usleep_range(150000, 151000); rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG, TYPEC_DISABLE_CMD_BIT, 0); if (rc < 0) { smblib_err(chg, "Couldn't enable typec rc=%d\n", rc); return rc; } return 0; }