diff options
Diffstat (limited to 'drivers/power/qcom-charger/smb-lib.c')
-rw-r--r-- | drivers/power/qcom-charger/smb-lib.c | 3403 |
1 files changed, 3403 insertions, 0 deletions
diff --git a/drivers/power/qcom-charger/smb-lib.c b/drivers/power/qcom-charger/smb-lib.c new file mode 100644 index 000000000000..0faf8aee8aa0 --- /dev/null +++ b/drivers/power/qcom-charger/smb-lib.c @@ -0,0 +1,3403 @@ +/* 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 <linux/device.h> +#include <linux/regmap.h> +#include <linux/delay.h> +#include <linux/iio/consumer.h> +#include <linux/power_supply.h> +#include <linux/regulator/driver.h> +#include <linux/qpnp/power-on.h> +#include <linux/irq.h> +#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 = smblib_get_apsd_result(chg); + + /* 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 apsd_result; + } + + 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 = vote(chg->usb_icl_votable, DCP_VOTER, false, 0); + 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 = vote(chg->usb_icl_votable, DCP_VOTER, true, + chg->dcp_icl_ua); + if (rc < 0) { + smblib_err(chg, "Couldn't vote for USB ICL rc=%d\n", + rc); + return rc; + } + + 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); + + 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 stat4, stat5; + 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, &stat4); + if (rc < 0) { + smblib_err(chg, "Couldn't read TYPE_C_STATUS_4 rc=%d\n", rc); + return IRQ_HANDLED; + } + + rc = smblib_read(chg, TYPE_C_STATUS_5_REG, &stat5); + if (rc < 0) { + smblib_err(chg, "Couldn't read TYPE_C_STATUS_5 rc=%d\n", rc); + return IRQ_HANDLED; + } + + debounce_done = (bool)(stat4 & TYPEC_DEBOUNCE_DONE_STATUS_BIT); + sink_attached = (bool)(stat4 & UFP_DFP_MODE_STATUS_BIT); + legacy_cable = (bool)(stat5 & TYPEC_LEGACY_CABLE_STATUS_BIT); + + smblib_handle_typec_debounce_done(chg, + debounce_done, sink_attached, legacy_cable); + + if (stat4 & TYPEC_VBUS_ERROR_STATUS_BIT) + smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s vbus-error\n", + irq_data->name); + + power_supply_changed(chg->usb_psy); + smblib_dbg(chg, PR_REGISTER, "TYPE_C_STATUS_4 = 0x%02x\n", stat4); + smblib_dbg(chg, PR_REGISTER, "TYPE_C_STATUS_5 = 0x%02x\n", stat5); + 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; +} |