From 58bfbabe9ecd6f4ac0ed8c08879240932b3dc457 Mon Sep 17 00:00:00 2001 From: Jishnu Prakash Date: Thu, 30 Apr 2020 18:00:24 +0530 Subject: [PATCH] power: supply: qcom: Add snapshot of SMB5 charger driver Add snapshot of qpnp-smb5 charger driver from msm-4.19 as of commit 1bb7bbeb1d09("sched: Fix compilation errors with !WALT"). Change-Id: Ie7c99bea8887dcb97244e43e7984e2a82d1e5526 Signed-off-by: Jishnu Prakash --- drivers/power/supply/qcom/Kconfig | 13 + drivers/power/supply/qcom/Makefile | 1 + drivers/power/supply/qcom/battery.c | 2118 +++++ drivers/power/supply/qcom/battery.h | 19 + drivers/power/supply/qcom/qpnp-smb5.c | 3780 +++++++++ drivers/power/supply/qcom/schgm-flash.c | 239 + drivers/power/supply/qcom/schgm-flash.h | 54 + drivers/power/supply/qcom/smb5-lib.c | 8220 ++++++++++++++++++++ drivers/power/supply/qcom/smb5-lib.h | 841 ++ drivers/power/supply/qcom/smb5-reg.h | 557 ++ drivers/power/supply/qcom/step-chg-jeita.c | 914 +++ drivers/power/supply/qcom/step-chg-jeita.h | 30 + drivers/power/supply/qcom/storm-watch.c | 69 + drivers/power/supply/qcom/storm-watch.h | 34 + 14 files changed, 16889 insertions(+) create mode 100644 drivers/power/supply/qcom/battery.c create mode 100644 drivers/power/supply/qcom/battery.h create mode 100644 drivers/power/supply/qcom/qpnp-smb5.c create mode 100644 drivers/power/supply/qcom/schgm-flash.c create mode 100644 drivers/power/supply/qcom/schgm-flash.h create mode 100644 drivers/power/supply/qcom/smb5-lib.c create mode 100644 drivers/power/supply/qcom/smb5-lib.h create mode 100644 drivers/power/supply/qcom/smb5-reg.h create mode 100644 drivers/power/supply/qcom/step-chg-jeita.c create mode 100644 drivers/power/supply/qcom/step-chg-jeita.h create mode 100644 drivers/power/supply/qcom/storm-watch.c create mode 100644 drivers/power/supply/qcom/storm-watch.h diff --git a/drivers/power/supply/qcom/Kconfig b/drivers/power/supply/qcom/Kconfig index b7c457fcd750..3d55f1ec7374 100644 --- a/drivers/power/supply/qcom/Kconfig +++ b/drivers/power/supply/qcom/Kconfig @@ -15,4 +15,17 @@ config QPNP_QG to determine the battery state-of-charge (SOC) and supports other battery management features. +config QPNP_SMB5 + tristate "SMB5 Battery Charger" + depends on MFD_SPMI_PMIC + help + Say Y to enables support for the SMB5 charging peripheral. + The QPNP SMB5 charger driver supports the charger peripheral + present in the chip. + The power supply framework is used to communicate battery and + usb properties to userspace and other driver consumers such + as fuel gauge, USB, and USB-PD. + VBUS and VCONN regulators are registered for supporting OTG, + and powered Type-C cables respectively. + endif diff --git a/drivers/power/supply/qcom/Makefile b/drivers/power/supply/qcom/Makefile index 0e23d0efed03..ece54c002ae1 100644 --- a/drivers/power/supply/qcom/Makefile +++ b/drivers/power/supply/qcom/Makefile @@ -1,3 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only obj-$(CONFIG_QPNP_QG) += qpnp-qg.o battery-profile-loader.o pmic-voter.o qg-util.o qg-soc.o qg-sdam.o qg-battery-profile.o qg-profile-lib.o fg-alg.o +obj-$(CONFIG_QPNP_SMB5) += step-chg-jeita.o battery.o qpnp-smb5.o smb5-lib.o pmic-voter.o storm-watch.o schgm-flash.o diff --git a/drivers/power/supply/qcom/battery.c b/drivers/power/supply/qcom/battery.c new file mode 100644 index 000000000000..4bfa6a3c01ee --- /dev/null +++ b/drivers/power/supply/qcom/battery.c @@ -0,0 +1,2118 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2017-2020 The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "QCOM-BATT: %s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "battery.h" + +#define DRV_MAJOR_VERSION 1 +#define DRV_MINOR_VERSION 0 + +#define BATT_PROFILE_VOTER "BATT_PROFILE_VOTER" +#define CHG_STATE_VOTER "CHG_STATE_VOTER" +#define TAPER_STEPPER_VOTER "TAPER_STEPPER_VOTER" +#define TAPER_END_VOTER "TAPER_END_VOTER" +#define PL_TAPER_EARLY_BAD_VOTER "PL_TAPER_EARLY_BAD_VOTER" +#define PARALLEL_PSY_VOTER "PARALLEL_PSY_VOTER" +#define PL_HW_ABSENT_VOTER "PL_HW_ABSENT_VOTER" +#define PL_VOTER "PL_VOTER" +#define RESTRICT_CHG_VOTER "RESTRICT_CHG_VOTER" +#define ICL_CHANGE_VOTER "ICL_CHANGE_VOTER" +#define PL_INDIRECT_VOTER "PL_INDIRECT_VOTER" +#define USBIN_I_VOTER "USBIN_I_VOTER" +#define PL_FCC_LOW_VOTER "PL_FCC_LOW_VOTER" +#define ICL_LIMIT_VOTER "ICL_LIMIT_VOTER" +#define FCC_STEPPER_VOTER "FCC_STEPPER_VOTER" +#define FCC_VOTER "FCC_VOTER" +#define MAIN_FCC_VOTER "MAIN_FCC_VOTER" + +struct pl_data { + int pl_mode; + int pl_batfet_mode; + int pl_min_icl_ua; + int slave_pct; + int slave_fcc_ua; + int main_fcc_ua; + int restricted_current; + bool restricted_charging_enabled; + struct votable *fcc_votable; + struct votable *fv_votable; + struct votable *pl_disable_votable; + struct votable *pl_awake_votable; + struct votable *hvdcp_hw_inov_dis_votable; + struct votable *usb_icl_votable; + struct votable *pl_enable_votable_indirect; + struct votable *cp_ilim_votable; + struct votable *cp_disable_votable; + struct votable *fcc_main_votable; + struct votable *cp_slave_disable_votable; + struct delayed_work status_change_work; + struct work_struct pl_disable_forever_work; + struct work_struct pl_taper_work; + struct delayed_work fcc_stepper_work; + bool taper_work_running; + struct power_supply *main_psy; + struct power_supply *pl_psy; + struct power_supply *batt_psy; + struct power_supply *usb_psy; + struct power_supply *dc_psy; + struct power_supply *cp_master_psy; + struct power_supply *cp_slave_psy; + int charge_type; + int total_settled_ua; + int pl_settled_ua; + int pl_fcc_max; + int fcc_stepper_enable; + int main_step_fcc_dir; + int main_step_fcc_count; + int main_step_fcc_residual; + int parallel_step_fcc_dir; + int parallel_step_fcc_count; + int parallel_step_fcc_residual; + int step_fcc; + int override_main_fcc_ua; + int total_fcc_ua; + u32 wa_flags; + struct class qcom_batt_class; + struct wakeup_source *pl_ws; + struct notifier_block nb; + struct charger_param *chg_param; + bool pl_disable; + bool cp_disabled; + int taper_entry_fv; + int main_fcc_max; + enum power_supply_type charger_type; + /* debugfs directory */ + struct dentry *dfs_root; + u32 float_voltage_uv; +}; + +struct pl_data *the_chip; + +enum print_reason { + PR_PARALLEL = BIT(0), +}; + +enum { + AICL_RERUN_WA_BIT = BIT(0), + FORCE_INOV_DISABLE_BIT = BIT(1), +}; + +static int debug_mask; + +#define pl_dbg(chip, reason, fmt, ...) \ + do { \ + if (debug_mask & (reason)) \ + pr_info(fmt, ##__VA_ARGS__); \ + else \ + pr_debug(fmt, ##__VA_ARGS__); \ + } while (0) + +#define IS_USBIN(mode) ((mode == POWER_SUPPLY_PL_USBIN_USBIN) \ + || (mode == POWER_SUPPLY_PL_USBIN_USBIN_EXT)) +enum { + VER = 0, + SLAVE_PCT, + RESTRICT_CHG_ENABLE, + RESTRICT_CHG_CURRENT, + FCC_STEPPING_IN_PROGRESS, +}; + +enum { + PARALLEL_INPUT_MODE, + PARALLEL_OUTPUT_MODE, +}; +/********* + * HELPER* + *********/ +static bool is_usb_available(struct pl_data *chip) +{ + if (!chip->usb_psy) + chip->usb_psy = + power_supply_get_by_name("usb"); + + return !!chip->usb_psy; +} + +static bool is_cp_available(struct pl_data *chip) +{ + if (!chip->cp_master_psy) + chip->cp_master_psy = + power_supply_get_by_name("charge_pump_master"); + + return !!chip->cp_master_psy; +} + +static int cp_get_parallel_mode(struct pl_data *chip, int mode) +{ + union power_supply_propval pval = {-EINVAL, }; + int rc = -EINVAL; + + if (!is_cp_available(chip)) + return -EINVAL; + + switch (mode) { + case PARALLEL_INPUT_MODE: + rc = power_supply_get_property(chip->cp_master_psy, + POWER_SUPPLY_PROP_PARALLEL_MODE, &pval); + break; + case PARALLEL_OUTPUT_MODE: + rc = power_supply_get_property(chip->cp_master_psy, + POWER_SUPPLY_PROP_PARALLEL_OUTPUT_MODE, &pval); + break; + default: + pr_err("Invalid mode request %d\n", mode); + break; + } + + if (rc < 0) + pr_err("Failed to read CP topology for mode=%d rc=%d\n", + mode, rc); + + return pval.intval; +} + +static int get_hvdcp3_icl_limit(struct pl_data *chip) +{ + int main_icl, target_icl = -EINVAL; + + if (chip->charger_type != POWER_SUPPLY_TYPE_USB_HVDCP_3 && + chip->charger_type != POWER_SUPPLY_TYPE_USB_HVDCP_3P5) + return target_icl; + + /* + * For HVDCP3 adapters, limit max. ILIM as follows: + * HVDCP3_ICL: Maximum ICL of HVDCP3 adapter(from DT configuration) + * For Parallel input configurations: + * VBUS: target_icl = HVDCP3_ICL - main_ICL + * VMID: target_icl = HVDCP3_ICL + */ + target_icl = chip->chg_param->hvdcp3_max_icl_ua; + if (cp_get_parallel_mode(chip, PARALLEL_INPUT_MODE) + == POWER_SUPPLY_PL_USBIN_USBIN) { + main_icl = get_effective_result_locked(chip->usb_icl_votable); + if ((main_icl >= 0) && (main_icl < target_icl)) + target_icl -= main_icl; + } + + return target_icl; +} + +/* + * Adapter CC Mode: ILIM over-ridden explicitly, below takes no effect. + * + * Adapter CV mode: Configuration of ILIM for different topology is as below: + * MID-VPH: + * SMB1390 ILIM: independent of FCC and based on the AICL result or + * PD advertised current, handled directly in SMB1390 + * driver. + * MID-VBAT: + * SMB1390 ILIM: based on minimum of FCC portion of SMB1390 or ICL. + * USBIN-VBAT: + * SMB1390 ILIM: based on FCC portion of SMB1390 and independent of ICL. + */ +static void cp_configure_ilim(struct pl_data *chip, const char *voter, int ilim) +{ + int rc, fcc, target_icl; + union power_supply_propval pval = {0, }; + + if (!is_usb_available(chip)) + return; + + if (!is_cp_available(chip)) + return; + + if (cp_get_parallel_mode(chip, PARALLEL_OUTPUT_MODE) + == POWER_SUPPLY_PL_OUTPUT_VPH) + return; + + target_icl = get_hvdcp3_icl_limit(chip); + ilim = (target_icl > 0) ? min(ilim, target_icl) : ilim; + + rc = power_supply_get_property(chip->cp_master_psy, + POWER_SUPPLY_PROP_MIN_ICL, &pval); + if (rc < 0) + return; + + if (!chip->cp_ilim_votable) + chip->cp_ilim_votable = find_votable("CP_ILIM"); + + if (chip->cp_ilim_votable) { + fcc = get_effective_result_locked(chip->fcc_votable); + /* + * If FCC >= (2 * MIN_ICL) then it is safe to enable CP + * with MIN_ICL. + * Configure ILIM as follows: + * if request_ilim < MIN_ICL cofigure ILIM to MIN_ICL. + * otherwise configure ILIM to requested_ilim. + */ + if ((fcc >= (pval.intval * 2)) && (ilim < pval.intval)) + vote(chip->cp_ilim_votable, voter, true, pval.intval); + else + vote(chip->cp_ilim_votable, voter, true, ilim); + + /* + * Rerun FCC votable to ensure offset for ILIM compensation is + * recalculated based on new ILIM. + */ + if (!chip->fcc_main_votable) + chip->fcc_main_votable = find_votable("FCC_MAIN"); + if ((chip->charger_type == POWER_SUPPLY_TYPE_USB_HVDCP_3) + && chip->fcc_main_votable) + rerun_election(chip->fcc_main_votable); + + pl_dbg(chip, PR_PARALLEL, + "ILIM: vote: %d voter:%s min_ilim=%d fcc = %d\n", + ilim, voter, pval.intval, fcc); + } +} + +/******* + * ICL * + ********/ +static int get_settled_split(struct pl_data *chip, int *main_icl_ua, + int *slave_icl_ua, int *total_settled_icl_ua) +{ + int slave_icl_pct, total_current_ua; + int slave_ua = 0, main_settled_ua = 0; + union power_supply_propval pval = {0, }; + int rc, total_settled_ua = 0; + + if (!IS_USBIN(chip->pl_mode)) + return -EINVAL; + + if (!chip->main_psy) + return -EINVAL; + + if (!get_effective_result_locked(chip->pl_disable_votable)) { + /* read the aicl settled value */ + rc = power_supply_get_property(chip->main_psy, + POWER_SUPPLY_PROP_INPUT_CURRENT_SETTLED, &pval); + if (rc < 0) { + pr_err("Couldn't get aicl settled value rc=%d\n", rc); + return rc; + } + main_settled_ua = pval.intval; + slave_icl_pct = max(0, chip->slave_pct); + slave_ua = ((main_settled_ua + chip->pl_settled_ua) + * slave_icl_pct) / 100; + total_settled_ua = main_settled_ua + chip->pl_settled_ua; + } + + total_current_ua = get_effective_result_locked(chip->usb_icl_votable); + if (total_current_ua < 0) { + if (!chip->usb_psy) + chip->usb_psy = power_supply_get_by_name("usb"); + if (!chip->usb_psy) { + pr_err("Couldn't get usbpsy while splitting settled\n"); + return -ENOENT; + } + /* no client is voting, so get the total current from charger */ + rc = power_supply_get_property(chip->usb_psy, + POWER_SUPPLY_PROP_HW_CURRENT_MAX, &pval); + if (rc < 0) { + pr_err("Couldn't get max current rc=%d\n", rc); + return rc; + } + total_current_ua = pval.intval; + } + + *main_icl_ua = total_current_ua - slave_ua; + *slave_icl_ua = slave_ua; + *total_settled_icl_ua = total_settled_ua; + + pl_dbg(chip, PR_PARALLEL, + "Split total_current_ua=%d total_settled_ua=%d main_settled_ua=%d slave_ua=%d\n", + total_current_ua, total_settled_ua, main_settled_ua, slave_ua); + + return 0; +} + +static int validate_parallel_icl(struct pl_data *chip, bool *disable) +{ + int rc = 0; + int main_ua = 0, slave_ua = 0, total_settled_ua = 0; + + if (!IS_USBIN(chip->pl_mode) + || get_effective_result_locked(chip->pl_disable_votable)) + return 0; + + rc = get_settled_split(chip, &main_ua, &slave_ua, &total_settled_ua); + if (rc < 0) { + pr_err("Couldn't get split current rc=%d\n", rc); + return rc; + } + + if (slave_ua < chip->pl_min_icl_ua) + *disable = true; + else + *disable = false; + + return 0; +} + +static void split_settled(struct pl_data *chip) +{ + union power_supply_propval pval = {0, }; + int rc, main_ua, slave_ua, total_settled_ua; + + rc = get_settled_split(chip, &main_ua, &slave_ua, &total_settled_ua); + if (rc < 0) { + pr_err("Couldn't get split current rc=%d\n", rc); + return; + } + + /* + * If there is an increase in slave share + * (Also handles parallel enable case) + * Set Main ICL then slave ICL + * else + * (Also handles parallel disable case) + * Set slave ICL then main ICL. + */ + if (slave_ua > chip->pl_settled_ua) { + pval.intval = main_ua; + /* Set ICL on main charger */ + rc = power_supply_set_property(chip->main_psy, + POWER_SUPPLY_PROP_CURRENT_MAX, &pval); + if (rc < 0) { + pr_err("Couldn't change slave suspend state rc=%d\n", + rc); + return; + } + + /* set parallel's ICL could be 0mA when pl is disabled */ + pval.intval = slave_ua; + rc = power_supply_set_property(chip->pl_psy, + POWER_SUPPLY_PROP_CURRENT_MAX, &pval); + if (rc < 0) { + pr_err("Couldn't set parallel icl, rc=%d\n", rc); + return; + } + } else { + /* set parallel's ICL could be 0mA when pl is disabled */ + pval.intval = slave_ua; + rc = power_supply_set_property(chip->pl_psy, + POWER_SUPPLY_PROP_CURRENT_MAX, &pval); + if (rc < 0) { + pr_err("Couldn't set parallel icl, rc=%d\n", rc); + return; + } + + pval.intval = main_ua; + /* Set ICL on main charger */ + rc = power_supply_set_property(chip->main_psy, + POWER_SUPPLY_PROP_CURRENT_MAX, &pval); + if (rc < 0) { + pr_err("Couldn't change slave suspend state rc=%d\n", + rc); + return; + } + } + + chip->total_settled_ua = total_settled_ua; + chip->pl_settled_ua = slave_ua; +} + +static ssize_t version_show(struct class *c, struct class_attribute *attr, + char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "%d.%d\n", + DRV_MAJOR_VERSION, DRV_MINOR_VERSION); +} +static CLASS_ATTR_RO(version); + +/************* + * SLAVE PCT * + **************/ +static ssize_t slave_pct_show(struct class *c, struct class_attribute *attr, + char *ubuf) +{ + struct pl_data *chip = container_of(c, struct pl_data, + qcom_batt_class); + + return scnprintf(ubuf, PAGE_SIZE, "%d\n", chip->slave_pct); +} + +static ssize_t slave_pct_store(struct class *c, struct class_attribute *attr, + const char *ubuf, size_t count) +{ + struct pl_data *chip = container_of(c, struct pl_data, qcom_batt_class); + int rc; + unsigned long val; + bool disable = false; + + if (kstrtoul(ubuf, 10, &val)) + return -EINVAL; + + chip->slave_pct = val; + + rc = validate_parallel_icl(chip, &disable); + if (rc < 0) + return rc; + + vote(chip->pl_disable_votable, ICL_LIMIT_VOTER, disable, 0); + rerun_election(chip->fcc_votable); + rerun_election(chip->fv_votable); + if (IS_USBIN(chip->pl_mode)) + split_settled(chip); + + return count; +} +static struct class_attribute class_attr_slave_pct = + __ATTR(parallel_pct, 0644, slave_pct_show, slave_pct_store); + +/************************ + * RESTRICTED CHARGIGNG * + ************************/ +static ssize_t restrict_chg_show(struct class *c, struct class_attribute *attr, + char *ubuf) +{ + struct pl_data *chip = container_of(c, struct pl_data, + qcom_batt_class); + + return scnprintf(ubuf, PAGE_SIZE, "%d\n", + chip->restricted_charging_enabled); +} + +static ssize_t restrict_chg_store(struct class *c, struct class_attribute *attr, + const char *ubuf, size_t count) +{ + struct pl_data *chip = container_of(c, struct pl_data, + qcom_batt_class); + unsigned long val; + + if (kstrtoul(ubuf, 10, &val)) + return -EINVAL; + + if (chip->restricted_charging_enabled == !!val) + goto no_change; + + chip->restricted_charging_enabled = !!val; + + /* disable parallel charger in case of restricted charging */ + vote(chip->pl_disable_votable, RESTRICT_CHG_VOTER, + chip->restricted_charging_enabled, 0); + + vote(chip->fcc_votable, RESTRICT_CHG_VOTER, + chip->restricted_charging_enabled, + chip->restricted_current); + +no_change: + return count; +} +static CLASS_ATTR_RW(restrict_chg); + +static ssize_t restrict_cur_show(struct class *c, struct class_attribute *attr, + char *ubuf) +{ + struct pl_data *chip = container_of(c, struct pl_data, + qcom_batt_class); + + return scnprintf(ubuf, PAGE_SIZE, "%d\n", chip->restricted_current); +} + +static ssize_t restrict_cur_store(struct class *c, struct class_attribute *attr, + const char *ubuf, size_t count) +{ + struct pl_data *chip = container_of(c, struct pl_data, + qcom_batt_class); + unsigned long val; + + if (kstrtoul(ubuf, 10, &val)) + return -EINVAL; + + chip->restricted_current = val; + + vote(chip->fcc_votable, RESTRICT_CHG_VOTER, + chip->restricted_charging_enabled, + chip->restricted_current); + + return count; +} +static CLASS_ATTR_RW(restrict_cur); + +/**************************** + * FCC STEPPING IN PROGRESS * + ****************************/ +static ssize_t fcc_stepping_in_progress_show(struct class *c, + struct class_attribute *attr, char *ubuf) +{ + struct pl_data *chip = container_of(c, struct pl_data, + qcom_batt_class); + + return scnprintf(ubuf, PAGE_SIZE, "%d\n", chip->step_fcc); +} +static CLASS_ATTR_RO(fcc_stepping_in_progress); + +static struct attribute *batt_class_attrs[] = { + [VER] = &class_attr_version.attr, + [SLAVE_PCT] = &class_attr_slave_pct.attr, + [RESTRICT_CHG_ENABLE] = &class_attr_restrict_chg.attr, + [RESTRICT_CHG_CURRENT] = &class_attr_restrict_cur.attr, + [FCC_STEPPING_IN_PROGRESS] + = &class_attr_fcc_stepping_in_progress.attr, + NULL, +}; +ATTRIBUTE_GROUPS(batt_class); + +/********* + * FCC * + **********/ +#define EFFICIENCY_PCT 80 +#define STEP_UP 1 +#define STEP_DOWN -1 +static void get_fcc_split(struct pl_data *chip, int total_ua, + int *master_ua, int *slave_ua) +{ + int rc, effective_total_ua, slave_limited_ua, hw_cc_delta_ua = 0, + icl_ua, adapter_uv, bcl_ua; + union power_supply_propval pval = {0, }; + + rc = power_supply_get_property(chip->main_psy, + POWER_SUPPLY_PROP_FCC_DELTA, &pval); + if (rc < 0) + hw_cc_delta_ua = 0; + else + hw_cc_delta_ua = pval.intval; + + bcl_ua = INT_MAX; + if (chip->pl_mode == POWER_SUPPLY_PL_USBMID_USBMID) { + rc = power_supply_get_property(chip->main_psy, + POWER_SUPPLY_PROP_INPUT_CURRENT_SETTLED, &pval); + if (rc < 0) { + pr_err("Couldn't get aicl settled value rc=%d\n", rc); + return; + } + icl_ua = pval.intval; + + rc = power_supply_get_property(chip->main_psy, + POWER_SUPPLY_PROP_INPUT_VOLTAGE_SETTLED, &pval); + if (rc < 0) { + pr_err("Couldn't get adaptive voltage rc=%d\n", rc); + return; + } + adapter_uv = pval.intval; + + bcl_ua = div64_s64((s64)icl_ua * adapter_uv * EFFICIENCY_PCT, + (s64)get_effective_result(chip->fv_votable) * 100); + } + + effective_total_ua = max(0, total_ua + hw_cc_delta_ua); + slave_limited_ua = min(effective_total_ua, bcl_ua); + *slave_ua = (slave_limited_ua * chip->slave_pct) / 100; + *slave_ua = min(*slave_ua, chip->pl_fcc_max); + + /* + * In stacked BATFET configuration charger's current goes + * through main charger's BATFET, keep the main charger's FCC + * to the votable result. + */ + if (chip->pl_batfet_mode == POWER_SUPPLY_PL_STACKED_BATFET) { + *master_ua = max(0, total_ua); + if (chip->main_fcc_max) + *master_ua = min(*master_ua, + chip->main_fcc_max + *slave_ua); + } else { + *master_ua = max(0, total_ua - *slave_ua); + if (chip->main_fcc_max) + *master_ua = min(*master_ua, chip->main_fcc_max); + } +} + +static void get_main_fcc_config(struct pl_data *chip, int *total_fcc) +{ + union power_supply_propval pval = {0, }; + int rc; + + if (!is_cp_available(chip)) + goto out; + + rc = power_supply_get_property(chip->cp_master_psy, + POWER_SUPPLY_PROP_CP_SWITCHER_EN, &pval); + if (rc < 0) { + pr_err("Couldn't get switcher enable status, rc=%d\n", rc); + goto out; + } + + if (!pval.intval) { + /* + * To honor main charger upper FCC limit, on CP switcher + * disable, skip fcc slewing as it will cause delay in limiting + * the charge current flowing through main charger. + */ + if (!chip->cp_disabled) { + chip->fcc_stepper_enable = false; + pl_dbg(chip, PR_PARALLEL, + "Disabling FCC slewing on CP Switcher disable\n"); + } + chip->cp_disabled = true; + } else { + chip->cp_disabled = false; + pl_dbg(chip, PR_PARALLEL, + "CP Switcher is enabled, don't limit main fcc\n"); + return; + } +out: + *total_fcc = min(*total_fcc, chip->main_fcc_max); +} + +static void get_fcc_stepper_params(struct pl_data *chip, int main_fcc_ua, + int parallel_fcc_ua) +{ + int main_set_fcc_ua, total_fcc_ua, target_icl; + bool override; + + if (!chip->chg_param->fcc_step_size_ua) { + pr_err("Invalid fcc stepper step size, value 0\n"); + return; + } + + total_fcc_ua = main_fcc_ua + parallel_fcc_ua; + override = is_override_vote_enabled_locked(chip->fcc_main_votable); + if (override) { + /* + * FCC stepper params need re-calculation in override mode + * only if there is change in Main or total FCC + */ + + main_set_fcc_ua = get_effective_result_locked( + chip->fcc_main_votable); + if ((main_set_fcc_ua != chip->override_main_fcc_ua) + || (total_fcc_ua != chip->total_fcc_ua)) { + chip->override_main_fcc_ua = main_set_fcc_ua; + chip->total_fcc_ua = total_fcc_ua; + } else { + goto skip_fcc_step_update; + } + } + + /* + * If override vote is removed then start main FCC from the + * last overridden value. + * Clear slave_fcc if requested parallel current is 0 i.e. + * parallel is disabled. + */ + if (chip->override_main_fcc_ua && !override) { + chip->main_fcc_ua = chip->override_main_fcc_ua; + chip->override_main_fcc_ua = 0; + if (!parallel_fcc_ua) + chip->slave_fcc_ua = 0; + } else { + chip->main_fcc_ua = get_effective_result_locked( + chip->fcc_main_votable); + } + + /* Skip stepping if override vote is applied on main */ + if (override) { + chip->main_step_fcc_count = 0; + chip->main_step_fcc_residual = 0; + } else { + chip->main_step_fcc_dir = + (main_fcc_ua > chip->main_fcc_ua) ? + STEP_UP : STEP_DOWN; + chip->main_step_fcc_count = + abs(main_fcc_ua - chip->main_fcc_ua) / + chip->chg_param->fcc_step_size_ua; + chip->main_step_fcc_residual = + abs(main_fcc_ua - chip->main_fcc_ua) % + chip->chg_param->fcc_step_size_ua; + } + + /* Calculate CP_ILIM based on adapter limit and max. FCC */ + if (!parallel_fcc_ua && is_cp_available(chip) && override) { + if (!chip->cp_ilim_votable) + chip->cp_ilim_votable = find_votable("CP_ILIM"); + + target_icl = get_hvdcp3_icl_limit(chip) * 2; + total_fcc_ua -= chip->main_fcc_ua; + + /* + * CP_ILIM = parallel_fcc_ua / 2. + * Calculate parallel_fcc_ua as follows: + * parallel_fcc_ua is based minimum of total FCC + * or adapter's maximum allowed ICL limitation(if adapter + * has max. ICL limitations). + */ + parallel_fcc_ua = (target_icl > 0) ? + min(target_icl, total_fcc_ua) : total_fcc_ua; + } + + /* Skip stepping if override vote is applied on CP */ + if (chip->cp_ilim_votable + && is_override_vote_enabled(chip->cp_ilim_votable)) { + chip->parallel_step_fcc_count = 0; + chip->parallel_step_fcc_residual = 0; + } else { + chip->parallel_step_fcc_dir = + (parallel_fcc_ua > chip->slave_fcc_ua) ? + STEP_UP : STEP_DOWN; + chip->parallel_step_fcc_count = + abs(parallel_fcc_ua - chip->slave_fcc_ua) / + chip->chg_param->fcc_step_size_ua; + chip->parallel_step_fcc_residual = + abs(parallel_fcc_ua - chip->slave_fcc_ua) % + chip->chg_param->fcc_step_size_ua; + } +skip_fcc_step_update: + if (chip->parallel_step_fcc_count || chip->parallel_step_fcc_residual + || chip->main_step_fcc_count || chip->main_step_fcc_residual) + chip->step_fcc = 1; + + pl_dbg(chip, PR_PARALLEL, + "Main FCC Stepper parameters: target_main_fcc: %d, current_main_fcc: %d main_step_direction: %d, main_step_count: %d, main_residual_fcc: %d override_main_fcc_ua: %d override: %d\n", + main_fcc_ua, chip->main_fcc_ua, chip->main_step_fcc_dir, + chip->main_step_fcc_count, chip->main_step_fcc_residual, + chip->override_main_fcc_ua, override); + pl_dbg(chip, PR_PARALLEL, + "Parallel FCC Stepper parameters: target_pl_fcc: %d current_pl_fcc: %d parallel_step_direction: %d, parallel_step_count: %d, parallel_residual_fcc: %d\n", + parallel_fcc_ua, chip->slave_fcc_ua, + chip->parallel_step_fcc_dir, chip->parallel_step_fcc_count, + chip->parallel_step_fcc_residual); + pl_dbg(chip, PR_PARALLEL, "FCC Stepper parameters: step_fcc=%d\n", + chip->step_fcc); +} + +#define MINIMUM_PARALLEL_FCC_UA 500000 +#define PL_TAPER_WORK_DELAY_MS 500 +#define TAPER_RESIDUAL_PCT 90 +#define TAPER_REDUCTION_UA 200000 +static void pl_taper_work(struct work_struct *work) +{ + struct pl_data *chip = container_of(work, struct pl_data, + pl_taper_work); + union power_supply_propval pval = {0, }; + int rc; + int fcc_ua, total_fcc_ua, master_fcc_ua, slave_fcc_ua = 0; + + chip->taper_entry_fv = get_effective_result(chip->fv_votable); + chip->taper_work_running = true; + fcc_ua = get_client_vote(chip->fcc_votable, BATT_PROFILE_VOTER); + vote(chip->fcc_votable, TAPER_STEPPER_VOTER, true, fcc_ua); + while (true) { + if (get_effective_result(chip->pl_disable_votable)) { + /* + * if parallel's FCC share is low, simply disable + * parallel with TAPER_END_VOTER + */ + total_fcc_ua = get_effective_result_locked( + chip->fcc_votable); + get_fcc_split(chip, total_fcc_ua, &master_fcc_ua, + &slave_fcc_ua); + if (slave_fcc_ua <= MINIMUM_PARALLEL_FCC_UA) { + pl_dbg(chip, PR_PARALLEL, "terminating: parallel's share is low\n"); + vote(chip->pl_disable_votable, TAPER_END_VOTER, + true, 0); + } else { + pl_dbg(chip, PR_PARALLEL, "terminating: parallel disabled\n"); + } + goto done; + } + + /* + * Due to reduction of float voltage in JEITA condition taper + * charging can be initiated at a lower FV. On removal of JEITA + * condition, FV readjusts itself. However, once taper charging + * is initiated, it doesn't exits until parallel chaging is + * disabled due to which FCC doesn't scale back to its original + * value, leading to slow charging thereafter. + * Check if FV increases in comparison to FV at which taper + * charging was initiated, and if yes, exit taper charging. + */ + if (get_effective_result(chip->fv_votable) > + chip->taper_entry_fv) { + pl_dbg(chip, PR_PARALLEL, "Float voltage increased. Exiting taper\n"); + goto done; + } else { + chip->taper_entry_fv = + get_effective_result(chip->fv_votable); + } + + rc = power_supply_get_property(chip->batt_psy, + POWER_SUPPLY_PROP_CHARGE_TYPE, &pval); + if (rc < 0) { + pr_err("Couldn't get batt charge type rc=%d\n", rc); + goto done; + } + + chip->charge_type = pval.intval; + if (pval.intval == POWER_SUPPLY_CHARGE_TYPE_TAPER) { + fcc_ua = get_client_vote(chip->fcc_votable, + TAPER_STEPPER_VOTER); + if (fcc_ua < 0) { + pr_err("Couldn't get fcc, exiting taper work\n"); + goto done; + } + fcc_ua -= TAPER_REDUCTION_UA; + if (fcc_ua < 0) { + pr_err("Can't reduce FCC any more\n"); + goto done; + } + + pl_dbg(chip, PR_PARALLEL, "master is taper charging; reducing FCC to %dua\n", + fcc_ua); + vote(chip->fcc_votable, TAPER_STEPPER_VOTER, + true, fcc_ua); + } else { + pl_dbg(chip, PR_PARALLEL, "master is fast charging; waiting for next taper\n"); + } + + /* wait for the charger state to deglitch after FCC change */ + msleep(PL_TAPER_WORK_DELAY_MS); + } +done: + chip->taper_work_running = false; + vote(chip->fcc_votable, TAPER_STEPPER_VOTER, false, 0); + vote(chip->pl_awake_votable, TAPER_END_VOTER, false, 0); +} + +static bool is_main_available(struct pl_data *chip) +{ + if (chip->main_psy) + return true; + + chip->main_psy = power_supply_get_by_name("main"); + + return !!chip->main_psy; +} + +static int pl_fcc_main_vote_callback(struct votable *votable, void *data, + int fcc_main_ua, const char *client) +{ + struct pl_data *chip = data; + union power_supply_propval pval = {0,}; + + if (!is_main_available(chip)) + return 0; + + pval.intval = fcc_main_ua; + return power_supply_set_property(chip->main_psy, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, + &pval); +} + +static int pl_fcc_vote_callback(struct votable *votable, void *data, + int total_fcc_ua, const char *client) +{ + struct pl_data *chip = data; + int master_fcc_ua = total_fcc_ua, slave_fcc_ua = 0; + int cp_fcc_ua = 0, rc = 0; + union power_supply_propval pval = {0, }; + + if (total_fcc_ua < 0) + return 0; + + if (!chip->main_psy) + return 0; + + if (!chip->cp_disable_votable) + chip->cp_disable_votable = find_votable("CP_DISABLE"); + + if (!chip->cp_master_psy) + chip->cp_master_psy = + power_supply_get_by_name("charge_pump_master"); + + if (!chip->cp_slave_psy) + chip->cp_slave_psy = power_supply_get_by_name("cp_slave"); + + if (!chip->cp_slave_disable_votable) + chip->cp_slave_disable_votable = + find_votable("CP_SLAVE_DISABLE"); + + /* + * CP charger current = Total FCC - Main charger's FCC. + * Main charger FCC is userspace's override vote on main. + */ + cp_fcc_ua = total_fcc_ua - chip->chg_param->forced_main_fcc; + pl_dbg(chip, PR_PARALLEL, + "cp_fcc_ua=%d total_fcc_ua=%d forced_main_fcc=%d\n", + cp_fcc_ua, total_fcc_ua, chip->chg_param->forced_main_fcc); + if (cp_fcc_ua > 0) { + if (chip->cp_master_psy) { + rc = power_supply_get_property(chip->cp_master_psy, + POWER_SUPPLY_PROP_MIN_ICL, &pval); + if (rc < 0) + pr_err("Couldn't get MIN ICL threshold rc=%d\n", + rc); + } + + if (chip->cp_slave_psy && chip->cp_slave_disable_votable) { + /* + * Disable Slave CP if FCC share + * falls below 3 * min ICL threshold. + */ + vote(chip->cp_slave_disable_votable, FCC_VOTER, + (cp_fcc_ua < (3 * pval.intval)), 0); + } + + if (chip->cp_disable_votable) { + /* + * Disable Master CP if FCC share + * falls below 2 * min ICL threshold. + */ + vote(chip->cp_disable_votable, FCC_VOTER, + (cp_fcc_ua < (2 * pval.intval)), 0); + } + } + + if (chip->pl_mode != POWER_SUPPLY_PL_NONE) { + get_fcc_split(chip, total_fcc_ua, &master_fcc_ua, + &slave_fcc_ua); + + if (slave_fcc_ua > MINIMUM_PARALLEL_FCC_UA) { + vote(chip->pl_disable_votable, PL_FCC_LOW_VOTER, + false, 0); + } else { + vote(chip->pl_disable_votable, PL_FCC_LOW_VOTER, + true, 0); + } + } + + rerun_election(chip->pl_disable_votable); + /* When FCC changes, trigger psy changed event for CC mode */ + if (chip->cp_master_psy) + power_supply_changed(chip->cp_master_psy); + + return 0; +} + +static void fcc_stepper_work(struct work_struct *work) +{ + struct pl_data *chip = container_of(work, struct pl_data, + fcc_stepper_work.work); + union power_supply_propval pval = {0, }; + int reschedule_ms = 0, rc = 0, charger_present = 0; + int main_fcc = chip->main_fcc_ua; + int parallel_fcc = chip->slave_fcc_ua; + + /* Check whether USB is present or not */ + rc = power_supply_get_property(chip->usb_psy, + POWER_SUPPLY_PROP_PRESENT, &pval); + if (rc < 0) + pr_err("Couldn't get USB Present status, rc=%d\n", rc); + + charger_present = pval.intval; + + /*Check whether DC charger is present or not */ + if (!chip->dc_psy) + chip->dc_psy = power_supply_get_by_name("dc"); + if (chip->dc_psy) { + rc = power_supply_get_property(chip->dc_psy, + POWER_SUPPLY_PROP_PRESENT, &pval); + if (rc < 0) + pr_err("Couldn't get DC Present status, rc=%d\n", rc); + + charger_present |= pval.intval; + } + + /* + * If USB is not present, then set parallel FCC to min value and + * main FCC to the effective value of FCC votable and exit. + */ + if (!charger_present) { + /* Disable parallel */ + parallel_fcc = 0; + + if (chip->pl_psy) { + pval.intval = 1; + rc = power_supply_set_property(chip->pl_psy, + POWER_SUPPLY_PROP_INPUT_SUSPEND, &pval); + if (rc < 0) { + pr_err("Couldn't change slave suspend state rc=%d\n", + rc); + goto out; + } + + chip->pl_disable = true; + power_supply_changed(chip->pl_psy); + } + + main_fcc = get_effective_result_locked(chip->fcc_votable); + vote(chip->fcc_main_votable, FCC_STEPPER_VOTER, true, main_fcc); + goto stepper_exit; + } + + if (chip->main_step_fcc_count) { + main_fcc += (chip->chg_param->fcc_step_size_ua + * chip->main_step_fcc_dir); + chip->main_step_fcc_count--; + reschedule_ms = chip->chg_param->fcc_step_delay_ms; + } else if (chip->main_step_fcc_residual) { + main_fcc += chip->main_step_fcc_residual; + chip->main_step_fcc_residual = 0; + } + + if (chip->parallel_step_fcc_count) { + parallel_fcc += (chip->chg_param->fcc_step_size_ua + * chip->parallel_step_fcc_dir); + chip->parallel_step_fcc_count--; + reschedule_ms = chip->chg_param->fcc_step_delay_ms; + } else if (chip->parallel_step_fcc_residual) { + parallel_fcc += chip->parallel_step_fcc_residual; + chip->parallel_step_fcc_residual = 0; + } + + if (parallel_fcc < chip->slave_fcc_ua) { + /* Set parallel FCC */ + if (chip->pl_psy && !chip->pl_disable) { + if (parallel_fcc < MINIMUM_PARALLEL_FCC_UA) { + pval.intval = 1; + rc = power_supply_set_property(chip->pl_psy, + POWER_SUPPLY_PROP_INPUT_SUSPEND, &pval); + if (rc < 0) { + pr_err("Couldn't change slave suspend state rc=%d\n", + rc); + goto out; + } + + if (IS_USBIN(chip->pl_mode)) + split_settled(chip); + + parallel_fcc = 0; + chip->parallel_step_fcc_count = 0; + chip->parallel_step_fcc_residual = 0; + chip->total_settled_ua = 0; + chip->pl_settled_ua = 0; + chip->pl_disable = true; + power_supply_changed(chip->pl_psy); + } else { + /* Set Parallel FCC */ + pval.intval = parallel_fcc; + rc = power_supply_set_property(chip->pl_psy, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, + &pval); + if (rc < 0) { + pr_err("Couldn't set parallel charger fcc, rc=%d\n", + rc); + goto out; + } + } + } + + /* Set main FCC */ + vote(chip->fcc_main_votable, FCC_STEPPER_VOTER, true, main_fcc); + } else { + /* Set main FCC */ + vote(chip->fcc_main_votable, FCC_STEPPER_VOTER, true, main_fcc); + + /* Set parallel FCC */ + if (chip->pl_psy) { + pval.intval = parallel_fcc; + rc = power_supply_set_property(chip->pl_psy, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, + &pval); + if (rc < 0) { + pr_err("Couldn't set parallel charger fcc, rc=%d\n", + rc); + goto out; + } + + /* + * Enable parallel charger only if it was disabled + * earlier and configured slave fcc is greater than or + * equal to minimum parallel FCC value. + */ + if (chip->pl_disable && parallel_fcc + >= MINIMUM_PARALLEL_FCC_UA) { + pval.intval = 0; + rc = power_supply_set_property(chip->pl_psy, + POWER_SUPPLY_PROP_INPUT_SUSPEND, &pval); + if (rc < 0) { + pr_err("Couldn't change slave suspend state rc=%d\n", + rc); + goto out; + } + + if (IS_USBIN(chip->pl_mode)) + split_settled(chip); + + chip->pl_disable = false; + power_supply_changed(chip->pl_psy); + } + } + } + +stepper_exit: + chip->main_fcc_ua = main_fcc; + chip->slave_fcc_ua = parallel_fcc; + cp_configure_ilim(chip, FCC_VOTER, chip->slave_fcc_ua / 2); + + if (reschedule_ms) { + schedule_delayed_work(&chip->fcc_stepper_work, + msecs_to_jiffies(reschedule_ms)); + pr_debug("Rescheduling FCC_STEPPER work\n"); + return; + } +out: + chip->step_fcc = 0; + vote(chip->pl_awake_votable, FCC_STEPPER_VOTER, false, 0); +} + +static bool is_batt_available(struct pl_data *chip) +{ + if (!chip->batt_psy) + chip->batt_psy = power_supply_get_by_name("battery"); + + if (!chip->batt_psy) + return false; + + return true; +} + +#define PARALLEL_FLOAT_VOLTAGE_DELTA_UV 50000 +static int pl_fv_vote_callback(struct votable *votable, void *data, + int fv_uv, const char *client) +{ + struct pl_data *chip = data; + union power_supply_propval pval = {0, }; + int rc = 0; + + if (fv_uv < 0) + return 0; + + if (!chip->main_psy) + return 0; + + pval.intval = fv_uv; + + rc = power_supply_set_property(chip->main_psy, + POWER_SUPPLY_PROP_VOLTAGE_MAX, &pval); + if (rc < 0) { + pr_err("Couldn't set main fv, rc=%d\n", rc); + return rc; + } + + if (chip->pl_mode != POWER_SUPPLY_PL_NONE) { + pval.intval += PARALLEL_FLOAT_VOLTAGE_DELTA_UV; + rc = power_supply_set_property(chip->pl_psy, + POWER_SUPPLY_PROP_VOLTAGE_MAX, &pval); + if (rc < 0) { + pr_err("Couldn't set float on parallel rc=%d\n", rc); + return rc; + } + } + + /* + * check for termination at reduced float voltage and re-trigger + * charging if new float voltage is above last FV. + */ + if ((chip->float_voltage_uv < fv_uv) && is_batt_available(chip)) { + rc = power_supply_get_property(chip->batt_psy, + POWER_SUPPLY_PROP_STATUS, &pval); + if (rc < 0) { + pr_err("Couldn't get battery status rc=%d\n", rc); + } else { + if (pval.intval == POWER_SUPPLY_STATUS_FULL) { + pr_debug("re-triggering charging\n"); + pval.intval = 1; + rc = power_supply_set_property(chip->batt_psy, + POWER_SUPPLY_PROP_FORCE_RECHARGE, + &pval); + if (rc < 0) + pr_err("Couldn't set force recharge rc=%d\n", + rc); + } + } + } + + chip->float_voltage_uv = fv_uv; + + return 0; +} + +#define ICL_STEP_UA 25000 +#define PL_DELAY_MS 3000 +static int usb_icl_vote_callback(struct votable *votable, void *data, + int icl_ua, const char *client) +{ + int rc; + struct pl_data *chip = data; + union power_supply_propval pval = {0, }; + bool rerun_aicl = false, dc_present = false; + + if (!chip->main_psy) + return 0; + + if (client == NULL) + icl_ua = INT_MAX; + + /* + * Disable parallel for new ICL vote - the call to split_settled will + * ensure that all the input current limit gets assigned to the main + * charger. + */ + vote(chip->pl_disable_votable, ICL_CHANGE_VOTER, true, 0); + + /* + * if (ICL < 1400) + * disable parallel charger using USBIN_I_VOTER + * else + * instead of re-enabling here rely on status_changed_work + * (triggered via AICL completed or scheduled from here to + * unvote USBIN_I_VOTER) the status_changed_work enables + * USBIN_I_VOTER based on settled current. + */ + if (icl_ua <= 1400000) + vote(chip->pl_enable_votable_indirect, USBIN_I_VOTER, false, 0); + else + schedule_delayed_work(&chip->status_change_work, + msecs_to_jiffies(PL_DELAY_MS)); + + /* rerun AICL */ + /* get the settled current */ + rc = power_supply_get_property(chip->main_psy, + POWER_SUPPLY_PROP_INPUT_CURRENT_SETTLED, + &pval); + if (rc < 0) { + pr_err("Couldn't get aicl settled value rc=%d\n", rc); + return rc; + } + + /* rerun AICL if new ICL is above settled ICL */ + if (icl_ua > pval.intval) + rerun_aicl = true; + + if (rerun_aicl && (chip->wa_flags & AICL_RERUN_WA_BIT)) { + /* set a lower ICL */ + pval.intval = max(pval.intval - ICL_STEP_UA, ICL_STEP_UA); + power_supply_set_property(chip->main_psy, + POWER_SUPPLY_PROP_CURRENT_MAX, + &pval); + } + + /* set the effective ICL */ + pval.intval = icl_ua; + power_supply_set_property(chip->main_psy, + POWER_SUPPLY_PROP_CURRENT_MAX, + &pval); + + vote(chip->pl_disable_votable, ICL_CHANGE_VOTER, false, 0); + + /* Configure ILIM based on AICL result only if input mode is USBMID */ + if (cp_get_parallel_mode(chip, PARALLEL_INPUT_MODE) + == POWER_SUPPLY_PL_USBMID_USBMID) { + if (chip->dc_psy) { + rc = power_supply_get_property(chip->dc_psy, + POWER_SUPPLY_PROP_PRESENT, &pval); + if (rc < 0) { + pr_err("Couldn't get DC PRESENT rc=%d\n", rc); + return rc; + } + dc_present = pval.intval; + } + + /* Don't configure ILIM if DC is present */ + if (!dc_present) + cp_configure_ilim(chip, ICL_CHANGE_VOTER, icl_ua); + } + + return 0; +} + +static void pl_disable_forever_work(struct work_struct *work) +{ + struct pl_data *chip = container_of(work, + struct pl_data, pl_disable_forever_work); + + /* Disable Parallel charger forever */ + vote(chip->pl_disable_votable, PL_HW_ABSENT_VOTER, true, 0); + + /* Re-enable autonomous mode */ + if (chip->hvdcp_hw_inov_dis_votable) + vote(chip->hvdcp_hw_inov_dis_votable, PL_VOTER, false, 0); +} + +static int pl_disable_vote_callback(struct votable *votable, + void *data, int pl_disable, const char *client) +{ + struct pl_data *chip = data; + union power_supply_propval pval = {0, }; + int master_fcc_ua = 0, total_fcc_ua = 0, slave_fcc_ua = 0; + int rc = 0, cp_ilim; + bool disable = false; + + if (!is_main_available(chip)) + return -ENODEV; + + if (!is_batt_available(chip)) + return -ENODEV; + + if (!chip->usb_psy) + chip->usb_psy = power_supply_get_by_name("usb"); + if (!chip->usb_psy) { + pr_err("Couldn't get usb psy\n"); + return -ENODEV; + } + + rc = power_supply_get_property(chip->batt_psy, + POWER_SUPPLY_PROP_FCC_STEPPER_ENABLE, &pval); + if (rc < 0) { + pr_err("Couldn't read FCC step update status, rc=%d\n", rc); + return rc; + } + chip->fcc_stepper_enable = pval.intval; + pr_debug("FCC Stepper %s\n", pval.intval ? "enabled" : "disabled"); + + rc = power_supply_get_property(chip->main_psy, + POWER_SUPPLY_PROP_MAIN_FCC_MAX, &pval); + if (rc < 0) { + pl_dbg(chip, PR_PARALLEL, + "Couldn't read primary charger FCC upper limit, rc=%d\n", + rc); + } else if (pval.intval > 0) { + chip->main_fcc_max = pval.intval; + } + + if (chip->fcc_stepper_enable) { + cancel_delayed_work_sync(&chip->fcc_stepper_work); + vote(chip->pl_awake_votable, FCC_STEPPER_VOTER, false, 0); + } + + total_fcc_ua = get_effective_result_locked(chip->fcc_votable); + + if (chip->pl_mode != POWER_SUPPLY_PL_NONE && !pl_disable) { + rc = validate_parallel_icl(chip, &disable); + if (rc < 0) + return rc; + + if (disable) { + pr_info("Parallel ICL is less than min ICL(%d), skipping parallel enable\n", + chip->pl_min_icl_ua); + return 0; + } + + /* enable parallel charging */ + rc = power_supply_get_property(chip->pl_psy, + POWER_SUPPLY_PROP_CHARGE_TYPE, &pval); + if (rc == -ENODEV) { + /* + * -ENODEV is returned only if parallel chip + * is not present in the system. + * Disable parallel charger forever. + */ + schedule_work(&chip->pl_disable_forever_work); + return rc; + } + + rerun_election(chip->fv_votable); + + get_fcc_split(chip, total_fcc_ua, &master_fcc_ua, + &slave_fcc_ua); + + if (chip->fcc_stepper_enable) { + get_fcc_stepper_params(chip, master_fcc_ua, + slave_fcc_ua); + if (chip->step_fcc) { + vote(chip->pl_awake_votable, FCC_STEPPER_VOTER, + true, 0); + schedule_delayed_work(&chip->fcc_stepper_work, + 0); + } + } else { + /* + * If there is an increase in slave share + * (Also handles parallel enable case) + * Set Main ICL then slave FCC + * else + * (Also handles parallel disable case) + * Set slave ICL then main FCC. + */ + if (slave_fcc_ua > chip->slave_fcc_ua) { + vote(chip->fcc_main_votable, MAIN_FCC_VOTER, + true, master_fcc_ua); + pval.intval = slave_fcc_ua; + rc = power_supply_set_property(chip->pl_psy, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, + &pval); + if (rc < 0) { + pr_err("Couldn't set parallel fcc, rc=%d\n", + rc); + return rc; + } + + chip->slave_fcc_ua = slave_fcc_ua; + } else { + pval.intval = slave_fcc_ua; + rc = power_supply_set_property(chip->pl_psy, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, + &pval); + if (rc < 0) { + pr_err("Couldn't set parallel fcc, rc=%d\n", + rc); + return rc; + } + + chip->slave_fcc_ua = slave_fcc_ua; + vote(chip->fcc_main_votable, MAIN_FCC_VOTER, + true, master_fcc_ua); + } + + /* + * Enable will be called with a valid pl_psy always. The + * PARALLEL_PSY_VOTER keeps it disabled unless a pl_psy + * is seen. + */ + pval.intval = 0; + rc = power_supply_set_property(chip->pl_psy, + POWER_SUPPLY_PROP_INPUT_SUSPEND, &pval); + if (rc < 0) + pr_err("Couldn't change slave suspend state rc=%d\n", + rc); + + if (IS_USBIN(chip->pl_mode)) + split_settled(chip); + } + + /* + * we could have been enabled while in taper mode, + * start the taper work if so + */ + rc = power_supply_get_property(chip->batt_psy, + POWER_SUPPLY_PROP_CHARGE_TYPE, &pval); + if (rc < 0) { + pr_err("Couldn't get batt charge type rc=%d\n", rc); + } else { + if (pval.intval == POWER_SUPPLY_CHARGE_TYPE_TAPER + && !chip->taper_work_running) { + pl_dbg(chip, PR_PARALLEL, + "pl enabled in Taper scheduing work\n"); + vote(chip->pl_awake_votable, TAPER_END_VOTER, + true, 0); + queue_work(system_long_wq, + &chip->pl_taper_work); + } + } + + pl_dbg(chip, 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); + } else { + if (chip->main_fcc_max) + get_main_fcc_config(chip, &total_fcc_ua); + + if (!chip->fcc_stepper_enable) { + if (IS_USBIN(chip->pl_mode)) + split_settled(chip); + + /* pl_psy may be NULL while in the disable branch */ + if (chip->pl_psy) { + pval.intval = 1; + rc = power_supply_set_property(chip->pl_psy, + POWER_SUPPLY_PROP_INPUT_SUSPEND, &pval); + if (rc < 0) + pr_err("Couldn't change slave suspend state rc=%d\n", + rc); + } + + /* main psy gets all share */ + vote(chip->fcc_main_votable, MAIN_FCC_VOTER, true, + total_fcc_ua); + cp_ilim = total_fcc_ua - get_effective_result_locked( + chip->fcc_main_votable); + if (cp_ilim > 0) + cp_configure_ilim(chip, FCC_VOTER, cp_ilim / 2); + + /* reset parallel FCC */ + chip->slave_fcc_ua = 0; + chip->total_settled_ua = 0; + chip->pl_settled_ua = 0; + } else { + get_fcc_stepper_params(chip, total_fcc_ua, 0); + if (chip->step_fcc) { + vote(chip->pl_awake_votable, FCC_STEPPER_VOTER, + true, 0); + schedule_delayed_work(&chip->fcc_stepper_work, + 0); + } + } + + rerun_election(chip->fv_votable); + } + + /* notify parallel state change */ + if (chip->pl_psy && (chip->pl_disable != pl_disable) + && !chip->fcc_stepper_enable) { + power_supply_changed(chip->pl_psy); + chip->pl_disable = (bool)pl_disable; + } + + pl_dbg(chip, PR_PARALLEL, "parallel charging %s\n", + pl_disable ? "disabled" : "enabled"); + + return 0; +} + +static int pl_enable_indirect_vote_callback(struct votable *votable, + void *data, int pl_enable, const char *client) +{ + struct pl_data *chip = data; + + vote(chip->pl_disable_votable, PL_INDIRECT_VOTER, !pl_enable, 0); + + return 0; +} + +static int pl_awake_vote_callback(struct votable *votable, + void *data, int awake, const char *client) +{ + struct pl_data *chip = data; + + if (awake) + __pm_stay_awake(chip->pl_ws); + else + __pm_relax(chip->pl_ws); + + pr_debug("client: %s awake: %d\n", client, awake); + return 0; +} + +static bool is_parallel_available(struct pl_data *chip) +{ + union power_supply_propval pval = {0, }; + int rc; + + if (chip->pl_psy) + return true; + + chip->pl_psy = power_supply_get_by_name("parallel"); + if (!chip->pl_psy) + return false; + + vote(chip->pl_disable_votable, PARALLEL_PSY_VOTER, false, 0); + + rc = power_supply_get_property(chip->pl_psy, + POWER_SUPPLY_PROP_PARALLEL_MODE, &pval); + if (rc < 0) { + pr_err("Couldn't get parallel mode from parallel rc=%d\n", + rc); + return false; + } + /* + * Note that pl_mode will be updated to anything other than a _NONE + * only after pl_psy is found. IOW pl_mode != _NONE implies that + * pl_psy is present and valid. + */ + chip->pl_mode = pval.intval; + + /* Disable autonomous votage increments for USBIN-USBIN */ + if (IS_USBIN(chip->pl_mode) + && (chip->wa_flags & FORCE_INOV_DISABLE_BIT)) { + if (!chip->hvdcp_hw_inov_dis_votable) + chip->hvdcp_hw_inov_dis_votable = + find_votable("HVDCP_HW_INOV_DIS"); + if (chip->hvdcp_hw_inov_dis_votable) + /* Read current pulse count */ + vote(chip->hvdcp_hw_inov_dis_votable, PL_VOTER, + true, 0); + else + return false; + } + + rc = power_supply_get_property(chip->pl_psy, + POWER_SUPPLY_PROP_PARALLEL_BATFET_MODE, &pval); + if (rc < 0) { + pr_err("Couldn't get parallel batfet mode rc=%d\n", + rc); + return false; + } + chip->pl_batfet_mode = pval.intval; + + pval.intval = 0; + power_supply_get_property(chip->pl_psy, POWER_SUPPLY_PROP_MIN_ICL, + &pval); + chip->pl_min_icl_ua = pval.intval; + + chip->pl_fcc_max = INT_MAX; + rc = power_supply_get_property(chip->pl_psy, + POWER_SUPPLY_PROP_PARALLEL_FCC_MAX, &pval); + if (!rc) + chip->pl_fcc_max = pval.intval; + + return true; +} + +static void handle_main_charge_type(struct pl_data *chip) +{ + union power_supply_propval pval = {0, }; + int rc; + + rc = power_supply_get_property(chip->batt_psy, + POWER_SUPPLY_PROP_CHARGE_TYPE, &pval); + if (rc < 0) { + pr_err("Couldn't get batt charge type rc=%d\n", rc); + return; + } + + /* not fast/not taper state to disables parallel */ + if ((pval.intval != POWER_SUPPLY_CHARGE_TYPE_FAST) + && (pval.intval != POWER_SUPPLY_CHARGE_TYPE_TAPER)) { + vote(chip->pl_disable_votable, CHG_STATE_VOTER, true, 0); + chip->charge_type = pval.intval; + return; + } + + /* handle taper charge entry */ + if (chip->charge_type == POWER_SUPPLY_CHARGE_TYPE_FAST + && (pval.intval == POWER_SUPPLY_CHARGE_TYPE_TAPER)) { + chip->charge_type = pval.intval; + if (!chip->taper_work_running) { + pl_dbg(chip, PR_PARALLEL, "taper entry scheduling work\n"); + vote(chip->pl_awake_votable, TAPER_END_VOTER, true, 0); + queue_work(system_long_wq, &chip->pl_taper_work); + } + return; + } + + /* handle fast/taper charge entry */ + if (pval.intval == POWER_SUPPLY_CHARGE_TYPE_TAPER + || pval.intval == POWER_SUPPLY_CHARGE_TYPE_FAST) { + /* + * Undo parallel charging termination if entered taper in + * reduced float voltage condition due to jeita mitigation. + */ + if (pval.intval == POWER_SUPPLY_CHARGE_TYPE_FAST && + (chip->taper_entry_fv < + get_effective_result(chip->fv_votable))) { + vote(chip->pl_disable_votable, TAPER_END_VOTER, + false, 0); + } + pl_dbg(chip, PR_PARALLEL, "chg_state enabling parallel\n"); + vote(chip->pl_disable_votable, CHG_STATE_VOTER, false, 0); + chip->charge_type = pval.intval; + return; + } + + /* remember the new state only if it isn't any of the above */ + chip->charge_type = pval.intval; +} + +#define MIN_ICL_CHANGE_DELTA_UA 300000 +static void handle_settled_icl_change(struct pl_data *chip) +{ + union power_supply_propval pval = {0, }; + int new_total_settled_ua; + int rc; + int main_settled_ua; + int main_limited; + int total_current_ua; + bool disable = false; + + total_current_ua = get_effective_result_locked(chip->usb_icl_votable); + + /* + * call aicl split only when USBIN_USBIN and enabled + * and if aicl changed + */ + rc = power_supply_get_property(chip->main_psy, + POWER_SUPPLY_PROP_INPUT_CURRENT_SETTLED, + &pval); + if (rc < 0) { + pr_err("Couldn't get aicl settled value rc=%d\n", rc); + return; + } + main_settled_ua = pval.intval; + + rc = power_supply_get_property(chip->batt_psy, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED, + &pval); + if (rc < 0) { + pr_err("Couldn't get aicl settled value rc=%d\n", rc); + return; + } + main_limited = pval.intval; + + if ((main_limited && (main_settled_ua + chip->pl_settled_ua) < 1400000) + || (main_settled_ua == 0) + || ((total_current_ua >= 0) && + (total_current_ua <= 1400000))) + vote(chip->pl_enable_votable_indirect, USBIN_I_VOTER, false, 0); + else + vote(chip->pl_enable_votable_indirect, USBIN_I_VOTER, true, 0); + + rerun_election(chip->fcc_votable); + + if (IS_USBIN(chip->pl_mode)) { + /* + * call aicl split only when USBIN_USBIN and enabled + * and if settled current has changed by more than 300mA + */ + + new_total_settled_ua = main_settled_ua + chip->pl_settled_ua; + pl_dbg(chip, PR_PARALLEL, + "total_settled_ua=%d settled_ua=%d new_total_settled_ua=%d\n", + chip->total_settled_ua, pval.intval, + new_total_settled_ua); + + /* If ICL change is small skip splitting */ + if (abs(new_total_settled_ua - chip->total_settled_ua) + > MIN_ICL_CHANGE_DELTA_UA) { + rc = validate_parallel_icl(chip, &disable); + if (rc < 0) + return; + + vote(chip->pl_disable_votable, ICL_LIMIT_VOTER, + disable, 0); + if (!get_effective_result_locked( + chip->pl_disable_votable)) + split_settled(chip); + } + } +} + +static void handle_parallel_in_taper(struct pl_data *chip) +{ + union power_supply_propval pval = {0, }; + int rc; + + if (get_effective_result_locked(chip->pl_disable_votable)) + return; + + if (!chip->pl_psy) + return; + + rc = power_supply_get_property(chip->pl_psy, + POWER_SUPPLY_PROP_CHARGE_TYPE, &pval); + if (rc < 0) { + pr_err("Couldn't get pl charge type rc=%d\n", rc); + return; + } + + /* + * if parallel is seen in taper mode ever, that is an anomaly and + * we disable parallel charger + */ + if (pval.intval == POWER_SUPPLY_CHARGE_TYPE_TAPER) { + vote(chip->pl_disable_votable, PL_TAPER_EARLY_BAD_VOTER, + true, 0); + return; + } +} + +static void handle_usb_change(struct pl_data *chip) +{ + int rc; + union power_supply_propval pval = {0, }; + + if (!chip->usb_psy) + chip->usb_psy = power_supply_get_by_name("usb"); + if (!chip->usb_psy) { + pr_err("Couldn't get usbpsy\n"); + return; + } + + rc = power_supply_get_property(chip->usb_psy, + POWER_SUPPLY_PROP_PRESENT, &pval); + if (rc < 0) { + pr_err("Couldn't get present from USB rc=%d\n", rc); + return; + } + + if (!pval.intval) { + /* USB removed: remove all stale votes */ + vote(chip->pl_disable_votable, TAPER_END_VOTER, false, 0); + vote(chip->pl_disable_votable, PL_TAPER_EARLY_BAD_VOTER, + false, 0); + vote(chip->pl_disable_votable, ICL_LIMIT_VOTER, false, 0); + chip->override_main_fcc_ua = 0; + chip->total_fcc_ua = 0; + chip->slave_fcc_ua = 0; + chip->main_fcc_ua = 0; + chip->charger_type = POWER_SUPPLY_TYPE_UNKNOWN; + } else { + rc = power_supply_get_property(chip->usb_psy, + POWER_SUPPLY_PROP_REAL_TYPE, &pval); + if (!rc) + chip->charger_type = pval.intval; + } +} + +static void status_change_work(struct work_struct *work) +{ + struct pl_data *chip = container_of(work, + struct pl_data, status_change_work.work); + + if (!chip->main_psy && is_main_available(chip)) { + /* + * re-run election for FCC/FV/ICL once main_psy + * is available to ensure all votes are reflected + * on hardware + */ + rerun_election(chip->usb_icl_votable); + rerun_election(chip->fcc_votable); + rerun_election(chip->fv_votable); + } + + if (!chip->main_psy) + return; + + if (!is_batt_available(chip)) + return; + + is_parallel_available(chip); + + handle_usb_change(chip); + handle_main_charge_type(chip); + handle_settled_icl_change(chip); + handle_parallel_in_taper(chip); +} + +static int pl_notifier_call(struct notifier_block *nb, + unsigned long ev, void *v) +{ + struct power_supply *psy = v; + struct pl_data *chip = container_of(nb, struct pl_data, nb); + + if (ev != PSY_EVENT_PROP_CHANGED) + return NOTIFY_OK; + + if ((strcmp(psy->desc->name, "parallel") == 0) + || (strcmp(psy->desc->name, "battery") == 0) + || (strcmp(psy->desc->name, "main") == 0)) + schedule_delayed_work(&chip->status_change_work, 0); + + return NOTIFY_OK; +} + +static int pl_register_notifier(struct pl_data *chip) +{ + int rc; + + chip->nb.notifier_call = pl_notifier_call; + rc = power_supply_reg_notifier(&chip->nb); + if (rc < 0) { + pr_err("Couldn't register psy notifier rc = %d\n", rc); + return rc; + } + + return 0; +} + +static int pl_determine_initial_status(struct pl_data *chip) +{ + status_change_work(&chip->status_change_work.work); + return 0; +} + +static void pl_config_init(struct pl_data *chip, int smb_version) +{ + switch (smb_version) { + case PMI8998_SUBTYPE: + case PM660_SUBTYPE: + chip->wa_flags = AICL_RERUN_WA_BIT | FORCE_INOV_DISABLE_BIT; + break; + default: + break; + } +} + +static void qcom_batt_create_debugfs(struct pl_data *chip) +{ + struct dentry *entry; + + chip->dfs_root = debugfs_create_dir("battery", NULL); + if (IS_ERR_OR_NULL(chip->dfs_root)) { + pr_err("Couldn't create battery debugfs rc=%ld\n", + (long)chip->dfs_root); + return; + } + + entry = debugfs_create_u32("debug_mask", 0600, chip->dfs_root, + &debug_mask); + if (IS_ERR_OR_NULL(entry)) + pr_err("Couldn't create force_dc_psy_update file rc=%ld\n", + (long)entry); +} + +#define DEFAULT_RESTRICTED_CURRENT_UA 1000000 +int qcom_batt_init(struct charger_param *chg_param) +{ + struct pl_data *chip; + int rc = 0; + + if (!chg_param) { + pr_err("invalid charger parameter\n"); + return -EINVAL; + } + + /* initialize just once */ + if (the_chip) { + pr_err("was initialized earlier. Failing now\n"); + return -EINVAL; + } + + chip = kzalloc(sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + qcom_batt_create_debugfs(chip); + + chip->slave_pct = 50; + chip->chg_param = chg_param; + pl_config_init(chip, chg_param->smb_version); + chip->restricted_current = DEFAULT_RESTRICTED_CURRENT_UA; + + chip->pl_ws = wakeup_source_register("qcom-battery"); + if (!chip->pl_ws) + goto cleanup; + + INIT_DELAYED_WORK(&chip->status_change_work, status_change_work); + INIT_WORK(&chip->pl_taper_work, pl_taper_work); + INIT_WORK(&chip->pl_disable_forever_work, pl_disable_forever_work); + INIT_DELAYED_WORK(&chip->fcc_stepper_work, fcc_stepper_work); + + chip->fcc_main_votable = create_votable("FCC_MAIN", VOTE_MIN, + pl_fcc_main_vote_callback, + chip); + if (IS_ERR(chip->fcc_main_votable)) { + rc = PTR_ERR(chip->fcc_main_votable); + chip->fcc_main_votable = NULL; + goto release_wakeup_source; + } + + chip->fcc_votable = create_votable("FCC", VOTE_MIN, + pl_fcc_vote_callback, + chip); + if (IS_ERR(chip->fcc_votable)) { + rc = PTR_ERR(chip->fcc_votable); + chip->fcc_votable = NULL; + goto destroy_votable; + } + + chip->fv_votable = create_votable("FV", VOTE_MIN, + pl_fv_vote_callback, + chip); + if (IS_ERR(chip->fv_votable)) { + rc = PTR_ERR(chip->fv_votable); + chip->fv_votable = NULL; + goto destroy_votable; + } + + chip->usb_icl_votable = create_votable("USB_ICL", VOTE_MIN, + usb_icl_vote_callback, + chip); + if (IS_ERR(chip->usb_icl_votable)) { + rc = PTR_ERR(chip->usb_icl_votable); + chip->usb_icl_votable = NULL; + goto destroy_votable; + } + + chip->pl_disable_votable = create_votable("PL_DISABLE", VOTE_SET_ANY, + pl_disable_vote_callback, + chip); + if (IS_ERR(chip->pl_disable_votable)) { + rc = PTR_ERR(chip->pl_disable_votable); + chip->pl_disable_votable = NULL; + goto destroy_votable; + } + vote(chip->pl_disable_votable, CHG_STATE_VOTER, true, 0); + vote(chip->pl_disable_votable, TAPER_END_VOTER, false, 0); + vote(chip->pl_disable_votable, PARALLEL_PSY_VOTER, true, 0); + + chip->pl_awake_votable = create_votable("PL_AWAKE", VOTE_SET_ANY, + pl_awake_vote_callback, + chip); + if (IS_ERR(chip->pl_awake_votable)) { + rc = PTR_ERR(chip->pl_awake_votable); + chip->pl_awake_votable = NULL; + goto destroy_votable; + } + + chip->pl_enable_votable_indirect = create_votable("PL_ENABLE_INDIRECT", + VOTE_SET_ANY, + pl_enable_indirect_vote_callback, + chip); + if (IS_ERR(chip->pl_enable_votable_indirect)) { + rc = PTR_ERR(chip->pl_enable_votable_indirect); + chip->pl_enable_votable_indirect = NULL; + goto destroy_votable; + } + + vote(chip->pl_disable_votable, PL_INDIRECT_VOTER, true, 0); + + rc = pl_register_notifier(chip); + if (rc < 0) { + pr_err("Couldn't register psy notifier rc = %d\n", rc); + goto unreg_notifier; + } + + rc = pl_determine_initial_status(chip); + if (rc < 0) { + pr_err("Couldn't determine initial status rc=%d\n", rc); + goto unreg_notifier; + } + + chip->pl_disable = true; + chip->cp_disabled = true; + chip->qcom_batt_class.name = "qcom-battery", + chip->qcom_batt_class.owner = THIS_MODULE, + chip->qcom_batt_class.class_groups = batt_class_groups; + + rc = class_register(&chip->qcom_batt_class); + if (rc < 0) { + pr_err("couldn't register pl_data sysfs class rc = %d\n", rc); + goto unreg_notifier; + } + + the_chip = chip; + + return 0; + +unreg_notifier: + power_supply_unreg_notifier(&chip->nb); +destroy_votable: + destroy_votable(chip->pl_enable_votable_indirect); + destroy_votable(chip->pl_awake_votable); + destroy_votable(chip->pl_disable_votable); + destroy_votable(chip->fv_votable); + destroy_votable(chip->fcc_votable); + destroy_votable(chip->fcc_main_votable); + destroy_votable(chip->usb_icl_votable); +release_wakeup_source: + wakeup_source_unregister(chip->pl_ws); +cleanup: + kfree(chip); + return rc; +} + +void qcom_batt_deinit(void) +{ + struct pl_data *chip = the_chip; + + if (chip == NULL) + return; + + cancel_delayed_work_sync(&chip->status_change_work); + cancel_work_sync(&chip->pl_taper_work); + cancel_work_sync(&chip->pl_disable_forever_work); + cancel_delayed_work_sync(&chip->fcc_stepper_work); + + power_supply_unreg_notifier(&chip->nb); + destroy_votable(chip->pl_enable_votable_indirect); + destroy_votable(chip->pl_awake_votable); + destroy_votable(chip->pl_disable_votable); + destroy_votable(chip->fv_votable); + destroy_votable(chip->fcc_votable); + destroy_votable(chip->fcc_main_votable); + wakeup_source_unregister(chip->pl_ws); + the_chip = NULL; + kfree(chip); +} diff --git a/drivers/power/supply/qcom/battery.h b/drivers/power/supply/qcom/battery.h new file mode 100644 index 000000000000..04163a96bb8a --- /dev/null +++ b/drivers/power/supply/qcom/battery.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2017, 2019-2020 The Linux Foundation. All rights reserved. + */ +#ifndef __BATTERY_H +#define __BATTERY_H + +struct charger_param { + u32 fcc_step_delay_ms; + u32 fcc_step_size_ua; + u32 smb_version; + u32 hvdcp2_max_icl_ua; + u32 hvdcp3_max_icl_ua; + u32 forced_main_fcc; +}; + +int qcom_batt_init(struct charger_param *param); +void qcom_batt_deinit(void); +#endif /* __BATTERY_H */ diff --git a/drivers/power/supply/qcom/qpnp-smb5.c b/drivers/power/supply/qcom/qpnp-smb5.c new file mode 100644 index 000000000000..89f0911dab08 --- /dev/null +++ b/drivers/power/supply/qcom/qpnp-smb5.c @@ -0,0 +1,3780 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2018-2020 The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "smb5-reg.h" +#include "smb5-lib.h" +#include "schgm-flash.h" + +static struct smb_params smb5_pmi632_params = { + .fcc = { + .name = "fast charge current", + .reg = CHGR_FAST_CHARGE_CURRENT_CFG_REG, + .min_u = 0, + .max_u = 3000000, + .step_u = 50000, + }, + .fv = { + .name = "float voltage", + .reg = CHGR_FLOAT_VOLTAGE_CFG_REG, + .min_u = 3600000, + .max_u = 4800000, + .step_u = 10000, + }, + .usb_icl = { + .name = "usb input current limit", + .reg = USBIN_CURRENT_LIMIT_CFG_REG, + .min_u = 0, + .max_u = 3000000, + .step_u = 50000, + }, + .icl_max_stat = { + .name = "dcdc icl max status", + .reg = ICL_MAX_STATUS_REG, + .min_u = 0, + .max_u = 3000000, + .step_u = 50000, + }, + .icl_stat = { + .name = "input current limit status", + .reg = ICL_STATUS_REG, + .min_u = 0, + .max_u = 3000000, + .step_u = 50000, + }, + .otg_cl = { + .name = "usb otg current limit", + .reg = DCDC_OTG_CURRENT_LIMIT_CFG_REG, + .min_u = 500000, + .max_u = 1000000, + .step_u = 250000, + }, + .jeita_cc_comp_hot = { + .name = "jeita fcc reduction", + .reg = JEITA_CCCOMP_CFG_HOT_REG, + .min_u = 0, + .max_u = 1575000, + .step_u = 25000, + }, + .jeita_cc_comp_cold = { + .name = "jeita fcc reduction", + .reg = JEITA_CCCOMP_CFG_COLD_REG, + .min_u = 0, + .max_u = 1575000, + .step_u = 25000, + }, + .freq_switcher = { + .name = "switching frequency", + .reg = DCDC_FSW_SEL_REG, + .min_u = 600, + .max_u = 1200, + .step_u = 400, + .set_proc = smblib_set_chg_freq, + }, + .aicl_5v_threshold = { + .name = "AICL 5V threshold", + .reg = USBIN_5V_AICL_THRESHOLD_REG, + .min_u = 4000, + .max_u = 4700, + .step_u = 100, + }, + .aicl_cont_threshold = { + .name = "AICL CONT threshold", + .reg = USBIN_CONT_AICL_THRESHOLD_REG, + .min_u = 4000, + .max_u = 8800, + .step_u = 100, + .get_proc = smblib_get_aicl_cont_threshold, + .set_proc = smblib_set_aicl_cont_threshold, + }, +}; + +static struct smb_params smb5_pm8150b_params = { + .fcc = { + .name = "fast charge current", + .reg = CHGR_FAST_CHARGE_CURRENT_CFG_REG, + .min_u = 0, + .max_u = 8000000, + .step_u = 50000, + }, + .fv = { + .name = "float voltage", + .reg = CHGR_FLOAT_VOLTAGE_CFG_REG, + .min_u = 3600000, + .max_u = 4790000, + .step_u = 10000, + }, + .usb_icl = { + .name = "usb input current limit", + .reg = USBIN_CURRENT_LIMIT_CFG_REG, + .min_u = 0, + .max_u = 5000000, + .step_u = 50000, + }, + .icl_max_stat = { + .name = "dcdc icl max status", + .reg = ICL_MAX_STATUS_REG, + .min_u = 0, + .max_u = 5000000, + .step_u = 50000, + }, + .icl_stat = { + .name = "aicl icl status", + .reg = AICL_ICL_STATUS_REG, + .min_u = 0, + .max_u = 5000000, + .step_u = 50000, + }, + .otg_cl = { + .name = "usb otg current limit", + .reg = DCDC_OTG_CURRENT_LIMIT_CFG_REG, + .min_u = 500000, + .max_u = 3000000, + .step_u = 500000, + }, + .dc_icl = { + .name = "DC input current limit", + .reg = DCDC_CFG_REF_MAX_PSNS_REG, + .min_u = 0, + .max_u = DCIN_ICL_MAX_UA, + .step_u = 50000, + }, + .jeita_cc_comp_hot = { + .name = "jeita fcc reduction", + .reg = JEITA_CCCOMP_CFG_HOT_REG, + .min_u = 0, + .max_u = 8000000, + .step_u = 25000, + .set_proc = NULL, + }, + .jeita_cc_comp_cold = { + .name = "jeita fcc reduction", + .reg = JEITA_CCCOMP_CFG_COLD_REG, + .min_u = 0, + .max_u = 8000000, + .step_u = 25000, + .set_proc = NULL, + }, + .freq_switcher = { + .name = "switching frequency", + .reg = DCDC_FSW_SEL_REG, + .min_u = 600, + .max_u = 1200, + .step_u = 400, + .set_proc = smblib_set_chg_freq, + }, + .aicl_5v_threshold = { + .name = "AICL 5V threshold", + .reg = USBIN_5V_AICL_THRESHOLD_REG, + .min_u = 4000, + .max_u = 4700, + .step_u = 100, + }, + .aicl_cont_threshold = { + .name = "AICL CONT threshold", + .reg = USBIN_CONT_AICL_THRESHOLD_REG, + .min_u = 4000, + .max_u = 11800, + .step_u = 100, + .get_proc = smblib_get_aicl_cont_threshold, + .set_proc = smblib_set_aicl_cont_threshold, + }, +}; + +struct smb_dt_props { + int usb_icl_ua; + struct device_node *revid_dev_node; + enum float_options float_option; + int chg_inhibit_thr_mv; + bool no_battery; + bool hvdcp_disable; + bool hvdcp_autonomous; + bool adc_based_aicl; + int sec_charger_config; + int auto_recharge_soc; + int auto_recharge_vbat_mv; + int wd_bark_time; + int wd_snarl_time_cfg; + int batt_profile_fcc_ua; + int batt_profile_fv_uv; + int term_current_src; + int term_current_thresh_hi_ma; + int term_current_thresh_lo_ma; + int disable_suspend_on_collapse; +}; + +struct smb5 { + struct smb_charger chg; + struct dentry *dfs_root; + struct smb_dt_props dt; +}; + +static int __debug_mask; + +static ssize_t pd_disabled_show(struct device *dev, struct device_attribute + *attr, char *buf) +{ + struct smb5 *chip = dev_get_drvdata(dev); + struct smb_charger *chg = &chip->chg; + + return scnprintf(buf, PAGE_SIZE, "%d\n", chg->pd_disabled); +} + +static ssize_t pd_disabled_store(struct device *dev, struct device_attribute + *attr, const char *buf, size_t count) +{ + int val; + struct smb5 *chip = dev_get_drvdata(dev); + struct smb_charger *chg = &chip->chg; + + if (kstrtos32(buf, 0, &val)) + return -EINVAL; + + chg->pd_disabled = val; + + return count; +} +static DEVICE_ATTR_RW(pd_disabled); + +static ssize_t weak_chg_icl_ua_show(struct device *dev, struct device_attribute + *attr, char *buf) +{ + struct smb5 *chip = dev_get_drvdata(dev); + struct smb_charger *chg = &chip->chg; + + return scnprintf(buf, PAGE_SIZE, "%d\n", chg->weak_chg_icl_ua); +} + +static ssize_t weak_chg_icl_ua_store(struct device *dev, struct device_attribute + *attr, const char *buf, size_t count) +{ + int val; + struct smb5 *chip = dev_get_drvdata(dev); + struct smb_charger *chg = &chip->chg; + + if (kstrtos32(buf, 0, &val)) + return -EINVAL; + + chg->weak_chg_icl_ua = val; + + return count; +} +static DEVICE_ATTR_RW(weak_chg_icl_ua); + +static struct attribute *smb5_attrs[] = { + &dev_attr_pd_disabled.attr, + &dev_attr_weak_chg_icl_ua.attr, + NULL, +}; +ATTRIBUTE_GROUPS(smb5); + +enum { + BAT_THERM = 0, + MISC_THERM, + CONN_THERM, + SMB_THERM, +}; + +static const struct clamp_config clamp_levels[] = { + { {0x11C6, 0x11F9, 0x13F1}, {0x60, 0x2E, 0x90} }, + { {0x11C6, 0x11F9, 0x13F1}, {0x60, 0x2B, 0x9C} }, +}; + +#define PMI632_MAX_ICL_UA 3000000 +#define PM6150_MAX_FCC_UA 3000000 +static int smb5_chg_config_init(struct smb5 *chip) +{ + struct smb_charger *chg = &chip->chg; + struct pmic_revid_data *pmic_rev_id; + struct device_node *revid_dev_node, *node = chg->dev->of_node; + int rc = 0; + + revid_dev_node = of_parse_phandle(node, "qcom,pmic-revid", 0); + if (!revid_dev_node) { + pr_err("Missing qcom,pmic-revid property\n"); + return -EINVAL; + } + + pmic_rev_id = get_revid_data(revid_dev_node); + if (IS_ERR_OR_NULL(pmic_rev_id)) { + /* + * the revid peripheral must be registered, any failure + * here only indicates that the rev-id module has not + * probed yet. + */ + rc = -EPROBE_DEFER; + goto out; + } + + switch (pmic_rev_id->pmic_subtype) { + case PM8150B_SUBTYPE: + chip->chg.chg_param.smb_version = PM8150B_SUBTYPE; + chg->param = smb5_pm8150b_params; + chg->name = "pm8150b_charger"; + chg->wa_flags |= CHG_TERMINATION_WA; + break; + case PM7250B_SUBTYPE: + chip->chg.chg_param.smb_version = PM7250B_SUBTYPE; + chg->param = smb5_pm8150b_params; + chg->name = "pm7250b_charger"; + chg->wa_flags |= CHG_TERMINATION_WA; + chg->uusb_moisture_protection_capable = true; + break; + case PM6150_SUBTYPE: + chip->chg.chg_param.smb_version = PM6150_SUBTYPE; + chg->param = smb5_pm8150b_params; + chg->name = "pm6150_charger"; + chg->wa_flags |= SW_THERM_REGULATION_WA | CHG_TERMINATION_WA; + if (pmic_rev_id->rev4 >= 2) + chg->uusb_moisture_protection_capable = true; + chg->main_fcc_max = PM6150_MAX_FCC_UA; + break; + case PMI632_SUBTYPE: + chip->chg.chg_param.smb_version = PMI632_SUBTYPE; + chg->wa_flags |= WEAK_ADAPTER_WA | USBIN_OV_WA + | CHG_TERMINATION_WA | USBIN_ADC_WA + | SKIP_MISC_PBS_IRQ_WA; + chg->param = smb5_pmi632_params; + chg->use_extcon = true; + chg->name = "pmi632_charger"; + /* PMI632 does not support PD */ + chg->pd_not_supported = true; + chg->lpd_disabled = true; + if (pmic_rev_id->rev4 >= 2) + chg->uusb_moisture_protection_enabled = true; + chg->hw_max_icl_ua = + (chip->dt.usb_icl_ua > 0) ? chip->dt.usb_icl_ua + : PMI632_MAX_ICL_UA; + break; + default: + pr_err("PMIC subtype %d not supported\n", + pmic_rev_id->pmic_subtype); + rc = -EINVAL; + goto out; + } + + chg->chg_freq.freq_5V = 600; + chg->chg_freq.freq_6V_8V = 800; + chg->chg_freq.freq_9V = 1050; + chg->chg_freq.freq_12V = 1200; + chg->chg_freq.freq_removal = 1050; + chg->chg_freq.freq_below_otg_threshold = 800; + chg->chg_freq.freq_above_otg_threshold = 800; + + if (of_property_read_bool(node, "qcom,disable-sw-thermal-regulation")) + chg->wa_flags &= ~SW_THERM_REGULATION_WA; + + if (of_property_read_bool(node, "qcom,disable-fcc-restriction")) + chg->main_fcc_max = -EINVAL; + +out: + of_node_put(revid_dev_node); + return rc; +} + +#define PULL_NO_PULL 0 +#define PULL_30K 30 +#define PULL_100K 100 +#define PULL_400K 400 +static int get_valid_pullup(int pull_up) +{ + /* pull up can only be 0/30K/100K/400K) */ + switch (pull_up) { + case PULL_NO_PULL: + return INTERNAL_PULL_NO_PULL; + case PULL_30K: + return INTERNAL_PULL_30K_PULL; + case PULL_100K: + return INTERNAL_PULL_100K_PULL; + case PULL_400K: + return INTERNAL_PULL_400K_PULL; + default: + return INTERNAL_PULL_100K_PULL; + } +} + +#define INTERNAL_PULL_UP_MASK 0x3 +static int smb5_configure_internal_pull(struct smb_charger *chg, int type, + int pull) +{ + int rc; + int shift = type * 2; + u8 mask = INTERNAL_PULL_UP_MASK << shift; + u8 val = pull << shift; + + rc = smblib_masked_write(chg, BATIF_ADC_INTERNAL_PULL_UP_REG, + mask, val); + if (rc < 0) + dev_err(chg->dev, + "Couldn't configure ADC pull-up reg rc=%d\n", rc); + + return rc; +} + +#define MICRO_1P5A 1500000 +#define MICRO_P1A 100000 +#define MICRO_1PA 1000000 +#define MICRO_3PA 3000000 +#define OTG_DEFAULT_DEGLITCH_TIME_MS 50 +#define DEFAULT_WD_BARK_TIME 64 +#define DEFAULT_WD_SNARL_TIME_8S 0x07 +#define DEFAULT_FCC_STEP_SIZE_UA 100000 +#define DEFAULT_FCC_STEP_UPDATE_DELAY_MS 1000 +static int smb5_parse_dt_misc(struct smb5 *chip, struct device_node *node) +{ + int rc = 0, byte_len; + struct smb_charger *chg = &chip->chg; + + of_property_read_u32(node, "qcom,sec-charger-config", + &chip->dt.sec_charger_config); + chg->sec_cp_present = + chip->dt.sec_charger_config == POWER_SUPPLY_CHARGER_SEC_CP || + chip->dt.sec_charger_config == POWER_SUPPLY_CHARGER_SEC_CP_PL; + + chg->sec_pl_present = + chip->dt.sec_charger_config == POWER_SUPPLY_CHARGER_SEC_PL || + chip->dt.sec_charger_config == POWER_SUPPLY_CHARGER_SEC_CP_PL; + + chg->step_chg_enabled = of_property_read_bool(node, + "qcom,step-charging-enable"); + + chg->typec_legacy_use_rp_icl = of_property_read_bool(node, + "qcom,typec-legacy-rp-icl"); + + chg->sw_jeita_enabled = of_property_read_bool(node, + "qcom,sw-jeita-enable"); + + chg->pd_not_supported = chg->pd_not_supported || + of_property_read_bool(node, "qcom,usb-pd-disable"); + + chg->lpd_disabled = of_property_read_bool(node, "qcom,lpd-disable"); + + rc = of_property_read_u32(node, "qcom,wd-bark-time-secs", + &chip->dt.wd_bark_time); + if (rc < 0 || chip->dt.wd_bark_time < MIN_WD_BARK_TIME) + chip->dt.wd_bark_time = DEFAULT_WD_BARK_TIME; + + rc = of_property_read_u32(node, "qcom,wd-snarl-time-config", + &chip->dt.wd_snarl_time_cfg); + if (rc < 0) + chip->dt.wd_snarl_time_cfg = DEFAULT_WD_SNARL_TIME_8S; + + chip->dt.no_battery = of_property_read_bool(node, + "qcom,batteryless-platform"); + + if (of_find_property(node, "qcom,thermal-mitigation", &byte_len)) { + chg->thermal_mitigation = devm_kzalloc(chg->dev, byte_len, + GFP_KERNEL); + + if (chg->thermal_mitigation == NULL) + return -ENOMEM; + + chg->thermal_levels = byte_len / sizeof(u32); + rc = of_property_read_u32_array(node, + "qcom,thermal-mitigation", + chg->thermal_mitigation, + chg->thermal_levels); + if (rc < 0) { + dev_err(chg->dev, + "Couldn't read threm limits rc = %d\n", rc); + return rc; + } + } + + rc = of_property_read_u32(node, "qcom,charger-temp-max", + &chg->charger_temp_max); + if (rc < 0) + chg->charger_temp_max = -EINVAL; + + rc = of_property_read_u32(node, "qcom,smb-temp-max", + &chg->smb_temp_max); + if (rc < 0) + chg->smb_temp_max = -EINVAL; + + rc = of_property_read_u32(node, "qcom,float-option", + &chip->dt.float_option); + if (!rc && (chip->dt.float_option < 0 || chip->dt.float_option > 4)) { + pr_err("qcom,float-option is out of range [0, 4]\n"); + return -EINVAL; + } + + chip->dt.hvdcp_disable = of_property_read_bool(node, + "qcom,hvdcp-disable"); + chg->hvdcp_disable = chip->dt.hvdcp_disable; + + chip->dt.hvdcp_autonomous = of_property_read_bool(node, + "qcom,hvdcp-autonomous-enable"); + + chip->dt.auto_recharge_soc = -EINVAL; + rc = of_property_read_u32(node, "qcom,auto-recharge-soc", + &chip->dt.auto_recharge_soc); + if (!rc && (chip->dt.auto_recharge_soc < 0 || + chip->dt.auto_recharge_soc > 100)) { + pr_err("qcom,auto-recharge-soc is incorrect\n"); + return -EINVAL; + } + chg->auto_recharge_soc = chip->dt.auto_recharge_soc; + + chg->suspend_input_on_debug_batt = of_property_read_bool(node, + "qcom,suspend-input-on-debug-batt"); + + chg->fake_chg_status_on_debug_batt = of_property_read_bool(node, + "qcom,fake-chg-status-on-debug-batt"); + + rc = of_property_read_u32(node, "qcom,otg-deglitch-time-ms", + &chg->otg_delay_ms); + if (rc < 0) + chg->otg_delay_ms = OTG_DEFAULT_DEGLITCH_TIME_MS; + + chg->fcc_stepper_enable = of_property_read_bool(node, + "qcom,fcc-stepping-enable"); + + if (chg->uusb_moisture_protection_capable) + chg->uusb_moisture_protection_enabled = + of_property_read_bool(node, + "qcom,uusb-moisture-protection-enable"); + + chg->hw_die_temp_mitigation = of_property_read_bool(node, + "qcom,hw-die-temp-mitigation"); + + chg->hw_connector_mitigation = of_property_read_bool(node, + "qcom,hw-connector-mitigation"); + + chg->hw_skin_temp_mitigation = of_property_read_bool(node, + "qcom,hw-skin-temp-mitigation"); + + chg->en_skin_therm_mitigation = of_property_read_bool(node, + "qcom,en-skin-therm-mitigation"); + + chg->connector_pull_up = -EINVAL; + of_property_read_u32(node, "qcom,connector-internal-pull-kohm", + &chg->connector_pull_up); + + chip->dt.disable_suspend_on_collapse = of_property_read_bool(node, + "qcom,disable-suspend-on-collapse"); + chg->smb_pull_up = -EINVAL; + of_property_read_u32(node, "qcom,smb-internal-pull-kohm", + &chg->smb_pull_up); + + chip->dt.adc_based_aicl = of_property_read_bool(node, + "qcom,adc-based-aicl"); + + of_property_read_u32(node, "qcom,fcc-step-delay-ms", + &chg->chg_param.fcc_step_delay_ms); + if (chg->chg_param.fcc_step_delay_ms <= 0) + chg->chg_param.fcc_step_delay_ms = + DEFAULT_FCC_STEP_UPDATE_DELAY_MS; + + of_property_read_u32(node, "qcom,fcc-step-size-ua", + &chg->chg_param.fcc_step_size_ua); + if (chg->chg_param.fcc_step_size_ua <= 0) + chg->chg_param.fcc_step_size_ua = DEFAULT_FCC_STEP_SIZE_UA; + + /* + * If property is present parallel charging with CP is disabled + * with HVDCP3 adapter. + */ + chg->hvdcp3_standalone_config = of_property_read_bool(node, + "qcom,hvdcp3-standalone-config"); + + of_property_read_u32(node, "qcom,hvdcp3-max-icl-ua", + &chg->chg_param.hvdcp3_max_icl_ua); + if (chg->chg_param.hvdcp3_max_icl_ua <= 0) + chg->chg_param.hvdcp3_max_icl_ua = MICRO_3PA; + + of_property_read_u32(node, "qcom,hvdcp2-max-icl-ua", + &chg->chg_param.hvdcp2_max_icl_ua); + if (chg->chg_param.hvdcp2_max_icl_ua <= 0) + chg->chg_param.hvdcp2_max_icl_ua = MICRO_3PA; + + return 0; +} + +static int smb5_parse_dt_adc_channels(struct smb_charger *chg) +{ + int rc = 0; + + rc = smblib_get_iio_channel(chg, "mid_voltage", &chg->iio.mid_chan); + if (rc < 0) + return rc; + + rc = smblib_get_iio_channel(chg, "usb_in_voltage", + &chg->iio.usbin_v_chan); + if (rc < 0) + return rc; + + rc = smblib_get_iio_channel(chg, "chg_temp", &chg->iio.temp_chan); + if (rc < 0) + return rc; + + rc = smblib_get_iio_channel(chg, "usb_in_current", + &chg->iio.usbin_i_chan); + if (rc < 0) + return rc; + + rc = smblib_get_iio_channel(chg, "sbux_res", &chg->iio.sbux_chan); + if (rc < 0) + return rc; + + rc = smblib_get_iio_channel(chg, "vph_voltage", &chg->iio.vph_v_chan); + if (rc < 0) + return rc; + + rc = smblib_get_iio_channel(chg, "die_temp", &chg->iio.die_temp_chan); + if (rc < 0) + return rc; + + rc = smblib_get_iio_channel(chg, "conn_temp", + &chg->iio.connector_temp_chan); + if (rc < 0) + return rc; + + rc = smblib_get_iio_channel(chg, "skin_temp", &chg->iio.skin_temp_chan); + if (rc < 0) + return rc; + + rc = smblib_get_iio_channel(chg, "smb_temp", &chg->iio.smb_temp_chan); + if (rc < 0) + return rc; + + return 0; +} + +static int smb5_parse_dt_currents(struct smb5 *chip, struct device_node *node) +{ + int rc = 0, tmp; + struct smb_charger *chg = &chip->chg; + + rc = of_property_read_u32(node, + "qcom,fcc-max-ua", &chip->dt.batt_profile_fcc_ua); + if (rc < 0) + chip->dt.batt_profile_fcc_ua = -EINVAL; + + rc = of_property_read_u32(node, + "qcom,usb-icl-ua", &chip->dt.usb_icl_ua); + if (rc < 0) + chip->dt.usb_icl_ua = -EINVAL; + chg->dcp_icl_ua = chip->dt.usb_icl_ua; + + rc = of_property_read_u32(node, + "qcom,otg-cl-ua", &chg->otg_cl_ua); + if (rc < 0) + chg->otg_cl_ua = + (chip->chg.chg_param.smb_version == PMI632_SUBTYPE) ? + MICRO_1PA : MICRO_3PA; + + rc = of_property_read_u32(node, "qcom,chg-term-src", + &chip->dt.term_current_src); + if (rc < 0) + chip->dt.term_current_src = ITERM_SRC_UNSPECIFIED; + + if (chip->dt.term_current_src == ITERM_SRC_ADC) + rc = of_property_read_u32(node, "qcom,chg-term-base-current-ma", + &chip->dt.term_current_thresh_lo_ma); + + rc = of_property_read_u32(node, "qcom,chg-term-current-ma", + &chip->dt.term_current_thresh_hi_ma); + + chg->wls_icl_ua = DCIN_ICL_MAX_UA; + rc = of_property_read_u32(node, "qcom,wls-current-max-ua", + &tmp); + if (!rc && tmp < DCIN_ICL_MAX_UA) + chg->wls_icl_ua = tmp; + + return 0; +} + +static int smb5_parse_dt_voltages(struct smb5 *chip, struct device_node *node) +{ + int rc = 0; + + rc = of_property_read_u32(node, + "qcom,fv-max-uv", &chip->dt.batt_profile_fv_uv); + if (rc < 0) + chip->dt.batt_profile_fv_uv = -EINVAL; + + rc = of_property_read_u32(node, "qcom,chg-inhibit-threshold-mv", + &chip->dt.chg_inhibit_thr_mv); + if (!rc && (chip->dt.chg_inhibit_thr_mv < 0 || + chip->dt.chg_inhibit_thr_mv > 300)) { + pr_err("qcom,chg-inhibit-threshold-mv is incorrect\n"); + return -EINVAL; + } + + chip->dt.auto_recharge_vbat_mv = -EINVAL; + rc = of_property_read_u32(node, "qcom,auto-recharge-vbat-mv", + &chip->dt.auto_recharge_vbat_mv); + if (!rc && (chip->dt.auto_recharge_vbat_mv < 0)) { + pr_err("qcom,auto-recharge-vbat-mv is incorrect\n"); + return -EINVAL; + } + + return 0; +} + +static int smb5_parse_sdam(struct smb5 *chip, struct device_node *node) +{ + struct device_node *child; + struct smb_charger *chg = &chip->chg; + struct property *prop; + const char *name; + int rc; + u32 base; + u8 type; + + for_each_available_child_of_node(node, child) { + of_property_for_each_string(child, "reg", prop, name) { + rc = of_property_read_u32(child, "reg", &base); + if (rc < 0) { + pr_err("Failed to read base rc=%d\n", rc); + return rc; + } + + rc = smblib_read(chg, base + PERPH_TYPE_OFFSET, &type); + if (rc < 0) { + pr_err("Failed to read type rc=%d\n", rc); + return rc; + } + + switch (type) { + case SDAM_TYPE: + chg->sdam_base = base; + break; + default: + break; + } + } + } + + if (!chg->sdam_base) + pr_debug("SDAM node not defined\n"); + + return 0; +} + +static int smb5_parse_dt(struct smb5 *chip) +{ + struct smb_charger *chg = &chip->chg; + struct device_node *node = chg->dev->of_node; + int rc = 0; + + if (!node) { + pr_err("device tree node missing\n"); + return -EINVAL; + } + + rc = smb5_parse_dt_voltages(chip, node); + if (rc < 0) + return rc; + + rc = smb5_parse_dt_currents(chip, node); + if (rc < 0) + return rc; + + rc = smb5_parse_dt_adc_channels(chg); + if (rc < 0) + return rc; + + rc = smb5_parse_dt_misc(chip, node); + if (rc < 0) + return rc; + + rc = smb5_parse_sdam(chip, node); + if (rc < 0) + return rc; + + return 0; +} + +static int smb5_set_prop_comp_clamp_level(struct smb_charger *chg, + const union power_supply_propval *val) +{ + int rc = 0, i; + struct clamp_config clamp_config; + enum comp_clamp_levels level; + + level = val->intval; + if (level >= MAX_CLAMP_LEVEL) { + pr_err("Invalid comp clamp level=%d\n", val->intval); + return -EINVAL; + } + + for (i = 0; i < ARRAY_SIZE(clamp_config.reg); i++) { + rc = smblib_write(chg, clamp_levels[level].reg[i], + clamp_levels[level].val[i]); + if (rc < 0) + dev_err(chg->dev, + "Failed to configure comp clamp settings for reg=0x%04x rc=%d\n", + clamp_levels[level].reg[i], rc); + } + + chg->comp_clamp_level = val->intval; + + return rc; +} + +/************************ + * USB PSY REGISTRATION * + ************************/ +static enum power_supply_property smb5_usb_props[] = { + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_PD_CURRENT_MAX, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_TYPE, + POWER_SUPPLY_PROP_TYPEC_MODE, + POWER_SUPPLY_PROP_TYPEC_POWER_ROLE, + POWER_SUPPLY_PROP_TYPEC_CC_ORIENTATION, + POWER_SUPPLY_PROP_LOW_POWER, + POWER_SUPPLY_PROP_PD_ACTIVE, + POWER_SUPPLY_PROP_INPUT_CURRENT_SETTLED, + POWER_SUPPLY_PROP_INPUT_CURRENT_NOW, + POWER_SUPPLY_PROP_BOOST_CURRENT, + POWER_SUPPLY_PROP_PE_START, + POWER_SUPPLY_PROP_CTM_CURRENT_MAX, + POWER_SUPPLY_PROP_HW_CURRENT_MAX, + POWER_SUPPLY_PROP_REAL_TYPE, + POWER_SUPPLY_PROP_PD_VOLTAGE_MAX, + POWER_SUPPLY_PROP_PD_VOLTAGE_MIN, + POWER_SUPPLY_PROP_CONNECTOR_TYPE, + POWER_SUPPLY_PROP_CONNECTOR_HEALTH, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_MAX_LIMIT, + POWER_SUPPLY_PROP_SMB_EN_MODE, + POWER_SUPPLY_PROP_SMB_EN_REASON, + POWER_SUPPLY_PROP_ADAPTER_CC_MODE, + POWER_SUPPLY_PROP_SCOPE, + POWER_SUPPLY_PROP_MOISTURE_DETECTED, + POWER_SUPPLY_PROP_HVDCP_OPTI_ALLOWED, + POWER_SUPPLY_PROP_QC_OPTI_DISABLE, + POWER_SUPPLY_PROP_VOLTAGE_VPH, + POWER_SUPPLY_PROP_THERM_ICL_LIMIT, + POWER_SUPPLY_PROP_SKIN_HEALTH, + POWER_SUPPLY_PROP_APSD_RERUN, + POWER_SUPPLY_PROP_APSD_TIMEOUT, + POWER_SUPPLY_PROP_CHARGER_STATUS, + POWER_SUPPLY_PROP_INPUT_VOLTAGE_SETTLED, +}; + +static int smb5_usb_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct smb5 *chip = power_supply_get_drvdata(psy); + struct smb_charger *chg = &chip->chg; + int rc = 0; + u8 reg = 0, buff[2] = {0}; + + val->intval = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_PRESENT: + rc = smblib_get_prop_usb_present(chg, val); + break; + case POWER_SUPPLY_PROP_ONLINE: + rc = smblib_get_usb_online(chg, val); + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + rc = smblib_get_prop_usb_voltage_max_design(chg, val); + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + rc = smblib_get_prop_usb_voltage_max(chg, val); + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_LIMIT: + if (chg->usbin_forced_max_uv) + val->intval = chg->usbin_forced_max_uv; + else + smblib_get_prop_usb_voltage_max_design(chg, val); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + rc = smblib_get_prop_usb_voltage_now(chg, val); + break; + case POWER_SUPPLY_PROP_PD_CURRENT_MAX: + val->intval = get_client_vote(chg->usb_icl_votable, PD_VOTER); + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + rc = smblib_get_prop_input_current_settled(chg, val); + break; + case POWER_SUPPLY_PROP_TYPE: + val->intval = POWER_SUPPLY_TYPE_USB_PD; + break; + case POWER_SUPPLY_PROP_REAL_TYPE: + val->intval = chg->real_charger_type; + break; + case POWER_SUPPLY_PROP_TYPEC_MODE: + rc = smblib_get_usb_prop_typec_mode(chg, val); + break; + case POWER_SUPPLY_PROP_TYPEC_POWER_ROLE: + rc = smblib_get_prop_typec_power_role(chg, val); + break; + case POWER_SUPPLY_PROP_TYPEC_CC_ORIENTATION: + rc = smblib_get_prop_typec_cc_orientation(chg, val); + break; + case POWER_SUPPLY_PROP_TYPEC_SRC_RP: + rc = smblib_get_prop_typec_select_rp(chg, val); + break; + case POWER_SUPPLY_PROP_LOW_POWER: + rc = smblib_get_prop_low_power(chg, val); + break; + case POWER_SUPPLY_PROP_PD_ACTIVE: + val->intval = chg->pd_active; + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_SETTLED: + rc = smblib_get_prop_input_current_settled(chg, val); + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_NOW: + rc = smblib_get_prop_usb_current_now(chg, val); + break; + case POWER_SUPPLY_PROP_BOOST_CURRENT: + val->intval = chg->boost_current_ua; + break; + case POWER_SUPPLY_PROP_PD_IN_HARD_RESET: + rc = smblib_get_prop_pd_in_hard_reset(chg, val); + break; + case POWER_SUPPLY_PROP_PD_USB_SUSPEND_SUPPORTED: + val->intval = chg->system_suspend_supported; + break; + case POWER_SUPPLY_PROP_PE_START: + rc = smblib_get_pe_start(chg, val); + break; + case POWER_SUPPLY_PROP_CTM_CURRENT_MAX: + val->intval = get_client_vote(chg->usb_icl_votable, CTM_VOTER); + break; + case POWER_SUPPLY_PROP_HW_CURRENT_MAX: + rc = smblib_get_charge_current(chg, &val->intval); + break; + case POWER_SUPPLY_PROP_PR_SWAP: + rc = smblib_get_prop_pr_swap_in_progress(chg, val); + break; + case POWER_SUPPLY_PROP_PD_VOLTAGE_MAX: + val->intval = chg->voltage_max_uv; + break; + case POWER_SUPPLY_PROP_PD_VOLTAGE_MIN: + val->intval = chg->voltage_min_uv; + break; + case POWER_SUPPLY_PROP_SDP_CURRENT_MAX: + val->intval = get_client_vote(chg->usb_icl_votable, + USB_PSY_VOTER); + break; + case POWER_SUPPLY_PROP_CONNECTOR_TYPE: + val->intval = chg->connector_type; + break; + case POWER_SUPPLY_PROP_CONNECTOR_HEALTH: + val->intval = smblib_get_prop_connector_health(chg); + break; + case POWER_SUPPLY_PROP_SCOPE: + rc = smblib_get_prop_scope(chg, val); + break; + case POWER_SUPPLY_PROP_SMB_EN_MODE: + mutex_lock(&chg->smb_lock); + val->intval = chg->sec_chg_selected; + mutex_unlock(&chg->smb_lock); + break; + case POWER_SUPPLY_PROP_SMB_EN_REASON: + val->intval = chg->cp_reason; + break; + case POWER_SUPPLY_PROP_MOISTURE_DETECTED: + val->intval = chg->moisture_present; + break; + case POWER_SUPPLY_PROP_HVDCP_OPTI_ALLOWED: + val->intval = !chg->flash_active; + break; + case POWER_SUPPLY_PROP_QC_OPTI_DISABLE: + if (chg->hw_die_temp_mitigation) + val->intval = POWER_SUPPLY_QC_THERMAL_BALANCE_DISABLE + | POWER_SUPPLY_QC_INOV_THERMAL_DISABLE; + if (chg->hw_connector_mitigation) + val->intval |= POWER_SUPPLY_QC_CTM_DISABLE; + break; + case POWER_SUPPLY_PROP_VOLTAGE_VPH: + rc = smblib_get_prop_vph_voltage_now(chg, val); + break; + case POWER_SUPPLY_PROP_THERM_ICL_LIMIT: + val->intval = get_client_vote(chg->usb_icl_votable, + THERMAL_THROTTLE_VOTER); + break; + case POWER_SUPPLY_PROP_ADAPTER_CC_MODE: + val->intval = chg->adapter_cc_mode; + break; + case POWER_SUPPLY_PROP_SKIN_HEALTH: + val->intval = smblib_get_skin_temp_status(chg); + break; + case POWER_SUPPLY_PROP_APSD_RERUN: + val->intval = 0; + break; + case POWER_SUPPLY_PROP_APSD_TIMEOUT: + val->intval = chg->apsd_ext_timeout; + break; + case POWER_SUPPLY_PROP_CHARGER_STATUS: + val->intval = 0; + if (chg->sdam_base) { + rc = smblib_read(chg, + chg->sdam_base + SDAM_QC_DET_STATUS_REG, ®); + if (!rc) + val->intval = reg; + } + break; + case POWER_SUPPLY_PROP_INPUT_VOLTAGE_SETTLED: + val->intval = 0; + if (chg->sdam_base) { + rc = smblib_batch_read(chg, + chg->sdam_base + SDAM_QC_ADC_LSB_REG, buff, 2); + if (!rc) + val->intval = (buff[1] << 8 | buff[0]) * 1038; + } + break; + default: + pr_err("get prop %d is not supported in usb\n", psp); + rc = -EINVAL; + break; + } + + if (rc < 0) { + pr_debug("Couldn't get prop %d rc = %d\n", psp, rc); + return -ENODATA; + } + + return 0; +} + +#define MIN_THERMAL_VOTE_UA 500000 +static int smb5_usb_set_prop(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct smb5 *chip = power_supply_get_drvdata(psy); + struct smb_charger *chg = &chip->chg; + int icl, rc = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_PD_CURRENT_MAX: + rc = smblib_set_prop_pd_current_max(chg, val); + break; + case POWER_SUPPLY_PROP_TYPEC_POWER_ROLE: + rc = smblib_set_prop_typec_power_role(chg, val); + break; + case POWER_SUPPLY_PROP_TYPEC_SRC_RP: + rc = smblib_set_prop_typec_select_rp(chg, val); + break; + case POWER_SUPPLY_PROP_PD_ACTIVE: + rc = smblib_set_prop_pd_active(chg, val); + break; + case POWER_SUPPLY_PROP_PD_IN_HARD_RESET: + rc = smblib_set_prop_pd_in_hard_reset(chg, val); + break; + case POWER_SUPPLY_PROP_PD_USB_SUSPEND_SUPPORTED: + chg->system_suspend_supported = val->intval; + break; + case POWER_SUPPLY_PROP_BOOST_CURRENT: + rc = smblib_set_prop_boost_current(chg, val); + break; + case POWER_SUPPLY_PROP_CTM_CURRENT_MAX: + rc = vote(chg->usb_icl_votable, CTM_VOTER, + val->intval >= 0, val->intval); + break; + case POWER_SUPPLY_PROP_PR_SWAP: + rc = smblib_set_prop_pr_swap_in_progress(chg, val); + break; + case POWER_SUPPLY_PROP_PD_VOLTAGE_MAX: + rc = smblib_set_prop_pd_voltage_max(chg, val); + break; + case POWER_SUPPLY_PROP_PD_VOLTAGE_MIN: + rc = smblib_set_prop_pd_voltage_min(chg, val); + break; + case POWER_SUPPLY_PROP_SDP_CURRENT_MAX: + rc = smblib_set_prop_sdp_current_max(chg, val); + break; + case POWER_SUPPLY_PROP_CONNECTOR_HEALTH: + chg->connector_health = val->intval; + power_supply_changed(chg->usb_psy); + break; + case POWER_SUPPLY_PROP_THERM_ICL_LIMIT: + if (!is_client_vote_enabled(chg->usb_icl_votable, + THERMAL_THROTTLE_VOTER)) { + chg->init_thermal_ua = get_effective_result( + chg->usb_icl_votable); + icl = chg->init_thermal_ua + val->intval; + } else { + icl = get_client_vote(chg->usb_icl_votable, + THERMAL_THROTTLE_VOTER) + val->intval; + } + + if (icl >= MIN_THERMAL_VOTE_UA) + rc = vote(chg->usb_icl_votable, THERMAL_THROTTLE_VOTER, + (icl != chg->init_thermal_ua) ? true : false, + icl); + else + rc = -EINVAL; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_LIMIT: + smblib_set_prop_usb_voltage_max_limit(chg, val); + break; + case POWER_SUPPLY_PROP_ADAPTER_CC_MODE: + chg->adapter_cc_mode = val->intval; + break; + case POWER_SUPPLY_PROP_APSD_RERUN: + del_timer_sync(&chg->apsd_timer); + chg->apsd_ext_timeout = false; + smblib_rerun_apsd(chg); + break; + default: + pr_err("set prop %d is not supported\n", psp); + rc = -EINVAL; + break; + } + + return rc; +} + +static int smb5_usb_prop_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_CTM_CURRENT_MAX: + case POWER_SUPPLY_PROP_CONNECTOR_HEALTH: + case POWER_SUPPLY_PROP_THERM_ICL_LIMIT: + case POWER_SUPPLY_PROP_VOLTAGE_MAX_LIMIT: + case POWER_SUPPLY_PROP_ADAPTER_CC_MODE: + case POWER_SUPPLY_PROP_APSD_RERUN: + return 1; + default: + break; + } + + return 0; +} + +static const struct power_supply_desc usb_psy_desc = { + .name = "usb", + .type = POWER_SUPPLY_TYPE_USB_PD, + .properties = smb5_usb_props, + .num_properties = ARRAY_SIZE(smb5_usb_props), + .get_property = smb5_usb_get_prop, + .set_property = smb5_usb_set_prop, + .property_is_writeable = smb5_usb_prop_is_writeable, +}; + +static int smb5_init_usb_psy(struct smb5 *chip) +{ + struct power_supply_config usb_cfg = {}; + struct smb_charger *chg = &chip->chg; + + usb_cfg.drv_data = chip; + usb_cfg.of_node = chg->dev->of_node; + chg->usb_psy = devm_power_supply_register(chg->dev, + &usb_psy_desc, + &usb_cfg); + if (IS_ERR(chg->usb_psy)) { + pr_err("Couldn't register USB power supply\n"); + return PTR_ERR(chg->usb_psy); + } + + return 0; +} + +/******************************** + * USB PC_PORT PSY REGISTRATION * + ********************************/ +static enum power_supply_property smb5_usb_port_props[] = { + POWER_SUPPLY_PROP_TYPE, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_CURRENT_MAX, +}; + +static int smb5_usb_port_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct smb5 *chip = power_supply_get_drvdata(psy); + struct smb_charger *chg = &chip->chg; + int rc = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_TYPE: + val->intval = POWER_SUPPLY_TYPE_USB; + break; + case POWER_SUPPLY_PROP_ONLINE: + rc = smblib_get_prop_usb_online(chg, val); + if (!val->intval) + break; + + if (((chg->typec_mode == POWER_SUPPLY_TYPEC_SOURCE_DEFAULT) || + (chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB)) + && (chg->real_charger_type == POWER_SUPPLY_TYPE_USB)) + val->intval = 1; + else + val->intval = 0; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + val->intval = 5000000; + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + rc = smblib_get_prop_input_current_settled(chg, val); + break; + default: + pr_err_ratelimited("Get prop %d is not supported in pc_port\n", + psp); + return -EINVAL; + } + + if (rc < 0) { + pr_debug("Couldn't get prop %d rc = %d\n", psp, rc); + return -ENODATA; + } + + return 0; +} + +static int smb5_usb_port_set_prop(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + int rc = 0; + + switch (psp) { + default: + pr_err_ratelimited("Set prop %d is not supported in pc_port\n", + psp); + rc = -EINVAL; + break; + } + + return rc; +} + +static const struct power_supply_desc usb_port_psy_desc = { + .name = "pc_port", + .type = POWER_SUPPLY_TYPE_USB, + .properties = smb5_usb_port_props, + .num_properties = ARRAY_SIZE(smb5_usb_port_props), + .get_property = smb5_usb_port_get_prop, + .set_property = smb5_usb_port_set_prop, +}; + +static int smb5_init_usb_port_psy(struct smb5 *chip) +{ + struct power_supply_config usb_port_cfg = {}; + struct smb_charger *chg = &chip->chg; + + usb_port_cfg.drv_data = chip; + usb_port_cfg.of_node = chg->dev->of_node; + chg->usb_port_psy = devm_power_supply_register(chg->dev, + &usb_port_psy_desc, + &usb_port_cfg); + if (IS_ERR(chg->usb_port_psy)) { + pr_err("Couldn't register USB pc_port power supply\n"); + return PTR_ERR(chg->usb_port_psy); + } + + return 0; +} + +/***************************** + * USB MAIN PSY REGISTRATION * + *****************************/ + +static enum power_supply_property smb5_usb_main_props[] = { + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, + POWER_SUPPLY_PROP_TYPE, + POWER_SUPPLY_PROP_INPUT_CURRENT_SETTLED, + POWER_SUPPLY_PROP_INPUT_VOLTAGE_SETTLED, + POWER_SUPPLY_PROP_FCC_DELTA, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_FLASH_ACTIVE, + POWER_SUPPLY_PROP_FLASH_TRIGGER, + POWER_SUPPLY_PROP_TOGGLE_STAT, + POWER_SUPPLY_PROP_MAIN_FCC_MAX, + POWER_SUPPLY_PROP_IRQ_STATUS, + POWER_SUPPLY_PROP_FORCE_MAIN_FCC, + POWER_SUPPLY_PROP_FORCE_MAIN_ICL, + POWER_SUPPLY_PROP_COMP_CLAMP_LEVEL, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_HOT_TEMP, +}; + +static int smb5_usb_main_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct smb5 *chip = power_supply_get_drvdata(psy); + struct smb_charger *chg = &chip->chg; + int rc = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + rc = smblib_get_charge_param(chg, &chg->param.fv, &val->intval); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + rc = smblib_get_charge_param(chg, &chg->param.fcc, + &val->intval); + break; + case POWER_SUPPLY_PROP_TYPE: + val->intval = POWER_SUPPLY_TYPE_MAIN; + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_SETTLED: + rc = smblib_get_prop_input_current_settled(chg, val); + break; + case POWER_SUPPLY_PROP_INPUT_VOLTAGE_SETTLED: + rc = smblib_get_prop_input_voltage_settled(chg, val); + break; + case POWER_SUPPLY_PROP_FCC_DELTA: + rc = smblib_get_prop_fcc_delta(chg, val); + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + rc = smblib_get_icl_current(chg, &val->intval); + break; + case POWER_SUPPLY_PROP_FLASH_ACTIVE: + val->intval = chg->flash_active; + break; + case POWER_SUPPLY_PROP_FLASH_TRIGGER: + val->intval = 0; + if (chg->chg_param.smb_version == PMI632_SUBTYPE) + rc = schgm_flash_get_vreg_ok(chg, &val->intval); + break; + case POWER_SUPPLY_PROP_TOGGLE_STAT: + val->intval = 0; + break; + case POWER_SUPPLY_PROP_MAIN_FCC_MAX: + val->intval = chg->main_fcc_max; + break; + case POWER_SUPPLY_PROP_IRQ_STATUS: + rc = smblib_get_irq_status(chg, val); + break; + case POWER_SUPPLY_PROP_FORCE_MAIN_FCC: + rc = smblib_get_charge_param(chg, &chg->param.fcc, + &val->intval); + break; + case POWER_SUPPLY_PROP_FORCE_MAIN_ICL: + rc = smblib_get_charge_param(chg, &chg->param.usb_icl, + &val->intval); + break; + case POWER_SUPPLY_PROP_COMP_CLAMP_LEVEL: + val->intval = chg->comp_clamp_level; + break; + /* Use this property to report SMB health */ + case POWER_SUPPLY_PROP_HEALTH: + rc = val->intval = smblib_get_prop_smb_health(chg); + break; + /* Use this property to report overheat status */ + case POWER_SUPPLY_PROP_HOT_TEMP: + val->intval = chg->thermal_overheat; + break; + default: + pr_debug("get prop %d is not supported in usb-main\n", psp); + rc = -EINVAL; + break; + } + if (rc < 0) + pr_debug("Couldn't get prop %d rc = %d\n", psp, rc); + + return rc; +} + +static int smb5_usb_main_set_prop(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct smb5 *chip = power_supply_get_drvdata(psy); + struct smb_charger *chg = &chip->chg; + union power_supply_propval pval = {0, }; + enum power_supply_type real_chg_type = chg->real_charger_type; + int rc = 0, offset_ua = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + rc = smblib_set_charge_param(chg, &chg->param.fv, val->intval); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + /* Adjust Main FCC for QC3.0 + SMB1390 */ + rc = smblib_get_qc3_main_icl_offset(chg, &offset_ua); + if (rc < 0) + offset_ua = 0; + + rc = smblib_set_charge_param(chg, &chg->param.fcc, + val->intval + offset_ua); + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + rc = smblib_set_icl_current(chg, val->intval); + break; + case POWER_SUPPLY_PROP_FLASH_ACTIVE: + if ((chg->chg_param.smb_version == PMI632_SUBTYPE) + && (chg->flash_active != val->intval)) { + chg->flash_active = val->intval; + + rc = smblib_get_prop_usb_present(chg, &pval); + if (rc < 0) + pr_err("Failed to get USB preset status rc=%d\n", + rc); + if (pval.intval) { + rc = smblib_force_vbus_voltage(chg, + chg->flash_active ? FORCE_5V_BIT + : IDLE_BIT); + if (rc < 0) + pr_err("Failed to force 5V\n"); + else + chg->pulse_cnt = 0; + } else { + /* USB absent & flash not-active - vote 100mA */ + vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, + true, SDP_100_MA); + } + + pr_debug("flash active VBUS 5V restriction %s\n", + chg->flash_active ? "applied" : "removed"); + + /* Update userspace */ + if (chg->batt_psy) + power_supply_changed(chg->batt_psy); + } + break; + case POWER_SUPPLY_PROP_TOGGLE_STAT: + rc = smblib_toggle_smb_en(chg, val->intval); + break; + case POWER_SUPPLY_PROP_MAIN_FCC_MAX: + chg->main_fcc_max = val->intval; + rerun_election(chg->fcc_votable); + break; + case POWER_SUPPLY_PROP_FORCE_MAIN_FCC: + vote_override(chg->fcc_main_votable, CC_MODE_VOTER, + (val->intval < 0) ? false : true, val->intval); + if (val->intval >= 0) + chg->chg_param.forced_main_fcc = val->intval; + /* + * Remove low vote on FCC_MAIN, for WLS, to allow FCC_MAIN to + * rise to its full value. + */ + if (val->intval < 0) + vote(chg->fcc_main_votable, WLS_PL_CHARGING_VOTER, + false, 0); + /* Main FCC updated re-calculate FCC */ + rerun_election(chg->fcc_votable); + break; + case POWER_SUPPLY_PROP_FORCE_MAIN_ICL: + vote_override(chg->usb_icl_votable, CC_MODE_VOTER, + (val->intval < 0) ? false : true, val->intval); + /* Main ICL updated re-calculate ILIM */ + if (real_chg_type == POWER_SUPPLY_TYPE_USB_HVDCP_3 || + real_chg_type == POWER_SUPPLY_TYPE_USB_HVDCP_3P5) + rerun_election(chg->fcc_votable); + break; + case POWER_SUPPLY_PROP_COMP_CLAMP_LEVEL: + rc = smb5_set_prop_comp_clamp_level(chg, val); + break; + case POWER_SUPPLY_PROP_HOT_TEMP: + rc = smblib_set_prop_thermal_overheat(chg, val->intval); + break; + default: + pr_err("set prop %d is not supported\n", psp); + rc = -EINVAL; + break; + } + + return rc; +} + +static int smb5_usb_main_prop_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + int rc; + + switch (psp) { + case POWER_SUPPLY_PROP_TOGGLE_STAT: + case POWER_SUPPLY_PROP_MAIN_FCC_MAX: + case POWER_SUPPLY_PROP_FORCE_MAIN_FCC: + case POWER_SUPPLY_PROP_FORCE_MAIN_ICL: + case POWER_SUPPLY_PROP_COMP_CLAMP_LEVEL: + case POWER_SUPPLY_PROP_HOT_TEMP: + rc = 1; + break; + default: + rc = 0; + break; + } + + return rc; +} + +static const struct power_supply_desc usb_main_psy_desc = { + .name = "main", + .type = POWER_SUPPLY_TYPE_MAIN, + .properties = smb5_usb_main_props, + .num_properties = ARRAY_SIZE(smb5_usb_main_props), + .get_property = smb5_usb_main_get_prop, + .set_property = smb5_usb_main_set_prop, + .property_is_writeable = smb5_usb_main_prop_is_writeable, +}; + +static int smb5_init_usb_main_psy(struct smb5 *chip) +{ + struct power_supply_config usb_main_cfg = {}; + struct smb_charger *chg = &chip->chg; + + usb_main_cfg.drv_data = chip; + usb_main_cfg.of_node = chg->dev->of_node; + chg->usb_main_psy = devm_power_supply_register(chg->dev, + &usb_main_psy_desc, + &usb_main_cfg); + if (IS_ERR(chg->usb_main_psy)) { + pr_err("Couldn't register USB main power supply\n"); + return PTR_ERR(chg->usb_main_psy); + } + + return 0; +} + +/************************* + * DC PSY REGISTRATION * + *************************/ + +static enum power_supply_property smb5_dc_props[] = { + POWER_SUPPLY_PROP_INPUT_SUSPEND, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_INPUT_VOLTAGE_REGULATION, + POWER_SUPPLY_PROP_REAL_TYPE, + POWER_SUPPLY_PROP_DC_RESET, + POWER_SUPPLY_PROP_AICL_DONE, +}; + +static int smb5_dc_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct smb5 *chip = power_supply_get_drvdata(psy); + struct smb_charger *chg = &chip->chg; + int rc = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_INPUT_SUSPEND: + val->intval = get_effective_result(chg->dc_suspend_votable); + break; + case POWER_SUPPLY_PROP_PRESENT: + rc = smblib_get_prop_dc_present(chg, val); + break; + case POWER_SUPPLY_PROP_ONLINE: + rc = smblib_get_prop_dc_online(chg, val); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + rc = smblib_get_prop_dc_voltage_now(chg, val); + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + rc = smblib_get_prop_dc_current_max(chg, val); + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + rc = smblib_get_prop_dc_voltage_max(chg, val); + break; + case POWER_SUPPLY_PROP_REAL_TYPE: + val->intval = POWER_SUPPLY_TYPE_WIRELESS; + break; + case POWER_SUPPLY_PROP_INPUT_VOLTAGE_REGULATION: + rc = smblib_get_prop_voltage_wls_output(chg, val); + break; + case POWER_SUPPLY_PROP_DC_RESET: + val->intval = 0; + break; + case POWER_SUPPLY_PROP_AICL_DONE: + val->intval = chg->dcin_aicl_done; + break; + default: + return -EINVAL; + } + if (rc < 0) { + pr_debug("Couldn't get prop %d rc = %d\n", psp, rc); + return -ENODATA; + } + return 0; +} + +static int smb5_dc_set_prop(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct smb5 *chip = power_supply_get_drvdata(psy); + struct smb_charger *chg = &chip->chg; + int rc = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_INPUT_SUSPEND: + rc = vote(chg->dc_suspend_votable, WBC_VOTER, + (bool)val->intval, 0); + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + rc = smblib_set_prop_dc_current_max(chg, val); + break; + case POWER_SUPPLY_PROP_INPUT_VOLTAGE_REGULATION: + rc = smblib_set_prop_voltage_wls_output(chg, val); + break; + case POWER_SUPPLY_PROP_DC_RESET: + rc = smblib_set_prop_dc_reset(chg); + break; + default: + return -EINVAL; + } + + return rc; +} + +static int smb5_dc_prop_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_INPUT_VOLTAGE_REGULATION: + case POWER_SUPPLY_PROP_CURRENT_MAX: + return 1; + default: + break; + } + + return 0; +} + +static const struct power_supply_desc dc_psy_desc = { + .name = "dc", + .type = POWER_SUPPLY_TYPE_WIRELESS, + .properties = smb5_dc_props, + .num_properties = ARRAY_SIZE(smb5_dc_props), + .get_property = smb5_dc_get_prop, + .set_property = smb5_dc_set_prop, + .property_is_writeable = smb5_dc_prop_is_writeable, +}; + +static int smb5_init_dc_psy(struct smb5 *chip) +{ + struct power_supply_config dc_cfg = {}; + struct smb_charger *chg = &chip->chg; + + dc_cfg.drv_data = chip; + dc_cfg.of_node = chg->dev->of_node; + chg->dc_psy = devm_power_supply_register(chg->dev, + &dc_psy_desc, + &dc_cfg); + if (IS_ERR(chg->dc_psy)) { + pr_err("Couldn't register USB power supply\n"); + return PTR_ERR(chg->dc_psy); + } + + return 0; +} + +/************************* + * BATT PSY REGISTRATION * + *************************/ +static enum power_supply_property smb5_batt_props[] = { + POWER_SUPPLY_PROP_INPUT_SUSPEND, + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CHARGER_TEMP, + POWER_SUPPLY_PROP_CHARGER_TEMP_MAX, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_VOLTAGE_QNOVO, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CURRENT_QNOVO, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, + POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_STEP_CHARGING_ENABLED, + POWER_SUPPLY_PROP_SW_JEITA_ENABLED, + POWER_SUPPLY_PROP_CHARGE_DONE, + POWER_SUPPLY_PROP_PARALLEL_DISABLE, + POWER_SUPPLY_PROP_SET_SHIP_MODE, + POWER_SUPPLY_PROP_DIE_HEALTH, + POWER_SUPPLY_PROP_RERUN_AICL, + POWER_SUPPLY_PROP_DP_DM, + POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX, + POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT, + POWER_SUPPLY_PROP_CHARGE_COUNTER, + POWER_SUPPLY_PROP_CYCLE_COUNT, + POWER_SUPPLY_PROP_RECHARGE_SOC, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_FORCE_RECHARGE, + POWER_SUPPLY_PROP_FCC_STEPPER_ENABLE, +}; + +#define DEBUG_ACCESSORY_TEMP_DECIDEGC 250 +static int smb5_batt_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct smb_charger *chg = power_supply_get_drvdata(psy); + int rc = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + rc = smblib_get_prop_batt_status(chg, val); + break; + case POWER_SUPPLY_PROP_HEALTH: + rc = smblib_get_prop_batt_health(chg, val); + break; + case POWER_SUPPLY_PROP_PRESENT: + rc = smblib_get_prop_batt_present(chg, val); + break; + case POWER_SUPPLY_PROP_INPUT_SUSPEND: + rc = smblib_get_prop_input_suspend(chg, val); + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + rc = smblib_get_prop_batt_charge_type(chg, val); + break; + case POWER_SUPPLY_PROP_CAPACITY: + rc = smblib_get_prop_batt_capacity(chg, val); + break; + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: + rc = smblib_get_prop_system_temp_level(chg, val); + break; + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX: + rc = smblib_get_prop_system_temp_level_max(chg, val); + break; + case POWER_SUPPLY_PROP_CHARGER_TEMP: + rc = smblib_get_prop_charger_temp(chg, val); + break; + case POWER_SUPPLY_PROP_CHARGER_TEMP_MAX: + val->intval = chg->charger_temp_max; + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED: + rc = smblib_get_prop_input_current_limited(chg, val); + break; + case POWER_SUPPLY_PROP_STEP_CHARGING_ENABLED: + val->intval = chg->step_chg_enabled; + break; + case POWER_SUPPLY_PROP_SW_JEITA_ENABLED: + val->intval = chg->sw_jeita_enabled; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + rc = smblib_get_prop_from_bms(chg, + POWER_SUPPLY_PROP_VOLTAGE_NOW, val); + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + val->intval = get_client_vote(chg->fv_votable, + QNOVO_VOTER); + if (val->intval < 0) + val->intval = get_client_vote(chg->fv_votable, + BATT_PROFILE_VOTER); + break; + case POWER_SUPPLY_PROP_VOLTAGE_QNOVO: + val->intval = get_client_vote_locked(chg->fv_votable, + QNOVO_VOTER); + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + rc = smblib_get_batt_current_now(chg, val); + break; + case POWER_SUPPLY_PROP_CURRENT_QNOVO: + val->intval = get_client_vote_locked(chg->fcc_votable, + QNOVO_VOTER); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + val->intval = get_client_vote(chg->fcc_votable, + BATT_PROFILE_VOTER); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + val->intval = get_effective_result(chg->fcc_votable); + break; + case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: + rc = smblib_get_prop_batt_iterm(chg, val); + break; + case POWER_SUPPLY_PROP_TEMP: + if (chg->typec_mode == POWER_SUPPLY_TYPEC_SINK_DEBUG_ACCESSORY) + val->intval = DEBUG_ACCESSORY_TEMP_DECIDEGC; + else + rc = smblib_get_prop_from_bms(chg, + POWER_SUPPLY_PROP_TEMP, val); + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + case POWER_SUPPLY_PROP_CHARGE_DONE: + rc = smblib_get_prop_batt_charge_done(chg, val); + break; + case POWER_SUPPLY_PROP_PARALLEL_DISABLE: + val->intval = get_client_vote(chg->pl_disable_votable, + USER_VOTER); + break; + case POWER_SUPPLY_PROP_SET_SHIP_MODE: + /* Not in ship mode as long as device is active */ + val->intval = 0; + break; + case POWER_SUPPLY_PROP_DIE_HEALTH: + rc = smblib_get_die_health(chg, val); + break; + case POWER_SUPPLY_PROP_DP_DM: + val->intval = chg->pulse_cnt; + break; + case POWER_SUPPLY_PROP_RERUN_AICL: + val->intval = 0; + break; + case POWER_SUPPLY_PROP_CHARGE_COUNTER: + rc = smblib_get_prop_from_bms(chg, + POWER_SUPPLY_PROP_CHARGE_COUNTER, val); + break; + case POWER_SUPPLY_PROP_CYCLE_COUNT: + rc = smblib_get_prop_from_bms(chg, + POWER_SUPPLY_PROP_CYCLE_COUNT, val); + break; + case POWER_SUPPLY_PROP_RECHARGE_SOC: + val->intval = chg->auto_recharge_soc; + break; + case POWER_SUPPLY_PROP_CHARGE_QNOVO_ENABLE: + val->intval = 0; + if (!chg->qnovo_disable_votable) + chg->qnovo_disable_votable = + find_votable("QNOVO_DISABLE"); + + if (chg->qnovo_disable_votable) + val->intval = + !get_effective_result( + chg->qnovo_disable_votable); + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + rc = smblib_get_prop_from_bms(chg, + POWER_SUPPLY_PROP_CHARGE_FULL, val); + break; + case POWER_SUPPLY_PROP_FORCE_RECHARGE: + val->intval = 0; + break; + case POWER_SUPPLY_PROP_FCC_STEPPER_ENABLE: + val->intval = chg->fcc_stepper_enable; + break; + default: + pr_err("batt power supply prop %d not supported\n", psp); + return -EINVAL; + } + + if (rc < 0) { + pr_debug("Couldn't get prop %d rc = %d\n", psp, rc); + return -ENODATA; + } + + return 0; +} + +static int smb5_batt_set_prop(struct power_supply *psy, + enum power_supply_property prop, + const union power_supply_propval *val) +{ + int rc = 0; + struct smb_charger *chg = power_supply_get_drvdata(psy); + + switch (prop) { + case POWER_SUPPLY_PROP_STATUS: + rc = smblib_set_prop_batt_status(chg, val); + break; + case POWER_SUPPLY_PROP_INPUT_SUSPEND: + rc = smblib_set_prop_input_suspend(chg, val); + break; + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: + rc = smblib_set_prop_system_temp_level(chg, val); + break; + case POWER_SUPPLY_PROP_CAPACITY: + rc = smblib_set_prop_batt_capacity(chg, val); + break; + case POWER_SUPPLY_PROP_PARALLEL_DISABLE: + vote(chg->pl_disable_votable, USER_VOTER, (bool)val->intval, 0); + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + chg->batt_profile_fv_uv = val->intval; + vote(chg->fv_votable, BATT_PROFILE_VOTER, true, val->intval); + break; + case POWER_SUPPLY_PROP_VOLTAGE_QNOVO: + if (val->intval == -EINVAL) { + vote(chg->fv_votable, BATT_PROFILE_VOTER, true, + chg->batt_profile_fv_uv); + vote(chg->fv_votable, QNOVO_VOTER, false, 0); + } else { + vote(chg->fv_votable, QNOVO_VOTER, true, val->intval); + vote(chg->fv_votable, BATT_PROFILE_VOTER, false, 0); + } + break; + case POWER_SUPPLY_PROP_STEP_CHARGING_ENABLED: + chg->step_chg_enabled = !!val->intval; + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + chg->batt_profile_fcc_ua = val->intval; + vote(chg->fcc_votable, BATT_PROFILE_VOTER, true, val->intval); + break; + case POWER_SUPPLY_PROP_CURRENT_QNOVO: + vote(chg->pl_disable_votable, PL_QNOVO_VOTER, + val->intval != -EINVAL && val->intval < 2000000, 0); + if (val->intval == -EINVAL) { + vote(chg->fcc_votable, BATT_PROFILE_VOTER, + true, chg->batt_profile_fcc_ua); + vote(chg->fcc_votable, QNOVO_VOTER, false, 0); + } else { + vote(chg->fcc_votable, QNOVO_VOTER, true, val->intval); + vote(chg->fcc_votable, BATT_PROFILE_VOTER, false, 0); + } + break; + case POWER_SUPPLY_PROP_SET_SHIP_MODE: + /* Not in ship mode as long as the device is active */ + if (!val->intval) + break; + if (chg->pl.psy) + power_supply_set_property(chg->pl.psy, + POWER_SUPPLY_PROP_SET_SHIP_MODE, val); + rc = smblib_set_prop_ship_mode(chg, val); + break; + case POWER_SUPPLY_PROP_RERUN_AICL: + rc = smblib_run_aicl(chg, RERUN_AICL); + break; + case POWER_SUPPLY_PROP_DP_DM: + if (!chg->flash_active) + rc = smblib_dp_dm(chg, val->intval); + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED: + rc = smblib_set_prop_input_current_limited(chg, val); + break; + case POWER_SUPPLY_PROP_DIE_HEALTH: + chg->die_health = val->intval; + power_supply_changed(chg->batt_psy); + break; + case POWER_SUPPLY_PROP_RECHARGE_SOC: + rc = smblib_set_prop_rechg_soc_thresh(chg, val); + break; + case POWER_SUPPLY_PROP_FORCE_RECHARGE: + /* toggle charging to force recharge */ + vote(chg->chg_disable_votable, FORCE_RECHARGE_VOTER, + true, 0); + /* charge disable delay */ + msleep(50); + vote(chg->chg_disable_votable, FORCE_RECHARGE_VOTER, + false, 0); + break; + case POWER_SUPPLY_PROP_FCC_STEPPER_ENABLE: + chg->fcc_stepper_enable = val->intval; + break; + default: + rc = -EINVAL; + } + + return rc; +} + +static int smb5_batt_prop_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + case POWER_SUPPLY_PROP_INPUT_SUSPEND: + case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL: + case POWER_SUPPLY_PROP_CAPACITY: + case POWER_SUPPLY_PROP_PARALLEL_DISABLE: + case POWER_SUPPLY_PROP_DP_DM: + case POWER_SUPPLY_PROP_RERUN_AICL: + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED: + case POWER_SUPPLY_PROP_STEP_CHARGING_ENABLED: + case POWER_SUPPLY_PROP_DIE_HEALTH: + return 1; + default: + break; + } + + return 0; +} + +static const struct power_supply_desc batt_psy_desc = { + .name = "battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = smb5_batt_props, + .num_properties = ARRAY_SIZE(smb5_batt_props), + .get_property = smb5_batt_get_prop, + .set_property = smb5_batt_set_prop, + .property_is_writeable = smb5_batt_prop_is_writeable, +}; + +static int smb5_init_batt_psy(struct smb5 *chip) +{ + struct power_supply_config batt_cfg = {}; + struct smb_charger *chg = &chip->chg; + int rc = 0; + + batt_cfg.drv_data = chg; + batt_cfg.of_node = chg->dev->of_node; + chg->batt_psy = devm_power_supply_register(chg->dev, + &batt_psy_desc, + &batt_cfg); + if (IS_ERR(chg->batt_psy)) { + pr_err("Couldn't register battery power supply\n"); + return PTR_ERR(chg->batt_psy); + } + + return rc; +} + +/****************************** + * VBUS REGULATOR REGISTRATION * + ******************************/ + +static struct regulator_ops smb5_vbus_reg_ops = { + .enable = smblib_vbus_regulator_enable, + .disable = smblib_vbus_regulator_disable, + .is_enabled = smblib_vbus_regulator_is_enabled, +}; + +static int smb5_init_vbus_regulator(struct smb5 *chip) +{ + struct smb_charger *chg = &chip->chg; + struct regulator_config cfg = {}; + int rc = 0; + + chg->vbus_vreg = devm_kzalloc(chg->dev, sizeof(*chg->vbus_vreg), + GFP_KERNEL); + if (!chg->vbus_vreg) + return -ENOMEM; + + cfg.dev = chg->dev; + cfg.driver_data = chip; + + chg->vbus_vreg->rdesc.owner = THIS_MODULE; + chg->vbus_vreg->rdesc.type = REGULATOR_VOLTAGE; + chg->vbus_vreg->rdesc.ops = &smb5_vbus_reg_ops; + chg->vbus_vreg->rdesc.of_match = "qcom,smb5-vbus"; + chg->vbus_vreg->rdesc.name = "qcom,smb5-vbus"; + + chg->vbus_vreg->rdev = devm_regulator_register(chg->dev, + &chg->vbus_vreg->rdesc, &cfg); + if (IS_ERR(chg->vbus_vreg->rdev)) { + rc = PTR_ERR(chg->vbus_vreg->rdev); + chg->vbus_vreg->rdev = NULL; + if (rc != -EPROBE_DEFER) + pr_err("Couldn't register VBUS regulator rc=%d\n", rc); + } + + return rc; +} + +/****************************** + * VCONN REGULATOR REGISTRATION * + ******************************/ + +static struct regulator_ops smb5_vconn_reg_ops = { + .enable = smblib_vconn_regulator_enable, + .disable = smblib_vconn_regulator_disable, + .is_enabled = smblib_vconn_regulator_is_enabled, +}; + +static int smb5_init_vconn_regulator(struct smb5 *chip) +{ + struct smb_charger *chg = &chip->chg; + struct regulator_config cfg = {}; + int rc = 0; + + if (chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB) + return 0; + + chg->vconn_vreg = devm_kzalloc(chg->dev, sizeof(*chg->vconn_vreg), + GFP_KERNEL); + if (!chg->vconn_vreg) + return -ENOMEM; + + cfg.dev = chg->dev; + cfg.driver_data = chip; + + chg->vconn_vreg->rdesc.owner = THIS_MODULE; + chg->vconn_vreg->rdesc.type = REGULATOR_VOLTAGE; + chg->vconn_vreg->rdesc.ops = &smb5_vconn_reg_ops; + chg->vconn_vreg->rdesc.of_match = "qcom,smb5-vconn"; + chg->vconn_vreg->rdesc.name = "qcom,smb5-vconn"; + + chg->vconn_vreg->rdev = devm_regulator_register(chg->dev, + &chg->vconn_vreg->rdesc, &cfg); + if (IS_ERR(chg->vconn_vreg->rdev)) { + rc = PTR_ERR(chg->vconn_vreg->rdev); + chg->vconn_vreg->rdev = NULL; + if (rc != -EPROBE_DEFER) + pr_err("Couldn't register VCONN regulator rc=%d\n", rc); + } + + return rc; +} + +/*************************** + * HARDWARE INITIALIZATION * + ***************************/ +static int smb5_configure_typec(struct smb_charger *chg) +{ + union power_supply_propval pval = {0, }; + int rc; + u8 val = 0; + + rc = smblib_read(chg, LEGACY_CABLE_STATUS_REG, &val); + if (rc < 0) { + dev_err(chg->dev, "Couldn't read Legacy status rc=%d\n", rc); + return rc; + } + + /* + * Across reboot, standard typeC cables get detected as legacy cables + * due to VBUS attachment prior to CC attach/dettach. To handle this, + * "early_usb_attach" flag is used, which assumes that across reboot, + * the cable connected can be standard typeC. However, its jurisdiction + * is limited to PD capable designs only. Hence, for non-PD type designs + * reset legacy cable detection by disabling/enabling typeC mode. + */ + if (chg->pd_not_supported && (val & TYPEC_LEGACY_CABLE_STATUS_BIT)) { + pval.intval = POWER_SUPPLY_TYPEC_PR_NONE; + smblib_set_prop_typec_power_role(chg, &pval); + if (rc < 0) { + dev_err(chg->dev, "Couldn't disable TYPEC rc=%d\n", rc); + return rc; + } + + /* delay before enabling typeC */ + msleep(50); + + pval.intval = POWER_SUPPLY_TYPEC_PR_DUAL; + smblib_set_prop_typec_power_role(chg, &pval); + if (rc < 0) { + dev_err(chg->dev, "Couldn't enable TYPEC rc=%d\n", rc); + return rc; + } + } + + smblib_apsd_enable(chg, true); + + rc = smblib_masked_write(chg, TYPE_C_CFG_REG, + BC1P2_START_ON_CC_BIT, 0); + if (rc < 0) { + dev_err(chg->dev, "failed to write TYPE_C_CFG_REG rc=%d\n", + rc); + + return rc; + } + + /* Use simple write to clear interrupts */ + rc = smblib_write(chg, TYPE_C_INTERRUPT_EN_CFG_1_REG, 0); + if (rc < 0) { + dev_err(chg->dev, + "Couldn't configure Type-C interrupts rc=%d\n", rc); + return rc; + } + + val = chg->lpd_disabled ? 0 : TYPEC_WATER_DETECTION_INT_EN_BIT; + /* Use simple write to enable only required interrupts */ + rc = smblib_write(chg, TYPE_C_INTERRUPT_EN_CFG_2_REG, + TYPEC_SRC_BATT_HPWR_INT_EN_BIT | val); + if (rc < 0) { + dev_err(chg->dev, + "Couldn't configure Type-C interrupts rc=%d\n", rc); + return rc; + } + + /* enable try.snk and clear force sink for DRP mode */ + rc = smblib_masked_write(chg, TYPE_C_MODE_CFG_REG, + EN_TRY_SNK_BIT | EN_SNK_ONLY_BIT, + EN_TRY_SNK_BIT); + if (rc < 0) { + dev_err(chg->dev, + "Couldn't configure TYPE_C_MODE_CFG_REG rc=%d\n", rc); + return rc; + } + chg->typec_try_mode |= EN_TRY_SNK_BIT; + + /* For PD capable targets configure VCONN for software control */ + if (!chg->pd_not_supported) { + rc = smblib_masked_write(chg, TYPE_C_VCONN_CONTROL_REG, + VCONN_EN_SRC_BIT | VCONN_EN_VALUE_BIT, + VCONN_EN_SRC_BIT); + if (rc < 0) { + dev_err(chg->dev, + "Couldn't configure VCONN for SW control rc=%d\n", + rc); + return rc; + } + } + + if (chg->chg_param.smb_version != PMI632_SUBTYPE) { + /* + * Enable detection of unoriented debug + * accessory in source mode. + */ + rc = smblib_masked_write(chg, DEBUG_ACCESS_SRC_CFG_REG, + EN_UNORIENTED_DEBUG_ACCESS_SRC_BIT, + EN_UNORIENTED_DEBUG_ACCESS_SRC_BIT); + if (rc < 0) { + dev_err(chg->dev, + "Couldn't configure TYPE_C_DEBUG_ACCESS_SRC_CFG_REG rc=%d\n", + rc); + return rc; + } + + rc = smblib_masked_write(chg, USBIN_LOAD_CFG_REG, + USBIN_IN_COLLAPSE_GF_SEL_MASK | + USBIN_AICL_STEP_TIMING_SEL_MASK, + 0); + if (rc < 0) { + dev_err(chg->dev, + "Couldn't set USBIN_LOAD_CFG_REG rc=%d\n", rc); + return rc; + } + } + + /* Set CC threshold to 1.6 V in source mode */ + rc = smblib_masked_write(chg, TYPE_C_EXIT_STATE_CFG_REG, + SEL_SRC_UPPER_REF_BIT, SEL_SRC_UPPER_REF_BIT); + if (rc < 0) + dev_err(chg->dev, + "Couldn't configure CC threshold voltage rc=%d\n", rc); + + return rc; +} + +static int smb5_configure_micro_usb(struct smb_charger *chg) +{ + int rc; + + /* For micro USB connector, use extcon by default */ + chg->use_extcon = true; + chg->pd_not_supported = true; + + rc = smblib_masked_write(chg, TYPE_C_INTERRUPT_EN_CFG_2_REG, + MICRO_USB_STATE_CHANGE_INT_EN_BIT, + MICRO_USB_STATE_CHANGE_INT_EN_BIT); + if (rc < 0) { + dev_err(chg->dev, + "Couldn't configure Type-C interrupts rc=%d\n", rc); + return rc; + } + + if (chg->uusb_moisture_protection_enabled) { + /* Enable moisture detection interrupt */ + rc = smblib_masked_write(chg, TYPE_C_INTERRUPT_EN_CFG_2_REG, + TYPEC_WATER_DETECTION_INT_EN_BIT, + TYPEC_WATER_DETECTION_INT_EN_BIT); + if (rc < 0) { + dev_err(chg->dev, "Couldn't enable moisture detection interrupt rc=%d\n", + rc); + return rc; + } + + /* Enable uUSB factory mode */ + rc = smblib_masked_write(chg, TYPEC_U_USB_CFG_REG, + EN_MICRO_USB_FACTORY_MODE_BIT, + EN_MICRO_USB_FACTORY_MODE_BIT); + if (rc < 0) { + dev_err(chg->dev, "Couldn't enable uUSB factory mode c=%d\n", + rc); + return rc; + } + + /* Disable periodic monitoring of CC_ID pin */ + rc = smblib_write(chg, + ((chg->chg_param.smb_version == PMI632_SUBTYPE) ? + PMI632_TYPEC_U_USB_WATER_PROTECTION_CFG_REG : + TYPEC_U_USB_WATER_PROTECTION_CFG_REG), 0); + if (rc < 0) { + dev_err(chg->dev, "Couldn't disable periodic monitoring of CC_ID rc=%d\n", + rc); + return rc; + } + } + + /* Enable HVDCP detection and authentication */ + if (!chg->hvdcp_disable) + smblib_hvdcp_detect_enable(chg, true); + + return rc; +} + +#define RAW_ITERM(iterm_ma, max_range) \ + div_s64((int64_t)iterm_ma * ADC_CHG_ITERM_MASK, max_range) +static int smb5_configure_iterm_thresholds_adc(struct smb5 *chip) +{ + u8 *buf; + int rc = 0; + s16 raw_hi_thresh, raw_lo_thresh, max_limit_ma; + struct smb_charger *chg = &chip->chg; + + if (chip->chg.chg_param.smb_version == PMI632_SUBTYPE) + max_limit_ma = ITERM_LIMITS_PMI632_MA; + else + max_limit_ma = ITERM_LIMITS_PM8150B_MA; + + if (chip->dt.term_current_thresh_hi_ma < (-1 * max_limit_ma) + || chip->dt.term_current_thresh_hi_ma > max_limit_ma + || chip->dt.term_current_thresh_lo_ma < (-1 * max_limit_ma) + || chip->dt.term_current_thresh_lo_ma > max_limit_ma) { + dev_err(chg->dev, "ITERM threshold out of range rc=%d\n", rc); + return -EINVAL; + } + + /* + * Conversion: + * raw (A) = (term_current * ADC_CHG_ITERM_MASK) / max_limit_ma + * Note: raw needs to be converted to big-endian format. + */ + + if (chip->dt.term_current_thresh_hi_ma) { + raw_hi_thresh = RAW_ITERM(chip->dt.term_current_thresh_hi_ma, + max_limit_ma); + raw_hi_thresh = sign_extend32(raw_hi_thresh, 15); + buf = (u8 *)&raw_hi_thresh; + raw_hi_thresh = buf[1] | (buf[0] << 8); + + rc = smblib_batch_write(chg, CHGR_ADC_ITERM_UP_THD_MSB_REG, + (u8 *)&raw_hi_thresh, 2); + if (rc < 0) { + dev_err(chg->dev, "Couldn't configure ITERM threshold HIGH rc=%d\n", + rc); + return rc; + } + } + + if (chip->dt.term_current_thresh_lo_ma) { + raw_lo_thresh = RAW_ITERM(chip->dt.term_current_thresh_lo_ma, + max_limit_ma); + raw_lo_thresh = sign_extend32(raw_lo_thresh, 15); + buf = (u8 *)&raw_lo_thresh; + raw_lo_thresh = buf[1] | (buf[0] << 8); + + rc = smblib_batch_write(chg, CHGR_ADC_ITERM_LO_THD_MSB_REG, + (u8 *)&raw_lo_thresh, 2); + if (rc < 0) { + dev_err(chg->dev, "Couldn't configure ITERM threshold LOW rc=%d\n", + rc); + return rc; + } + } + + return rc; +} + +static int smb5_configure_iterm_thresholds(struct smb5 *chip) +{ + int rc = 0; + struct smb_charger *chg = &chip->chg; + + switch (chip->dt.term_current_src) { + case ITERM_SRC_ADC: + if (chip->chg.chg_param.smb_version == PM8150B_SUBTYPE) { + rc = smblib_masked_write(chg, CHGR_ADC_TERM_CFG_REG, + TERM_BASED_ON_SYNC_CONV_OR_SAMPLE_CNT, + TERM_BASED_ON_SAMPLE_CNT); + if (rc < 0) { + dev_err(chg->dev, "Couldn't configure ADC_ITERM_CFG rc=%d\n", + rc); + return rc; + } + } + rc = smb5_configure_iterm_thresholds_adc(chip); + break; + default: + break; + } + + return rc; +} + +static int smb5_configure_mitigation(struct smb_charger *chg) +{ + int rc; + u8 chan = 0, src_cfg = 0; + + if (!chg->hw_die_temp_mitigation && !chg->hw_connector_mitigation && + !chg->hw_skin_temp_mitigation) { + src_cfg = THERMREG_SW_ICL_ADJUST_BIT; + } else { + if (chg->hw_die_temp_mitigation) { + chan = DIE_TEMP_CHANNEL_EN_BIT; + src_cfg = THERMREG_DIE_ADC_SRC_EN_BIT + | THERMREG_DIE_CMP_SRC_EN_BIT; + } + + if (chg->hw_connector_mitigation) { + chan |= CONN_THM_CHANNEL_EN_BIT; + src_cfg |= THERMREG_CONNECTOR_ADC_SRC_EN_BIT; + } + + if (chg->hw_skin_temp_mitigation) { + chan |= MISC_THM_CHANNEL_EN_BIT; + src_cfg |= THERMREG_SKIN_ADC_SRC_EN_BIT; + } + + rc = smblib_masked_write(chg, BATIF_ADC_CHANNEL_EN_REG, + CONN_THM_CHANNEL_EN_BIT | DIE_TEMP_CHANNEL_EN_BIT | + MISC_THM_CHANNEL_EN_BIT, chan); + if (rc < 0) { + dev_err(chg->dev, "Couldn't enable ADC channel rc=%d\n", + rc); + return rc; + } + } + + rc = smblib_masked_write(chg, MISC_THERMREG_SRC_CFG_REG, + THERMREG_SW_ICL_ADJUST_BIT | THERMREG_DIE_ADC_SRC_EN_BIT | + THERMREG_DIE_CMP_SRC_EN_BIT | THERMREG_SKIN_ADC_SRC_EN_BIT | + SKIN_ADC_CFG_BIT | THERMREG_CONNECTOR_ADC_SRC_EN_BIT, src_cfg); + if (rc < 0) { + dev_err(chg->dev, + "Couldn't configure THERM_SRC reg rc=%d\n", rc); + return rc; + } + + return 0; +} + +static int smb5_init_dc_peripheral(struct smb_charger *chg) +{ + int rc = 0; + + /* PMI632 does not have DC peripheral */ + if (chg->chg_param.smb_version == PMI632_SUBTYPE) + return 0; + + /* Set DCIN ICL to 100 mA */ + rc = smblib_set_charge_param(chg, &chg->param.dc_icl, DCIN_ICL_MIN_UA); + if (rc < 0) { + dev_err(chg->dev, "Couldn't set dc_icl rc=%d\n", rc); + return rc; + } + + /* Disable DC Input missing poller function */ + rc = smblib_masked_write(chg, DCIN_LOAD_CFG_REG, + INPUT_MISS_POLL_EN_BIT, 0); + if (rc < 0) { + dev_err(chg->dev, + "Couldn't disable DC Input missing poller rc=%d\n", rc); + return rc; + } + + return rc; +} + +static int smb5_configure_recharging(struct smb5 *chip) +{ + int rc = 0; + struct smb_charger *chg = &chip->chg; + union power_supply_propval pval; + /* Configure VBATT-based or automatic recharging */ + + rc = smblib_masked_write(chg, CHGR_CFG2_REG, RECHG_MASK, + (chip->dt.auto_recharge_vbat_mv != -EINVAL) ? + VBAT_BASED_RECHG_BIT : 0); + if (rc < 0) { + dev_err(chg->dev, "Couldn't configure VBAT-rechg CHG_CFG2_REG rc=%d\n", + rc); + return rc; + } + + /* program the auto-recharge VBAT threshold */ + if (chip->dt.auto_recharge_vbat_mv != -EINVAL) { + u32 temp = VBAT_TO_VRAW_ADC(chip->dt.auto_recharge_vbat_mv); + + temp = ((temp & 0xFF00) >> 8) | ((temp & 0xFF) << 8); + rc = smblib_batch_write(chg, + CHGR_ADC_RECHARGE_THRESHOLD_MSB_REG, (u8 *)&temp, 2); + if (rc < 0) { + dev_err(chg->dev, "Couldn't configure ADC_RECHARGE_THRESHOLD REG rc=%d\n", + rc); + return rc; + } + /* Program the sample count for VBAT based recharge to 3 */ + rc = smblib_masked_write(chg, CHGR_NO_SAMPLE_TERM_RCHG_CFG_REG, + NO_OF_SAMPLE_FOR_RCHG, + 2 << NO_OF_SAMPLE_FOR_RCHG_SHIFT); + if (rc < 0) { + dev_err(chg->dev, "Couldn't configure CHGR_NO_SAMPLE_FOR_TERM_RCHG_CFG rc=%d\n", + rc); + return rc; + } + } + + rc = smblib_masked_write(chg, CHGR_CFG2_REG, RECHG_MASK, + (chip->dt.auto_recharge_soc != -EINVAL) ? + SOC_BASED_RECHG_BIT : VBAT_BASED_RECHG_BIT); + if (rc < 0) { + dev_err(chg->dev, "Couldn't configure SOC-rechg CHG_CFG2_REG rc=%d\n", + rc); + return rc; + } + + /* program the auto-recharge threshold */ + if (chip->dt.auto_recharge_soc != -EINVAL) { + pval.intval = chip->dt.auto_recharge_soc; + rc = smblib_set_prop_rechg_soc_thresh(chg, &pval); + if (rc < 0) { + dev_err(chg->dev, "Couldn't configure CHG_RCHG_SOC_REG rc=%d\n", + rc); + return rc; + } + + /* Program the sample count for SOC based recharge to 1 */ + rc = smblib_masked_write(chg, CHGR_NO_SAMPLE_TERM_RCHG_CFG_REG, + NO_OF_SAMPLE_FOR_RCHG, 0); + if (rc < 0) { + dev_err(chg->dev, "Couldn't configure CHGR_NO_SAMPLE_FOR_TERM_RCHG_CFG rc=%d\n", + rc); + return rc; + } + } + + return 0; +} + +static int smb5_configure_float_charger(struct smb5 *chip) +{ + int rc = 0; + u8 val = 0; + struct smb_charger *chg = &chip->chg; + + /* configure float charger options */ + switch (chip->dt.float_option) { + case FLOAT_SDP: + val = FORCE_FLOAT_SDP_CFG_BIT; + break; + case DISABLE_CHARGING: + val = FLOAT_DIS_CHGING_CFG_BIT; + break; + case SUSPEND_INPUT: + val = SUSPEND_FLOAT_CFG_BIT; + break; + case FLOAT_DCP: + default: + val = 0; + break; + } + + chg->float_cfg = val; + /* Update float charger setting and set DCD timeout 300ms */ + rc = smblib_masked_write(chg, USBIN_OPTIONS_2_CFG_REG, + FLOAT_OPTIONS_MASK | DCD_TIMEOUT_SEL_BIT, val); + if (rc < 0) { + dev_err(chg->dev, "Couldn't change float charger setting rc=%d\n", + rc); + return rc; + } + + return 0; +} + +static int smb5_init_connector_type(struct smb_charger *chg) +{ + int rc, type = 0; + u8 val = 0; + + /* + * PMI632 can have the connector type defined by a dedicated register + * PMI632_TYPEC_MICRO_USB_MODE_REG or by a common TYPEC_U_USB_CFG_REG. + */ + if (chg->chg_param.smb_version == PMI632_SUBTYPE) { + rc = smblib_read(chg, PMI632_TYPEC_MICRO_USB_MODE_REG, &val); + if (rc < 0) { + dev_err(chg->dev, "Couldn't read USB mode rc=%d\n", rc); + return rc; + } + type = !!(val & MICRO_USB_MODE_ONLY_BIT); + } + + /* + * If PMI632_TYPEC_MICRO_USB_MODE_REG is not set and for all non-PMI632 + * check the connector type using TYPEC_U_USB_CFG_REG. + */ + if (!type) { + rc = smblib_read(chg, TYPEC_U_USB_CFG_REG, &val); + if (rc < 0) { + dev_err(chg->dev, "Couldn't read U_USB config rc=%d\n", + rc); + return rc; + } + + type = !!(val & EN_MICRO_USB_MODE_BIT); + } + + pr_debug("Connector type=%s\n", type ? "Micro USB" : "TypeC"); + + if (type) { + chg->connector_type = POWER_SUPPLY_CONNECTOR_MICRO_USB; + rc = smb5_configure_micro_usb(chg); + } else { + chg->connector_type = POWER_SUPPLY_CONNECTOR_TYPEC; + rc = smb5_configure_typec(chg); + } + if (rc < 0) { + dev_err(chg->dev, + "Couldn't configure TypeC/micro-USB mode rc=%d\n", rc); + return rc; + } + + /* + * PMI632 based hw init: + * - Rerun APSD to ensure proper charger detection if device + * boots with charger connected. + * - Initialize flash module for PMI632 + */ + if (chg->chg_param.smb_version == PMI632_SUBTYPE) { + schgm_flash_init(chg); + smblib_rerun_apsd_if_required(chg); + } + + return 0; + +} + +static int smb5_init_hw(struct smb5 *chip) +{ + struct smb_charger *chg = &chip->chg; + int rc; + u8 val = 0, mask = 0, buf[2] = {0}; + + if (chip->dt.no_battery) + chg->fake_capacity = 50; + + if (chg->sdam_base) { + rc = smblib_write(chg, + chg->sdam_base + SDAM_QC_DET_STATUS_REG, 0); + if (rc < 0) + pr_err("Couldn't clear SDAM QC status rc=%d\n", rc); + + rc = smblib_batch_write(chg, + chg->sdam_base + SDAM_QC_ADC_LSB_REG, buf, 2); + if (rc < 0) + pr_err("Couldn't clear SDAM ADC status rc=%d\n", rc); + } + + if (chip->dt.batt_profile_fcc_ua < 0) + smblib_get_charge_param(chg, &chg->param.fcc, + &chg->batt_profile_fcc_ua); + + if (chip->dt.batt_profile_fv_uv < 0) + smblib_get_charge_param(chg, &chg->param.fv, + &chg->batt_profile_fv_uv); + + smblib_get_charge_param(chg, &chg->param.usb_icl, + &chg->default_icl_ua); + smblib_get_charge_param(chg, &chg->param.aicl_5v_threshold, + &chg->default_aicl_5v_threshold_mv); + chg->aicl_5v_threshold_mv = chg->default_aicl_5v_threshold_mv; + smblib_get_charge_param(chg, &chg->param.aicl_cont_threshold, + &chg->default_aicl_cont_threshold_mv); + chg->aicl_cont_threshold_mv = chg->default_aicl_cont_threshold_mv; + + if (chg->charger_temp_max == -EINVAL) { + rc = smblib_get_thermal_threshold(chg, + DIE_REG_H_THRESHOLD_MSB_REG, + &chg->charger_temp_max); + if (rc < 0) { + dev_err(chg->dev, "Couldn't get charger_temp_max rc=%d\n", + rc); + return rc; + } + } + + /* + * If SW thermal regulation WA is active then all the HW temperature + * comparators need to be disabled to prevent HW thermal regulation, + * apart from DIE_TEMP analog comparator for SHDN regulation. + */ + if (chg->wa_flags & SW_THERM_REGULATION_WA) { + rc = smblib_write(chg, MISC_THERMREG_SRC_CFG_REG, + THERMREG_SW_ICL_ADJUST_BIT + | THERMREG_DIE_CMP_SRC_EN_BIT); + if (rc < 0) { + dev_err(chg->dev, "Couldn't disable HW thermal regulation rc=%d\n", + rc); + return rc; + } + } else { + /* configure temperature mitigation */ + rc = smb5_configure_mitigation(chg); + if (rc < 0) { + dev_err(chg->dev, "Couldn't configure mitigation rc=%d\n", + rc); + return rc; + } + } + + /* Set HVDCP autonomous mode per DT option */ + smblib_hvdcp_hw_inov_enable(chg, chip->dt.hvdcp_autonomous); + + /* Enable HVDCP authentication algorithm for non-PD designs */ + if (chg->pd_not_supported) + smblib_hvdcp_detect_enable(chg, true); + + /* Disable HVDCP and authentication algorithm if specified in DT */ + if (chg->hvdcp_disable) + smblib_hvdcp_detect_enable(chg, false); + + rc = smb5_init_connector_type(chg); + if (rc < 0) { + dev_err(chg->dev, "Couldn't configure connector type rc=%d\n", + rc); + return rc; + } + + /* Use ICL results from HW */ + rc = smblib_icl_override(chg, HW_AUTO_MODE); + if (rc < 0) { + pr_err("Couldn't disable ICL override rc=%d\n", rc); + return rc; + } + + /* set OTG current limit */ + rc = smblib_set_charge_param(chg, &chg->param.otg_cl, chg->otg_cl_ua); + if (rc < 0) { + pr_err("Couldn't set otg current limit rc=%d\n", rc); + return rc; + } + + /* vote 0mA on usb_icl for non battery platforms */ + vote(chg->usb_icl_votable, + DEFAULT_VOTER, chip->dt.no_battery, 0); + vote(chg->dc_suspend_votable, + DEFAULT_VOTER, chip->dt.no_battery, 0); + vote(chg->fcc_votable, HW_LIMIT_VOTER, + chip->dt.batt_profile_fcc_ua > 0, chip->dt.batt_profile_fcc_ua); + vote(chg->fv_votable, HW_LIMIT_VOTER, + chip->dt.batt_profile_fv_uv > 0, chip->dt.batt_profile_fv_uv); + vote(chg->fcc_votable, + BATT_PROFILE_VOTER, chg->batt_profile_fcc_ua > 0, + chg->batt_profile_fcc_ua); + vote(chg->fv_votable, + BATT_PROFILE_VOTER, chg->batt_profile_fv_uv > 0, + chg->batt_profile_fv_uv); + + /* Some h/w limit maximum supported ICL */ + vote(chg->usb_icl_votable, HW_LIMIT_VOTER, + chg->hw_max_icl_ua > 0, chg->hw_max_icl_ua); + + /* Initialize DC peripheral configurations */ + rc = smb5_init_dc_peripheral(chg); + if (rc < 0) + return rc; + + /* + * AICL configuration: enable aicl and aicl rerun and based on DT + * configuration enable/disable ADB based AICL and Suspend on collapse. + */ + mask = USBIN_AICL_PERIODIC_RERUN_EN_BIT | USBIN_AICL_ADC_EN_BIT + | USBIN_AICL_EN_BIT | SUSPEND_ON_COLLAPSE_USBIN_BIT; + val = USBIN_AICL_PERIODIC_RERUN_EN_BIT | USBIN_AICL_EN_BIT; + if (!chip->dt.disable_suspend_on_collapse) + val |= SUSPEND_ON_COLLAPSE_USBIN_BIT; + if (chip->dt.adc_based_aicl) + val |= USBIN_AICL_ADC_EN_BIT; + + rc = smblib_masked_write(chg, USBIN_AICL_OPTIONS_CFG_REG, + mask, val); + if (rc < 0) { + dev_err(chg->dev, "Couldn't config AICL rc=%d\n", rc); + return rc; + } + + rc = smblib_write(chg, AICL_RERUN_TIME_CFG_REG, + AICL_RERUN_TIME_12S_VAL); + if (rc < 0) { + dev_err(chg->dev, + "Couldn't configure AICL rerun interval rc=%d\n", rc); + return rc; + } + + /* enable the charging path */ + rc = vote(chg->chg_disable_votable, DEFAULT_VOTER, false, 0); + if (rc < 0) { + dev_err(chg->dev, "Couldn't enable charging rc=%d\n", rc); + return rc; + } + + /* configure VBUS for software control */ + rc = smblib_masked_write(chg, DCDC_OTG_CFG_REG, OTG_EN_SRC_CFG_BIT, 0); + if (rc < 0) { + dev_err(chg->dev, + "Couldn't configure VBUS for SW control rc=%d\n", rc); + return rc; + } + + val = (ilog2(chip->dt.wd_bark_time / 16) << BARK_WDOG_TIMEOUT_SHIFT) + & BARK_WDOG_TIMEOUT_MASK; + val |= (BITE_WDOG_TIMEOUT_8S | BITE_WDOG_DISABLE_CHARGING_CFG_BIT); + val |= (chip->dt.wd_snarl_time_cfg << SNARL_WDOG_TIMEOUT_SHIFT) + & SNARL_WDOG_TIMEOUT_MASK; + + rc = smblib_masked_write(chg, SNARL_BARK_BITE_WD_CFG_REG, + BITE_WDOG_DISABLE_CHARGING_CFG_BIT | + SNARL_WDOG_TIMEOUT_MASK | BARK_WDOG_TIMEOUT_MASK | + BITE_WDOG_TIMEOUT_MASK, + val); + if (rc < 0) { + pr_err("Couldn't configue WD config rc=%d\n", rc); + return rc; + } + + /* enable WD BARK and enable it on plugin */ + val = WDOG_TIMER_EN_ON_PLUGIN_BIT | BARK_WDOG_INT_EN_BIT; + rc = smblib_masked_write(chg, WD_CFG_REG, + WATCHDOG_TRIGGER_AFP_EN_BIT | + WDOG_TIMER_EN_ON_PLUGIN_BIT | + BARK_WDOG_INT_EN_BIT, val); + if (rc < 0) { + pr_err("Couldn't configue WD config rc=%d\n", rc); + return rc; + } + + /* set termination current threshold values */ + rc = smb5_configure_iterm_thresholds(chip); + if (rc < 0) { + pr_err("Couldn't configure ITERM thresholds rc=%d\n", + rc); + return rc; + } + + rc = smb5_configure_float_charger(chip); + if (rc < 0) + return rc; + + switch (chip->dt.chg_inhibit_thr_mv) { + case 50: + rc = smblib_masked_write(chg, CHARGE_INHIBIT_THRESHOLD_CFG_REG, + CHARGE_INHIBIT_THRESHOLD_MASK, + INHIBIT_ANALOG_VFLT_MINUS_50MV); + break; + case 100: + rc = smblib_masked_write(chg, CHARGE_INHIBIT_THRESHOLD_CFG_REG, + CHARGE_INHIBIT_THRESHOLD_MASK, + INHIBIT_ANALOG_VFLT_MINUS_100MV); + break; + case 200: + rc = smblib_masked_write(chg, CHARGE_INHIBIT_THRESHOLD_CFG_REG, + CHARGE_INHIBIT_THRESHOLD_MASK, + INHIBIT_ANALOG_VFLT_MINUS_200MV); + break; + case 300: + rc = smblib_masked_write(chg, CHARGE_INHIBIT_THRESHOLD_CFG_REG, + CHARGE_INHIBIT_THRESHOLD_MASK, + INHIBIT_ANALOG_VFLT_MINUS_300MV); + break; + case 0: + rc = smblib_masked_write(chg, CHGR_CFG2_REG, + CHARGER_INHIBIT_BIT, 0); + default: + break; + } + + if (rc < 0) { + dev_err(chg->dev, "Couldn't configure charge inhibit threshold rc=%d\n", + rc); + return rc; + } + + rc = smblib_write(chg, CHGR_FAST_CHARGE_SAFETY_TIMER_CFG_REG, + FAST_CHARGE_SAFETY_TIMER_768_MIN); + if (rc < 0) { + dev_err(chg->dev, "Couldn't set CHGR_FAST_CHARGE_SAFETY_TIMER_CFG_REG rc=%d\n", + rc); + return rc; + } + + rc = smb5_configure_recharging(chip); + if (rc < 0) + return rc; + + rc = smblib_disable_hw_jeita(chg, true); + if (rc < 0) { + dev_err(chg->dev, "Couldn't set hw jeita rc=%d\n", rc); + return rc; + } + + rc = smblib_masked_write(chg, DCDC_ENG_SDCDC_CFG5_REG, + ENG_SDCDC_BAT_HPWR_MASK, BOOST_MODE_THRESH_3P6_V); + if (rc < 0) { + dev_err(chg->dev, "Couldn't configure DCDC_ENG_SDCDC_CFG5 rc=%d\n", + rc); + return rc; + } + + if (chg->connector_pull_up != -EINVAL) { + rc = smb5_configure_internal_pull(chg, CONN_THERM, + get_valid_pullup(chg->connector_pull_up)); + if (rc < 0) { + dev_err(chg->dev, + "Couldn't configure CONN_THERM pull-up rc=%d\n", + rc); + return rc; + } + } + + if (chg->smb_pull_up != -EINVAL) { + rc = smb5_configure_internal_pull(chg, SMB_THERM, + get_valid_pullup(chg->smb_pull_up)); + if (rc < 0) { + dev_err(chg->dev, + "Couldn't configure SMB pull-up rc=%d\n", + rc); + return rc; + } + } + + return rc; +} + +static int smb5_post_init(struct smb5 *chip) +{ + struct smb_charger *chg = &chip->chg; + union power_supply_propval pval; + int rc; + + /* + * In case the usb path is suspended, we would have missed disabling + * the icl change interrupt because the interrupt could have been + * not requested + */ + rerun_election(chg->usb_icl_votable); + + /* configure power role for dual-role */ + pval.intval = POWER_SUPPLY_TYPEC_PR_DUAL; + rc = smblib_set_prop_typec_power_role(chg, &pval); + if (rc < 0) { + dev_err(chg->dev, "Couldn't configure DRP role rc=%d\n", + rc); + return rc; + } + + rerun_election(chg->temp_change_irq_disable_votable); + + return 0; +} + +/**************************** + * DETERMINE INITIAL STATUS * + ****************************/ + +static int smb5_determine_initial_status(struct smb5 *chip) +{ + struct smb_irq_data irq_data = {chip, "determine-initial-status"}; + struct smb_charger *chg = &chip->chg; + union power_supply_propval val; + int rc; + + rc = smblib_get_prop_usb_present(chg, &val); + if (rc < 0) { + pr_err("Couldn't get usb present rc=%d\n", rc); + return rc; + } + chg->early_usb_attach = val.intval; + + if (chg->bms_psy) + smblib_suspend_on_debug_battery(chg); + + usb_plugin_irq_handler(0, &irq_data); + dc_plugin_irq_handler(0, &irq_data); + typec_attach_detach_irq_handler(0, &irq_data); + typec_state_change_irq_handler(0, &irq_data); + usb_source_change_irq_handler(0, &irq_data); + chg_state_change_irq_handler(0, &irq_data); + icl_change_irq_handler(0, &irq_data); + batt_temp_changed_irq_handler(0, &irq_data); + wdog_bark_irq_handler(0, &irq_data); + typec_or_rid_detection_change_irq_handler(0, &irq_data); + wdog_snarl_irq_handler(0, &irq_data); + + return 0; +} + +/************************** + * INTERRUPT REGISTRATION * + **************************/ + +static struct smb_irq_info smb5_irqs[] = { + /* CHARGER IRQs */ + [CHGR_ERROR_IRQ] = { + .name = "chgr-error", + .handler = default_irq_handler, + }, + [CHG_STATE_CHANGE_IRQ] = { + .name = "chg-state-change", + .handler = chg_state_change_irq_handler, + .wake = true, + }, + [STEP_CHG_STATE_CHANGE_IRQ] = { + .name = "step-chg-state-change", + }, + [STEP_CHG_SOC_UPDATE_FAIL_IRQ] = { + .name = "step-chg-soc-update-fail", + }, + [STEP_CHG_SOC_UPDATE_REQ_IRQ] = { + .name = "step-chg-soc-update-req", + }, + [FG_FVCAL_QUALIFIED_IRQ] = { + .name = "fg-fvcal-qualified", + }, + [VPH_ALARM_IRQ] = { + .name = "vph-alarm", + }, + [VPH_DROP_PRECHG_IRQ] = { + .name = "vph-drop-prechg", + }, + /* DCDC IRQs */ + [OTG_FAIL_IRQ] = { + .name = "otg-fail", + .handler = default_irq_handler, + }, + [OTG_OC_DISABLE_SW_IRQ] = { + .name = "otg-oc-disable-sw", + }, + [OTG_OC_HICCUP_IRQ] = { + .name = "otg-oc-hiccup", + }, + [BSM_ACTIVE_IRQ] = { + .name = "bsm-active", + }, + [HIGH_DUTY_CYCLE_IRQ] = { + .name = "high-duty-cycle", + .handler = high_duty_cycle_irq_handler, + .wake = true, + }, + [INPUT_CURRENT_LIMITING_IRQ] = { + .name = "input-current-limiting", + .handler = default_irq_handler, + }, + [CONCURRENT_MODE_DISABLE_IRQ] = { + .name = "concurrent-mode-disable", + }, + [SWITCHER_POWER_OK_IRQ] = { + .name = "switcher-power-ok", + .handler = switcher_power_ok_irq_handler, + }, + /* BATTERY IRQs */ + [BAT_TEMP_IRQ] = { + .name = "bat-temp", + .handler = batt_temp_changed_irq_handler, + .wake = true, + }, + [ALL_CHNL_CONV_DONE_IRQ] = { + .name = "all-chnl-conv-done", + }, + [BAT_OV_IRQ] = { + .name = "bat-ov", + .handler = batt_psy_changed_irq_handler, + }, + [BAT_LOW_IRQ] = { + .name = "bat-low", + .handler = batt_psy_changed_irq_handler, + }, + [BAT_THERM_OR_ID_MISSING_IRQ] = { + .name = "bat-therm-or-id-missing", + .handler = batt_psy_changed_irq_handler, + }, + [BAT_TERMINAL_MISSING_IRQ] = { + .name = "bat-terminal-missing", + .handler = batt_psy_changed_irq_handler, + }, + [BUCK_OC_IRQ] = { + .name = "buck-oc", + }, + [VPH_OV_IRQ] = { + .name = "vph-ov", + }, + /* USB INPUT IRQs */ + [USBIN_COLLAPSE_IRQ] = { + .name = "usbin-collapse", + .handler = default_irq_handler, + }, + [USBIN_VASHDN_IRQ] = { + .name = "usbin-vashdn", + .handler = default_irq_handler, + }, + [USBIN_UV_IRQ] = { + .name = "usbin-uv", + .handler = usbin_uv_irq_handler, + .wake = true, + .storm_data = {true, 3000, 5}, + }, + [USBIN_OV_IRQ] = { + .name = "usbin-ov", + .handler = usbin_ov_irq_handler, + }, + [USBIN_PLUGIN_IRQ] = { + .name = "usbin-plugin", + .handler = usb_plugin_irq_handler, + .wake = true, + }, + [USBIN_REVI_CHANGE_IRQ] = { + .name = "usbin-revi-change", + }, + [USBIN_SRC_CHANGE_IRQ] = { + .name = "usbin-src-change", + .handler = usb_source_change_irq_handler, + .wake = true, + }, + [USBIN_ICL_CHANGE_IRQ] = { + .name = "usbin-icl-change", + .handler = icl_change_irq_handler, + .wake = true, + }, + /* DC INPUT IRQs */ + [DCIN_VASHDN_IRQ] = { + .name = "dcin-vashdn", + }, + [DCIN_UV_IRQ] = { + .name = "dcin-uv", + .handler = dcin_uv_irq_handler, + .wake = true, + }, + [DCIN_OV_IRQ] = { + .name = "dcin-ov", + .handler = default_irq_handler, + }, + [DCIN_PLUGIN_IRQ] = { + .name = "dcin-plugin", + .handler = dc_plugin_irq_handler, + .wake = true, + }, + [DCIN_REVI_IRQ] = { + .name = "dcin-revi", + }, + [DCIN_PON_IRQ] = { + .name = "dcin-pon", + .handler = default_irq_handler, + }, + [DCIN_EN_IRQ] = { + .name = "dcin-en", + .handler = default_irq_handler, + }, + /* TYPEC IRQs */ + [TYPEC_OR_RID_DETECTION_CHANGE_IRQ] = { + .name = "typec-or-rid-detect-change", + .handler = typec_or_rid_detection_change_irq_handler, + .wake = true, + }, + [TYPEC_VPD_DETECT_IRQ] = { + .name = "typec-vpd-detect", + }, + [TYPEC_CC_STATE_CHANGE_IRQ] = { + .name = "typec-cc-state-change", + .handler = typec_state_change_irq_handler, + .wake = true, + }, + [TYPEC_VCONN_OC_IRQ] = { + .name = "typec-vconn-oc", + .handler = default_irq_handler, + }, + [TYPEC_VBUS_CHANGE_IRQ] = { + .name = "typec-vbus-change", + }, + [TYPEC_ATTACH_DETACH_IRQ] = { + .name = "typec-attach-detach", + .handler = typec_attach_detach_irq_handler, + .wake = true, + }, + [TYPEC_LEGACY_CABLE_DETECT_IRQ] = { + .name = "typec-legacy-cable-detect", + .handler = default_irq_handler, + }, + [TYPEC_TRY_SNK_SRC_DETECT_IRQ] = { + .name = "typec-try-snk-src-detect", + }, + /* MISCELLANEOUS IRQs */ + [WDOG_SNARL_IRQ] = { + .name = "wdog-snarl", + .handler = wdog_snarl_irq_handler, + .wake = true, + }, + [WDOG_BARK_IRQ] = { + .name = "wdog-bark", + .handler = wdog_bark_irq_handler, + .wake = true, + }, + [AICL_FAIL_IRQ] = { + .name = "aicl-fail", + }, + [AICL_DONE_IRQ] = { + .name = "aicl-done", + .handler = default_irq_handler, + }, + [SMB_EN_IRQ] = { + .name = "smb-en", + .handler = smb_en_irq_handler, + }, + [IMP_TRIGGER_IRQ] = { + .name = "imp-trigger", + }, + /* + * triggered when DIE or SKIN or CONNECTOR temperature across + * either of the _REG_L, _REG_H, _RST, or _SHDN thresholds + */ + [TEMP_CHANGE_IRQ] = { + .name = "temp-change", + .handler = temp_change_irq_handler, + .wake = true, + }, + [TEMP_CHANGE_SMB_IRQ] = { + .name = "temp-change-smb", + }, + /* FLASH */ + [VREG_OK_IRQ] = { + .name = "vreg-ok", + }, + [ILIM_S2_IRQ] = { + .name = "ilim2-s2", + .handler = schgm_flash_ilim2_irq_handler, + }, + [ILIM_S1_IRQ] = { + .name = "ilim1-s1", + }, + [VOUT_DOWN_IRQ] = { + .name = "vout-down", + }, + [VOUT_UP_IRQ] = { + .name = "vout-up", + }, + [FLASH_STATE_CHANGE_IRQ] = { + .name = "flash-state-change", + .handler = schgm_flash_state_change_irq_handler, + }, + [TORCH_REQ_IRQ] = { + .name = "torch-req", + }, + [FLASH_EN_IRQ] = { + .name = "flash-en", + }, + /* SDAM */ + [SDAM_STS_IRQ] = { + .name = "sdam-sts", + .handler = sdam_sts_change_irq_handler, + }, +}; + +static int smb5_get_irq_index_byname(const char *irq_name) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(smb5_irqs); i++) { + if (strcmp(smb5_irqs[i].name, irq_name) == 0) + return i; + } + + return -ENOENT; +} + +static int smb5_request_interrupt(struct smb5 *chip, + struct device_node *node, const char *irq_name) +{ + struct smb_charger *chg = &chip->chg; + int rc, irq, irq_index; + struct smb_irq_data *irq_data; + + irq = of_irq_get_byname(node, irq_name); + if (irq < 0) { + pr_err("Couldn't get irq %s byname\n", irq_name); + return irq; + } + + irq_index = smb5_get_irq_index_byname(irq_name); + if (irq_index < 0) { + pr_err("%s is not a defined irq\n", irq_name); + return irq_index; + } + + if (!smb5_irqs[irq_index].handler) + return 0; + + irq_data = devm_kzalloc(chg->dev, sizeof(*irq_data), GFP_KERNEL); + if (!irq_data) + return -ENOMEM; + + irq_data->parent_data = chip; + irq_data->name = irq_name; + irq_data->storm_data = smb5_irqs[irq_index].storm_data; + mutex_init(&irq_data->storm_data.storm_lock); + + smb5_irqs[irq_index].enabled = true; + rc = devm_request_threaded_irq(chg->dev, irq, NULL, + smb5_irqs[irq_index].handler, + IRQF_ONESHOT, irq_name, irq_data); + if (rc < 0) { + pr_err("Couldn't request irq %d\n", irq); + return rc; + } + + smb5_irqs[irq_index].irq = irq; + smb5_irqs[irq_index].irq_data = irq_data; + if (smb5_irqs[irq_index].wake) + enable_irq_wake(irq); + + return rc; +} + +static int smb5_request_interrupts(struct smb5 *chip) +{ + struct smb_charger *chg = &chip->chg; + struct device_node *node = chg->dev->of_node; + struct device_node *child; + int rc = 0; + const char *name; + struct property *prop; + + for_each_available_child_of_node(node, child) { + of_property_for_each_string(child, "interrupt-names", + prop, name) { + rc = smb5_request_interrupt(chip, child, name); + if (rc < 0) + return rc; + } + } + + vote(chg->limited_irq_disable_votable, CHARGER_TYPE_VOTER, true, 0); + vote(chg->hdc_irq_disable_votable, CHARGER_TYPE_VOTER, true, 0); + + return rc; +} + +static void smb5_free_interrupts(struct smb_charger *chg) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(smb5_irqs); i++) { + if (smb5_irqs[i].irq > 0) + if (smb5_irqs[i].wake) + disable_irq_wake(smb5_irqs[i].irq); + } +} + +static void smb5_disable_interrupts(struct smb_charger *chg) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(smb5_irqs); i++) { + if (smb5_irqs[i].irq > 0) + disable_irq(smb5_irqs[i].irq); + } +} + +#if defined(CONFIG_DEBUG_FS) + +static int force_batt_psy_update_write(void *data, u64 val) +{ + struct smb_charger *chg = data; + + power_supply_changed(chg->batt_psy); + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(force_batt_psy_update_ops, NULL, + force_batt_psy_update_write, "0x%02llx\n"); + +static int force_usb_psy_update_write(void *data, u64 val) +{ + struct smb_charger *chg = data; + + power_supply_changed(chg->usb_psy); + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(force_usb_psy_update_ops, NULL, + force_usb_psy_update_write, "0x%02llx\n"); + +static int force_dc_psy_update_write(void *data, u64 val) +{ + struct smb_charger *chg = data; + + power_supply_changed(chg->dc_psy); + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(force_dc_psy_update_ops, NULL, + force_dc_psy_update_write, "0x%02llx\n"); + +static void smb5_create_debugfs(struct smb5 *chip) +{ + struct dentry *file; + + chip->dfs_root = debugfs_create_dir("charger", NULL); + if (IS_ERR_OR_NULL(chip->dfs_root)) { + pr_err("Couldn't create charger debugfs rc=%ld\n", + (long)chip->dfs_root); + return; + } + + file = debugfs_create_file("force_batt_psy_update", 0600, + chip->dfs_root, chip, &force_batt_psy_update_ops); + if (IS_ERR_OR_NULL(file)) + pr_err("Couldn't create force_batt_psy_update file rc=%ld\n", + (long)file); + + file = debugfs_create_file("force_usb_psy_update", 0600, + chip->dfs_root, chip, &force_usb_psy_update_ops); + if (IS_ERR_OR_NULL(file)) + pr_err("Couldn't create force_usb_psy_update file rc=%ld\n", + (long)file); + + file = debugfs_create_file("force_dc_psy_update", 0600, + chip->dfs_root, chip, &force_dc_psy_update_ops); + if (IS_ERR_OR_NULL(file)) + pr_err("Couldn't create force_dc_psy_update file rc=%ld\n", + (long)file); + + file = debugfs_create_u32("debug_mask", 0600, chip->dfs_root, + &__debug_mask); + if (IS_ERR_OR_NULL(file)) + pr_err("Couldn't create debug_mask file rc=%ld\n", (long)file); +} + +#else + +static void smb5_create_debugfs(struct smb5 *chip) +{} + +#endif + +static int smb5_show_charger_status(struct smb5 *chip) +{ + struct smb_charger *chg = &chip->chg; + union power_supply_propval val; + int usb_present, batt_present, batt_health, batt_charge_type; + int rc; + + rc = smblib_get_prop_usb_present(chg, &val); + if (rc < 0) { + pr_err("Couldn't get usb present rc=%d\n", rc); + return rc; + } + usb_present = val.intval; + + rc = smblib_get_prop_batt_present(chg, &val); + if (rc < 0) { + pr_err("Couldn't get batt present rc=%d\n", rc); + return rc; + } + batt_present = val.intval; + + rc = smblib_get_prop_batt_health(chg, &val); + if (rc < 0) { + pr_err("Couldn't get batt health rc=%d\n", rc); + val.intval = POWER_SUPPLY_HEALTH_UNKNOWN; + } + batt_health = val.intval; + + rc = smblib_get_prop_batt_charge_type(chg, &val); + if (rc < 0) { + pr_err("Couldn't get batt charge type rc=%d\n", rc); + return rc; + } + batt_charge_type = val.intval; + + pr_info("SMB5 status - usb:present=%d type=%d batt:present = %d health = %d charge = %d\n", + usb_present, chg->real_charger_type, + batt_present, batt_health, batt_charge_type); + return rc; +} + +/********************************* + * TYPEC CLASS REGISTRATION * + **********************************/ + +static int smb5_init_typec_class(struct smb5 *chip) +{ + struct smb_charger *chg = &chip->chg; + int rc = 0; + + /* Register typec class for only non-PD TypeC and uUSB designs */ + if (!chg->pd_not_supported) + return rc; + + mutex_init(&chg->typec_lock); + chg->typec_caps.type = TYPEC_PORT_DRP; + chg->typec_caps.data = TYPEC_PORT_DRD; + chg->typec_partner_desc.usb_pd = false; + chg->typec_partner_desc.accessory = TYPEC_ACCESSORY_NONE; + chg->typec_caps.port_type_set = smblib_typec_port_type_set; + chg->typec_caps.revision = 0x0130; + + chg->typec_port = typec_register_port(chg->dev, &chg->typec_caps); + if (IS_ERR(chg->typec_port)) { + rc = PTR_ERR(chg->typec_port); + pr_err("failed to register typec_port rc=%d\n", rc); + return rc; + } + + return rc; +} + +static int smb5_probe(struct platform_device *pdev) +{ + struct smb5 *chip; + struct smb_charger *chg; + int rc = 0; + + chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chg = &chip->chg; + chg->dev = &pdev->dev; + chg->debug_mask = &__debug_mask; + chg->pd_disabled = 0; + chg->weak_chg_icl_ua = 500000; + chg->mode = PARALLEL_MASTER; + chg->irq_info = smb5_irqs; + chg->die_health = -EINVAL; + chg->connector_health = -EINVAL; + chg->otg_present = false; + chg->main_fcc_max = -EINVAL; + mutex_init(&chg->adc_lock); + + chg->regmap = dev_get_regmap(chg->dev->parent, NULL); + if (!chg->regmap) { + pr_err("parent regmap is missing\n"); + return -EINVAL; + } + + rc = smb5_chg_config_init(chip); + if (rc < 0) { + if (rc != -EPROBE_DEFER) + pr_err("Couldn't setup chg_config rc=%d\n", rc); + return rc; + } + + rc = smb5_parse_dt(chip); + if (rc < 0) { + pr_err("Couldn't parse device tree rc=%d\n", rc); + return rc; + } + + if (alarmtimer_get_rtcdev()) + alarm_init(&chg->lpd_recheck_timer, ALARM_REALTIME, + smblib_lpd_recheck_timer); + else + return -EPROBE_DEFER; + + rc = smblib_init(chg); + if (rc < 0) { + pr_err("Smblib_init failed rc=%d\n", rc); + return rc; + } + + /* set driver data before resources request it */ + platform_set_drvdata(pdev, chip); + + /* extcon registration */ + chg->extcon = devm_extcon_dev_allocate(chg->dev, smblib_extcon_cable); + if (IS_ERR(chg->extcon)) { + rc = PTR_ERR(chg->extcon); + dev_err(chg->dev, "failed to allocate extcon device rc=%d\n", + rc); + goto cleanup; + } + + rc = devm_extcon_dev_register(chg->dev, chg->extcon); + if (rc < 0) { + dev_err(chg->dev, "failed to register extcon device rc=%d\n", + rc); + goto cleanup; + } + + /* Support reporting polarity and speed via properties */ + rc = extcon_set_property_capability(chg->extcon, + EXTCON_USB, EXTCON_PROP_USB_TYPEC_POLARITY); + rc |= extcon_set_property_capability(chg->extcon, + EXTCON_USB, EXTCON_PROP_USB_SS); + rc |= extcon_set_property_capability(chg->extcon, + EXTCON_USB_HOST, EXTCON_PROP_USB_TYPEC_POLARITY); + rc |= extcon_set_property_capability(chg->extcon, + EXTCON_USB_HOST, EXTCON_PROP_USB_SS); + if (rc < 0) { + dev_err(chg->dev, + "failed to configure extcon capabilities\n"); + goto cleanup; + } + + rc = smb5_init_hw(chip); + if (rc < 0) { + pr_err("Couldn't initialize hardware rc=%d\n", rc); + goto cleanup; + } + + /* + * VBUS regulator enablement/disablement for host mode is handled + * by USB-PD driver only. For micro-USB and non-PD typeC designs, + * the VBUS regulator is enabled/disabled by the smb driver itself + * before sending extcon notifications. + * Hence, register vbus and vconn regulators for PD supported designs + * only. + */ + if (!chg->pd_not_supported) { + rc = smb5_init_vbus_regulator(chip); + if (rc < 0) { + pr_err("Couldn't initialize vbus regulator rc=%d\n", + rc); + goto cleanup; + } + + rc = smb5_init_vconn_regulator(chip); + if (rc < 0) { + pr_err("Couldn't initialize vconn regulator rc=%d\n", + rc); + goto cleanup; + } + } + + switch (chg->chg_param.smb_version) { + case PM8150B_SUBTYPE: + case PM6150_SUBTYPE: + case PM7250B_SUBTYPE: + rc = smb5_init_dc_psy(chip); + if (rc < 0) { + pr_err("Couldn't initialize dc psy rc=%d\n", rc); + goto cleanup; + } + break; + default: + break; + } + + rc = smb5_init_usb_psy(chip); + if (rc < 0) { + pr_err("Couldn't initialize usb psy rc=%d\n", rc); + goto cleanup; + } + + rc = smb5_init_usb_main_psy(chip); + if (rc < 0) { + pr_err("Couldn't initialize usb main psy rc=%d\n", rc); + goto cleanup; + } + + rc = smb5_init_usb_port_psy(chip); + if (rc < 0) { + pr_err("Couldn't initialize usb pc_port psy rc=%d\n", rc); + goto cleanup; + } + + rc = smb5_init_batt_psy(chip); + if (rc < 0) { + pr_err("Couldn't initialize batt psy rc=%d\n", rc); + goto cleanup; + } + + rc = smb5_init_typec_class(chip); + if (rc < 0) { + pr_err("Couldn't initialize typec class rc=%d\n", rc); + goto cleanup; + } + + rc = smb5_determine_initial_status(chip); + if (rc < 0) { + pr_err("Couldn't determine initial status rc=%d\n", + rc); + goto cleanup; + } + + rc = smb5_request_interrupts(chip); + if (rc < 0) { + pr_err("Couldn't request interrupts rc=%d\n", rc); + goto cleanup; + } + + rc = smb5_post_init(chip); + if (rc < 0) { + pr_err("Failed in post init rc=%d\n", rc); + goto free_irq; + } + + smb5_create_debugfs(chip); + + rc = sysfs_create_groups(&chg->dev->kobj, smb5_groups); + if (rc < 0) { + pr_err("Couldn't create sysfs files rc=%d\n", rc); + goto free_irq; + } + + rc = smb5_show_charger_status(chip); + if (rc < 0) { + pr_err("Failed in getting charger status rc=%d\n", rc); + goto free_irq; + } + + device_init_wakeup(chg->dev, true); + + pr_info("QPNP SMB5 probed successfully\n"); + + return rc; + +free_irq: + smb5_free_interrupts(chg); +cleanup: + smblib_deinit(chg); + platform_set_drvdata(pdev, NULL); + + return rc; +} + +static int smb5_remove(struct platform_device *pdev) +{ + struct smb5 *chip = platform_get_drvdata(pdev); + struct smb_charger *chg = &chip->chg; + + /* force enable APSD */ + smblib_masked_write(chg, USBIN_OPTIONS_1_CFG_REG, + BC1P2_SRC_DETECT_BIT, BC1P2_SRC_DETECT_BIT); + + smb5_free_interrupts(chg); + smblib_deinit(chg); + sysfs_remove_groups(&chg->dev->kobj, smb5_groups); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static void smb5_shutdown(struct platform_device *pdev) +{ + struct smb5 *chip = platform_get_drvdata(pdev); + struct smb_charger *chg = &chip->chg; + + /* disable all interrupts */ + smb5_disable_interrupts(chg); + + /* disable the SMB_EN configuration */ + smblib_masked_write(chg, MISC_SMB_EN_CMD_REG, EN_CP_CMD_BIT, 0); + + /* configure power role for UFP */ + if (chg->connector_type == POWER_SUPPLY_CONNECTOR_TYPEC) + smblib_masked_write(chg, TYPE_C_MODE_CFG_REG, + TYPEC_POWER_ROLE_CMD_MASK, EN_SNK_ONLY_BIT); + + /* force enable and rerun APSD */ + smblib_apsd_enable(chg, true); + smblib_hvdcp_exit_config(chg); +} + +static const struct of_device_id match_table[] = { + { .compatible = "qcom,qpnp-smb5", }, + { }, +}; + +static struct platform_driver smb5_driver = { + .driver = { + .name = "qcom,qpnp-smb5", + .of_match_table = match_table, + }, + .probe = smb5_probe, + .remove = smb5_remove, + .shutdown = smb5_shutdown, +}; +module_platform_driver(smb5_driver); + +MODULE_DESCRIPTION("QPNP SMB5 Charger Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/power/supply/qcom/schgm-flash.c b/drivers/power/supply/qcom/schgm-flash.c new file mode 100644 index 000000000000..b92ace1d4300 --- /dev/null +++ b/drivers/power/supply/qcom/schgm-flash.c @@ -0,0 +1,239 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2018 The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "SCHG-FLASH: %s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "smb5-lib.h" +#include "schgm-flash.h" + +#define IS_BETWEEN(left, right, value) \ + (((left) >= (right) && (left) >= (value) \ + && (value) >= (right)) \ + || ((left) <= (right) && (left) <= (value) \ + && (value) <= (right))) + +irqreturn_t schgm_flash_default_irq_handler(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + + pr_debug("IRQ: %s\n", irq_data->name); + + return IRQ_HANDLED; +} + +irqreturn_t schgm_flash_ilim2_irq_handler(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + int rc; + + rc = smblib_write(chg, SCHGM_FLASH_S2_LATCH_RESET_CMD_REG, + FLASH_S2_LATCH_RESET_BIT); + if (rc < 0) + pr_err("Couldn't reset S2_LATCH reset rc=%d\n", rc); + + return IRQ_HANDLED; +} + +irqreturn_t schgm_flash_state_change_irq_handler(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + int rc; + u8 reg; + + rc = smblib_read(chg, SCHGM_FLASH_STATUS_3_REG, ®); + if (rc < 0) + pr_err("Couldn't read flash status_3 rc=%d\n", rc); + else + pr_debug("Flash status changed state=[%x]\n", + (reg && FLASH_STATE_MASK)); + + return IRQ_HANDLED; +} + +#define FIXED_MODE 0 +#define ADAPTIVE_MODE 1 +static void schgm_flash_parse_dt(struct smb_charger *chg) +{ + struct device_node *node = chg->dev->of_node; + u32 val; + int rc; + + chg->flash_derating_soc = -EINVAL; + rc = of_property_read_u32(node, "qcom,flash-derating-soc", &val); + if (!rc) { + if (IS_BETWEEN(0, 100, val)) + chg->flash_derating_soc = (val * 255) / 100; + } + + chg->flash_disable_soc = -EINVAL; + rc = of_property_read_u32(node, "qcom,flash-disable-soc", &val); + if (!rc) { + if (IS_BETWEEN(0, 100, val)) + chg->flash_disable_soc = (val * 255) / 100; + } + + chg->headroom_mode = -EINVAL; + rc = of_property_read_u32(node, "qcom,headroom-mode", &val); + if (!rc) { + if (IS_BETWEEN(FIXED_MODE, ADAPTIVE_MODE, val)) + chg->headroom_mode = val; + } +} + +bool is_flash_active(struct smb_charger *chg) +{ + return chg->flash_active ? true : false; +} + +int schgm_flash_get_vreg_ok(struct smb_charger *chg, int *val) +{ + int rc, vreg_state; + u8 stat = 0; + + if (!chg->flash_init_done) + return -EPERM; + + rc = smblib_read(chg, SCHGM_FLASH_STATUS_2_REG, &stat); + if (rc < 0) { + pr_err("Couldn't read FLASH STATUS_2 rc=%d\n", rc); + return rc; + } + vreg_state = !!(stat & VREG_OK_BIT); + + /* If VREG_OK is not set check for flash error */ + if (!vreg_state) { + rc = smblib_read(chg, SCHGM_FLASH_STATUS_3_REG, &stat); + if (rc < 0) { + pr_err("Couldn't read FLASH_STATUS_3 rc=%d\n", rc); + return rc; + } + if ((stat & FLASH_STATE_MASK) == FLASH_ERROR_VAL) { + vreg_state = -EFAULT; + rc = smblib_read(chg, SCHGM_FLASH_STATUS_5_REG, + &stat); + if (rc < 0) { + pr_err("Couldn't read FLASH_STATUS_5 rc=%d\n", + rc); + return rc; + } + pr_debug("Flash error: status=%x\n", stat); + } + } + + /* + * val can be one of the following: + * 1 - VREG_OK is set. + * 0 - VREG_OK is 0 but no Flash error. + * -EFAULT - Flash Error is set. + */ + *val = vreg_state; + + return 0; +} + +void schgm_flash_torch_priority(struct smb_charger *chg, enum torch_mode mode) +{ + int rc; + u8 reg; + + /* + * If torch is configured in default BOOST mode, skip any update in the + * mode configuration. + */ + if (chg->headroom_mode == FIXED_MODE) + return; + + if ((mode != TORCH_BOOST_MODE) && (mode != TORCH_BUCK_MODE)) + return; + + reg = mode; + rc = smblib_masked_write(chg, SCHGM_TORCH_PRIORITY_CONTROL_REG, + TORCH_PRIORITY_CONTROL_BIT, reg); + if (rc < 0) + pr_err("Couldn't configure Torch priority control rc=%d\n", + rc); + + pr_debug("Torch priority changed to: %d\n", mode); +} + +int schgm_flash_init(struct smb_charger *chg) +{ + int rc; + u8 reg; + + schgm_flash_parse_dt(chg); + + if (chg->flash_derating_soc != -EINVAL) { + rc = smblib_write(chg, SCHGM_SOC_BASED_FLASH_DERATE_TH_CFG_REG, + chg->flash_derating_soc); + if (rc < 0) { + pr_err("Couldn't configure SOC for flash derating rc=%d\n", + rc); + return rc; + } + } + + if (chg->flash_disable_soc != -EINVAL) { + rc = smblib_write(chg, SCHGM_SOC_BASED_FLASH_DISABLE_TH_CFG_REG, + chg->flash_disable_soc); + if (rc < 0) { + pr_err("Couldn't configure SOC for flash disable rc=%d\n", + rc); + return rc; + } + } + + if (chg->headroom_mode != -EINVAL) { + /* + * configure headroom management policy for + * flash and torch mode. + */ + reg = (chg->headroom_mode == FIXED_MODE) + ? FORCE_FLASH_BOOST_5V_BIT : 0; + rc = smblib_write(chg, SCHGM_FORCE_BOOST_CONTROL, reg); + if (rc < 0) { + pr_err("Couldn't write force boost control reg rc=%d\n", + rc); + return rc; + } + + reg = (chg->headroom_mode == FIXED_MODE) + ? TORCH_PRIORITY_CONTROL_BIT : 0; + rc = smblib_write(chg, SCHGM_TORCH_PRIORITY_CONTROL_REG, reg); + if (rc < 0) { + pr_err("Couldn't force 5V boost in torch mode rc=%d\n", + rc); + return rc; + } + } + + if ((chg->flash_derating_soc != -EINVAL) + || (chg->flash_disable_soc != -EINVAL)) { + /* Check if SOC based derating/disable is enabled */ + rc = smblib_read(chg, SCHGM_FLASH_CONTROL_REG, ®); + if (rc < 0) { + pr_err("Couldn't read flash control reg rc=%d\n", rc); + return rc; + } + if (!(reg & SOC_LOW_FOR_FLASH_EN_BIT)) + pr_warn("Soc based flash derating not enabled\n"); + } + + chg->flash_init_done = true; + + return 0; +} diff --git a/drivers/power/supply/qcom/schgm-flash.h b/drivers/power/supply/qcom/schgm-flash.h new file mode 100644 index 000000000000..bf7ac585cc17 --- /dev/null +++ b/drivers/power/supply/qcom/schgm-flash.h @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2018, 2020 The Linux Foundation. All rights reserved. + */ + +#ifndef __SCHGM_FLASH_H__ +#define __SCHGM_FLASH_H__ + +#include + +#define SCHGM_FLASH_BASE 0xA600 + +#define SCHGM_FLASH_STATUS_2_REG (SCHGM_FLASH_BASE + 0x07) +#define VREG_OK_BIT BIT(4) + +#define SCHGM_FLASH_STATUS_3_REG (SCHGM_FLASH_BASE + 0x08) +#define FLASH_STATE_MASK GENMASK(2, 0) +#define FLASH_ERROR_VAL 0x7 + +#define SCHGM_FLASH_INT_RT_STS_REG (SCHGM_FLASH_BASE + 0x10) + +#define SCHGM_FLASH_STATUS_5_REG (SCHGM_FLASH_BASE + 0x0B) + +#define SCHGM_FORCE_BOOST_CONTROL (SCHGM_FLASH_BASE + 0x41) +#define FORCE_FLASH_BOOST_5V_BIT BIT(0) + +#define SCHGM_FLASH_S2_LATCH_RESET_CMD_REG (SCHGM_FLASH_BASE + 0x44) +#define FLASH_S2_LATCH_RESET_BIT BIT(0) + +#define SCHGM_FLASH_CONTROL_REG (SCHGM_FLASH_BASE + 0x60) +#define SOC_LOW_FOR_FLASH_EN_BIT BIT(7) + +#define SCHGM_TORCH_PRIORITY_CONTROL_REG (SCHGM_FLASH_BASE + 0x63) +#define TORCH_PRIORITY_CONTROL_BIT BIT(0) + +#define SCHGM_SOC_BASED_FLASH_DERATE_TH_CFG_REG (SCHGM_FLASH_BASE + 0x67) + +#define SCHGM_SOC_BASED_FLASH_DISABLE_TH_CFG_REG \ + (SCHGM_FLASH_BASE + 0x68) + +enum torch_mode { + TORCH_BUCK_MODE = 0, + TORCH_BOOST_MODE, +}; + +int schgm_flash_get_vreg_ok(struct smb_charger *chg, int *val); +void schgm_flash_torch_priority(struct smb_charger *chg, enum torch_mode mode); +int schgm_flash_init(struct smb_charger *chg); +bool is_flash_active(struct smb_charger *chg); + +irqreturn_t schgm_flash_default_irq_handler(int irq, void *data); +irqreturn_t schgm_flash_ilim2_irq_handler(int irq, void *data); +irqreturn_t schgm_flash_state_change_irq_handler(int irq, void *data); +#endif /* __SCHGM_FLASH_H__ */ diff --git a/drivers/power/supply/qcom/smb5-lib.c b/drivers/power/supply/qcom/smb5-lib.c new file mode 100644 index 000000000000..ec6f92822020 --- /dev/null +++ b/drivers/power/supply/qcom/smb5-lib.c @@ -0,0 +1,8220 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2018-2020 The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "smb5-lib.h" +#include "smb5-reg.h" +#include "schgm-flash.h" +#include "step-chg-jeita.h" +#include "storm-watch.h" +#include "battery-profile-loader.h" + +#define smblib_err(chg, fmt, ...) \ + pr_err("%s: %s: " fmt, chg->name, \ + __func__, ##__VA_ARGS__) \ + +#define smblib_dbg(chg, reason, fmt, ...) \ + do { \ + if (*chg->debug_mask & (reason)) \ + pr_info("%s: %s: " fmt, chg->name, \ + __func__, ##__VA_ARGS__); \ + else \ + pr_debug("%s: %s: " fmt, chg->name, \ + __func__, ##__VA_ARGS__); \ + } while (0) + +#define typec_rp_med_high(chg, typec_mode) \ + ((typec_mode == POWER_SUPPLY_TYPEC_SOURCE_MEDIUM \ + || typec_mode == POWER_SUPPLY_TYPEC_SOURCE_HIGH) \ + && (!chg->typec_legacy || chg->typec_legacy_use_rp_icl)) + +static void update_sw_icl_max(struct smb_charger *chg, int pst); +static int smblib_get_prop_typec_mode(struct smb_charger *chg); + +int smblib_read(struct smb_charger *chg, u16 addr, u8 *val) +{ + unsigned int value; + int rc = 0; + + rc = regmap_read(chg->regmap, addr, &value); + if (rc >= 0) + *val = (u8)value; + + return rc; +} + +int smblib_batch_read(struct smb_charger *chg, u16 addr, u8 *val, + int count) +{ + return regmap_bulk_read(chg->regmap, addr, val, count); +} + +int smblib_write(struct smb_charger *chg, u16 addr, u8 val) +{ + return regmap_write(chg->regmap, addr, val); +} + +int smblib_batch_write(struct smb_charger *chg, u16 addr, u8 *val, + int count) +{ + return regmap_bulk_write(chg->regmap, addr, val, count); +} + +int smblib_masked_write(struct smb_charger *chg, u16 addr, u8 mask, u8 val) +{ + return regmap_update_bits(chg->regmap, addr, mask, val); +} + +int smblib_get_iio_channel(struct smb_charger *chg, const char *propname, + struct iio_channel **chan) +{ + int rc = 0; + + rc = of_property_match_string(chg->dev->of_node, + "io-channel-names", propname); + if (rc < 0) + return 0; + + *chan = iio_channel_get(chg->dev, propname); + if (IS_ERR(*chan)) { + rc = PTR_ERR(*chan); + if (rc != -EPROBE_DEFER) + smblib_err(chg, "%s channel unavailable, %d\n", + propname, rc); + *chan = NULL; + } + + return rc; +} + +#define DIV_FACTOR_MICRO_V_I 1 +#define DIV_FACTOR_MILI_V_I 1000 +#define DIV_FACTOR_DECIDEGC 100 +int smblib_read_iio_channel(struct smb_charger *chg, struct iio_channel *chan, + int div, int *data) +{ + int rc = 0; + *data = -ENODATA; + + if (chan) { + rc = iio_read_channel_processed(chan, data); + if (rc < 0) { + smblib_err(chg, "Error in reading IIO channel data, rc=%d\n", + rc); + return rc; + } + + if (div != 0) + *data /= div; + } + + return rc; +} + +int smblib_get_jeita_cc_delta(struct smb_charger *chg, int *cc_delta_ua) +{ + int rc, cc_minus_ua; + u8 stat; + + rc = smblib_read(chg, BATTERY_CHARGER_STATUS_7_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_2 rc=%d\n", + rc); + return rc; + } + + if (stat & BAT_TEMP_STATUS_HOT_SOFT_BIT) { + rc = smblib_get_charge_param(chg, &chg->param.jeita_cc_comp_hot, + &cc_minus_ua); + if (rc < 0) { + smblib_err(chg, "Couldn't get jeita cc minus rc=%d\n", + rc); + return rc; + } + } else if (stat & BAT_TEMP_STATUS_COLD_SOFT_BIT) { + rc = smblib_get_charge_param(chg, + &chg->param.jeita_cc_comp_cold, + &cc_minus_ua); + if (rc < 0) { + smblib_err(chg, "Couldn't get jeita cc minus rc=%d\n", + rc); + return rc; + } + } else { + cc_minus_ua = 0; + } + + *cc_delta_ua = -cc_minus_ua; + + return 0; +} + +int smblib_icl_override(struct smb_charger *chg, enum icl_override_mode mode) +{ + int rc; + u8 usb51_mode, icl_override, apsd_override; + + switch (mode) { + case SW_OVERRIDE_USB51_MODE: + usb51_mode = 0; + icl_override = ICL_OVERRIDE_BIT; + apsd_override = 0; + break; + case SW_OVERRIDE_HC_MODE: + usb51_mode = USBIN_MODE_CHG_BIT; + icl_override = 0; + apsd_override = ICL_OVERRIDE_AFTER_APSD_BIT; + break; + case HW_AUTO_MODE: + default: + usb51_mode = USBIN_MODE_CHG_BIT; + icl_override = 0; + apsd_override = 0; + break; + } + + rc = smblib_masked_write(chg, USBIN_ICL_OPTIONS_REG, + USBIN_MODE_CHG_BIT, usb51_mode); + if (rc < 0) { + smblib_err(chg, "Couldn't set USBIN_ICL_OPTIONS rc=%d\n", rc); + return rc; + } + + rc = smblib_masked_write(chg, CMD_ICL_OVERRIDE_REG, + ICL_OVERRIDE_BIT, icl_override); + if (rc < 0) { + smblib_err(chg, "Couldn't override ICL rc=%d\n", rc); + return rc; + } + + rc = smblib_masked_write(chg, USBIN_LOAD_CFG_REG, + ICL_OVERRIDE_AFTER_APSD_BIT, apsd_override); + if (rc < 0) { + smblib_err(chg, "Couldn't override ICL_AFTER_APSD rc=%d\n", rc); + return rc; + } + + return rc; +} + +/* + * This function does smb_en pin access, which is lock protected. + * It should be called with smb_lock held. + */ +static int smblib_select_sec_charger_locked(struct smb_charger *chg, + int sec_chg) +{ + int rc = 0; + + switch (sec_chg) { + case POWER_SUPPLY_CHARGER_SEC_CP: + vote(chg->pl_disable_votable, PL_SMB_EN_VOTER, true, 0); + + /* select Charge Pump instead of slave charger */ + rc = smblib_masked_write(chg, MISC_SMB_CFG_REG, + SMB_EN_SEL_BIT, SMB_EN_SEL_BIT); + if (rc < 0) { + dev_err(chg->dev, "Couldn't select SMB charger rc=%d\n", + rc); + return rc; + } + /* Enable Charge Pump, under HW control */ + rc = smblib_masked_write(chg, MISC_SMB_EN_CMD_REG, + EN_CP_CMD_BIT, EN_CP_CMD_BIT); + if (rc < 0) { + dev_err(chg->dev, "Couldn't enable SMB charger rc=%d\n", + rc); + return rc; + } + vote(chg->smb_override_votable, PL_SMB_EN_VOTER, false, 0); + break; + case POWER_SUPPLY_CHARGER_SEC_PL: + /* select slave charger instead of Charge Pump */ + rc = smblib_masked_write(chg, MISC_SMB_CFG_REG, + SMB_EN_SEL_BIT, 0); + if (rc < 0) { + dev_err(chg->dev, "Couldn't select SMB charger rc=%d\n", + rc); + return rc; + } + /* Enable slave charger, under HW control */ + rc = smblib_masked_write(chg, MISC_SMB_EN_CMD_REG, + EN_STAT_CMD_BIT, EN_STAT_CMD_BIT); + if (rc < 0) { + dev_err(chg->dev, "Couldn't enable SMB charger rc=%d\n", + rc); + return rc; + } + vote(chg->smb_override_votable, PL_SMB_EN_VOTER, false, 0); + + vote(chg->pl_disable_votable, PL_SMB_EN_VOTER, false, 0); + + break; + case POWER_SUPPLY_CHARGER_SEC_NONE: + default: + vote(chg->pl_disable_votable, PL_SMB_EN_VOTER, true, 0); + + /* SW override, disabling secondary charger(s) */ + vote(chg->smb_override_votable, PL_SMB_EN_VOTER, true, 0); + break; + } + + return rc; +} + +static int smblib_select_sec_charger(struct smb_charger *chg, int sec_chg, + int reason, bool toggle) +{ + int rc; + + mutex_lock(&chg->smb_lock); + + if (toggle && sec_chg == POWER_SUPPLY_CHARGER_SEC_CP) { + rc = smblib_select_sec_charger_locked(chg, + POWER_SUPPLY_CHARGER_SEC_NONE); + if (rc < 0) { + dev_err(chg->dev, "Couldn't disable secondary charger rc=%d\n", + rc); + goto unlock_out; + } + + /* + * A minimum of 20us delay is expected before switching on STAT + * pin. + */ + usleep_range(20, 30); + } + + rc = smblib_select_sec_charger_locked(chg, sec_chg); + if (rc < 0) { + dev_err(chg->dev, "Couldn't switch secondary charger rc=%d\n", + rc); + goto unlock_out; + } + + chg->sec_chg_selected = sec_chg; + chg->cp_reason = reason; + +unlock_out: + mutex_unlock(&chg->smb_lock); + + return rc; +} + +static void smblib_notify_extcon_props(struct smb_charger *chg, int id) +{ + union extcon_property_value val; + union power_supply_propval prop_val; + + if (chg->connector_type == POWER_SUPPLY_CONNECTOR_TYPEC) { + smblib_get_prop_typec_cc_orientation(chg, &prop_val); + val.intval = ((prop_val.intval == 2) ? 1 : 0); + extcon_set_property(chg->extcon, id, + EXTCON_PROP_USB_TYPEC_POLARITY, val); + val.intval = true; + extcon_set_property(chg->extcon, id, + EXTCON_PROP_USB_SS, val); + } else if (chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB) { + val.intval = false; + extcon_set_property(chg->extcon, id, + EXTCON_PROP_USB_SS, val); + } +} + +static void smblib_notify_device_mode(struct smb_charger *chg, bool enable) +{ + if (enable) + smblib_notify_extcon_props(chg, EXTCON_USB); + + extcon_set_state_sync(chg->extcon, EXTCON_USB, enable); +} + +static void smblib_notify_usb_host(struct smb_charger *chg, bool enable) +{ + int rc = 0; + + if (enable) { + smblib_dbg(chg, PR_OTG, "enabling VBUS in OTG mode\n"); + rc = smblib_masked_write(chg, DCDC_CMD_OTG_REG, + OTG_EN_BIT, OTG_EN_BIT); + if (rc < 0) { + smblib_err(chg, + "Couldn't enable VBUS in OTG mode rc=%d\n", rc); + return; + } + + smblib_notify_extcon_props(chg, EXTCON_USB_HOST); + } else { + smblib_dbg(chg, PR_OTG, "disabling VBUS in OTG mode\n"); + rc = smblib_masked_write(chg, DCDC_CMD_OTG_REG, + OTG_EN_BIT, 0); + if (rc < 0) { + smblib_err(chg, + "Couldn't disable VBUS in OTG mode rc=%d\n", + rc); + return; + } + } + + extcon_set_state_sync(chg->extcon, EXTCON_USB_HOST, enable); +} + +/******************** + * REGISTER GETTERS * + ********************/ + +int smblib_get_charge_param(struct smb_charger *chg, + struct smb_chg_param *param, int *val_u) +{ + int rc = 0; + u8 val_raw; + + rc = smblib_read(chg, param->reg, &val_raw); + if (rc < 0) { + smblib_err(chg, "%s: Couldn't read from 0x%04x rc=%d\n", + param->name, param->reg, rc); + return rc; + } + + if (param->get_proc) + *val_u = param->get_proc(param, val_raw); + else + *val_u = val_raw * param->step_u + param->min_u; + smblib_dbg(chg, PR_REGISTER, "%s = %d (0x%02x)\n", + param->name, *val_u, val_raw); + + return rc; +} + +int smblib_get_usb_suspend(struct smb_charger *chg, int *suspend) +{ + int rc = 0; + u8 temp; + + rc = smblib_read(chg, USBIN_CMD_IL_REG, &temp); + if (rc < 0) { + smblib_err(chg, "Couldn't read USBIN_CMD_IL rc=%d\n", rc); + return rc; + } + *suspend = temp & USBIN_SUSPEND_BIT; + + return rc; +} + + +static const s16 therm_lookup_table[] = { + /* Index -30C~85C, ADC raw code */ + 0x6C92, 0x6C43, 0x6BF0, 0x6B98, 0x6B3A, 0x6AD8, 0x6A70, 0x6A03, + 0x6990, 0x6916, 0x6897, 0x6811, 0x6785, 0x66F2, 0x6658, 0x65B7, + 0x650F, 0x6460, 0x63AA, 0x62EC, 0x6226, 0x6159, 0x6084, 0x5FA8, + 0x5EC3, 0x5DD8, 0x5CE4, 0x5BE9, 0x5AE7, 0x59DD, 0x58CD, 0x57B5, + 0x5696, 0x5571, 0x5446, 0x5314, 0x51DD, 0x50A0, 0x4F5E, 0x4E17, + 0x4CCC, 0x4B7D, 0x4A2A, 0x48D4, 0x477C, 0x4621, 0x44C4, 0x4365, + 0x4206, 0x40A6, 0x3F45, 0x3DE6, 0x3C86, 0x3B28, 0x39CC, 0x3872, + 0x3719, 0x35C4, 0x3471, 0x3322, 0x31D7, 0x308F, 0x2F4C, 0x2E0D, + 0x2CD3, 0x2B9E, 0x2A6E, 0x2943, 0x281D, 0x26FE, 0x25E3, 0x24CF, + 0x23C0, 0x22B8, 0x21B5, 0x20B8, 0x1FC2, 0x1ED1, 0x1DE6, 0x1D01, + 0x1C22, 0x1B49, 0x1A75, 0x19A8, 0x18E0, 0x181D, 0x1761, 0x16A9, + 0x15F7, 0x154A, 0x14A2, 0x13FF, 0x1361, 0x12C8, 0x1234, 0x11A4, + 0x1119, 0x1091, 0x100F, 0x0F90, 0x0F15, 0x0E9E, 0x0E2B, 0x0DBC, + 0x0D50, 0x0CE8, 0x0C83, 0x0C21, 0x0BC3, 0x0B67, 0x0B0F, 0x0AB9, + 0x0A66, 0x0A16, 0x09C9, 0x097E, +}; + +int smblib_get_thermal_threshold(struct smb_charger *chg, u16 addr, int *val) +{ + u8 buff[2]; + s16 temp; + int rc = 0; + int i, lower, upper; + + rc = smblib_batch_read(chg, addr, buff, 2); + if (rc < 0) { + pr_err("failed to write to 0x%04X, rc=%d\n", addr, rc); + return rc; + } + + temp = buff[1] | buff[0] << 8; + + lower = 0; + upper = ARRAY_SIZE(therm_lookup_table) - 1; + while (lower <= upper) { + i = (upper + lower) / 2; + if (therm_lookup_table[i] < temp) + upper = i - 1; + else if (therm_lookup_table[i] > temp) + lower = i + 1; + else + break; + } + + /* index 0 corresonds to -30C */ + *val = (i - 30) * 10; + + return rc; +} + +struct apsd_result { + const char * const name; + const u8 bit; + const enum power_supply_type pst; +}; + +enum { + UNKNOWN, + SDP, + CDP, + DCP, + OCP, + FLOAT, + HVDCP2, + HVDCP3, + MAX_TYPES +}; + +static const struct apsd_result 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_FLOAT + }, + [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; +} + +#define INPUT_NOT_PRESENT 0 +#define INPUT_PRESENT_USB BIT(1) +#define INPUT_PRESENT_DC BIT(2) +static int smblib_is_input_present(struct smb_charger *chg, + int *present) +{ + int rc; + union power_supply_propval pval = {0, }; + + *present = INPUT_NOT_PRESENT; + + rc = smblib_get_prop_usb_present(chg, &pval); + if (rc < 0) { + pr_err("Couldn't get usb presence status rc=%d\n", rc); + return rc; + } + *present |= pval.intval ? INPUT_PRESENT_USB : INPUT_NOT_PRESENT; + + rc = smblib_get_prop_dc_present(chg, &pval); + if (rc < 0) { + pr_err("Couldn't get dc presence status rc=%d\n", rc); + return rc; + } + *present |= pval.intval ? INPUT_PRESENT_DC : INPUT_NOT_PRESENT; + + return 0; +} + +#define AICL_RANGE2_MIN_MV 5600 +#define AICL_RANGE2_STEP_DELTA_MV 200 +#define AICL_RANGE2_OFFSET 16 +int smblib_get_aicl_cont_threshold(struct smb_chg_param *param, u8 val_raw) +{ + int base = param->min_u; + u8 reg = val_raw; + int step = param->step_u; + + + if (val_raw >= AICL_RANGE2_OFFSET) { + reg = val_raw - AICL_RANGE2_OFFSET; + base = AICL_RANGE2_MIN_MV; + step = AICL_RANGE2_STEP_DELTA_MV; + } + + return base + (reg * step); +} + +/******************** + * REGISTER SETTERS * + ********************/ +static const struct buck_boost_freq chg_freq_list[] = { + [0] = { + .freq_khz = 2400, + .val = 7, + }, + [1] = { + .freq_khz = 2100, + .val = 8, + }, + [2] = { + .freq_khz = 1600, + .val = 11, + }, + [3] = { + .freq_khz = 1200, + .val = 15, + }, +}; + +int smblib_set_chg_freq(struct smb_chg_param *param, + int val_u, u8 *val_raw) +{ + u8 i; + + if (val_u > param->max_u || val_u < param->min_u) + return -EINVAL; + + /* Charger FSW is the configured freqency / 2 */ + val_u *= 2; + for (i = 0; i < ARRAY_SIZE(chg_freq_list); i++) { + if (chg_freq_list[i].freq_khz == val_u) + break; + } + if (i == ARRAY_SIZE(chg_freq_list)) { + pr_err("Invalid frequency %d Hz\n", val_u / 2); + return -EINVAL; + } + + *val_raw = chg_freq_list[i].val; + + return 0; +} + +int smblib_set_opt_switcher_freq(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_switcher, 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; + /* + * Some parallel charging implementations may not have + * PROP_BUCK_FREQ property - they could be running + * with a fixed frequency + */ + power_supply_set_property(chg->pl.psy, + POWER_SUPPLY_PROP_BUCK_FREQ, &pval); + } + + return rc; +} + +int smblib_set_charge_param(struct smb_charger *chg, + struct smb_chg_param *param, int val_u) +{ + int rc = 0; + u8 val_raw; + + if (param->set_proc) { + rc = param->set_proc(param, val_u, &val_raw); + if (rc < 0) + return -EINVAL; + } else { + if (val_u > param->max_u || val_u < param->min_u) + smblib_dbg(chg, PR_MISC, + "%s: %d is out of range [%d, %d]\n", + param->name, val_u, param->min_u, param->max_u); + + if (val_u > param->max_u) + val_u = param->max_u; + if (val_u < param->min_u) + val_u = param->min_u; + + val_raw = (val_u - param->min_u) / param->step_u; + } + + rc = smblib_write(chg, param->reg, val_raw); + if (rc < 0) { + smblib_err(chg, "%s: Couldn't write 0x%02x to 0x%04x rc=%d\n", + param->name, val_raw, param->reg, rc); + return rc; + } + + smblib_dbg(chg, PR_REGISTER, "%s = %d (0x%02x)\n", + param->name, val_u, val_raw); + + return rc; +} + +int smblib_set_usb_suspend(struct smb_charger *chg, bool suspend) +{ + int rc = 0; + + if (suspend) + vote(chg->icl_irq_disable_votable, USB_SUSPEND_VOTER, + true, 0); + + rc = smblib_masked_write(chg, USBIN_CMD_IL_REG, USBIN_SUSPEND_BIT, + suspend ? USBIN_SUSPEND_BIT : 0); + if (rc < 0) + smblib_err(chg, "Couldn't write %s to USBIN_SUSPEND_BIT rc=%d\n", + suspend ? "suspend" : "resume", rc); + + if (!suspend) + vote(chg->icl_irq_disable_votable, USB_SUSPEND_VOTER, + false, 0); + + return rc; +} + +int smblib_set_dc_suspend(struct smb_charger *chg, bool suspend) +{ + int rc = 0; + + rc = smblib_masked_write(chg, DCIN_CMD_IL_REG, DCIN_SUSPEND_BIT, + suspend ? DCIN_SUSPEND_BIT : 0); + if (rc < 0) + smblib_err(chg, "Couldn't write %s to DCIN_SUSPEND_BIT rc=%d\n", + suspend ? "suspend" : "resume", rc); + + return rc; +} + +static int smblib_usb_pd_adapter_allowance_override(struct smb_charger *chg, + u8 allowed_voltage) +{ + int rc = 0; + + if (chg->chg_param.smb_version == PMI632_SUBTYPE) + return 0; + + rc = smblib_write(chg, USBIN_ADAPTER_ALLOW_OVERRIDE_REG, + allowed_voltage); + if (rc < 0) + smblib_err(chg, "Couldn't write 0x%02x to USBIN_ADAPTER_ALLOW_OVERRIDE_REG rc=%d\n", + allowed_voltage, rc); + + smblib_dbg(chg, PR_MISC, "set USBIN_ALLOW_OVERRIDE: %d\n", + allowed_voltage); + return rc; +} + +#define MICRO_5V 5000000 +#define MICRO_9V 9000000 +#define MICRO_12V 12000000 +static int smblib_set_usb_pd_fsw(struct smb_charger *chg, int voltage) +{ + int rc = 0; + + if (voltage == MICRO_5V) + rc = smblib_set_opt_switcher_freq(chg, chg->chg_freq.freq_5V); + else if (voltage > MICRO_5V && voltage < MICRO_9V) + rc = smblib_set_opt_switcher_freq(chg, + chg->chg_freq.freq_6V_8V); + else if (voltage >= MICRO_9V && voltage < MICRO_12V) + rc = smblib_set_opt_switcher_freq(chg, chg->chg_freq.freq_9V); + else if (voltage == MICRO_12V) + rc = smblib_set_opt_switcher_freq(chg, chg->chg_freq.freq_12V); + else { + smblib_err(chg, "Couldn't set Fsw: invalid voltage %d\n", + voltage); + return -EINVAL; + } + + return rc; +} + +#define CONT_AICL_HEADROOM_MV 1000 +#define AICL_THRESHOLD_MV_IN_CC 5000 +static int smblib_set_usb_pd_allowed_voltage(struct smb_charger *chg, + int min_allowed_uv, int max_allowed_uv) +{ + int rc, aicl_threshold; + u8 vbus_allowance; + + if (chg->chg_param.smb_version == PMI632_SUBTYPE) + return 0; + + if (chg->pd_active == POWER_SUPPLY_PD_PPS_ACTIVE) { + vbus_allowance = CONTINUOUS; + } else if (min_allowed_uv == MICRO_5V && max_allowed_uv == MICRO_5V) { + vbus_allowance = FORCE_5V; + } else if (min_allowed_uv == MICRO_9V && max_allowed_uv == MICRO_9V) { + vbus_allowance = FORCE_9V; + } else if (min_allowed_uv == MICRO_12V && max_allowed_uv == MICRO_12V) { + vbus_allowance = FORCE_12V; + } else if (min_allowed_uv < MICRO_12V && max_allowed_uv <= MICRO_12V) { + vbus_allowance = CONTINUOUS; + } else { + smblib_err(chg, "invalid allowed voltage [%d, %d]\n", + min_allowed_uv, max_allowed_uv); + return -EINVAL; + } + + rc = smblib_usb_pd_adapter_allowance_override(chg, vbus_allowance); + if (rc < 0) { + smblib_err(chg, "set CONTINUOUS allowance failed, rc=%d\n", + rc); + return rc; + } + + if (vbus_allowance != CONTINUOUS) + return 0; + + aicl_threshold = min_allowed_uv / 1000 - CONT_AICL_HEADROOM_MV; + if (chg->adapter_cc_mode) + aicl_threshold = min(aicl_threshold, AICL_THRESHOLD_MV_IN_CC); + + rc = smblib_set_charge_param(chg, &chg->param.aicl_cont_threshold, + aicl_threshold); + if (rc < 0) { + smblib_err(chg, "set CONT_AICL_THRESHOLD failed, rc=%d\n", + rc); + return rc; + } + + return rc; +} + +int smblib_set_aicl_cont_threshold(struct smb_chg_param *param, + int val_u, u8 *val_raw) +{ + int base = param->min_u; + int offset = 0; + int step = param->step_u; + + if (val_u > param->max_u) + val_u = param->max_u; + if (val_u < param->min_u) + val_u = param->min_u; + + if (val_u >= AICL_RANGE2_MIN_MV) { + base = AICL_RANGE2_MIN_MV; + step = AICL_RANGE2_STEP_DELTA_MV; + offset = AICL_RANGE2_OFFSET; + } + + *val_raw = ((val_u - base) / step) + offset; + + return 0; +} + +/******************** + * HELPER FUNCTIONS * + ********************/ +static bool is_cp_available(struct smb_charger *chg) +{ + if (!chg->cp_psy) + chg->cp_psy = power_supply_get_by_name("charge_pump_master"); + + return !!chg->cp_psy; +} + +static bool is_cp_topo_vbatt(struct smb_charger *chg) +{ + int rc; + bool is_vbatt; + union power_supply_propval pval; + + if (!is_cp_available(chg)) + return false; + + rc = power_supply_get_property(chg->cp_psy, + POWER_SUPPLY_PROP_PARALLEL_OUTPUT_MODE, &pval); + if (rc < 0) + return false; + + is_vbatt = (pval.intval == POWER_SUPPLY_PL_OUTPUT_VBAT); + + smblib_dbg(chg, PR_WLS, "%s\n", is_vbatt ? "true" : "false"); + + return is_vbatt; +} + +#define CP_TO_MAIN_ICL_OFFSET_PC 10 +int smblib_get_qc3_main_icl_offset(struct smb_charger *chg, int *offset_ua) +{ + union power_supply_propval pval = {0, }; + int rc; + + /* + * Apply ILIM offset to main charger's FCC if all of the following + * conditions are met: + * - HVDCP3 adapter with CP as parallel charger + * - Output connection topology is VBAT + */ + if (!is_cp_topo_vbatt(chg) || chg->hvdcp3_standalone_config + || ((chg->real_charger_type != POWER_SUPPLY_TYPE_USB_HVDCP_3) + && chg->real_charger_type != POWER_SUPPLY_TYPE_USB_HVDCP_3P5)) + return -EINVAL; + + rc = power_supply_get_property(chg->cp_psy, POWER_SUPPLY_PROP_CP_ENABLE, + &pval); + if (rc < 0) { + smblib_err(chg, "Couldn't get CP ENABLE rc=%d\n", rc); + return rc; + } + + if (!pval.intval) + return -EINVAL; + + rc = power_supply_get_property(chg->cp_psy, POWER_SUPPLY_PROP_CP_ILIM, + &pval); + if (rc < 0) { + smblib_err(chg, "Couldn't get CP ILIM rc=%d\n", rc); + return rc; + } + + *offset_ua = (pval.intval * CP_TO_MAIN_ICL_OFFSET_PC * 2) / 100; + + return 0; +} + +int smblib_get_prop_from_bms(struct smb_charger *chg, + enum power_supply_property psp, + union power_supply_propval *val) +{ + int rc; + + if (!chg->bms_psy) + return -EINVAL; + + rc = power_supply_get_property(chg->bms_psy, psp, val); + + return rc; +} + +void smblib_apsd_enable(struct smb_charger *chg, bool enable) +{ + int rc; + + rc = smblib_masked_write(chg, USBIN_OPTIONS_1_CFG_REG, + BC1P2_SRC_DETECT_BIT, + enable ? BC1P2_SRC_DETECT_BIT : 0); + if (rc < 0) + smblib_err(chg, "failed to write USBIN_OPTIONS_1_CFG rc=%d\n", + rc); +} + +void smblib_hvdcp_detect_enable(struct smb_charger *chg, bool enable) +{ + int rc; + u8 mask; + + mask = HVDCP_AUTH_ALG_EN_CFG_BIT | HVDCP_EN_BIT; + rc = smblib_masked_write(chg, USBIN_OPTIONS_1_CFG_REG, mask, + enable ? mask : 0); + if (rc < 0) + smblib_err(chg, "failed to write USBIN_OPTIONS_1_CFG rc=%d\n", + rc); +} + +static void smblib_hvdcp_detect_try_enable(struct smb_charger *chg, bool enable) +{ + if (chg->hvdcp_disable || chg->pd_not_supported) + return; + smblib_hvdcp_detect_enable(chg, enable); +} + +void smblib_hvdcp_hw_inov_enable(struct smb_charger *chg, bool enable) +{ + int rc; + + rc = smblib_masked_write(chg, USBIN_OPTIONS_1_CFG_REG, + HVDCP_AUTONOMOUS_MODE_EN_CFG_BIT, + enable ? HVDCP_AUTONOMOUS_MODE_EN_CFG_BIT : 0); + if (rc < 0) + smblib_err(chg, "failed to write USBIN_OPTIONS_1_CFG rc=%d\n", + rc); +} + +void smblib_hvdcp_exit_config(struct smb_charger *chg) +{ + u8 stat; + int rc; + + rc = smblib_read(chg, APSD_RESULT_STATUS_REG, &stat); + if (rc < 0) + return; + + if (stat & (QC_3P0_BIT | QC_2P0_BIT)) { + /* force HVDCP to 5V */ + smblib_masked_write(chg, USBIN_OPTIONS_1_CFG_REG, + HVDCP_AUTONOMOUS_MODE_EN_CFG_BIT, 0); + smblib_write(chg, CMD_HVDCP_2_REG, FORCE_5V_BIT); + + /* rerun APSD */ + smblib_masked_write(chg, CMD_APSD_REG, APSD_RERUN_BIT, + APSD_RERUN_BIT); + } +} + +static int smblib_request_dpdm(struct smb_charger *chg, bool enable) +{ + int rc = 0; + + if (chg->pr_swap_in_progress) + return 0; + + /* fetch the DPDM regulator */ + if (!chg->dpdm_reg && of_get_property(chg->dev->of_node, + "dpdm-supply", NULL)) { + chg->dpdm_reg = devm_regulator_get(chg->dev, "dpdm"); + if (IS_ERR(chg->dpdm_reg)) { + rc = PTR_ERR(chg->dpdm_reg); + smblib_err(chg, "Couldn't get dpdm regulator rc=%d\n", + rc); + chg->dpdm_reg = NULL; + return rc; + } + } + + mutex_lock(&chg->dpdm_lock); + if (enable) { + if (chg->dpdm_reg && !chg->dpdm_enabled) { + smblib_dbg(chg, PR_MISC, "enabling DPDM regulator\n"); + rc = regulator_enable(chg->dpdm_reg); + if (rc < 0) + smblib_err(chg, + "Couldn't enable dpdm regulator rc=%d\n", + rc); + else + chg->dpdm_enabled = true; + } + } else { + if (chg->dpdm_reg && chg->dpdm_enabled) { + smblib_dbg(chg, PR_MISC, "disabling DPDM regulator\n"); + rc = regulator_disable(chg->dpdm_reg); + if (rc < 0) + smblib_err(chg, + "Couldn't disable dpdm regulator rc=%d\n", + rc); + else + chg->dpdm_enabled = false; + } + } + mutex_unlock(&chg->dpdm_lock); + + return rc; +} + +void smblib_rerun_apsd(struct smb_charger *chg) +{ + int rc; + + smblib_dbg(chg, PR_MISC, "re-running APSD\n"); + + rc = smblib_masked_write(chg, CMD_APSD_REG, + APSD_RERUN_BIT, APSD_RERUN_BIT); + if (rc < 0) + smblib_err(chg, "Couldn't re-run APSD rc=%d\n", rc); +} + +static const struct apsd_result *smblib_update_usb_type(struct smb_charger *chg) +{ + const struct apsd_result *apsd_result = smblib_get_apsd_result(chg); + + /* if PD is active, APSD is disabled so won't have a valid result */ + if (chg->pd_active) { + chg->real_charger_type = POWER_SUPPLY_TYPE_USB_PD; + } else if (chg->qc3p5_detected) { + chg->real_charger_type = POWER_SUPPLY_TYPE_USB_HVDCP_3P5; + } else { + /* + * Update real charger type only if its not FLOAT + * detected as as SDP + */ + if (!(apsd_result->pst == POWER_SUPPLY_TYPE_USB_FLOAT && + chg->real_charger_type == POWER_SUPPLY_TYPE_USB)) + chg->real_charger_type = apsd_result->pst; + } + + smblib_dbg(chg, PR_MISC, "APSD=%s PD=%d QC3P5=%d\n", + apsd_result->name, chg->pd_active, chg->qc3p5_detected); + return apsd_result; +} + +static int smblib_notifier_call(struct notifier_block *nb, + unsigned long ev, void *v) +{ + struct power_supply *psy = v; + struct smb_charger *chg = container_of(nb, struct smb_charger, nb); + + if (!strcmp(psy->desc->name, "bms")) { + if (!chg->bms_psy) + chg->bms_psy = psy; + if (ev == PSY_EVENT_PROP_CHANGED) + schedule_work(&chg->bms_update_work); + } + + if (chg->jeita_configured == JEITA_CFG_NONE) + schedule_work(&chg->jeita_update_work); + + if (chg->sec_pl_present && !chg->pl.psy + && !strcmp(psy->desc->name, "parallel")) { + chg->pl.psy = psy; + schedule_work(&chg->pl_update_work); + } + + if (!strcmp(psy->desc->name, "charge_pump_master")) { + pm_stay_awake(chg->dev); + schedule_work(&chg->cp_status_change_work); + } + + return NOTIFY_OK; +} + +static int smblib_register_notifier(struct smb_charger *chg) +{ + int rc; + + chg->nb.notifier_call = smblib_notifier_call; + rc = power_supply_reg_notifier(&chg->nb); + if (rc < 0) { + smblib_err(chg, "Couldn't register psy notifier rc = %d\n", rc); + return rc; + } + + return 0; +} + +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; +} + +static void smblib_uusb_removal(struct smb_charger *chg) +{ + int rc; + struct smb_irq_data *data; + struct storm_watch *wdata; + int sec_charger; + + sec_charger = chg->sec_pl_present ? POWER_SUPPLY_CHARGER_SEC_PL : + POWER_SUPPLY_CHARGER_SEC_NONE; + smblib_select_sec_charger(chg, sec_charger, POWER_SUPPLY_CP_NONE, + false); + + cancel_delayed_work_sync(&chg->pl_enable_work); + + if (chg->wa_flags & BOOST_BACK_WA) { + data = chg->irq_info[SWITCHER_POWER_OK_IRQ].irq_data; + if (data) { + wdata = &data->storm_data; + update_storm_count(wdata, WEAK_CHG_STORM_COUNT); + vote(chg->usb_icl_votable, BOOST_BACK_VOTER, false, 0); + vote(chg->usb_icl_votable, WEAK_CHARGER_VOTER, + false, 0); + } + } + vote(chg->pl_disable_votable, PL_DELAY_VOTER, true, 0); + vote(chg->awake_votable, PL_DELAY_VOTER, false, 0); + + /* reset both usbin current and voltage votes */ + vote(chg->pl_enable_votable_indirect, USBIN_I_VOTER, false, 0); + vote(chg->pl_enable_votable_indirect, USBIN_V_VOTER, false, 0); + vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, true, + is_flash_active(chg) ? SDP_CURRENT_UA : SDP_100_MA); + vote(chg->usb_icl_votable, SW_QC3_VOTER, false, 0); + vote(chg->usb_icl_votable, HVDCP2_ICL_VOTER, false, 0); + vote(chg->usb_icl_votable, CHG_TERMINATION_VOTER, false, 0); + vote(chg->usb_icl_votable, THERMAL_THROTTLE_VOTER, false, 0); + vote(chg->limited_irq_disable_votable, CHARGER_TYPE_VOTER, + true, 0); + vote(chg->hdc_irq_disable_votable, CHARGER_TYPE_VOTER, true, 0); + vote(chg->hdc_irq_disable_votable, HDC_IRQ_VOTER, false, 0); + + /* Remove SW thermal regulation WA votes */ + vote(chg->usb_icl_votable, SW_THERM_REGULATION_VOTER, false, 0); + vote(chg->pl_disable_votable, SW_THERM_REGULATION_VOTER, false, 0); + vote(chg->dc_suspend_votable, SW_THERM_REGULATION_VOTER, false, 0); + if (chg->cp_disable_votable) + vote(chg->cp_disable_votable, SW_THERM_REGULATION_VOTER, + false, 0); + + /* reset USBOV votes and cancel work */ + cancel_delayed_work_sync(&chg->usbov_dbc_work); + vote(chg->awake_votable, USBOV_DBC_VOTER, false, 0); + chg->dbc_usbov = false; + + chg->voltage_min_uv = MICRO_5V; + chg->voltage_max_uv = MICRO_5V; + chg->usbin_forced_max_uv = 0; + chg->usb_icl_delta_ua = 0; + chg->pulse_cnt = 0; + chg->uusb_apsd_rerun_done = false; + chg->chg_param.forced_main_fcc = 0; + + del_timer_sync(&chg->apsd_timer); + chg->apsd_ext_timeout = false; + + /* write back the default FLOAT charger configuration */ + rc = smblib_masked_write(chg, USBIN_OPTIONS_2_CFG_REG, + (u8)FLOAT_OPTIONS_MASK, chg->float_cfg); + if (rc < 0) + smblib_err(chg, "Couldn't write float charger options rc=%d\n", + rc); + + /* clear USB ICL vote for USB_PSY_VOTER */ + rc = vote(chg->usb_icl_votable, USB_PSY_VOTER, false, 0); + if (rc < 0) + smblib_err(chg, "Couldn't un-vote for USB ICL rc=%d\n", rc); + + /* clear USB ICL vote for DCP_VOTER */ + rc = vote(chg->usb_icl_votable, DCP_VOTER, false, 0); + if (rc < 0) + smblib_err(chg, + "Couldn't un-vote DCP from USB ICL rc=%d\n", rc); + + /* + * if non-compliant charger caused UV, restore original max pulses + * and turn SUSPEND_ON_COLLAPSE_USBIN_BIT back on. + */ + if (chg->qc2_unsupported_voltage) { + rc = smblib_masked_write(chg, HVDCP_PULSE_COUNT_MAX_REG, + HVDCP_PULSE_COUNT_MAX_QC2_MASK, + chg->qc2_max_pulses); + if (rc < 0) + smblib_err(chg, "Couldn't restore max pulses rc=%d\n", + rc); + + rc = smblib_masked_write(chg, USBIN_AICL_OPTIONS_CFG_REG, + SUSPEND_ON_COLLAPSE_USBIN_BIT, + SUSPEND_ON_COLLAPSE_USBIN_BIT); + if (rc < 0) + smblib_err(chg, "Couldn't turn on SUSPEND_ON_COLLAPSE_USBIN_BIT rc=%d\n", + rc); + + chg->qc2_unsupported_voltage = QC2_COMPLIANT; + } + + chg->qc3p5_detected = false; + smblib_update_usb_type(chg); +} + +void smblib_suspend_on_debug_battery(struct smb_charger *chg) +{ + int rc; + union power_supply_propval val; + + rc = smblib_get_prop_from_bms(chg, + POWER_SUPPLY_PROP_DEBUG_BATTERY, &val); + if (rc < 0) { + smblib_err(chg, "Couldn't get debug battery prop rc=%d\n", rc); + return; + } + if (chg->suspend_input_on_debug_batt) { + vote(chg->usb_icl_votable, DEBUG_BOARD_VOTER, val.intval, 0); + vote(chg->dc_suspend_votable, DEBUG_BOARD_VOTER, val.intval, 0); + if (val.intval) + pr_info("Input suspended: Fake battery\n"); + } else { + vote(chg->chg_disable_votable, DEBUG_BOARD_VOTER, + val.intval, 0); + } +} + +int smblib_rerun_apsd_if_required(struct smb_charger *chg) +{ + union power_supply_propval val; + int rc; + + rc = smblib_get_prop_usb_present(chg, &val); + if (rc < 0) { + smblib_err(chg, "Couldn't get usb present rc = %d\n", rc); + return rc; + } + + if (!val.intval) + return 0; + + rc = smblib_request_dpdm(chg, true); + if (rc < 0) + smblib_err(chg, "Couldn't to enable DPDM rc=%d\n", rc); + + chg->uusb_apsd_rerun_done = true; + smblib_rerun_apsd(chg); + + return 0; +} + +static int smblib_get_pulse_cnt(struct smb_charger *chg, int *count) +{ + *count = chg->pulse_cnt; + return 0; +} + +#define USBIN_25MA 25000 +#define USBIN_100MA 100000 +#define USBIN_150MA 150000 +#define USBIN_500MA 500000 +#define USBIN_900MA 900000 +static int set_sdp_current(struct smb_charger *chg, int icl_ua) +{ + int rc; + u8 icl_options; + const struct apsd_result *apsd_result = smblib_get_apsd_result(chg); + + /* power source is SDP */ + switch (icl_ua) { + case USBIN_100MA: + /* USB 2.0 100mA */ + icl_options = 0; + break; + case USBIN_150MA: + /* USB 3.0 150mA */ + icl_options = CFG_USB3P0_SEL_BIT; + break; + case USBIN_500MA: + /* USB 2.0 500mA */ + icl_options = USB51_MODE_BIT; + break; + case USBIN_900MA: + /* USB 3.0 900mA */ + icl_options = CFG_USB3P0_SEL_BIT | USB51_MODE_BIT; + break; + default: + return -EINVAL; + } + + if (chg->real_charger_type == POWER_SUPPLY_TYPE_USB && + apsd_result->pst == POWER_SUPPLY_TYPE_USB_FLOAT) { + /* + * change the float charger configuration to SDP, if this + * is the case of SDP being detected as FLOAT + */ + rc = smblib_masked_write(chg, USBIN_OPTIONS_2_CFG_REG, + FORCE_FLOAT_SDP_CFG_BIT, FORCE_FLOAT_SDP_CFG_BIT); + if (rc < 0) { + smblib_err(chg, "Couldn't set float ICL options rc=%d\n", + rc); + return rc; + } + } + + rc = smblib_masked_write(chg, USBIN_ICL_OPTIONS_REG, + CFG_USB3P0_SEL_BIT | USB51_MODE_BIT, icl_options); + if (rc < 0) { + smblib_err(chg, "Couldn't set ICL options rc=%d\n", rc); + return rc; + } + + rc = smblib_icl_override(chg, SW_OVERRIDE_USB51_MODE); + if (rc < 0) { + smblib_err(chg, "Couldn't set ICL override rc=%d\n", rc); + return rc; + } + + return rc; +} + +int smblib_set_icl_current(struct smb_charger *chg, int icl_ua) +{ + int rc = 0; + enum icl_override_mode icl_override = HW_AUTO_MODE; + /* suspend if 25mA or less is requested */ + bool suspend = (icl_ua <= USBIN_25MA); + + if (chg->chg_param.smb_version == PMI632_SUBTYPE) + schgm_flash_torch_priority(chg, suspend ? TORCH_BOOST_MODE : + TORCH_BUCK_MODE); + + /* Do not configure ICL from SW for DAM cables */ + if (smblib_get_prop_typec_mode(chg) == + POWER_SUPPLY_TYPEC_SINK_DEBUG_ACCESSORY) + return 0; + + if (suspend) + return smblib_set_usb_suspend(chg, true); + + if (icl_ua == INT_MAX) + goto set_mode; + + /* configure current */ + if (chg->real_charger_type == POWER_SUPPLY_TYPE_USB + && (chg->typec_legacy + || chg->typec_mode == POWER_SUPPLY_TYPEC_SOURCE_DEFAULT + || chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB)) { + rc = set_sdp_current(chg, icl_ua); + if (rc < 0) { + smblib_err(chg, "Couldn't set SDP ICL rc=%d\n", rc); + goto out; + } + } else { + /* + * Try USB 2.0/3,0 option first on USB path when maximum input + * current limit is 500mA or below for better accuracy; in case + * of error, proceed to use USB high-current mode. + */ + if (icl_ua <= USBIN_500MA) { + rc = set_sdp_current(chg, icl_ua); + if (rc >= 0) + goto unsuspend; + } + + rc = smblib_set_charge_param(chg, &chg->param.usb_icl, icl_ua); + if (rc < 0) { + smblib_err(chg, "Couldn't set HC ICL rc=%d\n", rc); + goto out; + } + icl_override = SW_OVERRIDE_HC_MODE; + } + +set_mode: + rc = smblib_icl_override(chg, icl_override); + if (rc < 0) { + smblib_err(chg, "Couldn't set ICL override rc=%d\n", rc); + goto out; + } + +unsuspend: + /* unsuspend after configuring current and override */ + rc = smblib_set_usb_suspend(chg, false); + if (rc < 0) { + smblib_err(chg, "Couldn't resume input rc=%d\n", rc); + goto out; + } + + /* Re-run AICL */ + if (icl_override != SW_OVERRIDE_HC_MODE) + rc = smblib_run_aicl(chg, RERUN_AICL); +out: + return rc; +} + +int smblib_get_icl_current(struct smb_charger *chg, int *icl_ua) +{ + int rc; + + rc = smblib_get_charge_param(chg, &chg->param.icl_max_stat, icl_ua); + if (rc < 0) + smblib_err(chg, "Couldn't get HC ICL rc=%d\n", rc); + + return rc; +} + +int smblib_toggle_smb_en(struct smb_charger *chg, int toggle) +{ + int rc = 0; + + if (!toggle) + return rc; + + rc = smblib_select_sec_charger(chg, chg->sec_chg_selected, + chg->cp_reason, true); + + return rc; +} + +int smblib_get_irq_status(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc; + u8 reg; + + if (chg->wa_flags & SKIP_MISC_PBS_IRQ_WA) { + val->intval = 0; + return 0; + } + + mutex_lock(&chg->irq_status_lock); + /* Report and clear cached status */ + val->intval = chg->irq_status; + chg->irq_status = 0; + + /* get real time status of pulse skip irq */ + rc = smblib_read(chg, MISC_PBS_RT_STS_REG, ®); + if (rc < 0) + smblib_err(chg, "Couldn't read MISC_PBS_RT_STS_REG rc=%d\n", + rc); + else + val->intval |= (reg & PULSE_SKIP_IRQ_BIT); + mutex_unlock(&chg->irq_status_lock); + + return rc; +} + +/**************************** + * uUSB Moisture Protection * + ****************************/ +#define MICRO_USB_DETECTION_ON_TIME_20_MS 0x08 +#define MICRO_USB_DETECTION_PERIOD_X_100 0x03 +#define U_USB_STATUS_WATER_PRESENT 0x00 +static int smblib_set_moisture_protection(struct smb_charger *chg, + bool enable) +{ + int rc = 0; + + if (chg->moisture_present == enable) { + smblib_dbg(chg, PR_MISC, "No change in moisture protection status\n"); + return rc; + } + + if (enable) { + chg->moisture_present = true; + + /* Disable uUSB factory mode detection */ + rc = smblib_masked_write(chg, TYPEC_U_USB_CFG_REG, + EN_MICRO_USB_FACTORY_MODE_BIT, 0); + if (rc < 0) { + smblib_err(chg, "Couldn't disable uUSB factory mode detection rc=%d\n", + rc); + return rc; + } + + /* Disable moisture detection and uUSB state change interrupt */ + rc = smblib_masked_write(chg, TYPE_C_INTERRUPT_EN_CFG_2_REG, + TYPEC_WATER_DETECTION_INT_EN_BIT | + MICRO_USB_STATE_CHANGE_INT_EN_BIT, 0); + if (rc < 0) { + smblib_err(chg, "Couldn't disable moisture detection interrupt rc=%d\n", + rc); + return rc; + } + + /* Set 1% duty cycle on ID detection */ + rc = smblib_masked_write(chg, + ((chg->chg_param.smb_version == PMI632_SUBTYPE) + ? PMI632_TYPEC_U_USB_WATER_PROTECTION_CFG_REG : + TYPEC_U_USB_WATER_PROTECTION_CFG_REG), + EN_MICRO_USB_WATER_PROTECTION_BIT | + MICRO_USB_DETECTION_ON_TIME_CFG_MASK | + MICRO_USB_DETECTION_PERIOD_CFG_MASK, + EN_MICRO_USB_WATER_PROTECTION_BIT | + MICRO_USB_DETECTION_ON_TIME_20_MS | + MICRO_USB_DETECTION_PERIOD_X_100); + if (rc < 0) { + smblib_err(chg, "Couldn't set 1 percent CC_ID duty cycle rc=%d\n", + rc); + return rc; + } + + vote(chg->usb_icl_votable, MOISTURE_VOTER, true, 0); + } else { + chg->moisture_present = false; + vote(chg->usb_icl_votable, MOISTURE_VOTER, false, 0); + + /* Enable moisture detection and uUSB state change interrupt */ + rc = smblib_masked_write(chg, TYPE_C_INTERRUPT_EN_CFG_2_REG, + TYPEC_WATER_DETECTION_INT_EN_BIT | + MICRO_USB_STATE_CHANGE_INT_EN_BIT, + TYPEC_WATER_DETECTION_INT_EN_BIT | + MICRO_USB_STATE_CHANGE_INT_EN_BIT); + if (rc < 0) { + smblib_err(chg, "Couldn't enable moisture detection and uUSB state change interrupt rc=%d\n", + rc); + return rc; + } + + /* Disable periodic monitoring of CC_ID pin */ + rc = smblib_write(chg, + ((chg->chg_param.smb_version == PMI632_SUBTYPE) + ? PMI632_TYPEC_U_USB_WATER_PROTECTION_CFG_REG : + TYPEC_U_USB_WATER_PROTECTION_CFG_REG), 0); + if (rc < 0) { + smblib_err(chg, "Couldn't disable 1 percent CC_ID duty cycle rc=%d\n", + rc); + return rc; + } + + /* Enable uUSB factory mode detection */ + rc = smblib_masked_write(chg, TYPEC_U_USB_CFG_REG, + EN_MICRO_USB_FACTORY_MODE_BIT, + EN_MICRO_USB_FACTORY_MODE_BIT); + if (rc < 0) { + smblib_err(chg, "Couldn't disable uUSB factory mode detection rc=%d\n", + rc); + return rc; + } + } + + smblib_dbg(chg, PR_MISC, "Moisture protection %s\n", + chg->moisture_present ? "enabled" : "disabled"); + return rc; +} + +/********************* + * VOTABLE CALLBACKS * + *********************/ +static int smblib_smb_disable_override_vote_callback(struct votable *votable, + void *data, int disable_smb, const char *client) +{ + struct smb_charger *chg = data; + int rc = 0; + + /* Enable/disable SMB_EN pin */ + rc = smblib_masked_write(chg, MISC_SMB_EN_CMD_REG, + SMB_EN_OVERRIDE_BIT | SMB_EN_OVERRIDE_VALUE_BIT, + disable_smb ? SMB_EN_OVERRIDE_BIT : 0); + if (rc < 0) + smblib_err(chg, "Couldn't configure SMB_EN, rc=%d\n", rc); + + return rc; +} + +static int smblib_dc_suspend_vote_callback(struct votable *votable, void *data, + int suspend, const char *client) +{ + struct smb_charger *chg = data; + + if (chg->chg_param.smb_version == PMI632_SUBTYPE) + return 0; + + /* resume input if suspend is invalid */ + if (suspend < 0) + suspend = 0; + + return smblib_set_dc_suspend(chg, (bool)suspend); +} + +static int smblib_awake_vote_callback(struct votable *votable, void *data, + int awake, const char *client) +{ + struct smb_charger *chg = data; + + if (awake) + pm_stay_awake(chg->dev); + else + pm_relax(chg->dev); + + return 0; +} + +static int smblib_chg_disable_vote_callback(struct votable *votable, void *data, + int chg_disable, const char *client) +{ + struct smb_charger *chg = data; + int rc; + + rc = smblib_masked_write(chg, CHARGING_ENABLE_CMD_REG, + CHARGING_ENABLE_CMD_BIT, + chg_disable ? 0 : CHARGING_ENABLE_CMD_BIT); + if (rc < 0) { + smblib_err(chg, "Couldn't %s charging rc=%d\n", + chg_disable ? "disable" : "enable", rc); + return rc; + } + + return 0; +} + +static int smblib_hdc_irq_disable_vote_callback(struct votable *votable, + void *data, int disable, const char *client) +{ + struct smb_charger *chg = data; + + if (!chg->irq_info[HIGH_DUTY_CYCLE_IRQ].irq) + return 0; + + if (chg->irq_info[HIGH_DUTY_CYCLE_IRQ].enabled) { + if (disable) + disable_irq_nosync( + chg->irq_info[HIGH_DUTY_CYCLE_IRQ].irq); + } else { + if (!disable) + enable_irq(chg->irq_info[HIGH_DUTY_CYCLE_IRQ].irq); + } + + chg->irq_info[HIGH_DUTY_CYCLE_IRQ].enabled = !disable; + + return 0; +} + +static int smblib_limited_irq_disable_vote_callback(struct votable *votable, + void *data, int disable, const char *client) +{ + struct smb_charger *chg = data; + + if (!chg->irq_info[INPUT_CURRENT_LIMITING_IRQ].irq) + return 0; + + if (chg->irq_info[INPUT_CURRENT_LIMITING_IRQ].enabled) { + if (disable) + disable_irq_nosync( + chg->irq_info[INPUT_CURRENT_LIMITING_IRQ].irq); + } else { + if (!disable) + enable_irq( + chg->irq_info[INPUT_CURRENT_LIMITING_IRQ].irq); + } + + chg->irq_info[INPUT_CURRENT_LIMITING_IRQ].enabled = !disable; + + return 0; +} + +static int smblib_icl_irq_disable_vote_callback(struct votable *votable, + void *data, int disable, const char *client) +{ + struct smb_charger *chg = data; + + if (!chg->irq_info[USBIN_ICL_CHANGE_IRQ].irq) + return 0; + + if (chg->irq_info[USBIN_ICL_CHANGE_IRQ].enabled) { + if (disable) + disable_irq_nosync( + chg->irq_info[USBIN_ICL_CHANGE_IRQ].irq); + } else { + if (!disable) + enable_irq(chg->irq_info[USBIN_ICL_CHANGE_IRQ].irq); + } + + chg->irq_info[USBIN_ICL_CHANGE_IRQ].enabled = !disable; + + return 0; +} + +static int smblib_temp_change_irq_disable_vote_callback(struct votable *votable, + void *data, int disable, const char *client) +{ + struct smb_charger *chg = data; + + if (!chg->irq_info[TEMP_CHANGE_IRQ].irq) + return 0; + + if (chg->irq_info[TEMP_CHANGE_IRQ].enabled && disable) { + if (chg->irq_info[TEMP_CHANGE_IRQ].wake) + disable_irq_wake(chg->irq_info[TEMP_CHANGE_IRQ].irq); + disable_irq_nosync(chg->irq_info[TEMP_CHANGE_IRQ].irq); + } else if (!chg->irq_info[TEMP_CHANGE_IRQ].enabled && !disable) { + enable_irq(chg->irq_info[TEMP_CHANGE_IRQ].irq); + if (chg->irq_info[TEMP_CHANGE_IRQ].wake) + enable_irq_wake(chg->irq_info[TEMP_CHANGE_IRQ].irq); + } + + chg->irq_info[TEMP_CHANGE_IRQ].enabled = !disable; + + return 0; +} + +/******************* + * VCONN REGULATOR * + * *****************/ + +int smblib_vconn_regulator_enable(struct regulator_dev *rdev) +{ + struct smb_charger *chg = rdev_get_drvdata(rdev); + int rc = 0; + u8 stat, orientation; + + smblib_dbg(chg, PR_OTG, "enabling VCONN\n"); + + rc = smblib_read(chg, TYPE_C_MISC_STATUS_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read TYPE_C_STATUS_4 rc=%d\n", rc); + return rc; + } + + /* VCONN orientation is opposite to that of CC */ + orientation = + stat & TYPEC_CCOUT_VALUE_BIT ? 0 : VCONN_EN_ORIENTATION_BIT; + rc = smblib_masked_write(chg, TYPE_C_VCONN_CONTROL_REG, + VCONN_EN_VALUE_BIT | VCONN_EN_ORIENTATION_BIT, + VCONN_EN_VALUE_BIT | orientation); + if (rc < 0) { + smblib_err(chg, "Couldn't read TYPE_C_CCOUT_CONTROL_REG rc=%d\n", + rc); + return rc; + } + + return 0; +} + +int smblib_vconn_regulator_disable(struct regulator_dev *rdev) +{ + struct smb_charger *chg = rdev_get_drvdata(rdev); + int rc = 0; + + smblib_dbg(chg, PR_OTG, "disabling VCONN\n"); + rc = smblib_masked_write(chg, TYPE_C_VCONN_CONTROL_REG, + VCONN_EN_VALUE_BIT, 0); + if (rc < 0) + smblib_err(chg, "Couldn't disable vconn regulator rc=%d\n", rc); + + return 0; +} + +int smblib_vconn_regulator_is_enabled(struct regulator_dev *rdev) +{ + struct smb_charger *chg = rdev_get_drvdata(rdev); + int rc; + u8 cmd; + + rc = smblib_read(chg, TYPE_C_VCONN_CONTROL_REG, &cmd); + if (rc < 0) { + smblib_err(chg, "Couldn't read TYPE_C_INTRPT_ENB_SOFTWARE_CTRL rc=%d\n", + rc); + return rc; + } + + return (cmd & VCONN_EN_VALUE_BIT) ? 1 : 0; +} + +/***************** + * OTG REGULATOR * + *****************/ + +int smblib_vbus_regulator_enable(struct regulator_dev *rdev) +{ + struct smb_charger *chg = rdev_get_drvdata(rdev); + int rc; + + smblib_dbg(chg, PR_OTG, "enabling OTG\n"); + + rc = smblib_masked_write(chg, DCDC_CMD_OTG_REG, OTG_EN_BIT, OTG_EN_BIT); + if (rc < 0) { + smblib_err(chg, "Couldn't enable OTG rc=%d\n", rc); + return rc; + } + + return 0; +} + +int smblib_vbus_regulator_disable(struct regulator_dev *rdev) +{ + struct smb_charger *chg = rdev_get_drvdata(rdev); + int rc; + + smblib_dbg(chg, PR_OTG, "disabling OTG\n"); + + rc = smblib_masked_write(chg, DCDC_CMD_OTG_REG, OTG_EN_BIT, 0); + if (rc < 0) { + smblib_err(chg, "Couldn't disable OTG regulator rc=%d\n", rc); + return rc; + } + + return 0; +} + +int smblib_vbus_regulator_is_enabled(struct regulator_dev *rdev) +{ + struct smb_charger *chg = rdev_get_drvdata(rdev); + int rc = 0; + u8 cmd; + + rc = smblib_read(chg, DCDC_CMD_OTG_REG, &cmd); + if (rc < 0) { + smblib_err(chg, "Couldn't read CMD_OTG rc=%d", rc); + return rc; + } + + return (cmd & OTG_EN_BIT) ? 1 : 0; +} + +/******************** + * BATT PSY GETTERS * + ********************/ + +int smblib_get_prop_input_suspend(struct smb_charger *chg, + union power_supply_propval *val) +{ + val->intval + = (get_client_vote(chg->usb_icl_votable, USER_VOTER) == 0) + && get_client_vote(chg->dc_suspend_votable, USER_VOTER); + return 0; +} + +int smblib_get_prop_batt_present(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc; + u8 stat; + + rc = smblib_read(chg, BATIF_BASE + INT_RT_STS_OFFSET, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read BATIF_INT_RT_STS rc=%d\n", rc); + return rc; + } + + val->intval = !(stat & (BAT_THERM_OR_ID_MISSING_RT_STS_BIT + | BAT_TERMINAL_MISSING_RT_STS_BIT)); + + return rc; +} + +int smblib_get_prop_batt_capacity(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc = -EINVAL; + + if (chg->fake_capacity >= 0) { + val->intval = chg->fake_capacity; + return 0; + } + + rc = smblib_get_prop_from_bms(chg, POWER_SUPPLY_PROP_CAPACITY, val); + + return rc; +} + +static bool is_charging_paused(struct smb_charger *chg) +{ + int rc; + u8 val; + + rc = smblib_read(chg, CHARGING_PAUSE_CMD_REG, &val); + if (rc < 0) { + smblib_err(chg, "Couldn't read CHARGING_PAUSE_CMD rc=%d\n", rc); + return false; + } + + return val & CHARGING_PAUSE_CMD_BIT; +} + +#define CUTOFF_COUNT 3 +int smblib_get_prop_batt_status(struct smb_charger *chg, + union power_supply_propval *val) +{ + union power_supply_propval pval = {0, }; + bool usb_online, dc_online; + u8 stat; + int rc, suspend = 0, input_present = 0; + + if (chg->fake_chg_status_on_debug_batt) { + rc = smblib_get_prop_from_bms(chg, + POWER_SUPPLY_PROP_DEBUG_BATTERY, &pval); + if (rc < 0) { + pr_err_ratelimited("Couldn't get debug battery prop rc=%d\n", + rc); + } else if (pval.intval == 1) { + val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + return 0; + } + } + + rc = smblib_get_prop_batt_health(chg, &pval); + if (rc < 0) { + smblib_err(chg, "Couldn't get batt health rc=%d\n", rc); + return rc; + } + /* + * The charger status register shows charging even though the battery + * is discharging when the over voltage condition is hit. Report power + * supply state as NOT_CHARGING when the battery health reports + * over voltage. + */ + if (pval.intval == POWER_SUPPLY_HEALTH_OVERVOLTAGE) { + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + return 0; + } + + /* + * If SOC = 0 and we are discharging with input connected, report + * the battery status as DISCHARGING. + */ + smblib_is_input_present(chg, &input_present); + rc = smblib_get_prop_from_bms(chg, POWER_SUPPLY_PROP_CAPACITY, &pval); + if (!rc && pval.intval == 0 && input_present) { + rc = smblib_get_prop_from_bms(chg, + POWER_SUPPLY_PROP_CURRENT_NOW, &pval); + if (!rc && pval.intval > 0) { + if (chg->cutoff_count > CUTOFF_COUNT) { + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + return 0; + } + chg->cutoff_count++; + } else { + chg->cutoff_count = 0; + } + } else { + chg->cutoff_count = 0; + } + + if (chg->dbc_usbov) { + rc = smblib_get_prop_usb_present(chg, &pval); + if (rc < 0) { + smblib_err(chg, + "Couldn't get usb present prop rc=%d\n", rc); + return rc; + } + + rc = smblib_get_usb_suspend(chg, &suspend); + if (rc < 0) { + smblib_err(chg, + "Couldn't get usb suspend rc=%d\n", rc); + return rc; + } + + /* + * Report charging as long as USBOV is not debounced and + * charging path is un-suspended. + */ + if (pval.intval && !suspend) { + val->intval = POWER_SUPPLY_STATUS_CHARGING; + return 0; + } + } + + rc = smblib_get_prop_usb_online(chg, &pval); + if (rc < 0) { + smblib_err(chg, "Couldn't get usb online property rc=%d\n", + rc); + return rc; + } + usb_online = (bool)pval.intval; + + rc = smblib_get_prop_dc_online(chg, &pval); + if (rc < 0) { + smblib_err(chg, "Couldn't get dc online property rc=%d\n", + rc); + return rc; + } + dc_online = (bool)pval.intval; + + rc = smblib_read(chg, BATTERY_CHARGER_STATUS_1_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_1 rc=%d\n", + rc); + return rc; + } + stat = stat & BATTERY_CHARGER_STATUS_MASK; + + if (!usb_online && !dc_online) { + switch (stat) { + case TERMINATE_CHARGE: + case INHIBIT_CHARGE: + val->intval = POWER_SUPPLY_STATUS_FULL; + break; + default: + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + break; + } + return rc; + } + + switch (stat) { + case TRICKLE_CHARGE: + case PRE_CHARGE: + case FULLON_CHARGE: + case TAPER_CHARGE: + val->intval = POWER_SUPPLY_STATUS_CHARGING; + break; + case TERMINATE_CHARGE: + case INHIBIT_CHARGE: + val->intval = POWER_SUPPLY_STATUS_FULL; + break; + case DISABLE_CHARGE: + case PAUSE_CHARGE: + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + default: + val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + break; + } + + if (is_charging_paused(chg)) { + val->intval = POWER_SUPPLY_STATUS_CHARGING; + return 0; + } + + /* + * If charge termination WA is active and has suspended charging, then + * continue reporting charging status as FULL. + */ + if (is_client_vote_enabled_locked(chg->usb_icl_votable, + CHG_TERMINATION_VOTER)) { + val->intval = POWER_SUPPLY_STATUS_FULL; + return 0; + } + + if (val->intval != POWER_SUPPLY_STATUS_CHARGING) + return 0; + + if (!usb_online && dc_online + && chg->fake_batt_status == POWER_SUPPLY_STATUS_FULL) { + val->intval = POWER_SUPPLY_STATUS_FULL; + return 0; + } + + rc = smblib_read(chg, BATTERY_CHARGER_STATUS_5_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_2 rc=%d\n", + rc); + return rc; + } + + stat &= ENABLE_TRICKLE_BIT | ENABLE_PRE_CHARGING_BIT | + ENABLE_FULLON_MODE_BIT; + + if (!stat) + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + + return 0; +} + +int smblib_get_prop_batt_charge_type(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc; + u8 stat; + + rc = smblib_read(chg, BATTERY_CHARGER_STATUS_1_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_1 rc=%d\n", + rc); + return rc; + } + + switch (stat & BATTERY_CHARGER_STATUS_MASK) { + case TRICKLE_CHARGE: + case PRE_CHARGE: + val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + break; + case FULLON_CHARGE: + val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; + break; + case TAPER_CHARGE: + val->intval = POWER_SUPPLY_CHARGE_TYPE_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; + int effective_fv_uv; + u8 stat; + + rc = smblib_read(chg, BATTERY_CHARGER_STATUS_2_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_2 rc=%d\n", + rc); + return rc; + } + smblib_dbg(chg, PR_REGISTER, "BATTERY_CHARGER_STATUS_2 = 0x%02x\n", + stat); + + if (stat & CHARGER_ERROR_STATUS_BAT_OV_BIT) { + rc = smblib_get_prop_from_bms(chg, + POWER_SUPPLY_PROP_VOLTAGE_NOW, &pval); + if (!rc) { + /* + * If Vbatt is within 40mV above Vfloat, then don't + * treat it as overvoltage. + */ + effective_fv_uv = get_effective_result(chg->fv_votable); + if (pval.intval >= effective_fv_uv + 40000) { + val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + smblib_err(chg, "battery over-voltage vbat_fg = %duV, fv = %duV\n", + pval.intval, effective_fv_uv); + goto done; + } + } + } + + rc = smblib_read(chg, BATTERY_CHARGER_STATUS_7_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_2 rc=%d\n", + rc); + return rc; + } + if (stat & BAT_TEMP_STATUS_TOO_COLD_BIT) + val->intval = POWER_SUPPLY_HEALTH_COLD; + else if (stat & BAT_TEMP_STATUS_TOO_HOT_BIT) + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + else if (stat & BAT_TEMP_STATUS_COLD_SOFT_BIT) + val->intval = POWER_SUPPLY_HEALTH_COOL; + else if (stat & BAT_TEMP_STATUS_HOT_SOFT_BIT) + val->intval = POWER_SUPPLY_HEALTH_WARM; + else + val->intval = POWER_SUPPLY_HEALTH_GOOD; + +done: + return rc; +} + +int smblib_get_prop_system_temp_level(struct smb_charger *chg, + union power_supply_propval *val) +{ + val->intval = chg->system_temp_level; + return 0; +} + +int smblib_get_prop_system_temp_level_max(struct smb_charger *chg, + union power_supply_propval *val) +{ + val->intval = chg->thermal_levels; + return 0; +} + +int smblib_get_prop_input_current_limited(struct smb_charger *chg, + union power_supply_propval *val) +{ + u8 stat; + int rc; + + if (chg->fake_input_current_limited >= 0) { + val->intval = chg->fake_input_current_limited; + return 0; + } + + rc = smblib_read(chg, AICL_STATUS_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read AICL_STATUS rc=%d\n", rc); + return rc; + } + val->intval = (stat & SOFT_ILIMIT_BIT) || chg->is_hdc; + return 0; +} + +int smblib_get_prop_batt_iterm(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc, temp; + u8 stat, buf[2]; + + /* + * Currently, only ADC comparator-based termination is supported, + * hence read only the threshold corresponding to ADC source. + * Proceed only if CHGR_ITERM_USE_ANALOG_BIT is 0. + */ + rc = smblib_read(chg, CHGR_ENG_CHARGING_CFG_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read CHGR_ENG_CHARGING_CFG_REG rc=%d\n", + rc); + return rc; + } + + if (stat & CHGR_ITERM_USE_ANALOG_BIT) { + val->intval = -EINVAL; + return 0; + } + + rc = smblib_batch_read(chg, CHGR_ADC_ITERM_UP_THD_MSB_REG, buf, 2); + + if (rc < 0) { + smblib_err(chg, "Couldn't read CHGR_ADC_ITERM_UP_THD_MSB_REG rc=%d\n", + rc); + return rc; + } + + temp = buf[1] | (buf[0] << 8); + temp = sign_extend32(temp, 15); + + if (chg->chg_param.smb_version == PMI632_SUBTYPE) + temp = DIV_ROUND_CLOSEST(temp * ITERM_LIMITS_PMI632_MA, + ADC_CHG_ITERM_MASK); + else + temp = DIV_ROUND_CLOSEST(temp * ITERM_LIMITS_PM8150B_MA, + ADC_CHG_ITERM_MASK); + + val->intval = temp; + + return rc; +} + +int smblib_get_prop_batt_charge_done(struct smb_charger *chg, + 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; +} + +int smblib_get_batt_current_now(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc; + + rc = smblib_get_prop_from_bms(chg, + POWER_SUPPLY_PROP_CURRENT_NOW, val); + if (!rc) + val->intval *= (-1); + + return rc; +} + +/*********************** + * BATTERY PSY SETTERS * + ***********************/ + +int smblib_set_prop_input_suspend(struct smb_charger *chg, + const union power_supply_propval *val) +{ + int rc; + + /* vote 0mA when suspended */ + rc = vote(chg->usb_icl_votable, USER_VOTER, (bool)val->intval, 0); + if (rc < 0) { + smblib_err(chg, "Couldn't vote to %s USB rc=%d\n", + (bool)val->intval ? "suspend" : "resume", rc); + return rc; + } + + rc = vote(chg->dc_suspend_votable, USER_VOTER, (bool)val->intval, 0); + if (rc < 0) { + smblib_err(chg, "Couldn't vote to %s DC rc=%d\n", + (bool)val->intval ? "suspend" : "resume", rc); + return rc; + } + + power_supply_changed(chg->batt_psy); + return rc; +} + +int smblib_set_prop_batt_capacity(struct smb_charger *chg, + const union power_supply_propval *val) +{ + chg->fake_capacity = val->intval; + + power_supply_changed(chg->batt_psy); + + return 0; +} + +int smblib_set_prop_batt_status(struct smb_charger *chg, + const union power_supply_propval *val) +{ + /* Faking battery full */ + if (val->intval == POWER_SUPPLY_STATUS_FULL) + chg->fake_batt_status = val->intval; + else + chg->fake_batt_status = -EINVAL; + + power_supply_changed(chg->batt_psy); + + return 0; +} + +int smblib_set_prop_system_temp_level(struct smb_charger *chg, + const union power_supply_propval *val) +{ + if (val->intval < 0) + return -EINVAL; + + if (chg->thermal_levels <= 0) + return -EINVAL; + + if (val->intval > chg->thermal_levels) + return -EINVAL; + + chg->system_temp_level = val->intval; + + if (chg->system_temp_level == chg->thermal_levels) + return vote(chg->chg_disable_votable, + THERMAL_DAEMON_VOTER, true, 0); + + vote(chg->chg_disable_votable, THERMAL_DAEMON_VOTER, false, 0); + if (chg->system_temp_level == 0) + return vote(chg->fcc_votable, THERMAL_DAEMON_VOTER, false, 0); + + vote(chg->fcc_votable, THERMAL_DAEMON_VOTER, true, + chg->thermal_mitigation[chg->system_temp_level]); + return 0; +} + +int smblib_set_prop_input_current_limited(struct smb_charger *chg, + const union power_supply_propval *val) +{ + chg->fake_input_current_limited = val->intval; + return 0; +} + +int smblib_set_prop_rechg_soc_thresh(struct smb_charger *chg, + const union power_supply_propval *val) +{ + int rc; + u8 new_thr = DIV_ROUND_CLOSEST(val->intval * 255, 100); + + rc = smblib_write(chg, CHARGE_RCHG_SOC_THRESHOLD_CFG_REG, + new_thr); + if (rc < 0) { + smblib_err(chg, "Couldn't write to RCHG_SOC_THRESHOLD_CFG_REG rc=%d\n", + rc); + return rc; + } + + chg->auto_recharge_soc = val->intval; + + return rc; +} + +int smblib_run_aicl(struct smb_charger *chg, int type) +{ + int rc; + u8 stat; + + rc = smblib_read(chg, POWER_PATH_STATUS_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read POWER_PATH_STATUS rc=%d\n", + rc); + return rc; + } + + /* USB is suspended so skip re-running AICL */ + if (stat & USBIN_SUSPEND_STS_BIT) + return rc; + + smblib_dbg(chg, PR_MISC, "re-running AICL\n"); + + stat = (type == RERUN_AICL) ? RERUN_AICL_BIT : RESTART_AICL_BIT; + rc = smblib_masked_write(chg, AICL_CMD_REG, stat, stat); + if (rc < 0) + smblib_err(chg, "Couldn't write to AICL_CMD_REG rc=%d\n", + rc); + return 0; +} + +static int smblib_dp_pulse(struct smb_charger *chg) +{ + int rc; + + /* QC 3.0 increment */ + rc = smblib_masked_write(chg, CMD_HVDCP_2_REG, SINGLE_INCREMENT_BIT, + SINGLE_INCREMENT_BIT); + if (rc < 0) + smblib_err(chg, "Couldn't write to CMD_HVDCP_2_REG rc=%d\n", + rc); + + return rc; +} + +static int smblib_dm_pulse(struct smb_charger *chg) +{ + int rc; + + /* QC 3.0 decrement */ + rc = smblib_masked_write(chg, CMD_HVDCP_2_REG, SINGLE_DECREMENT_BIT, + SINGLE_DECREMENT_BIT); + if (rc < 0) + smblib_err(chg, "Couldn't write to CMD_HVDCP_2_REG rc=%d\n", + rc); + + return rc; +} + +int smblib_force_vbus_voltage(struct smb_charger *chg, u8 val) +{ + int rc; + + rc = smblib_masked_write(chg, CMD_HVDCP_2_REG, val, val); + if (rc < 0) + smblib_err(chg, "Couldn't write to CMD_HVDCP_2_REG rc=%d\n", + rc); + + return rc; +} + +static void smblib_hvdcp_set_fsw(struct smb_charger *chg, int bit) +{ + switch (bit) { + case QC_5V_BIT: + smblib_set_opt_switcher_freq(chg, + chg->chg_freq.freq_5V); + break; + case QC_9V_BIT: + smblib_set_opt_switcher_freq(chg, + chg->chg_freq.freq_9V); + break; + case QC_12V_BIT: + smblib_set_opt_switcher_freq(chg, + chg->chg_freq.freq_12V); + break; + default: + smblib_set_opt_switcher_freq(chg, + chg->chg_freq.freq_removal); + break; + } +} + +#define QC3_PULSES_FOR_6V 5 +#define QC3_PULSES_FOR_9V 20 +#define QC3_PULSES_FOR_12V 35 +static int smblib_hvdcp3_set_fsw(struct smb_charger *chg) +{ + int pulse_count, rc; + + rc = smblib_get_pulse_cnt(chg, &pulse_count); + if (rc < 0) { + smblib_err(chg, "Couldn't read QC_PULSE_COUNT rc=%d\n", rc); + return rc; + } + + if (pulse_count < QC3_PULSES_FOR_6V) + smblib_set_opt_switcher_freq(chg, + chg->chg_freq.freq_5V); + else if (pulse_count < QC3_PULSES_FOR_9V) + smblib_set_opt_switcher_freq(chg, + chg->chg_freq.freq_6V_8V); + else if (pulse_count < QC3_PULSES_FOR_12V) + smblib_set_opt_switcher_freq(chg, + chg->chg_freq.freq_9V); + else + smblib_set_opt_switcher_freq(chg, + chg->chg_freq.freq_12V); + + return 0; +} + +static void smblib_hvdcp_adaptive_voltage_change(struct smb_charger *chg) +{ + int rc; + u8 stat; + + if (chg->real_charger_type == POWER_SUPPLY_TYPE_USB_HVDCP) { + rc = smblib_read(chg, QC_CHANGE_STATUS_REG, &stat); + if (rc < 0) { + smblib_err(chg, + "Couldn't read QC_CHANGE_STATUS rc=%d\n", rc); + return; + } + + smblib_hvdcp_set_fsw(chg, stat & QC_2P0_STATUS_MASK); + vote(chg->usb_icl_votable, HVDCP2_ICL_VOTER, false, 0); + } + + if (chg->real_charger_type == POWER_SUPPLY_TYPE_USB_HVDCP_3 + || chg->real_charger_type == POWER_SUPPLY_TYPE_USB_HVDCP_3P5) { + rc = smblib_hvdcp3_set_fsw(chg); + if (rc < 0) + smblib_err(chg, "Couldn't set QC3.0 Fsw rc=%d\n", rc); + } + + power_supply_changed(chg->usb_main_psy); +} + +int smblib_dp_dm(struct smb_charger *chg, int val) +{ + int target_icl_ua, rc = 0; + union power_supply_propval pval; + u8 stat; + + switch (val) { + case POWER_SUPPLY_DP_DM_DP_PULSE: + /* + * Pre-emptively increment pulse count to enable the setting + * of FSW prior to increasing voltage. + */ + chg->pulse_cnt++; + + rc = smblib_hvdcp3_set_fsw(chg); + if (rc < 0) + smblib_err(chg, "Couldn't set QC3.0 Fsw rc=%d\n", rc); + + rc = smblib_dp_pulse(chg); + if (rc < 0) { + smblib_err(chg, "Couldn't increase pulse count rc=%d\n", + rc); + /* + * Increment pulse count failed; + * reset to former value. + */ + chg->pulse_cnt--; + } + + smblib_dbg(chg, PR_PARALLEL, "DP_DM_DP_PULSE rc=%d cnt=%d\n", + rc, chg->pulse_cnt); + break; + case POWER_SUPPLY_DP_DM_DM_PULSE: + rc = smblib_dm_pulse(chg); + if (!rc && chg->pulse_cnt) + chg->pulse_cnt--; + smblib_dbg(chg, PR_PARALLEL, "DP_DM_DM_PULSE rc=%d cnt=%d\n", + rc, chg->pulse_cnt); + break; + case POWER_SUPPLY_DP_DM_ICL_DOWN: + target_icl_ua = get_effective_result(chg->usb_icl_votable); + if (target_icl_ua < 0) { + /* no client vote, get the ICL from charger */ + rc = power_supply_get_property(chg->usb_psy, + POWER_SUPPLY_PROP_HW_CURRENT_MAX, + &pval); + if (rc < 0) { + smblib_err(chg, "Couldn't get max curr rc=%d\n", + rc); + return rc; + } + target_icl_ua = pval.intval; + } + + /* + * Check if any other voter voted on USB_ICL in case of + * voter other than SW_QC3_VOTER reset and restart reduction + * again. + */ + if (target_icl_ua != get_client_vote(chg->usb_icl_votable, + SW_QC3_VOTER)) + chg->usb_icl_delta_ua = 0; + + chg->usb_icl_delta_ua += 100000; + vote(chg->usb_icl_votable, SW_QC3_VOTER, true, + target_icl_ua - 100000); + smblib_dbg(chg, PR_PARALLEL, "ICL DOWN ICL=%d reduction=%d\n", + target_icl_ua, chg->usb_icl_delta_ua); + break; + case POWER_SUPPLY_DP_DM_FORCE_5V: + rc = smblib_force_vbus_voltage(chg, FORCE_5V_BIT); + if (rc < 0) + pr_err("Failed to force 5V\n"); + break; + case POWER_SUPPLY_DP_DM_FORCE_9V: + if (chg->qc2_unsupported_voltage == QC2_NON_COMPLIANT_9V) { + smblib_err(chg, "Couldn't set 9V: unsupported\n"); + return -EINVAL; + } + + /* If we are increasing voltage to get to 9V, set FSW first */ + rc = smblib_read(chg, QC_CHANGE_STATUS_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read QC_CHANGE_STATUS_REG rc=%d\n", + rc); + break; + } + + if (stat & QC_5V_BIT) { + /* Force 1A ICL before requesting higher voltage */ + vote(chg->usb_icl_votable, HVDCP2_ICL_VOTER, + true, 1000000); + smblib_hvdcp_set_fsw(chg, QC_9V_BIT); + } + + rc = smblib_force_vbus_voltage(chg, FORCE_9V_BIT); + if (rc < 0) + pr_err("Failed to force 9V\n"); + break; + case POWER_SUPPLY_DP_DM_FORCE_12V: + if (chg->qc2_unsupported_voltage == QC2_NON_COMPLIANT_12V) { + smblib_err(chg, "Couldn't set 12V: unsupported\n"); + return -EINVAL; + } + + /* If we are increasing voltage to get to 12V, set FSW first */ + rc = smblib_read(chg, QC_CHANGE_STATUS_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read QC_CHANGE_STATUS_REG rc=%d\n", + rc); + break; + } + + if ((stat & QC_9V_BIT) || (stat & QC_5V_BIT)) { + /* Force 1A ICL before requesting higher voltage */ + vote(chg->usb_icl_votable, HVDCP2_ICL_VOTER, + true, 1000000); + smblib_hvdcp_set_fsw(chg, QC_12V_BIT); + } + + rc = smblib_force_vbus_voltage(chg, FORCE_12V_BIT); + if (rc < 0) + pr_err("Failed to force 12V\n"); + break; + case POWER_SUPPLY_DP_DM_CONFIRMED_HVDCP3P5: + chg->qc3p5_detected = true; + smblib_update_usb_type(chg); + break; + case POWER_SUPPLY_DP_DM_ICL_UP: + default: + break; + } + + return rc; +} + +int smblib_disable_hw_jeita(struct smb_charger *chg, bool disable) +{ + int rc; + u8 mask; + + /* + * Disable h/w base JEITA compensation if s/w JEITA is enabled + */ + mask = JEITA_EN_COLD_SL_FCV_BIT + | JEITA_EN_HOT_SL_FCV_BIT + | JEITA_EN_HOT_SL_CCC_BIT + | JEITA_EN_COLD_SL_CCC_BIT, + rc = smblib_masked_write(chg, JEITA_EN_CFG_REG, mask, + disable ? 0 : mask); + if (rc < 0) { + dev_err(chg->dev, "Couldn't configure s/w jeita rc=%d\n", + rc); + return rc; + } + + return 0; +} + +static int smblib_set_sw_thermal_regulation(struct smb_charger *chg, + bool enable) +{ + int rc = 0; + + if (!(chg->wa_flags & SW_THERM_REGULATION_WA)) + return rc; + + if (enable) { + /* + * Configure min time to quickly address thermal + * condition. + */ + rc = smblib_masked_write(chg, SNARL_BARK_BITE_WD_CFG_REG, + SNARL_WDOG_TIMEOUT_MASK, SNARL_WDOG_TMOUT_62P5MS); + if (rc < 0) { + smblib_err(chg, "Couldn't configure snarl wdog tmout, rc=%d\n", + rc); + return rc; + } + + /* + * Schedule SW_THERM_REGULATION_WORK directly if USB input + * is suspended due to SW thermal regulation WA since WDOG + * IRQ won't trigger with input suspended. + */ + if (is_client_vote_enabled(chg->usb_icl_votable, + SW_THERM_REGULATION_VOTER)) { + vote(chg->awake_votable, SW_THERM_REGULATION_VOTER, + true, 0); + schedule_delayed_work(&chg->thermal_regulation_work, 0); + } + } else { + cancel_delayed_work_sync(&chg->thermal_regulation_work); + vote(chg->awake_votable, SW_THERM_REGULATION_VOTER, false, 0); + } + + smblib_dbg(chg, PR_MISC, "WDOG SNARL INT %s\n", + enable ? "Enabled" : "Disabled"); + + return rc; +} + +static int smblib_update_thermal_readings(struct smb_charger *chg) +{ + union power_supply_propval pval = {0, }; + int rc = 0; + + if (!chg->pl.psy) + chg->pl.psy = power_supply_get_by_name("parallel"); + + rc = smblib_read_iio_channel(chg, chg->iio.die_temp_chan, + DIV_FACTOR_DECIDEGC, &chg->die_temp); + if (rc < 0) { + smblib_err(chg, "Couldn't read DIE TEMP channel, rc=%d\n", rc); + return rc; + } + + rc = smblib_read_iio_channel(chg, chg->iio.connector_temp_chan, + DIV_FACTOR_DECIDEGC, &chg->connector_temp); + if (rc < 0) { + smblib_err(chg, "Couldn't read CONN TEMP channel, rc=%d\n", rc); + return rc; + } + + rc = smblib_read_iio_channel(chg, chg->iio.skin_temp_chan, + DIV_FACTOR_DECIDEGC, &chg->skin_temp); + if (rc < 0) { + smblib_err(chg, "Couldn't read SKIN TEMP channel, rc=%d\n", rc); + return rc; + } + + if (chg->sec_chg_selected == POWER_SUPPLY_CHARGER_SEC_CP) { + if (is_cp_available(chg)) { + rc = power_supply_get_property(chg->cp_psy, + POWER_SUPPLY_PROP_CP_DIE_TEMP, &pval); + if (rc < 0) { + smblib_err(chg, "Couldn't get smb1390 charger temp, rc=%d\n", + rc); + return rc; + } + chg->smb_temp = pval.intval; + } else { + smblib_dbg(chg, PR_MISC, "Coudln't find cp_psy\n"); + chg->smb_temp = -ENODATA; + } + } else if (chg->pl.psy && chg->sec_chg_selected == + POWER_SUPPLY_CHARGER_SEC_PL) { + rc = power_supply_get_property(chg->pl.psy, + POWER_SUPPLY_PROP_CHARGER_TEMP, &pval); + if (rc < 0) { + smblib_err(chg, "Couldn't get smb1355 charger temp, rc=%d\n", + rc); + return rc; + } + chg->smb_temp = pval.intval; + } else { + chg->smb_temp = -ENODATA; + } + + return rc; +} + +/* SW thermal regulation thresholds in deciDegC */ +#define DIE_TEMP_RST_THRESH 1000 +#define DIE_TEMP_REG_H_THRESH 800 +#define DIE_TEMP_REG_L_THRESH 600 + +#define CONNECTOR_TEMP_SHDN_THRESH 700 +#define CONNECTOR_TEMP_RST_THRESH 600 +#define CONNECTOR_TEMP_REG_H_THRESH 550 +#define CONNECTOR_TEMP_REG_L_THRESH 500 + +#define SMB_TEMP_SHDN_THRESH 1400 +#define SMB_TEMP_RST_THRESH 900 +#define SMB_TEMP_REG_H_THRESH 800 +#define SMB_TEMP_REG_L_THRESH 600 + +#define SKIN_TEMP_SHDN_THRESH 700 +#define SKIN_TEMP_RST_THRESH 600 +#define SKIN_TEMP_REG_H_THRESH 550 +#define SKIN_TEMP_REG_L_THRESH 500 + +#define THERM_REG_RECHECK_DELAY_1S 1000 /* 1 sec */ +#define THERM_REG_RECHECK_DELAY_8S 8000 /* 8 sec */ +static int smblib_process_thermal_readings(struct smb_charger *chg) +{ + int rc = 0, wdog_timeout = SNARL_WDOG_TMOUT_8S; + u32 thermal_status = TEMP_BELOW_RANGE; + bool suspend_input = false, disable_smb = false; + + /* + * Following is the SW thermal regulation flow: + * + * TEMP_SHUT_DOWN_LEVEL: If either connector temp or skin temp + * exceeds their respective SHDN threshold. Need to suspend input + * and secondary charger. + * + * TEMP_SHUT_DOWN_SMB_LEVEL: If smb temp exceed its SHDN threshold + * but connector and skin temp are below it. Need to suspend SMB. + * + * TEMP_ALERT_LEVEL: If die, connector, smb or skin temp exceeds it's + * respective RST threshold. Stay put and monitor temperature closely. + * + * TEMP_ABOVE_RANGE or TEMP_WITHIN_RANGE or TEMP_BELOW_RANGE: If die, + * connector, smb or skin temp exceeds it's respective REG_H or REG_L + * threshold. Unsuspend input and SMB. + */ + if (chg->connector_temp > CONNECTOR_TEMP_SHDN_THRESH || + chg->skin_temp > SKIN_TEMP_SHDN_THRESH) { + thermal_status = TEMP_SHUT_DOWN; + wdog_timeout = SNARL_WDOG_TMOUT_1S; + suspend_input = true; + disable_smb = true; + goto out; + } + + if (chg->smb_temp > SMB_TEMP_SHDN_THRESH) { + thermal_status = TEMP_SHUT_DOWN_SMB; + wdog_timeout = SNARL_WDOG_TMOUT_1S; + disable_smb = true; + goto out; + } + + if (chg->connector_temp > CONNECTOR_TEMP_RST_THRESH || + chg->skin_temp > SKIN_TEMP_RST_THRESH || + chg->smb_temp > SMB_TEMP_RST_THRESH || + chg->die_temp > DIE_TEMP_RST_THRESH) { + thermal_status = TEMP_ALERT_LEVEL; + wdog_timeout = SNARL_WDOG_TMOUT_1S; + goto out; + } + + if (chg->connector_temp > CONNECTOR_TEMP_REG_H_THRESH || + chg->skin_temp > SKIN_TEMP_REG_H_THRESH || + chg->smb_temp > SMB_TEMP_REG_H_THRESH || + chg->die_temp > DIE_TEMP_REG_H_THRESH) { + thermal_status = TEMP_ABOVE_RANGE; + wdog_timeout = SNARL_WDOG_TMOUT_1S; + goto out; + } + + if (chg->connector_temp > CONNECTOR_TEMP_REG_L_THRESH || + chg->skin_temp > SKIN_TEMP_REG_L_THRESH || + chg->smb_temp > SMB_TEMP_REG_L_THRESH || + chg->die_temp > DIE_TEMP_REG_L_THRESH) { + thermal_status = TEMP_WITHIN_RANGE; + wdog_timeout = SNARL_WDOG_TMOUT_8S; + } +out: + smblib_dbg(chg, PR_MISC, "Current temperatures: \tDIE_TEMP: %d,\tCONN_TEMP: %d,\tSMB_TEMP: %d,\tSKIN_TEMP: %d\nTHERMAL_STATUS: %d\n", + chg->die_temp, chg->connector_temp, chg->smb_temp, + chg->skin_temp, thermal_status); + + if (thermal_status != chg->thermal_status) { + chg->thermal_status = thermal_status; + /* + * If thermal level changes to TEMP ALERT LEVEL, don't + * enable/disable main/parallel charging. + */ + if (chg->thermal_status == TEMP_ALERT_LEVEL) + goto exit; + + vote(chg->smb_override_votable, SW_THERM_REGULATION_VOTER, + disable_smb, 0); + + /* + * Enable/disable secondary charger through votables to ensure + * that if SMB_EN pin get's toggled somehow, secondary charger + * remains enabled/disabled according to SW thermal regulation. + */ + if (!chg->cp_disable_votable) + chg->cp_disable_votable = find_votable("CP_DISABLE"); + if (chg->cp_disable_votable) + vote(chg->cp_disable_votable, SW_THERM_REGULATION_VOTER, + disable_smb, 0); + + vote(chg->pl_disable_votable, SW_THERM_REGULATION_VOTER, + disable_smb, 0); + smblib_dbg(chg, PR_MISC, "Parallel %s as per SW thermal regulation\n", + disable_smb ? "disabled" : "enabled"); + + /* + * If thermal level changes to TEMP_SHUT_DOWN_SMB, don't + * enable/disable main charger. + */ + if (chg->thermal_status == TEMP_SHUT_DOWN_SMB) + goto exit; + + /* Suspend input if SHDN threshold reached */ + vote(chg->dc_suspend_votable, SW_THERM_REGULATION_VOTER, + suspend_input, 0); + vote(chg->usb_icl_votable, SW_THERM_REGULATION_VOTER, + suspend_input, 0); + smblib_dbg(chg, PR_MISC, "USB/DC %s as per SW thermal regulation\n", + suspend_input ? "suspended" : "unsuspended"); + } +exit: + /* + * On USB suspend, WDOG IRQ stops triggering. To continue thermal + * monitoring and regulation until USB is plugged out, reschedule + * the SW thermal regulation work without releasing the wake lock. + */ + if (is_client_vote_enabled(chg->usb_icl_votable, + SW_THERM_REGULATION_VOTER)) { + schedule_delayed_work(&chg->thermal_regulation_work, + msecs_to_jiffies(THERM_REG_RECHECK_DELAY_1S)); + return 0; + } + + rc = smblib_masked_write(chg, SNARL_BARK_BITE_WD_CFG_REG, + SNARL_WDOG_TIMEOUT_MASK, wdog_timeout); + if (rc < 0) + smblib_err(chg, "Couldn't set WD SNARL timer, rc=%d\n", rc); + + vote(chg->awake_votable, SW_THERM_REGULATION_VOTER, false, 0); + return rc; +} + +/******************* + * DC PSY GETTERS * + *******************/ + +int smblib_get_prop_voltage_wls_output(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc; + + if (!chg->wls_psy) { + chg->wls_psy = power_supply_get_by_name("wireless"); + if (!chg->wls_psy) + return -ENODEV; + } + + rc = power_supply_get_property(chg->wls_psy, + POWER_SUPPLY_PROP_INPUT_VOLTAGE_REGULATION, + val); + if (rc < 0) + dev_err(chg->dev, "Couldn't get POWER_SUPPLY_PROP_VOLTAGE_REGULATION, rc=%d\n", + rc); + + return rc; +} + +int smblib_get_prop_dc_present(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc; + u8 stat; + + if (chg->chg_param.smb_version == PMI632_SUBTYPE) { + val->intval = 0; + return 0; + } + + rc = smblib_read(chg, DCIN_BASE + INT_RT_STS_OFFSET, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read DCIN_RT_STS rc=%d\n", rc); + return rc; + } + + val->intval = (bool)(stat & DCIN_PLUGIN_RT_STS_BIT); + return 0; +} + +int smblib_get_prop_dc_online(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc = 0; + u8 stat; + + if (chg->chg_param.smb_version == PMI632_SUBTYPE) { + val->intval = 0; + return 0; + } + + if (get_client_vote(chg->dc_suspend_votable, USER_VOTER)) { + val->intval = false; + return rc; + } + + if (is_client_vote_enabled(chg->dc_suspend_votable, + CHG_TERMINATION_VOTER)) { + rc = smblib_get_prop_dc_present(chg, val); + return rc; + } + + rc = smblib_read(chg, POWER_PATH_STATUS_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read POWER_PATH_STATUS rc=%d\n", + rc); + return rc; + } + smblib_dbg(chg, PR_REGISTER, "POWER_PATH_STATUS = 0x%02x\n", + stat); + + val->intval = (stat & USE_DCIN_BIT) && + (stat & VALID_INPUT_POWER_SOURCE_STS_BIT); + + return rc; +} + +int smblib_get_prop_dc_current_max(struct smb_charger *chg, + union power_supply_propval *val) +{ + return smblib_get_charge_param(chg, &chg->param.dc_icl, &val->intval); +} + +int smblib_get_prop_dc_voltage_max(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc; + + val->intval = MICRO_12V; + + if (!chg->wls_psy) + chg->wls_psy = power_supply_get_by_name("wireless"); + + if (chg->wls_psy) { + rc = power_supply_get_property(chg->wls_psy, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + val); + if (rc < 0) { + dev_err(chg->dev, "Couldn't get VOLTAGE_MAX, rc=%d\n", + rc); + return rc; + } + } + + return 0; +} + +int smblib_get_prop_dc_voltage_now(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc; + + if (!chg->wls_psy) { + chg->wls_psy = power_supply_get_by_name("wireless"); + if (!chg->wls_psy) + return -ENODEV; + } + + rc = power_supply_get_property(chg->wls_psy, + POWER_SUPPLY_PROP_INPUT_VOLTAGE_REGULATION, + val); + if (rc < 0) { + dev_err(chg->dev, "Couldn't get POWER_SUPPLY_PROP_VOLTAGE_REGULATION, rc=%d\n", + rc); + return rc; + } + + return rc; +} + +/******************* + * DC PSY SETTERS * + *******************/ + +int smblib_set_prop_dc_current_max(struct smb_charger *chg, + const union power_supply_propval *val) +{ + chg->dcin_icl_user_set = true; + return smblib_set_charge_param(chg, &chg->param.dc_icl, val->intval); +} + +#define DCIN_AICL_RERUN_DELAY_MS 5000 +int smblib_set_prop_voltage_wls_output(struct smb_charger *chg, + const union power_supply_propval *val) +{ + int rc; + + if (!chg->wls_psy) { + chg->wls_psy = power_supply_get_by_name("wireless"); + if (!chg->wls_psy) + return -ENODEV; + } + + rc = power_supply_set_property(chg->wls_psy, + POWER_SUPPLY_PROP_INPUT_VOLTAGE_REGULATION, + val); + if (rc < 0) + dev_err(chg->dev, "Couldn't set POWER_SUPPLY_PROP_VOLTAGE_REGULATION, rc=%d\n", + rc); + + smblib_dbg(chg, PR_WLS, "%d\n", val->intval); + + /* + * When WLS VOUT goes down, the power-constrained adaptor may be able + * to supply more current, so allow it to do so - unless userspace has + * changed DCIN ICL value already due to thermal considerations. + */ + if (!chg->dcin_icl_user_set && (val->intval > 0) && + (val->intval < chg->last_wls_vout)) { + alarm_start_relative(&chg->dcin_aicl_alarm, + ms_to_ktime(DCIN_AICL_RERUN_DELAY_MS)); + } + + chg->last_wls_vout = val->intval; + + return rc; +} + +int smblib_set_prop_dc_reset(struct smb_charger *chg) +{ + int rc; + + rc = vote(chg->dc_suspend_votable, VOUT_VOTER, true, 0); + if (rc < 0) { + smblib_err(chg, "Couldn't suspend DC rc=%d\n", rc); + return rc; + } + + rc = smblib_masked_write(chg, DCIN_CMD_IL_REG, DCIN_EN_MASK, + DCIN_EN_OVERRIDE_BIT); + if (rc < 0) { + smblib_err(chg, "Couldn't set DCIN_EN_OVERRIDE_BIT rc=%d\n", + rc); + return rc; + } + + rc = smblib_write(chg, DCIN_CMD_PON_REG, DCIN_PON_BIT | MID_CHG_BIT); + if (rc < 0) { + smblib_err(chg, "Couldn't write %d to DCIN_CMD_PON_REG rc=%d\n", + DCIN_PON_BIT | MID_CHG_BIT, rc); + return rc; + } + + /* Wait for 10ms to allow the charge to get drained */ + usleep_range(10000, 10010); + + rc = smblib_write(chg, DCIN_CMD_PON_REG, 0); + if (rc < 0) { + smblib_err(chg, "Couldn't clear DCIN_CMD_PON_REG rc=%d\n", rc); + return rc; + } + + rc = smblib_masked_write(chg, DCIN_CMD_IL_REG, DCIN_EN_MASK, 0); + if (rc < 0) { + smblib_err(chg, "Couldn't clear DCIN_EN_OVERRIDE_BIT rc=%d\n", + rc); + return rc; + } + + rc = vote(chg->dc_suspend_votable, VOUT_VOTER, false, 0); + if (rc < 0) { + smblib_err(chg, "Couldn't unsuspend DC rc=%d\n", rc); + return rc; + } + + smblib_dbg(chg, PR_MISC, "Wireless charger removal detection successful\n"); + return rc; +} + +/******************* + * USB PSY GETTERS * + *******************/ + +int smblib_get_prop_usb_present(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc; + u8 stat; + + rc = smblib_read(chg, USBIN_BASE + INT_RT_STS_OFFSET, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read USBIN_RT_STS rc=%d\n", rc); + return rc; + } + + val->intval = (bool)(stat & USBIN_PLUGIN_RT_STS_BIT); + return 0; +} + +int smblib_get_prop_usb_online(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc = 0; + u8 stat; + + if (get_client_vote_locked(chg->usb_icl_votable, USER_VOTER) == 0) { + val->intval = false; + return rc; + } + + if (is_client_vote_enabled_locked(chg->usb_icl_votable, + CHG_TERMINATION_VOTER)) { + rc = smblib_get_prop_usb_present(chg, val); + return rc; + } + + rc = smblib_read(chg, POWER_PATH_STATUS_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read POWER_PATH_STATUS rc=%d\n", + rc); + return rc; + } + smblib_dbg(chg, PR_REGISTER, "POWER_PATH_STATUS = 0x%02x\n", + stat); + + val->intval = (stat & USE_USBIN_BIT) && + (stat & VALID_INPUT_POWER_SOURCE_STS_BIT); + return rc; +} + +int smblib_get_usb_online(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc; + + rc = smblib_get_prop_usb_online(chg, val); + if (!val->intval) + goto exit; + + if (((chg->typec_mode == POWER_SUPPLY_TYPEC_SOURCE_DEFAULT) || + (chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB)) + && (chg->real_charger_type == POWER_SUPPLY_TYPE_USB)) + val->intval = 0; + else + val->intval = 1; + + if (chg->real_charger_type == POWER_SUPPLY_TYPE_UNKNOWN) + val->intval = 0; + +exit: + return rc; +} + +int smblib_get_prop_usb_voltage_max_design(struct smb_charger *chg, + union power_supply_propval *val) +{ + switch (chg->real_charger_type) { + case POWER_SUPPLY_TYPE_USB_HVDCP: + if (chg->qc2_unsupported_voltage == QC2_NON_COMPLIANT_9V) { + val->intval = MICRO_5V; + break; + } else if (chg->qc2_unsupported_voltage == + QC2_NON_COMPLIANT_12V) { + val->intval = MICRO_9V; + break; + } + /* else, fallthrough */ + case POWER_SUPPLY_TYPE_USB_HVDCP_3P5: + case POWER_SUPPLY_TYPE_USB_HVDCP_3: + case POWER_SUPPLY_TYPE_USB_PD: + if (chg->chg_param.smb_version == PMI632_SUBTYPE) + val->intval = MICRO_9V; + else + val->intval = MICRO_12V; + break; + default: + val->intval = MICRO_5V; + break; + } + + return 0; +} + +int smblib_get_prop_usb_voltage_max(struct smb_charger *chg, + union power_supply_propval *val) +{ + switch (chg->real_charger_type) { + case POWER_SUPPLY_TYPE_USB_HVDCP: + if (chg->qc2_unsupported_voltage == QC2_NON_COMPLIANT_9V) { + val->intval = MICRO_5V; + break; + } else if (chg->qc2_unsupported_voltage == + QC2_NON_COMPLIANT_12V) { + val->intval = MICRO_9V; + break; + } + /* else, fallthrough */ + case POWER_SUPPLY_TYPE_USB_HVDCP_3P5: + case POWER_SUPPLY_TYPE_USB_HVDCP_3: + if (chg->chg_param.smb_version == PMI632_SUBTYPE) + val->intval = MICRO_9V; + else + val->intval = MICRO_12V; + break; + case POWER_SUPPLY_TYPE_USB_PD: + val->intval = chg->voltage_max_uv; + break; + default: + val->intval = MICRO_5V; + break; + } + + return 0; +} + +#define HVDCP3_STEP_UV 200000 +#define HVDCP3P5_STEP_UV 20000 +static int smblib_estimate_adaptor_voltage(struct smb_charger *chg, + union power_supply_propval *val) +{ + int step_uv = HVDCP3_STEP_UV; + + switch (chg->real_charger_type) { + case POWER_SUPPLY_TYPE_USB_HVDCP: + val->intval = MICRO_12V; + break; + case POWER_SUPPLY_TYPE_USB_HVDCP_3P5: + step_uv = HVDCP3P5_STEP_UV; + case POWER_SUPPLY_TYPE_USB_HVDCP_3: + val->intval = MICRO_5V + (step_uv * chg->pulse_cnt); + break; + case POWER_SUPPLY_TYPE_USB_PD: + /* Take the average of min and max values */ + val->intval = chg->voltage_min_uv + + ((chg->voltage_max_uv - chg->voltage_min_uv) / 2); + break; + default: + val->intval = MICRO_5V; + break; + } + + return 0; +} + +static int smblib_read_mid_voltage_chan(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc; + + if (!chg->iio.mid_chan) + return -ENODATA; + + rc = iio_read_channel_processed(chg->iio.mid_chan, &val->intval); + if (rc < 0) { + smblib_err(chg, "Couldn't read MID channel rc=%d\n", rc); + return rc; + } + + /* + * If MID voltage < 1V, it is unreliable. + * Figure out voltage from registers and calculations. + */ + if (val->intval < 1000000) + return smblib_estimate_adaptor_voltage(chg, val); + + return 0; +} + +static int smblib_read_usbin_voltage_chan(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc; + + if (!chg->iio.usbin_v_chan) + return -ENODATA; + + rc = iio_read_channel_processed(chg->iio.usbin_v_chan, &val->intval); + if (rc < 0) { + smblib_err(chg, "Couldn't read USBIN channel rc=%d\n", rc); + return rc; + } + + return 0; +} + +int smblib_get_prop_usb_voltage_now(struct smb_charger *chg, + union power_supply_propval *val) +{ + union power_supply_propval pval = {0, }; + int rc, ret = 0; + u8 reg, adc_ch_reg; + + mutex_lock(&chg->adc_lock); + + if (chg->wa_flags & USBIN_ADC_WA) { + /* Store ADC channel config in order to restore later */ + rc = smblib_read(chg, BATIF_ADC_CHANNEL_EN_REG, &adc_ch_reg); + if (rc < 0) { + smblib_err(chg, "Couldn't read ADC config rc=%d\n", rc); + ret = rc; + goto unlock; + } + + /* Disable all ADC channels except IBAT channel */ + rc = smblib_write(chg, BATIF_ADC_CHANNEL_EN_REG, + IBATT_CHANNEL_EN_BIT); + if (rc < 0) { + smblib_err(chg, "Couldn't disable ADC channels rc=%d\n", + rc); + ret = rc; + goto unlock; + } + } + + rc = smblib_get_prop_usb_present(chg, &pval); + if (rc < 0) { + smblib_err(chg, "Couldn't get usb presence status rc=%d\n", rc); + ret = -ENODATA; + goto restore_adc_config; + } + + /* + * Skip reading voltage only if USB is not present and we are not in + * OTG mode. + */ + if (!pval.intval) { + rc = smblib_read(chg, DCDC_CMD_OTG_REG, ®); + if (rc < 0) { + smblib_err(chg, "Couldn't read CMD_OTG rc=%d", rc); + goto restore_adc_config; + } + + if (!(reg & OTG_EN_BIT)) + goto restore_adc_config; + } + + /* + * For PM8150B, use MID_CHG ADC channel because overvoltage is observed + * to occur randomly in the USBIN channel, particularly at high + * voltages. + */ + if (chg->chg_param.smb_version == PM8150B_SUBTYPE) + rc = smblib_read_mid_voltage_chan(chg, val); + else + rc = smblib_read_usbin_voltage_chan(chg, val); + if (rc < 0) { + smblib_err(chg, "Failed to read USBIN over vadc, rc=%d\n", rc); + ret = rc; + } + +restore_adc_config: + /* Restore ADC channel config */ + if (chg->wa_flags & USBIN_ADC_WA) { + rc = smblib_write(chg, BATIF_ADC_CHANNEL_EN_REG, adc_ch_reg); + if (rc < 0) + smblib_err(chg, "Couldn't write ADC config rc=%d\n", + rc); + } + +unlock: + mutex_unlock(&chg->adc_lock); + + return ret; +} + +int smblib_get_prop_vph_voltage_now(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc; + + if (!chg->iio.vph_v_chan) + return -ENODATA; + + rc = iio_read_channel_processed(chg->iio.vph_v_chan, &val->intval); + if (rc < 0) { + smblib_err(chg, "Couldn't read vph channel rc=%d\n", rc); + return rc; + } + + return 0; +} + +bool smblib_rsbux_low(struct smb_charger *chg, int r_thr) +{ + int r_sbu1, r_sbu2; + bool ret = false; + int rc; + + if (!chg->iio.sbux_chan) + return false; + + /* disable crude sensors */ + rc = smblib_masked_write(chg, TYPE_C_CRUDE_SENSOR_CFG_REG, + EN_SRC_CRUDE_SENSOR_BIT | EN_SNK_CRUDE_SENSOR_BIT, + 0); + if (rc < 0) { + smblib_err(chg, "Couldn't disable crude sensor rc=%d\n", rc); + return false; + } + + /* select SBU1 as current source */ + rc = smblib_write(chg, TYPE_C_SBU_CFG_REG, SEL_SBU1_ISRC_VAL); + if (rc < 0) { + smblib_err(chg, "Couldn't select SBU1 rc=%d\n", rc); + goto cleanup; + } + + rc = iio_read_channel_processed(chg->iio.sbux_chan, &r_sbu1); + if (rc < 0) { + smblib_err(chg, "Couldn't read SBU1 rc=%d\n", rc); + goto cleanup; + } + + if (r_sbu1 < r_thr) { + ret = true; + goto cleanup; + } + + /* select SBU2 as current source */ + rc = smblib_write(chg, TYPE_C_SBU_CFG_REG, SEL_SBU2_ISRC_VAL); + if (rc < 0) { + smblib_err(chg, "Couldn't select SBU1 rc=%d\n", rc); + goto cleanup; + } + + rc = iio_read_channel_processed(chg->iio.sbux_chan, &r_sbu2); + if (rc < 0) { + smblib_err(chg, "Couldn't read SBU1 rc=%d\n", rc); + goto cleanup; + } + + if (r_sbu2 < r_thr) + ret = true; +cleanup: + /* enable crude sensors */ + rc = smblib_masked_write(chg, TYPE_C_CRUDE_SENSOR_CFG_REG, + EN_SRC_CRUDE_SENSOR_BIT | EN_SNK_CRUDE_SENSOR_BIT, + EN_SRC_CRUDE_SENSOR_BIT | EN_SNK_CRUDE_SENSOR_BIT); + if (rc < 0) + smblib_err(chg, "Couldn't enable crude sensor rc=%d\n", rc); + + /* disable current source */ + rc = smblib_write(chg, TYPE_C_SBU_CFG_REG, 0); + if (rc < 0) + smblib_err(chg, "Couldn't select SBU1 rc=%d\n", rc); + + return ret; +} + +int smblib_get_prop_charger_temp(struct smb_charger *chg, + union power_supply_propval *val) +{ + int temp, rc; + int input_present; + + rc = smblib_is_input_present(chg, &input_present); + if (rc < 0) + return rc; + + if (input_present == INPUT_NOT_PRESENT) + return -ENODATA; + + if (chg->iio.temp_chan) { + rc = iio_read_channel_processed(chg->iio.temp_chan, + &temp); + if (rc < 0) { + pr_err("Error in reading temp channel, rc=%d\n", rc); + return rc; + } + val->intval = temp / 100; + } else { + return -ENODATA; + } + + return rc; +} + +int smblib_get_prop_typec_cc_orientation(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc = 0; + u8 stat; + + val->intval = 0; + + if (chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB) + return 0; + + rc = smblib_read(chg, TYPE_C_MISC_STATUS_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read TYPE_C_STATUS_4 rc=%d\n", rc); + return rc; + } + smblib_dbg(chg, PR_REGISTER, "TYPE_C_STATUS_4 = 0x%02x\n", stat); + + if (stat & CC_ATTACHED_BIT) + val->intval = (bool)(stat & CC_ORIENTATION_BIT) + 1; + + 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_SNK_STATUS_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 & DETECTED_SRC_TYPE_MASK) { + case SNK_RP_STD_BIT: + return POWER_SUPPLY_TYPEC_SOURCE_DEFAULT; + case SNK_RP_1P5_BIT: + return POWER_SUPPLY_TYPEC_SOURCE_MEDIUM; + case SNK_RP_3P0_BIT: + return POWER_SUPPLY_TYPEC_SOURCE_HIGH; + case SNK_RP_SHORT_BIT: + return POWER_SUPPLY_TYPEC_NON_COMPLIANT; + case SNK_DAM_500MA_BIT: + case SNK_DAM_1500MA_BIT: + case SNK_DAM_3000MA_BIT: + return POWER_SUPPLY_TYPEC_SINK_DEBUG_ACCESSORY; + default: + break; + } + + return POWER_SUPPLY_TYPEC_NONE; +} + +static int smblib_get_prop_dfp_mode(struct smb_charger *chg) +{ + int rc; + u8 stat; + + if (chg->lpd_stage == LPD_STAGE_COMMIT) + return POWER_SUPPLY_TYPEC_NONE; + + rc = smblib_read(chg, TYPE_C_SRC_STATUS_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read TYPE_C_SRC_STATUS_REG rc=%d\n", + rc); + return POWER_SUPPLY_TYPEC_NONE; + } + smblib_dbg(chg, PR_REGISTER, "TYPE_C_SRC_STATUS_REG = 0x%02x\n", stat); + + switch (stat & DETECTED_SNK_TYPE_MASK) { + case AUDIO_ACCESS_RA_RA_BIT: + return POWER_SUPPLY_TYPEC_SINK_AUDIO_ADAPTER; + case SRC_DEBUG_ACCESS_BIT: + return POWER_SUPPLY_TYPEC_SINK_DEBUG_ACCESSORY; + case SRC_RD_RA_VCONN_BIT: + return POWER_SUPPLY_TYPEC_SINK_POWERED_CABLE; + case SRC_RD_OPEN_BIT: + return POWER_SUPPLY_TYPEC_SINK; + default: + break; + } + + return POWER_SUPPLY_TYPEC_NONE; +} + +static int smblib_get_prop_typec_mode(struct smb_charger *chg) +{ + int rc; + u8 stat; + + rc = smblib_read(chg, TYPE_C_MISC_STATUS_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read TYPE_C_MISC_STATUS_REG rc=%d\n", + rc); + return 0; + } + smblib_dbg(chg, PR_REGISTER, "TYPE_C_MISC_STATUS_REG = 0x%02x\n", stat); + + if (stat & SNK_SRC_MODE_BIT) + return smblib_get_prop_dfp_mode(chg); + else + return smblib_get_prop_ufp_mode(chg); +} + +inline int smblib_get_usb_prop_typec_mode(struct smb_charger *chg, + union power_supply_propval *val) +{ + if (chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB) + val->intval = POWER_SUPPLY_TYPEC_NONE; + else + val->intval = chg->typec_mode; + + return 0; +} + +int smblib_get_prop_typec_power_role(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc = 0; + u8 ctrl; + + if (chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB) { + val->intval = POWER_SUPPLY_TYPEC_PR_NONE; + return 0; + } + + rc = smblib_read(chg, TYPE_C_MODE_CFG_REG, &ctrl); + if (rc < 0) { + smblib_err(chg, "Couldn't read TYPE_C_MODE_CFG_REG rc=%d\n", + rc); + return rc; + } + smblib_dbg(chg, PR_REGISTER, "TYPE_C_MODE_CFG_REG = 0x%02x\n", + ctrl); + + if (ctrl & TYPEC_DISABLE_CMD_BIT) { + val->intval = POWER_SUPPLY_TYPEC_PR_NONE; + return rc; + } + + switch (ctrl & (EN_SRC_ONLY_BIT | EN_SNK_ONLY_BIT)) { + case 0: + val->intval = POWER_SUPPLY_TYPEC_PR_DUAL; + break; + case EN_SRC_ONLY_BIT: + val->intval = POWER_SUPPLY_TYPEC_PR_SOURCE; + break; + case EN_SNK_ONLY_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 & (EN_SRC_ONLY_BIT | EN_SNK_ONLY_BIT)); + return -EINVAL; + } + + chg->power_role = val->intval; + return rc; +} + +static inline bool typec_in_src_mode(struct smb_charger *chg) +{ + return (chg->typec_mode > POWER_SUPPLY_TYPEC_NONE && + chg->typec_mode < POWER_SUPPLY_TYPEC_SOURCE_DEFAULT); +} + +int smblib_get_prop_typec_select_rp(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc, rp; + u8 stat; + + if (!typec_in_src_mode(chg)) + return -ENODATA; + + rc = smblib_read(chg, TYPE_C_CURRSRC_CFG_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read TYPE_C_CURRSRC_CFG_REG rc=%d\n", + rc); + return rc; + } + + switch (stat & TYPEC_SRC_RP_SEL_MASK) { + case TYPEC_SRC_RP_STD: + rp = POWER_SUPPLY_TYPEC_SRC_RP_STD; + break; + case TYPEC_SRC_RP_1P5A: + rp = POWER_SUPPLY_TYPEC_SRC_RP_1P5A; + break; + case TYPEC_SRC_RP_3A: + case TYPEC_SRC_RP_3A_DUPLICATE: + rp = POWER_SUPPLY_TYPEC_SRC_RP_3A; + break; + default: + return -EINVAL; + } + + val->intval = rp; + + return 0; +} + +int smblib_get_prop_usb_current_now(struct smb_charger *chg, + union power_supply_propval *val) +{ + union power_supply_propval pval = {0, }; + int rc = 0, buck_scale = 1, boost_scale = 1; + + if (chg->iio.usbin_i_chan) { + rc = iio_read_channel_processed(chg->iio.usbin_i_chan, + &val->intval); + if (rc < 0) { + pr_err("Error in reading USBIN_I channel, rc=%d\n", rc); + return rc; + } + + /* + * For PM8150B, scaling factor = reciprocal of + * 0.2V/A in Buck mode, 0.4V/A in Boost mode. + * For PMI632, scaling factor = reciprocal of + * 0.4V/A in Buck mode, 0.8V/A in Boost mode. + */ + switch (chg->chg_param.smb_version) { + case PMI632_SUBTYPE: + buck_scale = 40; + boost_scale = 80; + break; + default: + buck_scale = 20; + boost_scale = 40; + break; + } + + if (chg->otg_present || smblib_get_prop_dfp_mode(chg) != + POWER_SUPPLY_TYPEC_NONE) { + val->intval = DIV_ROUND_CLOSEST(val->intval * 100, + boost_scale); + return rc; + } + + rc = smblib_get_prop_usb_present(chg, &pval); + if (rc < 0) { + smblib_err(chg, "Couldn't get usb present status,rc=%d\n", + rc); + return -ENODATA; + } + + /* If USB is not present, return 0 */ + if (!pval.intval) + val->intval = 0; + else + val->intval = DIV_ROUND_CLOSEST(val->intval * 100, + buck_scale); + } else { + val->intval = 0; + rc = -ENODATA; + } + + return rc; +} + +int smblib_get_prop_low_power(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc; + u8 stat; + + if (chg->sink_src_mode != SRC_MODE) + return -ENODATA; + + rc = smblib_read(chg, TYPE_C_SRC_STATUS_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read TYPE_C_SRC_STATUS_REG rc=%d\n", + rc); + return rc; + } + + val->intval = !(stat & SRC_HIGH_BATT_BIT); + + return 0; +} + +int smblib_get_prop_input_current_settled(struct smb_charger *chg, + union power_supply_propval *val) +{ + return smblib_get_charge_param(chg, &chg->param.icl_stat, &val->intval); +} + +int smblib_get_prop_input_voltage_settled(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc, pulses; + int step_uv = HVDCP3_STEP_UV; + + switch (chg->real_charger_type) { + case POWER_SUPPLY_TYPE_USB_HVDCP_3P5: + step_uv = HVDCP3P5_STEP_UV; + case POWER_SUPPLY_TYPE_USB_HVDCP_3: + rc = smblib_get_pulse_cnt(chg, &pulses); + if (rc < 0) { + smblib_err(chg, + "Couldn't read QC_PULSE_COUNT rc=%d\n", rc); + return 0; + } + val->intval = MICRO_5V + step_uv * pulses; + break; + case POWER_SUPPLY_TYPE_USB_PD: + val->intval = chg->voltage_min_uv; + break; + default: + val->intval = MICRO_5V; + break; + } + + return 0; +} + +int smblib_get_prop_pd_in_hard_reset(struct smb_charger *chg, + union power_supply_propval *val) +{ + val->intval = chg->pd_hard_reset; + return 0; +} + +int smblib_get_pe_start(struct smb_charger *chg, + union power_supply_propval *val) +{ + val->intval = chg->ok_to_pd; + return 0; +} + +int smblib_get_prop_smb_health(struct smb_charger *chg) +{ + int rc; + int input_present; + union power_supply_propval prop = {0, }; + + rc = smblib_is_input_present(chg, &input_present); + if (rc < 0) + return rc; + + if ((input_present == INPUT_NOT_PRESENT) || (!is_cp_available(chg))) + return POWER_SUPPLY_HEALTH_UNKNOWN; + + rc = power_supply_get_property(chg->cp_psy, + POWER_SUPPLY_PROP_CP_DIE_TEMP, &prop); + if (rc < 0) + return rc; + + if (prop.intval > SMB_TEMP_RST_THRESH) + return POWER_SUPPLY_HEALTH_OVERHEAT; + + if (prop.intval > SMB_TEMP_REG_H_THRESH) + return POWER_SUPPLY_HEALTH_HOT; + + if (prop.intval > SMB_TEMP_REG_L_THRESH) + return POWER_SUPPLY_HEALTH_WARM; + + return POWER_SUPPLY_HEALTH_COOL; +} + +int smblib_get_prop_die_health(struct smb_charger *chg) +{ + int rc; + u8 stat; + int input_present; + + rc = smblib_is_input_present(chg, &input_present); + if (rc < 0) + return rc; + + if (input_present == INPUT_NOT_PRESENT) + return POWER_SUPPLY_HEALTH_UNKNOWN; + + if (chg->wa_flags & SW_THERM_REGULATION_WA) { + if (chg->die_temp == -ENODATA) + return POWER_SUPPLY_HEALTH_UNKNOWN; + + if (chg->die_temp > DIE_TEMP_RST_THRESH) + return POWER_SUPPLY_HEALTH_OVERHEAT; + + if (chg->die_temp > DIE_TEMP_REG_H_THRESH) + return POWER_SUPPLY_HEALTH_HOT; + + if (chg->die_temp > DIE_TEMP_REG_L_THRESH) + return POWER_SUPPLY_HEALTH_WARM; + + return POWER_SUPPLY_HEALTH_COOL; + } + + rc = smblib_read(chg, DIE_TEMP_STATUS_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read DIE_TEMP_STATUS_REG, rc=%d\n", + rc); + return POWER_SUPPLY_HEALTH_UNKNOWN; + } + + if (stat & DIE_TEMP_RST_BIT) + return POWER_SUPPLY_HEALTH_OVERHEAT; + + if (stat & DIE_TEMP_UB_BIT) + return POWER_SUPPLY_HEALTH_HOT; + + if (stat & DIE_TEMP_LB_BIT) + return POWER_SUPPLY_HEALTH_WARM; + + return POWER_SUPPLY_HEALTH_COOL; +} + +int smblib_get_die_health(struct smb_charger *chg, + union power_supply_propval *val) +{ + if (chg->die_health == -EINVAL) + val->intval = smblib_get_prop_die_health(chg); + else + val->intval = chg->die_health; + + return 0; +} + +int smblib_get_prop_scope(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc; + union power_supply_propval pval; + + val->intval = POWER_SUPPLY_SCOPE_UNKNOWN; + rc = smblib_get_prop_usb_present(chg, &pval); + if (rc < 0) + return rc; + + val->intval = pval.intval ? POWER_SUPPLY_SCOPE_DEVICE + : chg->otg_present ? POWER_SUPPLY_SCOPE_SYSTEM + : POWER_SUPPLY_SCOPE_UNKNOWN; + + return 0; +} + +static int smblib_get_typec_connector_temp_status(struct smb_charger *chg) +{ + int rc; + u8 stat; + + if (chg->connector_health != -EINVAL) + return chg->connector_health; + + if (chg->wa_flags & SW_THERM_REGULATION_WA) { + if (chg->connector_temp == -ENODATA) + return POWER_SUPPLY_HEALTH_UNKNOWN; + + if (chg->connector_temp > CONNECTOR_TEMP_RST_THRESH) + return POWER_SUPPLY_HEALTH_OVERHEAT; + + if (chg->connector_temp > CONNECTOR_TEMP_REG_H_THRESH) + return POWER_SUPPLY_HEALTH_HOT; + + if (chg->connector_temp > CONNECTOR_TEMP_REG_L_THRESH) + return POWER_SUPPLY_HEALTH_WARM; + + return POWER_SUPPLY_HEALTH_COOL; + } + + rc = smblib_read(chg, CONNECTOR_TEMP_STATUS_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read CONNECTOR_TEMP_STATUS_REG, rc=%d\n", + rc); + return POWER_SUPPLY_HEALTH_UNKNOWN; + } + + if (stat & CONNECTOR_TEMP_RST_BIT) + return POWER_SUPPLY_HEALTH_OVERHEAT; + + if (stat & CONNECTOR_TEMP_UB_BIT) + return POWER_SUPPLY_HEALTH_HOT; + + if (stat & CONNECTOR_TEMP_LB_BIT) + return POWER_SUPPLY_HEALTH_WARM; + + return POWER_SUPPLY_HEALTH_COOL; +} + +int smblib_get_skin_temp_status(struct smb_charger *chg) +{ + int rc; + u8 stat; + + if (!chg->en_skin_therm_mitigation) + return POWER_SUPPLY_HEALTH_UNKNOWN; + + rc = smblib_read(chg, SKIN_TEMP_STATUS_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read SKIN_TEMP_STATUS_REG, rc=%d\n", + rc); + return POWER_SUPPLY_HEALTH_UNKNOWN; + } + + if (stat & SKIN_TEMP_RST_BIT) + return POWER_SUPPLY_HEALTH_OVERHEAT; + + if (stat & SKIN_TEMP_UB_BIT) + return POWER_SUPPLY_HEALTH_HOT; + + if (stat & SKIN_TEMP_LB_BIT) + return POWER_SUPPLY_HEALTH_WARM; + + return POWER_SUPPLY_HEALTH_COOL; +} + +int smblib_get_prop_connector_health(struct smb_charger *chg) +{ + bool dc_present, usb_present; + int input_present; + int rc; + + rc = smblib_is_input_present(chg, &input_present); + if (rc < 0) + return POWER_SUPPLY_HEALTH_UNKNOWN; + + dc_present = input_present & INPUT_PRESENT_DC; + usb_present = input_present & INPUT_PRESENT_USB; + + if (usb_present) + return smblib_get_typec_connector_temp_status(chg); + + /* + * In PM8150B, SKIN channel measures Wireless charger receiver + * temp, used to regulate DC ICL. + */ + if (chg->chg_param.smb_version == PM8150B_SUBTYPE && dc_present) + return smblib_get_skin_temp_status(chg); + + return POWER_SUPPLY_HEALTH_COOL; +} + +static int get_rp_based_dcp_current(struct smb_charger *chg, int typec_mode) +{ + int rp_ua; + + switch (typec_mode) { + case POWER_SUPPLY_TYPEC_SOURCE_HIGH: + rp_ua = TYPEC_HIGH_CURRENT_UA; + break; + case POWER_SUPPLY_TYPEC_SOURCE_MEDIUM: + case POWER_SUPPLY_TYPEC_SOURCE_DEFAULT: + /* fall through */ + default: + rp_ua = DCP_CURRENT_UA; + } + + return rp_ua; +} + +/******************* + * USB PSY SETTERS * + * *****************/ + +int smblib_set_prop_pd_current_max(struct smb_charger *chg, + const union power_supply_propval *val) +{ + int rc, icl; + + if (chg->pd_active) { + icl = get_client_vote(chg->usb_icl_votable, PD_VOTER); + rc = vote(chg->usb_icl_votable, PD_VOTER, true, val->intval); + if (val->intval != icl) + power_supply_changed(chg->usb_psy); + } else { + rc = -EPERM; + } + + return rc; +} + +static int smblib_handle_usb_current(struct smb_charger *chg, + int usb_current) +{ + int rc = 0, rp_ua, typec_mode; + union power_supply_propval val = {0, }; + + if (chg->real_charger_type == POWER_SUPPLY_TYPE_USB_FLOAT) { + if (usb_current == -ETIMEDOUT) { + if ((chg->float_cfg & FLOAT_OPTIONS_MASK) + == FORCE_FLOAT_SDP_CFG_BIT) { + /* + * Confiugure USB500 mode if Float charger is + * configured for SDP mode. + */ + rc = vote(chg->usb_icl_votable, + SW_ICL_MAX_VOTER, true, USBIN_500MA); + if (rc < 0) + smblib_err(chg, + "Couldn't set SDP ICL rc=%d\n", + rc); + return rc; + } + + if (chg->connector_type == + POWER_SUPPLY_CONNECTOR_TYPEC) { + /* + * Valid FLOAT charger, report the current + * based of Rp. + */ + typec_mode = smblib_get_prop_typec_mode(chg); + rp_ua = get_rp_based_dcp_current(chg, + typec_mode); + rc = vote(chg->usb_icl_votable, + SW_ICL_MAX_VOTER, true, rp_ua); + if (rc < 0) + return rc; + } else { + rc = vote(chg->usb_icl_votable, + SW_ICL_MAX_VOTER, true, DCP_CURRENT_UA); + if (rc < 0) + return rc; + } + } else { + /* + * FLOAT charger detected as SDP by USB driver, + * charge with the requested current and update the + * real_charger_type + */ + chg->real_charger_type = POWER_SUPPLY_TYPE_USB; + rc = vote(chg->usb_icl_votable, USB_PSY_VOTER, + true, usb_current); + if (rc < 0) + return rc; + rc = vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, + false, 0); + if (rc < 0) + return rc; + } + } else { + rc = smblib_get_prop_usb_present(chg, &val); + if (!rc && !val.intval) + return 0; + + /* if flash is active force 500mA */ + if ((usb_current < SDP_CURRENT_UA) && is_flash_active(chg)) + usb_current = SDP_CURRENT_UA; + + rc = vote(chg->usb_icl_votable, USB_PSY_VOTER, true, + usb_current); + if (rc < 0) { + pr_err("Couldn't vote ICL USB_PSY_VOTER rc=%d\n", rc); + return rc; + } + + rc = vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, false, 0); + if (rc < 0) { + pr_err("Couldn't remove SW_ICL_MAX vote rc=%d\n", rc); + return rc; + } + + } + + return 0; +} + +int smblib_set_prop_sdp_current_max(struct smb_charger *chg, + const union power_supply_propval *val) +{ + union power_supply_propval pval; + int rc = 0; + + if (!chg->pd_active) { + rc = smblib_get_prop_usb_present(chg, &pval); + if (rc < 0) { + smblib_err(chg, "Couldn't get usb present rc = %d\n", + rc); + return rc; + } + + /* handle the request only when USB is present */ + if (pval.intval) + rc = smblib_handle_usb_current(chg, val->intval); + } else if (chg->system_suspend_supported) { + if (val->intval <= USBIN_25MA) + rc = vote(chg->usb_icl_votable, + PD_SUSPEND_SUPPORTED_VOTER, true, val->intval); + else + rc = vote(chg->usb_icl_votable, + PD_SUSPEND_SUPPORTED_VOTER, false, 0); + } + return rc; +} + +int smblib_set_prop_boost_current(struct smb_charger *chg, + const union power_supply_propval *val) +{ + int rc = 0; + + rc = smblib_set_charge_param(chg, &chg->param.freq_switcher, + val->intval <= chg->boost_threshold_ua ? + chg->chg_freq.freq_below_otg_threshold : + chg->chg_freq.freq_above_otg_threshold); + if (rc < 0) { + dev_err(chg->dev, "Error in setting freq_boost rc=%d\n", rc); + return rc; + } + + chg->boost_current_ua = val->intval; + return rc; +} + +int smblib_set_prop_usb_voltage_max_limit(struct smb_charger *chg, + const union power_supply_propval *val) +{ + union power_supply_propval pval = {0, }; + + /* Exit if same value is re-configured */ + if (val->intval == chg->usbin_forced_max_uv) + return 0; + + smblib_get_prop_usb_voltage_max_design(chg, &pval); + + if (val->intval >= MICRO_5V && val->intval <= pval.intval) { + chg->usbin_forced_max_uv = val->intval; + smblib_dbg(chg, PR_MISC, "Max VBUS limit changed to: %d\n", + val->intval); + } else if (chg->usbin_forced_max_uv) { + chg->usbin_forced_max_uv = 0; + } else { + return 0; + } + + power_supply_changed(chg->usb_psy); + + return 0; +} + +void smblib_typec_irq_config(struct smb_charger *chg, bool en) +{ + if (en == chg->typec_irq_en) + return; + + if (en) { + enable_irq( + chg->irq_info[TYPEC_ATTACH_DETACH_IRQ].irq); + enable_irq( + chg->irq_info[TYPEC_CC_STATE_CHANGE_IRQ].irq); + enable_irq( + chg->irq_info[TYPEC_OR_RID_DETECTION_CHANGE_IRQ].irq); + } else { + disable_irq_nosync( + chg->irq_info[TYPEC_ATTACH_DETACH_IRQ].irq); + disable_irq_nosync( + chg->irq_info[TYPEC_CC_STATE_CHANGE_IRQ].irq); + disable_irq_nosync( + chg->irq_info[TYPEC_OR_RID_DETECTION_CHANGE_IRQ].irq); + } + + chg->typec_irq_en = en; +} + +#define PR_LOCK_TIMEOUT_MS 1000 +int smblib_set_prop_typec_power_role(struct smb_charger *chg, + const union power_supply_propval *val) +{ + int rc = 0; + u8 power_role; + enum power_supply_typec_mode typec_mode; + bool snk_attached = false, src_attached = false, is_pr_lock = false; + + if (chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB) + return 0; + + smblib_dbg(chg, PR_MISC, "power role change: %d --> %d!", + chg->power_role, val->intval); + + if (chg->power_role == val->intval) { + smblib_dbg(chg, PR_MISC, "power role already in %d, ignore!", + chg->power_role); + return 0; + } + + typec_mode = smblib_get_prop_typec_mode(chg); + if (typec_mode >= POWER_SUPPLY_TYPEC_SINK && + typec_mode <= POWER_SUPPLY_TYPEC_SINK_AUDIO_ADAPTER) + snk_attached = true; + else if (typec_mode >= POWER_SUPPLY_TYPEC_SOURCE_DEFAULT && + typec_mode <= POWER_SUPPLY_TYPEC_SOURCE_HIGH) + src_attached = true; + + /* + * If current power role is in DRP, and type-c is already in the + * mode (source or sink) that's being requested, it means this is + * a power role locking request from USBPD driver. Disable type-c + * related interrupts for locking power role to avoid the redundant + * notifications. + */ + if ((chg->power_role == POWER_SUPPLY_TYPEC_PR_DUAL) && + ((src_attached && val->intval == POWER_SUPPLY_TYPEC_PR_SINK) || + (snk_attached && val->intval == POWER_SUPPLY_TYPEC_PR_SOURCE))) + is_pr_lock = true; + + smblib_dbg(chg, PR_MISC, "snk_attached = %d, src_attached = %d, is_pr_lock = %d\n", + snk_attached, src_attached, is_pr_lock); + cancel_delayed_work(&chg->pr_lock_clear_work); + spin_lock(&chg->typec_pr_lock); + if (!chg->pr_lock_in_progress && is_pr_lock) { + smblib_dbg(chg, PR_MISC, "disable type-c interrupts for power role locking\n"); + smblib_typec_irq_config(chg, false); + schedule_delayed_work(&chg->pr_lock_clear_work, + msecs_to_jiffies(PR_LOCK_TIMEOUT_MS)); + } else if (chg->pr_lock_in_progress && !is_pr_lock) { + smblib_dbg(chg, PR_MISC, "restore type-c interrupts after exit power role locking\n"); + smblib_typec_irq_config(chg, true); + } + + chg->pr_lock_in_progress = is_pr_lock; + spin_unlock(&chg->typec_pr_lock); + + switch (val->intval) { + case POWER_SUPPLY_TYPEC_PR_NONE: + power_role = TYPEC_DISABLE_CMD_BIT; + break; + case POWER_SUPPLY_TYPEC_PR_DUAL: + power_role = chg->typec_try_mode; + break; + case POWER_SUPPLY_TYPEC_PR_SINK: + power_role = EN_SNK_ONLY_BIT; + break; + case POWER_SUPPLY_TYPEC_PR_SOURCE: + power_role = EN_SRC_ONLY_BIT; + break; + default: + smblib_err(chg, "power role %d not supported\n", val->intval); + return -EINVAL; + } + + rc = smblib_masked_write(chg, TYPE_C_MODE_CFG_REG, + TYPEC_POWER_ROLE_CMD_MASK | TYPEC_TRY_MODE_MASK, + power_role); + if (rc < 0) { + smblib_err(chg, "Couldn't write 0x%02x to TYPE_C_INTRPT_ENB_SOFTWARE_CTRL rc=%d\n", + power_role, rc); + return rc; + } + + chg->power_role = val->intval; + return rc; +} + +int smblib_set_prop_typec_select_rp(struct smb_charger *chg, + const union power_supply_propval *val) +{ + int rc; + + if (!typec_in_src_mode(chg)) { + smblib_err(chg, "Couldn't set curr src: not in SRC mode\n"); + return -EINVAL; + } + + if (val->intval < TYPEC_SRC_RP_MAX_ELEMENTS) { + rc = smblib_masked_write(chg, TYPE_C_CURRSRC_CFG_REG, + TYPEC_SRC_RP_SEL_MASK, + val->intval); + if (rc < 0) + smblib_err(chg, "Couldn't write to TYPE_C_CURRSRC_CFG rc=%d\n", + rc); + return rc; + } + + return -EINVAL; +} + +int smblib_set_prop_pd_voltage_min(struct smb_charger *chg, + const union power_supply_propval *val) +{ + int rc, min_uv; + + min_uv = min(val->intval, chg->voltage_max_uv); + if (chg->voltage_min_uv == min_uv) + return 0; + + rc = smblib_set_usb_pd_allowed_voltage(chg, min_uv, + chg->voltage_max_uv); + if (rc < 0) { + smblib_err(chg, "invalid min voltage %duV rc=%d\n", + val->intval, rc); + return rc; + } + + chg->voltage_min_uv = min_uv; + power_supply_changed(chg->usb_main_psy); + + return rc; +} + +int smblib_set_prop_pd_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); + if (chg->voltage_max_uv == max_uv) + return 0; + + rc = smblib_set_usb_pd_fsw(chg, max_uv); + if (rc < 0) { + smblib_err(chg, "Couldn't set FSW for voltage %duV rc=%d\n", + val->intval, rc); + return rc; + } + + rc = smblib_set_usb_pd_allowed_voltage(chg, chg->voltage_min_uv, + max_uv); + if (rc < 0) { + smblib_err(chg, "invalid max voltage %duV rc=%d\n", + val->intval, rc); + return rc; + } + + chg->voltage_max_uv = max_uv; + power_supply_changed(chg->usb_main_psy); + + return rc; +} + +int smblib_set_prop_pd_active(struct smb_charger *chg, + const union power_supply_propval *val) +{ + const struct apsd_result *apsd = smblib_get_apsd_result(chg); + + int rc = 0; + int sec_charger, typec_mode; + + /* + * Ignore repetitive notification while PD is active, which + * is caused by hard reset. + */ + if (chg->pd_active && chg->pd_active == val->intval) + return 0; + + chg->pd_active = val->intval; + + smblib_apsd_enable(chg, !chg->pd_active); + + update_sw_icl_max(chg, apsd->pst); + + if (chg->pd_active) { + vote(chg->limited_irq_disable_votable, CHARGER_TYPE_VOTER, + false, 0); + vote(chg->hdc_irq_disable_votable, CHARGER_TYPE_VOTER, + false, 0); + + /* + * Enforce 100mA for PD until the real vote comes in later. + * It is guaranteed that pd_active is set prior to + * pd_current_max + */ + vote(chg->usb_icl_votable, PD_VOTER, true, USBIN_100MA); + vote(chg->usb_icl_votable, USB_PSY_VOTER, false, 0); + vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, false, 0); + + /* + * For PPS, Charge Pump is preferred over parallel charger if + * present. + */ + if (chg->pd_active == POWER_SUPPLY_PD_PPS_ACTIVE + && chg->sec_cp_present) { + rc = smblib_select_sec_charger(chg, + POWER_SUPPLY_CHARGER_SEC_CP, + POWER_SUPPLY_CP_PPS, false); + if (rc < 0) + dev_err(chg->dev, "Couldn't enable secondary charger rc=%d\n", + rc); + } + } else { + vote(chg->usb_icl_votable, PD_VOTER, false, 0); + vote(chg->limited_irq_disable_votable, CHARGER_TYPE_VOTER, + true, 0); + vote(chg->hdc_irq_disable_votable, CHARGER_TYPE_VOTER, + true, 0); + + sec_charger = chg->sec_pl_present ? + POWER_SUPPLY_CHARGER_SEC_PL : + POWER_SUPPLY_CHARGER_SEC_NONE; + rc = smblib_select_sec_charger(chg, sec_charger, + POWER_SUPPLY_CP_NONE, false); + if (rc < 0) + dev_err(chg->dev, + "Couldn't enable secondary charger rc=%d\n", + rc); + + /* PD hard resets failed, proceed to detect QC2/3 */ + if (chg->ok_to_pd) { + chg->ok_to_pd = false; + smblib_hvdcp_detect_try_enable(chg, true); + } + } + + smblib_usb_pd_adapter_allowance_override(chg, + !!chg->pd_active ? FORCE_5V : FORCE_NULL); + smblib_update_usb_type(chg); + + if (chg->real_charger_type == POWER_SUPPLY_TYPE_USB && + !chg->ok_to_pd) { + typec_mode = smblib_get_prop_typec_mode(chg); + if (typec_rp_med_high(chg, typec_mode)) + vote(chg->usb_icl_votable, USB_PSY_VOTER, false, 0); + } + + power_supply_changed(chg->usb_psy); + return rc; +} + +int smblib_set_prop_ship_mode(struct smb_charger *chg, + const union power_supply_propval *val) +{ + int rc; + + smblib_dbg(chg, PR_MISC, "Set ship mode: %d!!\n", !!val->intval); + + rc = smblib_masked_write(chg, SHIP_MODE_REG, SHIP_MODE_EN_BIT, + !!val->intval ? SHIP_MODE_EN_BIT : 0); + if (rc < 0) + dev_err(chg->dev, "Couldn't %s ship mode, rc=%d\n", + !!val->intval ? "enable" : "disable", rc); + + return rc; +} + +int smblib_set_prop_pd_in_hard_reset(struct smb_charger *chg, + const union power_supply_propval *val) +{ + int rc = 0; + + if (chg->pd_hard_reset == val->intval) + return rc; + + chg->pd_hard_reset = val->intval; + rc = smblib_masked_write(chg, TYPE_C_EXIT_STATE_CFG_REG, + EXIT_SNK_BASED_ON_CC_BIT, + (chg->pd_hard_reset) ? EXIT_SNK_BASED_ON_CC_BIT : 0); + if (rc < 0) + smblib_err(chg, "Couldn't set EXIT_SNK_BASED_ON_CC rc=%d\n", + rc); + + return rc; +} + +#define JEITA_SOFT 0 +#define JEITA_HARD 1 +static int smblib_update_jeita(struct smb_charger *chg, u32 *thresholds, + int type) +{ + int rc; + u16 temp, base; + + base = CHGR_JEITA_THRESHOLD_BASE_REG(type); + + temp = thresholds[1] & 0xFFFF; + temp = ((temp & 0xFF00) >> 8) | ((temp & 0xFF) << 8); + rc = smblib_batch_write(chg, base, (u8 *)&temp, 2); + if (rc < 0) { + smblib_err(chg, + "Couldn't configure Jeita %s hot threshold rc=%d\n", + (type == JEITA_SOFT) ? "Soft" : "Hard", rc); + return rc; + } + + temp = thresholds[0] & 0xFFFF; + temp = ((temp & 0xFF00) >> 8) | ((temp & 0xFF) << 8); + rc = smblib_batch_write(chg, base + 2, (u8 *)&temp, 2); + if (rc < 0) { + smblib_err(chg, + "Couldn't configure Jeita %s cold threshold rc=%d\n", + (type == JEITA_SOFT) ? "Soft" : "Hard", rc); + return rc; + } + + smblib_dbg(chg, PR_MISC, "%s Jeita threshold configured\n", + (type == JEITA_SOFT) ? "Soft" : "Hard"); + + return 0; +} + +static int smblib_charge_inhibit_en(struct smb_charger *chg, bool enable) +{ + int rc; + + rc = smblib_masked_write(chg, CHGR_CFG2_REG, + CHARGER_INHIBIT_BIT, + enable ? CHARGER_INHIBIT_BIT : 0); + return rc; +} + +static int smblib_soft_jeita_arb_wa(struct smb_charger *chg) +{ + union power_supply_propval pval; + int rc = 0; + bool soft_jeita; + + rc = smblib_get_prop_batt_health(chg, &pval); + if (rc < 0) { + smblib_err(chg, "Couldn't get battery health rc=%d\n", rc); + return rc; + } + + /* Do nothing on entering hard JEITA condition */ + if (pval.intval == POWER_SUPPLY_HEALTH_COLD || + pval.intval == POWER_SUPPLY_HEALTH_HOT) + return 0; + + if (chg->jeita_soft_fcc[0] < 0 || chg->jeita_soft_fcc[1] < 0 || + chg->jeita_soft_fv[0] < 0 || chg->jeita_soft_fv[1] < 0) + return 0; + + soft_jeita = (pval.intval == POWER_SUPPLY_HEALTH_COOL) || + (pval.intval == POWER_SUPPLY_HEALTH_WARM); + + /* Do nothing on entering soft JEITA from hard JEITA */ + if (chg->jeita_arb_flag && soft_jeita) + return 0; + + /* Do nothing, initial to health condition */ + if (!chg->jeita_arb_flag && !soft_jeita) + return 0; + + /* Entering soft JEITA from normal state */ + if (!chg->jeita_arb_flag && soft_jeita) { + vote(chg->chg_disable_votable, JEITA_ARB_VOTER, true, 0); + + rc = smblib_charge_inhibit_en(chg, true); + if (rc < 0) + smblib_err(chg, "Couldn't enable charge inhibit rc=%d\n", + rc); + + rc = smblib_update_jeita(chg, chg->jeita_soft_hys_thlds, + JEITA_SOFT); + if (rc < 0) + smblib_err(chg, + "Couldn't configure Jeita soft threshold rc=%d\n", + rc); + + if (pval.intval == POWER_SUPPLY_HEALTH_COOL) { + vote(chg->fcc_votable, JEITA_ARB_VOTER, true, + chg->jeita_soft_fcc[0]); + vote(chg->fv_votable, JEITA_ARB_VOTER, true, + chg->jeita_soft_fv[0]); + } else { + vote(chg->fcc_votable, JEITA_ARB_VOTER, true, + chg->jeita_soft_fcc[1]); + vote(chg->fv_votable, JEITA_ARB_VOTER, true, + chg->jeita_soft_fv[1]); + } + + vote(chg->chg_disable_votable, JEITA_ARB_VOTER, false, 0); + chg->jeita_arb_flag = true; + } else if (chg->jeita_arb_flag && !soft_jeita) { + /* Exit to health state from soft JEITA */ + + vote(chg->chg_disable_votable, JEITA_ARB_VOTER, true, 0); + + rc = smblib_charge_inhibit_en(chg, false); + if (rc < 0) + smblib_err(chg, "Couldn't disable charge inhibit rc=%d\n", + rc); + + rc = smblib_update_jeita(chg, chg->jeita_soft_thlds, + JEITA_SOFT); + if (rc < 0) + smblib_err(chg, "Couldn't configure Jeita soft threshold rc=%d\n", + rc); + + vote(chg->fcc_votable, JEITA_ARB_VOTER, false, 0); + vote(chg->fv_votable, JEITA_ARB_VOTER, false, 0); + vote(chg->chg_disable_votable, JEITA_ARB_VOTER, false, 0); + chg->jeita_arb_flag = false; + } + + smblib_dbg(chg, PR_MISC, "JEITA ARB status %d, soft JEITA status %d\n", + chg->jeita_arb_flag, soft_jeita); + return rc; +} + +/************************ + * USB MAIN PSY GETTERS * + ************************/ +int smblib_get_prop_fcc_delta(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc, jeita_cc_delta_ua = 0; + + if (chg->sw_jeita_enabled) { + val->intval = 0; + return 0; + } + + rc = smblib_get_jeita_cc_delta(chg, &jeita_cc_delta_ua); + if (rc < 0) { + smblib_err(chg, "Couldn't get jeita cc delta rc=%d\n", rc); + jeita_cc_delta_ua = 0; + } + + val->intval = jeita_cc_delta_ua; + return 0; +} + +/************************ + * USB MAIN PSY SETTERS * + ************************/ +int smblib_get_charge_current(struct smb_charger *chg, + int *total_current_ua) +{ + const struct apsd_result *apsd_result = smblib_get_apsd_result(chg); + union power_supply_propval val = {0, }; + int rc = 0, typec_source_rd, current_ua; + bool non_compliant; + u8 stat; + + if (chg->pd_active) { + *total_current_ua = + get_client_vote_locked(chg->usb_icl_votable, PD_VOTER); + return rc; + } + + rc = smblib_read(chg, LEGACY_CABLE_STATUS_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read TYPE_C_STATUS_5 rc=%d\n", rc); + return rc; + } + non_compliant = stat & TYPEC_NONCOMP_LEGACY_CABLE_STATUS_BIT; + + /* get settled ICL */ + rc = smblib_get_prop_input_current_settled(chg, &val); + if (rc < 0) { + smblib_err(chg, "Couldn't get settled ICL rc=%d\n", rc); + return rc; + } + + typec_source_rd = smblib_get_prop_ufp_mode(chg); + + /* QC 2.0/3.0 adapter */ + if (apsd_result->bit & (QC_3P0_BIT | QC_2P0_BIT)) { + *total_current_ua = HVDCP_CURRENT_UA; + return 0; + } + + if (non_compliant && !chg->typec_legacy_use_rp_icl) { + switch (apsd_result->bit) { + case CDP_CHARGER_BIT: + current_ua = CDP_CURRENT_UA; + break; + case DCP_CHARGER_BIT: + case OCP_CHARGER_BIT: + case FLOAT_CHARGER_BIT: + current_ua = DCP_CURRENT_UA; + break; + default: + current_ua = 0; + break; + } + + *total_current_ua = max(current_ua, val.intval); + return 0; + } + + switch (typec_source_rd) { + case POWER_SUPPLY_TYPEC_SOURCE_DEFAULT: + switch (apsd_result->bit) { + case CDP_CHARGER_BIT: + current_ua = CDP_CURRENT_UA; + break; + case DCP_CHARGER_BIT: + case OCP_CHARGER_BIT: + case FLOAT_CHARGER_BIT: + current_ua = chg->default_icl_ua; + break; + default: + current_ua = 0; + break; + } + break; + case POWER_SUPPLY_TYPEC_SOURCE_MEDIUM: + current_ua = TYPEC_MEDIUM_CURRENT_UA; + break; + case POWER_SUPPLY_TYPEC_SOURCE_HIGH: + current_ua = TYPEC_HIGH_CURRENT_UA; + break; + case POWER_SUPPLY_TYPEC_NON_COMPLIANT: + case POWER_SUPPLY_TYPEC_NONE: + default: + current_ua = 0; + break; + } + + *total_current_ua = max(current_ua, val.intval); + return 0; +} + +#define IADP_OVERHEAT_UA 500000 +int smblib_set_prop_thermal_overheat(struct smb_charger *chg, + int therm_overheat) +{ + int icl_ua = 0; + + if (chg->thermal_overheat == !!therm_overheat) + return 0; + + /* Configure ICL to 500mA in case system health is Overheat */ + if (therm_overheat) + icl_ua = IADP_OVERHEAT_UA; + + if (!chg->cp_disable_votable) + chg->cp_disable_votable = find_votable("CP_DISABLE"); + + if (chg->cp_disable_votable) { + vote(chg->cp_disable_votable, OVERHEAT_LIMIT_VOTER, + therm_overheat, 0); + vote(chg->usb_icl_votable, OVERHEAT_LIMIT_VOTER, + therm_overheat, icl_ua); + } + + chg->thermal_overheat = !!therm_overheat; + return 0; +} + +/********************** + * INTERRUPT HANDLERS * + **********************/ + +irqreturn_t default_irq_handler(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + + smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name); + return IRQ_HANDLED; +} + +irqreturn_t smb_en_irq_handler(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + int rc, input_present; + + if (!chg->cp_disable_votable) { + chg->cp_disable_votable = find_votable("CP_DISABLE"); + if (!chg->cp_disable_votable) + return IRQ_HANDLED; + } + + if (chg->pd_hard_reset) { + vote(chg->cp_disable_votable, BOOST_BACK_VOTER, true, 0); + return IRQ_HANDLED; + } + + rc = smblib_is_input_present(chg, &input_present); + if (rc < 0) { + pr_err("Couldn't get usb presence status rc=%d\n", rc); + return IRQ_HANDLED; + } + + if (input_present) { + /* + * Add some delay to enable SMB1390 switcher after SMB_EN + * pin goes high + */ + usleep_range(1000, 1100); + vote(chg->cp_disable_votable, BOOST_BACK_VOTER, false, 0); + } + + return IRQ_HANDLED; +} + +irqreturn_t sdam_sts_change_irq_handler(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + + smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name); + + mutex_lock(&chg->irq_status_lock); + chg->irq_status |= PULSE_SKIP_IRQ_BIT; + mutex_unlock(&chg->irq_status_lock); + + power_supply_changed(chg->usb_main_psy); + + return IRQ_HANDLED; +} + +#define CHG_TERM_WA_ENTRY_DELAY_MS 300000 /* 5 min */ +#define CHG_TERM_WA_EXIT_DELAY_MS 60000 /* 1 min */ +static void smblib_eval_chg_termination(struct smb_charger *chg, u8 batt_status) +{ + union power_supply_propval pval = {0, }; + int rc = 0; + + rc = smblib_get_prop_from_bms(chg, + POWER_SUPPLY_PROP_REAL_CAPACITY, &pval); + if (rc < 0) { + smblib_err(chg, "Couldn't read SOC value, rc=%d\n", rc); + return; + } + + /* + * Post charge termination, switch to BSM mode triggers the risk of + * over charging as BATFET opening may take some time post the necessity + * of staying in supplemental mode, leading to unintended charging of + * battery. Trigger the charge termination WA once charging is completed + * to prevent overcharing. + */ + if ((batt_status == TERMINATE_CHARGE) && (pval.intval == 100)) { + chg->cc_soc_ref = 0; + chg->last_cc_soc = 0; + chg->term_vbat_uv = 0; + alarm_start_relative(&chg->chg_termination_alarm, + ms_to_ktime(CHG_TERM_WA_ENTRY_DELAY_MS)); + } else if (pval.intval < 100) { + /* + * Reset CC_SOC reference value for charge termination WA once + * we exit the TERMINATE_CHARGE state and soc drops below 100% + */ + chg->cc_soc_ref = 0; + chg->last_cc_soc = 0; + chg->term_vbat_uv = 0; + } +} + +irqreturn_t chg_state_change_irq_handler(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + u8 stat; + int rc; + + smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name); + + rc = smblib_read(chg, BATTERY_CHARGER_STATUS_1_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_1 rc=%d\n", + rc); + return IRQ_HANDLED; + } + + stat = stat & BATTERY_CHARGER_STATUS_MASK; + + if (chg->wa_flags & CHG_TERMINATION_WA) + smblib_eval_chg_termination(chg, stat); + + power_supply_changed(chg->batt_psy); + return IRQ_HANDLED; +} + +irqreturn_t batt_temp_changed_irq_handler(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + int rc; + + smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name); + + if (chg->jeita_configured != JEITA_CFG_COMPLETE) + return IRQ_HANDLED; + + rc = smblib_soft_jeita_arb_wa(chg); + if (rc < 0) { + smblib_err(chg, "Couldn't fix soft jeita arb rc=%d\n", + rc); + return IRQ_HANDLED; + } + + return IRQ_HANDLED; +} + +irqreturn_t batt_psy_changed_irq_handler(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + + smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name); + power_supply_changed(chg->batt_psy); + return IRQ_HANDLED; +} + +#define AICL_STEP_MV 200 +#define MAX_AICL_THRESHOLD_MV 4800 +irqreturn_t usbin_uv_irq_handler(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + struct storm_watch *wdata; + const struct apsd_result *apsd = smblib_get_apsd_result(chg); + int rc; + u8 stat = 0, max_pulses = 0; + + smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name); + + if ((chg->wa_flags & WEAK_ADAPTER_WA) + && is_storming(&irq_data->storm_data)) { + + if (chg->aicl_max_reached) { + smblib_dbg(chg, PR_MISC, + "USBIN_UV storm at max AICL threshold\n"); + return IRQ_HANDLED; + } + + smblib_dbg(chg, PR_MISC, "USBIN_UV storm at threshold %d\n", + chg->aicl_5v_threshold_mv); + + /* suspend USBIN before updating AICL threshold */ + vote(chg->usb_icl_votable, AICL_THRESHOLD_VOTER, true, 0); + + /* delay for VASHDN deglitch */ + msleep(20); + + if (chg->aicl_5v_threshold_mv > MAX_AICL_THRESHOLD_MV) { + /* reached max AICL threshold */ + chg->aicl_max_reached = true; + goto unsuspend_input; + } + + /* Increase AICL threshold by 200mV */ + rc = smblib_set_charge_param(chg, &chg->param.aicl_5v_threshold, + chg->aicl_5v_threshold_mv + AICL_STEP_MV); + if (rc < 0) + dev_err(chg->dev, + "Error in setting AICL threshold rc=%d\n", rc); + else + chg->aicl_5v_threshold_mv += AICL_STEP_MV; + + rc = smblib_set_charge_param(chg, + &chg->param.aicl_cont_threshold, + chg->aicl_cont_threshold_mv + AICL_STEP_MV); + if (rc < 0) + dev_err(chg->dev, + "Error in setting AICL threshold rc=%d\n", rc); + else + chg->aicl_cont_threshold_mv += AICL_STEP_MV; + +unsuspend_input: + /* Force torch in boost mode to ensure it works with low ICL */ + if (chg->chg_param.smb_version == PMI632_SUBTYPE) + schgm_flash_torch_priority(chg, TORCH_BOOST_MODE); + + if (chg->aicl_max_reached) { + smblib_dbg(chg, PR_MISC, + "Reached max AICL threshold resctricting ICL to 100mA\n"); + vote(chg->usb_icl_votable, AICL_THRESHOLD_VOTER, + true, USBIN_100MA); + smblib_run_aicl(chg, RESTART_AICL); + } else { + smblib_run_aicl(chg, RESTART_AICL); + vote(chg->usb_icl_votable, AICL_THRESHOLD_VOTER, + false, 0); + } + + wdata = &chg->irq_info[USBIN_UV_IRQ].irq_data->storm_data; + reset_storm_count(wdata); + } + + if (!chg->irq_info[SWITCHER_POWER_OK_IRQ].irq_data) + return IRQ_HANDLED; + + wdata = &chg->irq_info[SWITCHER_POWER_OK_IRQ].irq_data->storm_data; + reset_storm_count(wdata); + + /* Workaround for non-QC2.0-compliant chargers follows */ + if (!chg->qc2_unsupported_voltage && + apsd->pst == POWER_SUPPLY_TYPE_USB_HVDCP) { + rc = smblib_read(chg, QC_CHANGE_STATUS_REG, &stat); + if (rc < 0) + smblib_err(chg, + "Couldn't read CHANGE_STATUS_REG rc=%d\n", rc); + + if (stat & QC_5V_BIT) + return IRQ_HANDLED; + + rc = smblib_read(chg, HVDCP_PULSE_COUNT_MAX_REG, &max_pulses); + if (rc < 0) + smblib_err(chg, + "Couldn't read QC2 max pulses rc=%d\n", rc); + + chg->qc2_max_pulses = (max_pulses & + HVDCP_PULSE_COUNT_MAX_QC2_MASK); + + if (stat & QC_12V_BIT) { + chg->qc2_unsupported_voltage = QC2_NON_COMPLIANT_12V; + rc = smblib_masked_write(chg, HVDCP_PULSE_COUNT_MAX_REG, + HVDCP_PULSE_COUNT_MAX_QC2_MASK, + HVDCP_PULSE_COUNT_MAX_QC2_9V); + if (rc < 0) + smblib_err(chg, "Couldn't force max pulses to 9V rc=%d\n", + rc); + + } else if (stat & QC_9V_BIT) { + chg->qc2_unsupported_voltage = QC2_NON_COMPLIANT_9V; + rc = smblib_masked_write(chg, HVDCP_PULSE_COUNT_MAX_REG, + HVDCP_PULSE_COUNT_MAX_QC2_MASK, + HVDCP_PULSE_COUNT_MAX_QC2_5V); + if (rc < 0) + smblib_err(chg, "Couldn't force max pulses to 5V rc=%d\n", + rc); + + } + + rc = smblib_masked_write(chg, USBIN_AICL_OPTIONS_CFG_REG, + SUSPEND_ON_COLLAPSE_USBIN_BIT, + 0); + if (rc < 0) + smblib_err(chg, "Couldn't turn off SUSPEND_ON_COLLAPSE_USBIN_BIT rc=%d\n", + rc); + + smblib_rerun_apsd(chg); + } + + return IRQ_HANDLED; +} + +#define USB_WEAK_INPUT_UA 1400000 +#define ICL_CHANGE_DELAY_MS 1000 +irqreturn_t icl_change_irq_handler(int irq, void *data) +{ + u8 stat; + int rc, settled_ua, delay = ICL_CHANGE_DELAY_MS; + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + + if (chg->mode == PARALLEL_MASTER) { + /* + * Ignore if change in ICL is due to DIE temp mitigation. + * This is to prevent any further ICL split. + */ + if (chg->hw_die_temp_mitigation) { + rc = smblib_read(chg, DIE_TEMP_STATUS_REG, &stat); + if (rc < 0) { + smblib_err(chg, + "Couldn't read DIE_TEMP rc=%d\n", rc); + return IRQ_HANDLED; + } + if (stat & (DIE_TEMP_UB_BIT | DIE_TEMP_LB_BIT)) { + smblib_dbg(chg, PR_PARALLEL, + "skip ICL change DIE_TEMP %x\n", stat); + return IRQ_HANDLED; + } + } + + rc = smblib_read(chg, AICL_STATUS_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read AICL_STATUS rc=%d\n", + rc); + return IRQ_HANDLED; + } + + rc = smblib_get_charge_param(chg, &chg->param.icl_stat, + &settled_ua); + if (rc < 0) { + smblib_err(chg, "Couldn't get ICL status rc=%d\n", rc); + return IRQ_HANDLED; + } + + /* If AICL settled then schedule work now */ + if (settled_ua == get_effective_result(chg->usb_icl_votable)) + delay = 0; + + cancel_delayed_work_sync(&chg->icl_change_work); + schedule_delayed_work(&chg->icl_change_work, + msecs_to_jiffies(delay)); + } + + return IRQ_HANDLED; +} + +static void smblib_micro_usb_plugin(struct smb_charger *chg, bool vbus_rising) +{ + if (!vbus_rising) { + smblib_update_usb_type(chg); + smblib_notify_device_mode(chg, false); + smblib_uusb_removal(chg); + } +} + +void smblib_usb_plugin_hard_reset_locked(struct smb_charger *chg) +{ + int rc; + u8 stat; + bool vbus_rising; + struct smb_irq_data *data; + struct storm_watch *wdata; + + rc = smblib_read(chg, USBIN_BASE + INT_RT_STS_OFFSET, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read USB_INT_RT_STS rc=%d\n", rc); + return; + } + + vbus_rising = (bool)(stat & USBIN_PLUGIN_RT_STS_BIT); + + if (vbus_rising) { + /* Remove FCC_STEPPER 1.5A init vote to allow FCC ramp up */ + if (chg->fcc_stepper_enable) + vote(chg->fcc_votable, FCC_STEPPER_VOTER, false, 0); + } else { + if (chg->wa_flags & BOOST_BACK_WA) { + data = chg->irq_info[SWITCHER_POWER_OK_IRQ].irq_data; + if (data) { + wdata = &data->storm_data; + update_storm_count(wdata, + WEAK_CHG_STORM_COUNT); + vote(chg->usb_icl_votable, BOOST_BACK_VOTER, + false, 0); + vote(chg->usb_icl_votable, WEAK_CHARGER_VOTER, + false, 0); + } + } + + /* Force 1500mA FCC on USB removal if fcc stepper is enabled */ + if (chg->fcc_stepper_enable) + vote(chg->fcc_votable, FCC_STEPPER_VOTER, + true, 1500000); + } + + power_supply_changed(chg->usb_psy); + smblib_dbg(chg, PR_INTERRUPT, "IRQ: usbin-plugin %s\n", + vbus_rising ? "attached" : "detached"); +} + +#define PL_DELAY_MS 30000 +void smblib_usb_plugin_locked(struct smb_charger *chg) +{ + int rc; + u8 stat; + bool vbus_rising; + struct smb_irq_data *data; + struct storm_watch *wdata; + + rc = smblib_read(chg, USBIN_BASE + INT_RT_STS_OFFSET, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read USB_INT_RT_STS rc=%d\n", rc); + return; + } + + vbus_rising = (bool)(stat & USBIN_PLUGIN_RT_STS_BIT); + smblib_set_opt_switcher_freq(chg, vbus_rising ? chg->chg_freq.freq_5V : + chg->chg_freq.freq_removal); + + if (vbus_rising) { + cancel_delayed_work_sync(&chg->pr_swap_detach_work); + vote(chg->awake_votable, DETACH_DETECT_VOTER, false, 0); + rc = smblib_request_dpdm(chg, true); + if (rc < 0) + smblib_err(chg, "Couldn't to enable DPDM rc=%d\n", rc); + + /* Enable SW Thermal regulation */ + rc = smblib_set_sw_thermal_regulation(chg, true); + if (rc < 0) + smblib_err(chg, "Couldn't start SW thermal regulation WA, rc=%d\n", + rc); + + /* Remove FCC_STEPPER 1.5A init vote to allow FCC ramp up */ + if (chg->fcc_stepper_enable) + vote(chg->fcc_votable, FCC_STEPPER_VOTER, false, 0); + + /* Schedule work to enable parallel charger */ + vote(chg->awake_votable, PL_DELAY_VOTER, true, 0); + schedule_delayed_work(&chg->pl_enable_work, + msecs_to_jiffies(PL_DELAY_MS)); + } else { + /* Disable SW Thermal Regulation */ + rc = smblib_set_sw_thermal_regulation(chg, false); + if (rc < 0) + smblib_err(chg, "Couldn't stop SW thermal regulation WA, rc=%d\n", + rc); + + if (chg->wa_flags & BOOST_BACK_WA) { + data = chg->irq_info[SWITCHER_POWER_OK_IRQ].irq_data; + if (data) { + wdata = &data->storm_data; + update_storm_count(wdata, + WEAK_CHG_STORM_COUNT); + vote(chg->usb_icl_votable, BOOST_BACK_VOTER, + false, 0); + vote(chg->usb_icl_votable, WEAK_CHARGER_VOTER, + false, 0); + } + } + + /* Force 1500mA FCC on removal if fcc stepper is enabled */ + if (chg->fcc_stepper_enable) + vote(chg->fcc_votable, FCC_STEPPER_VOTER, + true, 1500000); + + if (chg->wa_flags & WEAK_ADAPTER_WA) { + chg->aicl_5v_threshold_mv = + chg->default_aicl_5v_threshold_mv; + chg->aicl_cont_threshold_mv = + chg->default_aicl_cont_threshold_mv; + + smblib_set_charge_param(chg, + &chg->param.aicl_5v_threshold, + chg->aicl_5v_threshold_mv); + smblib_set_charge_param(chg, + &chg->param.aicl_cont_threshold, + chg->aicl_cont_threshold_mv); + chg->aicl_max_reached = false; + + if (chg->chg_param.smb_version == PMI632_SUBTYPE) + schgm_flash_torch_priority(chg, + TORCH_BUCK_MODE); + + data = chg->irq_info[USBIN_UV_IRQ].irq_data; + if (data) { + wdata = &data->storm_data; + reset_storm_count(wdata); + } + vote(chg->usb_icl_votable, AICL_THRESHOLD_VOTER, + false, 0); + } + + rc = smblib_request_dpdm(chg, false); + if (rc < 0) + smblib_err(chg, "Couldn't disable DPDM rc=%d\n", rc); + + smblib_update_usb_type(chg); + } + + if (chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB) + smblib_micro_usb_plugin(chg, vbus_rising); + + vote(chg->temp_change_irq_disable_votable, DEFAULT_VOTER, + !vbus_rising, 0); + + power_supply_changed(chg->usb_psy); + smblib_dbg(chg, PR_INTERRUPT, "IRQ: usbin-plugin %s\n", + vbus_rising ? "attached" : "detached"); +} + +irqreturn_t usb_plugin_irq_handler(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + + if (chg->pd_hard_reset) + smblib_usb_plugin_hard_reset_locked(chg); + else + smblib_usb_plugin_locked(chg); + + return IRQ_HANDLED; +} + +static void smblib_handle_slow_plugin_timeout(struct smb_charger *chg, + bool rising) +{ + smblib_dbg(chg, PR_INTERRUPT, "IRQ: slow-plugin-timeout %s\n", + rising ? "rising" : "falling"); +} + +static void smblib_handle_sdp_enumeration_done(struct smb_charger *chg, + bool rising) +{ + smblib_dbg(chg, PR_INTERRUPT, "IRQ: sdp-enumeration-done %s\n", + rising ? "rising" : "falling"); +} + +#define APSD_EXTENDED_TIMEOUT_MS 400 +/* triggers when HVDCP 3.0 authentication has finished */ +static void smblib_handle_hvdcp_3p0_auth_done(struct smb_charger *chg, + bool rising) +{ + const struct apsd_result *apsd_result; + int rc; + + if (!rising) + return; + + if (chg->mode == PARALLEL_MASTER) + vote(chg->pl_enable_votable_indirect, USBIN_V_VOTER, true, 0); + + /* the APSD done handler will set the USB supply type */ + apsd_result = smblib_get_apsd_result(chg); + + if (apsd_result->bit & QC_3P0_BIT) { + /* for QC3, switch to CP if present */ + if (chg->sec_cp_present) { + rc = smblib_select_sec_charger(chg, + POWER_SUPPLY_CHARGER_SEC_CP, + POWER_SUPPLY_CP_HVDCP3, false); + if (rc < 0) + dev_err(chg->dev, + "Couldn't enable secondary chargers rc=%d\n", + rc); + } + + /* QC3.5 detection timeout */ + if (!chg->apsd_ext_timeout && + !timer_pending(&chg->apsd_timer)) { + smblib_dbg(chg, PR_MISC, + "APSD Extented timer started at %lld\n", + jiffies_to_msecs(jiffies)); + + mod_timer(&chg->apsd_timer, + msecs_to_jiffies(APSD_EXTENDED_TIMEOUT_MS) + + jiffies); + } + } + + smblib_dbg(chg, PR_INTERRUPT, "IRQ: hvdcp-3p0-auth-done rising; %s detected\n", + apsd_result->name); +} + +static void smblib_handle_hvdcp_check_timeout(struct smb_charger *chg, + bool rising, bool qc_charger) +{ + u32 hvdcp_ua = 0; + + if (rising) { + + if (qc_charger) { + hvdcp_ua = (chg->real_charger_type == + POWER_SUPPLY_TYPE_USB_HVDCP) ? + chg->chg_param.hvdcp2_max_icl_ua : + HVDCP_CURRENT_UA; + + /* enable HDC and ICL irq for QC2/3 charger */ + vote(chg->limited_irq_disable_votable, + CHARGER_TYPE_VOTER, false, 0); + vote(chg->hdc_irq_disable_votable, + CHARGER_TYPE_VOTER, false, 0); + vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, true, + hvdcp_ua); + } else { + /* A plain DCP, enforce DCP ICL if specified */ + vote(chg->usb_icl_votable, DCP_VOTER, + chg->dcp_icl_ua != -EINVAL, chg->dcp_icl_ua); + } + } + + smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s %s\n", __func__, + rising ? "rising" : "falling"); +} + +/* triggers when HVDCP is detected */ +static void smblib_handle_hvdcp_detect_done(struct smb_charger *chg, + bool rising) +{ + smblib_dbg(chg, PR_INTERRUPT, "IRQ: hvdcp-detect-done %s\n", + rising ? "rising" : "falling"); +} + +static void update_sw_icl_max(struct smb_charger *chg, int pst) +{ + int typec_mode; + int rp_ua; + + /* while PD is active it should have complete ICL control */ + if (chg->pd_active) + return; + + if (chg->typec_mode == POWER_SUPPLY_TYPEC_SINK_AUDIO_ADAPTER) { + vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, true, 500000); + return; + } + + /* + * HVDCP 2/3, handled separately + */ + if (pst == POWER_SUPPLY_TYPE_USB_HVDCP + || pst == POWER_SUPPLY_TYPE_USB_HVDCP_3) + return; + + /* TypeC rp med or high, use rp value */ + typec_mode = smblib_get_prop_typec_mode(chg); + if (typec_rp_med_high(chg, typec_mode)) { + rp_ua = get_rp_based_dcp_current(chg, typec_mode); + vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, true, rp_ua); + return; + } + + /* rp-std or legacy, USB BC 1.2 */ + switch (pst) { + case POWER_SUPPLY_TYPE_USB: + /* + * USB_PSY will vote to increase the current to 500/900mA once + * enumeration is done. + */ + if (!is_client_vote_enabled(chg->usb_icl_votable, + USB_PSY_VOTER)) { + /* if flash is active force 500mA */ + vote(chg->usb_icl_votable, USB_PSY_VOTER, true, + is_flash_active(chg) ? + SDP_CURRENT_UA : SDP_100_MA); + } + vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, false, 0); + break; + case POWER_SUPPLY_TYPE_USB_CDP: + vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, true, + CDP_CURRENT_UA); + break; + case POWER_SUPPLY_TYPE_USB_DCP: + rp_ua = get_rp_based_dcp_current(chg, typec_mode); + vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, true, rp_ua); + break; + case POWER_SUPPLY_TYPE_USB_FLOAT: + /* + * limit ICL to 100mA, the USB driver will enumerate to check + * if this is a SDP and appropriately set the current + */ + vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, true, + SDP_100_MA); + break; + case POWER_SUPPLY_TYPE_UNKNOWN: + default: + vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, true, + SDP_100_MA); + break; + } +} + +static void smblib_handle_apsd_done(struct smb_charger *chg, bool rising) +{ + const struct apsd_result *apsd_result; + + if (!rising) + return; + + apsd_result = smblib_update_usb_type(chg); + + update_sw_icl_max(chg, apsd_result->pst); + + switch (apsd_result->bit) { + case SDP_CHARGER_BIT: + case CDP_CHARGER_BIT: + case FLOAT_CHARGER_BIT: + if (chg->use_extcon) + smblib_notify_device_mode(chg, true); + break; + case OCP_CHARGER_BIT: + case DCP_CHARGER_BIT: + break; + default: + break; + } + + smblib_dbg(chg, PR_INTERRUPT, "IRQ: apsd-done rising; %s detected\n", + apsd_result->name); +} + +irqreturn_t usb_source_change_irq_handler(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + int rc = 0; + u8 stat; + + /* PD session is ongoing, ignore BC1.2 and QC detection */ + if (chg->pd_active) + return IRQ_HANDLED; + + rc = smblib_read(chg, APSD_STATUS_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read APSD_STATUS rc=%d\n", rc); + return IRQ_HANDLED; + } + smblib_dbg(chg, PR_INTERRUPT, "APSD_STATUS = 0x%02x\n", stat); + + if ((chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB) + && (stat & APSD_DTC_STATUS_DONE_BIT) + && !chg->uusb_apsd_rerun_done) { + /* + * Force re-run APSD to handle slow insertion related + * charger-mis-detection. + */ + chg->uusb_apsd_rerun_done = true; + smblib_rerun_apsd_if_required(chg); + return IRQ_HANDLED; + } + + smblib_handle_apsd_done(chg, + (bool)(stat & APSD_DTC_STATUS_DONE_BIT)); + + smblib_handle_hvdcp_detect_done(chg, + (bool)(stat & QC_CHARGER_BIT)); + + smblib_handle_hvdcp_check_timeout(chg, + (bool)(stat & HVDCP_CHECK_TIMEOUT_BIT), + (bool)(stat & QC_CHARGER_BIT)); + + smblib_handle_hvdcp_3p0_auth_done(chg, + (bool)(stat & QC_AUTH_DONE_STATUS_BIT)); + + smblib_handle_sdp_enumeration_done(chg, + (bool)(stat & ENUMERATION_DONE_BIT)); + + smblib_handle_slow_plugin_timeout(chg, + (bool)(stat & SLOW_PLUGIN_TIMEOUT_BIT)); + + smblib_hvdcp_adaptive_voltage_change(chg); + + power_supply_changed(chg->usb_psy); + + rc = smblib_read(chg, APSD_STATUS_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read APSD_STATUS rc=%d\n", rc); + return IRQ_HANDLED; + } + smblib_dbg(chg, PR_INTERRUPT, "APSD_STATUS = 0x%02x\n", stat); + + return IRQ_HANDLED; +} + +enum alarmtimer_restart smblib_lpd_recheck_timer(struct alarm *alarm, + ktime_t time) +{ + union power_supply_propval pval; + struct smb_charger *chg = container_of(alarm, struct smb_charger, + lpd_recheck_timer); + int rc; + + if (chg->lpd_reason == LPD_MOISTURE_DETECTED) { + pval.intval = POWER_SUPPLY_TYPEC_PR_DUAL; + rc = smblib_set_prop_typec_power_role(chg, &pval); + if (rc < 0) { + smblib_err(chg, "Couldn't write 0x%02x to TYPE_C_INTRPT_ENB_SOFTWARE_CTRL rc=%d\n", + pval.intval, rc); + return ALARMTIMER_NORESTART; + } + chg->moisture_present = false; + power_supply_changed(chg->usb_psy); + } else { + rc = smblib_masked_write(chg, TYPE_C_INTERRUPT_EN_CFG_2_REG, + TYPEC_WATER_DETECTION_INT_EN_BIT, + TYPEC_WATER_DETECTION_INT_EN_BIT); + if (rc < 0) { + smblib_err(chg, "Couldn't set TYPE_C_INTERRUPT_EN_CFG_2_REG rc=%d\n", + rc); + return ALARMTIMER_NORESTART; + } + } + + chg->lpd_stage = LPD_STAGE_NONE; + chg->lpd_reason = LPD_NONE; + + return ALARMTIMER_NORESTART; +} + +#define RSBU_K_300K_UV 3000000 +static bool smblib_src_lpd(struct smb_charger *chg) +{ + union power_supply_propval pval; + bool lpd_flag = false; + u8 stat; + int rc; + + if (chg->lpd_disabled) + return false; + + rc = smblib_read(chg, TYPE_C_SRC_STATUS_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read TYPE_C_SRC_STATUS_REG rc=%d\n", + rc); + return false; + } + + switch (stat & DETECTED_SNK_TYPE_MASK) { + case SRC_DEBUG_ACCESS_BIT: + if (smblib_rsbux_low(chg, RSBU_K_300K_UV)) + lpd_flag = true; + break; + case SRC_RD_RA_VCONN_BIT: + case SRC_RD_OPEN_BIT: + case AUDIO_ACCESS_RA_RA_BIT: + default: + break; + } + + if (lpd_flag) { + chg->lpd_stage = LPD_STAGE_COMMIT; + pval.intval = POWER_SUPPLY_TYPEC_PR_SINK; + rc = smblib_set_prop_typec_power_role(chg, &pval); + if (rc < 0) + smblib_err(chg, "Couldn't write 0x%02x to TYPE_C_INTRPT_ENB_SOFTWARE_CTRL rc=%d\n", + pval.intval, rc); + chg->lpd_reason = LPD_MOISTURE_DETECTED; + chg->moisture_present = true; + vote(chg->usb_icl_votable, LPD_VOTER, true, 0); + alarm_start_relative(&chg->lpd_recheck_timer, + ms_to_ktime(60000)); + power_supply_changed(chg->usb_psy); + } else { + chg->lpd_reason = LPD_NONE; + chg->typec_mode = smblib_get_prop_typec_mode(chg); + } + + return lpd_flag; +} + +static void typec_src_fault_condition_cfg(struct smb_charger *chg, bool src) +{ + int rc; + u8 mask = USBIN_MID_COMP_FAULT_EN_BIT | USBIN_COLLAPSE_FAULT_EN_BIT; + + rc = smblib_masked_write(chg, OTG_FAULT_CONDITION_CFG_REG, mask, + src ? 0 : mask); + if (rc < 0) + smblib_err(chg, "Couldn't write OTG_FAULT_CONDITION_CFG_REG rc=%d\n", + rc); +} + +static void typec_sink_insertion(struct smb_charger *chg) +{ + int rc; + + typec_src_fault_condition_cfg(chg, true); + rc = smblib_set_charge_param(chg, &chg->param.freq_switcher, + chg->chg_freq.freq_above_otg_threshold); + if (rc < 0) + dev_err(chg->dev, "Error in setting freq_boost rc=%d\n", rc); + + if (chg->use_extcon) { + smblib_notify_usb_host(chg, true); + chg->otg_present = true; + } + + if (!chg->pr_swap_in_progress) + chg->ok_to_pd = (!(chg->pd_disabled) || chg->early_usb_attach) + && !chg->pd_not_supported; +} + +static void typec_src_insertion(struct smb_charger *chg) +{ + int rc = 0; + u8 stat; + + if (chg->pr_swap_in_progress) { + vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, false, 0); + return; + } + + rc = smblib_read(chg, LEGACY_CABLE_STATUS_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read TYPE_C_STATE_MACHINE_STATUS_REG rc=%d\n", + rc); + return; + } + + chg->typec_legacy = stat & TYPEC_LEGACY_CABLE_STATUS_BIT; + chg->ok_to_pd = (!(chg->typec_legacy || chg->pd_disabled) + || chg->early_usb_attach) && !chg->pd_not_supported; + + /* allow apsd proceed to detect QC2/3 */ + if (!chg->ok_to_pd) + smblib_hvdcp_detect_try_enable(chg, true); +} + +static void typec_ra_ra_insertion(struct smb_charger *chg) +{ + vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, true, 500000); + vote(chg->usb_icl_votable, USB_PSY_VOTER, false, 0); + chg->ok_to_pd = false; + smblib_hvdcp_detect_enable(chg, true); +} + +static int smblib_role_switch_failure(struct smb_charger *chg) +{ + int rc = 0; + union power_supply_propval pval = {0, }; + + if (!chg->use_extcon) + return 0; + + rc = smblib_get_prop_usb_present(chg, &pval); + if (rc < 0) { + smblib_err(chg, "Couldn't get usb presence status rc=%d\n", + rc); + return rc; + } + + /* + * When role switch fails notify the + * current charger state to usb driver. + */ + if (pval.intval) { + smblib_dbg(chg, PR_MISC, " Role reversal failed, notifying device mode to usb driver.\n"); + smblib_notify_device_mode(chg, true); + } + + return rc; +} + +static int typec_partner_register(struct smb_charger *chg) +{ + int typec_mode, rc = 0; + + mutex_lock(&chg->typec_lock); + + if (!chg->typec_port || chg->pr_swap_in_progress) + goto unlock; + + if (!chg->typec_partner) { + if (chg->sink_src_mode == AUDIO_ACCESS_MODE) + chg->typec_partner_desc.accessory = + TYPEC_ACCESSORY_AUDIO; + else + chg->typec_partner_desc.accessory = + TYPEC_ACCESSORY_NONE; + + chg->typec_partner = typec_register_partner(chg->typec_port, + &chg->typec_partner_desc); + if (IS_ERR(chg->typec_partner)) { + rc = PTR_ERR(chg->typec_partner); + pr_err("failed to register typec_partner rc=%d\n", rc); + goto unlock; + } + } + + typec_mode = smblib_get_prop_typec_mode(chg); + + if (typec_mode >= POWER_SUPPLY_TYPEC_SOURCE_DEFAULT + || typec_mode == POWER_SUPPLY_TYPEC_NONE) { + + if (chg->typec_role_swap_failed) { + rc = smblib_role_switch_failure(chg); + if (rc < 0) + smblib_err(chg, "Failed to role switch rc=%d\n", + rc); + chg->typec_role_swap_failed = false; + } + + typec_set_data_role(chg->typec_port, TYPEC_DEVICE); + typec_set_pwr_role(chg->typec_port, TYPEC_SINK); + } else { + typec_set_data_role(chg->typec_port, TYPEC_HOST); + typec_set_pwr_role(chg->typec_port, TYPEC_SOURCE); + } + +unlock: + mutex_unlock(&chg->typec_lock); + return rc; +} + +static void typec_partner_unregister(struct smb_charger *chg) +{ + mutex_lock(&chg->typec_lock); + + if (chg->typec_partner && !chg->pr_swap_in_progress) { + smblib_dbg(chg, PR_MISC, "Un-registering typeC partner\n"); + typec_unregister_partner(chg->typec_partner); + chg->typec_partner = NULL; + } + + mutex_unlock(&chg->typec_lock); +} + +static const char * const dr_mode_text[] = { + "ufp", "dfp", "none" +}; + +static int smblib_force_dr_mode(struct smb_charger *chg, int mode) +{ + int rc = 0; + + switch (mode) { + case TYPEC_PORT_SNK: + rc = smblib_masked_write(chg, TYPE_C_MODE_CFG_REG, + TYPEC_POWER_ROLE_CMD_MASK, EN_SNK_ONLY_BIT); + if (rc < 0) { + smblib_err(chg, "Couldn't enable snk, rc=%d\n", rc); + return rc; + } + break; + case TYPEC_PORT_SRC: + rc = smblib_masked_write(chg, TYPE_C_MODE_CFG_REG, + TYPEC_POWER_ROLE_CMD_MASK, EN_SRC_ONLY_BIT); + if (rc < 0) { + smblib_err(chg, "Couldn't enable src, rc=%d\n", rc); + return rc; + } + break; + case TYPEC_PORT_DRP: + rc = smblib_masked_write(chg, TYPE_C_MODE_CFG_REG, + TYPEC_POWER_ROLE_CMD_MASK, 0); + if (rc < 0) { + smblib_err(chg, "Couldn't enable DRP, rc=%d\n", rc); + return rc; + } + break; + default: + smblib_err(chg, "Power role %d not supported\n", mode); + return -EINVAL; + } + + chg->dr_mode = mode; + + return rc; +} + +int smblib_typec_port_type_set(const struct typec_capability *cap, + enum typec_port_type type) +{ + struct smb_charger *chg = container_of(cap, + struct smb_charger, typec_caps); + int rc = 0; + + mutex_lock(&chg->typec_lock); + + if ((chg->pr_swap_in_progress) || (type == TYPEC_PORT_DRP)) { + smblib_dbg(chg, PR_MISC, "Ignoring port type request type = %d swap_in_progress = %d\n", + type, chg->pr_swap_in_progress); + goto unlock; + } + + chg->pr_swap_in_progress = true; + + rc = smblib_force_dr_mode(chg, type); + if (rc < 0) { + chg->pr_swap_in_progress = false; + smblib_err(chg, "Failed to force mode, rc=%d\n", rc); + goto unlock; + } + + smblib_dbg(chg, PR_MISC, "Requested role %s\n", + type ? "SINK" : "SOURCE"); + + /* + * As per the hardware requirements, + * schedule the work with required delay. + */ + if (!(delayed_work_pending(&chg->role_reversal_check))) { + cancel_delayed_work_sync(&chg->role_reversal_check); + schedule_delayed_work(&chg->role_reversal_check, + msecs_to_jiffies(ROLE_REVERSAL_DELAY_MS)); + vote(chg->awake_votable, TYPEC_SWAP_VOTER, true, 0); + } + +unlock: + mutex_unlock(&chg->typec_lock); + return rc; +} + +static void smblib_typec_role_check_work(struct work_struct *work) +{ + struct smb_charger *chg = container_of(work, struct smb_charger, + role_reversal_check.work); + int rc = 0; + + mutex_lock(&chg->typec_lock); + + switch (chg->dr_mode) { + case TYPEC_PORT_SNK: + if (chg->typec_mode < POWER_SUPPLY_TYPEC_SOURCE_DEFAULT) { + smblib_dbg(chg, PR_MISC, "Role reversal not latched to UFP in %d msecs. Resetting to DRP mode\n", + ROLE_REVERSAL_DELAY_MS); + rc = smblib_force_dr_mode(chg, TYPEC_PORT_DRP); + if (rc < 0) + smblib_err(chg, "Failed to set DRP mode, rc=%d\n", + rc); + } else { + chg->power_role = POWER_SUPPLY_TYPEC_PR_SINK; + typec_set_pwr_role(chg->typec_port, TYPEC_SINK); + typec_set_data_role(chg->typec_port, TYPEC_DEVICE); + smblib_dbg(chg, PR_MISC, "Role changed successfully to SINK"); + } + break; + case TYPEC_PORT_SRC: + if (chg->typec_mode >= POWER_SUPPLY_TYPEC_SOURCE_DEFAULT + || chg->typec_mode == POWER_SUPPLY_TYPEC_NONE) { + smblib_dbg(chg, PR_MISC, "Role reversal not latched to DFP in %d msecs. Resetting to DRP mode\n", + ROLE_REVERSAL_DELAY_MS); + chg->pr_swap_in_progress = false; + chg->typec_role_swap_failed = true; + rc = smblib_force_dr_mode(chg, TYPEC_PORT_DRP); + if (rc < 0) + smblib_err(chg, "Failed to set DRP mode, rc=%d\n", + rc); + } else { + chg->power_role = POWER_SUPPLY_TYPEC_PR_SOURCE; + typec_set_pwr_role(chg->typec_port, TYPEC_SOURCE); + typec_set_data_role(chg->typec_port, TYPEC_HOST); + smblib_dbg(chg, PR_MISC, "Role changed successfully to SOURCE"); + } + break; + default: + pr_debug("Already in DRP mode\n"); + break; + } + + chg->pr_swap_in_progress = false; + vote(chg->awake_votable, TYPEC_SWAP_VOTER, false, 0); + mutex_unlock(&chg->typec_lock); +} + +static void typec_sink_removal(struct smb_charger *chg) +{ + int rc; + + typec_src_fault_condition_cfg(chg, false); + rc = smblib_set_charge_param(chg, &chg->param.freq_switcher, + chg->chg_freq.freq_removal); + if (rc < 0) + dev_err(chg->dev, "Error in setting freq_removal rc=%d\n", rc); + + if (chg->use_extcon) { + if (chg->otg_present) + smblib_notify_usb_host(chg, false); + chg->otg_present = false; + } +} + +static void typec_src_removal(struct smb_charger *chg) +{ + int rc; + struct smb_irq_data *data; + struct storm_watch *wdata; + int sec_charger; + u8 val[2] = {0}; + + sec_charger = chg->sec_pl_present ? POWER_SUPPLY_CHARGER_SEC_PL : + POWER_SUPPLY_CHARGER_SEC_NONE; + + rc = smblib_select_sec_charger(chg, sec_charger, POWER_SUPPLY_CP_NONE, + false); + if (rc < 0) + dev_err(chg->dev, + "Couldn't disable secondary charger rc=%d\n", rc); + + chg->qc3p5_detected = false; + typec_src_fault_condition_cfg(chg, false); + smblib_hvdcp_detect_try_enable(chg, false); + smblib_update_usb_type(chg); + + if (chg->wa_flags & BOOST_BACK_WA) { + data = chg->irq_info[SWITCHER_POWER_OK_IRQ].irq_data; + if (data) { + wdata = &data->storm_data; + update_storm_count(wdata, WEAK_CHG_STORM_COUNT); + vote(chg->usb_icl_votable, BOOST_BACK_VOTER, false, 0); + vote(chg->usb_icl_votable, WEAK_CHARGER_VOTER, + false, 0); + } + } + + cancel_delayed_work_sync(&chg->pl_enable_work); + + /* reset input current limit voters */ + vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, true, + is_flash_active(chg) ? SDP_CURRENT_UA : SDP_100_MA); + vote(chg->usb_icl_votable, PD_VOTER, false, 0); + vote(chg->usb_icl_votable, USB_PSY_VOTER, false, 0); + vote(chg->usb_icl_votable, DCP_VOTER, false, 0); + vote(chg->usb_icl_votable, SW_QC3_VOTER, false, 0); + vote(chg->usb_icl_votable, CTM_VOTER, false, 0); + vote(chg->usb_icl_votable, HVDCP2_ICL_VOTER, false, 0); + vote(chg->usb_icl_votable, CHG_TERMINATION_VOTER, false, 0); + vote(chg->usb_icl_votable, THERMAL_THROTTLE_VOTER, false, 0); + vote(chg->usb_icl_votable, LPD_VOTER, false, 0); + + /* reset usb irq voters */ + vote(chg->limited_irq_disable_votable, CHARGER_TYPE_VOTER, + true, 0); + vote(chg->hdc_irq_disable_votable, CHARGER_TYPE_VOTER, true, 0); + vote(chg->hdc_irq_disable_votable, HDC_IRQ_VOTER, false, 0); + + /* reset parallel voters */ + vote(chg->pl_disable_votable, PL_DELAY_VOTER, true, 0); + vote(chg->pl_disable_votable, PL_FCC_LOW_VOTER, false, 0); + vote(chg->pl_enable_votable_indirect, USBIN_I_VOTER, false, 0); + vote(chg->pl_enable_votable_indirect, USBIN_V_VOTER, false, 0); + vote(chg->awake_votable, PL_DELAY_VOTER, false, 0); + + /* Remove SW thermal regulation WA votes */ + vote(chg->usb_icl_votable, SW_THERM_REGULATION_VOTER, false, 0); + vote(chg->pl_disable_votable, SW_THERM_REGULATION_VOTER, false, 0); + vote(chg->dc_suspend_votable, SW_THERM_REGULATION_VOTER, false, 0); + if (chg->cp_disable_votable) + vote(chg->cp_disable_votable, SW_THERM_REGULATION_VOTER, + false, 0); + + /* reset USBOV votes and cancel work */ + cancel_delayed_work_sync(&chg->usbov_dbc_work); + vote(chg->awake_votable, USBOV_DBC_VOTER, false, 0); + chg->dbc_usbov = false; + + chg->pulse_cnt = 0; + chg->usb_icl_delta_ua = 0; + chg->voltage_min_uv = MICRO_5V; + chg->voltage_max_uv = MICRO_5V; + chg->usbin_forced_max_uv = 0; + chg->chg_param.forced_main_fcc = 0; + + /* Reset all CC mode votes */ + vote(chg->fcc_main_votable, MAIN_FCC_VOTER, false, 0); + chg->adapter_cc_mode = 0; + chg->thermal_overheat = 0; + vote_override(chg->fcc_votable, CC_MODE_VOTER, false, 0); + vote_override(chg->usb_icl_votable, CC_MODE_VOTER, false, 0); + vote(chg->cp_disable_votable, OVERHEAT_LIMIT_VOTER, false, 0); + vote(chg->usb_icl_votable, OVERHEAT_LIMIT_VOTER, false, 0); + + /* write back the default FLOAT charger configuration */ + rc = smblib_masked_write(chg, USBIN_OPTIONS_2_CFG_REG, + (u8)FLOAT_OPTIONS_MASK, chg->float_cfg); + if (rc < 0) + smblib_err(chg, "Couldn't write float charger options rc=%d\n", + rc); + + if (chg->sdam_base) { + rc = smblib_write(chg, + chg->sdam_base + SDAM_QC_DET_STATUS_REG, 0); + if (rc < 0) + pr_err("Couldn't clear SDAM QC status rc=%d\n", rc); + + rc = smblib_batch_write(chg, + chg->sdam_base + SDAM_QC_ADC_LSB_REG, val, 2); + if (rc < 0) + pr_err("Couldn't clear SDAM ADC status rc=%d\n", rc); + } + + if (!chg->pr_swap_in_progress) { + rc = smblib_usb_pd_adapter_allowance_override(chg, FORCE_NULL); + if (rc < 0) + smblib_err(chg, "Couldn't set FORCE_NULL rc=%d\n", rc); + + rc = smblib_set_charge_param(chg, + &chg->param.aicl_cont_threshold, + chg->default_aicl_cont_threshold_mv); + if (rc < 0) + smblib_err(chg, "Couldn't restore aicl_cont_threshold, rc=%d", + rc); + } + /* + * if non-compliant charger caused UV, restore original max pulses + * and turn SUSPEND_ON_COLLAPSE_USBIN_BIT back on. + */ + if (chg->qc2_unsupported_voltage) { + rc = smblib_masked_write(chg, HVDCP_PULSE_COUNT_MAX_REG, + HVDCP_PULSE_COUNT_MAX_QC2_MASK, + chg->qc2_max_pulses); + if (rc < 0) + smblib_err(chg, "Couldn't restore max pulses rc=%d\n", + rc); + + rc = smblib_masked_write(chg, USBIN_AICL_OPTIONS_CFG_REG, + SUSPEND_ON_COLLAPSE_USBIN_BIT, + SUSPEND_ON_COLLAPSE_USBIN_BIT); + if (rc < 0) + smblib_err(chg, "Couldn't turn on SUSPEND_ON_COLLAPSE_USBIN_BIT rc=%d\n", + rc); + + chg->qc2_unsupported_voltage = QC2_COMPLIANT; + } + + if (chg->use_extcon) + smblib_notify_device_mode(chg, false); + + chg->typec_legacy = false; + + del_timer_sync(&chg->apsd_timer); + chg->apsd_ext_timeout = false; +} + +static void typec_mode_unattached(struct smb_charger *chg) +{ + vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, true, USBIN_100MA); +} + +static void smblib_handle_rp_change(struct smb_charger *chg, int typec_mode) +{ + const struct apsd_result *apsd = smblib_get_apsd_result(chg); + + /* + * We want the ICL vote @ 100mA for a FLOAT charger + * until the detection by the USB stack is complete. + * Ignore the Rp changes unless there is a + * pre-existing valid vote or FLOAT is configured for + * SDP current. + */ + if (apsd->pst == POWER_SUPPLY_TYPE_USB_FLOAT) { + if (get_client_vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER) + <= USBIN_100MA + || (chg->float_cfg & FLOAT_OPTIONS_MASK) + == FORCE_FLOAT_SDP_CFG_BIT) + return; + } + + update_sw_icl_max(chg, apsd->pst); + + smblib_dbg(chg, PR_MISC, "CC change old_mode=%d new_mode=%d\n", + chg->typec_mode, typec_mode); +} + +static void smblib_lpd_launch_ra_open_work(struct smb_charger *chg) +{ + u8 stat; + int rc; + + if (chg->lpd_disabled) + return; + + rc = smblib_read(chg, TYPE_C_MISC_STATUS_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read TYPE_C_MISC_STATUS_REG rc=%d\n", + rc); + return; + } + + if (!(stat & TYPEC_TCCDEBOUNCE_DONE_STATUS_BIT) + && chg->lpd_stage == LPD_STAGE_NONE) { + chg->lpd_stage = LPD_STAGE_FLOAT; + cancel_delayed_work_sync(&chg->lpd_ra_open_work); + vote(chg->awake_votable, LPD_VOTER, true, 0); + schedule_delayed_work(&chg->lpd_ra_open_work, + msecs_to_jiffies(300)); + } +} + +irqreturn_t typec_or_rid_detection_change_irq_handler(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + + smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name); + + if (chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB) { + if (chg->uusb_moisture_protection_enabled) { + /* + * Adding pm_stay_awake as because pm_relax is called + * on exit path from the work routine. + */ + pm_stay_awake(chg->dev); + schedule_work(&chg->moisture_protection_work); + } + + cancel_delayed_work_sync(&chg->uusb_otg_work); + /* + * Skip OTG enablement if RID interrupt triggers with moisture + * protection still enabled. + */ + if (!chg->moisture_present) { + vote(chg->awake_votable, OTG_DELAY_VOTER, true, 0); + smblib_dbg(chg, PR_INTERRUPT, "Scheduling OTG work\n"); + schedule_delayed_work(&chg->uusb_otg_work, + msecs_to_jiffies(chg->otg_delay_ms)); + } + + goto out; + } + + if (chg->pr_swap_in_progress || chg->pd_hard_reset) + goto out; + + smblib_lpd_launch_ra_open_work(chg); + + if (chg->usb_psy) + power_supply_changed(chg->usb_psy); + +out: + return IRQ_HANDLED; +} + +irqreturn_t typec_state_change_irq_handler(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + int typec_mode; + + if (chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB) { + smblib_dbg(chg, PR_INTERRUPT, + "Ignoring for micro USB\n"); + return IRQ_HANDLED; + } + + typec_mode = smblib_get_prop_typec_mode(chg); + if (chg->sink_src_mode != UNATTACHED_MODE + && (typec_mode != chg->typec_mode)) + smblib_handle_rp_change(chg, typec_mode); + chg->typec_mode = typec_mode; + + smblib_dbg(chg, PR_INTERRUPT, "IRQ: cc-state-change; Type-C %s detected\n", + smblib_typec_mode_name[chg->typec_mode]); + + power_supply_changed(chg->usb_psy); + + return IRQ_HANDLED; +} + +static void smblib_lpd_clear_ra_open_work(struct smb_charger *chg) +{ + if (chg->lpd_disabled) + return; + + cancel_delayed_work_sync(&chg->lpd_detach_work); + chg->lpd_stage = LPD_STAGE_FLOAT_CANCEL; + cancel_delayed_work_sync(&chg->lpd_ra_open_work); + vote(chg->awake_votable, LPD_VOTER, false, 0); +} + +#define TYPEC_DETACH_DETECT_DELAY_MS 2000 +irqreturn_t typec_attach_detach_irq_handler(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + u8 stat; + bool attached = false; + int rc; + + smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name); + + rc = smblib_read(chg, TYPE_C_STATE_MACHINE_STATUS_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read TYPE_C_STATE_MACHINE_STATUS_REG rc=%d\n", + rc); + return IRQ_HANDLED; + } + + attached = !!(stat & TYPEC_ATTACH_DETACH_STATE_BIT); + + if (attached) { + smblib_lpd_clear_ra_open_work(chg); + + rc = smblib_read(chg, TYPE_C_MISC_STATUS_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read TYPE_C_MISC_STATUS_REG rc=%d\n", + rc); + return IRQ_HANDLED; + } + + if (smblib_get_prop_dfp_mode(chg) == + POWER_SUPPLY_TYPEC_SINK_AUDIO_ADAPTER) { + chg->sink_src_mode = AUDIO_ACCESS_MODE; + typec_ra_ra_insertion(chg); + } else if (stat & SNK_SRC_MODE_BIT) { + if (smblib_src_lpd(chg)) + return IRQ_HANDLED; + chg->sink_src_mode = SRC_MODE; + typec_sink_insertion(chg); + } else { + chg->sink_src_mode = SINK_MODE; + typec_src_insertion(chg); + } + + rc = typec_partner_register(chg); + if (rc < 0) + smblib_err(chg, "failed to register partner rc =%d\n", + rc); + } else { + switch (chg->sink_src_mode) { + case SRC_MODE: + typec_sink_removal(chg); + break; + case SINK_MODE: + case AUDIO_ACCESS_MODE: + typec_src_removal(chg); + break; + case UNATTACHED_MODE: + default: + typec_mode_unattached(chg); + break; + } + + if (!chg->pr_swap_in_progress) { + chg->ok_to_pd = false; + chg->sink_src_mode = UNATTACHED_MODE; + chg->early_usb_attach = false; + smblib_apsd_enable(chg, true); + } + + /* + * Restore DRP mode on type-C cable disconnect if role + * swap is not in progress, to ensure forced sink or src + * mode configuration is reset properly. + */ + mutex_lock(&chg->typec_lock); + + if (chg->typec_port && !chg->pr_swap_in_progress) { + + /* + * Schedule the work to differentiate actual removal + * of cable and detach interrupt during role swap, + * unregister the partner only during actual cable + * removal. + */ + cancel_delayed_work(&chg->pr_swap_detach_work); + vote(chg->awake_votable, DETACH_DETECT_VOTER, true, 0); + schedule_delayed_work(&chg->pr_swap_detach_work, + msecs_to_jiffies(TYPEC_DETACH_DETECT_DELAY_MS)); + smblib_force_dr_mode(chg, TYPEC_PORT_DRP); + /* + * To handle cable removal during role + * swap failure. + */ + chg->typec_role_swap_failed = false; + } + + mutex_unlock(&chg->typec_lock); + + if (chg->lpd_stage == LPD_STAGE_FLOAT_CANCEL) + schedule_delayed_work(&chg->lpd_detach_work, + msecs_to_jiffies(1000)); + } + + rc = smblib_masked_write(chg, USB_CMD_PULLDOWN_REG, + EN_PULLDOWN_USB_IN_BIT, + attached ? 0 : EN_PULLDOWN_USB_IN_BIT); + if (rc < 0) + smblib_err(chg, "Couldn't configure pulldown on USB_IN rc=%d\n", + rc); + + power_supply_changed(chg->usb_psy); + + return IRQ_HANDLED; +} + +static void dcin_aicl(struct smb_charger *chg) +{ + int rc, icl, icl_save; + int input_present; + bool aicl_done = true; + + /* + * Hold awake votable to prevent pm_relax being called prior to + * completion of this work. + */ + vote(chg->awake_votable, DCIN_AICL_VOTER, true, 0); + +increment: + mutex_lock(&chg->dcin_aicl_lock); + + rc = smblib_get_charge_param(chg, &chg->param.dc_icl, &icl); + if (rc < 0) + goto err; + + if (icl == chg->wls_icl_ua) { + /* Upper limit reached; do nothing */ + smblib_dbg(chg, PR_WLS, "hit max ICL: stop\n"); + + rc = smblib_is_input_present(chg, &input_present); + if (rc < 0 || !(input_present & INPUT_PRESENT_DC)) + aicl_done = false; + + goto unlock; + } + + icl = min(chg->wls_icl_ua, icl + DCIN_ICL_STEP_UA); + icl_save = icl; + + rc = smblib_set_charge_param(chg, &chg->param.dc_icl, icl); + if (rc < 0) + goto err; + + mutex_unlock(&chg->dcin_aicl_lock); + + smblib_dbg(chg, PR_WLS, "icl: %d mA\n", (icl / 1000)); + + /* Check to see if DC is still present before and after sleep */ + rc = smblib_is_input_present(chg, &input_present); + if (rc < 0 || !(input_present & INPUT_PRESENT_DC)) { + aicl_done = false; + goto unvote; + } + + /* + * Wait awhile to check for any DCIN_UVs (the UV handler reduces the + * ICL). If the adaptor collapses, the ICL read after waking up will be + * lesser, indicating that the AICL process is complete. + */ + msleep(500); + + rc = smblib_is_input_present(chg, &input_present); + if (rc < 0 || !(input_present & INPUT_PRESENT_DC)) { + aicl_done = false; + goto unvote; + } + + mutex_lock(&chg->dcin_aicl_lock); + + rc = smblib_get_charge_param(chg, &chg->param.dc_icl, &icl); + if (rc < 0) + goto err; + + if (icl < icl_save) { + smblib_dbg(chg, PR_WLS, "done: icl: %d mA\n", (icl / 1000)); + goto unlock; + } + + mutex_unlock(&chg->dcin_aicl_lock); + + goto increment; + +err: + aicl_done = false; +unlock: + mutex_unlock(&chg->dcin_aicl_lock); +unvote: + vote(chg->awake_votable, DCIN_AICL_VOTER, false, 0); + chg->dcin_aicl_done = aicl_done; +} + +static void dcin_aicl_work(struct work_struct *work) +{ + struct smb_charger *chg = container_of(work, struct smb_charger, + dcin_aicl_work); + dcin_aicl(chg); +} + +static enum alarmtimer_restart dcin_aicl_alarm_cb(struct alarm *alarm, + ktime_t now) +{ + struct smb_charger *chg = container_of(alarm, struct smb_charger, + dcin_aicl_alarm); + + smblib_dbg(chg, PR_WLS, "rerunning DCIN AICL\n"); + + pm_stay_awake(chg->dev); + schedule_work(&chg->dcin_aicl_work); + + return ALARMTIMER_NORESTART; +} + +static void dcin_icl_decrement(struct smb_charger *chg) +{ + int rc, icl; + ktime_t now = ktime_get(); + + rc = smblib_get_charge_param(chg, &chg->param.dc_icl, &icl); + if (rc < 0) { + smblib_err(chg, "reading DCIN ICL failed: %d\n", rc); + return; + } + + if (icl == DCIN_ICL_MIN_UA) { + /* Cannot possibly decrease ICL any further - do nothing */ + smblib_dbg(chg, PR_WLS, "hit min ICL: stop\n"); + return; + } + + /* Reduce ICL by 100 mA if 3 UVs happen in a row */ + if (ktime_us_delta(now, chg->dcin_uv_last_time) > (200 * 1000)) { + chg->dcin_uv_count = 0; + } else if (chg->dcin_uv_count >= 3) { + icl -= DCIN_ICL_STEP_UA; + + smblib_dbg(chg, PR_WLS, "icl: %d mA\n", (icl / 1000)); + rc = smblib_set_charge_param(chg, &chg->param.dc_icl, icl); + if (rc < 0) { + smblib_err(chg, "setting DCIN ICL failed: %d\n", rc); + return; + } + + chg->dcin_uv_count = 0; + } + + chg->dcin_uv_last_time = now; +} + +irqreturn_t dcin_uv_irq_handler(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + + mutex_lock(&chg->dcin_aicl_lock); + + chg->dcin_uv_count++; + smblib_dbg(chg, (PR_WLS | PR_INTERRUPT), "DCIN UV count: %d\n", + chg->dcin_uv_count); + dcin_icl_decrement(chg); + + mutex_unlock(&chg->dcin_aicl_lock); + + return IRQ_HANDLED; +} + +irqreturn_t dc_plugin_irq_handler(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + union power_supply_propval pval; + int input_present; + bool dcin_present, vbus_present; + int rc, wireless_vout = 0, wls_set = 0; + int sec_charger; + + rc = smblib_get_prop_vph_voltage_now(chg, &pval); + if (rc < 0) + return IRQ_HANDLED; + + /* 2*VPH, with a granularity of 100mV */ + wireless_vout = ((pval.intval * 2) / 100000) * 100000; + + rc = smblib_is_input_present(chg, &input_present); + if (rc < 0) + return IRQ_HANDLED; + + dcin_present = input_present & INPUT_PRESENT_DC; + vbus_present = input_present & INPUT_PRESENT_USB; + + if (!chg->cp_ilim_votable) + chg->cp_ilim_votable = find_votable("CP_ILIM"); + + if (dcin_present && !vbus_present) { + cancel_work_sync(&chg->dcin_aicl_work); + + /* Reset DCIN ICL to 100 mA */ + mutex_lock(&chg->dcin_aicl_lock); + rc = smblib_set_charge_param(chg, &chg->param.dc_icl, + DCIN_ICL_MIN_UA); + mutex_unlock(&chg->dcin_aicl_lock); + if (rc < 0) + return IRQ_HANDLED; + + smblib_dbg(chg, (PR_WLS | PR_INTERRUPT), "reset: icl: 100 mA\n"); + + /* + * Remove USB's CP ILIM vote - inapplicable for wireless + * parallel charging. + */ + if (chg->cp_ilim_votable) + vote(chg->cp_ilim_votable, ICL_CHANGE_VOTER, false, 0); + + if (chg->sec_cp_present) { + /* + * If CP output topology is VBATT, limit main charger's + * FCC share and let the CPs handle the rest. + */ + if (is_cp_topo_vbatt(chg)) + vote(chg->fcc_main_votable, + WLS_PL_CHARGING_VOTER, true, 800000); + + rc = smblib_get_prop_batt_status(chg, &pval); + if (rc < 0) + smblib_err(chg, "Couldn't read batt status rc=%d\n", + rc); + + wls_set = (pval.intval == POWER_SUPPLY_STATUS_FULL) ? + MICRO_5V : wireless_vout; + + pval.intval = wls_set; + rc = smblib_set_prop_voltage_wls_output(chg, &pval); + if (rc < 0) + dev_err(chg->dev, "Couldn't set dc voltage to 2*vph rc=%d\n", + rc); + + rc = smblib_select_sec_charger(chg, + POWER_SUPPLY_CHARGER_SEC_CP, + POWER_SUPPLY_CP_WIRELESS, false); + if (rc < 0) + dev_err(chg->dev, "Couldn't enable secondary chargers rc=%d\n", + rc); + } else { + /* + * If no secondary charger is present, commence + * wireless charging at 5 V by default. + */ + pval.intval = 5000000; + rc = smblib_set_prop_voltage_wls_output(chg, &pval); + if (rc < 0) + dev_err(chg->dev, "Couldn't set dc voltage to 5 V rc=%d\n", + rc); + } + + schedule_work(&chg->dcin_aicl_work); + } else { + if (chg->cp_reason == POWER_SUPPLY_CP_WIRELESS) { + sec_charger = chg->sec_pl_present ? + POWER_SUPPLY_CHARGER_SEC_PL : + POWER_SUPPLY_CHARGER_SEC_NONE; + rc = smblib_select_sec_charger(chg, sec_charger, + POWER_SUPPLY_CP_NONE, false); + if (rc < 0) + dev_err(chg->dev, "Couldn't disable secondary charger rc=%d\n", + rc); + } + + vote(chg->dc_suspend_votable, CHG_TERMINATION_VOTER, false, 0); + vote(chg->fcc_main_votable, WLS_PL_CHARGING_VOTER, false, 0); + + chg->last_wls_vout = 0; + chg->dcin_aicl_done = false; + chg->dcin_icl_user_set = false; + } + + /* + * Vote for 1500mA FCC upon WLS detach and remove vote upon attach if + * FCC stepper is enabled. + */ + if (chg->fcc_stepper_enable && !vbus_present) + vote(chg->fcc_votable, FCC_STEPPER_VOTER, !dcin_present, + dcin_present ? 0 : 1500000); + + if (chg->dc_psy) + power_supply_changed(chg->dc_psy); + + smblib_dbg(chg, (PR_WLS | PR_INTERRUPT), "dcin_present= %d, usbin_present= %d, cp_reason = %d\n", + dcin_present, vbus_present, chg->cp_reason); + + return IRQ_HANDLED; +} + +irqreturn_t high_duty_cycle_irq_handler(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + + chg->is_hdc = true; + /* + * Disable usb IRQs after the flag set and re-enable IRQs after + * the flag cleared in the delayed work queue, to avoid any IRQ + * storming during the delays + */ + vote(chg->hdc_irq_disable_votable, HDC_IRQ_VOTER, true, 0); + + schedule_delayed_work(&chg->clear_hdc_work, msecs_to_jiffies(60)); + + return IRQ_HANDLED; +} + +static void smblib_bb_removal_work(struct work_struct *work) +{ + struct smb_charger *chg = container_of(work, struct smb_charger, + bb_removal_work.work); + + vote(chg->usb_icl_votable, BOOST_BACK_VOTER, false, 0); + vote(chg->awake_votable, BOOST_BACK_VOTER, false, 0); +} + +#define BOOST_BACK_UNVOTE_DELAY_MS 750 +#define BOOST_BACK_STORM_COUNT 3 +#define WEAK_CHG_STORM_COUNT 8 +irqreturn_t switcher_power_ok_irq_handler(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + struct storm_watch *wdata = &irq_data->storm_data; + int rc, usb_icl; + u8 stat; + + if (!(chg->wa_flags & BOOST_BACK_WA)) + return IRQ_HANDLED; + + rc = smblib_read(chg, POWER_PATH_STATUS_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read POWER_PATH_STATUS rc=%d\n", rc); + return IRQ_HANDLED; + } + + /* skip suspending input if its already suspended by some other voter */ + usb_icl = get_effective_result(chg->usb_icl_votable); + if ((stat & USE_USBIN_BIT) && usb_icl >= 0 && usb_icl <= USBIN_25MA) + return IRQ_HANDLED; + + if (stat & USE_DCIN_BIT) + return IRQ_HANDLED; + + if (is_storming(&irq_data->storm_data)) { + /* This could be a weak charger reduce ICL */ + if (!is_client_vote_enabled(chg->usb_icl_votable, + WEAK_CHARGER_VOTER)) { + smblib_err(chg, + "Weak charger detected: voting %dmA ICL\n", + chg->weak_chg_icl_ua / 1000); + vote(chg->usb_icl_votable, WEAK_CHARGER_VOTER, + true, chg->weak_chg_icl_ua); + /* + * reset storm data and set the storm threshold + * to 3 for reverse boost detection. + */ + update_storm_count(wdata, BOOST_BACK_STORM_COUNT); + } else { + smblib_err(chg, + "Reverse boost detected: voting 0mA to suspend input\n"); + vote(chg->usb_icl_votable, BOOST_BACK_VOTER, true, 0); + vote(chg->awake_votable, BOOST_BACK_VOTER, true, 0); + /* + * Remove the boost-back vote after a delay, to avoid + * permanently suspending the input if the boost-back + * condition is unintentionally hit. + */ + schedule_delayed_work(&chg->bb_removal_work, + msecs_to_jiffies(BOOST_BACK_UNVOTE_DELAY_MS)); + } + } + + return IRQ_HANDLED; +} + +irqreturn_t wdog_snarl_irq_handler(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + + smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name); + + if (chg->wa_flags & SW_THERM_REGULATION_WA) { + cancel_delayed_work_sync(&chg->thermal_regulation_work); + vote(chg->awake_votable, SW_THERM_REGULATION_VOTER, true, 0); + schedule_delayed_work(&chg->thermal_regulation_work, 0); + } + + power_supply_changed(chg->batt_psy); + + return IRQ_HANDLED; +} + +irqreturn_t wdog_bark_irq_handler(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + int rc; + + smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name); + + rc = smblib_write(chg, BARK_BITE_WDOG_PET_REG, BARK_BITE_WDOG_PET_BIT); + if (rc < 0) + smblib_err(chg, "Couldn't pet the dog rc=%d\n", rc); + + return IRQ_HANDLED; +} + +static void smblib_die_rst_icl_regulate(struct smb_charger *chg) +{ + int rc; + u8 temp; + + rc = smblib_read(chg, DIE_TEMP_STATUS_REG, &temp); + if (rc < 0) { + smblib_err(chg, "Couldn't read DIE_TEMP_STATUS_REG rc=%d\n", + rc); + return; + } + + /* Regulate ICL on die temp crossing DIE_RST threshold */ + vote(chg->usb_icl_votable, DIE_TEMP_VOTER, + temp & DIE_TEMP_RST_BIT, 500000); +} + +/* + * triggered when DIE or SKIN or CONNECTOR temperature across + * either of the _REG_L, _REG_H, _RST, or _SHDN thresholds + */ +irqreturn_t temp_change_irq_handler(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + + smblib_die_rst_icl_regulate(chg); + + return IRQ_HANDLED; +} + +static void smblib_usbov_dbc_work(struct work_struct *work) +{ + struct smb_charger *chg = container_of(work, struct smb_charger, + usbov_dbc_work.work); + + smblib_dbg(chg, PR_MISC, "Resetting USBOV debounce\n"); + chg->dbc_usbov = false; + vote(chg->awake_votable, USBOV_DBC_VOTER, false, 0); +} + +#define USB_OV_DBC_PERIOD_MS 1000 +irqreturn_t usbin_ov_irq_handler(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + u8 stat; + int rc; + + smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name); + + if (!(chg->wa_flags & USBIN_OV_WA)) + return IRQ_HANDLED; + + rc = smblib_read(chg, USBIN_BASE + INT_RT_STS_OFFSET, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read USB_INT_RT_STS rc=%d\n", rc); + return IRQ_HANDLED; + } + + /* + * On specific PMICs, OV IRQ triggers for very small duration in + * interim periods affecting charging status reflection. In order to + * differentiate between OV IRQ glitch and real OV_IRQ, add a debounce + * period for evaluation. + */ + if (stat & USBIN_OV_RT_STS_BIT) { + chg->dbc_usbov = true; + vote(chg->awake_votable, USBOV_DBC_VOTER, true, 0); + schedule_delayed_work(&chg->usbov_dbc_work, + msecs_to_jiffies(USB_OV_DBC_PERIOD_MS)); + } else { + cancel_delayed_work_sync(&chg->usbov_dbc_work); + chg->dbc_usbov = false; + vote(chg->awake_votable, USBOV_DBC_VOTER, false, 0); + } + + smblib_dbg(chg, PR_MISC, "USBOV debounce status %d\n", + chg->dbc_usbov); + return IRQ_HANDLED; +} + +/************** + * Additional USB PSY getters/setters + * that call interrupt functions + ***************/ + +int smblib_get_prop_pr_swap_in_progress(struct smb_charger *chg, + union power_supply_propval *val) +{ + val->intval = chg->pr_swap_in_progress; + return 0; +} + +#define DETACH_DETECT_DELAY_MS 20 +int smblib_set_prop_pr_swap_in_progress(struct smb_charger *chg, + const union power_supply_propval *val) +{ + int rc; + u8 stat = 0, orientation; + + smblib_dbg(chg, PR_MISC, "Requested PR_SWAP %d\n", val->intval); + chg->pr_swap_in_progress = val->intval; + + /* check for cable removal during pr_swap */ + if (!chg->pr_swap_in_progress) { + cancel_delayed_work_sync(&chg->pr_swap_detach_work); + vote(chg->awake_votable, DETACH_DETECT_VOTER, true, 0); + schedule_delayed_work(&chg->pr_swap_detach_work, + msecs_to_jiffies(DETACH_DETECT_DELAY_MS)); + } + + rc = smblib_masked_write(chg, TYPE_C_DEBOUNCE_OPTION_REG, + REDUCE_TCCDEBOUNCE_TO_2MS_BIT, + val->intval ? REDUCE_TCCDEBOUNCE_TO_2MS_BIT : 0); + if (rc < 0) + smblib_err(chg, "Couldn't set tCC debounce rc=%d\n", rc); + + rc = smblib_masked_write(chg, TYPE_C_EXIT_STATE_CFG_REG, + BYPASS_VSAFE0V_DURING_ROLE_SWAP_BIT, + val->intval ? BYPASS_VSAFE0V_DURING_ROLE_SWAP_BIT : 0); + if (rc < 0) + smblib_err(chg, "Couldn't set exit state cfg rc=%d\n", rc); + + if (chg->pr_swap_in_progress) { + rc = smblib_read(chg, TYPE_C_MISC_STATUS_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read TYPE_C_STATUS_4 rc=%d\n", + rc); + } + + orientation = + stat & CC_ORIENTATION_BIT ? TYPEC_CCOUT_VALUE_BIT : 0; + rc = smblib_masked_write(chg, TYPE_C_CCOUT_CONTROL_REG, + TYPEC_CCOUT_SRC_BIT | TYPEC_CCOUT_BUFFER_EN_BIT + | TYPEC_CCOUT_VALUE_BIT, + TYPEC_CCOUT_SRC_BIT | TYPEC_CCOUT_BUFFER_EN_BIT + | orientation); + if (rc < 0) { + smblib_err(chg, "Couldn't read TYPE_C_CCOUT_CONTROL_REG rc=%d\n", + rc); + } + } else { + rc = smblib_masked_write(chg, TYPE_C_CCOUT_CONTROL_REG, + TYPEC_CCOUT_SRC_BIT, 0); + if (rc < 0) { + smblib_err(chg, "Couldn't read TYPE_C_CCOUT_CONTROL_REG rc=%d\n", + rc); + return rc; + } + + /* enable DRP */ + rc = smblib_masked_write(chg, TYPE_C_MODE_CFG_REG, + TYPEC_POWER_ROLE_CMD_MASK, 0); + if (rc < 0) { + smblib_err(chg, "Couldn't enable DRP rc=%d\n", rc); + return rc; + } + chg->power_role = POWER_SUPPLY_TYPEC_PR_DUAL; + smblib_dbg(chg, PR_MISC, "restore power role: %d\n", + chg->power_role); + } + + return 0; +} + +/*************** + * Work Queues * + ***************/ +static void smblib_pr_lock_clear_work(struct work_struct *work) +{ + struct smb_charger *chg = container_of(work, struct smb_charger, + pr_lock_clear_work.work); + + spin_lock(&chg->typec_pr_lock); + if (chg->pr_lock_in_progress) { + smblib_dbg(chg, PR_MISC, "restore type-c interrupts\n"); + smblib_typec_irq_config(chg, true); + chg->pr_lock_in_progress = false; + } + spin_unlock(&chg->typec_pr_lock); +} + +static void smblib_pr_swap_detach_work(struct work_struct *work) +{ + struct smb_charger *chg = container_of(work, struct smb_charger, + pr_swap_detach_work.work); + int rc; + u8 stat; + + rc = smblib_read(chg, TYPE_C_STATE_MACHINE_STATUS_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read STATE_MACHINE_STS rc=%d\n", rc); + goto out; + } + smblib_dbg(chg, PR_REGISTER, "STATE_MACHINE_STS %x\n", stat); + if (!(stat & TYPEC_ATTACH_DETACH_STATE_BIT)) { + rc = smblib_request_dpdm(chg, false); + if (rc < 0) + smblib_err(chg, "Couldn't disable DPDM rc=%d\n", rc); + + if (chg->typec_port) + typec_partner_unregister(chg); + } +out: + vote(chg->awake_votable, DETACH_DETECT_VOTER, false, 0); +} + +static void smblib_uusb_otg_work(struct work_struct *work) +{ + struct smb_charger *chg = container_of(work, struct smb_charger, + uusb_otg_work.work); + int rc; + u8 stat; + bool otg; + + rc = smblib_read(chg, TYPEC_U_USB_STATUS_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read TYPE_C_STATUS_3 rc=%d\n", rc); + goto out; + } + otg = !!(stat & U_USB_GROUND_NOVBUS_BIT); + if (chg->otg_present != otg) + smblib_notify_usb_host(chg, otg); + else + goto out; + + chg->otg_present = otg; + if (!otg) + chg->boost_current_ua = 0; + + rc = smblib_set_charge_param(chg, &chg->param.freq_switcher, + otg ? chg->chg_freq.freq_below_otg_threshold + : chg->chg_freq.freq_removal); + if (rc < 0) + dev_err(chg->dev, "Error in setting freq_boost rc=%d\n", rc); + + smblib_dbg(chg, PR_REGISTER, "TYPE_C_U_USB_STATUS = 0x%02x OTG=%d\n", + stat, otg); + power_supply_changed(chg->usb_psy); + +out: + vote(chg->awake_votable, OTG_DELAY_VOTER, false, 0); +} + +static void bms_update_work(struct work_struct *work) +{ + struct smb_charger *chg = container_of(work, struct smb_charger, + bms_update_work); + + smblib_suspend_on_debug_battery(chg); + + if (chg->batt_psy) + power_supply_changed(chg->batt_psy); +} + +static void pl_update_work(struct work_struct *work) +{ + union power_supply_propval prop_val; + struct smb_charger *chg = container_of(work, struct smb_charger, + pl_update_work); + int rc; + + if (chg->smb_temp_max == -EINVAL) { + rc = smblib_get_thermal_threshold(chg, + SMB_REG_H_THRESHOLD_MSB_REG, + &chg->smb_temp_max); + if (rc < 0) { + dev_err(chg->dev, "Couldn't get charger_temp_max rc=%d\n", + rc); + return; + } + } + + prop_val.intval = chg->smb_temp_max; + rc = power_supply_set_property(chg->pl.psy, + POWER_SUPPLY_PROP_CHARGER_TEMP_MAX, + &prop_val); + if (rc < 0) { + dev_err(chg->dev, "Couldn't set POWER_SUPPLY_PROP_CHARGER_TEMP_MAX rc=%d\n", + rc); + return; + } + + if (chg->sec_chg_selected == POWER_SUPPLY_CHARGER_SEC_CP) + return; + + smblib_select_sec_charger(chg, POWER_SUPPLY_CHARGER_SEC_PL, + POWER_SUPPLY_CP_NONE, false); +} + +static void clear_hdc_work(struct work_struct *work) +{ + struct smb_charger *chg = container_of(work, struct smb_charger, + clear_hdc_work.work); + + chg->is_hdc = false; + vote(chg->hdc_irq_disable_votable, HDC_IRQ_VOTER, false, 0); +} + +static void smblib_icl_change_work(struct work_struct *work) +{ + struct smb_charger *chg = container_of(work, struct smb_charger, + icl_change_work.work); + int rc, settled_ua; + + rc = smblib_get_charge_param(chg, &chg->param.icl_stat, &settled_ua); + if (rc < 0) { + smblib_err(chg, "Couldn't get ICL status rc=%d\n", rc); + return; + } + + power_supply_changed(chg->usb_main_psy); + + smblib_dbg(chg, PR_INTERRUPT, "icl_settled=%d\n", settled_ua); +} + +static void smblib_pl_enable_work(struct work_struct *work) +{ + struct smb_charger *chg = container_of(work, struct smb_charger, + pl_enable_work.work); + + smblib_dbg(chg, PR_PARALLEL, "timer expired, enabling parallel\n"); + vote(chg->pl_disable_votable, PL_DELAY_VOTER, false, 0); + vote(chg->awake_votable, PL_DELAY_VOTER, false, 0); +} + +static void smblib_thermal_regulation_work(struct work_struct *work) +{ + struct smb_charger *chg = container_of(work, struct smb_charger, + thermal_regulation_work.work); + int rc; + + rc = smblib_update_thermal_readings(chg); + if (rc < 0) + smblib_err(chg, "Couldn't read current thermal values %d\n", + rc); + + rc = smblib_process_thermal_readings(chg); + if (rc < 0) + smblib_err(chg, "Couldn't run sw thermal regulation %d\n", + rc); +} + +#define MOISTURE_PROTECTION_CHECK_DELAY_MS 300000 /* 5 mins */ +static void smblib_moisture_protection_work(struct work_struct *work) +{ + struct smb_charger *chg = container_of(work, struct smb_charger, + moisture_protection_work); + int rc; + bool usb_plugged_in; + u8 stat; + + /* + * Hold awake votable to prevent pm_relax being called prior to + * completion of this work. + */ + vote(chg->awake_votable, MOISTURE_VOTER, true, 0); + + /* + * Disable 1% duty cycle on CC_ID pin and enable uUSB factory mode + * detection to track any change on RID, as interrupts are disable. + */ + rc = smblib_write(chg, ((chg->chg_param.smb_version == PMI632_SUBTYPE) ? + PMI632_TYPEC_U_USB_WATER_PROTECTION_CFG_REG : + TYPEC_U_USB_WATER_PROTECTION_CFG_REG), 0); + if (rc < 0) { + smblib_err(chg, "Couldn't disable periodic monitoring of CC_ID rc=%d\n", + rc); + goto out; + } + + rc = smblib_masked_write(chg, TYPEC_U_USB_CFG_REG, + EN_MICRO_USB_FACTORY_MODE_BIT, + EN_MICRO_USB_FACTORY_MODE_BIT); + if (rc < 0) { + smblib_err(chg, "Couldn't enable uUSB factory mode detection rc=%d\n", + rc); + goto out; + } + + /* + * Add a delay of 100ms to allow change in rid to reflect on + * status registers. + */ + msleep(100); + + rc = smblib_read(chg, USBIN_BASE + INT_RT_STS_OFFSET, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read USB_INT_RT_STS rc=%d\n", rc); + goto out; + } + usb_plugged_in = (bool)(stat & USBIN_PLUGIN_RT_STS_BIT); + + /* Check uUSB status for moisture presence */ + rc = smblib_read(chg, TYPEC_U_USB_STATUS_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read TYPE_C_U_USB_STATUS_REG rc=%d\n", + rc); + goto out; + } + + /* + * Factory mode detection happens in case of USB plugged-in by using + * a different current source of 2uA which can hamper moisture + * detection. Since factory mode is not supported in kernel, factory + * mode detection can be considered as equivalent to presence of + * moisture. + */ + if (stat == U_USB_STATUS_WATER_PRESENT || stat == U_USB_FMB1_BIT || + stat == U_USB_FMB2_BIT || (usb_plugged_in && + stat == U_USB_FLOAT1_BIT)) { + smblib_set_moisture_protection(chg, true); + alarm_start_relative(&chg->moisture_protection_alarm, + ms_to_ktime(MOISTURE_PROTECTION_CHECK_DELAY_MS)); + } else { + smblib_set_moisture_protection(chg, false); + rc = alarm_cancel(&chg->moisture_protection_alarm); + if (rc < 0) + smblib_err(chg, "Couldn't cancel moisture protection alarm\n"); + } + +out: + vote(chg->awake_votable, MOISTURE_VOTER, false, 0); +} + +static enum alarmtimer_restart moisture_protection_alarm_cb(struct alarm *alarm, + ktime_t now) +{ + struct smb_charger *chg = container_of(alarm, struct smb_charger, + moisture_protection_alarm); + + smblib_dbg(chg, PR_MISC, "moisture Protection Alarm Triggered %lld\n", + ktime_to_ms(now)); + + /* Atomic context, cannot use voter */ + pm_stay_awake(chg->dev); + schedule_work(&chg->moisture_protection_work); + + return ALARMTIMER_NORESTART; +} + +static void smblib_chg_termination_work(struct work_struct *work) +{ + union power_supply_propval pval; + struct smb_charger *chg = container_of(work, struct smb_charger, + chg_termination_work); + int rc, input_present, delay = CHG_TERM_WA_ENTRY_DELAY_MS; + int vbat_now_uv, max_fv_uv; + + /* + * Hold awake votable to prevent pm_relax being called prior to + * completion of this work. + */ + vote(chg->awake_votable, CHG_TERMINATION_VOTER, true, 0); + + rc = smblib_is_input_present(chg, &input_present); + if ((rc < 0) || !input_present) + goto out; + + rc = smblib_get_prop_from_bms(chg, + POWER_SUPPLY_PROP_REAL_CAPACITY, &pval); + if ((rc < 0) || (pval.intval < 100)) { + vote(chg->usb_icl_votable, CHG_TERMINATION_VOTER, false, 0); + vote(chg->dc_suspend_votable, CHG_TERMINATION_VOTER, false, 0); + goto out; + } + + /* Get the battery float voltage */ + rc = smblib_get_prop_from_bms(chg, POWER_SUPPLY_PROP_VOLTAGE_MAX, + &pval); + if (rc < 0) + goto out; + + max_fv_uv = pval.intval; + + rc = smblib_get_prop_from_bms(chg, POWER_SUPPLY_PROP_CHARGE_FULL, + &pval); + if (rc < 0) + goto out; + + /* + * On change in the value of learned capacity, re-initialize the + * reference cc_soc value due to change in cc_soc characteristic value + * at full capacity. Also, in case cc_soc_ref value is reset, + * re-initialize it. + */ + if (pval.intval != chg->charge_full_cc || !chg->cc_soc_ref) { + chg->charge_full_cc = pval.intval; + + rc = smblib_get_prop_from_bms(chg, + POWER_SUPPLY_PROP_VOLTAGE_NOW, &pval); + if (rc < 0) + goto out; + + /* + * Store the Vbat at the charge termination to compare with + * the current voltage to see if the Vbat is increasing after + * charge termination in BSM. + */ + chg->term_vbat_uv = pval.intval; + vbat_now_uv = pval.intval; + + rc = smblib_get_prop_from_bms(chg, POWER_SUPPLY_PROP_CC_SOC, + &pval); + if (rc < 0) + goto out; + + chg->cc_soc_ref = pval.intval; + } else { + rc = smblib_get_prop_from_bms(chg, + POWER_SUPPLY_PROP_VOLTAGE_NOW, &pval); + if (rc < 0) + goto out; + + vbat_now_uv = pval.intval; + + rc = smblib_get_prop_from_bms(chg, POWER_SUPPLY_PROP_CC_SOC, + &pval); + if (rc < 0) + goto out; + } + + /* + * In BSM a sudden jump in CC_SOC is not expected. If seen, its a + * good_ocv or updated capacity, reject it. + */ + if (chg->last_cc_soc && pval.intval > (chg->last_cc_soc + 100)) { + /* CC_SOC has increased by 1% from last time */ + chg->cc_soc_ref = pval.intval; + smblib_dbg(chg, PR_MISC, "cc_soc jumped(%d->%d), reset cc_soc_ref\n", + chg->last_cc_soc, pval.intval); + } + chg->last_cc_soc = pval.intval; + + /* + * Suspend/Unsuspend USB input to keep cc_soc within the 0.5% to 0.75% + * overshoot range of the cc_soc value at termination and make sure that + * vbat is indeed rising above vfloat. + */ + if (pval.intval < DIV_ROUND_CLOSEST(chg->cc_soc_ref * 10050, 10000)) { + vote(chg->usb_icl_votable, CHG_TERMINATION_VOTER, false, 0); + vote(chg->dc_suspend_votable, CHG_TERMINATION_VOTER, false, 0); + delay = CHG_TERM_WA_ENTRY_DELAY_MS; + } else if ((pval.intval > DIV_ROUND_CLOSEST(chg->cc_soc_ref * 10075, + 10000)) + && ((vbat_now_uv > chg->term_vbat_uv) && + (vbat_now_uv > max_fv_uv))) { + + if (input_present & INPUT_PRESENT_USB) + vote(chg->usb_icl_votable, CHG_TERMINATION_VOTER, + true, 0); + if (input_present & INPUT_PRESENT_DC) + vote(chg->dc_suspend_votable, CHG_TERMINATION_VOTER, + true, 0); + delay = CHG_TERM_WA_EXIT_DELAY_MS; + } + + smblib_dbg(chg, PR_MISC, "Chg Term WA readings: cc_soc: %d, cc_soc_ref: %d, delay: %d vbat_now %d term_vbat %d\n", + pval.intval, chg->cc_soc_ref, delay, vbat_now_uv, + chg->term_vbat_uv); + alarm_start_relative(&chg->chg_termination_alarm, ms_to_ktime(delay)); +out: + vote(chg->awake_votable, CHG_TERMINATION_VOTER, false, 0); +} + +static enum alarmtimer_restart chg_termination_alarm_cb(struct alarm *alarm, + ktime_t now) +{ + struct smb_charger *chg = container_of(alarm, struct smb_charger, + chg_termination_alarm); + + smblib_dbg(chg, PR_MISC, "Charge termination WA alarm triggered %lld\n", + ktime_to_ms(now)); + + /* Atomic context, cannot use voter */ + pm_stay_awake(chg->dev); + schedule_work(&chg->chg_termination_work); + + return ALARMTIMER_NORESTART; +} + +static void apsd_timer_cb(struct timer_list *tm) +{ + struct smb_charger *chg = container_of(tm, struct smb_charger, + apsd_timer); + + smblib_dbg(chg, PR_MISC, "APSD Extented timer timeout at %lld\n", + jiffies_to_msecs(jiffies)); + + chg->apsd_ext_timeout = true; +} + +#define SOFT_JEITA_HYSTERESIS_OFFSET 0x200 +static void jeita_update_work(struct work_struct *work) +{ + struct smb_charger *chg = container_of(work, struct smb_charger, + jeita_update_work); + struct device_node *node = chg->dev->of_node; + struct device_node *batt_node, *pnode; + union power_supply_propval val; + int rc, tmp[2], max_fcc_ma, max_fv_uv; + u32 jeita_hard_thresholds[2]; + u16 addr; + u8 buff[2]; + + batt_node = of_find_node_by_name(node, "qcom,battery-data"); + if (!batt_node) { + smblib_err(chg, "Batterydata not available\n"); + goto out; + } + + /* if BMS is not ready, defer the work */ + if (!chg->bms_psy) + return; + + rc = smblib_get_prop_from_bms(chg, + POWER_SUPPLY_PROP_RESISTANCE_ID, &val); + if (rc < 0) { + smblib_err(chg, "Failed to get batt-id rc=%d\n", rc); + goto out; + } + + /* if BMS hasn't read out the batt_id yet, defer the work */ + if (val.intval <= 0) + return; + + pnode = of_batterydata_get_best_profile(batt_node, + val.intval / 1000, NULL); + if (IS_ERR(pnode)) { + rc = PTR_ERR(pnode); + smblib_err(chg, "Failed to detect valid battery profile %d\n", + rc); + goto out; + } + + rc = of_property_read_u32_array(pnode, "qcom,jeita-hard-thresholds", + jeita_hard_thresholds, 2); + if (!rc) { + rc = smblib_update_jeita(chg, jeita_hard_thresholds, + JEITA_HARD); + if (rc < 0) { + smblib_err(chg, "Couldn't configure Hard Jeita rc=%d\n", + rc); + goto out; + } + } + + rc = of_property_read_u32_array(pnode, "qcom,jeita-soft-thresholds", + chg->jeita_soft_thlds, 2); + if (!rc) { + rc = smblib_update_jeita(chg, chg->jeita_soft_thlds, + JEITA_SOFT); + if (rc < 0) { + smblib_err(chg, "Couldn't configure Soft Jeita rc=%d\n", + rc); + goto out; + } + + rc = of_property_read_u32_array(pnode, + "qcom,jeita-soft-hys-thresholds", + chg->jeita_soft_hys_thlds, 2); + if (rc < 0) { + smblib_err(chg, "Couldn't get Soft Jeita hysteresis thresholds rc=%d\n", + rc); + goto out; + } + } else { + /* Populate the jeita-soft-thresholds */ + addr = CHGR_JEITA_THRESHOLD_BASE_REG(JEITA_SOFT); + rc = smblib_batch_read(chg, addr, buff, 2); + if (rc < 0) { + pr_err("failed to read 0x%4X, rc=%d\n", addr, rc); + goto out; + } + + chg->jeita_soft_thlds[1] = buff[1] | buff[0] << 8; + + rc = smblib_batch_read(chg, addr + 2, buff, 2); + if (rc < 0) { + pr_err("failed to read 0x%4X, rc=%d\n", addr + 2, rc); + goto out; + } + + chg->jeita_soft_thlds[0] = buff[1] | buff[0] << 8; + + /* + * Update the soft jeita hysteresis 2 DegC less for warm and + * 2 DegC more for cool than the soft jeita thresholds to avoid + * overwriting the registers with invalid values. + */ + chg->jeita_soft_hys_thlds[0] = + chg->jeita_soft_thlds[0] - SOFT_JEITA_HYSTERESIS_OFFSET; + chg->jeita_soft_hys_thlds[1] = + chg->jeita_soft_thlds[1] + SOFT_JEITA_HYSTERESIS_OFFSET; + } + + chg->jeita_soft_fcc[0] = chg->jeita_soft_fcc[1] = -EINVAL; + chg->jeita_soft_fv[0] = chg->jeita_soft_fv[1] = -EINVAL; + max_fcc_ma = max_fv_uv = -EINVAL; + + of_property_read_u32(pnode, "qcom,fastchg-current-ma", &max_fcc_ma); + of_property_read_u32(pnode, "qcom,max-voltage-uv", &max_fv_uv); + + if (max_fcc_ma <= 0 || max_fv_uv <= 0) { + smblib_err(chg, "Incorrect fastchg-current-ma or max-voltage-uv\n"); + goto out; + } + + rc = of_property_read_u32_array(pnode, "qcom,jeita-soft-fcc-ua", + tmp, 2); + if (rc < 0) { + smblib_err(chg, "Couldn't get fcc values for soft JEITA rc=%d\n", + rc); + goto out; + } + + max_fcc_ma *= 1000; + if (tmp[0] > max_fcc_ma || tmp[1] > max_fcc_ma) { + smblib_err(chg, "Incorrect FCC value [%d %d] max: %d\n", tmp[0], + tmp[1], max_fcc_ma); + goto out; + } + chg->jeita_soft_fcc[0] = tmp[0]; + chg->jeita_soft_fcc[1] = tmp[1]; + + rc = of_property_read_u32_array(pnode, "qcom,jeita-soft-fv-uv", tmp, + 2); + if (rc < 0) { + smblib_err(chg, "Couldn't get fv values for soft JEITA rc=%d\n", + rc); + goto out; + } + + if (tmp[0] > max_fv_uv || tmp[1] > max_fv_uv) { + smblib_err(chg, "Incorrect FV value [%d %d] max: %d\n", tmp[0], + tmp[1], max_fv_uv); + goto out; + } + chg->jeita_soft_fv[0] = tmp[0]; + chg->jeita_soft_fv[1] = tmp[1]; + + rc = smblib_soft_jeita_arb_wa(chg); + if (rc < 0) { + smblib_err(chg, "Couldn't fix soft jeita arb rc=%d\n", + rc); + goto out; + } + + chg->jeita_configured = JEITA_CFG_COMPLETE; + return; + +out: + chg->jeita_configured = JEITA_CFG_FAILURE; +} + +static void smblib_lpd_ra_open_work(struct work_struct *work) +{ + struct smb_charger *chg = container_of(work, struct smb_charger, + lpd_ra_open_work.work); + union power_supply_propval pval; + u8 stat; + int rc; + + if (chg->pr_swap_in_progress || chg->pd_hard_reset) { + chg->lpd_stage = LPD_STAGE_NONE; + goto out; + } + + if (chg->lpd_stage != LPD_STAGE_FLOAT) + goto out; + + rc = smblib_read(chg, TYPE_C_MISC_STATUS_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read TYPE_C_MISC_STATUS_REG rc=%d\n", + rc); + goto out; + } + + /* quit if moisture status is gone or in attached state */ + if (!(stat & TYPEC_WATER_DETECTION_STATUS_BIT) + || (stat & TYPEC_TCCDEBOUNCE_DONE_STATUS_BIT)) { + chg->lpd_stage = LPD_STAGE_NONE; + goto out; + } + + chg->lpd_stage = LPD_STAGE_COMMIT; + + /* Enable source only mode */ + pval.intval = POWER_SUPPLY_TYPEC_PR_SOURCE; + rc = smblib_set_prop_typec_power_role(chg, &pval); + if (rc < 0) { + smblib_err(chg, "Couldn't set typec source only mode rc=%d\n", + rc); + goto out; + } + + /* Wait 1.5ms to get SBUx ready */ + usleep_range(1500, 1510); + + if (smblib_rsbux_low(chg, RSBU_K_300K_UV)) { + /* Moisture detected, enable sink only mode */ + pval.intval = POWER_SUPPLY_TYPEC_PR_SINK; + rc = smblib_set_prop_typec_power_role(chg, &pval); + if (rc < 0) { + smblib_err(chg, "Couldn't set typec sink only rc=%d\n", + rc); + goto out; + } + + chg->lpd_reason = LPD_MOISTURE_DETECTED; + chg->moisture_present = true; + vote(chg->usb_icl_votable, LPD_VOTER, true, 0); + + } else { + /* Floating cable, disable water detection irq temporarily */ + rc = smblib_masked_write(chg, TYPE_C_INTERRUPT_EN_CFG_2_REG, + TYPEC_WATER_DETECTION_INT_EN_BIT, 0); + if (rc < 0) { + smblib_err(chg, "Couldn't set TYPE_C_INTERRUPT_EN_CFG_2_REG rc=%d\n", + rc); + goto out; + } + + /* restore DRP mode */ + pval.intval = POWER_SUPPLY_TYPEC_PR_DUAL; + rc = smblib_set_prop_typec_power_role(chg, &pval); + if (rc < 0) { + smblib_err(chg, "Couldn't write 0x%02x to TYPE_C_INTRPT_ENB_SOFTWARE_CTRL rc=%d\n", + pval.intval, rc); + goto out; + } + + chg->lpd_reason = LPD_FLOATING_CABLE; + } + + /* recheck in 60 seconds */ + alarm_start_relative(&chg->lpd_recheck_timer, ms_to_ktime(60000)); +out: + vote(chg->awake_votable, LPD_VOTER, false, 0); +} + +static void smblib_lpd_detach_work(struct work_struct *work) +{ + struct smb_charger *chg = container_of(work, struct smb_charger, + lpd_detach_work.work); + + if (chg->lpd_stage == LPD_STAGE_FLOAT_CANCEL) + chg->lpd_stage = LPD_STAGE_NONE; +} + +static void smblib_cp_status_change_work(struct work_struct *work) +{ + int rc; + union power_supply_propval pval; + struct smb_charger *chg = container_of(work, struct smb_charger, + cp_status_change_work); + + if (!chg->cp_psy) + chg->cp_psy = power_supply_get_by_name("charge_pump_master"); + + if (!chg->cp_psy) + goto relax; + + if (chg->cp_topo == -EINVAL) { + rc = power_supply_get_property(chg->cp_psy, + POWER_SUPPLY_PROP_PARALLEL_OUTPUT_MODE, &pval); + if (rc < 0) { + smblib_err(chg, "Couldn't read cp topo rc=%d\n", rc); + goto relax; + } + + chg->cp_topo = pval.intval; + + if (chg->cp_topo == POWER_SUPPLY_PL_OUTPUT_VBAT && + chg->cp_reason == POWER_SUPPLY_CP_WIRELESS) + vote(chg->fcc_main_votable, WLS_PL_CHARGING_VOTER, true, + 800000); + } +relax: + pm_relax(chg->dev); +} + +static int smblib_create_votables(struct smb_charger *chg) +{ + int rc = 0; + + chg->fcc_votable = find_votable("FCC"); + if (chg->fcc_votable == NULL) { + rc = -EINVAL; + smblib_err(chg, "Couldn't find FCC votable rc=%d\n", rc); + return rc; + } + + chg->fcc_main_votable = find_votable("FCC_MAIN"); + if (chg->fcc_main_votable == NULL) { + rc = -EINVAL; + smblib_err(chg, "Couldn't find FCC Main votable rc=%d\n", rc); + return rc; + } + + chg->fv_votable = find_votable("FV"); + if (chg->fv_votable == NULL) { + rc = -EINVAL; + smblib_err(chg, "Couldn't find FV votable rc=%d\n", rc); + return rc; + } + + chg->usb_icl_votable = find_votable("USB_ICL"); + if (chg->usb_icl_votable == NULL) { + rc = -EINVAL; + smblib_err(chg, "Couldn't find USB_ICL votable rc=%d\n", rc); + return rc; + } + + chg->pl_disable_votable = find_votable("PL_DISABLE"); + if (chg->pl_disable_votable == NULL) { + rc = -EINVAL; + smblib_err(chg, "Couldn't find votable PL_DISABLE rc=%d\n", rc); + return rc; + } + + chg->pl_enable_votable_indirect = find_votable("PL_ENABLE_INDIRECT"); + if (chg->pl_enable_votable_indirect == NULL) { + rc = -EINVAL; + smblib_err(chg, + "Couldn't find votable PL_ENABLE_INDIRECT rc=%d\n", + rc); + return rc; + } + + vote(chg->pl_disable_votable, PL_DELAY_VOTER, true, 0); + + chg->smb_override_votable = create_votable("SMB_EN_OVERRIDE", + VOTE_SET_ANY, + smblib_smb_disable_override_vote_callback, chg); + if (IS_ERR(chg->smb_override_votable)) { + rc = PTR_ERR(chg->smb_override_votable); + chg->smb_override_votable = NULL; + return rc; + } + + chg->dc_suspend_votable = create_votable("DC_SUSPEND", VOTE_SET_ANY, + smblib_dc_suspend_vote_callback, + chg); + if (IS_ERR(chg->dc_suspend_votable)) { + rc = PTR_ERR(chg->dc_suspend_votable); + chg->dc_suspend_votable = NULL; + return rc; + } + + chg->awake_votable = create_votable("AWAKE", VOTE_SET_ANY, + smblib_awake_vote_callback, + chg); + if (IS_ERR(chg->awake_votable)) { + rc = PTR_ERR(chg->awake_votable); + chg->awake_votable = NULL; + return rc; + } + + chg->chg_disable_votable = create_votable("CHG_DISABLE", VOTE_SET_ANY, + smblib_chg_disable_vote_callback, + chg); + if (IS_ERR(chg->chg_disable_votable)) { + rc = PTR_ERR(chg->chg_disable_votable); + chg->chg_disable_votable = NULL; + return rc; + } + + chg->limited_irq_disable_votable = create_votable( + "USB_LIMITED_IRQ_DISABLE", + VOTE_SET_ANY, + smblib_limited_irq_disable_vote_callback, + chg); + if (IS_ERR(chg->limited_irq_disable_votable)) { + rc = PTR_ERR(chg->limited_irq_disable_votable); + chg->limited_irq_disable_votable = NULL; + return rc; + } + + chg->hdc_irq_disable_votable = create_votable("USB_HDC_IRQ_DISABLE", + VOTE_SET_ANY, + smblib_hdc_irq_disable_vote_callback, + chg); + if (IS_ERR(chg->hdc_irq_disable_votable)) { + rc = PTR_ERR(chg->hdc_irq_disable_votable); + chg->hdc_irq_disable_votable = NULL; + return rc; + } + + chg->icl_irq_disable_votable = create_votable("USB_ICL_IRQ_DISABLE", + VOTE_SET_ANY, + smblib_icl_irq_disable_vote_callback, + chg); + if (IS_ERR(chg->icl_irq_disable_votable)) { + rc = PTR_ERR(chg->icl_irq_disable_votable); + chg->icl_irq_disable_votable = NULL; + return rc; + } + + chg->temp_change_irq_disable_votable = create_votable( + "TEMP_CHANGE_IRQ_DISABLE", VOTE_SET_ANY, + smblib_temp_change_irq_disable_vote_callback, chg); + if (IS_ERR(chg->temp_change_irq_disable_votable)) { + rc = PTR_ERR(chg->temp_change_irq_disable_votable); + chg->temp_change_irq_disable_votable = NULL; + return rc; + } + + return rc; +} + +static void smblib_destroy_votables(struct smb_charger *chg) +{ + if (chg->dc_suspend_votable) + destroy_votable(chg->dc_suspend_votable); + if (chg->usb_icl_votable) + destroy_votable(chg->usb_icl_votable); + if (chg->awake_votable) + destroy_votable(chg->awake_votable); + if (chg->chg_disable_votable) + destroy_votable(chg->chg_disable_votable); +} + +static void smblib_iio_deinit(struct smb_charger *chg) +{ + if (!IS_ERR_OR_NULL(chg->iio.usbin_v_chan)) + iio_channel_release(chg->iio.usbin_v_chan); + if (!IS_ERR_OR_NULL(chg->iio.usbin_i_chan)) + iio_channel_release(chg->iio.usbin_i_chan); + if (!IS_ERR_OR_NULL(chg->iio.temp_chan)) + iio_channel_release(chg->iio.temp_chan); + if (!IS_ERR_OR_NULL(chg->iio.sbux_chan)) + iio_channel_release(chg->iio.sbux_chan); + if (!IS_ERR_OR_NULL(chg->iio.vph_v_chan)) + iio_channel_release(chg->iio.vph_v_chan); + if (!IS_ERR_OR_NULL(chg->iio.die_temp_chan)) + iio_channel_release(chg->iio.die_temp_chan); + if (!IS_ERR_OR_NULL(chg->iio.connector_temp_chan)) + iio_channel_release(chg->iio.connector_temp_chan); + if (!IS_ERR_OR_NULL(chg->iio.skin_temp_chan)) + iio_channel_release(chg->iio.skin_temp_chan); + if (!IS_ERR_OR_NULL(chg->iio.smb_temp_chan)) + iio_channel_release(chg->iio.smb_temp_chan); +} + +int smblib_init(struct smb_charger *chg) +{ + union power_supply_propval prop_val; + int rc = 0; + + mutex_init(&chg->smb_lock); + mutex_init(&chg->irq_status_lock); + mutex_init(&chg->dcin_aicl_lock); + mutex_init(&chg->dpdm_lock); + spin_lock_init(&chg->typec_pr_lock); + INIT_WORK(&chg->bms_update_work, bms_update_work); + INIT_WORK(&chg->pl_update_work, pl_update_work); + INIT_WORK(&chg->jeita_update_work, jeita_update_work); + INIT_WORK(&chg->dcin_aicl_work, dcin_aicl_work); + INIT_WORK(&chg->cp_status_change_work, smblib_cp_status_change_work); + INIT_DELAYED_WORK(&chg->clear_hdc_work, clear_hdc_work); + INIT_DELAYED_WORK(&chg->icl_change_work, smblib_icl_change_work); + INIT_DELAYED_WORK(&chg->pl_enable_work, smblib_pl_enable_work); + INIT_DELAYED_WORK(&chg->uusb_otg_work, smblib_uusb_otg_work); + INIT_DELAYED_WORK(&chg->bb_removal_work, smblib_bb_removal_work); + INIT_DELAYED_WORK(&chg->lpd_ra_open_work, smblib_lpd_ra_open_work); + INIT_DELAYED_WORK(&chg->lpd_detach_work, smblib_lpd_detach_work); + INIT_DELAYED_WORK(&chg->thermal_regulation_work, + smblib_thermal_regulation_work); + INIT_DELAYED_WORK(&chg->usbov_dbc_work, smblib_usbov_dbc_work); + INIT_DELAYED_WORK(&chg->pr_swap_detach_work, + smblib_pr_swap_detach_work); + INIT_DELAYED_WORK(&chg->pr_lock_clear_work, + smblib_pr_lock_clear_work); + timer_setup(&chg->apsd_timer, apsd_timer_cb, 0); + + INIT_DELAYED_WORK(&chg->role_reversal_check, + smblib_typec_role_check_work); + + if (chg->wa_flags & CHG_TERMINATION_WA) { + INIT_WORK(&chg->chg_termination_work, + smblib_chg_termination_work); + + if (alarmtimer_get_rtcdev()) { + alarm_init(&chg->chg_termination_alarm, ALARM_BOOTTIME, + chg_termination_alarm_cb); + } else { + smblib_err(chg, "Couldn't get rtc device\n"); + return -ENODEV; + } + } + + if (chg->uusb_moisture_protection_enabled) { + INIT_WORK(&chg->moisture_protection_work, + smblib_moisture_protection_work); + + if (alarmtimer_get_rtcdev()) { + alarm_init(&chg->moisture_protection_alarm, + ALARM_BOOTTIME, moisture_protection_alarm_cb); + } else { + smblib_err(chg, "Failed to initialize moisture protection alarm\n"); + return -ENODEV; + } + } + + if (alarmtimer_get_rtcdev()) { + alarm_init(&chg->dcin_aicl_alarm, ALARM_REALTIME, + dcin_aicl_alarm_cb); + } else { + smblib_err(chg, "Failed to initialize dcin aicl alarm\n"); + return -ENODEV; + } + + chg->fake_capacity = -EINVAL; + chg->fake_input_current_limited = -EINVAL; + chg->fake_batt_status = -EINVAL; + chg->sink_src_mode = UNATTACHED_MODE; + chg->jeita_configured = false; + chg->sec_chg_selected = POWER_SUPPLY_CHARGER_SEC_NONE; + chg->cp_reason = POWER_SUPPLY_CP_NONE; + chg->thermal_status = TEMP_BELOW_RANGE; + chg->typec_irq_en = true; + chg->cp_topo = -EINVAL; + chg->dr_mode = TYPEC_PORT_DRP; + + switch (chg->mode) { + case PARALLEL_MASTER: + rc = qcom_batt_init(&chg->chg_param); + if (rc < 0) { + smblib_err(chg, "Couldn't init qcom_batt_init rc=%d\n", + rc); + return rc; + } + + rc = qcom_step_chg_init(chg->dev, chg->step_chg_enabled, + chg->sw_jeita_enabled, false); + if (rc < 0) { + smblib_err(chg, "Couldn't init qcom_step_chg_init rc=%d\n", + rc); + return rc; + } + + rc = smblib_create_votables(chg); + if (rc < 0) { + smblib_err(chg, "Couldn't create votables rc=%d\n", + rc); + return rc; + } + + chg->bms_psy = power_supply_get_by_name("bms"); + + if (chg->sec_pl_present) { + chg->pl.psy = power_supply_get_by_name("parallel"); + if (chg->pl.psy) { + if (chg->sec_chg_selected + != POWER_SUPPLY_CHARGER_SEC_CP) { + rc = smblib_select_sec_charger(chg, + POWER_SUPPLY_CHARGER_SEC_PL, + POWER_SUPPLY_CP_NONE, false); + if (rc < 0) + smblib_err(chg, "Couldn't config pl charger rc=%d\n", + rc); + } + + if (chg->smb_temp_max == -EINVAL) { + rc = smblib_get_thermal_threshold(chg, + SMB_REG_H_THRESHOLD_MSB_REG, + &chg->smb_temp_max); + if (rc < 0) { + dev_err(chg->dev, "Couldn't get charger_temp_max rc=%d\n", + rc); + return rc; + } + } + + prop_val.intval = chg->smb_temp_max; + rc = power_supply_set_property(chg->pl.psy, + POWER_SUPPLY_PROP_CHARGER_TEMP_MAX, + &prop_val); + if (rc < 0) { + dev_err(chg->dev, "Couldn't set POWER_SUPPLY_PROP_CHARGER_TEMP_MAX rc=%d\n", + rc); + return rc; + } + } + } + + rc = smblib_register_notifier(chg); + if (rc < 0) { + smblib_err(chg, + "Couldn't register notifier rc=%d\n", rc); + return rc; + } + break; + case PARALLEL_SLAVE: + break; + default: + smblib_err(chg, "Unsupported mode %d\n", chg->mode); + return -EINVAL; + } + + return rc; +} + +int smblib_deinit(struct smb_charger *chg) +{ + switch (chg->mode) { + case PARALLEL_MASTER: + if (chg->uusb_moisture_protection_enabled) { + alarm_cancel(&chg->moisture_protection_alarm); + cancel_work_sync(&chg->moisture_protection_work); + } + if (chg->wa_flags & CHG_TERMINATION_WA) { + alarm_cancel(&chg->chg_termination_alarm); + cancel_work_sync(&chg->chg_termination_work); + } + del_timer_sync(&chg->apsd_timer); + cancel_work_sync(&chg->bms_update_work); + cancel_work_sync(&chg->jeita_update_work); + cancel_work_sync(&chg->pl_update_work); + cancel_work_sync(&chg->dcin_aicl_work); + cancel_work_sync(&chg->cp_status_change_work); + cancel_delayed_work_sync(&chg->clear_hdc_work); + cancel_delayed_work_sync(&chg->icl_change_work); + cancel_delayed_work_sync(&chg->pl_enable_work); + cancel_delayed_work_sync(&chg->uusb_otg_work); + cancel_delayed_work_sync(&chg->bb_removal_work); + cancel_delayed_work_sync(&chg->lpd_ra_open_work); + cancel_delayed_work_sync(&chg->lpd_detach_work); + cancel_delayed_work_sync(&chg->thermal_regulation_work); + cancel_delayed_work_sync(&chg->usbov_dbc_work); + cancel_delayed_work_sync(&chg->role_reversal_check); + cancel_delayed_work_sync(&chg->pr_swap_detach_work); + power_supply_unreg_notifier(&chg->nb); + smblib_destroy_votables(chg); + qcom_step_chg_deinit(); + qcom_batt_deinit(); + break; + case PARALLEL_SLAVE: + break; + default: + smblib_err(chg, "Unsupported mode %d\n", chg->mode); + return -EINVAL; + } + + smblib_iio_deinit(chg); + + return 0; +} diff --git a/drivers/power/supply/qcom/smb5-lib.h b/drivers/power/supply/qcom/smb5-lib.h new file mode 100644 index 000000000000..2136155a3b88 --- /dev/null +++ b/drivers/power/supply/qcom/smb5-lib.h @@ -0,0 +1,841 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2018-2020 The Linux Foundation. All rights reserved. + */ + +#ifndef __SMB5_CHARGER_H +#define __SMB5_CHARGER_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "storm-watch.h" +#include "battery.h" + +enum print_reason { + PR_INTERRUPT = BIT(0), + PR_REGISTER = BIT(1), + PR_MISC = BIT(2), + PR_PARALLEL = BIT(3), + PR_OTG = BIT(4), + PR_WLS = BIT(5), +}; + +#define DEFAULT_VOTER "DEFAULT_VOTER" +#define USER_VOTER "USER_VOTER" +#define PD_VOTER "PD_VOTER" +#define DCP_VOTER "DCP_VOTER" +#define QC_VOTER "QC_VOTER" +#define USB_PSY_VOTER "USB_PSY_VOTER" +#define PL_TAPER_WORK_RUNNING_VOTER "PL_TAPER_WORK_RUNNING_VOTER" +#define USBIN_V_VOTER "USBIN_V_VOTER" +#define CHG_STATE_VOTER "CHG_STATE_VOTER" +#define TAPER_END_VOTER "TAPER_END_VOTER" +#define THERMAL_DAEMON_VOTER "THERMAL_DAEMON_VOTER" +#define DIE_TEMP_VOTER "DIE_TEMP_VOTER" +#define BOOST_BACK_VOTER "BOOST_BACK_VOTER" +#define MICRO_USB_VOTER "MICRO_USB_VOTER" +#define DEBUG_BOARD_VOTER "DEBUG_BOARD_VOTER" +#define PD_SUSPEND_SUPPORTED_VOTER "PD_SUSPEND_SUPPORTED_VOTER" +#define PL_DELAY_VOTER "PL_DELAY_VOTER" +#define CTM_VOTER "CTM_VOTER" +#define SW_QC3_VOTER "SW_QC3_VOTER" +#define AICL_RERUN_VOTER "AICL_RERUN_VOTER" +#define SW_ICL_MAX_VOTER "SW_ICL_MAX_VOTER" +#define PL_QNOVO_VOTER "PL_QNOVO_VOTER" +#define QNOVO_VOTER "QNOVO_VOTER" +#define BATT_PROFILE_VOTER "BATT_PROFILE_VOTER" +#define OTG_DELAY_VOTER "OTG_DELAY_VOTER" +#define USBIN_I_VOTER "USBIN_I_VOTER" +#define WEAK_CHARGER_VOTER "WEAK_CHARGER_VOTER" +#define PL_FCC_LOW_VOTER "PL_FCC_LOW_VOTER" +#define WBC_VOTER "WBC_VOTER" +#define HW_LIMIT_VOTER "HW_LIMIT_VOTER" +#define PL_SMB_EN_VOTER "PL_SMB_EN_VOTER" +#define FORCE_RECHARGE_VOTER "FORCE_RECHARGE_VOTER" +#define LPD_VOTER "LPD_VOTER" +#define FCC_STEPPER_VOTER "FCC_STEPPER_VOTER" +#define SW_THERM_REGULATION_VOTER "SW_THERM_REGULATION_VOTER" +#define JEITA_ARB_VOTER "JEITA_ARB_VOTER" +#define MOISTURE_VOTER "MOISTURE_VOTER" +#define HVDCP2_ICL_VOTER "HVDCP2_ICL_VOTER" +#define AICL_THRESHOLD_VOTER "AICL_THRESHOLD_VOTER" +#define USBOV_DBC_VOTER "USBOV_DBC_VOTER" +#define CHG_TERMINATION_VOTER "CHG_TERMINATION_VOTER" +#define THERMAL_THROTTLE_VOTER "THERMAL_THROTTLE_VOTER" +#define VOUT_VOTER "VOUT_VOTER" +#define USB_SUSPEND_VOTER "USB_SUSPEND_VOTER" +#define CHARGER_TYPE_VOTER "CHARGER_TYPE_VOTER" +#define HDC_IRQ_VOTER "HDC_IRQ_VOTER" +#define DETACH_DETECT_VOTER "DETACH_DETECT_VOTER" +#define CC_MODE_VOTER "CC_MODE_VOTER" +#define MAIN_FCC_VOTER "MAIN_FCC_VOTER" +#define DCIN_AICL_VOTER "DCIN_AICL_VOTER" +#define WLS_PL_CHARGING_VOTER "WLS_PL_CHARGING_VOTER" +#define ICL_CHANGE_VOTER "ICL_CHANGE_VOTER" +#define OVERHEAT_LIMIT_VOTER "OVERHEAT_LIMIT_VOTER" +#define TYPEC_SWAP_VOTER "TYPEC_SWAP_VOTER" + +#define BOOST_BACK_STORM_COUNT 3 +#define WEAK_CHG_STORM_COUNT 8 + +#define VBAT_TO_VRAW_ADC(v) div_u64((u64)v * 1000000UL, 194637UL) + +#define ITERM_LIMITS_PMI632_MA 5000 +#define ITERM_LIMITS_PM8150B_MA 10000 +#define ADC_CHG_ITERM_MASK 32767 + +#define SDP_100_MA 100000 +#define SDP_CURRENT_UA 500000 +#define CDP_CURRENT_UA 1500000 +#define DCP_CURRENT_UA 1500000 +#define HVDCP_CURRENT_UA 3000000 +#define TYPEC_DEFAULT_CURRENT_UA 900000 +#define TYPEC_MEDIUM_CURRENT_UA 1500000 +#define TYPEC_HIGH_CURRENT_UA 3000000 +#define DCIN_ICL_MIN_UA 100000 +#define DCIN_ICL_MAX_UA 1500000 +#define DCIN_ICL_STEP_UA 100000 +#define ROLE_REVERSAL_DELAY_MS 500 + +enum smb_mode { + PARALLEL_MASTER = 0, + PARALLEL_SLAVE, + NUM_MODES, +}; + +enum sink_src_mode { + SINK_MODE, + SRC_MODE, + AUDIO_ACCESS_MODE, + UNATTACHED_MODE, +}; + +enum qc2_non_comp_voltage { + QC2_COMPLIANT, + QC2_NON_COMPLIANT_9V, + QC2_NON_COMPLIANT_12V +}; + +enum { + BOOST_BACK_WA = BIT(0), + SW_THERM_REGULATION_WA = BIT(1), + WEAK_ADAPTER_WA = BIT(2), + USBIN_OV_WA = BIT(3), + CHG_TERMINATION_WA = BIT(4), + USBIN_ADC_WA = BIT(5), + SKIP_MISC_PBS_IRQ_WA = BIT(6), +}; + +enum jeita_cfg_stat { + JEITA_CFG_NONE = 0, + JEITA_CFG_FAILURE, + JEITA_CFG_COMPLETE, +}; + +enum { + RERUN_AICL = 0, + RESTART_AICL, +}; + +enum smb_irq_index { + /* CHGR */ + CHGR_ERROR_IRQ = 0, + CHG_STATE_CHANGE_IRQ, + STEP_CHG_STATE_CHANGE_IRQ, + STEP_CHG_SOC_UPDATE_FAIL_IRQ, + STEP_CHG_SOC_UPDATE_REQ_IRQ, + FG_FVCAL_QUALIFIED_IRQ, + VPH_ALARM_IRQ, + VPH_DROP_PRECHG_IRQ, + /* DCDC */ + OTG_FAIL_IRQ, + OTG_OC_DISABLE_SW_IRQ, + OTG_OC_HICCUP_IRQ, + BSM_ACTIVE_IRQ, + HIGH_DUTY_CYCLE_IRQ, + INPUT_CURRENT_LIMITING_IRQ, + CONCURRENT_MODE_DISABLE_IRQ, + SWITCHER_POWER_OK_IRQ, + /* BATIF */ + BAT_TEMP_IRQ, + ALL_CHNL_CONV_DONE_IRQ, + BAT_OV_IRQ, + BAT_LOW_IRQ, + BAT_THERM_OR_ID_MISSING_IRQ, + BAT_TERMINAL_MISSING_IRQ, + BUCK_OC_IRQ, + VPH_OV_IRQ, + /* USB */ + USBIN_COLLAPSE_IRQ, + USBIN_VASHDN_IRQ, + USBIN_UV_IRQ, + USBIN_OV_IRQ, + USBIN_PLUGIN_IRQ, + USBIN_REVI_CHANGE_IRQ, + USBIN_SRC_CHANGE_IRQ, + USBIN_ICL_CHANGE_IRQ, + /* DC */ + DCIN_VASHDN_IRQ, + DCIN_UV_IRQ, + DCIN_OV_IRQ, + DCIN_PLUGIN_IRQ, + DCIN_REVI_IRQ, + DCIN_PON_IRQ, + DCIN_EN_IRQ, + /* TYPEC */ + TYPEC_OR_RID_DETECTION_CHANGE_IRQ, + TYPEC_VPD_DETECT_IRQ, + TYPEC_CC_STATE_CHANGE_IRQ, + TYPEC_VCONN_OC_IRQ, + TYPEC_VBUS_CHANGE_IRQ, + TYPEC_ATTACH_DETACH_IRQ, + TYPEC_LEGACY_CABLE_DETECT_IRQ, + TYPEC_TRY_SNK_SRC_DETECT_IRQ, + /* MISC */ + WDOG_SNARL_IRQ, + WDOG_BARK_IRQ, + AICL_FAIL_IRQ, + AICL_DONE_IRQ, + SMB_EN_IRQ, + IMP_TRIGGER_IRQ, + TEMP_CHANGE_IRQ, + TEMP_CHANGE_SMB_IRQ, + /* FLASH */ + VREG_OK_IRQ, + ILIM_S2_IRQ, + ILIM_S1_IRQ, + VOUT_DOWN_IRQ, + VOUT_UP_IRQ, + FLASH_STATE_CHANGE_IRQ, + TORCH_REQ_IRQ, + FLASH_EN_IRQ, + SDAM_STS_IRQ, + /* END */ + SMB_IRQ_MAX, +}; + +enum float_options { + FLOAT_DCP = 1, + FLOAT_SDP = 2, + DISABLE_CHARGING = 3, + SUSPEND_INPUT = 4, +}; + +enum chg_term_config_src { + ITERM_SRC_UNSPECIFIED, + ITERM_SRC_ADC, + ITERM_SRC_ANALOG +}; + +enum comp_clamp_levels { + CLAMP_LEVEL_DEFAULT = 0, + CLAMP_LEVEL_1, + MAX_CLAMP_LEVEL, +}; + +struct clamp_config { + u16 reg[3]; + u16 val[3]; +}; + +struct smb_irq_info { + const char *name; + const irq_handler_t handler; + const bool wake; + const struct storm_watch storm_data; + struct smb_irq_data *irq_data; + int irq; + bool enabled; +}; + +static const unsigned int smblib_extcon_cable[] = { + EXTCON_USB, + EXTCON_USB_HOST, + EXTCON_NONE, +}; + +enum lpd_reason { + LPD_NONE, + LPD_MOISTURE_DETECTED, + LPD_FLOATING_CABLE, +}; + +/* Following states are applicable only for floating cable during LPD */ +enum lpd_stage { + /* initial stage */ + LPD_STAGE_NONE, + /* started and ongoing */ + LPD_STAGE_FLOAT, + /* cancel if started, or don't start */ + LPD_STAGE_FLOAT_CANCEL, + /* confirmed and mitigation measures taken for 60 s */ + LPD_STAGE_COMMIT, +}; + +enum thermal_status_levels { + TEMP_SHUT_DOWN = 0, + TEMP_SHUT_DOWN_SMB, + TEMP_ALERT_LEVEL, + TEMP_ABOVE_RANGE, + TEMP_WITHIN_RANGE, + TEMP_BELOW_RANGE, +}; + +enum icl_override_mode { + /* APSD/Type-C/QC auto */ + HW_AUTO_MODE, + /* 100/150/500/900mA */ + SW_OVERRIDE_USB51_MODE, + /* ICL other than USB51 */ + SW_OVERRIDE_HC_MODE, +}; + +/* EXTCON_USB and EXTCON_USB_HOST are mutually exclusive */ +static const u32 smblib_extcon_exclusive[] = {0x3, 0}; + +struct smb_regulator { + struct regulator_dev *rdev; + struct regulator_desc rdesc; +}; + +struct smb_irq_data { + void *parent_data; + const char *name; + struct storm_watch storm_data; +}; + +struct smb_chg_param { + const char *name; + u16 reg; + int min_u; + int max_u; + int step_u; + int (*get_proc)(struct smb_chg_param *param, + u8 val_raw); + int (*set_proc)(struct smb_chg_param *param, + int val_u, + u8 *val_raw); +}; + +struct buck_boost_freq { + int freq_khz; + u8 val; +}; + +struct smb_chg_freq { + unsigned int freq_5V; + unsigned int freq_6V_8V; + unsigned int freq_9V; + unsigned int freq_12V; + unsigned int freq_removal; + unsigned int freq_below_otg_threshold; + unsigned int freq_above_otg_threshold; +}; + +struct smb_params { + struct smb_chg_param fcc; + struct smb_chg_param fv; + struct smb_chg_param usb_icl; + struct smb_chg_param icl_max_stat; + struct smb_chg_param icl_stat; + struct smb_chg_param otg_cl; + struct smb_chg_param dc_icl; + struct smb_chg_param jeita_cc_comp_hot; + struct smb_chg_param jeita_cc_comp_cold; + struct smb_chg_param freq_switcher; + struct smb_chg_param aicl_5v_threshold; + struct smb_chg_param aicl_cont_threshold; +}; + +struct parallel_params { + struct power_supply *psy; +}; + +struct smb_iio { + struct iio_channel *temp_chan; + struct iio_channel *usbin_i_chan; + struct iio_channel *usbin_v_chan; + struct iio_channel *mid_chan; + struct iio_channel *batt_i_chan; + struct iio_channel *connector_temp_chan; + struct iio_channel *sbux_chan; + struct iio_channel *vph_v_chan; + struct iio_channel *die_temp_chan; + struct iio_channel *skin_temp_chan; + struct iio_channel *smb_temp_chan; +}; + +struct smb_charger { + struct device *dev; + char *name; + struct regmap *regmap; + struct smb_irq_info *irq_info; + struct smb_params param; + struct smb_iio iio; + int *debug_mask; + int pd_disabled; + enum smb_mode mode; + struct smb_chg_freq chg_freq; + int otg_delay_ms; + int weak_chg_icl_ua; + u32 sdam_base; + bool pd_not_supported; + + /* locks */ + struct mutex smb_lock; + struct mutex ps_change_lock; + struct mutex irq_status_lock; + struct mutex dcin_aicl_lock; + spinlock_t typec_pr_lock; + struct mutex adc_lock; + struct mutex dpdm_lock; + struct mutex typec_lock; + + /* power supplies */ + struct power_supply *batt_psy; + struct power_supply *usb_psy; + struct power_supply *dc_psy; + struct power_supply *bms_psy; + struct power_supply *usb_main_psy; + struct power_supply *usb_port_psy; + struct power_supply *wls_psy; + struct power_supply *cp_psy; + enum power_supply_type real_charger_type; + + /* notifiers */ + struct notifier_block nb; + + /* parallel charging */ + struct parallel_params pl; + + /* CC Mode */ + int adapter_cc_mode; + int thermal_overheat; + + /* regulators */ + struct smb_regulator *vbus_vreg; + struct smb_regulator *vconn_vreg; + struct regulator *dpdm_reg; + + /* typec */ + struct typec_port *typec_port; + struct typec_capability typec_caps; + struct typec_partner *typec_partner; + struct typec_partner_desc typec_partner_desc; + + /* votables */ + struct votable *dc_suspend_votable; + struct votable *fcc_votable; + struct votable *fcc_main_votable; + struct votable *fv_votable; + struct votable *usb_icl_votable; + struct votable *awake_votable; + struct votable *pl_disable_votable; + struct votable *chg_disable_votable; + struct votable *pl_enable_votable_indirect; + struct votable *cp_disable_votable; + struct votable *cp_ilim_votable; + struct votable *smb_override_votable; + struct votable *icl_irq_disable_votable; + struct votable *limited_irq_disable_votable; + struct votable *hdc_irq_disable_votable; + struct votable *temp_change_irq_disable_votable; + struct votable *qnovo_disable_votable; + + /* work */ + struct work_struct bms_update_work; + struct work_struct pl_update_work; + struct work_struct jeita_update_work; + struct work_struct moisture_protection_work; + struct work_struct chg_termination_work; + struct work_struct dcin_aicl_work; + struct work_struct cp_status_change_work; + struct delayed_work ps_change_timeout_work; + struct delayed_work clear_hdc_work; + struct delayed_work icl_change_work; + struct delayed_work pl_enable_work; + struct delayed_work uusb_otg_work; + struct delayed_work bb_removal_work; + struct delayed_work lpd_ra_open_work; + struct delayed_work lpd_detach_work; + struct delayed_work thermal_regulation_work; + struct delayed_work usbov_dbc_work; + struct delayed_work pr_swap_detach_work; + struct delayed_work pr_lock_clear_work; + struct delayed_work role_reversal_check; + + struct alarm lpd_recheck_timer; + struct alarm moisture_protection_alarm; + struct alarm chg_termination_alarm; + struct alarm dcin_aicl_alarm; + + struct timer_list apsd_timer; + + struct charger_param chg_param; + /* secondary charger config */ + bool sec_pl_present; + bool sec_cp_present; + int sec_chg_selected; + int cp_reason; + int cp_topo; + + /* pd */ + int voltage_min_uv; + int voltage_max_uv; + int pd_active; + bool pd_hard_reset; + bool pr_lock_in_progress; + bool pr_swap_in_progress; + bool early_usb_attach; + bool ok_to_pd; + bool typec_legacy; + bool typec_irq_en; + bool typec_role_swap_failed; + + /* cached status */ + bool system_suspend_supported; + int boost_threshold_ua; + int system_temp_level; + int thermal_levels; + int *thermal_mitigation; + int dcp_icl_ua; + int fake_capacity; + int fake_batt_status; + bool step_chg_enabled; + bool sw_jeita_enabled; + bool typec_legacy_use_rp_icl; + bool is_hdc; + bool chg_done; + int connector_type; + bool otg_en; + bool suspend_input_on_debug_batt; + bool fake_chg_status_on_debug_batt; + int default_icl_ua; + int otg_cl_ua; + bool uusb_apsd_rerun_done; + bool typec_present; + int fake_input_current_limited; + int typec_mode; + int dr_mode; + int usb_icl_change_irq_enabled; + u32 jeita_status; + u8 float_cfg; + bool jeita_arb_flag; + bool use_extcon; + bool otg_present; + bool hvdcp_disable; + int hw_max_icl_ua; + int auto_recharge_soc; + enum sink_src_mode sink_src_mode; + enum power_supply_typec_power_role power_role; + enum jeita_cfg_stat jeita_configured; + int charger_temp_max; + int smb_temp_max; + u8 typec_try_mode; + enum lpd_stage lpd_stage; + bool lpd_disabled; + enum lpd_reason lpd_reason; + bool fcc_stepper_enable; + int die_temp; + int smb_temp; + int skin_temp; + int connector_temp; + int thermal_status; + int main_fcc_max; + u32 jeita_soft_thlds[2]; + u32 jeita_soft_hys_thlds[2]; + int jeita_soft_fcc[2]; + int jeita_soft_fv[2]; + bool moisture_present; + bool uusb_moisture_protection_capable; + bool uusb_moisture_protection_enabled; + bool hw_die_temp_mitigation; + bool hw_connector_mitigation; + bool hw_skin_temp_mitigation; + bool en_skin_therm_mitigation; + int connector_pull_up; + int smb_pull_up; + int aicl_5v_threshold_mv; + int default_aicl_5v_threshold_mv; + int aicl_cont_threshold_mv; + int default_aicl_cont_threshold_mv; + bool aicl_max_reached; + int charge_full_cc; + int cc_soc_ref; + int last_cc_soc; + int term_vbat_uv; + int usbin_forced_max_uv; + int init_thermal_ua; + u32 comp_clamp_level; + int wls_icl_ua; + int cutoff_count; + bool dcin_aicl_done; + bool hvdcp3_standalone_config; + bool dcin_icl_user_set; + bool dpdm_enabled; + bool apsd_ext_timeout; + bool qc3p5_detected; + + /* workaround flag */ + u32 wa_flags; + int boost_current_ua; + int qc2_max_pulses; + enum qc2_non_comp_voltage qc2_unsupported_voltage; + bool dbc_usbov; + + /* extcon for VBUS / ID notification to USB for uUSB */ + struct extcon_dev *extcon; + + /* battery profile */ + int batt_profile_fcc_ua; + int batt_profile_fv_uv; + + int usb_icl_delta_ua; + int pulse_cnt; + + int die_health; + int connector_health; + + /* flash */ + u32 flash_derating_soc; + u32 flash_disable_soc; + u32 headroom_mode; + bool flash_init_done; + bool flash_active; + u32 irq_status; + + /* wireless */ + int dcin_uv_count; + ktime_t dcin_uv_last_time; + int last_wls_vout; +}; + +int smblib_read(struct smb_charger *chg, u16 addr, u8 *val); +int smblib_masked_write(struct smb_charger *chg, u16 addr, u8 mask, u8 val); +int smblib_write(struct smb_charger *chg, u16 addr, u8 val); +int smblib_batch_write(struct smb_charger *chg, u16 addr, u8 *val, int count); +int smblib_batch_read(struct smb_charger *chg, u16 addr, u8 *val, int count); + +int smblib_get_charge_param(struct smb_charger *chg, + struct smb_chg_param *param, int *val_u); +int smblib_get_usb_suspend(struct smb_charger *chg, int *suspend); +int smblib_get_aicl_cont_threshold(struct smb_chg_param *param, u8 val_raw); +int smblib_enable_charging(struct smb_charger *chg, bool enable); +int smblib_set_charge_param(struct smb_charger *chg, + struct smb_chg_param *param, int val_u); +int smblib_set_usb_suspend(struct smb_charger *chg, bool suspend); +int smblib_set_dc_suspend(struct smb_charger *chg, bool suspend); + +int smblib_mapping_soc_from_field_value(struct smb_chg_param *param, + int val_u, u8 *val_raw); +int smblib_mapping_cc_delta_to_field_value(struct smb_chg_param *param, + u8 val_raw); +int smblib_mapping_cc_delta_from_field_value(struct smb_chg_param *param, + int val_u, u8 *val_raw); +int smblib_set_chg_freq(struct smb_chg_param *param, + int val_u, u8 *val_raw); +int smblib_set_prop_boost_current(struct smb_charger *chg, + const union power_supply_propval *val); +int smblib_set_aicl_cont_threshold(struct smb_chg_param *param, + int val_u, u8 *val_raw); +int smblib_vbus_regulator_enable(struct regulator_dev *rdev); +int smblib_vbus_regulator_disable(struct regulator_dev *rdev); +int smblib_vbus_regulator_is_enabled(struct regulator_dev *rdev); + +int smblib_vconn_regulator_enable(struct regulator_dev *rdev); +int smblib_vconn_regulator_disable(struct regulator_dev *rdev); +int smblib_vconn_regulator_is_enabled(struct regulator_dev *rdev); + +irqreturn_t default_irq_handler(int irq, void *data); +irqreturn_t smb_en_irq_handler(int irq, void *data); +irqreturn_t chg_state_change_irq_handler(int irq, void *data); +irqreturn_t batt_temp_changed_irq_handler(int irq, void *data); +irqreturn_t batt_psy_changed_irq_handler(int irq, void *data); +irqreturn_t usbin_uv_irq_handler(int irq, void *data); +irqreturn_t usb_plugin_irq_handler(int irq, void *data); +irqreturn_t usb_source_change_irq_handler(int irq, void *data); +irqreturn_t icl_change_irq_handler(int irq, void *data); +irqreturn_t typec_state_change_irq_handler(int irq, void *data); +irqreturn_t typec_attach_detach_irq_handler(int irq, void *data); +irqreturn_t dcin_uv_irq_handler(int irq, void *data); +irqreturn_t dc_plugin_irq_handler(int irq, void *data); +irqreturn_t high_duty_cycle_irq_handler(int irq, void *data); +irqreturn_t switcher_power_ok_irq_handler(int irq, void *data); +irqreturn_t wdog_snarl_irq_handler(int irq, void *data); +irqreturn_t wdog_bark_irq_handler(int irq, void *data); +irqreturn_t typec_or_rid_detection_change_irq_handler(int irq, void *data); +irqreturn_t temp_change_irq_handler(int irq, void *data); +irqreturn_t usbin_ov_irq_handler(int irq, void *data); +irqreturn_t sdam_sts_change_irq_handler(int irq, void *data); +int smblib_get_prop_input_suspend(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_batt_present(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_batt_capacity(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_batt_status(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_batt_charge_type(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_batt_charge_done(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_batt_current_now(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_batt_health(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_system_temp_level(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_system_temp_level_max(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_input_current_limited(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_batt_iterm(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_set_prop_input_suspend(struct smb_charger *chg, + const union power_supply_propval *val); +int smblib_set_prop_batt_capacity(struct smb_charger *chg, + const union power_supply_propval *val); +int smblib_set_prop_batt_status(struct smb_charger *chg, + const union power_supply_propval *val); +int smblib_set_prop_system_temp_level(struct smb_charger *chg, + const union power_supply_propval *val); +int smblib_set_prop_input_current_limited(struct smb_charger *chg, + const union power_supply_propval *val); + +int smblib_get_prop_dc_present(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_dc_online(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_dc_current_max(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_set_prop_dc_current_max(struct smb_charger *chg, + const union power_supply_propval *val); +int smblib_get_prop_dc_voltage_now(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_dc_voltage_max(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_voltage_wls_output(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_set_prop_voltage_wls_output(struct smb_charger *chg, + const union power_supply_propval *val); +int smblib_set_prop_dc_reset(struct smb_charger *chg); +int smblib_get_prop_usb_present(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_usb_online(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_usb_online(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_usb_suspend(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_usb_voltage_max(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_usb_voltage_max_design(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_set_prop_usb_voltage_max_limit(struct smb_charger *chg, + const union power_supply_propval *val); +int smblib_get_prop_usb_voltage_now(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_low_power(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_usb_current_now(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_usb_prop_typec_mode(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_typec_cc_orientation(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_scope(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_typec_select_rp(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_typec_power_role(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_input_current_settled(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_input_voltage_settled(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_pd_in_hard_reset(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_pe_start(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_charger_temp(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_die_health(struct smb_charger *chg); +int smblib_get_die_health(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_smb_health(struct smb_charger *chg); +int smblib_get_prop_connector_health(struct smb_charger *chg); +int smblib_set_prop_thermal_overheat(struct smb_charger *chg, + int therm_overheat); +int smblib_get_skin_temp_status(struct smb_charger *chg); +int smblib_get_prop_vph_voltage_now(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_set_prop_pd_current_max(struct smb_charger *chg, + const union power_supply_propval *val); +int smblib_set_prop_sdp_current_max(struct smb_charger *chg, + const union power_supply_propval *val); +int smblib_set_prop_pd_voltage_max(struct smb_charger *chg, + const union power_supply_propval *val); +int smblib_set_prop_pd_voltage_min(struct smb_charger *chg, + const union power_supply_propval *val); +int smblib_set_prop_typec_power_role(struct smb_charger *chg, + const union power_supply_propval *val); +int smblib_set_prop_typec_select_rp(struct smb_charger *chg, + const union power_supply_propval *val); +int smblib_set_prop_pd_active(struct smb_charger *chg, + const union power_supply_propval *val); +int smblib_set_prop_pd_in_hard_reset(struct smb_charger *chg, + const union power_supply_propval *val); +int smblib_set_prop_ship_mode(struct smb_charger *chg, + const union power_supply_propval *val); +int smblib_set_prop_rechg_soc_thresh(struct smb_charger *chg, + const union power_supply_propval *val); +void smblib_suspend_on_debug_battery(struct smb_charger *chg); +int smblib_rerun_apsd_if_required(struct smb_charger *chg); +void smblib_rerun_apsd(struct smb_charger *chg); +int smblib_get_prop_fcc_delta(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_thermal_threshold(struct smb_charger *chg, u16 addr, int *val); +int smblib_dp_dm(struct smb_charger *chg, int val); +int smblib_disable_hw_jeita(struct smb_charger *chg, bool disable); +int smblib_run_aicl(struct smb_charger *chg, int type); +int smblib_set_icl_current(struct smb_charger *chg, int icl_ua); +int smblib_get_icl_current(struct smb_charger *chg, int *icl_ua); +int smblib_get_charge_current(struct smb_charger *chg, int *total_current_ua); +int smblib_get_prop_pr_swap_in_progress(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_set_prop_pr_swap_in_progress(struct smb_charger *chg, + const union power_supply_propval *val); +int smblib_typec_port_type_set(const struct typec_capability *cap, + enum typec_port_type type); +int smblib_get_prop_from_bms(struct smb_charger *chg, + enum power_supply_property psp, + union power_supply_propval *val); +int smblib_get_iio_channel(struct smb_charger *chg, const char *propname, + struct iio_channel **chan); +int smblib_read_iio_channel(struct smb_charger *chg, struct iio_channel *chan, + int div, int *data); +int smblib_configure_hvdcp_apsd(struct smb_charger *chg, bool enable); +int smblib_icl_override(struct smb_charger *chg, enum icl_override_mode mode); +enum alarmtimer_restart smblib_lpd_recheck_timer(struct alarm *alarm, + ktime_t time); +int smblib_toggle_smb_en(struct smb_charger *chg, int toggle); +void smblib_hvdcp_detect_enable(struct smb_charger *chg, bool enable); +void smblib_hvdcp_hw_inov_enable(struct smb_charger *chg, bool enable); +void smblib_hvdcp_exit_config(struct smb_charger *chg); +void smblib_apsd_enable(struct smb_charger *chg, bool enable); +int smblib_force_vbus_voltage(struct smb_charger *chg, u8 val); +int smblib_get_irq_status(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_qc3_main_icl_offset(struct smb_charger *chg, int *offset_ua); + +int smblib_init(struct smb_charger *chg); +int smblib_deinit(struct smb_charger *chg); +#endif /* __SMB5_CHARGER_H */ diff --git a/drivers/power/supply/qcom/smb5-reg.h b/drivers/power/supply/qcom/smb5-reg.h new file mode 100644 index 000000000000..3aa2c2ea1d90 --- /dev/null +++ b/drivers/power/supply/qcom/smb5-reg.h @@ -0,0 +1,557 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2018-2020 The Linux Foundation. All rights reserved. + */ + +#ifndef __SMB5_CHARGER_REG_H +#define __SMB5_CHARGER_REG_H + +#include + +#define CHGR_BASE 0x1000 +#define DCDC_BASE 0x1100 +#define BATIF_BASE 0x1200 +#define USBIN_BASE 0x1300 +#define DCIN_BASE 0x1400 +#define TYPEC_BASE 0X1500 +#define MISC_BASE 0x1600 +#define MISC_PBS_BASE 0x7500 + +#define PERPH_TYPE_OFFSET 0x04 +#define TYPE_MASK GENMASK(7, 0) +#define PERPH_SUBTYPE_OFFSET 0x05 +#define SUBTYPE_MASK GENMASK(7, 0) +#define INT_RT_STS_OFFSET 0x10 +#define SDAM_TYPE 0x2E + +/******************************** + * CHGR Peripheral Registers * + ********************************/ +#define BATTERY_CHARGER_STATUS_1_REG (CHGR_BASE + 0x06) +#define BATTERY_CHARGER_STATUS_MASK GENMASK(2, 0) +enum { + INHIBIT_CHARGE = 0, + TRICKLE_CHARGE, + PRE_CHARGE, + FULLON_CHARGE, + TAPER_CHARGE, + TERMINATE_CHARGE, + PAUSE_CHARGE, + DISABLE_CHARGE, +}; + +#define BATTERY_CHARGER_STATUS_2_REG (CHGR_BASE + 0x07) +#define CHARGER_ERROR_STATUS_BAT_OV_BIT BIT(1) + +#define BATTERY_CHARGER_STATUS_5_REG (CHGR_BASE + 0x0B) +#define ENABLE_TRICKLE_BIT BIT(2) +#define ENABLE_PRE_CHARGING_BIT BIT(1) +#define ENABLE_FULLON_MODE_BIT BIT(0) + +#define BATTERY_CHARGER_STATUS_7_REG (CHGR_BASE + 0x0D) +#define BAT_TEMP_STATUS_SOFT_LIMIT_MASK GENMASK(5, 4) +#define BAT_TEMP_STATUS_HOT_SOFT_BIT BIT(5) +#define BAT_TEMP_STATUS_COLD_SOFT_BIT BIT(4) +#define BAT_TEMP_STATUS_HARD_LIMIT_MASK GENMASK(3, 2) +#define BAT_TEMP_STATUS_TOO_HOT_BIT BIT(3) +#define BAT_TEMP_STATUS_TOO_COLD_BIT BIT(2) +#define BAT_TEMP_STATUS_TOO_HOT_AFP_BIT BIT(1) +#define BAT_TEMP_STATUS_TOO_COLD_AFP_BIT BIT(0) + +#define CHARGING_ENABLE_CMD_REG (CHGR_BASE + 0x42) +#define CHARGING_ENABLE_CMD_BIT BIT(0) + +#define CHARGING_PAUSE_CMD_REG (CHGR_BASE + 0x43) +#define CHARGING_PAUSE_CMD_BIT BIT(0) + +#define CHGR_CFG2_REG (CHGR_BASE + 0x51) +#define RECHG_MASK GENMASK(2, 1) +#define VBAT_BASED_RECHG_BIT BIT(2) +#define SOC_BASED_RECHG_BIT GENMASK(2, 1) +#define CHARGER_INHIBIT_BIT BIT(0) + +#define CHGR_FAST_CHARGE_CURRENT_CFG_REG (CHGR_BASE + 0x61) + +#define CHGR_ADC_ITERM_UP_THD_MSB_REG (CHGR_BASE + 0x67) +#define CHGR_ADC_ITERM_UP_THD_LSB_REG (CHGR_BASE + 0x68) +#define CHGR_ADC_ITERM_LO_THD_MSB_REG (CHGR_BASE + 0x69) +#define CHGR_ADC_ITERM_LO_THD_LSB_REG (CHGR_BASE + 0x6A) + +#define CHGR_NO_SAMPLE_TERM_RCHG_CFG_REG (CHGR_BASE + 0x6B) +#define NO_OF_SAMPLE_FOR_RCHG_SHIFT 2 +#define NO_OF_SAMPLE_FOR_RCHG GENMASK(3, 2) + +#define CHGR_ADC_TERM_CFG_REG (CHGR_BASE + 0x6C) +#define TERM_BASED_ON_SYNC_CONV_OR_SAMPLE_CNT BIT(0) +#define TERM_BASED_ON_SYNC_CONV 0 +#define TERM_BASED_ON_SAMPLE_CNT 1 + +#define CHGR_FLOAT_VOLTAGE_CFG_REG (CHGR_BASE + 0x70) + +#define CHARGE_INHIBIT_THRESHOLD_CFG_REG (CHGR_BASE + 0x72) +#define CHARGE_INHIBIT_THRESHOLD_MASK GENMASK(1, 0) +#define INHIBIT_ANALOG_VFLT_MINUS_50MV 0 +#define INHIBIT_ANALOG_VFLT_MINUS_100MV 1 +#define INHIBIT_ANALOG_VFLT_MINUS_200MV 2 +#define INHIBIT_ANALOG_VFLT_MINUS_300MV 3 + +#define CHARGE_RCHG_SOC_THRESHOLD_CFG_REG (CHGR_BASE + 0x7D) + +#define CHGR_ADC_RECHARGE_THRESHOLD_MSB_REG (CHGR_BASE + 0x7E) + +#define CHGR_ADC_RECHARGE_THRESHOLD_LSB_REG (CHGR_BASE + 0x7F) + +#define JEITA_EN_CFG_REG (CHGR_BASE + 0x90) +#define JEITA_EN_HOT_SL_FCV_BIT BIT(3) +#define JEITA_EN_COLD_SL_FCV_BIT BIT(2) +#define JEITA_EN_HOT_SL_CCC_BIT BIT(1) +#define JEITA_EN_COLD_SL_CCC_BIT BIT(0) + +#define JEITA_CCCOMP_CFG_HOT_REG (CHGR_BASE + 0x92) +#define JEITA_CCCOMP_CFG_COLD_REG (CHGR_BASE + 0x93) + +#define CHGR_JEITA_THRESHOLD_BASE_REG(i) (CHGR_BASE + 0x94 + (i * 4)) +#define CHGR_JEITA_HOT_THRESHOLD_MSB_REG CHGR_JEITA_THRESHOLD_BASE_REG(0) + +#define CHGR_FAST_CHARGE_SAFETY_TIMER_CFG_REG (CHGR_BASE + 0xA2) +#define FAST_CHARGE_SAFETY_TIMER_192_MIN 0x0 +#define FAST_CHARGE_SAFETY_TIMER_384_MIN 0x1 +#define FAST_CHARGE_SAFETY_TIMER_768_MIN 0x2 +#define FAST_CHARGE_SAFETY_TIMER_1536_MIN 0x3 + +#define CHGR_ENG_CHARGING_CFG_REG (CHGR_BASE + 0xC0) +#define CHGR_ITERM_USE_ANALOG_BIT BIT(3) + +/******************************** + * DCDC Peripheral Registers * + ********************************/ +#define ICL_MAX_STATUS_REG (DCDC_BASE + 0x06) +#define ICL_STATUS_REG (DCDC_BASE + 0x07) +#define AICL_ICL_STATUS_REG (DCDC_BASE + 0x08) + +#define AICL_STATUS_REG (DCDC_BASE + 0x0A) +#define SOFT_ILIMIT_BIT BIT(6) +#define AICL_DONE_BIT BIT(0) + +#define POWER_PATH_STATUS_REG (DCDC_BASE + 0x0B) +#define USBIN_SUSPEND_STS_BIT BIT(6) +#define USE_USBIN_BIT BIT(4) +#define USE_DCIN_BIT BIT(3) +#define POWER_PATH_MASK GENMASK(2, 1) +#define VALID_INPUT_POWER_SOURCE_STS_BIT BIT(0) + +#define DCDC_CMD_OTG_REG (DCDC_BASE + 0x40) +#define OTG_EN_BIT BIT(0) + +#define DCDC_FSW_SEL_REG (DCDC_BASE + 0x50) + +#define DCDC_OTG_CURRENT_LIMIT_CFG_REG (DCDC_BASE + 0x52) + +#define DCDC_OTG_CFG_REG (DCDC_BASE + 0x53) +#define OTG_EN_SRC_CFG_BIT BIT(1) + +#define OTG_FAULT_CONDITION_CFG_REG (DCDC_BASE + 0x56) +#define USBIN_MID_COMP_FAULT_EN_BIT BIT(5) +#define USBIN_COLLAPSE_FAULT_EN_BIT BIT(4) + +#define DCDC_CFG_REF_MAX_PSNS_REG (DCDC_BASE + 0x8C) + +#define DCDC_ENG_SDCDC_CFG5_REG (DCDC_BASE + 0xC4) +#define ENG_SDCDC_BAT_HPWR_MASK GENMASK(7, 6) +enum { + BOOST_MODE_THRESH_3P3_V, + BOOST_MODE_THRESH_3P4_V = 0x40, + BOOST_MODE_THRESH_3P5_V = 0x80, + BOOST_MODE_THRESH_3P6_V = 0xC0 +}; + +/******************************** + * BATIF Peripheral Registers * + ********************************/ + +/* BATIF Interrupt Bits */ +#define VPH_OV_RT_STS_BIT BIT(7) +#define BUCK_OC_RT_STS_BIT BIT(6) +#define BAT_TERMINAL_MISSING_RT_STS_BIT BIT(5) +#define BAT_THERM_OR_ID_MISSING_RT_STS_BIT BIT(4) +#define BAT_LOW_RT_STS_BIT BIT(3) +#define BAT_OV_RT_STS_BIT BIT(2) +#define ALL_CHNL_CONV_DONE_RT_STS BIT(1) +#define BAT_TEMP_RT_STS_BIT BIT(0) + +#define SHIP_MODE_REG (BATIF_BASE + 0x40) +#define SHIP_MODE_EN_BIT BIT(0) + +#define BATIF_ADC_CHANNEL_EN_REG (BATIF_BASE + 0x82) +#define IBATT_CHANNEL_EN_BIT BIT(6) +#define CONN_THM_CHANNEL_EN_BIT BIT(4) +#define DIE_TEMP_CHANNEL_EN_BIT BIT(2) +#define MISC_THM_CHANNEL_EN_BIT BIT(1) + +#define BATIF_ADC_INTERNAL_PULL_UP_REG (BATIF_BASE + 0x86) +#define INTERNAL_PULL_UP_CONN_THM_MASK GENMASK(5, 4) +#define CONN_THM_SHIFT 4 +#define INTERNAL_PULL_NO_PULL 0x00 +#define INTERNAL_PULL_30K_PULL 0x01 +#define INTERNAL_PULL_100K_PULL 0x02 +#define INTERNAL_PULL_400K_PULL 0x03 + +/******************************** + * USBIN Peripheral Registers * + ********************************/ +#define APSD_STATUS_REG (USBIN_BASE + 0x07) +#define APSD_STATUS_7_BIT BIT(7) +#define HVDCP_CHECK_TIMEOUT_BIT BIT(6) +#define SLOW_PLUGIN_TIMEOUT_BIT BIT(5) +#define ENUMERATION_DONE_BIT BIT(4) +#define VADP_CHANGE_DONE_AFTER_AUTH_BIT BIT(3) +#define QC_AUTH_DONE_STATUS_BIT BIT(2) +#define QC_CHARGER_BIT BIT(1) +#define APSD_DTC_STATUS_DONE_BIT BIT(0) + +#define APSD_RESULT_STATUS_REG (USBIN_BASE + 0x08) +#define APSD_RESULT_STATUS_7_BIT BIT(7) +#define APSD_RESULT_STATUS_MASK GENMASK(6, 0) +#define QC_3P0_BIT BIT(6) +#define QC_2P0_BIT BIT(5) +#define FLOAT_CHARGER_BIT BIT(4) +#define DCP_CHARGER_BIT BIT(3) +#define CDP_CHARGER_BIT BIT(2) +#define OCP_CHARGER_BIT BIT(1) +#define SDP_CHARGER_BIT BIT(0) + +#define QC_CHANGE_STATUS_REG (USBIN_BASE + 0x09) +#define QC_12V_BIT BIT(2) +#define QC_9V_BIT BIT(1) +#define QC_5V_BIT BIT(0) +#define QC_2P0_STATUS_MASK GENMASK(2, 0) + +/* USBIN Interrupt Bits */ +#define USBIN_ICL_CHANGE_RT_STS_BIT BIT(7) +#define USBIN_SOURCE_CHANGE_RT_STS_BIT BIT(6) +#define USBIN_REVI_RT_STS_BIT BIT(5) +#define USBIN_PLUGIN_RT_STS_BIT BIT(4) +#define USBIN_OV_RT_STS_BIT BIT(3) +#define USBIN_UV_RT_STS_BIT BIT(2) +#define USBIN_VASHDN_RT_STS_BIT BIT(1) +#define USBIN_COLLAPSE_RT_STS_BIT BIT(0) + +#define USBIN_CMD_IL_REG (USBIN_BASE + 0x40) +#define USBIN_SUSPEND_BIT BIT(0) + +#define CMD_APSD_REG (USBIN_BASE + 0x41) +#define APSD_RERUN_BIT BIT(0) + +#define CMD_ICL_OVERRIDE_REG (USBIN_BASE + 0x42) +#define ICL_OVERRIDE_BIT BIT(0) + +#define CMD_HVDCP_2_REG (USBIN_BASE + 0x43) +#define FORCE_12V_BIT BIT(5) +#define FORCE_9V_BIT BIT(4) +#define FORCE_5V_BIT BIT(3) +#define IDLE_BIT BIT(2) +#define SINGLE_DECREMENT_BIT BIT(1) +#define SINGLE_INCREMENT_BIT BIT(0) + +#define USBIN_ADAPTER_ALLOW_OVERRIDE_REG (USBIN_BASE + 0x44) +#define CONTINUOUS BIT(3) +#define FORCE_12V BIT(2) +#define FORCE_9V BIT(1) +#define FORCE_5V BIT(0) +#define FORCE_NULL 0 + +#define USB_CMD_PULLDOWN_REG (USBIN_BASE + 0x45) +#define EN_PULLDOWN_USB_IN_BIT BIT(0) + +#define TYPE_C_CFG_REG (USBIN_BASE + 0x58) +#define BC1P2_START_ON_CC_BIT BIT(7) + +#define HVDCP_PULSE_COUNT_MAX_REG (USBIN_BASE + 0x5B) +#define HVDCP_PULSE_COUNT_MAX_QC2_MASK GENMASK(7, 6) +enum { + HVDCP_PULSE_COUNT_MAX_QC2_5V = 0, + HVDCP_PULSE_COUNT_MAX_QC2_9V = 0x40, + HVDCP_PULSE_COUNT_MAX_QC2_12V = 0x80, + HVDCP_PULSE_COUNT_MAX_QC2_INVALID = 0xC0 +}; + +#define USBIN_OPTIONS_1_CFG_REG (USBIN_BASE + 0x62) +#define HVDCP_AUTH_ALG_EN_CFG_BIT BIT(6) +#define HVDCP_AUTONOMOUS_MODE_EN_CFG_BIT BIT(5) +#define BC1P2_SRC_DETECT_BIT BIT(3) +#define HVDCP_EN_BIT BIT(2) + +#define USBIN_OPTIONS_2_CFG_REG (USBIN_BASE + 0x63) +#define DCD_TIMEOUT_SEL_BIT BIT(5) +#define FLOAT_OPTIONS_MASK GENMASK(2, 0) +#define FLOAT_DIS_CHGING_CFG_BIT BIT(2) +#define SUSPEND_FLOAT_CFG_BIT BIT(1) +#define FORCE_FLOAT_SDP_CFG_BIT BIT(0) + +#define USBIN_LOAD_CFG_REG (USBIN_BASE + 0x65) +#define ICL_OVERRIDE_AFTER_APSD_BIT BIT(4) +#define USBIN_AICL_STEP_TIMING_SEL_MASK GENMASK(3, 2) +#define USBIN_IN_COLLAPSE_GF_SEL_MASK GENMASK(1, 0) + +#define USBIN_ICL_OPTIONS_REG (USBIN_BASE + 0x66) +#define CFG_USB3P0_SEL_BIT BIT(2) +#define USB51_MODE_BIT BIT(1) +#define USBIN_MODE_CHG_BIT BIT(0) + +#define USBIN_CURRENT_LIMIT_CFG_REG (USBIN_BASE + 0x70) + +#define USBIN_AICL_OPTIONS_CFG_REG (USBIN_BASE + 0x80) +#define SUSPEND_ON_COLLAPSE_USBIN_BIT BIT(7) +#define USBIN_AICL_PERIODIC_RERUN_EN_BIT BIT(4) +#define USBIN_AICL_ADC_EN_BIT BIT(3) +#define USBIN_AICL_EN_BIT BIT(2) + +#define USB_ENG_SSUPPLY_USB2_REG (USBIN_BASE + 0xC0) +#define ENG_SSUPPLY_12V_OV_OPT_BIT BIT(1) + +#define USBIN_5V_AICL_THRESHOLD_REG (USBIN_BASE + 0x81) +#define USBIN_CONT_AICL_THRESHOLD_REG (USBIN_BASE + 0x84) +/******************************** + * DCIN Peripheral Registers * + ********************************/ + +/* DCIN Interrupt Bits */ +#define DCIN_PLUGIN_RT_STS_BIT BIT(4) + +#define DCIN_CMD_IL_REG (DCIN_BASE + 0x40) +#define DCIN_SUSPEND_BIT BIT(0) +#define DCIN_EN_OVERRIDE_BIT BIT(1) +#define DCIN_EN_MASK GENMASK(2, 1) + +#define DCIN_CMD_PON_REG (DCIN_BASE + 0x45) +#define DCIN_PON_BIT BIT(0) +#define MID_CHG_BIT BIT(1) + +#define DCIN_LOAD_CFG_REG (DCIN_BASE + 0x65) +#define INPUT_MISS_POLL_EN_BIT BIT(5) + +/******************************** + * TYPEC Peripheral Registers * + ********************************/ +#define TYPE_C_SNK_STATUS_REG (TYPEC_BASE + 0x06) +#define DETECTED_SRC_TYPE_MASK GENMASK(6, 0) +#define SNK_DAM_500MA_BIT BIT(6) +#define SNK_DAM_1500MA_BIT BIT(5) +#define SNK_DAM_3000MA_BIT BIT(4) +#define SNK_RP_STD_BIT BIT(3) +#define SNK_RP_1P5_BIT BIT(2) +#define SNK_RP_3P0_BIT BIT(1) +#define SNK_RP_SHORT_BIT BIT(0) + +#define TYPE_C_SRC_STATUS_REG (TYPEC_BASE + 0x08) +#define DETECTED_SNK_TYPE_MASK GENMASK(4, 0) +#define SRC_HIGH_BATT_BIT BIT(5) +#define SRC_DEBUG_ACCESS_BIT BIT(4) +#define SRC_RD_OPEN_BIT BIT(3) +#define SRC_RD_RA_VCONN_BIT BIT(2) +#define SRC_RA_OPEN_BIT BIT(1) +#define AUDIO_ACCESS_RA_RA_BIT BIT(0) + +#define TYPE_C_STATE_MACHINE_STATUS_REG (TYPEC_BASE + 0x09) +#define TYPEC_ATTACH_DETACH_STATE_BIT BIT(5) + +#define TYPE_C_MISC_STATUS_REG (TYPEC_BASE + 0x0B) +#define TYPEC_WATER_DETECTION_STATUS_BIT BIT(7) +#define SNK_SRC_MODE_BIT BIT(6) +#define TYPEC_VBUS_ERROR_STATUS_BIT BIT(4) +#define TYPEC_TCCDEBOUNCE_DONE_STATUS_BIT BIT(3) +#define CC_ORIENTATION_BIT BIT(1) +#define CC_ATTACHED_BIT BIT(0) + +#define LEGACY_CABLE_STATUS_REG (TYPEC_BASE + 0x0D) +#define TYPEC_LEGACY_CABLE_STATUS_BIT BIT(1) +#define TYPEC_NONCOMP_LEGACY_CABLE_STATUS_BIT BIT(0) + +#define TYPEC_U_USB_STATUS_REG (TYPEC_BASE + 0x0F) +#define U_USB_GROUND_NOVBUS_BIT BIT(6) +#define U_USB_GROUND_BIT BIT(4) +#define U_USB_FMB1_BIT BIT(3) +#define U_USB_FLOAT1_BIT BIT(2) +#define U_USB_FMB2_BIT BIT(1) +#define U_USB_FLOAT2_BIT BIT(0) + +#define TYPE_C_MODE_CFG_REG (TYPEC_BASE + 0x44) +#define TYPEC_TRY_MODE_MASK GENMASK(4, 3) +#define EN_TRY_SNK_BIT BIT(4) +#define EN_TRY_SRC_BIT BIT(3) +#define TYPEC_POWER_ROLE_CMD_MASK GENMASK(2, 0) +#define EN_SRC_ONLY_BIT BIT(2) +#define EN_SNK_ONLY_BIT BIT(1) +#define TYPEC_DISABLE_CMD_BIT BIT(0) + +#define TYPE_C_VCONN_CONTROL_REG (TYPEC_BASE + 0x46) +#define VCONN_EN_ORIENTATION_BIT BIT(2) +#define VCONN_EN_VALUE_BIT BIT(1) +#define VCONN_EN_SRC_BIT BIT(0) + +#define TYPE_C_CCOUT_CONTROL_REG (TYPEC_BASE + 0x48) +#define TYPEC_CCOUT_BUFFER_EN_BIT BIT(2) +#define TYPEC_CCOUT_VALUE_BIT BIT(1) +#define TYPEC_CCOUT_SRC_BIT BIT(0) + +#define DEBUG_ACCESS_SRC_CFG_REG (TYPEC_BASE + 0x4C) +#define EN_UNORIENTED_DEBUG_ACCESS_SRC_BIT BIT(0) + +#define TYPE_C_CRUDE_SENSOR_CFG_REG (TYPEC_BASE + 0x4e) +#define EN_SRC_CRUDE_SENSOR_BIT BIT(1) +#define EN_SNK_CRUDE_SENSOR_BIT BIT(0) + +#define TYPE_C_EXIT_STATE_CFG_REG (TYPEC_BASE + 0x50) +#define BYPASS_VSAFE0V_DURING_ROLE_SWAP_BIT BIT(3) +#define SEL_SRC_UPPER_REF_BIT BIT(2) +#define EXIT_SNK_BASED_ON_CC_BIT BIT(0) + +#define TYPE_C_CURRSRC_CFG_REG (TYPEC_BASE + 0x52) +#define TYPEC_SRC_RP_SEL_MASK GENMASK(1, 0) +enum { + TYPEC_SRC_RP_STD, + TYPEC_SRC_RP_1P5A, + TYPEC_SRC_RP_3A, + TYPEC_SRC_RP_3A_DUPLICATE, + TYPEC_SRC_RP_MAX_ELEMENTS +}; + +#define TYPE_C_INTERRUPT_EN_CFG_1_REG (TYPEC_BASE + 0x5E) +#define TYPEC_LEGACY_CABLE_INT_EN_BIT BIT(7) +#define TYPEC_NONCOMPLIANT_LEGACY_CABLE_INT_EN_BIT BIT(6) +#define TYPEC_TRYSOURCE_DETECT_INT_EN_BIT BIT(5) +#define TYPEC_TRYSINK_DETECT_INT_EN_BIT BIT(4) +#define TYPEC_CCOUT_DETACH_INT_EN_BIT BIT(3) +#define TYPEC_CCOUT_ATTACH_INT_EN_BIT BIT(2) +#define TYPEC_VBUS_DEASSERT_INT_EN_BIT BIT(1) +#define TYPEC_VBUS_ASSERT_INT_EN_BIT BIT(0) + +#define TYPE_C_INTERRUPT_EN_CFG_2_REG (TYPEC_BASE + 0x60) +#define TYPEC_SRC_BATT_HPWR_INT_EN_BIT BIT(6) +#define MICRO_USB_STATE_CHANGE_INT_EN_BIT BIT(5) +#define TYPEC_STATE_MACHINE_CHANGE_INT_EN_BIT BIT(4) +#define TYPEC_DEBUG_ACCESS_DETECT_INT_EN_BIT BIT(3) +#define TYPEC_WATER_DETECTION_INT_EN_BIT BIT(2) +#define TYPEC_VBUS_ERROR_INT_EN_BIT BIT(1) +#define TYPEC_DEBOUNCE_DONE_INT_EN_BIT BIT(0) + +#define TYPE_C_DEBOUNCE_OPTION_REG (TYPEC_BASE + 0x62) +#define REDUCE_TCCDEBOUNCE_TO_2MS_BIT BIT(2) + +#define TYPE_C_SBU_CFG_REG (TYPEC_BASE + 0x6A) +#define SEL_SBU1_ISRC_VAL 0x04 +#define SEL_SBU2_ISRC_VAL 0x01 + +#define TYPEC_U_USB_CFG_REG (TYPEC_BASE + 0x70) +#define EN_MICRO_USB_FACTORY_MODE_BIT BIT(1) +#define EN_MICRO_USB_MODE_BIT BIT(0) + +#define PMI632_TYPEC_U_USB_WATER_PROTECTION_CFG_REG (TYPEC_BASE + 0x72) +#define TYPEC_U_USB_WATER_PROTECTION_CFG_REG (TYPEC_BASE + 0x73) +#define EN_MICRO_USB_WATER_PROTECTION_BIT BIT(4) +#define MICRO_USB_DETECTION_ON_TIME_CFG_MASK GENMASK(3, 2) +#define MICRO_USB_DETECTION_PERIOD_CFG_MASK GENMASK(1, 0) + +#define PMI632_TYPEC_MICRO_USB_MODE_REG (TYPEC_BASE + 0x73) +#define MICRO_USB_MODE_ONLY_BIT BIT(0) +/******************************** + * MISC Peripheral Registers * + ********************************/ +#define TEMP_RANGE_STATUS_REG (MISC_BASE + 0x06) +#define THERM_REG_ACTIVE_BIT BIT(6) +#define TLIM_BIT BIT(5) +#define TEMP_RANGE_MASK GENMASK(4, 1) +#define ALERT_LEVEL_BIT BIT(4) +#define TEMP_ABOVE_RANGE_BIT BIT(3) +#define TEMP_WITHIN_RANGE_BIT BIT(2) +#define TEMP_BELOW_RANGE_BIT BIT(1) +#define THERMREG_DISABLED_BIT BIT(0) + +#define DIE_TEMP_STATUS_REG (MISC_BASE + 0x07) +#define DIE_TEMP_SHDN_BIT BIT(3) +#define DIE_TEMP_RST_BIT BIT(2) +#define DIE_TEMP_UB_BIT BIT(1) +#define DIE_TEMP_LB_BIT BIT(0) + +#define SKIN_TEMP_STATUS_REG (MISC_BASE + 0x08) +#define SKIN_TEMP_SHDN_BIT BIT(3) +#define SKIN_TEMP_RST_BIT BIT(2) +#define SKIN_TEMP_UB_BIT BIT(1) +#define SKIN_TEMP_LB_BIT BIT(0) + +#define CONNECTOR_TEMP_STATUS_REG (MISC_BASE + 0x09) +#define CONNECTOR_TEMP_SHDN_BIT BIT(3) +#define CONNECTOR_TEMP_RST_BIT BIT(2) +#define CONNECTOR_TEMP_UB_BIT BIT(1) +#define CONNECTOR_TEMP_LB_BIT BIT(0) + +#define SMB_TEMP_STATUS_REG (MISC_BASE + 0x0A) +#define SMB_TEMP_SHDN_BIT BIT(3) +#define SMB_TEMP_RST_BIT BIT(2) +#define SMB_TEMP_UB_BIT BIT(1) +#define SMB_TEMP_LB_BIT BIT(0) + +#define BARK_BITE_WDOG_PET_REG (MISC_BASE + 0x43) +#define BARK_BITE_WDOG_PET_BIT BIT(0) + +#define AICL_CMD_REG (MISC_BASE + 0x44) +#define RESTART_AICL_BIT BIT(1) +#define RERUN_AICL_BIT BIT(0) + +#define MISC_SMB_EN_CMD_REG (MISC_BASE + 0x48) +#define SMB_EN_OVERRIDE_VALUE_BIT BIT(4) +#define SMB_EN_OVERRIDE_BIT BIT(3) +#define EN_STAT_CMD_BIT BIT(2) +#define EN_CP_FPF_CMD_BIT BIT(1) +#define EN_CP_CMD_BIT BIT(0) + +#define WD_CFG_REG (MISC_BASE + 0x51) +#define WATCHDOG_TRIGGER_AFP_EN_BIT BIT(7) +#define BARK_WDOG_INT_EN_BIT BIT(6) +#define WDOG_TIMER_EN_ON_PLUGIN_BIT BIT(1) + +#define SNARL_BARK_BITE_WD_CFG_REG (MISC_BASE + 0x53) +#define BITE_WDOG_DISABLE_CHARGING_CFG_BIT BIT(7) +#define SNARL_WDOG_TIMEOUT_MASK GENMASK(6, 4) +#define SNARL_WDOG_TIMEOUT_SHIFT 4 +#define SNARL_WDOG_TMOUT_62P5MS 0x00 +#define SNARL_WDOG_TMOUT_1S 0x40 +#define SNARL_WDOG_TMOUT_8S 0x70 +#define BARK_WDOG_TIMEOUT_MASK GENMASK(3, 2) +#define BARK_WDOG_TIMEOUT_SHIFT 2 +#define BITE_WDOG_TIMEOUT_MASK GENMASK(1, 0) +#define BITE_WDOG_TIMEOUT_8S 0x3 +#define MIN_WD_BARK_TIME 16 + +#define AICL_RERUN_TIME_CFG_REG (MISC_BASE + 0x61) +#define AICL_RERUN_TIME_12S_VAL 0x01 + +#define MISC_THERMREG_SRC_CFG_REG (MISC_BASE + 0x70) +#define THERMREG_SW_ICL_ADJUST_BIT BIT(7) +#define DIE_ADC_SEL_BIT BIT(6) +#define THERMREG_SMB_ADC_SRC_EN_BIT BIT(5) +#define THERMREG_CONNECTOR_ADC_SRC_EN_BIT BIT(4) +#define SKIN_ADC_CFG_BIT BIT(3) +#define THERMREG_SKIN_ADC_SRC_EN_BIT BIT(2) +#define THERMREG_DIE_ADC_SRC_EN_BIT BIT(1) +#define THERMREG_DIE_CMP_SRC_EN_BIT BIT(0) + +#define MISC_SMB_CFG_REG (MISC_BASE + 0x90) +#define SMB_EN_SEL_BIT BIT(4) +#define CP_EN_POLARITY_CFG_BIT BIT(3) +#define STAT_POLARITY_CFG_BIT BIT(2) +#define STAT_FUNCTION_CFG_BIT BIT(1) +#define STAT_IRQ_PULSING_EN_BIT BIT(0) + +#define DIE_REG_H_THRESHOLD_MSB_REG (MISC_BASE + 0xA0) + +#define SMB_REG_H_THRESHOLD_MSB_REG (MISC_BASE + 0XBC) + +/* SDAM regs */ +#define MISC_PBS_RT_STS_REG (MISC_PBS_BASE + 0x10) +#define PULSE_SKIP_IRQ_BIT BIT(4) + +#define SDAM_QC_DET_STATUS_REG 0x58 +#define SDAM_QC_ADC_LSB_REG 0x54 + +#endif /* __SMB5_CHARGER_REG_H */ diff --git a/drivers/power/supply/qcom/step-chg-jeita.c b/drivers/power/supply/qcom/step-chg-jeita.c new file mode 100644 index 000000000000..e05ab75f459a --- /dev/null +++ b/drivers/power/supply/qcom/step-chg-jeita.c @@ -0,0 +1,914 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2017-2020 The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "QCOM-STEPCHG: %s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include "step-chg-jeita.h" +#include "battery-profile-loader.h" + +#define STEP_CHG_VOTER "STEP_CHG_VOTER" +#define JEITA_VOTER "JEITA_VOTER" + +#define is_between(left, right, value) \ + (((left) >= (right) && (left) >= (value) \ + && (value) >= (right)) \ + || ((left) <= (right) && (left) <= (value) \ + && (value) <= (right))) + +struct step_chg_cfg { + struct step_chg_jeita_param param; + struct range_data fcc_cfg[MAX_STEP_CHG_ENTRIES]; +}; + +struct jeita_fcc_cfg { + struct step_chg_jeita_param param; + struct range_data fcc_cfg[MAX_STEP_CHG_ENTRIES]; +}; + +struct jeita_fv_cfg { + struct step_chg_jeita_param param; + struct range_data fv_cfg[MAX_STEP_CHG_ENTRIES]; +}; + +struct step_chg_info { + struct device *dev; + ktime_t step_last_update_time; + ktime_t jeita_last_update_time; + bool step_chg_enable; + bool sw_jeita_enable; + bool jeita_arb_en; + bool config_is_read; + bool step_chg_cfg_valid; + bool sw_jeita_cfg_valid; + bool soc_based_step_chg; + bool ocv_based_step_chg; + bool vbat_avg_based_step_chg; + bool batt_missing; + bool taper_fcc; + int jeita_fcc_index; + int jeita_fv_index; + int step_index; + int get_config_retry_count; + + struct step_chg_cfg *step_chg_config; + struct jeita_fcc_cfg *jeita_fcc_config; + struct jeita_fv_cfg *jeita_fv_config; + + struct votable *fcc_votable; + struct votable *fv_votable; + struct votable *usb_icl_votable; + struct wakeup_source *step_chg_ws; + struct power_supply *batt_psy; + struct power_supply *bms_psy; + struct power_supply *usb_psy; + struct power_supply *dc_psy; + struct delayed_work status_change_work; + struct delayed_work get_config_work; + struct notifier_block nb; +}; + +static struct step_chg_info *the_chip; + +#define STEP_CHG_HYSTERISIS_DELAY_US 5000000 /* 5 secs */ + +#define BATT_HOT_DECIDEGREE_MAX 600 +#define GET_CONFIG_DELAY_MS 2000 +#define GET_CONFIG_RETRY_COUNT 50 +#define WAIT_BATT_ID_READY_MS 200 + +static bool is_batt_available(struct step_chg_info *chip) +{ + if (!chip->batt_psy) + chip->batt_psy = power_supply_get_by_name("battery"); + + if (!chip->batt_psy) + return false; + + return true; +} + +static bool is_bms_available(struct step_chg_info *chip) +{ + if (!chip->bms_psy) + chip->bms_psy = power_supply_get_by_name("bms"); + + if (!chip->bms_psy) + return false; + + return true; +} + +static bool is_usb_available(struct step_chg_info *chip) +{ + if (!chip->usb_psy) + chip->usb_psy = power_supply_get_by_name("usb"); + + if (!chip->usb_psy) + return false; + + return true; +} + +static bool is_input_present(struct step_chg_info *chip) +{ + int rc = 0, input_present = 0; + union power_supply_propval pval = {0, }; + + if (!chip->usb_psy) + chip->usb_psy = power_supply_get_by_name("usb"); + if (chip->usb_psy) { + rc = power_supply_get_property(chip->usb_psy, + POWER_SUPPLY_PROP_PRESENT, &pval); + if (rc < 0) + pr_err("Couldn't read USB Present status, rc=%d\n", rc); + else + input_present |= pval.intval; + } + + if (!chip->dc_psy) + chip->dc_psy = power_supply_get_by_name("dc"); + if (chip->dc_psy) { + rc = power_supply_get_property(chip->dc_psy, + POWER_SUPPLY_PROP_PRESENT, &pval); + if (rc < 0) + pr_err("Couldn't read DC Present status, rc=%d\n", rc); + else + input_present |= pval.intval; + } + + if (input_present) + return true; + + return false; +} + +int read_range_data_from_node(struct device_node *node, + const char *prop_str, struct range_data *ranges, + int max_threshold, u32 max_value) +{ + int rc = 0, i, length, per_tuple_length, tuples; + + if (!node || !prop_str || !ranges) { + pr_err("Invalid parameters passed\n"); + return -EINVAL; + } + + rc = of_property_count_elems_of_size(node, prop_str, sizeof(u32)); + if (rc < 0) { + pr_err("Count %s failed, rc=%d\n", prop_str, rc); + return rc; + } + + length = rc; + per_tuple_length = sizeof(struct range_data) / sizeof(u32); + if (length % per_tuple_length) { + pr_err("%s length (%d) should be multiple of %d\n", + prop_str, length, per_tuple_length); + return -EINVAL; + } + tuples = length / per_tuple_length; + + if (tuples > MAX_STEP_CHG_ENTRIES) { + pr_err("too many entries(%d), only %d allowed\n", + tuples, MAX_STEP_CHG_ENTRIES); + return -EINVAL; + } + + rc = of_property_read_u32_array(node, prop_str, + (u32 *)ranges, length); + if (rc) { + pr_err("Read %s failed, rc=%d\n", prop_str, rc); + return rc; + } + + for (i = 0; i < tuples; i++) { + if (ranges[i].low_threshold > + ranges[i].high_threshold) { + pr_err("%s thresholds should be in ascendant ranges\n", + prop_str); + rc = -EINVAL; + goto clean; + } + + if (i != 0) { + if (ranges[i - 1].high_threshold > + ranges[i].low_threshold) { + pr_err("%s thresholds should be in ascendant ranges\n", + prop_str); + rc = -EINVAL; + goto clean; + } + } + + if (ranges[i].low_threshold > max_threshold) + ranges[i].low_threshold = max_threshold; + if (ranges[i].high_threshold > max_threshold) + ranges[i].high_threshold = max_threshold; + if (ranges[i].value > max_value) + ranges[i].value = max_value; + } + + return rc; +clean: + memset(ranges, 0, tuples * sizeof(struct range_data)); + return rc; +} +EXPORT_SYMBOL(read_range_data_from_node); + +static int get_step_chg_jeita_setting_from_profile(struct step_chg_info *chip) +{ + struct device_node *batt_node, *profile_node; + u32 max_fv_uv, max_fcc_ma; + const char *batt_type_str; + const __be32 *handle; + int batt_id_ohms, rc; + union power_supply_propval prop = {0, }; + + handle = of_get_property(chip->dev->of_node, + "qcom,battery-data", NULL); + if (!handle) { + pr_debug("ignore getting sw-jeita/step charging settings from profile\n"); + return 0; + } + + batt_node = of_find_node_by_phandle(be32_to_cpup(handle)); + if (!batt_node) { + pr_err("Get battery data node failed\n"); + return -EINVAL; + } + + if (!is_bms_available(chip)) + return -ENODEV; + + power_supply_get_property(chip->bms_psy, + POWER_SUPPLY_PROP_RESISTANCE_ID, &prop); + batt_id_ohms = prop.intval; + + /* bms_psy has not yet read the batt_id */ + if (batt_id_ohms < 0) + return -EBUSY; + + profile_node = of_batterydata_get_best_profile(batt_node, + batt_id_ohms / 1000, NULL); + if (IS_ERR(profile_node)) + return PTR_ERR(profile_node); + + if (!profile_node) { + pr_err("Couldn't find profile\n"); + return -ENODATA; + } + + rc = of_property_read_string(profile_node, "qcom,battery-type", + &batt_type_str); + if (rc < 0) { + pr_err("battery type unavailable, rc:%d\n", rc); + return rc; + } + pr_debug("battery: %s detected, getting sw-jeita/step charging settings\n", + batt_type_str); + + rc = of_property_read_u32(profile_node, "qcom,max-voltage-uv", + &max_fv_uv); + if (rc < 0) { + pr_err("max-voltage_uv reading failed, rc=%d\n", rc); + return rc; + } + + rc = of_property_read_u32(profile_node, "qcom,fastchg-current-ma", + &max_fcc_ma); + if (rc < 0) { + pr_err("max-fastchg-current-ma reading failed, rc=%d\n", rc); + return rc; + } + + chip->taper_fcc = of_property_read_bool(profile_node, "qcom,taper-fcc"); + + chip->soc_based_step_chg = + of_property_read_bool(profile_node, "qcom,soc-based-step-chg"); + if (chip->soc_based_step_chg) { + chip->step_chg_config->param.psy_prop = + POWER_SUPPLY_PROP_CAPACITY; + chip->step_chg_config->param.prop_name = "SOC"; + chip->step_chg_config->param.hysteresis = 0; + } + + chip->ocv_based_step_chg = + of_property_read_bool(profile_node, "qcom,ocv-based-step-chg"); + if (chip->ocv_based_step_chg) { + chip->step_chg_config->param.psy_prop = + POWER_SUPPLY_PROP_VOLTAGE_OCV; + chip->step_chg_config->param.prop_name = "OCV"; + chip->step_chg_config->param.hysteresis = 0; + chip->step_chg_config->param.use_bms = true; + } + + chip->vbat_avg_based_step_chg = + of_property_read_bool(profile_node, + "qcom,vbat-avg-based-step-chg"); + if (chip->vbat_avg_based_step_chg) { + chip->step_chg_config->param.psy_prop = + POWER_SUPPLY_PROP_VOLTAGE_AVG; + chip->step_chg_config->param.prop_name = "VBAT_AVG"; + chip->step_chg_config->param.hysteresis = 0; + chip->step_chg_config->param.use_bms = true; + } + + chip->step_chg_cfg_valid = true; + rc = read_range_data_from_node(profile_node, + "qcom,step-chg-ranges", + chip->step_chg_config->fcc_cfg, + chip->soc_based_step_chg ? 100 : max_fv_uv, + max_fcc_ma * 1000); + if (rc < 0) { + pr_debug("Read qcom,step-chg-ranges failed from battery profile, rc=%d\n", + rc); + chip->step_chg_cfg_valid = false; + } + + chip->sw_jeita_cfg_valid = true; + rc = read_range_data_from_node(profile_node, + "qcom,jeita-fcc-ranges", + chip->jeita_fcc_config->fcc_cfg, + BATT_HOT_DECIDEGREE_MAX, max_fcc_ma * 1000); + if (rc < 0) { + pr_debug("Read qcom,jeita-fcc-ranges failed from battery profile, rc=%d\n", + rc); + chip->sw_jeita_cfg_valid = false; + } + + rc = read_range_data_from_node(profile_node, + "qcom,jeita-fv-ranges", + chip->jeita_fv_config->fv_cfg, + BATT_HOT_DECIDEGREE_MAX, max_fv_uv); + if (rc < 0) { + pr_debug("Read qcom,jeita-fv-ranges failed from battery profile, rc=%d\n", + rc); + chip->sw_jeita_cfg_valid = false; + } + + return rc; +} + +static void get_config_work(struct work_struct *work) +{ + struct step_chg_info *chip = container_of(work, + struct step_chg_info, get_config_work.work); + int i, rc; + + chip->config_is_read = false; + rc = get_step_chg_jeita_setting_from_profile(chip); + + if (rc < 0) { + if (rc == -ENODEV || rc == -EBUSY) { + if (chip->get_config_retry_count++ + < GET_CONFIG_RETRY_COUNT) { + pr_debug("bms_psy is not ready, retry: %d\n", + chip->get_config_retry_count); + goto reschedule; + } + } + } + + chip->config_is_read = true; + + for (i = 0; i < MAX_STEP_CHG_ENTRIES; i++) + pr_debug("step-chg-cfg: %duV(SoC) ~ %duV(SoC), %duA\n", + chip->step_chg_config->fcc_cfg[i].low_threshold, + chip->step_chg_config->fcc_cfg[i].high_threshold, + chip->step_chg_config->fcc_cfg[i].value); + for (i = 0; i < MAX_STEP_CHG_ENTRIES; i++) + pr_debug("jeita-fcc-cfg: %ddecidegree ~ %ddecidegre, %duA\n", + chip->jeita_fcc_config->fcc_cfg[i].low_threshold, + chip->jeita_fcc_config->fcc_cfg[i].high_threshold, + chip->jeita_fcc_config->fcc_cfg[i].value); + for (i = 0; i < MAX_STEP_CHG_ENTRIES; i++) + pr_debug("jeita-fv-cfg: %ddecidegree ~ %ddecidegre, %duV\n", + chip->jeita_fv_config->fv_cfg[i].low_threshold, + chip->jeita_fv_config->fv_cfg[i].high_threshold, + chip->jeita_fv_config->fv_cfg[i].value); + + return; + +reschedule: + schedule_delayed_work(&chip->get_config_work, + msecs_to_jiffies(GET_CONFIG_DELAY_MS)); + +} + +static int get_val(struct range_data *range, int hysteresis, int current_index, + int threshold, + int *new_index, int *val) +{ + int i; + + *new_index = -EINVAL; + + /* + * If the threshold is lesser than the minimum allowed range, + * return -ENODATA. + */ + if (threshold < range[0].low_threshold) + return -ENODATA; + + /* First try to find the matching index without hysteresis */ + for (i = 0; i < MAX_STEP_CHG_ENTRIES; i++) { + if (!range[i].high_threshold && !range[i].low_threshold) { + /* First invalid table entry; exit loop */ + break; + } + + if (is_between(range[i].low_threshold, + range[i].high_threshold, threshold)) { + *new_index = i; + *val = range[i].value; + break; + } + } + + /* + * If nothing was found, the threshold exceeds the max range for sure + * as the other case where it is lesser than the min range is handled + * at the very beginning of this function. Therefore, clip it to the + * max allowed range value, which is the one corresponding to the last + * valid entry in the battery profile data array. + */ + if (*new_index == -EINVAL) { + if (i == 0) { + /* Battery profile data array is completely invalid */ + return -ENODATA; + } + + *new_index = (i - 1); + *val = range[*new_index].value; + } + + /* + * If we don't have a current_index return this + * newfound value. There is no hysterisis from out of range + * to in range transition + */ + if (current_index == -EINVAL) + return 0; + + /* + * Check for hysteresis if it in the neighbourhood + * of our current index. + */ + if (*new_index == current_index + 1) { + if (threshold < range[*new_index].low_threshold + hysteresis) { + /* + * Stay in the current index, threshold is not higher + * by hysteresis amount + */ + *new_index = current_index; + *val = range[current_index].value; + } + } else if (*new_index == current_index - 1) { + if (threshold > range[*new_index].high_threshold - hysteresis) { + /* + * stay in the current index, threshold is not lower + * by hysteresis amount + */ + *new_index = current_index; + *val = range[current_index].value; + } + } + return 0; +} + +#define TAPERED_STEP_CHG_FCC_REDUCTION_STEP_MA 50000 /* 50 mA */ +static void taper_fcc_step_chg(struct step_chg_info *chip, int index, + int current_voltage) +{ + u32 current_fcc, target_fcc; + + if (index < 0) { + pr_err("Invalid STEP CHG index\n"); + return; + } + + current_fcc = get_effective_result(chip->fcc_votable); + target_fcc = chip->step_chg_config->fcc_cfg[index].value; + + if (index == 0) { + vote(chip->fcc_votable, STEP_CHG_VOTER, true, target_fcc); + } else if (current_voltage > + (chip->step_chg_config->fcc_cfg[index - 1].high_threshold + + chip->step_chg_config->param.hysteresis)) { + /* + * Ramp down FCC in pre-configured steps till the current index + * FCC configuration is reached, whenever the step charging + * control parameter exceeds the high threshold of previous + * step charging index configuration. + */ + vote(chip->fcc_votable, STEP_CHG_VOTER, true, max(target_fcc, + current_fcc - TAPERED_STEP_CHG_FCC_REDUCTION_STEP_MA)); + } else if ((current_fcc > + chip->step_chg_config->fcc_cfg[index - 1].value) && + (current_voltage > + chip->step_chg_config->fcc_cfg[index - 1].low_threshold + + chip->step_chg_config->param.hysteresis)) { + /* + * In case the step charging index switch to the next higher + * index without FCCs saturation for the previous index, ramp + * down FCC till previous index FCC configuration is reached. + */ + vote(chip->fcc_votable, STEP_CHG_VOTER, true, + max(chip->step_chg_config->fcc_cfg[index - 1].value, + current_fcc - TAPERED_STEP_CHG_FCC_REDUCTION_STEP_MA)); + } +} + +static int handle_step_chg_config(struct step_chg_info *chip) +{ + union power_supply_propval pval = {0, }; + int rc = 0, fcc_ua = 0, current_index; + u64 elapsed_us; + + elapsed_us = ktime_us_delta(ktime_get(), chip->step_last_update_time); + /* skip processing, event too early */ + if (elapsed_us < STEP_CHG_HYSTERISIS_DELAY_US) + return 0; + + rc = power_supply_get_property(chip->batt_psy, + POWER_SUPPLY_PROP_STEP_CHARGING_ENABLED, &pval); + if (rc < 0) + chip->step_chg_enable = false; + else + chip->step_chg_enable = pval.intval; + + if (!chip->step_chg_enable || !chip->step_chg_cfg_valid) { + if (chip->fcc_votable) + vote(chip->fcc_votable, STEP_CHG_VOTER, false, 0); + goto update_time; + } + + if (chip->step_chg_config->param.use_bms) + rc = power_supply_get_property(chip->bms_psy, + chip->step_chg_config->param.psy_prop, &pval); + else + rc = power_supply_get_property(chip->batt_psy, + chip->step_chg_config->param.psy_prop, &pval); + + if (rc < 0) { + pr_err("Couldn't read %s property rc=%d\n", + chip->step_chg_config->param.prop_name, rc); + return rc; + } + + current_index = chip->step_index; + rc = get_val(chip->step_chg_config->fcc_cfg, + chip->step_chg_config->param.hysteresis, + chip->step_index, + pval.intval, + &chip->step_index, + &fcc_ua); + if (rc < 0) { + /* remove the vote if no step-based fcc is found */ + if (chip->fcc_votable) + vote(chip->fcc_votable, STEP_CHG_VOTER, false, 0); + goto update_time; + } + + /* Do not drop step-chg index, if input supply is present */ + if (is_input_present(chip)) { + if (chip->step_index < current_index) + chip->step_index = current_index; + } else { + chip->step_index = 0; + } + + if (!chip->fcc_votable) + chip->fcc_votable = find_votable("FCC"); + if (!chip->fcc_votable) + return -EINVAL; + + if (chip->taper_fcc) { + taper_fcc_step_chg(chip, chip->step_index, pval.intval); + } else { + fcc_ua = chip->step_chg_config->fcc_cfg[chip->step_index].value; + vote(chip->fcc_votable, STEP_CHG_VOTER, true, fcc_ua); + } + + pr_debug("%s = %d Step-FCC = %duA taper-fcc: %d\n", + chip->step_chg_config->param.prop_name, pval.intval, + get_client_vote(chip->fcc_votable, STEP_CHG_VOTER), + chip->taper_fcc); + +update_time: + chip->step_last_update_time = ktime_get(); + return 0; +} + +#define JEITA_SUSPEND_HYST_UV 50000 +static int handle_jeita(struct step_chg_info *chip) +{ + union power_supply_propval pval = {0, }; + int rc = 0, fcc_ua = 0, fv_uv = 0; + u64 elapsed_us; + + rc = power_supply_get_property(chip->batt_psy, + POWER_SUPPLY_PROP_SW_JEITA_ENABLED, &pval); + if (rc < 0) + chip->sw_jeita_enable = false; + else + chip->sw_jeita_enable = pval.intval; + + if (!chip->sw_jeita_enable || !chip->sw_jeita_cfg_valid) { + if (chip->fcc_votable) + vote(chip->fcc_votable, JEITA_VOTER, false, 0); + if (chip->fv_votable) + vote(chip->fv_votable, JEITA_VOTER, false, 0); + if (chip->usb_icl_votable) + vote(chip->usb_icl_votable, JEITA_VOTER, false, 0); + return 0; + } + + elapsed_us = ktime_us_delta(ktime_get(), chip->jeita_last_update_time); + /* skip processing, event too early */ + if (elapsed_us < STEP_CHG_HYSTERISIS_DELAY_US) + return 0; + + if (chip->jeita_fcc_config->param.use_bms) + rc = power_supply_get_property(chip->bms_psy, + chip->jeita_fcc_config->param.psy_prop, &pval); + else + rc = power_supply_get_property(chip->batt_psy, + chip->jeita_fcc_config->param.psy_prop, &pval); + + if (rc < 0) { + pr_err("Couldn't read %s property rc=%d\n", + chip->jeita_fcc_config->param.prop_name, rc); + return rc; + } + + rc = get_val(chip->jeita_fcc_config->fcc_cfg, + chip->jeita_fcc_config->param.hysteresis, + chip->jeita_fcc_index, + pval.intval, + &chip->jeita_fcc_index, + &fcc_ua); + if (rc < 0) + fcc_ua = 0; + + if (!chip->fcc_votable) + chip->fcc_votable = find_votable("FCC"); + if (!chip->fcc_votable) + /* changing FCC is a must */ + return -EINVAL; + + vote(chip->fcc_votable, JEITA_VOTER, fcc_ua ? true : false, fcc_ua); + + rc = get_val(chip->jeita_fv_config->fv_cfg, + chip->jeita_fv_config->param.hysteresis, + chip->jeita_fv_index, + pval.intval, + &chip->jeita_fv_index, + &fv_uv); + if (rc < 0) + fv_uv = 0; + + chip->fv_votable = find_votable("FV"); + if (!chip->fv_votable) + goto update_time; + + if (!chip->usb_icl_votable) + chip->usb_icl_votable = find_votable("USB_ICL"); + + if (!chip->usb_icl_votable) + goto set_jeita_fv; + + /* + * If JEITA float voltage is same as max-vfloat of battery then + * skip any further VBAT specific checks. + */ + rc = power_supply_get_property(chip->batt_psy, + POWER_SUPPLY_PROP_VOLTAGE_MAX, &pval); + if (rc || (pval.intval == fv_uv)) { + vote(chip->usb_icl_votable, JEITA_VOTER, false, 0); + goto set_jeita_fv; + } + + /* + * Suspend USB input path if battery voltage is above + * JEITA VFLOAT threshold. + */ + if (chip->jeita_arb_en && fv_uv > 0) { + rc = power_supply_get_property(chip->batt_psy, + POWER_SUPPLY_PROP_VOLTAGE_NOW, &pval); + if (!rc && (pval.intval > fv_uv)) + vote(chip->usb_icl_votable, JEITA_VOTER, true, 0); + else if (pval.intval < (fv_uv - JEITA_SUSPEND_HYST_UV)) + vote(chip->usb_icl_votable, JEITA_VOTER, false, 0); + } + +set_jeita_fv: + vote(chip->fv_votable, JEITA_VOTER, fv_uv ? true : false, fv_uv); + +update_time: + chip->jeita_last_update_time = ktime_get(); + + return 0; +} + +static int handle_battery_insertion(struct step_chg_info *chip) +{ + int rc; + union power_supply_propval pval = {0, }; + + rc = power_supply_get_property(chip->batt_psy, + POWER_SUPPLY_PROP_PRESENT, &pval); + if (rc < 0) { + pr_err("Get battery present status failed, rc=%d\n", rc); + return rc; + } + + if (chip->batt_missing != (!pval.intval)) { + chip->batt_missing = !pval.intval; + pr_debug("battery %s detected\n", + chip->batt_missing ? "removal" : "insertion"); + if (chip->batt_missing) { + chip->step_chg_cfg_valid = false; + chip->sw_jeita_cfg_valid = false; + chip->get_config_retry_count = 0; + } else { + /* + * Get config for the new inserted battery, delay + * to make sure BMS has read out the batt_id. + */ + schedule_delayed_work(&chip->get_config_work, + msecs_to_jiffies(WAIT_BATT_ID_READY_MS)); + } + } + + return rc; +} + +static void status_change_work(struct work_struct *work) +{ + struct step_chg_info *chip = container_of(work, + struct step_chg_info, status_change_work.work); + int rc = 0; + union power_supply_propval prop = {0, }; + + if (!is_batt_available(chip) || !is_bms_available(chip)) + goto exit_work; + + handle_battery_insertion(chip); + + /* skip elapsed_us debounce for handling battery temperature */ + rc = handle_jeita(chip); + if (rc < 0) + pr_err("Couldn't handle sw jeita rc = %d\n", rc); + + rc = handle_step_chg_config(chip); + if (rc < 0) + pr_err("Couldn't handle step rc = %d\n", rc); + + /* Remove stale votes on USB removal */ + if (is_usb_available(chip)) { + prop.intval = 0; + power_supply_get_property(chip->usb_psy, + POWER_SUPPLY_PROP_PRESENT, &prop); + if (!prop.intval) { + if (chip->usb_icl_votable) + vote(chip->usb_icl_votable, JEITA_VOTER, + false, 0); + } + } + +exit_work: + __pm_relax(chip->step_chg_ws); +} + +static int step_chg_notifier_call(struct notifier_block *nb, + unsigned long ev, void *v) +{ + struct power_supply *psy = v; + struct step_chg_info *chip = container_of(nb, struct step_chg_info, nb); + + if (ev != PSY_EVENT_PROP_CHANGED) + return NOTIFY_OK; + + if ((strcmp(psy->desc->name, "battery") == 0) + || (strcmp(psy->desc->name, "usb") == 0)) { + __pm_stay_awake(chip->step_chg_ws); + schedule_delayed_work(&chip->status_change_work, 0); + } + + if ((strcmp(psy->desc->name, "bms") == 0)) { + if (chip->bms_psy == NULL) + chip->bms_psy = psy; + if (!chip->config_is_read) + schedule_delayed_work(&chip->get_config_work, 0); + } + + return NOTIFY_OK; +} + +static int step_chg_register_notifier(struct step_chg_info *chip) +{ + int rc; + + chip->nb.notifier_call = step_chg_notifier_call; + rc = power_supply_reg_notifier(&chip->nb); + if (rc < 0) { + pr_err("Couldn't register psy notifier rc = %d\n", rc); + return rc; + } + + return 0; +} + +int qcom_step_chg_init(struct device *dev, + bool step_chg_enable, bool sw_jeita_enable, bool jeita_arb_en) +{ + int rc; + struct step_chg_info *chip; + + if (the_chip) { + pr_err("Already initialized\n"); + return -EINVAL; + } + + chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->step_chg_ws = wakeup_source_register("qcom-step-chg"); + if (!chip->step_chg_ws) + return -EINVAL; + + chip->dev = dev; + chip->step_chg_enable = step_chg_enable; + chip->sw_jeita_enable = sw_jeita_enable; + chip->jeita_arb_en = jeita_arb_en; + chip->step_index = -EINVAL; + chip->jeita_fcc_index = -EINVAL; + chip->jeita_fv_index = -EINVAL; + + chip->step_chg_config = devm_kzalloc(dev, + sizeof(struct step_chg_cfg), GFP_KERNEL); + if (!chip->step_chg_config) + return -ENOMEM; + + chip->step_chg_config->param.psy_prop = POWER_SUPPLY_PROP_VOLTAGE_NOW; + chip->step_chg_config->param.prop_name = "VBATT"; + chip->step_chg_config->param.hysteresis = 100000; + + chip->jeita_fcc_config = devm_kzalloc(dev, + sizeof(struct jeita_fcc_cfg), GFP_KERNEL); + chip->jeita_fv_config = devm_kzalloc(dev, + sizeof(struct jeita_fv_cfg), GFP_KERNEL); + if (!chip->jeita_fcc_config || !chip->jeita_fv_config) + return -ENOMEM; + + chip->jeita_fcc_config->param.psy_prop = POWER_SUPPLY_PROP_TEMP; + chip->jeita_fcc_config->param.prop_name = "BATT_TEMP"; + chip->jeita_fcc_config->param.hysteresis = 10; + chip->jeita_fv_config->param.psy_prop = POWER_SUPPLY_PROP_TEMP; + chip->jeita_fv_config->param.prop_name = "BATT_TEMP"; + chip->jeita_fv_config->param.hysteresis = 10; + + INIT_DELAYED_WORK(&chip->status_change_work, status_change_work); + INIT_DELAYED_WORK(&chip->get_config_work, get_config_work); + + rc = step_chg_register_notifier(chip); + if (rc < 0) { + pr_err("Couldn't register psy notifier rc = %d\n", rc); + goto release_wakeup_source; + } + + schedule_delayed_work(&chip->get_config_work, + msecs_to_jiffies(GET_CONFIG_DELAY_MS)); + + the_chip = chip; + + return 0; + +release_wakeup_source: + wakeup_source_unregister(chip->step_chg_ws); + return rc; +} + +void qcom_step_chg_deinit(void) +{ + struct step_chg_info *chip = the_chip; + + if (!chip) + return; + + cancel_delayed_work_sync(&chip->status_change_work); + cancel_delayed_work_sync(&chip->get_config_work); + power_supply_unreg_notifier(&chip->nb); + wakeup_source_unregister(chip->step_chg_ws); + the_chip = NULL; +} diff --git a/drivers/power/supply/qcom/step-chg-jeita.h b/drivers/power/supply/qcom/step-chg-jeita.h new file mode 100644 index 000000000000..5c9c128e5f72 --- /dev/null +++ b/drivers/power/supply/qcom/step-chg-jeita.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2017-2020 The Linux Foundation. All rights reserved. + */ + +#ifndef __STEP_CHG_H__ +#define __STEP_CHG_H__ + +#define MAX_STEP_CHG_ENTRIES 8 + +struct step_chg_jeita_param { + u32 psy_prop; + char *prop_name; + int hysteresis; + bool use_bms; +}; + +struct range_data { + int low_threshold; + int high_threshold; + u32 value; +}; + +int qcom_step_chg_init(struct device *dev, + bool step_chg_enable, bool sw_jeita_enable, bool jeita_arb_en); +void qcom_step_chg_deinit(void); +int read_range_data_from_node(struct device_node *node, + const char *prop_str, struct range_data *ranges, + int max_threshold, u32 max_value); +#endif /* __STEP_CHG_H__ */ diff --git a/drivers/power/supply/qcom/storm-watch.c b/drivers/power/supply/qcom/storm-watch.c new file mode 100644 index 000000000000..40abf97e4faf --- /dev/null +++ b/drivers/power/supply/qcom/storm-watch.c @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2016-2017 The Linux Foundation. All rights reserved. + */ + +#include "storm-watch.h" + +/** + * is_storming(): Check if an event is storming + * + * @data: Data for tracking an event storm + * + * The return value will be true if a storm has been detected and + * false if a storm was not detected. + */ +bool is_storming(struct storm_watch *data) +{ + ktime_t curr_kt, delta_kt; + bool is_storming = false; + + if (!data) + return false; + + if (!data->enabled) + return false; + + /* max storm count must be greater than 0 */ + if (data->max_storm_count <= 0) + return false; + + /* the period threshold must be greater than 0ms */ + if (data->storm_period_ms <= 0) + return false; + + mutex_lock(&data->storm_lock); + curr_kt = ktime_get_boottime(); + delta_kt = ktime_sub(curr_kt, data->last_kt); + + if (ktime_to_ms(delta_kt) < data->storm_period_ms) + data->storm_count++; + else + data->storm_count = 0; + + if (data->storm_count > data->max_storm_count) { + is_storming = true; + data->storm_count = 0; + } + + data->last_kt = curr_kt; + mutex_unlock(&data->storm_lock); + return is_storming; +} + +void reset_storm_count(struct storm_watch *data) +{ + mutex_lock(&data->storm_lock); + data->storm_count = 0; + mutex_unlock(&data->storm_lock); +} + +void update_storm_count(struct storm_watch *data, int max_count) +{ + if (!data) + return; + + mutex_lock(&data->storm_lock); + data->max_storm_count = max_count; + mutex_unlock(&data->storm_lock); +} diff --git a/drivers/power/supply/qcom/storm-watch.h b/drivers/power/supply/qcom/storm-watch.h new file mode 100644 index 000000000000..512ef755b45e --- /dev/null +++ b/drivers/power/supply/qcom/storm-watch.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2016-2017, 2020 The Linux Foundation. All rights reserved. + */ + +#ifndef __STORM_WATCH_H +#define __STORM_WATCH_H +#include +#include + +/** + * Data used to track an event storm. + * + * @storm_period_ms: The maximum time interval between two events. If this limit + * is exceeded then the event chain will be broken and removed + * from consideration for a storm. + * @max_storm_count: The number of chained events required to trigger a storm. + * @storm_count: The current number of chained events. + * @last_kt: Kernel time of the last event seen. + * @storm_lock: Mutex lock to protect storm_watch data. + */ +struct storm_watch { + bool enabled; + int storm_period_ms; + int max_storm_count; + int storm_count; + ktime_t last_kt; + struct mutex storm_lock; +}; + +bool is_storming(struct storm_watch *data); +void reset_storm_count(struct storm_watch *data); +void update_storm_count(struct storm_watch *data, int max_count); +#endif