diff options
Diffstat (limited to 'drivers/power/qcom-charger/qpnp-smb2.c')
-rw-r--r-- | drivers/power/qcom-charger/qpnp-smb2.c | 1898 |
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"); |