summaryrefslogtreecommitdiff
path: root/drivers/power/qcom-charger/smb-lib.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/power/qcom-charger/smb-lib.c')
-rw-r--r--drivers/power/qcom-charger/smb-lib.c3403
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;
+}