summaryrefslogtreecommitdiff
path: root/drivers/power/qcom-charger/qpnp-smb2.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/power/qcom-charger/qpnp-smb2.c')
-rw-r--r--drivers/power/qcom-charger/qpnp-smb2.c1898
1 files changed, 1898 insertions, 0 deletions
diff --git a/drivers/power/qcom-charger/qpnp-smb2.c b/drivers/power/qcom-charger/qpnp-smb2.c
new file mode 100644
index 000000000000..e73ed2f1d288
--- /dev/null
+++ b/drivers/power/qcom-charger/qpnp-smb2.c
@@ -0,0 +1,1898 @@
+/* Copyright (c) 2016 The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/debugfs.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/power_supply.h>
+#include <linux/interrupt.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/qpnp/qpnp-revid.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/of_regulator.h>
+#include <linux/regulator/machine.h>
+#include "smb-reg.h"
+#include "smb-lib.h"
+#include "storm-watch.h"
+#include "pmic-voter.h"
+
+#define SMB2_DEFAULT_WPWR_UW 8000000
+
+static struct smb_params v1_params = {
+ .fcc = {
+ .name = "fast charge current",
+ .reg = FAST_CHARGE_CURRENT_CFG_REG,
+ .min_u = 0,
+ .max_u = 4500000,
+ .step_u = 25000,
+ },
+ .fv = {
+ .name = "float voltage",
+ .reg = FLOAT_VOLTAGE_CFG_REG,
+ .min_u = 3487500,
+ .max_u = 4920000,
+ .step_u = 7500,
+ },
+ .usb_icl = {
+ .name = "usb input current limit",
+ .reg = USBIN_CURRENT_LIMIT_CFG_REG,
+ .min_u = 0,
+ .max_u = 4800000,
+ .step_u = 25000,
+ },
+ .icl_stat = {
+ .name = "input current limit status",
+ .reg = ICL_STATUS_REG,
+ .min_u = 0,
+ .max_u = 4800000,
+ .step_u = 25000,
+ },
+ .otg_cl = {
+ .name = "usb otg current limit",
+ .reg = OTG_CURRENT_LIMIT_CFG_REG,
+ .min_u = 250000,
+ .max_u = 2000000,
+ .step_u = 250000,
+ },
+ .dc_icl = {
+ .name = "dc input current limit",
+ .reg = DCIN_CURRENT_LIMIT_CFG_REG,
+ .min_u = 0,
+ .max_u = 6000000,
+ .step_u = 25000,
+ },
+ .dc_icl_pt_lv = {
+ .name = "dc icl PT <8V",
+ .reg = ZIN_ICL_PT_REG,
+ .min_u = 0,
+ .max_u = 3000000,
+ .step_u = 25000,
+ },
+ .dc_icl_pt_hv = {
+ .name = "dc icl PT >8V",
+ .reg = ZIN_ICL_PT_HV_REG,
+ .min_u = 0,
+ .max_u = 3000000,
+ .step_u = 25000,
+ },
+ .dc_icl_div2_lv = {
+ .name = "dc icl div2 <5.5V",
+ .reg = ZIN_ICL_LV_REG,
+ .min_u = 0,
+ .max_u = 3000000,
+ .step_u = 25000,
+ },
+ .dc_icl_div2_mid_lv = {
+ .name = "dc icl div2 5.5-6.5V",
+ .reg = ZIN_ICL_MID_LV_REG,
+ .min_u = 0,
+ .max_u = 3000000,
+ .step_u = 25000,
+ },
+ .dc_icl_div2_mid_hv = {
+ .name = "dc icl div2 6.5-8.0V",
+ .reg = ZIN_ICL_MID_HV_REG,
+ .min_u = 0,
+ .max_u = 3000000,
+ .step_u = 25000,
+ },
+ .dc_icl_div2_hv = {
+ .name = "dc icl div2 >8.0V",
+ .reg = ZIN_ICL_HV_REG,
+ .min_u = 0,
+ .max_u = 3000000,
+ .step_u = 25000,
+ },
+ .jeita_cc_comp = {
+ .name = "jeita fcc reduction",
+ .reg = JEITA_CCCOMP_CFG_REG,
+ .min_u = 0,
+ .max_u = 1575000,
+ .step_u = 25000,
+ },
+ .step_soc_threshold[0] = {
+ .name = "step charge soc threshold 1",
+ .reg = STEP_CHG_SOC_OR_BATT_V_TH1_REG,
+ .min_u = 0,
+ .max_u = 100,
+ .step_u = 1,
+ },
+ .step_soc_threshold[1] = {
+ .name = "step charge soc threshold 2",
+ .reg = STEP_CHG_SOC_OR_BATT_V_TH2_REG,
+ .min_u = 0,
+ .max_u = 100,
+ .step_u = 1,
+ },
+ .step_soc_threshold[2] = {
+ .name = "step charge soc threshold 3",
+ .reg = STEP_CHG_SOC_OR_BATT_V_TH3_REG,
+ .min_u = 0,
+ .max_u = 100,
+ .step_u = 1,
+ },
+ .step_soc_threshold[3] = {
+ .name = "step charge soc threshold 4",
+ .reg = STEP_CHG_SOC_OR_BATT_V_TH4_REG,
+ .min_u = 0,
+ .max_u = 100,
+ .step_u = 1,
+ },
+ .step_soc = {
+ .name = "step charge soc",
+ .reg = STEP_CHG_SOC_VBATT_V_REG,
+ .min_u = 0,
+ .max_u = 100,
+ .step_u = 1,
+ .set_proc = smblib_mapping_soc_from_field_value,
+ },
+ .step_cc_delta[0] = {
+ .name = "step charge current delta 1",
+ .reg = STEP_CHG_CURRENT_DELTA1_REG,
+ .min_u = 100000,
+ .max_u = 3200000,
+ .step_u = 100000,
+ .get_proc = smblib_mapping_cc_delta_to_field_value,
+ .set_proc = smblib_mapping_cc_delta_from_field_value,
+ },
+ .step_cc_delta[1] = {
+ .name = "step charge current delta 2",
+ .reg = STEP_CHG_CURRENT_DELTA2_REG,
+ .min_u = 100000,
+ .max_u = 3200000,
+ .step_u = 100000,
+ .get_proc = smblib_mapping_cc_delta_to_field_value,
+ .set_proc = smblib_mapping_cc_delta_from_field_value,
+ },
+ .step_cc_delta[2] = {
+ .name = "step charge current delta 3",
+ .reg = STEP_CHG_CURRENT_DELTA3_REG,
+ .min_u = 100000,
+ .max_u = 3200000,
+ .step_u = 100000,
+ .get_proc = smblib_mapping_cc_delta_to_field_value,
+ .set_proc = smblib_mapping_cc_delta_from_field_value,
+ },
+ .step_cc_delta[3] = {
+ .name = "step charge current delta 4",
+ .reg = STEP_CHG_CURRENT_DELTA4_REG,
+ .min_u = 100000,
+ .max_u = 3200000,
+ .step_u = 100000,
+ .get_proc = smblib_mapping_cc_delta_to_field_value,
+ .set_proc = smblib_mapping_cc_delta_from_field_value,
+ },
+ .step_cc_delta[4] = {
+ .name = "step charge current delta 5",
+ .reg = STEP_CHG_CURRENT_DELTA5_REG,
+ .min_u = 100000,
+ .max_u = 3200000,
+ .step_u = 100000,
+ .get_proc = smblib_mapping_cc_delta_to_field_value,
+ .set_proc = smblib_mapping_cc_delta_from_field_value,
+ },
+ .freq_buck = {
+ .name = "buck switching frequency",
+ .reg = CFG_BUCKBOOST_FREQ_SELECT_BUCK_REG,
+ .min_u = 600,
+ .max_u = 2000,
+ .step_u = 200,
+ },
+};
+
+#define STEP_CHARGING_MAX_STEPS 5
+struct smb_dt_props {
+ int fcc_ua;
+ int usb_icl_ua;
+ int otg_cl_ua;
+ int dc_icl_ua;
+ int fv_uv;
+ int wipower_max_uw;
+ u32 step_soc_threshold[STEP_CHARGING_MAX_STEPS - 1];
+ s32 step_cc_delta[STEP_CHARGING_MAX_STEPS];
+ struct device_node *revid_dev_node;
+ int float_option;
+ int chg_inhibit_thr_mv;
+ bool no_battery;
+ bool hvdcp_disable;
+ bool auto_recharge_soc;
+};
+
+struct smb2 {
+ struct smb_charger chg;
+ struct dentry *dfs_root;
+ struct smb_dt_props dt;
+ bool bad_part;
+};
+
+static int __debug_mask;
+module_param_named(
+ debug_mask, __debug_mask, int, S_IRUSR | S_IWUSR
+);
+
+#define MICRO_1P5A 1500000
+static int smb2_parse_dt(struct smb2 *chip)
+{
+ struct smb_charger *chg = &chip->chg;
+ struct device_node *node = chg->dev->of_node;
+ int rc, byte_len;
+
+ if (!node) {
+ pr_err("device tree node missing\n");
+ return -EINVAL;
+ }
+
+ chg->step_chg_enabled = true;
+
+ if (of_property_count_u32_elems(node, "qcom,step-soc-thresholds")
+ != STEP_CHARGING_MAX_STEPS - 1)
+ chg->step_chg_enabled = false;
+
+ rc = of_property_read_u32_array(node, "qcom,step-soc-thresholds",
+ chip->dt.step_soc_threshold,
+ STEP_CHARGING_MAX_STEPS - 1);
+ if (rc < 0)
+ chg->step_chg_enabled = false;
+
+ if (of_property_count_u32_elems(node, "qcom,step-current-deltas")
+ != STEP_CHARGING_MAX_STEPS)
+ chg->step_chg_enabled = false;
+
+ rc = of_property_read_u32_array(node, "qcom,step-current-deltas",
+ chip->dt.step_cc_delta,
+ STEP_CHARGING_MAX_STEPS);
+ if (rc < 0)
+ chg->step_chg_enabled = false;
+
+ chip->dt.no_battery = of_property_read_bool(node,
+ "qcom,batteryless-platform");
+
+ rc = of_property_read_u32(node,
+ "qcom,fcc-max-ua", &chip->dt.fcc_ua);
+ if (rc < 0)
+ chip->dt.fcc_ua = -EINVAL;
+
+ rc = of_property_read_u32(node,
+ "qcom,fv-max-uv", &chip->dt.fv_uv);
+ if (rc < 0)
+ chip->dt.fv_uv = -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;
+
+ rc = of_property_read_u32(node,
+ "qcom,otg-cl-ua", &chip->dt.otg_cl_ua);
+ if (rc < 0)
+ chip->dt.otg_cl_ua = MICRO_1P5A;
+
+ rc = of_property_read_u32(node,
+ "qcom,dc-icl-ua", &chip->dt.dc_icl_ua);
+ if (rc < 0)
+ chip->dt.dc_icl_ua = -EINVAL;
+
+ rc = of_property_read_u32(node, "qcom,wipower-max-uw",
+ &chip->dt.wipower_max_uw);
+ if (rc < 0)
+ chip->dt.wipower_max_uw = -EINVAL;
+
+ 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;
+ }
+ }
+
+ of_property_read_u32(node, "qcom,float-option", &chip->dt.float_option);
+ if (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");
+
+ of_property_read_u32(node, "qcom,chg-inhibit-threshold-mv",
+ &chip->dt.chg_inhibit_thr_mv);
+ if ((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_soc = of_property_read_bool(node,
+ "qcom,auto-recharge-soc");
+ return 0;
+}
+
+/************************
+ * USB PSY REGISTRATION *
+ ************************/
+
+static enum power_supply_property smb2_usb_props[] = {
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX,
+ 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_PD_ALLOWED,
+ POWER_SUPPLY_PROP_PD_ACTIVE,
+ POWER_SUPPLY_PROP_INPUT_CURRENT_SETTLED,
+ POWER_SUPPLY_PROP_INPUT_CURRENT_NOW,
+ POWER_SUPPLY_PROP_PE_START,
+};
+
+static int smb2_usb_get_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct smb2 *chip = power_supply_get_drvdata(psy);
+ struct smb_charger *chg = &chip->chg;
+ int rc = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_PRESENT:
+ if (chip->bad_part)
+ val->intval = 1;
+ else
+ rc = smblib_get_prop_usb_present(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ rc = smblib_get_prop_usb_online(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN:
+ val->intval = chg->voltage_min_uv;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ val->intval = chg->voltage_max_uv;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ rc = smblib_get_prop_usb_voltage_now(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_PD_CURRENT_MAX:
+ rc = smblib_get_prop_pd_current_max(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ rc = smblib_get_prop_usb_current_max(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_TYPE:
+ if (chip->bad_part)
+ val->intval = POWER_SUPPLY_TYPE_USB;
+ else
+ val->intval = chg->usb_psy_desc.type;
+ break;
+ case POWER_SUPPLY_PROP_TYPEC_MODE:
+ if (chip->bad_part)
+ val->intval = POWER_SUPPLY_TYPEC_SOURCE_DEFAULT;
+ else
+ rc = smblib_get_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_PD_ALLOWED:
+ rc = smblib_get_prop_pd_allowed(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_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;
+ default:
+ pr_err("get prop %d is not supported\n", psp);
+ rc = -EINVAL;
+ break;
+ }
+ if (rc < 0) {
+ pr_debug("Couldn't get prop %d rc = %d\n", psp, rc);
+ return -ENODATA;
+ }
+ return 0;
+}
+
+static int smb2_usb_set_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct smb2 *chip = power_supply_get_drvdata(psy);
+ struct smb_charger *chg = &chip->chg;
+ int rc = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN:
+ rc = smblib_set_prop_usb_voltage_min(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ rc = smblib_set_prop_usb_voltage_max(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_PD_CURRENT_MAX:
+ rc = smblib_set_prop_pd_current_max(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ rc = smblib_set_prop_usb_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_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;
+ default:
+ pr_err("set prop %d is not supported\n", psp);
+ rc = -EINVAL;
+ break;
+ }
+
+ return rc;
+}
+
+static int smb2_usb_prop_is_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ switch (psp) {
+ case POWER_SUPPLY_PROP_TYPEC_POWER_ROLE:
+ return 1;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int smb2_init_usb_psy(struct smb2 *chip)
+{
+ struct power_supply_config usb_cfg = {};
+ struct smb_charger *chg = &chip->chg;
+
+ chg->usb_psy_desc.name = "usb";
+ chg->usb_psy_desc.type = POWER_SUPPLY_TYPE_UNKNOWN;
+ chg->usb_psy_desc.properties = smb2_usb_props;
+ chg->usb_psy_desc.num_properties = ARRAY_SIZE(smb2_usb_props);
+ chg->usb_psy_desc.get_property = smb2_usb_get_prop;
+ chg->usb_psy_desc.set_property = smb2_usb_set_prop;
+ chg->usb_psy_desc.property_is_writeable = smb2_usb_prop_is_writeable;
+
+ usb_cfg.drv_data = chip;
+ usb_cfg.of_node = chg->dev->of_node;
+ chg->usb_psy = devm_power_supply_register(chg->dev,
+ &chg->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;
+}
+
+/*************************
+ * DC PSY REGISTRATION *
+ *************************/
+
+static enum power_supply_property smb2_dc_props[] = {
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+};
+
+static int smb2_dc_get_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct smb2 *chip = power_supply_get_drvdata(psy);
+ struct smb_charger *chg = &chip->chg;
+ int rc = 0;
+
+ switch (psp) {
+ 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_CURRENT_MAX:
+ rc = smblib_get_prop_dc_current_max(chg, val);
+ 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 smb2_dc_set_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct smb2 *chip = power_supply_get_drvdata(psy);
+ struct smb_charger *chg = &chip->chg;
+ int rc = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ rc = smblib_set_prop_dc_current_max(chg, val);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return rc;
+}
+
+static int smb2_dc_prop_is_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ int rc;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ rc = 1;
+ break;
+ default:
+ rc = 0;
+ break;
+ }
+
+ return rc;
+}
+
+static const struct power_supply_desc dc_psy_desc = {
+ .name = "dc",
+ .type = POWER_SUPPLY_TYPE_WIPOWER,
+ .properties = smb2_dc_props,
+ .num_properties = ARRAY_SIZE(smb2_dc_props),
+ .get_property = smb2_dc_get_prop,
+ .set_property = smb2_dc_set_prop,
+ .property_is_writeable = smb2_dc_prop_is_writeable,
+};
+
+static int smb2_init_dc_psy(struct smb2 *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 smb2_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_SYSTEM_TEMP_LEVEL,
+ 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_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_STEP_CHARGING_ENABLED,
+ POWER_SUPPLY_PROP_STEP_CHARGING_STEP,
+ POWER_SUPPLY_PROP_CHARGE_DONE,
+ POWER_SUPPLY_PROP_PARALLEL_DISABLE,
+ POWER_SUPPLY_PROP_PARALLEL_PERCENT,
+};
+
+static int smb2_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_SYSTEM_TEMP_LEVEL:
+ rc = smblib_get_prop_system_temp_level(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:
+ rc = smblib_get_prop_charger_temp_max(chg, val);
+ 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_STEP_CHARGING_STEP:
+ rc = smblib_get_prop_step_chg_step(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ rc = smblib_get_prop_batt_voltage_now(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ val->intval = get_client_vote(chg->fv_votable, DEFAULT_VOTER);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ rc = smblib_get_prop_batt_current_now(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ val->intval = get_client_vote(chg->fcc_max_votable,
+ DEFAULT_VOTER);
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ rc = smblib_get_prop_batt_temp(chg, 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_PARALLEL_PERCENT:
+ val->intval = chg->pl.slave_pct;
+ 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 smb2_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_INPUT_SUSPEND:
+ rc = smblib_set_prop_input_suspend(chg, val);
+ break;
+ case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL:
+ 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_PARALLEL_PERCENT:
+ if (val->intval < 0 || val->intval > 100)
+ return -EINVAL;
+ chg->pl.slave_pct = val->intval;
+ rerun_election(chg->fcc_votable);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ vote(chg->fv_votable, DEFAULT_VOTER, true, val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ vote(chg->fcc_max_votable, DEFAULT_VOTER, true, val->intval);
+ break;
+ default:
+ rc = -EINVAL;
+ }
+
+ return rc;
+}
+
+static int smb2_batt_prop_is_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ switch (psp) {
+ 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_PARALLEL_PERCENT:
+ return 1;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static const struct power_supply_desc batt_psy_desc = {
+ .name = "battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = smb2_batt_props,
+ .num_properties = ARRAY_SIZE(smb2_batt_props),
+ .get_property = smb2_batt_get_prop,
+ .set_property = smb2_batt_set_prop,
+ .property_is_writeable = smb2_batt_prop_is_writeable,
+};
+
+static int smb2_init_batt_psy(struct smb2 *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 *
+ ******************************/
+
+struct regulator_ops smb2_vbus_reg_ops = {
+ .enable = smblib_vbus_regulator_enable,
+ .disable = smblib_vbus_regulator_disable,
+ .is_enabled = smblib_vbus_regulator_is_enabled,
+};
+
+static int smb2_init_vbus_regulator(struct smb2 *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 = &smb2_vbus_reg_ops;
+ chg->vbus_vreg->rdesc.of_match = "qcom,smb2-vbus";
+ chg->vbus_vreg->rdesc.name = "qcom,smb2-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 regualtor rc=%d\n", rc);
+ }
+
+ return rc;
+}
+
+/******************************
+ * VCONN REGULATOR REGISTRATION *
+ ******************************/
+
+struct regulator_ops smb2_vconn_reg_ops = {
+ .enable = smblib_vconn_regulator_enable,
+ .disable = smblib_vconn_regulator_disable,
+ .is_enabled = smblib_vconn_regulator_is_enabled,
+};
+
+static int smb2_init_vconn_regulator(struct smb2 *chip)
+{
+ struct smb_charger *chg = &chip->chg;
+ struct regulator_config cfg = {};
+ int rc = 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 = &smb2_vconn_reg_ops;
+ chg->vconn_vreg->rdesc.of_match = "qcom,smb2-vconn";
+ chg->vconn_vreg->rdesc.name = "qcom,smb2-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 regualtor rc=%d\n", rc);
+ }
+
+ return rc;
+}
+
+/***************************
+ * HARDWARE INITIALIZATION *
+ ***************************/
+static int smb2_config_step_charging(struct smb2 *chip)
+{
+ struct smb_charger *chg = &chip->chg;
+ int rc = 0;
+ int i;
+
+ if (!chg->step_chg_enabled)
+ return rc;
+
+ for (i = 0; i < STEP_CHARGING_MAX_STEPS - 1; i++) {
+ rc = smblib_set_charge_param(chg,
+ &chg->param.step_soc_threshold[i],
+ chip->dt.step_soc_threshold[i]);
+ if (rc < 0) {
+ pr_err("Couldn't configure soc thresholds rc = %d\n",
+ rc);
+ goto err_out;
+ }
+ }
+
+ for (i = 0; i < STEP_CHARGING_MAX_STEPS; i++) {
+ rc = smblib_set_charge_param(chg, &chg->param.step_cc_delta[i],
+ chip->dt.step_cc_delta[i]);
+ if (rc < 0) {
+ pr_err("Couldn't configure cc delta rc = %d\n",
+ rc);
+ goto err_out;
+ }
+ }
+
+ rc = smblib_write(chg, STEP_CHG_UPDATE_REQUEST_TIMEOUT_CFG_REG,
+ STEP_CHG_UPDATE_REQUEST_TIMEOUT_40S);
+ if (rc < 0) {
+ dev_err(chg->dev,
+ "Couldn't configure soc request timeout reg rc=%d\n",
+ rc);
+ goto err_out;
+ }
+
+ rc = smblib_write(chg, STEP_CHG_UPDATE_FAIL_TIMEOUT_CFG_REG,
+ STEP_CHG_UPDATE_FAIL_TIMEOUT_120S);
+ if (rc < 0) {
+ dev_err(chg->dev,
+ "Couldn't configure soc fail timeout reg rc=%d\n",
+ rc);
+ goto err_out;
+ }
+
+ /*
+ * enable step charging, source soc, standard mode, go to final
+ * state in case of failure.
+ */
+ rc = smblib_write(chg, CHGR_STEP_CHG_MODE_CFG_REG,
+ STEP_CHARGING_ENABLE_BIT |
+ STEP_CHARGING_SOURCE_SELECT_BIT |
+ STEP_CHARGING_SOC_FAIL_OPTION_BIT);
+ if (rc < 0) {
+ dev_err(chg->dev, "Couldn't configure charger rc=%d\n", rc);
+ goto err_out;
+ }
+
+ return 0;
+err_out:
+ chg->step_chg_enabled = false;
+ return rc;
+}
+
+static int smb2_config_wipower_input_power(struct smb2 *chip, int uw)
+{
+ int rc;
+ int ua;
+ struct smb_charger *chg = &chip->chg;
+ s64 nw = (s64)uw * 1000;
+
+ if (uw < 0)
+ return 0;
+
+ ua = div_s64(nw, ZIN_ICL_PT_MAX_MV);
+ rc = smblib_set_charge_param(chg, &chg->param.dc_icl_pt_lv, ua);
+ if (rc < 0) {
+ pr_err("Couldn't configure dc_icl_pt_lv rc = %d\n", rc);
+ return rc;
+ }
+
+ ua = div_s64(nw, ZIN_ICL_PT_HV_MAX_MV);
+ rc = smblib_set_charge_param(chg, &chg->param.dc_icl_pt_hv, ua);
+ if (rc < 0) {
+ pr_err("Couldn't configure dc_icl_pt_hv rc = %d\n", rc);
+ return rc;
+ }
+
+ ua = div_s64(nw, ZIN_ICL_LV_MAX_MV);
+ rc = smblib_set_charge_param(chg, &chg->param.dc_icl_div2_lv, ua);
+ if (rc < 0) {
+ pr_err("Couldn't configure dc_icl_div2_lv rc = %d\n", rc);
+ return rc;
+ }
+
+ ua = div_s64(nw, ZIN_ICL_MID_LV_MAX_MV);
+ rc = smblib_set_charge_param(chg, &chg->param.dc_icl_div2_mid_lv, ua);
+ if (rc < 0) {
+ pr_err("Couldn't configure dc_icl_div2_mid_lv rc = %d\n", rc);
+ return rc;
+ }
+
+ ua = div_s64(nw, ZIN_ICL_MID_HV_MAX_MV);
+ rc = smblib_set_charge_param(chg, &chg->param.dc_icl_div2_mid_hv, ua);
+ if (rc < 0) {
+ pr_err("Couldn't configure dc_icl_div2_mid_hv rc = %d\n", rc);
+ return rc;
+ }
+
+ ua = div_s64(nw, ZIN_ICL_HV_MAX_MV);
+ rc = smblib_set_charge_param(chg, &chg->param.dc_icl_div2_hv, ua);
+ if (rc < 0) {
+ pr_err("Couldn't configure dc_icl_div2_hv rc = %d\n", rc);
+ return rc;
+ }
+
+ return 0;
+}
+
+static int smb2_init_hw(struct smb2 *chip)
+{
+ struct smb_charger *chg = &chip->chg;
+ int rc;
+ u8 stat;
+
+ if (chip->dt.no_battery)
+ chg->fake_capacity = 50;
+
+ if (chip->dt.fcc_ua < 0)
+ smblib_get_charge_param(chg, &chg->param.fcc, &chip->dt.fcc_ua);
+
+ if (chip->dt.fv_uv < 0)
+ smblib_get_charge_param(chg, &chg->param.fv, &chip->dt.fv_uv);
+
+ if (chip->dt.usb_icl_ua < 0)
+ smblib_get_charge_param(chg, &chg->param.usb_icl,
+ &chip->dt.usb_icl_ua);
+
+ if (chip->dt.dc_icl_ua < 0)
+ smblib_get_charge_param(chg, &chg->param.dc_icl,
+ &chip->dt.dc_icl_ua);
+
+ chg->otg_cl_ua = chip->dt.otg_cl_ua;
+ chg->dcp_icl_ua = chip->dt.usb_icl_ua;
+
+ rc = smblib_read(chg, APSD_RESULT_STATUS_REG, &stat);
+ if (rc < 0) {
+ pr_err("Couldn't read APSD_RESULT_STATUS rc=%d\n", rc);
+ return rc;
+ }
+
+ /* clear the ICL override if it is set */
+ if (stat & ICL_OVERRIDE_LATCH_BIT) {
+ rc = smblib_write(chg, CMD_APSD_REG, ICL_OVERRIDE_BIT);
+ if (rc < 0) {
+ pr_err("Couldn't disable ICL override rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ /* votes must be cast before configuring software control */
+ vote(chg->pl_disable_votable,
+ PL_INDIRECT_VOTER, true, 0);
+ vote(chg->pl_disable_votable,
+ CHG_STATE_VOTER, true, 0);
+ vote(chg->pl_disable_votable,
+ PARALLEL_PSY_VOTER, true, 0);
+ vote(chg->usb_suspend_votable,
+ DEFAULT_VOTER, chip->dt.no_battery, 0);
+ vote(chg->dc_suspend_votable,
+ DEFAULT_VOTER, chip->dt.no_battery, 0);
+ vote(chg->fcc_max_votable,
+ DEFAULT_VOTER, true, chip->dt.fcc_ua);
+ vote(chg->fv_votable,
+ DEFAULT_VOTER, true, chip->dt.fv_uv);
+ vote(chg->usb_icl_votable,
+ DCP_VOTER, true, chip->dt.usb_icl_ua);
+ vote(chg->dc_icl_votable,
+ DEFAULT_VOTER, true, chip->dt.dc_icl_ua);
+ vote(chg->hvdcp_disable_votable, DEFAULT_VOTER,
+ chip->dt.hvdcp_disable, 0);
+ vote(chg->hvdcp_disable_votable, PD_INACTIVE_VOTER,
+ true, 0);
+ vote(chg->pd_disallowed_votable_indirect, CC_DETACHED_VOTER,
+ true, 0);
+ vote(chg->pd_disallowed_votable_indirect, HVDCP_TIMEOUT_VOTER,
+ true, 0);
+
+ /* Configure charge enable for software control; active high */
+ rc = smblib_masked_write(chg, CHGR_CFG2_REG,
+ CHG_EN_POLARITY_BIT |
+ CHG_EN_SRC_BIT, 0);
+ if (rc < 0) {
+ dev_err(chg->dev, "Couldn't configure charger 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;
+ }
+
+ /*
+ * trigger the usb-typec-change interrupt only when the CC state
+ * changes
+ */
+ rc = smblib_write(chg, TYPE_C_INTRPT_ENB_REG,
+ TYPEC_CCSTATE_CHANGE_INT_EN_BIT);
+ if (rc < 0) {
+ dev_err(chg->dev,
+ "Couldn't configure Type-C interrupts rc=%d\n", rc);
+ return rc;
+ }
+
+ /* configure VCONN for software control */
+ rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_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;
+ }
+
+ /* configure VBUS for software control */
+ rc = smblib_masked_write(chg, 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;
+ }
+
+ /* configure power role for dual-role */
+ rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
+ TYPEC_POWER_ROLE_CMD_MASK, 0);
+ if (rc < 0) {
+ dev_err(chg->dev,
+ "Couldn't configure power role for DRP rc=%d\n", rc);
+ return rc;
+ }
+
+ /*
+ * disable Type-C factory mode and stay in Attached.SRC state when VCONN
+ * over-current happens
+ */
+ rc = smblib_masked_write(chg, TYPE_C_CFG_REG,
+ FACTORY_MODE_DETECTION_EN_BIT | VCONN_OC_CFG_BIT, 0);
+ if (rc < 0) {
+ dev_err(chg->dev, "Couldn't configure Type-C rc=%d\n", rc);
+ return rc;
+ }
+
+ /* increase VCONN softstart */
+ rc = smblib_masked_write(chg, TYPE_C_CFG_2_REG,
+ VCONN_SOFTSTART_CFG_MASK, VCONN_SOFTSTART_CFG_MASK);
+ if (rc < 0) {
+ dev_err(chg->dev, "Couldn't increase VCONN softstart rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ /* disable try.SINK mode */
+ rc = smblib_masked_write(chg, TYPE_C_CFG_3_REG, EN_TRYSINK_MODE_BIT, 0);
+ if (rc < 0) {
+ dev_err(chg->dev, "Couldn't set TRYSINK_MODE rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = smblib_masked_write(chg, QNOVO_PT_ENABLE_CMD_REG,
+ QNOVO_PT_ENABLE_CMD_BIT, QNOVO_PT_ENABLE_CMD_BIT);
+ if (rc < 0) {
+ dev_err(chg->dev, "Couldn't enable qnovo rc=%d\n", rc);
+ return rc;
+ }
+
+ /* configure step charging */
+ rc = smb2_config_step_charging(chip);
+ if (rc < 0) {
+ dev_err(chg->dev, "Couldn't configure step charging rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ /* configure wipower watts */
+ rc = smb2_config_wipower_input_power(chip, chip->dt.wipower_max_uw);
+ if (rc < 0) {
+ dev_err(chg->dev, "Couldn't configure wipower rc=%d\n", rc);
+ return rc;
+ }
+
+ /* disable SW STAT override */
+ rc = smblib_masked_write(chg, STAT_CFG_REG,
+ STAT_SW_OVERRIDE_CFG_BIT, 0);
+ if (rc < 0) {
+ dev_err(chg->dev, "Couldn't disable SW STAT override rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ /* configure float charger options */
+ switch (chip->dt.float_option) {
+ case 1:
+ rc = smblib_masked_write(chg, USBIN_OPTIONS_2_CFG_REG,
+ FLOAT_OPTIONS_MASK, 0);
+ break;
+ case 2:
+ rc = smblib_masked_write(chg, USBIN_OPTIONS_2_CFG_REG,
+ FLOAT_OPTIONS_MASK, FORCE_FLOAT_SDP_CFG_BIT);
+ break;
+ case 3:
+ rc = smblib_masked_write(chg, USBIN_OPTIONS_2_CFG_REG,
+ FLOAT_OPTIONS_MASK, FLOAT_DIS_CHGING_CFG_BIT);
+ break;
+ case 4:
+ rc = smblib_masked_write(chg, USBIN_OPTIONS_2_CFG_REG,
+ FLOAT_OPTIONS_MASK, SUSPEND_FLOAT_CFG_BIT);
+ break;
+ default:
+ rc = 0;
+ break;
+ }
+
+ if (rc < 0) {
+ dev_err(chg->dev, "Couldn't configure float charger options rc=%d\n",
+ rc);
+ 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,
+ CHARGE_INHIBIT_THRESHOLD_50MV);
+ break;
+ case 100:
+ rc = smblib_masked_write(chg, CHARGE_INHIBIT_THRESHOLD_CFG_REG,
+ CHARGE_INHIBIT_THRESHOLD_MASK,
+ CHARGE_INHIBIT_THRESHOLD_100MV);
+ break;
+ case 200:
+ rc = smblib_masked_write(chg, CHARGE_INHIBIT_THRESHOLD_CFG_REG,
+ CHARGE_INHIBIT_THRESHOLD_MASK,
+ CHARGE_INHIBIT_THRESHOLD_200MV);
+ break;
+ case 300:
+ rc = smblib_masked_write(chg, CHARGE_INHIBIT_THRESHOLD_CFG_REG,
+ CHARGE_INHIBIT_THRESHOLD_MASK,
+ CHARGE_INHIBIT_THRESHOLD_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_validate_initial_typec_legacy_status(chg);
+ if (rc < 0) {
+ dev_err(chg->dev, "Couldn't validate typec legacy status rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ if (chip->dt.auto_recharge_soc) {
+ rc = smblib_masked_write(chg, FG_UPDATE_CFG_2_SEL_REG,
+ SOC_LT_CHG_RECHARGE_THRESH_SEL_BIT |
+ VBT_LT_CHG_RECHARGE_THRESH_SEL_BIT,
+ VBT_LT_CHG_RECHARGE_THRESH_SEL_BIT);
+ if (rc < 0) {
+ dev_err(chg->dev, "Couldn't configure FG_UPDATE_CFG2_SEL_REG rc=%d\n",
+ rc);
+ return rc;
+ }
+ } else {
+ rc = smblib_masked_write(chg, FG_UPDATE_CFG_2_SEL_REG,
+ SOC_LT_CHG_RECHARGE_THRESH_SEL_BIT |
+ VBT_LT_CHG_RECHARGE_THRESH_SEL_BIT,
+ SOC_LT_CHG_RECHARGE_THRESH_SEL_BIT);
+ if (rc < 0) {
+ dev_err(chg->dev, "Couldn't configure FG_UPDATE_CFG2_SEL_REG rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ return rc;
+}
+
+static int smb2_setup_wa_flags(struct smb2 *chip)
+{
+ struct smb_charger *chg = &chip->chg;
+ struct pmic_revid_data *pmic_rev_id;
+ struct device_node *revid_dev_node;
+
+ revid_dev_node = of_parse_phandle(chip->chg.dev->of_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.
+ */
+ return -EPROBE_DEFER;
+ }
+
+ switch (pmic_rev_id->pmic_subtype) {
+ case PMI8998_SUBTYPE:
+ chip->chg.wa_flags |= BOOST_BACK_WA;
+ if (pmic_rev_id->rev4 == PMI8998_V1P1_REV4) /* PMI rev 1.1 */
+ chg->wa_flags |= QC_CHARGER_DETECTION_WA_BIT;
+ if (pmic_rev_id->rev4 == PMI8998_V2P0_REV4) /* PMI rev 2.0 */
+ chg->wa_flags |= TYPEC_CC2_REMOVAL_WA_BIT;
+ break;
+ case PMFALCON_SUBTYPE:
+ chip->chg.wa_flags |= BOOST_BACK_WA;
+ break;
+ default:
+ pr_err("PMIC subtype %d not supported\n",
+ pmic_rev_id->pmic_subtype);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/****************************
+ * DETERMINE INITIAL STATUS *
+ ****************************/
+
+static int smb2_determine_initial_status(struct smb2 *chip)
+{
+ struct smb_irq_data irq_data = {chip, "determine-initial-status"};
+
+ smblib_handle_usb_plugin(0, &irq_data);
+ smblib_handle_usb_typec_change(0, &irq_data);
+ smblib_handle_usb_source_change(0, &irq_data);
+ smblib_handle_chg_state_change(0, &irq_data);
+ smblib_handle_icl_change(0, &irq_data);
+ smblib_handle_step_chg_state_change(0, &irq_data);
+ smblib_handle_step_chg_soc_update_request(0, &irq_data);
+
+ return 0;
+}
+
+/**************************
+ * INTERRUPT REGISTRATION *
+ **************************/
+
+struct smb2_irq_info {
+ const char *name;
+ const irq_handler_t handler;
+ const bool wake;
+ const struct storm_watch storm_data;
+ int irq;
+};
+
+static struct smb2_irq_info smb2_irqs[] = {
+/* CHARGER IRQs */
+ {
+ .name = "chg-error",
+ .handler = smblib_handle_debug,
+ },
+ {
+ .name = "chg-state-change",
+ .handler = smblib_handle_chg_state_change,
+ .wake = true,
+ },
+ {
+ .name = "step-chg-state-change",
+ .handler = smblib_handle_step_chg_state_change,
+ .wake = true,
+ },
+ {
+ .name = "step-chg-soc-update-fail",
+ .handler = smblib_handle_step_chg_soc_update_fail,
+ .wake = true,
+ },
+ {
+ .name = "step-chg-soc-update-request",
+ .handler = smblib_handle_step_chg_soc_update_request,
+ .wake = true,
+ },
+/* OTG IRQs */
+ {
+ .name = "otg-fail",
+ .handler = smblib_handle_debug,
+ },
+ {
+ .name = "otg-overcurrent",
+ .handler = smblib_handle_debug,
+ },
+ {
+ .name = "otg-oc-dis-sw-sts",
+ .handler = smblib_handle_debug,
+ },
+ {
+ .name = "testmode-change-detect",
+ .handler = smblib_handle_debug,
+ },
+/* BATTERY IRQs */
+ {
+ .name = "bat-temp",
+ .handler = smblib_handle_batt_temp_changed,
+ },
+ {
+ .name = "bat-ocp",
+ .handler = smblib_handle_batt_psy_changed,
+ },
+ {
+ .name = "bat-ov",
+ .handler = smblib_handle_batt_psy_changed,
+ },
+ {
+ .name = "bat-low",
+ .handler = smblib_handle_batt_psy_changed,
+ },
+ {
+ .name = "bat-therm-or-id-missing",
+ .handler = smblib_handle_batt_psy_changed,
+ },
+ {
+ .name = "bat-terminal-missing",
+ .handler = smblib_handle_batt_psy_changed,
+ },
+/* USB INPUT IRQs */
+ {
+ .name = "usbin-collapse",
+ .handler = smblib_handle_debug,
+ },
+ {
+ .name = "usbin-lt-3p6v",
+ .handler = smblib_handle_debug,
+ },
+ {
+ .name = "usbin-uv",
+ .handler = smblib_handle_debug,
+ },
+ {
+ .name = "usbin-ov",
+ .handler = smblib_handle_debug,
+ },
+ {
+ .name = "usbin-plugin",
+ .handler = smblib_handle_usb_plugin,
+ .wake = true,
+ },
+ {
+ .name = "usbin-src-change",
+ .handler = smblib_handle_usb_source_change,
+ .wake = true,
+ },
+ {
+ .name = "usbin-icl-change",
+ .handler = smblib_handle_icl_change,
+ .wake = true,
+ },
+ {
+ .name = "type-c-change",
+ .handler = smblib_handle_usb_typec_change,
+ .wake = true,
+ },
+/* DC INPUT IRQs */
+ {
+ .name = "dcin-collapse",
+ .handler = smblib_handle_debug,
+ },
+ {
+ .name = "dcin-lt-3p6v",
+ .handler = smblib_handle_debug,
+ },
+ {
+ .name = "dcin-uv",
+ .handler = smblib_handle_debug,
+ },
+ {
+ .name = "dcin-ov",
+ .handler = smblib_handle_debug,
+ },
+ {
+ .name = "dcin-plugin",
+ .handler = smblib_handle_dc_plugin,
+ .wake = true,
+ },
+ {
+ .name = "div2-en-dg",
+ .handler = smblib_handle_debug,
+ },
+ {
+ .name = "dcin-icl-change",
+ .handler = smblib_handle_debug,
+ },
+/* MISCELLANEOUS IRQs */
+ {
+ .name = "wdog-snarl",
+ .handler = NULL,
+ },
+ {
+ .name = "wdog-bark",
+ .handler = NULL,
+ },
+ {
+ .name = "aicl-fail",
+ .handler = smblib_handle_debug,
+ },
+ {
+ .name = "aicl-done",
+ .handler = smblib_handle_debug,
+ },
+ {
+ .name = "high-duty-cycle",
+ .handler = smblib_handle_high_duty_cycle,
+ .wake = true,
+ },
+ {
+ .name = "input-current-limiting",
+ .handler = smblib_handle_debug,
+ },
+ {
+ .name = "temperature-change",
+ .handler = smblib_handle_debug,
+ },
+ {
+ .name = "switcher-power-ok",
+ .handler = smblib_handle_switcher_power_ok,
+ .storm_data = {true, 1000, 3},
+ },
+};
+
+static int smb2_get_irq_index_byname(const char *irq_name)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(smb2_irqs); i++) {
+ if (strcmp(smb2_irqs[i].name, irq_name) == 0)
+ return i;
+ }
+
+ return -ENOENT;
+}
+
+static int smb2_request_interrupt(struct smb2 *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 = smb2_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 (!smb2_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 = smb2_irqs[irq_index].storm_data;
+
+ rc = devm_request_threaded_irq(chg->dev, irq, NULL,
+ smb2_irqs[irq_index].handler,
+ IRQF_ONESHOT, irq_name, irq_data);
+ if (rc < 0) {
+ pr_err("Couldn't request irq %d\n", irq);
+ return rc;
+ }
+
+ smb2_irqs[irq_index].irq = irq;
+ if (smb2_irqs[irq_index].wake)
+ enable_irq_wake(irq);
+
+ return rc;
+}
+
+static int smb2_request_interrupts(struct smb2 *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 = smb2_request_interrupt(chip, child, name);
+ if (rc < 0)
+ return rc;
+ }
+ }
+
+ return rc;
+}
+
+#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_SIMPLE_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_SIMPLE_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_SIMPLE_ATTRIBUTE(force_dc_psy_update_ops, NULL,
+ force_dc_psy_update_write, "0x%02llx\n");
+
+static void smb2_create_debugfs(struct smb2 *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", S_IRUSR | S_IWUSR,
+ 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", S_IRUSR | S_IWUSR,
+ 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", S_IRUSR | S_IWUSR,
+ 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);
+}
+
+#else
+
+static void smb2_create_debugfs(struct smb2 *chip)
+{}
+
+#endif
+
+static int smb2_probe(struct platform_device *pdev)
+{
+ struct smb2 *chip;
+ struct smb_charger *chg;
+ int rc = 0;
+ u8 stat;
+
+ chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chg = &chip->chg;
+ chg->dev = &pdev->dev;
+ chg->param = v1_params;
+ chg->debug_mask = &__debug_mask;
+ chg->mode = PARALLEL_MASTER;
+ chg->name = "PMI";
+
+ chg->regmap = dev_get_regmap(chg->dev->parent, NULL);
+ if (!chg->regmap) {
+ pr_err("parent regmap is missing\n");
+ return -EINVAL;
+ }
+
+ rc = smb2_setup_wa_flags(chip);
+ if (rc < 0) {
+ if (rc != -EPROBE_DEFER)
+ pr_err("Couldn't setup wa flags rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = smblib_init(chg);
+ if (rc < 0) {
+ pr_err("Smblib_init failed rc=%d\n", rc);
+ goto cleanup;
+ }
+
+ rc = smb2_parse_dt(chip);
+ if (rc < 0) {
+ pr_err("Couldn't parse device tree rc=%d\n", rc);
+ goto cleanup;
+ }
+
+ /* set driver data before resources request it */
+ platform_set_drvdata(pdev, chip);
+
+ rc = smb2_init_vbus_regulator(chip);
+ if (rc < 0) {
+ pr_err("Couldn't initialize vbus regulator rc=%d\n",
+ rc);
+ goto cleanup;
+ }
+
+ rc = smb2_init_vconn_regulator(chip);
+ if (rc < 0) {
+ pr_err("Couldn't initialize vconn regulator rc=%d\n",
+ rc);
+ goto cleanup;
+ }
+
+ rc = smb2_init_dc_psy(chip);
+ if (rc < 0) {
+ pr_err("Couldn't initialize dc psy rc=%d\n", rc);
+ goto cleanup;
+ }
+
+ rc = smb2_init_usb_psy(chip);
+ if (rc < 0) {
+ pr_err("Couldn't initialize usb psy rc=%d\n", rc);
+ goto cleanup;
+ }
+
+ rc = smblib_read(chg, SHDN_CMD_REG, &stat);
+ if (rc < 0) {
+ pr_err("Couldn't read MISC_SHDN_CMD_REG rc=%d\n", rc);
+ return rc;
+ }
+
+ if (stat) {
+ pr_err("bad charger part; faking USB insertion\n");
+ chip->bad_part = true;
+ power_supply_changed(chg->usb_psy);
+ return 0;
+ }
+
+ chg->pl.slave_pct = 50;
+ rc = smb2_init_batt_psy(chip);
+ if (rc < 0) {
+ pr_err("Couldn't initialize batt psy rc=%d\n", rc);
+ goto cleanup;
+ }
+
+ rc = smb2_init_hw(chip);
+ if (rc < 0) {
+ pr_err("Couldn't initialize hardware rc=%d\n", rc);
+ goto cleanup;
+ }
+
+ rc = smb2_determine_initial_status(chip);
+ if (rc < 0) {
+ pr_err("Couldn't determine initial status rc=%d\n",
+ rc);
+ goto cleanup;
+ }
+
+ rc = smb2_request_interrupts(chip);
+ if (rc < 0) {
+ pr_err("Couldn't request interrupts rc=%d\n", rc);
+ goto cleanup;
+ }
+
+ smb2_create_debugfs(chip);
+
+ pr_info("QPNP SMB2 probed successfully\n");
+ return rc;
+
+cleanup:
+ smblib_deinit(chg);
+ if (chg->usb_psy)
+ power_supply_unregister(chg->usb_psy);
+ if (chg->batt_psy)
+ power_supply_unregister(chg->batt_psy);
+ if (chg->vconn_vreg && chg->vconn_vreg->rdev)
+ regulator_unregister(chg->vconn_vreg->rdev);
+ if (chg->vbus_vreg && chg->vbus_vreg->rdev)
+ regulator_unregister(chg->vbus_vreg->rdev);
+ platform_set_drvdata(pdev, NULL);
+ return rc;
+}
+
+static int smb2_remove(struct platform_device *pdev)
+{
+ struct smb2 *chip = platform_get_drvdata(pdev);
+ struct smb_charger *chg = &chip->chg;
+
+ power_supply_unregister(chg->batt_psy);
+ power_supply_unregister(chg->usb_psy);
+ regulator_unregister(chg->vconn_vreg->rdev);
+ regulator_unregister(chg->vbus_vreg->rdev);
+
+ platform_set_drvdata(pdev, NULL);
+ return 0;
+}
+
+static void smb2_shutdown(struct platform_device *pdev)
+{
+ struct smb2 *chip = platform_get_drvdata(pdev);
+ struct smb_charger *chg = &chip->chg;
+
+ /* configure power role for UFP */
+ smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
+ TYPEC_POWER_ROLE_CMD_MASK, UFP_EN_CMD_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);
+}
+
+static const struct of_device_id match_table[] = {
+ { .compatible = "qcom,qpnp-smb2", },
+ { },
+};
+
+static struct platform_driver smb2_driver = {
+ .driver = {
+ .name = "qcom,qpnp-smb2",
+ .owner = THIS_MODULE,
+ .of_match_table = match_table,
+ },
+ .probe = smb2_probe,
+ .remove = smb2_remove,
+ .shutdown = smb2_shutdown,
+};
+module_platform_driver(smb2_driver);
+
+MODULE_DESCRIPTION("QPNP SMB2 Charger Driver");
+MODULE_LICENSE("GPL v2");