diff --git a/drivers/power/supply/qcom/Kconfig b/drivers/power/supply/qcom/Kconfig index ca26865d801c..5b286b014916 100644 --- a/drivers/power/supply/qcom/Kconfig +++ b/drivers/power/supply/qcom/Kconfig @@ -34,6 +34,18 @@ config QPNP_SMB5 To compile this driver as a module, choose M here: the module will be called qpnp-smb5-main. +config QPNP_SMBLITE + tristate "SMBLITE Battery Charger" + depends on MFD_SPMI_PMIC + help + Say Y to enable support for the SMBLITE charging peripheral. + The QPNP SMBLITE charger driver supports the charger peripheral + present in the chip. + The power supply framework is used to communicate battery and + usb properties to userspace and other driver consumers such + as fuel gauge and USB. + VBUS regulator is registered for supporting OTG. + config SMB1398_CHARGER tristate "SMB1398 power supply framework based driver" depends on MFD_I2C_PMIC && IIO diff --git a/drivers/power/supply/qcom/Makefile b/drivers/power/supply/qcom/Makefile index 65ecba4e6ce4..cb9cc3500505 100644 --- a/drivers/power/supply/qcom/Makefile +++ b/drivers/power/supply/qcom/Makefile @@ -7,3 +7,4 @@ qcom-qpnp-qg-y += qpnp-qg.o battery-profile-loader.o pmic-voter.o qg-util.o qg-s obj-$(CONFIG_SMB1398_CHARGER) += qcom-smb1398-charger.o qcom-smb1398-charger-y += smb1398-charger.o pmic-voter.o obj-$(CONFIG_SMB1355_SLAVE_CHARGER) += smb1355-charger.o pmic-voter.o +obj-$(CONFIG_QPNP_SMBLITE) += step-chg-jeita.o battery.o qpnp-smblite.o smblite-lib.o pmic-voter.o storm-watch.o schgm-falshlite.o diff --git a/drivers/power/supply/qcom/qpnp-smblite.c b/drivers/power/supply/qcom/qpnp-smblite.c new file mode 100644 index 000000000000..682f47836021 --- /dev/null +++ b/drivers/power/supply/qcom/qpnp-smblite.c @@ -0,0 +1,2119 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020 The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "smblite-reg.h" +#include "smblite-lib.h" +#include "schgm-flashlite.h" + +static struct power_supply_desc usb_psy_desc; + +static struct smb_params smblite_params = { + .fcc = { + .name = "fast charge current", + .reg = CHGR_FAST_CHARGE_CURRENT_CFG_REG, + .min_u = 0, + .max_u = 2000000, + .step_u = 100000, + }, + .fv = { + .name = "float voltage", + .reg = CHGR_FLOAT_VOLTAGE_CFG_REG, + .min_u = 3600000, + .max_u = 4600000, + .step_u = 20000, + }, + .usb_icl = { + .name = "usb input current limit", + .reg = USBIN_CURRENT_LIMIT_CFG_REG, + .min_u = 0, + .max_u = 2000000, + .step_u = 100000, + }, + .icl_max_stat = { + .name = "dcdc icl max status", + .reg = ICL_MAX_STATUS_REG, + .min_u = 0, + .max_u = 2000000, + .step_u = 100000, + }, + .icl_stat = { + .name = "input current limit status", + .reg = ICL_STATUS_REG, + .min_u = 0, + .max_u = 2000000, + .step_u = 100000, + }, + .aicl_5v_threshold = { + .name = "AICL 5V threshold", + .reg = USBIN_LV_AICL_THRESHOLD_REG, + .min_u = 4200, + .max_u = 4800, + .step_u = 200, + }, +}; + +struct smb_dt_props { + int usb_icl_ua; + struct device_node *revid_dev_node; + int chg_inhibit_thr_mv; + bool no_battery; + int auto_recharge_soc; + int auto_recharge_vbat_mv; + int wd_bark_time; + int batt_profile_fcc_ua; + int batt_profile_fv_uv; + int term_current_src; + int term_current_thresh_hi_ma; + int term_current_thresh_lo_ma; + int disable_suspend_on_collapse; +}; + +struct smblite { + struct smb_charger chg; + struct dentry *dfs_root; + struct smb_dt_props dt; +}; + +static int __debug_mask; + +static ssize_t weak_chg_icl_ua_show(struct device *dev, struct device_attribute + *attr, char *buf) +{ + struct smblite *chip = dev_get_drvdata(dev); + struct smb_charger *chg = &chip->chg; + + return scnprintf(buf, PAGE_SIZE, "%d\n", chg->weak_chg_icl_ua); +} + +static ssize_t weak_chg_icl_ua_store(struct device *dev, struct device_attribute + *attr, const char *buf, size_t count) +{ + int val; + struct smblite *chip = dev_get_drvdata(dev); + struct smb_charger *chg = &chip->chg; + + if (kstrtos32(buf, 0, &val)) + return -EINVAL; + + chg->weak_chg_icl_ua = val; + + return count; +} +static DEVICE_ATTR_RW(weak_chg_icl_ua); + +static struct attribute *smblite_attrs[] = { + &dev_attr_weak_chg_icl_ua.attr, + NULL, +}; +ATTRIBUTE_GROUPS(smblite); + +static int smblite_chg_config_init(struct smblite *chip) +{ + struct smb_charger *chg = &chip->chg; + struct pmic_revid_data *pmic_rev_id; + struct device_node *revid_dev_node, *node = chg->dev->of_node; + u8 val; + int rc = 0; + + revid_dev_node = of_parse_phandle(node, "qcom,pmic-revid", 0); + if (!revid_dev_node) { + pr_err("Missing qcom,pmic-revid property\n"); + return -EINVAL; + } + + pmic_rev_id = get_revid_data(revid_dev_node); + if (IS_ERR_OR_NULL(pmic_rev_id)) { + /* + * the revid peripheral must be registered, any failure + * here only indicates that the rev-id module has not + * probed yet. + */ + rc = -EPROBE_DEFER; + goto out; + } + + switch (pmic_rev_id->pmic_subtype) { + case PM2250_SUBTYPE: + chg->wa_flags |= WEAK_ADAPTER_WA; + if (pmic_rev_id->rev4 < 2) + chg->wa_flags |= FLASH_DIE_TEMP_DERATE_WA; + break; + default: + pr_err("Unsupported PMIC subtype=%d\n", + pmic_rev_id->pmic_subtype); + break; + } + + rc = smblite_lib_read(chg, DCDC_LDO_CFG_REG, &val); + if (rc < 0) { + pr_err("Couldn't read LDO config reg rc=%d\n", rc); + return rc; + } + chg->ldo_mode = !!(val & LDO_MODE_BIT); + + chip->chg.chg_param.smb_version = 0; + chg->param = smblite_params; + chg->name = "PM2250_charger"; + +out: + of_node_put(revid_dev_node); + return rc; +} + +#define MICRO_1P5A 1500000 +#define MICRO_P1A 100000 +#define MICRO_1PA 1000000 +#define MICRO_3PA 3000000 +#define OTG_DEFAULT_DEGLITCH_TIME_MS 50 +#define DEFAULT_WD_BARK_TIME 16 +#define DEFAULT_FCC_STEP_SIZE_UA 100000 +#define DEFAULT_FCC_STEP_UPDATE_DELAY_MS 1000 +static int smblite_parse_dt_misc(struct smblite *chip, struct device_node *node) +{ + int rc = 0, byte_len; + struct smb_charger *chg = &chip->chg; + + chg->typec_legacy_use_rp_icl = of_property_read_bool(node, + "qcom,typec-legacy-rp-icl"); + + rc = of_property_read_u32(node, "qcom,wd-bark-time-secs", + &chip->dt.wd_bark_time); + if (rc < 0 || chip->dt.wd_bark_time < MIN_WD_BARK_TIME) + chip->dt.wd_bark_time = DEFAULT_WD_BARK_TIME; + + + chip->dt.no_battery = of_property_read_bool(node, + "qcom,batteryless-platform"); + + if (of_find_property(node, "qcom,thermal-mitigation", &byte_len)) { + chg->thermal_mitigation = devm_kzalloc(chg->dev, byte_len, + GFP_KERNEL); + + if (chg->thermal_mitigation == NULL) + return -ENOMEM; + + chg->thermal_levels = byte_len / sizeof(u32); + rc = of_property_read_u32_array(node, + "qcom,thermal-mitigation", + chg->thermal_mitigation, + chg->thermal_levels); + if (rc < 0) { + dev_err(chg->dev, + "Couldn't read threm limits rc = %d\n", rc); + return rc; + } + } + + chip->dt.auto_recharge_soc = -EINVAL; + rc = of_property_read_u32(node, "qcom,auto-recharge-soc", + &chip->dt.auto_recharge_soc); + if ((rc < 0) || (chip->dt.auto_recharge_soc < 0 || + chip->dt.auto_recharge_soc > 100)) { + pr_err("qcom,auto-recharge-soc is incorrect\n"); + return -EINVAL; + } + chg->auto_recharge_soc = chip->dt.auto_recharge_soc; + + chg->suspend_input_on_debug_batt = of_property_read_bool(node, + "qcom,suspend-input-on-debug-batt"); + + chg->fake_chg_status_on_debug_batt = of_property_read_bool(node, + "qcom,fake-chg-status-on-debug-batt"); + + chg->fcc_stepper_enable = of_property_read_bool(node, + "qcom,fcc-stepping-enable"); + + chip->dt.disable_suspend_on_collapse = of_property_read_bool(node, + "qcom,disable-suspend-on-collapse"); + + of_property_read_u32(node, "qcom,fcc-step-delay-ms", + &chg->chg_param.fcc_step_delay_ms); + if (chg->chg_param.fcc_step_delay_ms <= 0) + chg->chg_param.fcc_step_delay_ms = + DEFAULT_FCC_STEP_UPDATE_DELAY_MS; + + of_property_read_u32(node, "qcom,fcc-step-size-ua", + &chg->chg_param.fcc_step_size_ua); + if (chg->chg_param.fcc_step_size_ua <= 0) + chg->chg_param.fcc_step_size_ua = DEFAULT_FCC_STEP_SIZE_UA; + + return 0; +} + +static int smblite_parse_dt_adc_channels(struct smb_charger *chg) +{ + int rc = 0; + + rc = smblite_lib_get_iio_channel(chg, "usb_in_voltage", + &chg->iio.usbin_v_chan); + if (rc < 0) + return rc; + + rc = smblite_lib_get_iio_channel(chg, "chg_temp", + &chg->iio.temp_chan); + if (rc < 0) + return rc; + + return 0; +} + +static int smblite_parse_dt_currents(struct smblite *chip, + struct device_node *node) +{ + int rc = 0; + + rc = of_property_read_u32(node, + "qcom,fcc-max-ua", &chip->dt.batt_profile_fcc_ua); + if (rc < 0) + chip->dt.batt_profile_fcc_ua = -EINVAL; + + rc = of_property_read_u32(node, + "qcom,usb-icl-ua", &chip->dt.usb_icl_ua); + if (rc < 0) + chip->dt.usb_icl_ua = -EINVAL; + + rc = of_property_read_u32(node, "qcom,chg-term-src", + &chip->dt.term_current_src); + if (rc < 0) + chip->dt.term_current_src = ITERM_SRC_UNSPECIFIED; + + if (chip->dt.term_current_src == ITERM_SRC_ADC) + rc = of_property_read_u32(node, "qcom,chg-term-base-current-ma", + &chip->dt.term_current_thresh_lo_ma); + + rc = of_property_read_u32(node, "qcom,chg-term-current-ma", + &chip->dt.term_current_thresh_hi_ma); + + return 0; +} + +static int smblite_parse_dt_voltages(struct smblite *chip, + struct device_node *node) +{ + int rc = 0, delta_mv = 4200; + + rc = of_property_read_u32(node, + "qcom,fv-max-uv", &chip->dt.batt_profile_fv_uv); + if (rc < 0) + chip->dt.batt_profile_fv_uv = -EINVAL; + else + delta_mv = chip->dt.batt_profile_fv_uv - 50; + + chip->dt.chg_inhibit_thr_mv = -EINVAL; + rc = of_property_read_u32(node, "qcom,chg-inhibit-threshold-mv", + &chip->dt.chg_inhibit_thr_mv); + if (!rc && (chip->dt.chg_inhibit_thr_mv <= 0 || + chip->dt.chg_inhibit_thr_mv > delta_mv)) { + pr_err("qcom,chg-inhibit-threshold-mv is incorrect\n"); + return -EINVAL; + } + + chip->dt.auto_recharge_vbat_mv = -EINVAL; + rc = of_property_read_u32(node, "qcom,auto-recharge-vbat-mv", + &chip->dt.auto_recharge_vbat_mv); + if (!rc && (chip->dt.auto_recharge_vbat_mv <= 0 || + chip->dt.auto_recharge_vbat_mv > delta_mv)) { + pr_err("qcom,auto-recharge-vbat-mv is incorrect\n"); + return -EINVAL; + } + + return 0; +} + +static int smblite_parse_dt(struct smblite *chip) +{ + struct smb_charger *chg = &chip->chg; + struct device_node *node = chg->dev->of_node; + int rc = 0; + + if (!node) { + pr_err("device tree node missing\n"); + return -EINVAL; + } + + rc = smblite_parse_dt_voltages(chip, node); + if (rc < 0) + return rc; + + rc = smblite_parse_dt_currents(chip, node); + if (rc < 0) + return rc; + + rc = smblite_parse_dt_adc_channels(chg); + if (rc < 0) + return rc; + + rc = smblite_parse_dt_misc(chip, node); + if (rc < 0) + return rc; + + return 0; +} + +/************************ + * USB PSY REGISTRATION * + ************************/ +static enum power_supply_property smblite_usb_props[] = { + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_SDP_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_INPUT_CURRENT_SETTLED, + POWER_SUPPLY_PROP_HW_CURRENT_MAX, + POWER_SUPPLY_PROP_REAL_TYPE, + POWER_SUPPLY_PROP_CONNECTOR_TYPE, + POWER_SUPPLY_PROP_SCOPE, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_VOLTAGE_MAX, +}; + +static int smblite_usb_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct smblite *chip = power_supply_get_drvdata(psy); + struct smb_charger *chg = &chip->chg; + int rc = 0; + + val->intval = 0; + switch (psp) { + case POWER_SUPPLY_PROP_PRESENT: + rc = smblite_lib_get_prop_usb_present(chg, val); + break; + case POWER_SUPPLY_PROP_ONLINE: + rc = smblite_lib_get_usb_online(chg, val); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + rc = smblite_lib_get_prop_usb_voltage_now(chg, val); + break; + case POWER_SUPPLY_PROP_SDP_CURRENT_MAX: + rc = smblite_lib_get_charge_current(chg, &val->intval); + break; + case POWER_SUPPLY_PROP_TYPE: + val->intval = POWER_SUPPLY_TYPE_USB; + break; + case POWER_SUPPLY_PROP_REAL_TYPE: + val->intval = chg->real_charger_type; + break; + case POWER_SUPPLY_PROP_TYPEC_MODE: + rc = smblite_lib_get_usb_prop_typec_mode(chg, val); + break; + case POWER_SUPPLY_PROP_TYPEC_POWER_ROLE: + rc = smblite_lib_get_prop_typec_power_role(chg, val); + break; + case POWER_SUPPLY_PROP_TYPEC_CC_ORIENTATION: + rc = smblite_lib_get_prop_typec_cc_orientation(chg, val); + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_SETTLED: + rc = smblite_lib_get_prop_input_current_settled(chg, val); + break; + case POWER_SUPPLY_PROP_HW_CURRENT_MAX: + rc = smblite_lib_get_hw_current_max(chg, &val->intval); + break; + case POWER_SUPPLY_PROP_CONNECTOR_TYPE: + val->intval = chg->connector_type; + break; + case POWER_SUPPLY_PROP_SCOPE: + rc = smblite_lib_get_prop_scope(chg, val); + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + val->intval = get_effective_result_locked(chg->usb_icl_votable); + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + val->intval = 5000000; + break; + default: + pr_err("get prop %d is not supported in usb\n", psp); + rc = -EINVAL; + break; + } + + if (rc < 0) { + pr_debug("Couldn't get prop %d rc = %d\n", psp, rc); + return -ENODATA; + } + + return 0; +} + +void smblite_update_usb_desc(struct smb_charger *chg) +{ + switch (chg->real_charger_type) { + case POWER_SUPPLY_TYPE_USB_CDP: + case POWER_SUPPLY_TYPE_USB_DCP: + case POWER_SUPPLY_TYPE_USB: + usb_psy_desc.type = chg->real_charger_type; + break; + default: + usb_psy_desc.type = POWER_SUPPLY_TYPE_USB; + break; + } +} + +#define MIN_THERMAL_VOTE_UA 500000 +static int smblite_usb_set_prop(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct smblite *chip = power_supply_get_drvdata(psy); + struct smb_charger *chg = &chip->chg; + int rc = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_SDP_CURRENT_MAX: + rc = smblite_lib_set_prop_current_max(chg, val); + break; + case POWER_SUPPLY_PROP_REAL_TYPE: + rc = smblite_lib_set_prop_usb_type(chg, val); + break; + default: + pr_err("set prop %d is not supported\n", psp); + rc = -EINVAL; + break; + } + + return rc; +} + +static int smblite_usb_prop_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + int rc = 0; + + switch (psp) { + default: + rc = 0; + break; + } + + return rc; +} + +static struct power_supply_desc usb_psy_desc = { + .name = "usb", + .type = POWER_SUPPLY_TYPE_USB, + .properties = smblite_usb_props, + .num_properties = ARRAY_SIZE(smblite_usb_props), + .get_property = smblite_usb_get_prop, + .set_property = smblite_usb_set_prop, + .property_is_writeable = smblite_usb_prop_is_writeable, +}; + +static int smblite_init_usb_psy(struct smblite *chip) +{ + struct power_supply_config usb_cfg = {}; + struct smb_charger *chg = &chip->chg; + + usb_cfg.drv_data = chip; + usb_cfg.of_node = chg->dev->of_node; + chg->usb_psy = devm_power_supply_register(chg->dev, + &usb_psy_desc, + &usb_cfg); + if (IS_ERR(chg->usb_psy)) { + pr_err("Couldn't register USB power supply\n"); + return PTR_ERR(chg->usb_psy); + } + + return 0; +} + +/***************************** + * USB MAIN PSY REGISTRATION * + *****************************/ + +static enum power_supply_property smblite_usb_main_props[] = { + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, + POWER_SUPPLY_PROP_TYPE, + POWER_SUPPLY_PROP_INPUT_CURRENT_SETTLED, + POWER_SUPPLY_PROP_INPUT_VOLTAGE_SETTLED, + POWER_SUPPLY_PROP_FCC_DELTA, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_FLASH_TRIGGER, + POWER_SUPPLY_PROP_FLASH_ACTIVE, +}; + +static int smblite_usb_main_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct smblite *chip = power_supply_get_drvdata(psy); + struct smb_charger *chg = &chip->chg; + int rc = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + rc = smblite_lib_get_charge_param(chg, &chg->param.fv, + &val->intval); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + rc = smblite_lib_get_charge_param(chg, &chg->param.fcc, + &val->intval); + break; + case POWER_SUPPLY_PROP_TYPE: + val->intval = POWER_SUPPLY_TYPE_MAIN; + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_SETTLED: + rc = smblite_lib_get_prop_input_current_settled(chg, val); + break; + case POWER_SUPPLY_PROP_INPUT_VOLTAGE_SETTLED: + rc = smblite_lib_get_prop_input_voltage_settled(chg, val); + break; + case POWER_SUPPLY_PROP_FCC_DELTA: + val->intval = 0; + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + rc = smblite_lib_get_icl_current(chg, &val->intval); + break; + case POWER_SUPPLY_PROP_FLASH_TRIGGER: + rc = schgm_flashlite_get_vreg_ok(chg, &val->intval); + break; + case POWER_SUPPLY_PROP_FLASH_ACTIVE: + val->intval = chg->flash_active; + break; + default: + pr_debug("get prop %d is not supported in usb-main\n", psp); + rc = -EINVAL; + break; + } + if (rc < 0) + pr_debug("Couldn't get prop %d rc = %d\n", psp, rc); + + return rc; +} + +static int smblite_usb_main_set_prop(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct smblite *chip = power_supply_get_drvdata(psy); + struct smb_charger *chg = &chip->chg; + int rc = 0, icl_ua; + union power_supply_propval pval = {0, }; + + switch (psp) { + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + rc = smblite_lib_set_charge_param(chg, &chg->param.fv, + val->intval); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + rc = smblite_lib_set_charge_param(chg, &chg->param.fcc, + val->intval); + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + rc = smblite_lib_set_icl_current(chg, val->intval); + break; + case POWER_SUPPLY_PROP_FLASH_ACTIVE: + if (chg->flash_active != val->intval) { + chg->flash_active = val->intval; + + rc = smblite_lib_get_prop_usb_present(chg, &pval); + if (rc < 0) + pr_err("Failed to get USB present status rc=%d\n", + rc); + if (!pval.intval) { + /* vote 100ma when usb is not present*/ + vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, + true, USBIN_100UA); + } else if (chg->flash_active) { + icl_ua = get_effective_result_locked( + chg->usb_icl_votable); + if (icl_ua >= USBIN_400UA) { + vote(chg->usb_icl_votable, + FLASH_ACTIVE_VOTER, + true, icl_ua - USBIN_300UA); + } + } else { + vote(chg->usb_icl_votable, FLASH_ACTIVE_VOTER, + false, 0); + } + pr_debug("flash_active=%d usb_present=%d icl=%d\n", + chg->flash_active, pval.intval, + get_effective_result_locked( + chg->usb_icl_votable)); + + } + break; + default: + pr_err("set prop %d is not supported\n", psp); + rc = -EINVAL; + break; + } + + return rc; +} + +static int smblite_usb_main_prop_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + int rc = 0; + + switch (psp) { + default: + rc = 0; + break; + } + + return rc; +} + +static const struct power_supply_desc usb_main_psy_desc = { + .name = "main", + .type = POWER_SUPPLY_TYPE_MAIN, + .properties = smblite_usb_main_props, + .num_properties = ARRAY_SIZE(smblite_usb_main_props), + .get_property = smblite_usb_main_get_prop, + .set_property = smblite_usb_main_set_prop, + .property_is_writeable = smblite_usb_main_prop_is_writeable, +}; + +static int smblite_init_usb_main_psy(struct smblite *chip) +{ + struct power_supply_config usb_main_cfg = {}; + struct smb_charger *chg = &chip->chg; + + usb_main_cfg.drv_data = chip; + usb_main_cfg.of_node = chg->dev->of_node; + chg->usb_main_psy = devm_power_supply_register(chg->dev, + &usb_main_psy_desc, + &usb_main_cfg); + if (IS_ERR(chg->usb_main_psy)) { + pr_err("Couldn't register USB main power supply\n"); + return PTR_ERR(chg->usb_main_psy); + } + + return 0; +} + +/************************* + * BATT PSY REGISTRATION * + *************************/ +static enum power_supply_property smblite_batt_props[] = { + POWER_SUPPLY_PROP_INPUT_SUSPEND, + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CHARGER_TEMP, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, + POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_STEP_CHARGING_ENABLED, + POWER_SUPPLY_PROP_SW_JEITA_ENABLED, + POWER_SUPPLY_PROP_CHARGE_DONE, + POWER_SUPPLY_PROP_SET_SHIP_MODE, + POWER_SUPPLY_PROP_DIE_HEALTH, + POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX, + POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT, + POWER_SUPPLY_PROP_CHARGE_COUNTER, + POWER_SUPPLY_PROP_CYCLE_COUNT, + POWER_SUPPLY_PROP_RECHARGE_SOC, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_TIME_TO_FULL_NOW, + POWER_SUPPLY_PROP_FORCE_RECHARGE, + POWER_SUPPLY_PROP_FCC_STEPPER_ENABLE, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED, +}; + +#define DEBUG_ACCESSORY_TEMP_DECIDEGC 250 +static int smblite_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 = smblite_lib_get_prop_batt_status(chg, val); + break; + case POWER_SUPPLY_PROP_HEALTH: + rc = smblite_lib_get_prop_batt_health(chg, val); + break; + case POWER_SUPPLY_PROP_PRESENT: + rc = smblite_lib_get_prop_batt_present(chg, val); + break; + case POWER_SUPPLY_PROP_INPUT_SUSPEND: + rc = smblite_lib_get_prop_input_suspend(chg, val); + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + rc = smblite_lib_get_prop_batt_charge_type(chg, val); + break; + case POWER_SUPPLY_PROP_CAPACITY: + rc = smblite_lib_get_prop_batt_capacity(chg, val); + break; + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: + rc = smblite_lib_get_prop_system_temp_level(chg, val); + break; + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX: + rc = smblite_lib_get_prop_system_temp_level_max(chg, val); + break; + case POWER_SUPPLY_PROP_CHARGER_TEMP: + rc = smblite_lib_get_prop_charger_temp(chg, val); + break; + case POWER_SUPPLY_PROP_STEP_CHARGING_ENABLED: + val->intval = chg->step_chg_enabled; + break; + case POWER_SUPPLY_PROP_SW_JEITA_ENABLED: + val->intval = 1; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + rc = smblite_lib_get_prop_from_bms(chg, + POWER_SUPPLY_PROP_VOLTAGE_NOW, val); + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + val->intval = get_client_vote(chg->fv_votable, + BATT_PROFILE_VOTER); + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + rc = smblite_lib_get_batt_current_now(chg, val); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + val->intval = get_client_vote(chg->fcc_votable, + BATT_PROFILE_VOTER); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + val->intval = get_effective_result(chg->fcc_votable); + break; + case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: + rc = smblite_lib_get_prop_batt_iterm(chg, val); + break; + case POWER_SUPPLY_PROP_TEMP: + if (chg->typec_mode == POWER_SUPPLY_TYPEC_SINK_DEBUG_ACCESSORY) + val->intval = DEBUG_ACCESSORY_TEMP_DECIDEGC; + else + rc = smblite_lib_get_prop_from_bms(chg, + POWER_SUPPLY_PROP_TEMP, val); + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + case POWER_SUPPLY_PROP_CHARGE_DONE: + rc = smblite_lib_get_prop_batt_charge_done(chg, val); + break; + case POWER_SUPPLY_PROP_SET_SHIP_MODE: + /* Not in ship mode as long as device is active */ + val->intval = 0; + break; + case POWER_SUPPLY_PROP_DIE_HEALTH: + rc = smblite_lib_get_die_health(chg, val); + break; + case POWER_SUPPLY_PROP_CHARGE_COUNTER: + rc = smblite_lib_get_prop_from_bms(chg, + POWER_SUPPLY_PROP_CHARGE_COUNTER, val); + break; + case POWER_SUPPLY_PROP_CYCLE_COUNT: + rc = smblite_lib_get_prop_from_bms(chg, + POWER_SUPPLY_PROP_CYCLE_COUNT, val); + break; + case POWER_SUPPLY_PROP_RECHARGE_SOC: + val->intval = chg->auto_recharge_soc; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + rc = smblite_lib_get_prop_from_bms(chg, + POWER_SUPPLY_PROP_CHARGE_FULL, val); + break; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + rc = smblite_lib_get_prop_from_bms(chg, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, val); + break; + case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW: + rc = smblite_lib_get_prop_from_bms(chg, + POWER_SUPPLY_PROP_TIME_TO_FULL_NOW, val); + break; + case POWER_SUPPLY_PROP_FORCE_RECHARGE: + val->intval = 0; + break; + case POWER_SUPPLY_PROP_FCC_STEPPER_ENABLE: + val->intval = chg->fcc_stepper_enable; + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED: + rc = smblite_lib_get_prop_input_current_limited(chg, val); + 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 smblite_batt_set_prop(struct power_supply *psy, + enum power_supply_property prop, + const union power_supply_propval *val) +{ + int rc = 0; + struct smb_charger *chg = power_supply_get_drvdata(psy); + + switch (prop) { + case POWER_SUPPLY_PROP_STATUS: + rc = smblite_lib_set_prop_batt_status(chg, val); + break; + case POWER_SUPPLY_PROP_INPUT_SUSPEND: + rc = smblite_lib_set_prop_input_suspend(chg, val); + break; + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: + rc = smblite_lib_set_prop_system_temp_level(chg, val); + break; + case POWER_SUPPLY_PROP_CAPACITY: + rc = smblite_lib_set_prop_batt_capacity(chg, val); + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + chg->batt_profile_fv_uv = val->intval; + vote(chg->fv_votable, BATT_PROFILE_VOTER, true, val->intval); + break; + case POWER_SUPPLY_PROP_STEP_CHARGING_ENABLED: + chg->step_chg_enabled = !!val->intval; + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + chg->batt_profile_fcc_ua = val->intval; + vote(chg->fcc_votable, BATT_PROFILE_VOTER, true, val->intval); + break; + case POWER_SUPPLY_PROP_SET_SHIP_MODE: + /* Not in ship mode as long as the device is active */ + if (!val->intval) + break; + if (chg->pl.psy) + power_supply_set_property(chg->pl.psy, + POWER_SUPPLY_PROP_SET_SHIP_MODE, val); + rc = smblite_lib_set_prop_ship_mode(chg, val); + break; + case POWER_SUPPLY_PROP_DIE_HEALTH: + power_supply_changed(chg->batt_psy); + break; + case POWER_SUPPLY_PROP_RECHARGE_SOC: + rc = smblite_lib_set_prop_rechg_soc_thresh(chg, val); + break; + case POWER_SUPPLY_PROP_FORCE_RECHARGE: + /* toggle charging to force recharge */ + vote(chg->chg_disable_votable, FORCE_RECHARGE_VOTER, + true, 0); + /* charge disable delay */ + msleep(50); + vote(chg->chg_disable_votable, FORCE_RECHARGE_VOTER, + false, 0); + break; + case POWER_SUPPLY_PROP_FCC_STEPPER_ENABLE: + chg->fcc_stepper_enable = val->intval; + break; + default: + rc = -EINVAL; + } + + return rc; +} + +static int smblite_batt_prop_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + int rc = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + case POWER_SUPPLY_PROP_INPUT_SUSPEND: + case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL: + case POWER_SUPPLY_PROP_CAPACITY: + case POWER_SUPPLY_PROP_DIE_HEALTH: + case POWER_SUPPLY_PROP_STEP_CHARGING_ENABLED: + rc = 1; + break; + default: + rc = 0; + break; + } + + return rc; +} + +static const struct power_supply_desc batt_psy_desc = { + .name = "battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = smblite_batt_props, + .num_properties = ARRAY_SIZE(smblite_batt_props), + .get_property = smblite_batt_get_prop, + .set_property = smblite_batt_set_prop, + .property_is_writeable = smblite_batt_prop_is_writeable, +}; + +static int smblite_init_batt_psy(struct smblite *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; +} + +/*************************** + * HARDWARE INITIALIZATION * + ***************************/ +static int smblite_configure_typec(struct smb_charger *chg) +{ + union power_supply_propval pval = {0, }; + int rc; + u8 val = 0; + + rc = smblite_lib_read(chg, LEGACY_CABLE_STATUS_REG, &val); + if (rc < 0) { + dev_err(chg->dev, "Couldn't read Legacy status rc=%d\n", rc); + return rc; + } + + /* + * Across reboot, standard typeC cables get detected as legacy cables + * due to VBUS attachment prior to CC attach/dettach, reset legacy cable + * detection by disabling/enabling typeC mode. + */ + if (val & TYPEC_LEGACY_CABLE_STATUS_BIT) { + pval.intval = POWER_SUPPLY_TYPEC_PR_NONE; + smblite_lib_set_prop_typec_power_role(chg, &pval); + if (rc < 0) { + dev_err(chg->dev, "Couldn't disable TYPEC rc=%d\n", rc); + return rc; + } + + /* delay before enabling typeC */ + msleep(50); + + pval.intval = POWER_SUPPLY_TYPEC_PR_DUAL; + smblite_lib_set_prop_typec_power_role(chg, &pval); + if (rc < 0) { + dev_err(chg->dev, "Couldn't enable TYPEC rc=%d\n", rc); + return rc; + } + } + + /* Use simple write to clear interrupts */ + rc = smblite_lib_write(chg, TYPE_C_INTERRUPT_EN_CFG_1_REG, 0); + if (rc < 0) { + dev_err(chg->dev, + "Couldn't configure Type-C interrupts rc=%d\n", rc); + return rc; + } + + rc = smblite_lib_write(chg, TYPE_C_INTERRUPT_EN_CFG_2_REG, 0); + if (rc < 0) { + dev_err(chg->dev, + "Couldn't configure Type-C interrupts rc=%d\n", rc); + return rc; + } + + rc = smblite_lib_masked_write(chg, TYPE_C_MODE_CFG_REG, + EN_TRY_SNK_BIT | EN_SNK_ONLY_BIT, + EN_TRY_SNK_BIT); + if (rc < 0) { + dev_err(chg->dev, + "Couldn't configure TYPE_C_MODE_CFG_REG rc=%d\n", + rc); + return rc; + } + + rc = smblite_lib_masked_write(chg, TYPE_C_EXIT_STATE_CFG_REG, + SEL_SRC_UPPER_REF_BIT, SEL_SRC_UPPER_REF_BIT); + if (rc < 0) + dev_err(chg->dev, + "Couldn't configure CC threshold voltage rc=%d\n", rc); + + return rc; +} + +#define RAW_ITERM(iterm_ma, max_range) \ + div_s64((int64_t)iterm_ma * ADC_CHG_ITERM_MASK, max_range) +static int smblite_configure_iterm_thresholds_adc(struct smblite *chip) +{ + u8 *buf; + int rc = 0; + s16 raw_hi_thresh, raw_lo_thresh, max_limit_ma; + struct smb_charger *chg = &chip->chg; + + max_limit_ma = ITERM_LIMITS_MA; + + if (chip->dt.term_current_thresh_hi_ma < (-1 * max_limit_ma) + || chip->dt.term_current_thresh_hi_ma > max_limit_ma + || chip->dt.term_current_thresh_lo_ma < (-1 * max_limit_ma) + || chip->dt.term_current_thresh_lo_ma > max_limit_ma) { + dev_err(chg->dev, "ITERM threshold out of range rc=%d\n", rc); + return -EINVAL; + } + + /* + * Conversion: + * raw (A) = (term_current * ADC_CHG_ITERM_MASK) / max_limit_ma + * Note: raw needs to be converted to big-endian format. + */ + + if (chip->dt.term_current_thresh_hi_ma) { + raw_hi_thresh = RAW_ITERM(chip->dt.term_current_thresh_hi_ma, + max_limit_ma); + raw_hi_thresh = sign_extend32(raw_hi_thresh, 15); + buf = (u8 *)&raw_hi_thresh; + raw_hi_thresh = buf[1] | (buf[0] << 8); + + rc = smblite_lib_batch_write(chg, CHGR_ADC_ITERM_UP_THD_MSB_REG, + (u8 *)&raw_hi_thresh, 2); + if (rc < 0) { + dev_err(chg->dev, "Couldn't configure ITERM threshold HIGH rc=%d\n", + rc); + return rc; + } + } + + if (chip->dt.term_current_thresh_lo_ma) { + raw_lo_thresh = RAW_ITERM(chip->dt.term_current_thresh_lo_ma, + max_limit_ma); + raw_lo_thresh = sign_extend32(raw_lo_thresh, 15); + buf = (u8 *)&raw_lo_thresh; + raw_lo_thresh = buf[1] | (buf[0] << 8); + + rc = smblite_lib_batch_write(chg, CHGR_ADC_ITERM_LO_THD_MSB_REG, + (u8 *)&raw_lo_thresh, 2); + if (rc < 0) { + dev_err(chg->dev, "Couldn't configure ITERM threshold LOW rc=%d\n", + rc); + return rc; + } + } + + return rc; +} + +static int smblite_configure_iterm_thresholds(struct smblite *chip) +{ + int rc = 0; + + switch (chip->dt.term_current_src) { + case ITERM_SRC_ADC: + rc = smblite_configure_iterm_thresholds_adc(chip); + break; + default: + break; + } + + return rc; +} + +static int smblite_configure_recharging(struct smblite *chip) +{ + int rc = 0; + struct smb_charger *chg = &chip->chg; + union power_supply_propval pval; + + /* Configure VBATT-based or automatic recharging */ + rc = smblite_lib_masked_write(chg, CHGR_RECHG_CFG_REG, RECHG_MASK, + (chip->dt.auto_recharge_vbat_mv > 0) ? + VBAT_BASED_RECHG_BIT : 0); + if (rc < 0) { + dev_err(chg->dev, "Couldn't configure VBAT-rechg CHG_CFG2_REG rc=%d\n", + rc); + return rc; + } + + /* program the auto-recharge VBAT threshold */ + if (chip->dt.auto_recharge_vbat_mv > 0) { + u32 temp = VBAT_TO_VRAW_ADC(chip->dt.auto_recharge_vbat_mv); + + temp = ((temp & 0xFF00) >> 8) | ((temp & 0xFF) << 8); + rc = smblite_lib_batch_write(chg, + CHGR_ADC_RECHARGE_THRESHOLD_MSB_REG, (u8 *)&temp, 2); + if (rc < 0) { + dev_err(chg->dev, "Couldn't configure ADC_RECHARGE_THRESHOLD REG rc=%d\n", + rc); + return rc; + } + /* Program the sample count for VBAT based recharge to 3 */ + rc = smblite_lib_masked_write(chg, CHGR_RECHG_CFG_REG, + NO_OF_SAMPLE_FOR_RCHG, 3); + if (rc < 0) { + dev_err(chg->dev, "Couldn't configure CHGR_NO_SAMPLE_FOR_TERM_RCHG_CFG rc=%d\n", + rc); + return rc; + } + } + + rc = smblite_lib_masked_write(chg, CHGR_RECHG_CFG_REG, RECHG_MASK, + (chip->dt.auto_recharge_soc != -EINVAL) ? + SOC_BASED_RECHG_BIT : VBAT_BASED_RECHG_BIT); + if (rc < 0) { + dev_err(chg->dev, "Couldn't configure SOC-rechg CHG_CFG2_REG rc=%d\n", + rc); + return rc; + } + + /* program the auto-recharge threshold */ + if (chip->dt.auto_recharge_soc != -EINVAL) { + pval.intval = chip->dt.auto_recharge_soc; + rc = smblite_lib_set_prop_rechg_soc_thresh(chg, &pval); + if (rc < 0) { + dev_err(chg->dev, "Couldn't configure CHG_RCHG_SOC_REG rc=%d\n", + rc); + return rc; + } + } + + return 0; +} + +static int smblite_init_connector_type(struct smb_charger *chg) +{ + int rc, type = 0; + u8 val = 0; + + rc = smblite_lib_read(chg, TYPEC_U_USB_CFG_REG, &val); + if (rc < 0) { + dev_err(chg->dev, "Couldn't read U_USB config rc=%d\n", + rc); + return rc; + } + + type = !!(val & EN_MICRO_USB_MODE_BIT); + + pr_debug("Connector type=%s\n", type ? "Micro USB" : "TypeC"); + + if (type) { + chg->connector_type = POWER_SUPPLY_CONNECTOR_MICRO_USB; + } else { + chg->connector_type = POWER_SUPPLY_CONNECTOR_TYPEC; + rc = smblite_configure_typec(chg); + if (rc < 0) { + dev_err(chg->dev, + "Couldn't configure TypeC/micro-USB mode rc=%d\n", + rc); + return rc; + } + } + + return 0; +} + +static int smblite_init_otg(struct smblite *chip) +{ + struct smb_charger *chg = &chip->chg; + + chg->usb_id_gpio = chg->usb_id_irq = -EINVAL; + + if (chg->connector_type == POWER_SUPPLY_CONNECTOR_TYPEC) + return 0; + + if (of_find_property(chg->dev->of_node, "qcom,usb-id-gpio", NULL)) + chg->usb_id_gpio = of_get_named_gpio(chg->dev->of_node, + "qcom,usb-id-gpio", 0); + + chg->usb_id_irq = of_irq_get_byname(chg->dev->of_node, + "usb_id_irq"); + if (chg->usb_id_irq < 0 || chg->usb_id_gpio < 0) + pr_err("OTG irq (%d) / gpio (%d) not defined\n", + chg->usb_id_irq, chg->usb_id_gpio); + + return 0; +} + +static int smblite_init_hw(struct smblite *chip) +{ + struct smb_charger *chg = &chip->chg; + int rc; + u8 val = 0, mask = 0; + u32 temp; + + if (chip->dt.no_battery) + chg->fake_capacity = 50; + + if (chip->dt.batt_profile_fcc_ua < 0) + smblite_lib_get_charge_param(chg, &chg->param.fcc, + &chg->batt_profile_fcc_ua); + + if (chip->dt.batt_profile_fv_uv < 0) + smblite_lib_get_charge_param(chg, &chg->param.fv, + &chg->batt_profile_fv_uv); + + smblite_lib_get_charge_param(chg, &chg->param.aicl_5v_threshold, + &chg->default_aicl_5v_threshold_mv); + chg->aicl_5v_threshold_mv = chg->default_aicl_5v_threshold_mv; + + rc = smblite_init_connector_type(chg); + if (rc < 0) { + dev_err(chg->dev, "Couldn't configure connector type rc=%d\n", + rc); + return rc; + } + + rc = smblite_init_otg(chip); + if (rc < 0) { + dev_err(chg->dev, "Couldn't init otg rc=%d\n", rc); + return rc; + } + + rc = schgm_flashlite_init(chg); + if (rc < 0) { + pr_err("Couldn't configure flash rc=%d\n", rc); + return rc; + } + + rc = smblite_lib_icl_override(chg, HW_AUTO_MODE); + if (rc < 0) { + pr_err("Couldn't disable ICL override rc=%d\n", rc); + return rc; + } + + /* vote 0mA on usb_icl for non battery platforms */ + vote(chg->usb_icl_votable, + DEFAULT_VOTER, chip->dt.no_battery, 0); + vote(chg->fcc_votable, HW_LIMIT_VOTER, + chip->dt.batt_profile_fcc_ua > 0, chip->dt.batt_profile_fcc_ua); + vote(chg->fv_votable, HW_LIMIT_VOTER, + chip->dt.batt_profile_fv_uv > 0, chip->dt.batt_profile_fv_uv); + vote(chg->fcc_votable, + BATT_PROFILE_VOTER, chg->batt_profile_fcc_ua > 0, + chg->batt_profile_fcc_ua); + vote(chg->fv_votable, + BATT_PROFILE_VOTER, chg->batt_profile_fv_uv > 0, + chg->batt_profile_fv_uv); + + /* Some h/w limit maximum supported ICL */ + vote(chg->usb_icl_votable, HW_LIMIT_VOTER, + chip->dt.usb_icl_ua > 0, chip->dt.usb_icl_ua); + + /* + * AICL configuration: enable aicl and aicl rerun and based on DT + * configuration enable/disable ADB based AICL and Suspend on collapse. + */ + + mask = USBIN_AICL_PERIODIC_RERUN_EN_BIT | USBIN_AICL_RERUN_TIME_MASK; + val = USBIN_AICL_PERIODIC_RERUN_EN_BIT | AICL_RERUN_TIME_12S_VAL; + rc = smblite_lib_masked_write(chg, MISC_AICL_RERUN_CFG_REG, mask, val); + if (rc < 0) { + dev_err(chg->dev, "Couldn't config AICL rerun rc=%d\n", rc); + return rc; + } + + mask = USBIN_AICL_EN_BIT | USBIN_AICL_START_AT_MAX; + val = USBIN_AICL_EN_BIT; + rc = smblite_lib_masked_write(chg, USBIN_AICL_OPTIONS_CFG_REG, mask, + val); + if (rc < 0) { + dev_err(chg->dev, "Couldn't config AICL rc=%d\n", rc); + return rc; + } + + if (!chip->dt.disable_suspend_on_collapse) { + rc = smblite_lib_masked_write(chg, USBIN_INPUT_SUSPEND_REG, + SUSPEND_ON_COLLAPSE_USBIN_BIT, + SUSPEND_ON_COLLAPSE_USBIN_BIT); + if (rc < 0) { + dev_err(chg->dev, "Couldn't config AICL rc=%d\n", rc); + return rc; + } + } + + /* enable the charging path */ + rc = vote(chg->chg_disable_votable, DEFAULT_VOTER, false, 0); + if (rc < 0) { + dev_err(chg->dev, "Couldn't enable charging rc=%d\n", rc); + return rc; + } + + /* configure VBUS for software control */ + rc = smblite_lib_masked_write(chg, DCDC_OTG_CFG_REG, + OTG_EN_SRC_CFG_BIT, 0); + if (rc < 0) { + dev_err(chg->dev, + "Couldn't configure VBUS for SW control rc=%d\n", rc); + return rc; + } + + val = (ilog2(chip->dt.wd_bark_time / 16) << BARK_WDOG_TIMEOUT_SHIFT) + & BARK_WDOG_TIMEOUT_MASK; + val |= BITE_WDOG_TIMEOUT_8S << BITE_WDOG_TIMEOUT_SHIFT; + + rc = smblite_lib_masked_write(chg, SNARL_BARK_BITE_WD_CFG_REG, + BARK_WDOG_TIMEOUT_MASK | BITE_WDOG_TIMEOUT_MASK, + val); + if (rc < 0) { + pr_err("Couldn't configue WD time config rc=%d\n", rc); + return rc; + } + + /* enable WD BARK and enable it on plugin */ + mask = WDOG_TIMER_EN_ON_PLUGIN_BIT | BARK_WDOG_INT_EN_BIT + | BITE_WDOG_DISABLE_CHARGING_CFG_BIT | WDOG_TIMER_EN_BIT; + val = WDOG_TIMER_EN_ON_PLUGIN_BIT | BARK_WDOG_INT_EN_BIT + | BITE_WDOG_DISABLE_CHARGING_CFG_BIT; + rc = smblite_lib_masked_write(chg, WD_CFG_REG, mask, val); + if (rc < 0) { + pr_err("Couldn't configue WD config rc=%d\n", rc); + return rc; + } + + /* set termination current threshold values */ + rc = smblite_configure_iterm_thresholds(chip); + if (rc < 0) { + pr_err("Couldn't configure ITERM thresholds rc=%d\n", + rc); + return rc; + } + + if (chip->dt.chg_inhibit_thr_mv > 0) { + temp = VBAT_TO_VRAW_ADC(chip->dt.chg_inhibit_thr_mv); + temp = ((temp & 0xFF00) >> 8) | ((temp & 0xFF) << 8); + rc = smblite_lib_batch_write(chg, + CHGR_INHIBIT_THRESHOLD_CFG_REG, (u8 *)&temp, 2); + if (rc < 0) { + dev_err(chg->dev, "Couldn't configure ADC_RECHARGE_THRESHOLD REG rc=%d\n", + rc); + return rc; + } + rc = smblite_lib_masked_write(chg, CHGR_INHIBIT_REG, + CHGR_INHIBIT_BIT, CHGR_INHIBIT_BIT); + if (rc < 0) { + dev_err(chg->dev, "Couldn't enable INHIBIT rc=%d\n", + rc); + return rc; + } + } else { + rc = smblite_lib_masked_write(chg, CHGR_INHIBIT_REG, + CHGR_INHIBIT_BIT, 0); + if (rc < 0) { + dev_err(chg->dev, "Couldn't enable INHIBIT rc=%d\n", + rc); + return rc; + } + } + + mask = FAST_CHARGE_SAFETY_TIMER_EN_BIT | FAST_CHARGE_SAFETY_TIMER_MASK; + val = FAST_CHARGE_SAFETY_TIMER_EN_BIT + | FAST_CHARGE_SAFETY_TIMER_768_MIN; + rc = smblite_lib_masked_write(chg, + CHGR_FAST_CHARGE_SAFETY_TIMER_CFG_REG, mask, val); + if (rc < 0) { + dev_err(chg->dev, "Couldn't set CHGR_FAST_CHARGE_SAFETY_TIMER_CFG_REG rc=%d\n", + rc); + return rc; + } + + rc = smblite_configure_recharging(chip); + if (rc < 0) + return rc; + + return rc; +} + +static int smblite_post_init(struct smblite *chip) +{ + struct smb_charger *chg = &chip->chg; + union power_supply_propval pval; + int rc; + + /* + * In case the usb path is suspended, we would have missed disabling + * the icl change interrupt because the interrupt could have been + * not requested + */ + rerun_election(chg->usb_icl_votable); + + if (chg->connector_type == POWER_SUPPLY_CONNECTOR_TYPEC) { + /* configure power role for dual-role */ + pval.intval = POWER_SUPPLY_TYPEC_PR_DUAL; + rc = smblite_lib_set_prop_typec_power_role(chg, &pval); + if (rc < 0) { + dev_err(chg->dev, "Couldn't configure DRP role rc=%d\n", + rc); + return rc; + } + } + + rerun_election(chg->temp_change_irq_disable_votable); + + return 0; +} + +/**************************** + * DETERMINE INITIAL STATUS * + ****************************/ + +static int smblite_determine_initial_status(struct smblite *chip) +{ + struct smb_irq_data irq_data = {chip, "determine-initial-status"}; + struct smb_charger *chg = &chip->chg; + union power_supply_propval val; + int rc; + + rc = smblite_lib_get_prop_usb_present(chg, &val); + if (rc < 0) { + pr_err("Couldn't get usb present rc=%d\n", rc); + return rc; + } + + if (chg->bms_psy) + smblite_lib_suspend_on_debug_battery(chg); + + smblite_usb_plugin_irq_handler(0, &irq_data); + smblite_typec_attach_detach_irq_handler(0, &irq_data); + smblite_typec_state_change_irq_handler(0, &irq_data); + smblite_chg_state_change_irq_handler(0, &irq_data); + smblite_icl_change_irq_handler(0, &irq_data); + smblite_batt_temp_changed_irq_handler(0, &irq_data); + smblite_wdog_bark_irq_handler(0, &irq_data); + smblite_typec_or_rid_detection_change_irq_handler(0, &irq_data); + + if (chg->usb_id_gpio > 0 && + chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB) + smblite_usb_id_irq_handler(0, chg); + + return 0; +} + +/************************** + * INTERRUPT REGISTRATION * + **************************/ + +static struct smb_irq_info smblite_irqs[] = { + /* CHARGER IRQs */ + [CHGR_ERROR_IRQ] = { + .name = "chgr-error", + .handler = smblite_default_irq_handler, + }, + [CHG_STATE_CHANGE_IRQ] = { + .name = "chg-state-change", + .handler = smblite_chg_state_change_irq_handler, + .wake = true, + }, + [VPH_OV_IRQ] = { + .name = "vph-ov", + }, + [BUCK_OC_IRQ] = { + .name = "buck-oc", + }, + /* DCDC IRQs */ + [OTG_FAIL_IRQ] = { + .name = "otg-fail", + .handler = smblite_default_irq_handler, + }, + [OTG_FAULT_IRQ] = { + .name = "otg-fault", + }, + [SKIP_MODE_IRQ] = { + .name = "skip-mode", + }, + [INPUT_CURRENT_LIMITING_IRQ] = { + .name = "input-current-limiting", + }, + [SWITCHER_POWER_OK_IRQ] = { + .name = "switcher-power-ok", + .handler = smblite_switcher_power_ok_irq_handler, + }, + /* BATTERY IRQs */ + [BAT_TEMP_IRQ] = { + .name = "bat-temp", + .handler = smblite_batt_temp_changed_irq_handler, + .wake = true, + }, + [BAT_THERM_OR_ID_MISSING_IRQ] = { + .name = "bat-therm-or-id-missing", + .handler = smblite_batt_psy_changed_irq_handler, + }, + [BAT_LOW_IRQ] = { + .name = "bat-low", + .handler = smblite_batt_psy_changed_irq_handler, + }, + [BAT_OV_IRQ] = { + .name = "bat-ov", + .handler = smblite_batt_psy_changed_irq_handler, + }, + [BSM_ACTIVE_IRQ] = { + .name = "bsm-active", + }, + /* USB INPUT IRQs */ + [USBIN_PLUGIN_IRQ] = { + .name = "usbin-plugin", + .handler = smblite_usb_plugin_irq_handler, + .wake = true, + }, + [USBIN_COLLAPSE_IRQ] = { + .name = "usbin-collapse", + }, + [USBIN_UV_IRQ] = { + .name = "usbin-uv", + .handler = smblite_usbin_uv_irq_handler, + .wake = true, + .storm_data = {true, 3000, 5}, + }, + [USBIN_OV_IRQ] = { + .name = "usbin-ov", + .handler = smblite_usbin_ov_irq_handler, + }, + [USBIN_GT_VT_IRQ] = { + .name = "usbin-gtvt", + }, + [USBIN_ICL_CHANGE_IRQ] = { + .name = "usbin-icl-change", + .handler = smblite_icl_change_irq_handler, + .wake = true, + }, + /* TYPEC IRQs */ + [TYPEC_OR_RID_DETECTION_CHANGE_IRQ] = { + .name = "typec-or-rid-detect-change", + .handler = + smblite_typec_or_rid_detection_change_irq_handler, + .wake = true, + }, + [TYPEC_VPD_DETECT_IRQ] = { + .name = "typec-vpd-detect", + }, + [TYPEC_CC_STATE_CHANGE_IRQ] = { + .name = "typec-cc-state-change", + .handler = smblite_typec_state_change_irq_handler, + .wake = true, + }, + [TYPEC_VBUS_CHANGE_IRQ] = { + .name = "typec-vbus-change", + }, + [TYPEC_ATTACH_DETACH_IRQ] = { + .name = "typec-attach-detach", + .handler = smblite_typec_attach_detach_irq_handler, + .wake = true, + }, + [TYPEC_LEGACY_CABLE_DETECT_IRQ] = { + .name = "typec-legacy-cable-detect", + .handler = smblite_default_irq_handler, + }, + [TYPEC_TRY_SNK_SRC_DETECT_IRQ] = { + .name = "typec-try-snk-src-detect", + }, + /* MISCELLANEOUS IRQs */ + [WDOG_SNARL_IRQ] = { + .name = "wdog-snarl", + }, + [WDOG_BARK_IRQ] = { + .name = "wdog-bark", + .handler = smblite_wdog_bark_irq_handler, + .wake = true, + }, + [AICL_FAIL_IRQ] = { + .name = "aicl-fail", + }, + [AICL_DONE_IRQ] = { + .name = "aicl-done", + }, + [IMP_TRIGGER_IRQ] = { + .name = "imp-trigger", + }, + [ALL_CHNL_CONV_DONE_IRQ] = { + .name = "all-chnl-cond-done", + }, + /* + * triggered when DIE temperature across either of the + * _REG_L, _REG_H, _RST, or _SHDN thresholds. + */ + [TEMP_CHANGE_IRQ] = { + .name = "temp-change", + .handler = smblite_temp_change_irq_handler, + .wake = true, + }, + /* FLASH */ + [VREG_OK_IRQ] = { + .name = "vreg-ok", + }, + [ILIM_S2_IRQ] = { + .name = "ilim2-s2", + .handler = schgm_flashlite_ilim2_irq_handler, + }, + [ILIM_S1_IRQ] = { + .name = "ilim1-s1", + }, + [FLASH_STATE_CHANGE_IRQ] = { + .name = "flash-state-change", + .handler = schgm_flashlite_state_change_irq_handler, + }, + [TORCH_REQ_IRQ] = { + .name = "torch-req", + }, + [FLASH_EN_IRQ] = { + .name = "flash-en", + }, +}; + +static int smblite_get_irq_index_byname(const char *irq_name) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(smblite_irqs); i++) { + if (strcmp(smblite_irqs[i].name, irq_name) == 0) + return i; + } + + return -ENOENT; +} + +static int smblite_request_interrupt(struct smblite *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 = smblite_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 (!smblite_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 = smblite_irqs[irq_index].storm_data; + mutex_init(&irq_data->storm_data.storm_lock); + + smblite_irqs[irq_index].enabled = true; + rc = devm_request_threaded_irq(chg->dev, irq, NULL, + smblite_irqs[irq_index].handler, + IRQF_ONESHOT, irq_name, irq_data); + if (rc < 0) { + pr_err("Couldn't request irq %d\n", irq); + return rc; + } + + smblite_irqs[irq_index].irq = irq; + smblite_irqs[irq_index].irq_data = irq_data; + if (smblite_irqs[irq_index].wake) + enable_irq_wake(irq); + + return rc; +} + +static int smblite_request_interrupts(struct smblite *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 = smblite_request_interrupt(chip, child, name); + if (rc < 0) + return rc; + } + } + + /* register the USB-id irq */ + if (chg->usb_id_irq > 0 && chg->usb_id_gpio > 0) { + rc = devm_request_threaded_irq(chg->dev, + chg->usb_id_irq, NULL, + smblite_usb_id_irq_handler, + IRQF_ONESHOT + | IRQF_TRIGGER_FALLING + | IRQF_TRIGGER_RISING, + "smblite_id_irq", chg); + if (rc < 0) { + pr_err("Failed to register id-irq rc=%d\n", rc); + return rc; + } + enable_irq_wake(chg->usb_id_irq); + } + + return rc; +} + +static void smblite_disable_interrupts(struct smb_charger *chg) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(smblite_irqs); i++) { + if (smblite_irqs[i].irq > 0) { + if (smblite_irqs[i].wake) + disable_irq_wake(smblite_irqs[i].irq); + disable_irq(smblite_irqs[i].irq); + } + } + + if (chg->usb_id_irq > 0 && chg->usb_id_gpio > 0) { + disable_irq_wake(chg->usb_id_irq); + disable_irq(chg->usb_id_irq); + } +} + +#if defined(CONFIG_DEBUG_FS) + +static int force_batt_psy_update_write(void *data, u64 val) +{ + struct smb_charger *chg = data; + + power_supply_changed(chg->batt_psy); + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(force_batt_psy_update_ops, NULL, + force_batt_psy_update_write, "0x%02llx\n"); + +static int force_usb_psy_update_write(void *data, u64 val) +{ + struct smb_charger *chg = data; + + power_supply_changed(chg->usb_psy); + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(force_usb_psy_update_ops, NULL, + force_usb_psy_update_write, "0x%02llx\n"); + +static void smblite_create_debugfs(struct smblite *chip) +{ + struct dentry *file; + + chip->dfs_root = debugfs_create_dir("charger", NULL); + if (IS_ERR_OR_NULL(chip->dfs_root)) { + pr_err("Couldn't create charger debugfs rc=%ld\n", + (long)chip->dfs_root); + return; + } + + file = debugfs_create_file("force_batt_psy_update", 0600, + chip->dfs_root, chip, &force_batt_psy_update_ops); + if (IS_ERR_OR_NULL(file)) + pr_err("Couldn't create force_batt_psy_update file rc=%ld\n", + (long)file); + + file = debugfs_create_file("force_usb_psy_update", 0600, + chip->dfs_root, chip, &force_usb_psy_update_ops); + if (IS_ERR_OR_NULL(file)) + pr_err("Couldn't create force_usb_psy_update file rc=%ld\n", + (long)file); + + file = debugfs_create_u32("debug_mask", 0600, chip->dfs_root, + &__debug_mask); + if (IS_ERR_OR_NULL(file)) + pr_err("Couldn't create debug_mask file rc=%ld\n", (long)file); +} + +#else + +static void smblite_create_debugfs(struct smblite *chip) +{} + +#endif + +static int smblite_show_charger_status(struct smblite *chip) +{ + struct smb_charger *chg = &chip->chg; + union power_supply_propval val; + int usb_present, batt_present, batt_health, batt_charge_type; + int rc; + + rc = smblite_lib_get_prop_usb_present(chg, &val); + if (rc < 0) { + pr_err("Couldn't get usb present rc=%d\n", rc); + return rc; + } + usb_present = val.intval; + + rc = smblite_lib_get_prop_batt_present(chg, &val); + if (rc < 0) { + pr_err("Couldn't get batt present rc=%d\n", rc); + return rc; + } + batt_present = val.intval; + + rc = smblite_lib_get_prop_batt_health(chg, &val); + if (rc < 0) { + pr_err("Couldn't get batt health rc=%d\n", rc); + val.intval = POWER_SUPPLY_HEALTH_UNKNOWN; + } + batt_health = val.intval; + + rc = smblite_lib_get_prop_batt_charge_type(chg, &val); + if (rc < 0) { + pr_err("Couldn't get batt charge type rc=%d\n", rc); + return rc; + } + batt_charge_type = val.intval; + + pr_info("SMBLITE: Mode=%s Conn=%s USB Present=%d Battery present=%d health=%d charge=%d\n", + chg->ldo_mode ? "LDO" : "SMBC", + (chg->connector_type == POWER_SUPPLY_CONNECTOR_TYPEC) ? + "TYPEC" : "uUSB", usb_present, batt_present, + batt_health, batt_charge_type); + return rc; +} + +/********************************* + * TYPEC CLASS REGISTRATION * + **********************************/ + +static int smblite_init_typec_class(struct smblite *chip) +{ + struct smb_charger *chg = &chip->chg; + int rc = 0; + + mutex_init(&chg->typec_lock); + chg->typec_caps.type = TYPEC_PORT_DRP; + chg->typec_caps.data = TYPEC_PORT_DRD; + chg->typec_partner_desc.usb_pd = false; + chg->typec_partner_desc.accessory = TYPEC_ACCESSORY_NONE; + chg->typec_caps.port_type_set = smblite_lib_typec_port_type_set; + chg->typec_caps.revision = 0x0130; + + chg->typec_port = typec_register_port(chg->dev, &chg->typec_caps); + if (IS_ERR(chg->typec_port)) { + rc = PTR_ERR(chg->typec_port); + pr_err("Couldn't to register typec_port rc=%d\n", rc); + return rc; + } + + return rc; +} + +static int smblite_probe(struct platform_device *pdev) +{ + struct smblite *chip; + struct smb_charger *chg; + int rc = 0; + + chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chg = &chip->chg; + chg->dev = &pdev->dev; + chg->debug_mask = &__debug_mask; + chg->weak_chg_icl_ua = 500000; + chg->mode = PARALLEL_MASTER; + chg->irq_info = smblite_irqs; + chg->otg_present = false; + chg->step_chg_enabled = true; + + chg->regmap = dev_get_regmap(chg->dev->parent, NULL); + if (!chg->regmap) { + pr_err("parent regmap is missing\n"); + return -EINVAL; + } + + rc = smblite_chg_config_init(chip); + if (rc < 0) { + if (rc != -EPROBE_DEFER) + pr_err("Couldn't setup chg_config rc=%d\n", rc); + return rc; + } + + rc = smblite_parse_dt(chip); + if (rc < 0) { + pr_err("Couldn't parse device tree rc=%d\n", rc); + return rc; + } + + rc = smblite_lib_init(chg); + if (rc < 0) { + pr_err("Smblib_init failed rc=%d\n", rc); + return rc; + } + + /* set driver data before resources request it */ + platform_set_drvdata(pdev, chip); + + /* extcon registration */ + chg->extcon = devm_extcon_dev_allocate(chg->dev, + smblite_lib_extcon_cable); + if (IS_ERR(chg->extcon)) { + rc = PTR_ERR(chg->extcon); + dev_err(chg->dev, "Couldn't to allocate extcon device rc=%d\n", + rc); + goto cleanup; + } + + rc = devm_extcon_dev_register(chg->dev, chg->extcon); + if (rc < 0) { + dev_err(chg->dev, "Couldn't to register extcon device rc=%d\n", + rc); + goto cleanup; + } + + /* Support reporting polarity and speed via properties */ + rc = extcon_set_property_capability(chg->extcon, + EXTCON_USB, EXTCON_PROP_USB_TYPEC_POLARITY); + rc |= extcon_set_property_capability(chg->extcon, + EXTCON_USB, EXTCON_PROP_USB_SS); + rc |= extcon_set_property_capability(chg->extcon, + EXTCON_USB_HOST, EXTCON_PROP_USB_TYPEC_POLARITY); + rc |= extcon_set_property_capability(chg->extcon, + EXTCON_USB_HOST, EXTCON_PROP_USB_SS); + if (rc < 0) { + dev_err(chg->dev, + "Couldn't to configure extcon capabilities\n"); + goto cleanup; + } + + rc = smblite_init_hw(chip); + if (rc < 0) { + pr_err("Couldn't initialize hardware rc=%d\n", rc); + goto cleanup; + } + + rc = smblite_init_usb_psy(chip); + if (rc < 0) { + pr_err("Couldn't initialize usb psy rc=%d\n", rc); + goto cleanup; + } + + rc = smblite_init_usb_main_psy(chip); + if (rc < 0) { + pr_err("Couldn't initialize usb main psy rc=%d\n", rc); + goto cleanup; + } + + rc = smblite_init_batt_psy(chip); + if (rc < 0) { + pr_err("Couldn't initialize batt psy rc=%d\n", rc); + goto cleanup; + } + + rc = smblite_init_typec_class(chip); + if (rc < 0) { + pr_err("Couldn't initialize typec class rc=%d\n", rc); + goto cleanup; + } + + rc = smblite_determine_initial_status(chip); + if (rc < 0) { + pr_err("Couldn't determine initial status rc=%d\n", + rc); + goto cleanup; + } + + rc = smblite_request_interrupts(chip); + if (rc < 0) { + pr_err("Couldn't request interrupts rc=%d\n", rc); + goto cleanup; + } + + rc = smblite_post_init(chip); + if (rc < 0) { + pr_err("Couldn't in post init rc=%d\n", rc); + goto disable_irq; + } + + smblite_create_debugfs(chip); + + rc = sysfs_create_groups(&chg->dev->kobj, smblite_groups); + if (rc < 0) { + pr_err("Couldn't create sysfs files rc=%d\n", rc); + goto disable_irq; + } + + rc = smblite_show_charger_status(chip); + if (rc < 0) { + pr_err("Couldn't in getting charger status rc=%d\n", rc); + goto disable_irq; + } + + device_init_wakeup(chg->dev, true); + + return rc; + +disable_irq: + smblite_disable_interrupts(chg); +cleanup: + smblite_lib_deinit(chg); + platform_set_drvdata(pdev, NULL); + + return rc; +} + +static int smblite_remove(struct platform_device *pdev) +{ + struct smblite *chip = platform_get_drvdata(pdev); + struct smb_charger *chg = &chip->chg; + + smblite_disable_interrupts(chg); + smblite_lib_deinit(chg); + sysfs_remove_groups(&chg->dev->kobj, smblite_groups); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static void smblite_shutdown(struct platform_device *pdev) +{ + struct smblite *chip = platform_get_drvdata(pdev); + struct smb_charger *chg = &chip->chg; + + /* disable all interrupts */ + smblite_disable_interrupts(chg); + + /* configure power role for UFP */ + if (chg->connector_type == POWER_SUPPLY_CONNECTOR_TYPEC) + smblite_lib_masked_write(chg, TYPE_C_MODE_CFG_REG, + TYPEC_POWER_ROLE_CMD_MASK, EN_SNK_ONLY_BIT); +} + +static const struct of_device_id match_table[] = { + { .compatible = "qcom,qpnp-smblite", }, + { }, +}; + +static struct platform_driver smblite_driver = { + .driver = { + .name = "qcom,qpnp-smblite", + .of_match_table = match_table, + }, + .probe = smblite_probe, + .remove = smblite_remove, + .shutdown = smblite_shutdown, +}; +module_platform_driver(smblite_driver); + +MODULE_DESCRIPTION("QPNP SMBLITE Charger Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/power/supply/qcom/schgm-flashlite.c b/drivers/power/supply/qcom/schgm-flashlite.c new file mode 100644 index 000000000000..4b3e4cd6e4e2 --- /dev/null +++ b/drivers/power/supply/qcom/schgm-flashlite.c @@ -0,0 +1,220 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2018, 2020 The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "SCHG-FLASH: %s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "smblite-lib.h" +#include "schgm-flashlite.h" + +#define IS_BETWEEN(left, right, value) \ + (((left) >= (right) && (left) >= (value) \ + && (value) >= (right)) \ + || ((left) <= (right) && (left) <= (value) \ + && (value) <= (right))) + +irqreturn_t schgm_flashlite_default_irq_handler(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + + pr_debug("IRQ: %s\n", irq_data->name); + + return IRQ_HANDLED; +} + +irqreturn_t schgm_flashlite_ilim2_irq_handler(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + int rc; + + rc = smblite_lib_write(chg, SCHGM_FLASH_S2_LATCH_RESET_CMD_REG, + FLASH_S2_LATCH_RESET_BIT); + if (rc < 0) + pr_err("Couldn't reset S2_LATCH reset rc=%d\n", rc); + + return IRQ_HANDLED; +} + +irqreturn_t schgm_flashlite_state_change_irq_handler(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + int rc; + u8 reg; + + rc = smblite_lib_read(chg, SCHGM_FLASH_STATUS_3_REG, ®); + if (rc < 0) + pr_err("Couldn't read flash status_3 rc=%d\n", rc); + else + pr_debug("Flash status changed state=[%x]\n", + (reg && FLASH_STATE_MASK)); + + return IRQ_HANDLED; +} + +#define FIXED_MODE 0 +#define ADAPTIVE_MODE 1 +static void schgm_flashlite_parse_dt(struct smb_charger *chg) +{ + struct device_node *node = chg->dev->of_node; + u32 val; + int rc; + + chg->flash_derating_soc = -EINVAL; + rc = of_property_read_u32(node, "qcom,flash-derating-soc", &val); + if (!rc) { + if (IS_BETWEEN(0, 100, val)) + chg->flash_derating_soc = (val * 255) / 100; + } + + chg->flash_disable_soc = -EINVAL; + rc = of_property_read_u32(node, "qcom,flash-disable-soc", &val); + if (!rc) { + if (IS_BETWEEN(0, 100, val)) + chg->flash_disable_soc = (val * 255) / 100; + } +} + +bool is_flashlite_active(struct smb_charger *chg) +{ + return chg->flash_active ? true : false; +} + +int schgm_flashlite_get_vreg_ok(struct smb_charger *chg, int *val) +{ + int rc, vreg_state; + u8 stat = 0; + + if (!chg->flash_init_done) + return -EPERM; + + rc = smblite_lib_read(chg, SCHGM_FLASH_STATUS_2_REG, &stat); + if (rc < 0) { + pr_err("Couldn't read FLASH STATUS_2 rc=%d\n", rc); + return rc; + } + vreg_state = !!(stat & VREG_OK_BIT); + + /* If VREG_OK is not set check for flash error */ + if (!vreg_state) { + rc = smblite_lib_read(chg, SCHGM_FLASH_STATUS_3_REG, &stat); + if (rc < 0) { + pr_err("Couldn't read FLASH_STATUS_3 rc=%d\n", rc); + return rc; + } + if ((stat & FLASH_STATE_MASK) == FLASH_ERROR_VAL) { + vreg_state = -EFAULT; + rc = smblite_lib_read(chg, SCHGM_FLASH_STATUS_5_REG, + &stat); + if (rc < 0) { + pr_err("Couldn't read FLASH_STATUS_5 rc=%d\n", + rc); + return rc; + } + pr_debug("Flash error: status=%x\n", stat); + } + } + + /* + * val can be one of the following: + * 1 - VREG_OK is set. + * 0 - VREG_OK is 0 but no Flash error. + * -EFAULT - Flash Error is set. + */ + *val = vreg_state; + + return 0; +} + +void schgm_flashlite_torch_priority(struct smb_charger *chg, + enum torch_mode mode) +{ + int rc; + u8 reg; + + if ((mode != TORCH_BOOST_MODE) && (mode != TORCH_BUCK_MODE)) + return; + + reg = mode; + rc = smblite_lib_masked_write(chg, SCHGM_TORCH_PRIORITY_CONTROL_REG, + TORCH_PRIORITY_CONTROL_BIT, reg); + if (rc < 0) + pr_err("Couldn't configure Torch priority control rc=%d\n", + rc); + + pr_debug("Torch priority changed to: %d\n", mode); +} + +int schgm_flashlite_config_usbin_collapse(struct smb_charger *chg, bool enable) +{ + return smblite_lib_masked_write(chg, SCHG_L_FLASH_FLASH_FAULT_CFG, + CFG_FLASH_USB_COLLAPSE_BIT, enable ? + CFG_FLASH_USB_COLLAPSE_BIT : 0); +} + +int schgm_flashlite_init(struct smb_charger *chg) +{ + int rc; + u8 reg, mask; + + schgm_flashlite_parse_dt(chg); + + if (chg->wa_flags & FLASH_DIE_TEMP_DERATE_WA) { + mask = TEMP_DIE_REG_L_DERATE_EN_BIT | + TEMP_DIE_REG_H_DERATE_EN_BIT; + rc = smblite_lib_masked_write(chg, SCHGM_FLASH_CONTROL_REG, + mask, 0); + if (rc < 0) + pr_err("Couldn't disable die-temp derate rc=%d\n", rc); + } + + if (chg->flash_derating_soc != -EINVAL) { + rc = smblite_lib_write(chg, + SCHGM_SOC_BASED_FLASH_DERATE_TH_CFG_REG, + chg->flash_derating_soc); + if (rc < 0) { + pr_err("Couldn't configure SOC for flash derating rc=%d\n", + rc); + return rc; + } + } + + if (chg->flash_disable_soc != -EINVAL) { + rc = smblite_lib_write(chg, + SCHGM_SOC_BASED_FLASH_DISABLE_TH_CFG_REG, + chg->flash_disable_soc); + if (rc < 0) { + pr_err("Couldn't configure SOC for flash disable rc=%d\n", + rc); + return rc; + } + } + + if ((chg->flash_derating_soc != -EINVAL) + || (chg->flash_disable_soc != -EINVAL)) { + /* Check if SOC based derating/disable is enabled */ + rc = smblite_lib_read(chg, SCHGM_FLASH_CONTROL_REG, ®); + if (rc < 0) { + pr_err("Couldn't read flash control reg rc=%d\n", rc); + return rc; + } + if (!(reg & SOC_LOW_FOR_FLASH_EN_BIT)) + pr_warn("Soc based flash derating not enabled\n"); + } + + chg->flash_init_done = true; + + return 0; +} diff --git a/drivers/power/supply/qcom/schgm-flashlite.h b/drivers/power/supply/qcom/schgm-flashlite.h new file mode 100644 index 000000000000..c71a42bb8766 --- /dev/null +++ b/drivers/power/supply/qcom/schgm-flashlite.h @@ -0,0 +1,59 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2018, 2020 The Linux Foundation. All rights reserved. + */ + +#ifndef __SCHGM_FLASHLITE_H__ +#define __SCHGM_FLASHLITE_H__ + +#include + +#define SCHGM_FLASH_BASE 0xA600 + +#define SCHGM_FLASH_STATUS_2_REG (SCHGM_FLASH_BASE + 0x07) +#define VREG_OK_BIT BIT(4) + +#define SCHGM_FLASH_STATUS_3_REG (SCHGM_FLASH_BASE + 0x08) +#define FLASH_STATE_MASK GENMASK(2, 0) +#define FLASH_ERROR_VAL 0x7 + +#define SCHGM_FLASH_INT_RT_STS_REG (SCHGM_FLASH_BASE + 0x10) + +#define SCHGM_FLASH_STATUS_5_REG (SCHGM_FLASH_BASE + 0x0B) + +#define SCHGM_FLASH_S2_LATCH_RESET_CMD_REG (SCHGM_FLASH_BASE + 0x44) +#define FLASH_S2_LATCH_RESET_BIT BIT(0) + +#define SCHGM_FLASH_CONTROL_REG (SCHGM_FLASH_BASE + 0x60) +#define SOC_LOW_FOR_FLASH_EN_BIT BIT(7) +#define TEMP_DIE_REG_H_DERATE_EN_BIT BIT(3) +#define TEMP_DIE_REG_L_DERATE_EN_BIT BIT(2) + +#define SCHGM_TORCH_PRIORITY_CONTROL_REG (SCHGM_FLASH_BASE + 0x63) +#define TORCH_PRIORITY_CONTROL_BIT BIT(0) + +#define SCHG_L_FLASH_FLASH_FAULT_CFG (SCHGM_FLASH_BASE + 0x64) +#define CFG_FLASH_USB_COLLAPSE_BIT BIT(7) + +#define SCHGM_SOC_BASED_FLASH_DERATE_TH_CFG_REG (SCHGM_FLASH_BASE + 0x67) + +#define SCHGM_SOC_BASED_FLASH_DISABLE_TH_CFG_REG \ + (SCHGM_FLASH_BASE + 0x68) + +enum torch_mode { + TORCH_BUCK_MODE = 0, + TORCH_BOOST_MODE, +}; + +int schgm_flashlite_get_vreg_ok(struct smb_charger *chg, int *val); +void schgm_flashlite_torch_priority(struct smb_charger *chg, + enum torch_mode mode); +int schgm_flashlite_config_usbin_collapse(struct smb_charger *chg, + bool enable); +int schgm_flashlite_init(struct smb_charger *chg); +bool is_flashlite_active(struct smb_charger *chg); + +irqreturn_t schgm_flashlite_default_irq_handler(int irq, void *data); +irqreturn_t schgm_flashlite_ilim2_irq_handler(int irq, void *data); +irqreturn_t schgm_flashlite_state_change_irq_handler(int irq, void *data); +#endif /* __SCHGM_FLASHLITE_H__ */ diff --git a/drivers/power/supply/qcom/smblite-lib.c b/drivers/power/supply/qcom/smblite-lib.c new file mode 100644 index 000000000000..0d1690ca10b6 --- /dev/null +++ b/drivers/power/supply/qcom/smblite-lib.c @@ -0,0 +1,3425 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020 The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "smblite-lib.h" +#include "smblite-reg.h" +#include "step-chg-jeita.h" +#include "storm-watch.h" +#include "schgm-flashlite.h" + +#define smblite_lib_err(chg, fmt, ...) \ + pr_err("%s: %s: " fmt, chg->name, \ + __func__, ##__VA_ARGS__) \ + +#define smblite_lib_dbg(chg, reason, fmt, ...) \ + do { \ + if (*chg->debug_mask & (reason)) \ + pr_info("%s: %s: " fmt, chg->name, \ + __func__, ##__VA_ARGS__); \ + else \ + pr_debug("%s: %s: " fmt, chg->name, \ + __func__, ##__VA_ARGS__); \ + } while (0) + +#define typec_rp_med_high(chg, typec_mode) \ + ((typec_mode == POWER_SUPPLY_TYPEC_SOURCE_MEDIUM \ + || typec_mode == POWER_SUPPLY_TYPEC_SOURCE_HIGH) \ + && (!chg->typec_legacy || chg->typec_legacy_use_rp_icl)) + +static void update_sw_icl_max(struct smb_charger *chg, + enum power_supply_type type); +static int smblite_lib_get_prop_typec_mode(struct smb_charger *chg); + +int smblite_lib_read(struct smb_charger *chg, u16 addr, u8 *val) +{ + unsigned int value; + int rc = 0; + + rc = regmap_read(chg->regmap, addr, &value); + if (rc >= 0) + *val = (u8)value; + + return rc; +} + +int smblite_lib_batch_read(struct smb_charger *chg, u16 addr, u8 *val, + int count) +{ + return regmap_bulk_read(chg->regmap, addr, val, count); +} + +int smblite_lib_write(struct smb_charger *chg, u16 addr, u8 val) +{ + return regmap_write(chg->regmap, addr, val); +} + +int smblite_lib_batch_write(struct smb_charger *chg, u16 addr, u8 *val, + int count) +{ + return regmap_bulk_write(chg->regmap, addr, val, count); +} + +int smblite_lib_masked_write(struct smb_charger *chg, u16 addr, u8 mask, u8 val) +{ + return regmap_update_bits(chg->regmap, addr, mask, val); +} + +int smblite_lib_get_iio_channel(struct smb_charger *chg, const char *propname, + struct iio_channel **chan) +{ + int rc = 0; + + rc = of_property_match_string(chg->dev->of_node, + "io-channel-names", propname); + if (rc < 0) + return 0; + + *chan = iio_channel_get(chg->dev, propname); + if (IS_ERR(*chan)) { + rc = PTR_ERR(*chan); + if (rc != -EPROBE_DEFER) + smblite_lib_err(chg, "%s channel unavailable, %d\n", + propname, rc); + *chan = NULL; + } + + return rc; +} + +#define DIV_FACTOR_MICRO_V_I 1 +#define DIV_FACTOR_MILI_V_I 1000 +#define DIV_FACTOR_DECIDEGC 100 +int smblite_lib_read_iio_channel(struct smb_charger *chg, + struct iio_channel *chan, int div, int *data) +{ + int rc = 0; + *data = -ENODATA; + + if (chan) { + rc = iio_read_channel_processed(chan, data); + if (rc < 0) { + smblite_lib_err(chg, "Error in reading IIO channel data, rc=%d\n", + rc); + return rc; + } + + if (div != 0) + *data /= div; + } + + return rc; +} + +int smblite_lib_icl_override(struct smb_charger *chg, + enum icl_override_mode mode) +{ + int rc; + u8 usb51_mode, icl_override; + + switch (mode) { + case SW_OVERRIDE_USB51_MODE: + usb51_mode = 0; + icl_override = ICL_OVERRIDE_BIT; + break; + case SW_OVERRIDE_HC_MODE: + usb51_mode = USBIN_MODE_CHG_BIT; + icl_override = ICL_OVERRIDE_BIT; + break; + case HW_AUTO_MODE: + default: + usb51_mode = USBIN_MODE_CHG_BIT; + icl_override = 0; + break; + } + + rc = smblite_lib_masked_write(chg, USBIN_ICL_OPTIONS_REG, + USBIN_MODE_CHG_BIT, usb51_mode); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't set USBIN_ICL_OPTIONS rc=%d\n", + rc); + return rc; + } + + rc = smblite_lib_masked_write(chg, CMD_ICL_OVERRIDE_REG, + ICL_OVERRIDE_BIT, icl_override); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't override ICL rc=%d\n", rc); + return rc; + } + + return rc; +} + +static void smblite_lib_notify_extcon_props(struct smb_charger *chg, int id) +{ + union extcon_property_value val; + union power_supply_propval prop_val; + + if (chg->connector_type == POWER_SUPPLY_CONNECTOR_TYPEC) { + smblite_lib_get_prop_typec_cc_orientation(chg, &prop_val); + val.intval = ((prop_val.intval == 2) ? 1 : 0); + extcon_set_property(chg->extcon, id, + EXTCON_PROP_USB_TYPEC_POLARITY, val); + val.intval = true; + extcon_set_property(chg->extcon, id, + EXTCON_PROP_USB_SS, val); + } else if (chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB) { + val.intval = false; + extcon_set_property(chg->extcon, id, + EXTCON_PROP_USB_SS, val); + } +} + +static void smblite_lib_notify_device_mode(struct smb_charger *chg, bool enable) +{ + if (enable) + smblite_lib_notify_extcon_props(chg, EXTCON_USB); + + extcon_set_state_sync(chg->extcon, EXTCON_USB, enable); +} + +#define VBOOST_5P00V 0x03 +static void smblite_lib_notify_usb_host(struct smb_charger *chg, bool enable) +{ + int rc = 0; + + /* LDO mode doesn't support OTG */ + if (chg->ldo_mode) + return; + + if (enable) { + smblite_lib_dbg(chg, PR_OTG, "enabling VBUS in OTG mode\n"); + rc = smblite_lib_masked_write(chg, DCDC_CMD_OTG_REG, + OTG_EN_BIT, OTG_EN_BIT); + if (rc < 0) { + smblite_lib_err(chg, + "Couldn't enable VBUS in OTG mode rc=%d\n", rc); + return; + } + rc = smblite_lib_masked_write(chg, DCDC_BST_VREG_SEL, + VBOOST_MASK, VBOOST_5P00V); + if (rc < 0) { + smblite_lib_err(chg, + "Couldn't write BST_VREG_SEL rc=%d\n", rc); + return; + } + smblite_lib_notify_extcon_props(chg, EXTCON_USB_HOST); + } else { + smblite_lib_dbg(chg, PR_OTG, "disabling VBUS in OTG mode\n"); + rc = smblite_lib_masked_write(chg, DCDC_CMD_OTG_REG, + OTG_EN_BIT, 0); + if (rc < 0) { + smblite_lib_err(chg, + "Couldn't disable VBUS in OTG mode rc=%d\n", + rc); + return; + } + } + + extcon_set_state_sync(chg->extcon, EXTCON_USB_HOST, enable); + chg->otg_present = enable; +} + +/******************** + * REGISTER GETTERS * + ********************/ + +int smblite_lib_get_charge_param(struct smb_charger *chg, + struct smb_chg_param *param, int *val_u) +{ + int rc = 0; + u8 val_raw; + + rc = smblite_lib_read(chg, param->reg, &val_raw); + if (rc < 0) { + smblite_lib_err(chg, "%s: Couldn't read from 0x%04x rc=%d\n", + param->name, param->reg, rc); + return rc; + } + + if (param->get_proc) + *val_u = param->get_proc(param, val_raw); + else + *val_u = val_raw * param->step_u + param->min_u; + smblite_lib_dbg(chg, PR_REGISTER, "%s = %d (0x%02x)\n", + param->name, *val_u, val_raw); + + return rc; +} + +int smblite_lib_get_usb_suspend(struct smb_charger *chg, int *suspend) +{ + int rc = 0; + u8 temp; + + rc = smblite_lib_read(chg, USBIN_INPUT_SUSPEND_REG, &temp); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't read USBIN_CMD_IL rc=%d\n", rc); + return rc; + } + *suspend = temp & USBIN_SUSPEND_BIT; + + return rc; +} + +#define INPUT_NOT_PRESENT 0 +#define INPUT_PRESENT_USB BIT(1) +static int smblite_lib_is_input_present(struct smb_charger *chg, + int *present) +{ + int rc; + union power_supply_propval pval = {0, }; + + *present = INPUT_NOT_PRESENT; + + rc = smblite_lib_get_prop_usb_present(chg, &pval); + if (rc < 0) { + pr_err("Couldn't get usb presence status rc=%d\n", rc); + return rc; + } + *present |= pval.intval ? INPUT_PRESENT_USB : INPUT_NOT_PRESENT; + + return 0; +} + +/******************** + * REGISTER SETTERS * + ********************/ +int smblite_lib_set_charge_param(struct smb_charger *chg, + struct smb_chg_param *param, int val_u) +{ + int rc = 0; + u8 val_raw; + + if (param->set_proc) { + rc = param->set_proc(param, val_u, &val_raw); + if (rc < 0) + return -EINVAL; + } else { + if (val_u > param->max_u || val_u < param->min_u) + smblite_lib_dbg(chg, PR_MISC, + "%s: %d is out of range [%d, %d]\n", + param->name, val_u, param->min_u, param->max_u); + + if (val_u > param->max_u) + val_u = param->max_u; + if (val_u < param->min_u) + val_u = param->min_u; + + val_raw = (val_u - param->min_u) / param->step_u; + } + + rc = smblite_lib_write(chg, param->reg, val_raw); + if (rc < 0) { + smblite_lib_err(chg, "%s: Couldn't write 0x%02x to 0x%04x rc=%d\n", + param->name, val_raw, param->reg, rc); + return rc; + } + + smblite_lib_dbg(chg, PR_REGISTER, "%s = %d (0x%02x)\n", + param->name, val_u, val_raw); + + return rc; +} + +int smblite_lib_set_usb_suspend(struct smb_charger *chg, bool suspend) +{ + int rc = 0; + + if (suspend) + vote(chg->icl_irq_disable_votable, USB_SUSPEND_VOTER, + true, 0); + + rc = smblite_lib_masked_write(chg, USBIN_INPUT_SUSPEND_REG, + USBIN_SUSPEND_BIT, + suspend ? USBIN_SUSPEND_BIT : 0); + if (rc < 0) + smblite_lib_err(chg, "Couldn't write %s to USBIN_SUSPEND_BIT rc=%d\n", + suspend ? "suspend" : "resume", rc); + + if (!suspend) + vote(chg->icl_irq_disable_votable, USB_SUSPEND_VOTER, + false, 0); + + return rc; +} + +/******************** + * HELPER FUNCTIONS * + ********************/ +int smblite_lib_get_prop_from_bms(struct smb_charger *chg, + enum power_supply_property psp, + union power_supply_propval *val) +{ + int rc; + + if (!chg->bms_psy) + return -EINVAL; + + rc = power_supply_get_property(chg->bms_psy, psp, val); + + return rc; +} + +static void smblite_lib_update_usb_type(struct smb_charger *chg, + enum power_supply_type type) +{ + chg->real_charger_type = type; + smblite_update_usb_desc(chg); +} + +static int smblite_lib_notifier_call(struct notifier_block *nb, + unsigned long ev, void *v) +{ + struct power_supply *psy = v; + struct smb_charger *chg = container_of(nb, struct smb_charger, nb); + + if (!strcmp(psy->desc->name, "bms")) { + if (!chg->bms_psy) + chg->bms_psy = psy; + if (ev == PSY_EVENT_PROP_CHANGED) + schedule_work(&chg->bms_update_work); + } + + if (chg->jeita_configured == JEITA_CFG_NONE) + schedule_work(&chg->jeita_update_work); + + return NOTIFY_OK; +} + +static int smblite_lib_register_notifier(struct smb_charger *chg) +{ + int rc; + + chg->nb.notifier_call = smblite_lib_notifier_call; + rc = power_supply_reg_notifier(&chg->nb); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't register psy notifier rc = %d\n", + rc); + return rc; + } + + return 0; +} + +static void smblite_lib_uusb_removal(struct smb_charger *chg) +{ + int rc; + struct smb_irq_data *data; + struct storm_watch *wdata; + + cancel_delayed_work_sync(&chg->pl_enable_work); + + if (chg->wa_flags & BOOST_BACK_WA) { + data = chg->irq_info[SWITCHER_POWER_OK_IRQ].irq_data; + if (data) { + wdata = &data->storm_data; + update_storm_count(wdata, WEAK_CHG_STORM_COUNT); + vote(chg->usb_icl_votable, BOOST_BACK_VOTER, false, 0); + vote(chg->usb_icl_votable, WEAK_CHARGER_VOTER, + false, 0); + } + } + vote(chg->pl_disable_votable, PL_DELAY_VOTER, true, 0); + vote(chg->awake_votable, PL_DELAY_VOTER, false, 0); + + /* reset both usbin current and voltage votes */ + vote(chg->pl_enable_votable_indirect, USBIN_I_VOTER, false, 0); + vote(chg->pl_enable_votable_indirect, USBIN_V_VOTER, false, 0); + vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, true, + is_flashlite_active(chg) ? USBIN_500UA : USBIN_100UA); + vote(chg->usb_icl_votable, FLASH_ACTIVE_VOTER, false, 0); + + /* Remove SW thermal regulation votes */ + vote(chg->usb_icl_votable, SW_THERM_REGULATION_VOTER, false, 0); + vote(chg->awake_votable, SW_THERM_REGULATION_VOTER, false, 0); + + /* clear USB ICL vote for USB_PSY_VOTER */ + rc = vote(chg->usb_icl_votable, USB_PSY_VOTER, false, 0); + if (rc < 0) + smblite_lib_err(chg, "Couldn't un-vote for USB ICL rc=%d\n", + rc); +} + +void smblite_lib_suspend_on_debug_battery(struct smb_charger *chg) +{ + int rc; + union power_supply_propval val; + + rc = smblite_lib_get_prop_from_bms(chg, + POWER_SUPPLY_PROP_DEBUG_BATTERY, &val); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't get debug battery prop rc=%d\n", + rc); + return; + } + if (chg->suspend_input_on_debug_batt) { + vote(chg->usb_icl_votable, DEBUG_BOARD_VOTER, val.intval, 0); + if (val.intval) { + pr_info("Input suspended: Fake battery\n"); + schgm_flashlite_config_usbin_collapse(chg, false); + } + } else { + vote(chg->chg_disable_votable, DEBUG_BOARD_VOTER, + val.intval, 0); + } +} + +static int set_sdp_current(struct smb_charger *chg, int icl_ua) +{ + int rc; + u8 icl_options; + enum icl_override_mode icl_override = SW_OVERRIDE_USB51_MODE; + + /* power source is SDP */ + switch (icl_ua) { + case USBIN_100UA: + case USBIN_150UA: + /* USB 2.0 100mA */ + icl_options = 0; + break; + case USBIN_500UA: + icl_options = USB51_MODE_BIT; + break; + default: + /* Use ICL configuration register for HC_MODE*/ + icl_override = SW_OVERRIDE_HC_MODE; + icl_options = USBIN_MODE_CHG_BIT; + break; + } + + rc = smblite_lib_set_charge_param(chg, &chg->param.usb_icl, + icl_ua); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't set HC ICL rc=%d\n", rc); + return rc; + } + + rc = smblite_lib_masked_write(chg, + USBIN_ICL_OPTIONS_REG, USB51_MODE_BIT, icl_options); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't set ICL options rc=%d\n", rc); + return rc; + } + + rc = smblite_lib_icl_override(chg, icl_override); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't set ICL override rc=%d\n", rc); + return rc; + } + + return rc; +} + +int smblite_lib_set_icl_current(struct smb_charger *chg, int icl_ua) +{ + int rc = 0; + enum icl_override_mode icl_override = HW_AUTO_MODE; + /* suspend if 25mA or less is requested */ + bool suspend = (icl_ua <= USBIN_25UA); + + schgm_flashlite_torch_priority(chg, suspend ? TORCH_BOOST_MODE : + TORCH_BUCK_MODE); + /* Do not configure ICL from SW for DAM */ + if (smblite_lib_get_prop_typec_mode(chg) == + POWER_SUPPLY_TYPEC_SINK_DEBUG_ACCESSORY) + return 0; + + if (suspend) + return smblite_lib_set_usb_suspend(chg, true); + + /* No client updated it's vote */ + if (icl_ua == INT_MAX) + goto set_mode; + + /* configure current */ + if (chg->real_charger_type == POWER_SUPPLY_TYPE_USB + && (chg->typec_legacy + || chg->typec_mode == POWER_SUPPLY_TYPEC_SOURCE_DEFAULT + || chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB)) { + rc = set_sdp_current(chg, icl_ua); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't set SDP ICL rc=%d\n", + rc); + goto out; + } + + goto unsuspend; + } else { + /* + * Try USB 2.0/3,0 option first on USB path when maximum input + * current limit is 500mA or below for better accuracy; in case + * of error, proceed to use USB high-current mode. + */ + if (icl_ua <= USBIN_500UA) { + rc = set_sdp_current(chg, icl_ua); + if (rc >= 0) + goto unsuspend; + } + + rc = smblite_lib_set_charge_param(chg, &chg->param.usb_icl, + icl_ua); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't set HC ICL rc=%d\n", rc); + goto out; + } + icl_override = SW_OVERRIDE_HC_MODE; + } + +set_mode: + rc = smblite_lib_icl_override(chg, icl_override); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't set ICL override rc=%d\n", rc); + goto out; + } + +unsuspend: + /* unsuspend after configuring current and override */ + rc = smblite_lib_set_usb_suspend(chg, false); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't resume input rc=%d\n", rc); + goto out; + } + + /* Re-run AICL */ + if (icl_override != SW_OVERRIDE_HC_MODE) + rc = smblite_lib_run_aicl(chg, RERUN_AICL); +out: + return rc; +} + +int smblite_lib_get_icl_current(struct smb_charger *chg, int *icl_ua) +{ + int rc; + + rc = smblite_lib_get_charge_param(chg, &chg->param.icl_max_stat, + icl_ua); + if (rc < 0) + smblite_lib_err(chg, "Couldn't get HC ICL rc=%d\n", rc); + + return rc; +} + +/********************* + * VOTABLE CALLBACKS * + *********************/ +static int smblite_lib_awake_vote_callback(struct votable *votable, void *data, + int awake, const char *client) +{ + struct smb_charger *chg = data; + + if (awake) + pm_stay_awake(chg->dev); + else + pm_relax(chg->dev); + + return 0; +} + +static int smblite_lib_chg_disable_vote_callback(struct votable *votable, + void *data, int chg_disable, const char *client) +{ + struct smb_charger *chg = data; + int rc; + + rc = smblite_lib_masked_write(chg, CHARGING_ENABLE_CMD_REG, + CHARGING_ENABLE_CMD_BIT, + chg_disable ? 0 : CHARGING_ENABLE_CMD_BIT); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't %s charging rc=%d\n", + chg_disable ? "disable" : "enable", rc); + return rc; + } + + return 0; +} + +static int smblite_lib_icl_irq_disable_vote_callback(struct votable *votable, + void *data, int disable, const char *client) +{ + struct smb_charger *chg = data; + + if (!chg->irq_info[USBIN_ICL_CHANGE_IRQ].irq) + return 0; + + if (chg->irq_info[USBIN_ICL_CHANGE_IRQ].enabled) { + if (disable) + disable_irq_nosync( + chg->irq_info[USBIN_ICL_CHANGE_IRQ].irq); + } else { + if (!disable) + enable_irq(chg->irq_info[USBIN_ICL_CHANGE_IRQ].irq); + } + + chg->irq_info[USBIN_ICL_CHANGE_IRQ].enabled = !disable; + + return 0; +} + +static int smblite_lib_temp_change_irq_disable_vote_callback( + struct votable *votable, void *data, + int disable, const char *client) +{ + struct smb_charger *chg = data; + + if (!chg->irq_info[TEMP_CHANGE_IRQ].irq) + return 0; + + if (chg->irq_info[TEMP_CHANGE_IRQ].enabled && disable) { + if (chg->irq_info[TEMP_CHANGE_IRQ].wake) + disable_irq_wake(chg->irq_info[TEMP_CHANGE_IRQ].irq); + disable_irq_nosync(chg->irq_info[TEMP_CHANGE_IRQ].irq); + } else if (!chg->irq_info[TEMP_CHANGE_IRQ].enabled && !disable) { + enable_irq(chg->irq_info[TEMP_CHANGE_IRQ].irq); + if (chg->irq_info[TEMP_CHANGE_IRQ].wake) + enable_irq_wake(chg->irq_info[TEMP_CHANGE_IRQ].irq); + } + + chg->irq_info[TEMP_CHANGE_IRQ].enabled = !disable; + + return 0; +} + +/******************** + * BATT PSY GETTERS * + ********************/ + +int smblite_lib_get_prop_input_suspend(struct smb_charger *chg, + union power_supply_propval *val) +{ + val->intval + = (get_client_vote(chg->usb_icl_votable, USER_VOTER) == 0); + return 0; +} + +int smblite_lib_get_prop_batt_present(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc; + u8 stat; + + rc = smblite_lib_read(chg, BATIF_BASE + INT_RT_STS_OFFSET, &stat); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't read BATIF_INT_RT_STS rc=%d\n", + rc); + return rc; + } + + val->intval = !(stat & BAT_THERM_OR_ID_MISSING_RT_STS_BIT); + + return rc; +} + +int smblite_lib_get_prop_batt_capacity(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc = -EINVAL; + + if (chg->fake_capacity >= 0) { + val->intval = chg->fake_capacity; + return 0; + } + + rc = smblite_lib_get_prop_from_bms(chg, POWER_SUPPLY_PROP_CAPACITY, + val); + + return rc; +} + +static bool is_charging_paused(struct smb_charger *chg) +{ + int rc; + u8 val; + + rc = smblite_lib_read(chg, CHARGING_ENABLE_CMD_REG, &val); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't read CHARGING_PAUSE_CMD rc=%d\n", + rc); + return false; + } + + return val & CHARGING_PAUSE_CMD_BIT; +} + +#define CUTOFF_COUNT 3 +int smblite_lib_get_prop_batt_status(struct smb_charger *chg, + union power_supply_propval *val) +{ + union power_supply_propval pval = {0, }; + bool usb_online; + u8 stat; + int rc, input_present = 0; + + if (chg->fake_chg_status_on_debug_batt) { + rc = smblite_lib_get_prop_from_bms(chg, + POWER_SUPPLY_PROP_DEBUG_BATTERY, &pval); + if (rc < 0) { + pr_err_ratelimited("Couldn't get debug battery prop rc=%d\n", + rc); + } else if (pval.intval == 1) { + val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + return 0; + } + } + + /* + * If SOC = 0 and we are discharging with input connected, report + * the battery status as DISCHARGING. + */ + smblite_lib_is_input_present(chg, &input_present); + rc = smblite_lib_get_prop_from_bms(chg, + POWER_SUPPLY_PROP_CAPACITY, &pval); + if (!rc && pval.intval == 0 && input_present) { + rc = smblite_lib_get_prop_from_bms(chg, + POWER_SUPPLY_PROP_CURRENT_NOW, &pval); + if (!rc && pval.intval > 0) { + if (chg->cutoff_count > CUTOFF_COUNT) { + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + return 0; + } + chg->cutoff_count++; + } else { + chg->cutoff_count = 0; + } + } else { + chg->cutoff_count = 0; + } + + rc = smblite_lib_get_prop_usb_online(chg, &pval); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't get usb online property rc=%d\n", + rc); + return rc; + } + usb_online = (bool)pval.intval; + + rc = smblite_lib_read(chg, BATTERY_CHARGER_STATUS_1_REG, &stat); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_1 rc=%d\n", + rc); + return rc; + } + stat = stat & BATTERY_CHARGER_STATUS_MASK; + + if (!usb_online) { + switch (stat) { + case TERMINATE_CHARGE: + case INHIBIT_CHARGE: + val->intval = POWER_SUPPLY_STATUS_FULL; + break; + default: + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + break; + } + return rc; + } + + switch (stat) { + case TRICKLE_CHARGE: + case PRE_CHARGE: + case FULLON_CHARGE: + case TAPER_CHARGE: + val->intval = POWER_SUPPLY_STATUS_CHARGING; + break; + case TERMINATE_CHARGE: + case INHIBIT_CHARGE: + val->intval = POWER_SUPPLY_STATUS_FULL; + break; + case DISABLE_CHARGE: + case PAUSE_CHARGE: + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + default: + val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + break; + } + + if (is_charging_paused(chg)) { + val->intval = POWER_SUPPLY_STATUS_CHARGING; + return 0; + } + + if (val->intval != POWER_SUPPLY_STATUS_CHARGING) + return 0; + + if (!usb_online + && chg->fake_batt_status == POWER_SUPPLY_STATUS_FULL) { + val->intval = POWER_SUPPLY_STATUS_FULL; + return 0; + } + + return 0; +} + +int smblite_lib_get_prop_batt_charge_type(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc; + u8 stat; + + rc = smblite_lib_read(chg, BATTERY_CHARGER_STATUS_1_REG, &stat); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_1 rc=%d\n", + rc); + return rc; + } + + switch (stat & BATTERY_CHARGER_STATUS_MASK) { + case TRICKLE_CHARGE: + case PRE_CHARGE: + val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + break; + case FULLON_CHARGE: + val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; + break; + case TAPER_CHARGE: + val->intval = POWER_SUPPLY_CHARGE_TYPE_TAPER; + break; + default: + val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; + } + + return rc; +} + +int smblite_lib_get_prop_batt_health(struct smb_charger *chg, + union power_supply_propval *val) +{ + union power_supply_propval pval; + int rc; + int effective_fv_uv; + u8 stat; + + rc = smblite_lib_read(chg, CHARGER_VBAT_STATUS_REG, &stat); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't read CHARGER_VBAT_STATUS_REG rc=%d\n", + rc); + return rc; + } + smblite_lib_dbg(chg, PR_REGISTER, "CHARGER_VBAT_STATUS_REG = 0x%02x\n", + stat); + + if (stat & BAT_OV_BIT) { + rc = smblite_lib_get_prop_from_bms(chg, + POWER_SUPPLY_PROP_VOLTAGE_NOW, &pval); + if (!rc) { + /* + * If Vbatt is within 40mV above Vfloat, then don't + * treat it as overvoltage. + */ + effective_fv_uv = get_effective_result(chg->fv_votable); + if (pval.intval >= effective_fv_uv + 40000) { + val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + smblite_lib_err(chg, "battery over-voltage vbat_fg = %duV, fv = %duV\n", + pval.intval, effective_fv_uv); + goto done; + } + } + } + + rc = smblite_lib_read(chg, BATTERY_TEMP_STATUS_REG, &stat); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_2 rc=%d\n", + rc); + return rc; + } + if (stat & BAT_TEMP_STATUS_TOO_COLD_BIT) + val->intval = POWER_SUPPLY_HEALTH_COLD; + else if (stat & BAT_TEMP_STATUS_TOO_HOT_BIT) + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + else if (stat & BAT_TEMP_STATUS_COLD_SOFT_BIT) + val->intval = POWER_SUPPLY_HEALTH_COOL; + else if (stat & BAT_TEMP_STATUS_HOT_SOFT_BIT) + val->intval = POWER_SUPPLY_HEALTH_WARM; + else + val->intval = POWER_SUPPLY_HEALTH_GOOD; + +done: + return rc; +} + +int smblite_lib_get_prop_system_temp_level(struct smb_charger *chg, + union power_supply_propval *val) +{ + val->intval = chg->system_temp_level; + return 0; +} + +int smblite_lib_get_prop_system_temp_level_max(struct smb_charger *chg, + union power_supply_propval *val) +{ + val->intval = chg->thermal_levels; + return 0; +} + +int smblite_lib_get_prop_input_current_limited(struct smb_charger *chg, + union power_supply_propval *val) +{ + u8 stat; + int rc; + + rc = smblite_lib_read(chg, AICL_STATUS_REG, &stat); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't read AICL_STATUS rc=%d\n", rc); + return rc; + } + + val->intval = (stat & SOFT_ILIMIT_BIT); + + return 0; +} + +int smblite_lib_get_prop_batt_iterm(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc, temp; + u8 stat, buf[2]; + + /* Currently, only ADC comparator-based termination is supported + * and validate, hence read only the threshold corresponding to ADC + * source. Proceed only if CHGR_ITERM_USE_ANALOG_BIT is 0. + */ + rc = smblite_lib_read(chg, CHGR_TERM_CFG_REG, &stat); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't read CHGR_TERM_CFG_REG rc=%d\n", + rc); + return rc; + } + + if (stat & CHGR_ITERM_USE_ANALOG_BIT) { + val->intval = -EINVAL; + return 0; + } + + rc = smblite_lib_batch_read(chg, CHGR_ADC_ITERM_UP_THD_MSB_REG, buf, 2); + + if (rc < 0) { + smblite_lib_err(chg, "Couldn't read CHGR_ADC_ITERM_UP_THD_MSB_REG rc=%d\n", + rc); + return rc; + } + + temp = buf[1] | (buf[0] << 8); + temp = sign_extend32(temp, 15); + + temp = DIV_ROUND_CLOSEST(temp * ITERM_LIMITS_MA, + ADC_CHG_ITERM_MASK); + + val->intval = temp; + + return rc; +} + +int smblite_lib_get_prop_batt_charge_done(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc; + u8 stat; + + rc = smblite_lib_read(chg, BATTERY_CHARGER_STATUS_1_REG, &stat); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_1 rc=%d\n", + rc); + return rc; + } + + stat = stat & BATTERY_CHARGER_STATUS_MASK; + val->intval = (stat == TERMINATE_CHARGE); + return 0; +} + +int smblite_lib_get_batt_current_now(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc; + + rc = smblite_lib_get_prop_from_bms(chg, + POWER_SUPPLY_PROP_CURRENT_NOW, val); + if (!rc) + val->intval *= (-1); + + return rc; +} + +/*********************** + * BATTERY PSY SETTERS * + ***********************/ + +int smblite_lib_set_prop_input_suspend(struct smb_charger *chg, + const union power_supply_propval *val) +{ + int rc; + + /* vote 0mA when suspended */ + rc = vote(chg->usb_icl_votable, USER_VOTER, (bool)val->intval, 0); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't vote to %s USB rc=%d\n", + (bool)val->intval ? "suspend" : "resume", rc); + return rc; + } + + power_supply_changed(chg->batt_psy); + return rc; +} + +int smblite_lib_set_prop_batt_capacity(struct smb_charger *chg, + const union power_supply_propval *val) +{ + chg->fake_capacity = val->intval; + + power_supply_changed(chg->batt_psy); + + return 0; +} + +int smblite_lib_set_prop_batt_status(struct smb_charger *chg, + const union power_supply_propval *val) +{ + /* Faking battery full */ + if (val->intval == POWER_SUPPLY_STATUS_FULL) + chg->fake_batt_status = val->intval; + else + chg->fake_batt_status = -EINVAL; + + power_supply_changed(chg->batt_psy); + + return 0; +} + +int smblite_lib_set_prop_system_temp_level(struct smb_charger *chg, + const union power_supply_propval *val) +{ + if (val->intval < 0) + return -EINVAL; + + if (chg->thermal_levels <= 0) + return -EINVAL; + + if (val->intval > chg->thermal_levels) + return -EINVAL; + + chg->system_temp_level = val->intval; + + if (chg->system_temp_level == chg->thermal_levels) + return vote(chg->chg_disable_votable, + THERMAL_DAEMON_VOTER, true, 0); + + vote(chg->chg_disable_votable, THERMAL_DAEMON_VOTER, false, 0); + if (chg->system_temp_level == 0) + return vote(chg->fcc_votable, THERMAL_DAEMON_VOTER, false, 0); + + vote(chg->fcc_votable, THERMAL_DAEMON_VOTER, true, + chg->thermal_mitigation[chg->system_temp_level]); + return 0; +} + +int smblite_lib_set_prop_rechg_soc_thresh(struct smb_charger *chg, + const union power_supply_propval *val) +{ + int rc; + u8 new_thr = DIV_ROUND_CLOSEST(val->intval * 255, 100); + + rc = smblite_lib_write(chg, CHARGE_RCHG_SOC_THRESHOLD_CFG_REG, + new_thr); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't write to RCHG_SOC_THRESHOLD_CFG_REG rc=%d\n", + rc); + return rc; + } + + chg->auto_recharge_soc = val->intval; + + return rc; +} + +int smblite_lib_run_aicl(struct smb_charger *chg, int type) +{ + int rc; + u8 stat; + + rc = smblite_lib_read(chg, POWER_PATH_STATUS_REG, &stat); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't read POWER_PATH_STATUS rc=%d\n", + rc); + return rc; + } + + /* USB is suspended so skip re-running AICL */ + if (stat & USBIN_SUSPEND_STS_BIT) + return rc; + + smblite_lib_dbg(chg, PR_MISC, "re-running AICL\n"); + + stat = (type == RERUN_AICL) ? RERUN_AICL_BIT : RESTART_AICL_BIT; + rc = smblite_lib_masked_write(chg, AICL_CMD_REG, stat, stat); + if (rc < 0) + smblite_lib_err(chg, "Couldn't write to AICL_CMD_REG rc=%d\n", + rc); + return 0; +} + +/******************* + * USB PSY GETTERS * + *******************/ + +int smblite_lib_get_prop_usb_present(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc; + u8 stat; + + rc = smblite_lib_read(chg, USBIN_BASE + INT_RT_STS_OFFSET, &stat); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't read USBIN_RT_STS rc=%d\n", rc); + return rc; + } + + val->intval = (bool)(stat & USBIN_PLUGIN_RT_STS_BIT); + return 0; +} + +int smblite_lib_get_prop_usb_online(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc = 0; + u8 stat; + + if (get_client_vote_locked(chg->usb_icl_votable, USER_VOTER) == 0) { + val->intval = false; + return rc; + } + + rc = smblite_lib_read(chg, POWER_PATH_STATUS_REG, &stat); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't read POWER_PATH_STATUS rc=%d\n", + rc); + return rc; + } + smblite_lib_dbg(chg, PR_REGISTER, "POWER_PATH_STATUS = 0x%02x\n", + stat); + + val->intval = (stat & USE_USBIN_BIT) && + (stat & VALID_INPUT_POWER_SOURCE_STS_BIT); + return rc; +} + +int smblite_lib_get_usb_online(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc; + + if (chg->real_charger_type == POWER_SUPPLY_TYPE_UNKNOWN) { + val->intval = 0; + return 0; + } + + rc = smblite_lib_get_prop_usb_online(chg, val); + if (!val->intval) + goto exit; + +exit: + return rc; +} + +static int smblite_lib_read_usbin_voltage_chan(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc; + + if (!chg->iio.usbin_v_chan) + return -ENODATA; + + rc = iio_read_channel_processed(chg->iio.usbin_v_chan, &val->intval); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't read USBIN channel rc=%d\n", rc); + return rc; + } + + return 0; +} + +int smblite_lib_get_prop_usb_voltage_now(struct smb_charger *chg, + union power_supply_propval *val) +{ + union power_supply_propval pval = {0, }; + int rc = 0; + u8 reg; + + rc = smblite_lib_get_prop_usb_present(chg, &pval); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't get usb presence status rc=%d\n", + rc); + goto out; + } + + /* + * Skip reading voltage only if USB is not present and we are not in + * OTG mode. + */ + if (!pval.intval) { + rc = smblite_lib_read(chg, DCDC_CMD_OTG_REG, ®); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't read CMD_OTG rc=%d", rc); + goto out; + } + + if (!(reg & OTG_EN_BIT)) + goto out; + } + + rc = smblite_lib_read_usbin_voltage_chan(chg, val); + if (rc < 0) + smblite_lib_err(chg, "Couldn't to read USBIN over vadc, rc=%d\n", + rc); + +out: + return rc; +} + +int smblite_lib_get_prop_charger_temp(struct smb_charger *chg, + union power_supply_propval *val) +{ + int temp, rc; + int input_present; + + rc = smblite_lib_is_input_present(chg, &input_present); + if (rc < 0) + return rc; + + if (input_present == INPUT_NOT_PRESENT) + return -ENODATA; + + if (chg->iio.temp_chan) { + rc = iio_read_channel_processed(chg->iio.temp_chan, + &temp); + if (rc < 0) { + pr_err("Error in reading temp channel, rc=%d\n", rc); + return rc; + } + val->intval = temp / 100; + } else { + return -ENODATA; + } + + return rc; +} + +int smblite_lib_get_prop_typec_cc_orientation(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc = 0; + u8 stat; + + val->intval = 0; + + if (chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB) + return 0; + + rc = smblite_lib_read(chg, TYPE_C_MISC_STATUS_REG, &stat); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't read TYPE_C_STATUS_4 rc=%d\n", + rc); + return rc; + } + smblite_lib_dbg(chg, PR_REGISTER, "TYPE_C_STATUS_4 = 0x%02x\n", stat); + + if (stat & CC_ATTACHED_BIT) + val->intval = (bool)(stat & CC_ORIENTATION_BIT) + 1; + + return rc; +} + +static const char * const smblite_lib_typec_mode_name[] = { + [POWER_SUPPLY_TYPEC_NONE] = "NONE", + [POWER_SUPPLY_TYPEC_SOURCE_DEFAULT] = "SOURCE_DEFAULT", + [POWER_SUPPLY_TYPEC_SOURCE_MEDIUM] = "SOURCE_MEDIUM", + [POWER_SUPPLY_TYPEC_SOURCE_HIGH] = "SOURCE_HIGH", + [POWER_SUPPLY_TYPEC_NON_COMPLIANT] = "NON_COMPLIANT", + [POWER_SUPPLY_TYPEC_SINK] = "SINK", + [POWER_SUPPLY_TYPEC_SINK_POWERED_CABLE] = "SINK_POWERED_CABLE", + [POWER_SUPPLY_TYPEC_SINK_DEBUG_ACCESSORY] = "SINK_DEBUG_ACCESSORY", + [POWER_SUPPLY_TYPEC_SINK_AUDIO_ADAPTER] = "SINK_AUDIO_ADAPTER", + [POWER_SUPPLY_TYPEC_POWERED_CABLE_ONLY] = "POWERED_CABLE_ONLY", +}; + +static int smblite_lib_get_prop_ufp_mode(struct smb_charger *chg) +{ + int rc; + u8 stat; + + rc = smblite_lib_read(chg, TYPE_C_SNK_STATUS_REG, &stat); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't read TYPE_C_STATUS_1 rc=%d\n", + rc); + return POWER_SUPPLY_TYPEC_NONE; + } + smblite_lib_dbg(chg, PR_REGISTER, "TYPE_C_STATUS_1 = 0x%02x\n", stat); + + switch (stat & DETECTED_SRC_TYPE_MASK) { + case SNK_RP_STD_BIT: + return POWER_SUPPLY_TYPEC_SOURCE_DEFAULT; + case SNK_RP_1P5_BIT: + return POWER_SUPPLY_TYPEC_SOURCE_MEDIUM; + case SNK_RP_3P0_BIT: + return POWER_SUPPLY_TYPEC_SOURCE_HIGH; + case SNK_RP_SHORT_BIT: + return POWER_SUPPLY_TYPEC_NON_COMPLIANT; + case SNK_DAM_500MA_BIT: + case SNK_DAM_1500MA_BIT: + case SNK_DAM_3000MA_BIT: + return POWER_SUPPLY_TYPEC_SINK_DEBUG_ACCESSORY; + default: + break; + } + + return POWER_SUPPLY_TYPEC_NONE; +} + +static int smblite_lib_get_prop_dfp_mode(struct smb_charger *chg) +{ + int rc; + u8 stat; + + rc = smblite_lib_read(chg, TYPE_C_SRC_STATUS_REG, &stat); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't read TYPE_C_SRC_STATUS_REG rc=%d\n", + rc); + return POWER_SUPPLY_TYPEC_NONE; + } + smblite_lib_dbg(chg, PR_REGISTER, "TYPE_C_SRC_STATUS_REG = 0x%02x\n", + stat); + + switch (stat & DETECTED_SNK_TYPE_MASK) { + case AUDIO_ACCESS_RA_RA_BIT: + return POWER_SUPPLY_TYPEC_SINK_AUDIO_ADAPTER; + case SRC_DEBUG_ACCESS_BIT: + return POWER_SUPPLY_TYPEC_SINK_DEBUG_ACCESSORY; + case SRC_RD_OPEN_BIT: + return POWER_SUPPLY_TYPEC_SINK; + default: + break; + } + + return POWER_SUPPLY_TYPEC_NONE; +} + +static int smblite_lib_get_prop_typec_mode(struct smb_charger *chg) +{ + int rc; + u8 stat; + + if (chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB) + return POWER_SUPPLY_TYPEC_NONE; + + rc = smblite_lib_read(chg, TYPE_C_MISC_STATUS_REG, &stat); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't read TYPE_C_MISC_STATUS_REG rc=%d\n", + rc); + return 0; + } + smblite_lib_dbg(chg, PR_REGISTER, "TYPE_C_MISC_STATUS_REG = 0x%02x\n", + stat); + + if (stat & SNK_SRC_MODE_BIT) + return smblite_lib_get_prop_dfp_mode(chg); + else + return smblite_lib_get_prop_ufp_mode(chg); +} + +inline int smblite_lib_get_usb_prop_typec_mode(struct smb_charger *chg, + union power_supply_propval *val) +{ + if (chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB) + val->intval = POWER_SUPPLY_TYPEC_NONE; + else + val->intval = chg->typec_mode; + + return 0; +} + +int smblite_lib_get_prop_typec_power_role(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc = 0; + u8 ctrl; + + if (chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB) { + val->intval = POWER_SUPPLY_TYPEC_PR_NONE; + return 0; + } + + rc = smblite_lib_read(chg, TYPE_C_MODE_CFG_REG, &ctrl); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't read TYPE_C_MODE_CFG_REG rc=%d\n", + rc); + return rc; + } + smblite_lib_dbg(chg, PR_REGISTER, "TYPE_C_MODE_CFG_REG = 0x%02x\n", + ctrl); + + if (ctrl & TYPEC_DISABLE_CMD_BIT) { + val->intval = POWER_SUPPLY_TYPEC_PR_NONE; + return rc; + } + + switch (ctrl & (EN_SRC_ONLY_BIT | EN_SNK_ONLY_BIT)) { + case 0: + val->intval = POWER_SUPPLY_TYPEC_PR_DUAL; + break; + case EN_SRC_ONLY_BIT: + val->intval = POWER_SUPPLY_TYPEC_PR_SOURCE; + break; + case EN_SNK_ONLY_BIT: + val->intval = POWER_SUPPLY_TYPEC_PR_SINK; + break; + default: + val->intval = POWER_SUPPLY_TYPEC_PR_NONE; + smblite_lib_err(chg, "unsupported power role 0x%02lx\n", + ctrl & (EN_SRC_ONLY_BIT | EN_SNK_ONLY_BIT)); + return -EINVAL; + } + + chg->power_role = val->intval; + return rc; +} + +static inline bool typec_in_src_mode(struct smb_charger *chg) +{ + return (chg->typec_mode > POWER_SUPPLY_TYPEC_NONE && + chg->typec_mode < POWER_SUPPLY_TYPEC_SOURCE_DEFAULT); +} + +int smblite_lib_get_prop_input_current_settled(struct smb_charger *chg, + union power_supply_propval *val) +{ + return smblite_lib_get_charge_param(chg, &chg->param.icl_stat, + &val->intval); +} + +int smblite_lib_get_prop_input_voltage_settled(struct smb_charger *chg, + union power_supply_propval *val) +{ + /* TODO: do we need to read the real VBUS */ + val->intval = 5000000; + return 0; +} + +int smblite_lib_get_prop_die_health(struct smb_charger *chg) +{ + int rc; + u8 stat; + int input_present; + + rc = smblite_lib_is_input_present(chg, &input_present); + if (rc < 0) + return rc; + + if (input_present == INPUT_NOT_PRESENT) + return POWER_SUPPLY_HEALTH_UNKNOWN; + + rc = smblite_lib_read(chg, DIE_TEMP_STATUS_REG, &stat); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't read DIE_TEMP_STATUS_REG, rc=%d\n", + rc); + return POWER_SUPPLY_HEALTH_UNKNOWN; + } + + if (stat & DIE_TEMP_RST_BIT) + return POWER_SUPPLY_HEALTH_OVERHEAT; + + if (stat & DIE_TEMP_UB_BIT) + return POWER_SUPPLY_HEALTH_HOT; + + if (stat & DIE_TEMP_LB_BIT) + return POWER_SUPPLY_HEALTH_WARM; + + return POWER_SUPPLY_HEALTH_COOL; +} + +int smblite_lib_get_die_health(struct smb_charger *chg, + union power_supply_propval *val) +{ + val->intval = smblite_lib_get_prop_die_health(chg); + return 0; +} + +int smblite_lib_get_prop_scope(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc; + union power_supply_propval pval; + + val->intval = POWER_SUPPLY_SCOPE_UNKNOWN; + rc = smblite_lib_get_prop_usb_present(chg, &pval); + if (rc < 0) + return rc; + + val->intval = pval.intval ? POWER_SUPPLY_SCOPE_DEVICE + : chg->otg_present ? POWER_SUPPLY_SCOPE_SYSTEM + : POWER_SUPPLY_SCOPE_UNKNOWN; + + return 0; +} + +static int get_rp_based_dcp_current(struct smb_charger *chg, int typec_mode) +{ + int rp_ua; + + switch (typec_mode) { + case POWER_SUPPLY_TYPEC_SOURCE_HIGH: + rp_ua = TYPEC_HIGH_CURRENT_UA; + break; + case POWER_SUPPLY_TYPEC_SOURCE_MEDIUM: + case POWER_SUPPLY_TYPEC_SOURCE_DEFAULT: + 'fallthrough;' + default : + rp_ua = DCP_CURRENT_UA; + } + + return rp_ua; +} + +/******************* + * USB PSY SETTERS * + * *****************/ + +int smblite_lib_set_prop_usb_type(struct smb_charger *chg, + const union power_supply_propval *val) +{ + smblite_lib_dbg(chg, PR_MISC, + "Charger type request form USB driver type=%d\n", val->intval); + /* update real charger type */ + smblite_lib_update_usb_type(chg, val->intval); + + /* For SDP rely on USB enumeration based reported the current */ + if ((chg->real_charger_type == POWER_SUPPLY_TYPE_USB) + || (chg->real_charger_type == POWER_SUPPLY_TYPE_UNKNOWN)) + return 0; + + vote(chg->usb_icl_votable, USB_PSY_VOTER, false, 0); + update_sw_icl_max(chg, chg->real_charger_type); + + power_supply_changed(chg->usb_psy); + return 0; +} + +int smblite_lib_set_prop_current_max(struct smb_charger *chg, + const union power_supply_propval *val) +{ + int rc = 0; + + smblite_lib_dbg(chg, PR_MISC, + "Current request from USB driver current=%dmA\n", val->intval); + /* ignore current request from USB for charger other than SDP */ + if (chg->real_charger_type != POWER_SUPPLY_TYPE_USB) + return 0; + + rc = vote(chg->usb_icl_votable, USB_PSY_VOTER, true, val->intval); + if (rc < 0) { + pr_err("Couldn't vote ICL USB_PSY_VOTER rc=%d\n", rc); + return rc; + } + + rc = vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, false, 0); + if (rc < 0) { + pr_err("Couldn't remove SW_ICL_MAX vote rc=%d\n", rc); + return rc; + } + + /* Update TypeC Rp based current */ + if (chg->connector_type == POWER_SUPPLY_CONNECTOR_TYPEC) { + update_sw_icl_max(chg, chg->real_charger_type); + } else if (is_flashlite_active(chg) && (val->intval >= USBIN_400UA)) { + /* For Uusb based SDP port */ + vote(chg->usb_icl_votable, FLASH_ACTIVE_VOTER, true, + val->intval - USBIN_300UA); + smblite_lib_dbg(chg, PR_MISC, "flash_active = 1, ICL set to %d\n", + val->intval - USBIN_300UA); + } + + return 0; +} + +int smblite_lib_set_prop_typec_power_role(struct smb_charger *chg, + const union power_supply_propval *val) +{ + int rc = 0; + u8 power_role; + + if (chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB) + return -EINVAL; + + smblite_lib_dbg(chg, PR_MISC, "power role change: %d --> %d!", + chg->power_role, val->intval); + + if (chg->power_role == val->intval) { + smblite_lib_dbg(chg, PR_MISC, "power role already in %d, ignore!", + chg->power_role); + return 0; + } + + switch (val->intval) { + case POWER_SUPPLY_TYPEC_PR_NONE: + power_role = TYPEC_DISABLE_CMD_BIT; + break; + case POWER_SUPPLY_TYPEC_PR_DUAL: + power_role = 0; + break; + case POWER_SUPPLY_TYPEC_PR_SINK: + power_role = EN_SNK_ONLY_BIT; + break; + case POWER_SUPPLY_TYPEC_PR_SOURCE: + power_role = EN_SRC_ONLY_BIT; + break; + default: + smblite_lib_err(chg, "power role %d not supported\n", + val->intval); + return -EINVAL; + } + + rc = smblite_lib_masked_write(chg, TYPE_C_MODE_CFG_REG, + TYPEC_POWER_ROLE_CMD_MASK | TYPEC_TRY_MODE_MASK, + power_role); + if (rc < 0) { + smblite_lib_err(chg, + "Couldn't write 0x%02x to TYPE_C_INTRPT_ENB_SOFTWARE_CTRL rc=%d\n", + power_role, rc); + return rc; + } + + chg->power_role = val->intval; + return rc; +} + +int smblite_lib_set_prop_ship_mode(struct smb_charger *chg, + const union power_supply_propval *val) +{ + int rc; + + smblite_lib_dbg(chg, PR_MISC, "Set ship mode: %d!!\n", !!val->intval); + + rc = smblite_lib_masked_write(chg, SHIP_MODE_REG, SHIP_MODE_EN_BIT, + !!val->intval ? SHIP_MODE_EN_BIT : 0); + if (rc < 0) + dev_err(chg->dev, "Couldn't %s ship mode, rc=%d\n", + !!val->intval ? "enable" : "disable", rc); + + return rc; +} + +#define JEITA_SOFT 0 +#define JEITA_HARD 1 +static int smblite_lib_update_jeita(struct smb_charger *chg, u32 *thresholds, + int type) +{ + int rc; + u16 temp, base_low, base_high; + + + base_low = (type == JEITA_SOFT) ? CHGR_JEITA_COOL_THRESHOLD_REG + : CHGR_JEITA_COLD_THRESHOLD_REG; + base_high = (type == JEITA_SOFT) ? CHGR_JEITA_WARM_THRESHOLD_REG + : CHGR_JEITA_HOT_THRESHOLD_REG; + temp = thresholds[1] & 0xFFFF; + temp = ((temp & 0xFF00) >> 8) | ((temp & 0xFF) << 8); + rc = smblite_lib_batch_write(chg, base_high, (u8 *)&temp, 2); + if (rc < 0) { + smblite_lib_err(chg, + "Couldn't configure Jeita %s hot threshold rc=%d\n", + (type == JEITA_SOFT) ? "Soft" : "Hard", rc); + return rc; + } + + temp = thresholds[0] & 0xFFFF; + temp = ((temp & 0xFF00) >> 8) | ((temp & 0xFF) << 8); + rc = smblite_lib_batch_write(chg, base_low, (u8 *)&temp, 2); + if (rc < 0) { + smblite_lib_err(chg, + "Couldn't configure Jeita %s cold threshold rc=%d\n", + (type == JEITA_SOFT) ? "Soft" : "Hard", rc); + return rc; + } + + smblite_lib_dbg(chg, PR_MISC, "%s Jeita threshold configured\n", + (type == JEITA_SOFT) ? "Soft" : "Hard"); + + return 0; +} + +static int smblite_lib_charge_inhibit_en(struct smb_charger *chg, bool enable) +{ + int rc; + + rc = smblite_lib_masked_write(chg, CHGR_INHIBIT_REG, + CHGR_INHIBIT_BIT, + enable ? CHGR_INHIBIT_BIT : 0); + return rc; +} + +static int smblite_lib_soft_jeita_arb_wa(struct smb_charger *chg) +{ + union power_supply_propval pval; + int rc = 0; + bool soft_jeita; + + rc = smblite_lib_get_prop_batt_health(chg, &pval); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't get battery health rc=%d\n", rc); + return rc; + } + + /* Do nothing on entering hard JEITA condition */ + if (pval.intval == POWER_SUPPLY_HEALTH_COLD || + pval.intval == POWER_SUPPLY_HEALTH_HOT) + return 0; + + if (chg->jeita_soft_fcc[0] < 0 || chg->jeita_soft_fcc[1] < 0 || + chg->jeita_soft_fv[0] < 0 || chg->jeita_soft_fv[1] < 0) + return 0; + + soft_jeita = (pval.intval == POWER_SUPPLY_HEALTH_COOL) || + (pval.intval == POWER_SUPPLY_HEALTH_WARM); + + /* Do nothing on entering soft JEITA from hard JEITA */ + if (chg->jeita_arb_flag && soft_jeita) + return 0; + + /* Do nothing, initial to health condition */ + if (!chg->jeita_arb_flag && !soft_jeita) + return 0; + + /* Entering soft JEITA from normal state */ + if (!chg->jeita_arb_flag && soft_jeita) { + vote(chg->chg_disable_votable, JEITA_ARB_VOTER, true, 0); + + rc = smblite_lib_charge_inhibit_en(chg, true); + if (rc < 0) + smblite_lib_err(chg, "Couldn't enable charge inhibit rc=%d\n", + rc); + + rc = smblite_lib_update_jeita(chg, chg->jeita_soft_hys_thlds, + JEITA_SOFT); + if (rc < 0) + smblite_lib_err(chg, + "Couldn't configure Jeita soft threshold rc=%d\n", + rc); + + if (pval.intval == POWER_SUPPLY_HEALTH_COOL) { + vote(chg->fcc_votable, JEITA_ARB_VOTER, true, + chg->jeita_soft_fcc[0]); + vote(chg->fv_votable, JEITA_ARB_VOTER, true, + chg->jeita_soft_fv[0]); + } else { + vote(chg->fcc_votable, JEITA_ARB_VOTER, true, + chg->jeita_soft_fcc[1]); + vote(chg->fv_votable, JEITA_ARB_VOTER, true, + chg->jeita_soft_fv[1]); + } + + vote(chg->chg_disable_votable, JEITA_ARB_VOTER, false, 0); + chg->jeita_arb_flag = true; + } else if (chg->jeita_arb_flag && !soft_jeita) { + /* Exit to health state from soft JEITA */ + + vote(chg->chg_disable_votable, JEITA_ARB_VOTER, true, 0); + + rc = smblite_lib_charge_inhibit_en(chg, false); + if (rc < 0) + smblite_lib_err(chg, "Couldn't disable charge inhibit rc=%d\n", + rc); + + rc = smblite_lib_update_jeita(chg, chg->jeita_soft_thlds, + JEITA_SOFT); + if (rc < 0) + smblite_lib_err(chg, "Couldn't configure Jeita soft threshold rc=%d\n", + rc); + + vote(chg->fcc_votable, JEITA_ARB_VOTER, false, 0); + vote(chg->fv_votable, JEITA_ARB_VOTER, false, 0); + vote(chg->chg_disable_votable, JEITA_ARB_VOTER, false, 0); + chg->jeita_arb_flag = false; + } + + smblite_lib_dbg(chg, PR_MISC, "JEITA ARB status %d, soft JEITA status %d\n", + chg->jeita_arb_flag, soft_jeita); + return rc; +} + +/************************ + * USB MAIN PSY SETTERS * + ************************/ +int smblite_lib_get_hw_current_max(struct smb_charger *chg, + int *total_current_ua) +{ + union power_supply_propval val = {0, }; + int rc = 0, typec_source_rd, current_ua; + bool non_compliant; + u8 stat; + + rc = smblite_lib_read(chg, LEGACY_CABLE_STATUS_REG, &stat); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't read TYPE_C_STATUS_5 rc=%d\n", + rc); + return rc; + } + non_compliant = stat & TYPEC_NONCOMP_LEGACY_CABLE_STATUS_BIT; + + /* get settled ICL */ + rc = smblite_lib_get_prop_input_current_settled(chg, &val); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't get settled ICL rc=%d\n", rc); + return rc; + } + + typec_source_rd = smblite_lib_get_prop_ufp_mode(chg); + + if ((chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB) + || (non_compliant && !chg->typec_legacy_use_rp_icl)) { + switch (chg->real_charger_type) { + case POWER_SUPPLY_TYPE_USB_CDP: + current_ua = CDP_CURRENT_UA; + break; + case POWER_SUPPLY_TYPE_USB_DCP: + current_ua = DCP_CURRENT_UA; + break; + default: + current_ua = 0; + break; + } + + *total_current_ua = max(current_ua, val.intval); + return 0; + } + + switch (typec_source_rd) { + case POWER_SUPPLY_TYPEC_SOURCE_DEFAULT: + switch (chg->real_charger_type) { + case POWER_SUPPLY_TYPE_USB_CDP: + current_ua = CDP_CURRENT_UA; + break; + case POWER_SUPPLY_TYPE_USB_DCP: + current_ua = DCP_CURRENT_UA; + break; + default: + current_ua = 0; + break; + } + break; + case POWER_SUPPLY_TYPEC_SOURCE_MEDIUM: + current_ua = TYPEC_MEDIUM_CURRENT_UA; + break; + case POWER_SUPPLY_TYPEC_SOURCE_HIGH: + current_ua = TYPEC_HIGH_CURRENT_UA; + break; + case POWER_SUPPLY_TYPEC_NON_COMPLIANT: + case POWER_SUPPLY_TYPEC_NONE: + default: + current_ua = 0; + break; + } + + *total_current_ua = max(current_ua, val.intval); + return 0; +} + +int smblite_lib_get_charge_current(struct smb_charger *chg, + int *total_current_ua) +{ + if (chg->usb_icl_votable) + *total_current_ua = get_effective_result(chg->usb_icl_votable); + + return 0; +} + +/********************** + * INTERRUPT HANDLERS * + **********************/ + +irqreturn_t smblite_default_irq_handler(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + + smblite_lib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name); + return IRQ_HANDLED; +} + +irqreturn_t smblite_chg_state_change_irq_handler(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + u8 stat; + int rc; + + smblite_lib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name); + + rc = smblite_lib_read(chg, BATTERY_CHARGER_STATUS_1_REG, &stat); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_1 rc=%d\n", + rc); + return IRQ_HANDLED; + } + + stat = stat & BATTERY_CHARGER_STATUS_MASK; + + power_supply_changed(chg->batt_psy); + return IRQ_HANDLED; +} + +irqreturn_t smblite_batt_temp_changed_irq_handler(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + int rc; + + smblite_lib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name); + + if (chg->jeita_configured != JEITA_CFG_COMPLETE) + return IRQ_HANDLED; + + rc = smblite_lib_soft_jeita_arb_wa(chg); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't fix soft jeita arb rc=%d\n", + rc); + return IRQ_HANDLED; + } + + return IRQ_HANDLED; +} + +irqreturn_t smblite_batt_psy_changed_irq_handler(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + + smblite_lib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name); + power_supply_changed(chg->batt_psy); + return IRQ_HANDLED; +} + +#define AICL_STEP_MV 200 +#define MAX_AICL_THRESHOLD_MV 4800 +irqreturn_t smblite_usbin_uv_irq_handler(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + struct storm_watch *wdata; + int rc; + + smblite_lib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name); + + if ((chg->wa_flags & WEAK_ADAPTER_WA) + && is_storming(&irq_data->storm_data)) { + + if (chg->aicl_max_reached) { + smblite_lib_dbg(chg, PR_MISC, + "USBIN_UV storm at max AICL threshold\n"); + return IRQ_HANDLED; + } + + smblite_lib_dbg(chg, PR_MISC, "USBIN_UV storm at threshold %d\n", + chg->aicl_5v_threshold_mv); + + /* suspend USBIN before updating AICL threshold */ + vote(chg->usb_icl_votable, AICL_THRESHOLD_VOTER, true, 0); + + /* delay for VASHDN deglitch */ + msleep(20); + + if (chg->aicl_5v_threshold_mv > MAX_AICL_THRESHOLD_MV) { + /* reached max AICL threshold */ + chg->aicl_max_reached = true; + goto unsuspend_input; + } + + /* Increase AICL threshold by 200mV */ + rc = smblite_lib_set_charge_param(chg, + &chg->param.aicl_5v_threshold, + chg->aicl_5v_threshold_mv + AICL_STEP_MV); + if (rc < 0) + dev_err(chg->dev, + "Error in setting AICL threshold rc=%d\n", rc); + else + chg->aicl_5v_threshold_mv += AICL_STEP_MV; + +unsuspend_input: + /* Force torch in boost mode to ensure it works with low ICL */ + schgm_flashlite_torch_priority(chg, TORCH_BOOST_MODE); + + if (chg->aicl_max_reached) { + smblite_lib_dbg(chg, PR_MISC, + "Reached max AICL threshold resctricting ICL to 100mA\n"); + vote(chg->usb_icl_votable, AICL_THRESHOLD_VOTER, + true, USBIN_100UA); + smblite_lib_run_aicl(chg, RESTART_AICL); + } else { + smblite_lib_run_aicl(chg, RESTART_AICL); + vote(chg->usb_icl_votable, AICL_THRESHOLD_VOTER, + false, 0); + } + + wdata = &chg->irq_info[USBIN_UV_IRQ].irq_data->storm_data; + reset_storm_count(wdata); + } + + if (!chg->irq_info[SWITCHER_POWER_OK_IRQ].irq_data) + return IRQ_HANDLED; + + wdata = &chg->irq_info[SWITCHER_POWER_OK_IRQ].irq_data->storm_data; + reset_storm_count(wdata); + + return IRQ_HANDLED; +} + +#define USB_WEAK_INPUT_UA 1400000 +#define ICL_CHANGE_DELAY_MS 1000 +irqreturn_t smblite_icl_change_irq_handler(int irq, void *data) +{ + int rc, settled_ua, delay = ICL_CHANGE_DELAY_MS; + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + + if (chg->mode == PARALLEL_MASTER) { + rc = smblite_lib_get_charge_param(chg, &chg->param.icl_stat, + &settled_ua); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't get ICL status rc=%d\n", + rc); + return IRQ_HANDLED; + } + + /* If AICL settled then schedule work now */ + if (settled_ua == get_effective_result(chg->usb_icl_votable)) + delay = 0; + + cancel_delayed_work_sync(&chg->icl_change_work); + schedule_delayed_work(&chg->icl_change_work, + msecs_to_jiffies(delay)); + } + + return IRQ_HANDLED; +} + +static int smblite_lib_role_switch_failure(struct smb_charger *chg) +{ + int rc = 0; + union power_supply_propval pval = {0, }; + + rc = smblite_lib_get_prop_usb_present(chg, &pval); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't get usb presence status rc=%d\n", + rc); + return rc; + } + + /* + * When role switch fails notify the + * current charger state to usb driver. + */ + if (pval.intval) { + smblite_lib_dbg(chg, PR_MISC, "Role reversal failed, notifying device mode to usb driver.\n"); + smblite_lib_notify_device_mode(chg, true); + } + + return rc; +} + +static int typec_partner_register(struct smb_charger *chg) +{ + int typec_mode, rc = 0; + + mutex_lock(&chg->typec_lock); + + if (!chg->typec_port || chg->pr_swap_in_progress) + goto unlock; + + if (!chg->typec_partner) { + if (chg->sink_src_mode == AUDIO_ACCESS_MODE) + chg->typec_partner_desc.accessory = + TYPEC_ACCESSORY_AUDIO; + else + chg->typec_partner_desc.accessory = + TYPEC_ACCESSORY_NONE; + + chg->typec_partner = typec_register_partner(chg->typec_port, + &chg->typec_partner_desc); + if (IS_ERR(chg->typec_partner)) { + rc = PTR_ERR(chg->typec_partner); + pr_err("Couldn't to register typec_partner rc=%d\n", + rc); + goto unlock; + } + } + + if (chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB) + goto unlock; + + typec_mode = smblite_lib_get_prop_typec_mode(chg); + + if (typec_mode >= POWER_SUPPLY_TYPEC_SOURCE_DEFAULT + || typec_mode == POWER_SUPPLY_TYPEC_NONE) { + if (chg->typec_role_swap_failed) { + rc = smblite_lib_role_switch_failure(chg); + if (rc < 0) + smblite_lib_err(chg, "Failed to role switch rc=%d\n", + rc); + chg->typec_role_swap_failed = false; + } + + typec_set_data_role(chg->typec_port, TYPEC_DEVICE); + typec_set_pwr_role(chg->typec_port, TYPEC_SINK); + } else { + typec_set_data_role(chg->typec_port, TYPEC_HOST); + typec_set_pwr_role(chg->typec_port, TYPEC_SOURCE); + } + +unlock: + mutex_unlock(&chg->typec_lock); + return rc; +} + +static void typec_partner_unregister(struct smb_charger *chg) +{ + mutex_lock(&chg->typec_lock); + + if (!chg->typec_port) + goto unlock; + + if (chg->typec_partner && !chg->pr_swap_in_progress) { + smblite_lib_dbg(chg, PR_MISC, "Un-registering typeC partner\n"); + typec_unregister_partner(chg->typec_partner); + chg->typec_partner = NULL; + } + +unlock: + mutex_unlock(&chg->typec_lock); +} + +static void smblite_lib_micro_usb_plugin(struct smb_charger *chg, + bool vbus_rising) +{ + int rc = 0; + + if (vbus_rising) { + smblite_lib_notify_device_mode(chg, true); + rc = typec_partner_register(chg); + if (rc < 0) + smblite_lib_err(chg, "Couldn't register partner rc =%d\n", + rc); + } else { + smblite_lib_notify_device_mode(chg, false); + smblite_lib_uusb_removal(chg); + typec_partner_unregister(chg); + } +} + +#define PL_DELAY_MS 30000 +void smblite_lib_usb_plugin_locked(struct smb_charger *chg) +{ + int rc; + u8 stat; + bool vbus_rising; + struct smb_irq_data *data; + struct storm_watch *wdata; + + rc = smblite_lib_read(chg, USBIN_BASE + INT_RT_STS_OFFSET, &stat); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't read USB_INT_RT_STS rc=%d\n", + rc); + return; + } + + vbus_rising = (bool)(stat & USBIN_PLUGIN_RT_STS_BIT); + + if (vbus_rising) { + /* Remove FCC_STEPPER 1.5A init vote to allow FCC ramp up */ + if (chg->fcc_stepper_enable) + vote(chg->fcc_votable, FCC_STEPPER_VOTER, false, 0); + + /* Schedule work to enable parallel charger */ + vote(chg->awake_votable, PL_DELAY_VOTER, true, 0); + schedule_delayed_work(&chg->pl_enable_work, + msecs_to_jiffies(PL_DELAY_MS)); + } else { + smblite_lib_update_usb_type(chg, POWER_SUPPLY_TYPE_UNKNOWN); + /* Disable SW Thermal Regulation */ + if (chg->wa_flags & BOOST_BACK_WA) { + data = chg->irq_info[SWITCHER_POWER_OK_IRQ].irq_data; + if (data) { + wdata = &data->storm_data; + update_storm_count(wdata, + WEAK_CHG_STORM_COUNT); + vote(chg->usb_icl_votable, BOOST_BACK_VOTER, + false, 0); + vote(chg->usb_icl_votable, WEAK_CHARGER_VOTER, + false, 0); + } + } + + /* Force 1500mA FCC on removal if fcc stepper is enabled */ + if (chg->fcc_stepper_enable) + vote(chg->fcc_votable, FCC_STEPPER_VOTER, + true, 1500000); + + if (chg->wa_flags & WEAK_ADAPTER_WA) { + chg->aicl_5v_threshold_mv = + chg->default_aicl_5v_threshold_mv; + + smblite_lib_set_charge_param(chg, + &chg->param.aicl_5v_threshold, + chg->aicl_5v_threshold_mv); + chg->aicl_max_reached = false; + + /* + * schgm_flash_torch_priority(chg, TORCH_BUCK_MODE); + */ + + data = chg->irq_info[USBIN_UV_IRQ].irq_data; + if (data) { + wdata = &data->storm_data; + reset_storm_count(wdata); + } + vote(chg->usb_icl_votable, AICL_THRESHOLD_VOTER, + false, 0); + } + } + + if (chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB) + smblite_lib_micro_usb_plugin(chg, vbus_rising); + + vote(chg->temp_change_irq_disable_votable, DEFAULT_VOTER, + !vbus_rising, 0); + power_supply_changed(chg->usb_psy); + smblite_lib_dbg(chg, PR_INTERRUPT, "IRQ: usbin-plugin %s\n", + vbus_rising ? "attached" : "detached"); +} + +irqreturn_t smblite_usb_plugin_irq_handler(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + + smblite_lib_usb_plugin_locked(chg); + + return IRQ_HANDLED; +} + +static void update_sw_icl_max(struct smb_charger *chg, + enum power_supply_type type) +{ + int typec_mode; + int rp_ua, icl_ua; + + if (chg->typec_mode == POWER_SUPPLY_TYPEC_SINK_AUDIO_ADAPTER) { + vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, true, 500000); + return; + } + + if (chg->real_charger_type == POWER_SUPPLY_TYPE_UNKNOWN) { + vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, true, + USBIN_100UA); + return; + } + + /* TypeC rp med or high, use rp value */ + typec_mode = smblite_lib_get_prop_typec_mode(chg); + if (typec_rp_med_high(chg, typec_mode)) { + rp_ua = get_rp_based_dcp_current(chg, typec_mode); + vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, true, rp_ua); + vote(chg->usb_icl_votable, USB_PSY_VOTER, false, 0); + return; + } + + /* rp-std or legacy, USB BC 1.2 */ + switch (type) { + case POWER_SUPPLY_TYPE_USB: + /* + * USB_PSY will vote to increase the current to 500/900mA once + * enumeration is done. + */ + if (!is_client_vote_enabled(chg->usb_icl_votable, + USB_PSY_VOTER)) { + /* if flash is active force 500mA */ + vote(chg->usb_icl_votable, USB_PSY_VOTER, true, + is_flashlite_active(chg) ? + USBIN_500UA : USBIN_100UA); + } + vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, false, 0); + break; + case POWER_SUPPLY_TYPE_USB_CDP: + vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, true, + CDP_CURRENT_UA); + break; + case POWER_SUPPLY_TYPE_USB_DCP: + rp_ua = get_rp_based_dcp_current(chg, typec_mode); + vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, true, rp_ua); + break; + case POWER_SUPPLY_TYPE_UNKNOWN: + default: + vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, true, + USBIN_100UA); + break; + } + + if (is_flashlite_active(chg)) { + icl_ua = get_effective_result(chg->usb_icl_votable); + if (icl_ua >= USBIN_400UA) { + vote(chg->usb_icl_votable, FLASH_ACTIVE_VOTER, true, + icl_ua - USBIN_300UA); + smblite_lib_dbg(chg, PR_MISC, "flash_active = 1 ICL is set to %d\n", + icl_ua - USBIN_300UA); + } + } +} + +static void typec_sink_insertion(struct smb_charger *chg) +{ + smblite_lib_notify_usb_host(chg, true); +} + +static void typec_src_insertion(struct smb_charger *chg) +{ + int rc = 0; + u8 stat; + + smblite_lib_notify_device_mode(chg, true); + if (chg->pr_swap_in_progress) { + vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, false, 0); + return; + } + + rc = smblite_lib_read(chg, LEGACY_CABLE_STATUS_REG, &stat); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't read TYPE_C_STATE_MACHINE_STATUS_REG rc=%d\n", + rc); + return; + } + + chg->typec_legacy = stat & TYPEC_LEGACY_CABLE_STATUS_BIT; + +} + +static void typec_ra_ra_insertion(struct smb_charger *chg) +{ + vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, true, 500000); + vote(chg->usb_icl_votable, USB_PSY_VOTER, false, 0); +} + +static const char * const dr_mode_text[] = { + "ufp", "dfp", "none" +}; + +static int smblite_lib_force_dr_mode(struct smb_charger *chg, int mode) +{ + int rc = 0; + + switch (mode) { + case TYPEC_PORT_SNK: + rc = smblite_lib_masked_write(chg, TYPE_C_MODE_CFG_REG, + TYPEC_POWER_ROLE_CMD_MASK, EN_SNK_ONLY_BIT); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't enable snk, rc=%d\n", + rc); + return rc; + } + break; + case TYPEC_PORT_SRC: + rc = smblite_lib_masked_write(chg, TYPE_C_MODE_CFG_REG, + TYPEC_POWER_ROLE_CMD_MASK, EN_SRC_ONLY_BIT); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't enable src, rc=%d\n", + rc); + return rc; + } + break; + case TYPEC_PORT_DRP: + rc = smblite_lib_masked_write(chg, TYPE_C_MODE_CFG_REG, + TYPEC_POWER_ROLE_CMD_MASK, 0); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't enable DRP, rc=%d\n", + rc); + return rc; + } + break; + default: + smblite_lib_err(chg, "Power role %d not supported\n", mode); + return -EINVAL; + } + + chg->dr_mode = mode; + + return rc; +} + +int smblite_lib_typec_port_type_set(const struct typec_capability *cap, + enum typec_port_type type) +{ + struct smb_charger *chg = container_of(cap, + struct smb_charger, typec_caps); + int rc = 0; + + if (chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB) + return 0; + + mutex_lock(&chg->typec_lock); + + if ((chg->pr_swap_in_progress) || (type == TYPEC_PORT_DRP)) { + smblite_lib_dbg(chg, PR_MISC, "Ignoring port type request type = %d swap_in_progress = %d\n", + type, chg->pr_swap_in_progress); + goto unlock; + } + + chg->pr_swap_in_progress = true; + + rc = smblite_lib_force_dr_mode(chg, type); + if (rc < 0) { + chg->pr_swap_in_progress = false; + smblite_lib_err(chg, "Couldn't to force mode, rc=%d\n", rc); + goto unlock; + } + + smblite_lib_dbg(chg, PR_MISC, "Requested role %s\n", + type ? "SINK" : "SOURCE"); + + /* + * As per the hardware requirements, + * schedule the work with required delay. + */ + if (!(delayed_work_pending(&chg->role_reversal_check))) { + cancel_delayed_work_sync(&chg->role_reversal_check); + schedule_delayed_work(&chg->role_reversal_check, + msecs_to_jiffies(ROLE_REVERSAL_DELAY_MS)); + vote(chg->awake_votable, TYPEC_SWAP_VOTER, true, 0); + } + +unlock: + mutex_unlock(&chg->typec_lock); + return rc; +} + +static void smblite_lib_typec_role_check_work(struct work_struct *work) +{ + struct smb_charger *chg = container_of(work, struct smb_charger, + role_reversal_check.work); + int rc = 0; + + if (chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB) { + chg->pr_swap_in_progress = false; + vote(chg->awake_votable, TYPEC_SWAP_VOTER, false, 0); + return; + } + + mutex_lock(&chg->typec_lock); + + switch (chg->dr_mode) { + case TYPEC_PORT_SNK: + if (chg->typec_mode < POWER_SUPPLY_TYPEC_SOURCE_DEFAULT) { + smblite_lib_dbg(chg, PR_MISC, "Role reversal not latched to UFP in %d msecs. Resetting to DRP mode\n", + ROLE_REVERSAL_DELAY_MS); + rc = smblite_lib_force_dr_mode(chg, TYPEC_PORT_DRP); + if (rc < 0) + smblite_lib_err(chg, "Couldn't to set DRP mode, rc=%d\n", + rc); + } else { + chg->power_role = POWER_SUPPLY_TYPEC_PR_SINK; + typec_set_pwr_role(chg->typec_port, TYPEC_SINK); + typec_set_data_role(chg->typec_port, TYPEC_DEVICE); + smblite_lib_dbg(chg, PR_MISC, "Role changed successfully to SINK"); + } + break; + case TYPEC_PORT_SRC: + if (chg->typec_mode >= POWER_SUPPLY_TYPEC_SOURCE_DEFAULT + || chg->typec_mode == POWER_SUPPLY_TYPEC_NONE) { + smblite_lib_dbg(chg, PR_MISC, "Role reversal not latched to DFP in %d msecs. Resetting to DRP mode\n", + ROLE_REVERSAL_DELAY_MS); + chg->pr_swap_in_progress = false; + chg->typec_role_swap_failed = true; + rc = smblite_lib_force_dr_mode(chg, + TYPEC_PORT_DRP); + if (rc < 0) + smblite_lib_err(chg, "Couldn't to set DRP mode, rc=%d\n", + rc); + } else { + chg->power_role = POWER_SUPPLY_TYPEC_PR_SOURCE; + typec_set_pwr_role(chg->typec_port, TYPEC_SOURCE); + typec_set_data_role(chg->typec_port, TYPEC_HOST); + smblite_lib_dbg(chg, PR_MISC, "Role changed successfully to SOURCE"); + } + break; + default: + pr_debug("Already in DRP mode\n"); + break; + } + + chg->pr_swap_in_progress = false; + vote(chg->awake_votable, TYPEC_SWAP_VOTER, false, 0); + mutex_unlock(&chg->typec_lock); +} + +static void typec_sink_removal(struct smb_charger *chg) +{ + if (chg->otg_present) + smblite_lib_notify_usb_host(chg, false); +} + +static void typec_src_removal(struct smb_charger *chg) +{ + struct smb_irq_data *data; + struct storm_watch *wdata; + + if (chg->wa_flags & BOOST_BACK_WA) { + data = chg->irq_info[SWITCHER_POWER_OK_IRQ].irq_data; + if (data) { + wdata = &data->storm_data; + update_storm_count(wdata, WEAK_CHG_STORM_COUNT); + vote(chg->usb_icl_votable, BOOST_BACK_VOTER, false, 0); + vote(chg->usb_icl_votable, WEAK_CHARGER_VOTER, + false, 0); + } + } + + cancel_delayed_work_sync(&chg->pl_enable_work); + + /* reset input current limit voters */ + vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, true, + is_flashlite_active(chg) ? USBIN_500UA : USBIN_100UA); + vote(chg->usb_icl_votable, FLASH_ACTIVE_VOTER, false, 0); + vote(chg->usb_icl_votable, USB_PSY_VOTER, false, 0); + + /* reset parallel voters */ + vote(chg->pl_disable_votable, PL_DELAY_VOTER, true, 0); + vote(chg->pl_enable_votable_indirect, USBIN_I_VOTER, false, 0); + vote(chg->pl_enable_votable_indirect, USBIN_V_VOTER, false, 0); + vote(chg->awake_votable, PL_DELAY_VOTER, false, 0); + + /* Remove SW thermal regulation votes */ + vote(chg->usb_icl_votable, SW_THERM_REGULATION_VOTER, false, 0); + vote(chg->awake_votable, SW_THERM_REGULATION_VOTER, false, 0); + + smblite_lib_notify_device_mode(chg, false); + + chg->typec_legacy = false; +} + +static void typec_mode_unattached(struct smb_charger *chg) +{ + vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, true, USBIN_100UA); +} + +static void smblite_lib_handle_rp_change(struct smb_charger *chg, + int typec_mode) +{ + /* + * if type is not updated or charger current is not set + * for SDP ignore Rp change requests. + */ + if (chg->real_charger_type == POWER_SUPPLY_TYPE_UNKNOWN + || (chg->real_charger_type == POWER_SUPPLY_TYPE_USB + && !is_client_vote_enabled(chg->usb_icl_votable, + USB_PSY_VOTER))) + return; + + update_sw_icl_max(chg, chg->real_charger_type); + smblite_lib_dbg(chg, PR_MISC, "CC change old_mode=%d new_mode=%d\n", + chg->typec_mode, typec_mode); +} + +irqreturn_t smblite_typec_or_rid_detection_change_irq_handler(int irq, + void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + + smblite_lib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name); + if (chg->usb_psy) + power_supply_changed(chg->usb_psy); + + return IRQ_HANDLED; +} + +irqreturn_t smblite_typec_state_change_irq_handler(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + int typec_mode; + + if (chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB) { + smblite_lib_dbg(chg, PR_INTERRUPT, + "Ignoring for micro USB\n"); + return IRQ_HANDLED; + } + + typec_mode = smblite_lib_get_prop_typec_mode(chg); + if (chg->sink_src_mode != UNATTACHED_MODE + && (typec_mode != chg->typec_mode)) + smblite_lib_handle_rp_change(chg, typec_mode); + chg->typec_mode = typec_mode; + + smblite_lib_dbg(chg, PR_INTERRUPT, "IRQ: cc-state-change; Type-C %s detected\n", + smblite_lib_typec_mode_name[chg->typec_mode]); + + power_supply_changed(chg->usb_psy); + + return IRQ_HANDLED; +} + +#define TYPEC_DETACH_DETECT_DELAY_MS 2000 +irqreturn_t smblite_typec_attach_detach_irq_handler(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + u8 stat; + bool attached = false; + int rc; + + /* IRQ not expected to be executed for uUSB, return */ + if (chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB) + return IRQ_HANDLED; + + smblite_lib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name); + + rc = smblite_lib_read(chg, TYPE_C_STATE_MACHINE_STATUS_REG, &stat); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't read TYPE_C_STATE_MACHINE_STATUS_REG rc=%d\n", + rc); + return IRQ_HANDLED; + } + + attached = !!(stat & TYPEC_ATTACH_DETACH_STATE_BIT); + + if (attached) { + rc = smblite_lib_read(chg, TYPE_C_MISC_STATUS_REG, &stat); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't read TYPE_C_MISC_STATUS_REG rc=%d\n", + rc); + return IRQ_HANDLED; + } + + if (smblite_lib_get_prop_dfp_mode(chg) == + POWER_SUPPLY_TYPEC_SINK_AUDIO_ADAPTER) { + chg->sink_src_mode = AUDIO_ACCESS_MODE; + typec_ra_ra_insertion(chg); + } else if (stat & SNK_SRC_MODE_BIT) { + chg->sink_src_mode = SRC_MODE; + typec_sink_insertion(chg); + } else { + chg->sink_src_mode = SINK_MODE; + typec_src_insertion(chg); + } + + rc = typec_partner_register(chg); + if (rc < 0) + smblite_lib_err(chg, "Couldn't to register partner rc =%d\n", + rc); + } else { + switch (chg->sink_src_mode) { + case SRC_MODE: + typec_sink_removal(chg); + break; + case SINK_MODE: + case AUDIO_ACCESS_MODE: + typec_src_removal(chg); + break; + case UNATTACHED_MODE: + default: + typec_mode_unattached(chg); + break; + } + + if (!chg->pr_swap_in_progress) + chg->sink_src_mode = UNATTACHED_MODE; + + /* + * Restore DRP mode on type-C cable disconnect if role + * swap is not in progress, to ensure forced sink or src + * mode configuration is reset properly. + */ + + if (chg->typec_port && !chg->pr_swap_in_progress) { + /* + * Schedule the work to differentiate actual removal + * of cable and detach interrupt during role swap, + * unregister the partner only during actual cable + * removal. + */ + cancel_delayed_work(&chg->pr_swap_detach_work); + vote(chg->awake_votable, DETACH_DETECT_VOTER, true, 0); + schedule_delayed_work(&chg->pr_swap_detach_work, + msecs_to_jiffies(TYPEC_DETACH_DETECT_DELAY_MS)); + smblite_lib_force_dr_mode(chg, TYPEC_PORT_DRP); + + /* + * To handle cable removal during role + * swap failure. + */ + chg->typec_role_swap_failed = false; + } + } + + rc = smblite_lib_masked_write(chg, USB_CMD_PULLDOWN_REG, + EN_PULLDOWN_USB_IN_BIT, + attached ? 0 : EN_PULLDOWN_USB_IN_BIT); + if (rc < 0) + smblite_lib_err(chg, "Couldn't configure pulldown on USB_IN rc=%d\n", + rc); + + power_supply_changed(chg->usb_psy); + + return IRQ_HANDLED; +} + +static void smblite_lib_bb_removal_work(struct work_struct *work) +{ + struct smb_charger *chg = container_of(work, struct smb_charger, + bb_removal_work.work); + + vote(chg->usb_icl_votable, BOOST_BACK_VOTER, false, 0); + vote(chg->awake_votable, BOOST_BACK_VOTER, false, 0); +} + +#define BOOST_BACK_UNVOTE_DELAY_MS 750 +#define BOOST_BACK_STORM_COUNT 3 +#define WEAK_CHG_STORM_COUNT 8 +irqreturn_t smblite_switcher_power_ok_irq_handler(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + struct storm_watch *wdata = &irq_data->storm_data; + int rc, usb_icl; + u8 stat; + + if (!(chg->wa_flags & BOOST_BACK_WA)) + return IRQ_HANDLED; + + rc = smblite_lib_read(chg, POWER_PATH_STATUS_REG, &stat); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't read POWER_PATH_STATUS rc=%d\n", + rc); + return IRQ_HANDLED; + } + + /* skip suspending input if its already suspended by some other voter */ + usb_icl = get_effective_result(chg->usb_icl_votable); + if ((stat & USE_USBIN_BIT) && usb_icl >= 0 && usb_icl <= USBIN_25UA) + return IRQ_HANDLED; + + if (is_storming(&irq_data->storm_data)) { + /* This could be a weak charger reduce ICL */ + if (!is_client_vote_enabled(chg->usb_icl_votable, + WEAK_CHARGER_VOTER)) { + smblite_lib_err(chg, + "Weak charger detected: voting %dmA ICL\n", + chg->weak_chg_icl_ua / 1000); + vote(chg->usb_icl_votable, WEAK_CHARGER_VOTER, + true, chg->weak_chg_icl_ua); + /* + * reset storm data and set the storm threshold + * to 3 for reverse boost detection. + */ + update_storm_count(wdata, BOOST_BACK_STORM_COUNT); + } else { + smblite_lib_err(chg, + "Reverse boost detected: voting 0mA to suspend input\n"); + vote(chg->usb_icl_votable, BOOST_BACK_VOTER, true, 0); + vote(chg->awake_votable, BOOST_BACK_VOTER, true, 0); + /* + * Remove the boost-back vote after a delay, to avoid + * permanently suspending the input if the boost-back + * condition is unintentionally hit. + */ + schedule_delayed_work(&chg->bb_removal_work, + msecs_to_jiffies(BOOST_BACK_UNVOTE_DELAY_MS)); + } + } + + return IRQ_HANDLED; +} + +irqreturn_t smblite_wdog_bark_irq_handler(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + int rc; + + smblite_lib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name); + + rc = smblite_lib_write(chg, BARK_BITE_WDOG_PET_REG, + BARK_BITE_WDOG_PET_BIT); + if (rc < 0) + smblite_lib_err(chg, "Couldn't pet the dog rc=%d\n", rc); + + power_supply_changed(chg->batt_psy); + + return IRQ_HANDLED; +} + +/* + * triggered when DIE temperature across + * either of the _REG_L, _REG_H, _RST, or _SHDN thresholds + */ +#define THERM_REGULATION_DELAY_MS 1000 +#define THERM_REGULATION_STEP_UA 100000 +irqreturn_t smblite_temp_change_irq_handler(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + + smblite_lib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name); + + vote(chg->awake_votable, SW_THERM_REGULATION_VOTER, true, 0); + cancel_delayed_work_sync(&chg->thermal_regulation_work); + schedule_delayed_work(&chg->thermal_regulation_work, + msecs_to_jiffies(THERM_REGULATION_DELAY_MS)); + + return IRQ_HANDLED; +} + +#define USB_OV_DBC_PERIOD_MS 1000 +irqreturn_t smblite_usbin_ov_irq_handler(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + + smblite_lib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name); + + return IRQ_HANDLED; +} + +irqreturn_t smblite_usb_id_irq_handler(int irq, void *data) +{ + struct smb_charger *chg = data; + bool id_state; + + id_state = gpio_get_value(chg->usb_id_gpio); + + smblite_lib_dbg(chg, PR_INTERRUPT, "IRQ: %s, id_state=%d\n", + "usb-id-irq", id_state); + + smblite_lib_notify_usb_host(chg, !id_state); + + return IRQ_HANDLED; +} + +/*************** + * Work Queues * + ***************/ +static void smblite_lib_pr_swap_detach_work(struct work_struct *work) +{ + struct smb_charger *chg = container_of(work, struct smb_charger, + pr_swap_detach_work.work); + int rc; + u8 stat; + + rc = smblite_lib_read(chg, TYPE_C_STATE_MACHINE_STATUS_REG, &stat); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't read STATE_MACHINE_STS rc=%d\n", + rc); + goto out; + } + smblite_lib_dbg(chg, PR_REGISTER, "STATE_MACHINE_STS %#x\n", stat); + + if (!(stat & TYPEC_ATTACH_DETACH_STATE_BIT)) + typec_partner_unregister(chg); + +out: + vote(chg->awake_votable, DETACH_DETECT_VOTER, false, 0); +} + +static void bms_update_work(struct work_struct *work) +{ + struct smb_charger *chg = container_of(work, struct smb_charger, + bms_update_work); + + smblite_lib_suspend_on_debug_battery(chg); + + if (chg->batt_psy) + power_supply_changed(chg->batt_psy); +} + +static void smblite_lib_icl_change_work(struct work_struct *work) +{ + struct smb_charger *chg = container_of(work, struct smb_charger, + icl_change_work.work); + int rc, settled_ua; + + rc = smblite_lib_get_charge_param(chg, &chg->param.icl_stat, + &settled_ua); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't get ICL status rc=%d\n", rc); + return; + } + + power_supply_changed(chg->usb_main_psy); + + smblite_lib_dbg(chg, PR_INTERRUPT, "icl_settled=%d\n", settled_ua); +} + +static void smblite_lib_pl_enable_work(struct work_struct *work) +{ + struct smb_charger *chg = container_of(work, struct smb_charger, + pl_enable_work.work); + + smblite_lib_dbg(chg, PR_PARALLEL, "timer expired, enabling parallel\n"); + vote(chg->pl_disable_votable, PL_DELAY_VOTER, false, 0); + vote(chg->awake_votable, PL_DELAY_VOTER, false, 0); +} + +static void smblite_lib_thermal_regulation_work(struct work_struct *work) +{ + struct smb_charger *chg = container_of(work, struct smb_charger, + thermal_regulation_work.work); + int rc, icl_ua, input_present; + u8 stat; + + + if (!chg->usb_icl_votable) + goto exit; + + rc = smblite_lib_is_input_present(chg, &input_present); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't read input status rc=%d\n", rc); + goto exit; + } + + if (input_present == INPUT_NOT_PRESENT) { + vote(chg->usb_icl_votable, SW_THERM_REGULATION_VOTER, false, 0); + goto exit; + } + + rc = smblite_lib_read(chg, DIE_TEMP_STATUS_REG, &stat); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't read DIE_TEMP_STATUS_REG, rc=%d\n", + rc); + goto reschedule; + } + + icl_ua = get_effective_result(chg->usb_icl_votable); + + if (stat & DIE_TEMP_RST_BIT) { + vote(chg->usb_icl_votable, SW_THERM_REGULATION_VOTER, + true, USBIN_500UA); + icl_ua = USBIN_500UA; + goto exit; + } + + if (stat & DIE_TEMP_UB_BIT) { + /* Check if we reached minimum ICL limit */ + if (icl_ua < USBIN_500UA + THERM_REGULATION_STEP_UA) + goto exit; + + /* Decrement ICL by one step */ + icl_ua -= THERM_REGULATION_STEP_UA; + vote(chg->usb_icl_votable, SW_THERM_REGULATION_VOTER, + true, icl_ua); + + goto reschedule; + } + + /* check if DIE_TEMP is below LB */ + if (!(stat & DIE_TEMP_MASK)) { + + /* + * Check if we need further increments: + * If thermal is still effective client then work can continue + * with increment otherwise if other voter has voted a lower + * ICL then remove vote and exit work. + */ + if (!strcmp(get_effective_client(chg->usb_icl_votable), + SW_THERM_REGULATION_VOTER)) { + icl_ua += THERM_REGULATION_STEP_UA; + vote(chg->usb_icl_votable, SW_THERM_REGULATION_VOTER, + true, icl_ua); + goto reschedule; + } + } + +exit: + smblite_lib_dbg(chg, PR_MISC, + "exiting DIE_TEMP regulation work DIE_TEMP_STATUS=%x icl=%duA\n", + stat, icl_ua); + vote(chg->awake_votable, SW_THERM_REGULATION_VOTER, false, 0); + return; + +reschedule: + smblite_lib_dbg(chg, PR_MISC, + "rescheduling DIE_TEMP regulation work DIE_TEMP_STATUS=%x icl=%duA\n", + stat, icl_ua); + schedule_delayed_work(&chg->thermal_regulation_work, + msecs_to_jiffies(THERM_REGULATION_DELAY_MS)); +} + +#define SOFT_JEITA_HYSTERESIS_OFFSET 0x200 +static void jeita_update_work(struct work_struct *work) +{ + struct smb_charger *chg = container_of(work, struct smb_charger, + jeita_update_work); + struct device_node *node = chg->dev->of_node; + struct device_node *batt_node, *pnode; + union power_supply_propval val; + int rc, tmp[2], max_fcc_ma, max_fv_uv; + u32 jeita_hard_thresholds[2]; + u16 addr; + u8 buff[2]; + + batt_node = of_find_node_by_name(node, "qcom,battery-data"); + if (!batt_node) { + smblite_lib_err(chg, "Batterydata not available\n"); + goto out; + } + + /* if BMS is not ready, defer the work */ + if (!chg->bms_psy) + return; + + rc = smblite_lib_get_prop_from_bms(chg, + POWER_SUPPLY_PROP_RESISTANCE_ID, &val); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't to get batt-id rc=%d\n", rc); + goto out; + } + + /* if BMS hasn't read out the batt_id yet, defer the work */ + if (val.intval <= 0) + return; + + pnode = of_batterydata_get_best_profile(batt_node, + val.intval / 1000, NULL); + if (IS_ERR(pnode)) { + rc = PTR_ERR(pnode); + smblite_lib_err(chg, "Couldn't to detect valid battery profile %d\n", + rc); + goto out; + } + + rc = of_property_read_u32_array(pnode, "qcom,jeita-hard-thresholds", + jeita_hard_thresholds, 2); + if (!rc) { + rc = smblite_lib_update_jeita(chg, jeita_hard_thresholds, + JEITA_HARD); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't configure Hard Jeita rc=%d\n", + rc); + goto out; + } + } + + rc = of_property_read_u32_array(pnode, "qcom,jeita-soft-thresholds", + chg->jeita_soft_thlds, 2); + if (!rc) { + rc = smblite_lib_update_jeita(chg, chg->jeita_soft_thlds, + JEITA_SOFT); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't configure Soft Jeita rc=%d\n", + rc); + goto out; + } + + rc = of_property_read_u32_array(pnode, + "qcom,jeita-soft-hys-thresholds", + chg->jeita_soft_hys_thlds, 2); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't get Soft Jeita hysteresis thresholds rc=%d\n", + rc); + goto out; + } + } else { + /* Populate the jeita-soft-thresholds */ + addr = CHGR_JEITA_COOL_THRESHOLD_REG; + rc = smblite_lib_batch_read(chg, addr, buff, 2); + if (rc < 0) { + pr_err("Couldn't to read 0x%4X, rc=%d\n", addr, rc); + goto out; + } + chg->jeita_soft_thlds[0] = buff[1] | buff[0] << 8; + + rc = smblite_lib_batch_read(chg, addr + 2, buff, 2); + if (rc < 0) { + pr_err("Couldn't to read 0x%4X, rc=%d\n", addr + 2, rc); + goto out; + } + chg->jeita_soft_thlds[1] = buff[1] | buff[0] << 8; + + /* + * Update the soft jeita hysteresis 2 DegC less for warm and + * 2 DegC more for cool than the soft jeita thresholds to avoid + * overwriting the registers with invalid values. + */ + chg->jeita_soft_hys_thlds[1] = + chg->jeita_soft_thlds[0] - SOFT_JEITA_HYSTERESIS_OFFSET; + chg->jeita_soft_hys_thlds[0] = + chg->jeita_soft_thlds[1] + SOFT_JEITA_HYSTERESIS_OFFSET; + } + + chg->jeita_soft_fcc[0] = chg->jeita_soft_fcc[1] = -EINVAL; + chg->jeita_soft_fv[0] = chg->jeita_soft_fv[1] = -EINVAL; + max_fcc_ma = max_fv_uv = -EINVAL; + + of_property_read_u32(pnode, "qcom,fastchg-current-ma", &max_fcc_ma); + of_property_read_u32(pnode, "qcom,max-voltage-uv", &max_fv_uv); + + if (max_fcc_ma <= 0 || max_fv_uv <= 0) { + smblite_lib_err(chg, "Incorrect fastchg-current-ma or max-voltage-uv\n"); + goto out; + } + + rc = of_property_read_u32_array(pnode, "qcom,jeita-soft-fcc-ua", + tmp, 2); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't get fcc values for soft JEITA rc=%d\n", + rc); + goto out; + } + + max_fcc_ma *= 1000; + if (tmp[0] > max_fcc_ma || tmp[1] > max_fcc_ma) { + smblite_lib_err(chg, "Incorrect FCC value [%d %d] max: %d\n", + tmp[0], tmp[1], max_fcc_ma); + goto out; + } + chg->jeita_soft_fcc[0] = tmp[0]; + chg->jeita_soft_fcc[1] = tmp[1]; + + rc = of_property_read_u32_array(pnode, "qcom,jeita-soft-fv-uv", tmp, + 2); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't get fv values for soft JEITA rc=%d\n", + rc); + goto out; + } + + if (tmp[0] > max_fv_uv || tmp[1] > max_fv_uv) { + smblite_lib_err(chg, "Incorrect FV value [%d %d] max: %d\n", + tmp[0], tmp[1], max_fv_uv); + goto out; + } + chg->jeita_soft_fv[0] = tmp[0]; + chg->jeita_soft_fv[1] = tmp[1]; + + rc = smblite_lib_soft_jeita_arb_wa(chg); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't fix soft jeita arb rc=%d\n", + rc); + goto out; + } + + chg->jeita_configured = JEITA_CFG_COMPLETE; + return; + +out: + chg->jeita_configured = JEITA_CFG_FAILURE; +} + +static int smblite_lib_create_votables(struct smb_charger *chg) +{ + int rc = 0; + + chg->fcc_votable = find_votable("FCC"); + if (chg->fcc_votable == NULL) { + rc = -EINVAL; + smblite_lib_err(chg, "Couldn't find FCC votable rc=%d\n", rc); + return rc; + } + + chg->fcc_main_votable = find_votable("FCC_MAIN"); + if (chg->fcc_main_votable == NULL) { + rc = -EINVAL; + smblite_lib_err(chg, "Couldn't find FCC Main votable rc=%d\n", + rc); + return rc; + } + + chg->fv_votable = find_votable("FV"); + if (chg->fv_votable == NULL) { + rc = -EINVAL; + smblite_lib_err(chg, "Couldn't find FV votable rc=%d\n", rc); + return rc; + } + + chg->usb_icl_votable = find_votable("USB_ICL"); + if (chg->usb_icl_votable == NULL) { + rc = -EINVAL; + smblite_lib_err(chg, "Couldn't find USB_ICL votable rc=%d\n", + rc); + return rc; + } + + chg->pl_disable_votable = find_votable("PL_DISABLE"); + if (chg->pl_disable_votable == NULL) { + rc = -EINVAL; + smblite_lib_err(chg, "Couldn't find votable PL_DISABLE rc=%d\n", + rc); + return rc; + } + + chg->pl_enable_votable_indirect = find_votable("PL_ENABLE_INDIRECT"); + if (chg->pl_enable_votable_indirect == NULL) { + rc = -EINVAL; + smblite_lib_err(chg, + "Couldn't find votable PL_ENABLE_INDIRECT rc=%d\n", + rc); + return rc; + } + + vote(chg->pl_disable_votable, PL_DELAY_VOTER, true, 0); + + chg->awake_votable = create_votable("AWAKE", VOTE_SET_ANY, + smblite_lib_awake_vote_callback, + chg); + if (IS_ERR(chg->awake_votable)) { + rc = PTR_ERR(chg->awake_votable); + chg->awake_votable = NULL; + return rc; + } + + chg->chg_disable_votable = create_votable("CHG_DISABLE", VOTE_SET_ANY, + smblite_lib_chg_disable_vote_callback, + chg); + if (IS_ERR(chg->chg_disable_votable)) { + rc = PTR_ERR(chg->chg_disable_votable); + chg->chg_disable_votable = NULL; + return rc; + } + + chg->icl_irq_disable_votable = create_votable("USB_ICL_IRQ_DISABLE", + VOTE_SET_ANY, + smblite_lib_icl_irq_disable_vote_callback, + chg); + if (IS_ERR(chg->icl_irq_disable_votable)) { + rc = PTR_ERR(chg->icl_irq_disable_votable); + chg->icl_irq_disable_votable = NULL; + return rc; + } + + chg->temp_change_irq_disable_votable = create_votable( + "TEMP_CHANGE_IRQ_DISABLE", VOTE_SET_ANY, + smblite_lib_temp_change_irq_disable_vote_callback, chg); + if (IS_ERR(chg->temp_change_irq_disable_votable)) { + rc = PTR_ERR(chg->temp_change_irq_disable_votable); + chg->temp_change_irq_disable_votable = NULL; + return rc; + } + + return rc; +} + +static void smblite_lib_destroy_votables(struct smb_charger *chg) +{ + if (chg->usb_icl_votable) + destroy_votable(chg->usb_icl_votable); + if (chg->awake_votable) + destroy_votable(chg->awake_votable); + if (chg->chg_disable_votable) + destroy_votable(chg->chg_disable_votable); +} + +static void smblite_lib_iio_deinit(struct smb_charger *chg) +{ + if (!IS_ERR_OR_NULL(chg->iio.usbin_v_chan)) + iio_channel_release(chg->iio.usbin_v_chan); + if (!IS_ERR_OR_NULL(chg->iio.temp_chan)) + iio_channel_release(chg->iio.temp_chan); +} + +int smblite_lib_init(struct smb_charger *chg) +{ + int rc = 0; + + INIT_WORK(&chg->bms_update_work, bms_update_work); + INIT_WORK(&chg->jeita_update_work, jeita_update_work); + INIT_DELAYED_WORK(&chg->icl_change_work, smblite_lib_icl_change_work); + INIT_DELAYED_WORK(&chg->pl_enable_work, smblite_lib_pl_enable_work); + INIT_DELAYED_WORK(&chg->bb_removal_work, smblite_lib_bb_removal_work); + INIT_DELAYED_WORK(&chg->thermal_regulation_work, + smblite_lib_thermal_regulation_work); + INIT_DELAYED_WORK(&chg->role_reversal_check, + smblite_lib_typec_role_check_work); + INIT_DELAYED_WORK(&chg->pr_swap_detach_work, + smblite_lib_pr_swap_detach_work); + chg->fake_capacity = -EINVAL; + chg->fake_batt_status = -EINVAL; + chg->sink_src_mode = UNATTACHED_MODE; + chg->jeita_configured = false; + chg->dr_mode = TYPEC_PORT_DRP; + + switch (chg->mode) { + case PARALLEL_MASTER: + rc = qcom_batt_init(&chg->chg_param); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't init qcom_batt_init rc=%d\n", + rc); + return rc; + } + + rc = qcom_step_chg_init(chg->dev, true, true, false); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't init qcom_step_chg_init rc=%d\n", + rc); + return rc; + } + + rc = smblite_lib_create_votables(chg); + if (rc < 0) { + smblite_lib_err(chg, "Couldn't create votables rc=%d\n", + rc); + return rc; + } + + chg->bms_psy = power_supply_get_by_name("bms"); + rc = smblite_lib_register_notifier(chg); + if (rc < 0) { + smblite_lib_err(chg, + "Couldn't register notifier rc=%d\n", rc); + return rc; + } + break; + case PARALLEL_SLAVE: + break; + default: + smblite_lib_err(chg, "Unsupported mode %d\n", chg->mode); + return -EINVAL; + } + + return rc; +} + +int smblite_lib_deinit(struct smb_charger *chg) +{ + switch (chg->mode) { + case PARALLEL_MASTER: + cancel_work_sync(&chg->bms_update_work); + cancel_work_sync(&chg->jeita_update_work); + cancel_delayed_work_sync(&chg->icl_change_work); + cancel_delayed_work_sync(&chg->pl_enable_work); + cancel_delayed_work_sync(&chg->bb_removal_work); + cancel_delayed_work_sync(&chg->thermal_regulation_work); + cancel_delayed_work_sync(&chg->role_reversal_check); + cancel_delayed_work_sync(&chg->pr_swap_detach_work); + power_supply_unreg_notifier(&chg->nb); + smblite_lib_destroy_votables(chg); + qcom_step_chg_deinit(); + qcom_batt_deinit(); + break; + case PARALLEL_SLAVE: + break; + default: + smblite_lib_err(chg, "Unsupported mode %d\n", chg->mode); + return -EINVAL; + } + + smblite_lib_iio_deinit(chg); + + return 0; +} diff --git a/drivers/power/supply/qcom/smblite-lib.h b/drivers/power/supply/qcom/smblite-lib.h new file mode 100644 index 000000000000..9bfb9c97eb8a --- /dev/null +++ b/drivers/power/supply/qcom/smblite-lib.h @@ -0,0 +1,477 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2020 The Linux Foundation. All rights reserved. + */ + +#ifndef __SMBLITE_LIB_H +#define __SMBLITE_LIB_H +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "storm-watch.h" +#include "battery.h" + +enum print_reason { + PR_INTERRUPT = BIT(0), + PR_REGISTER = BIT(1), + PR_MISC = BIT(2), + PR_PARALLEL = BIT(3), + PR_OTG = BIT(4), +}; + +#define DEFAULT_VOTER "DEFAULT_VOTER" +#define USER_VOTER "USER_VOTER" +#define USB_PSY_VOTER "USB_PSY_VOTER" +#define USBIN_V_VOTER "USBIN_V_VOTER" +#define THERMAL_DAEMON_VOTER "THERMAL_DAEMON_VOTER" +#define BOOST_BACK_VOTER "BOOST_BACK_VOTER" +#define DEBUG_BOARD_VOTER "DEBUG_BOARD_VOTER" +#define PL_DELAY_VOTER "PL_DELAY_VOTER" +#define SW_ICL_MAX_VOTER "SW_ICL_MAX_VOTER" +#define BATT_PROFILE_VOTER "BATT_PROFILE_VOTER" +#define USBIN_I_VOTER "USBIN_I_VOTER" +#define WEAK_CHARGER_VOTER "WEAK_CHARGER_VOTER" +#define HW_LIMIT_VOTER "HW_LIMIT_VOTER" +#define FORCE_RECHARGE_VOTER "FORCE_RECHARGE_VOTER" +#define FCC_STEPPER_VOTER "FCC_STEPPER_VOTER" +#define SW_THERM_REGULATION_VOTER "SW_THERM_REGULATION_VOTER" +#define JEITA_ARB_VOTER "JEITA_ARB_VOTER" +#define AICL_THRESHOLD_VOTER "AICL_THRESHOLD_VOTER" +#define USB_SUSPEND_VOTER "USB_SUSPEND_VOTER" +#define DETACH_DETECT_VOTER "DETACH_DETECT_VOTER" +#define ICL_CHANGE_VOTER "ICL_CHANGE_VOTER" +#define TYPEC_SWAP_VOTER "TYPEC_SWAP_VOTER" +#define FLASH_ACTIVE_VOTER "FLASH_ACTIVE_VOTER" + +#define BOOST_BACK_STORM_COUNT 3 +#define WEAK_CHG_STORM_COUNT 8 + +#define VBAT_TO_VRAW_ADC(v) div_u64((u64)v * 1000000UL, 194637UL) + +#define ITERM_LIMITS_MA 10000 +#define ADC_CHG_ITERM_MASK 32767 + +#define USBIN_25UA 25000 +#define USBIN_100UA 100000 +#define USBIN_150UA 150000 +#define USBIN_300UA 300000 +#define USBIN_400UA 400000 +#define USBIN_500UA 500000 +#define USBIN_900UA 900000 +#define CDP_CURRENT_UA 1500000 +#define DCP_CURRENT_UA 1500000 +#define TYPEC_DEFAULT_CURRENT_UA 900000 +#define TYPEC_MEDIUM_CURRENT_UA 1500000 +#define TYPEC_HIGH_CURRENT_UA 3000000 +#define ROLE_REVERSAL_DELAY_MS 500 + +enum smb_mode { + PARALLEL_MASTER = 0, + PARALLEL_SLAVE, + NUM_MODES, +}; + +enum sink_src_mode { + SINK_MODE, + SRC_MODE, + AUDIO_ACCESS_MODE, + UNATTACHED_MODE, +}; + +enum { + BOOST_BACK_WA = BIT(0), + WEAK_ADAPTER_WA = BIT(1), + FLASH_DIE_TEMP_DERATE_WA = BIT(2), +}; + +enum jeita_cfg_stat { + JEITA_CFG_NONE = 0, + JEITA_CFG_FAILURE, + JEITA_CFG_COMPLETE, +}; + +enum { + RERUN_AICL = 0, + RESTART_AICL, +}; + +enum smb_irq_index { + /* CHGR */ + CHG_STATE_CHANGE_IRQ = 0, + CHGR_ERROR_IRQ, + BUCK_OC_IRQ, + VPH_OV_IRQ, + /* DCDC */ + OTG_FAIL_IRQ, + OTG_FAULT_IRQ, + SKIP_MODE_IRQ, + INPUT_CURRENT_LIMITING_IRQ, + SWITCHER_POWER_OK_IRQ, + /* BATIF */ + BAT_TEMP_IRQ, + BAT_THERM_OR_ID_MISSING_IRQ, + BAT_LOW_IRQ, + BAT_OV_IRQ, + BSM_ACTIVE_IRQ, + /* USB */ + USBIN_PLUGIN_IRQ, + USBIN_COLLAPSE_IRQ, + USBIN_UV_IRQ, + USBIN_OV_IRQ, + USBIN_GT_VT_IRQ, + USBIN_ICL_CHANGE_IRQ, + /* TYPEC */ + TYPEC_OR_RID_DETECTION_CHANGE_IRQ, + TYPEC_VPD_DETECT_IRQ, + TYPEC_CC_STATE_CHANGE_IRQ, + TYPEC_VBUS_CHANGE_IRQ, + TYPEC_ATTACH_DETACH_IRQ, + TYPEC_LEGACY_CABLE_DETECT_IRQ, + TYPEC_TRY_SNK_SRC_DETECT_IRQ, + /* MISC */ + WDOG_SNARL_IRQ, + WDOG_BARK_IRQ, + AICL_FAIL_IRQ, + AICL_DONE_IRQ, + IMP_TRIGGER_IRQ, + ALL_CHNL_CONV_DONE_IRQ, + TEMP_CHANGE_IRQ, + /* FLASH */ + VREG_OK_IRQ, + ILIM_S2_IRQ, + ILIM_S1_IRQ, + FLASH_STATE_CHANGE_IRQ, + TORCH_REQ_IRQ, + FLASH_EN_IRQ, + /* END */ + SMB_IRQ_MAX, +}; + +enum chg_term_config_src { + ITERM_SRC_UNSPECIFIED, + ITERM_SRC_ADC, + ITERM_SRC_ANALOG +}; + +struct smb_irq_info { + const char *name; + const irq_handler_t handler; + const bool wake; + const struct storm_watch storm_data; + struct smb_irq_data *irq_data; + int irq; + bool enabled; +}; + +static const unsigned int smblite_lib_extcon_cable[] = { + EXTCON_USB, + EXTCON_USB_HOST, + EXTCON_NONE, +}; + +enum icl_override_mode { + /* APSD/Type-C/QC auto */ + HW_AUTO_MODE, + /* 100/150/500/900mA */ + SW_OVERRIDE_USB51_MODE, + /* ICL other than USB51 */ + SW_OVERRIDE_HC_MODE, +}; + +/* EXTCON_USB and EXTCON_USB_HOST are mutually exclusive */ +static const u32 smblite_lib_extcon_exclusive[] = {0x3, 0}; + +struct smb_irq_data { + void *parent_data; + const char *name; + struct storm_watch storm_data; +}; + +struct smb_chg_param { + const char *name; + u16 reg; + int min_u; + int max_u; + int step_u; + int (*get_proc)(struct smb_chg_param *param, + u8 val_raw); + int (*set_proc)(struct smb_chg_param *param, + int val_u, + u8 *val_raw); +}; + +struct smb_params { + struct smb_chg_param fcc; + struct smb_chg_param fv; + struct smb_chg_param usb_icl; + struct smb_chg_param icl_max_stat; + struct smb_chg_param icl_stat; + struct smb_chg_param aicl_5v_threshold; +}; + +struct parallel_params { + struct power_supply *psy; +}; + +struct smb_iio { + struct iio_channel *temp_chan; + struct iio_channel *usbin_v_chan; +}; + +struct smb_charger { + struct device *dev; + char *name; + struct regmap *regmap; + struct smb_irq_info *irq_info; + struct smb_params param; + struct smb_iio iio; + int *debug_mask; + enum smb_mode mode; + int weak_chg_icl_ua; + + /* locks */ + struct mutex typec_lock; + + /* power supplies */ + struct power_supply *batt_psy; + struct power_supply *usb_psy; + struct power_supply *bms_psy; + struct power_supply *usb_main_psy; + enum power_supply_type real_charger_type; + + /* notifiers */ + struct notifier_block nb; + + /* parallel charging */ + struct parallel_params pl; + + /* typec */ + struct typec_port *typec_port; + struct typec_capability typec_caps; + struct typec_partner *typec_partner; + struct typec_partner_desc typec_partner_desc; + + /* votables */ + struct votable *fcc_votable; + struct votable *fcc_main_votable; + struct votable *fv_votable; + struct votable *usb_icl_votable; + struct votable *awake_votable; + struct votable *pl_disable_votable; + struct votable *chg_disable_votable; + struct votable *pl_enable_votable_indirect; + struct votable *icl_irq_disable_votable; + struct votable *temp_change_irq_disable_votable; + + /* work */ + struct work_struct bms_update_work; + struct work_struct pl_update_work; + struct work_struct jeita_update_work; + struct delayed_work icl_change_work; + struct delayed_work pl_enable_work; + struct delayed_work bb_removal_work; + struct delayed_work thermal_regulation_work; + struct delayed_work role_reversal_check; + struct delayed_work pr_swap_detach_work; + + struct charger_param chg_param; + + /* cached status */ + int system_temp_level; + int thermal_levels; + int *thermal_mitigation; + int fake_capacity; + int fake_batt_status; + bool step_chg_enabled; + bool typec_legacy_use_rp_icl; + int connector_type; + bool suspend_input_on_debug_batt; + bool fake_chg_status_on_debug_batt; + int typec_mode; + int dr_mode; + int term_vbat_uv; + u32 jeita_status; + bool jeita_arb_flag; + bool typec_legacy; + bool otg_present; + int auto_recharge_soc; + enum sink_src_mode sink_src_mode; + enum power_supply_typec_power_role power_role; + enum jeita_cfg_stat jeita_configured; + bool fcc_stepper_enable; + u32 jeita_soft_thlds[2]; + u32 jeita_soft_hys_thlds[2]; + int jeita_soft_fcc[2]; + int jeita_soft_fv[2]; + int aicl_5v_threshold_mv; + int default_aicl_5v_threshold_mv; + int cutoff_count; + bool aicl_max_reached; + bool pr_swap_in_progress; + bool ldo_mode; + int usb_id_gpio; + int usb_id_irq; + bool typec_role_swap_failed; + + /* workaround flag */ + u32 wa_flags; + int boost_current_ua; + + /* extcon for VBUS / ID notification to USB for uUSB */ + struct extcon_dev *extcon; + + /* battery profile */ + int batt_profile_fcc_ua; + int batt_profile_fv_uv; + + /* flash */ + u32 flash_derating_soc; + u32 flash_disable_soc; + u32 headroom_mode; + bool flash_init_done; + bool flash_active; + u32 irq_status; +}; + +int smblite_lib_read(struct smb_charger *chg, u16 addr, u8 *val); +int smblite_lib_masked_write(struct smb_charger *chg, u16 addr, u8 mask, + u8 val); +int smblite_lib_write(struct smb_charger *chg, u16 addr, u8 val); +int smblite_lib_batch_write(struct smb_charger *chg, u16 addr, u8 *val, + int count); +int smblite_lib_batch_read(struct smb_charger *chg, u16 addr, u8 *val, + int count); +int smblite_lib_get_charge_param(struct smb_charger *chg, + struct smb_chg_param *param, int *val_u); +int smblite_lib_get_usb_suspend(struct smb_charger *chg, int *suspend); +int smblite_lib_enable_charging(struct smb_charger *chg, bool enable); +int smblite_lib_set_charge_param(struct smb_charger *chg, + struct smb_chg_param *param, int val_u); +int smblite_lib_set_usb_suspend(struct smb_charger *chg, bool suspend); + +irqreturn_t smblite_default_irq_handler(int irq, void *data); +irqreturn_t smblite_chg_state_change_irq_handler(int irq, void *data); +irqreturn_t smblite_batt_temp_changed_irq_handler(int irq, void *data); +irqreturn_t smblite_batt_psy_changed_irq_handler(int irq, void *data); +irqreturn_t smblite_usbin_uv_irq_handler(int irq, void *data); +irqreturn_t smblite_usb_plugin_irq_handler(int irq, void *data); +irqreturn_t smblite_icl_change_irq_handler(int irq, void *data); +irqreturn_t smblite_typec_state_change_irq_handler(int irq, void *data); +irqreturn_t smblite_typec_attach_detach_irq_handler(int irq, void *data); +irqreturn_t smblite_switcher_power_ok_irq_handler(int irq, void *data); +irqreturn_t smblite_wdog_bark_irq_handler(int irq, void *data); +irqreturn_t smblite_typec_or_rid_detection_change_irq_handler(int irq, + void *data); +irqreturn_t smblite_temp_change_irq_handler(int irq, void *data); +irqreturn_t smblite_usbin_ov_irq_handler(int irq, void *data); +irqreturn_t smblite_usb_id_irq_handler(int irq, void *data); + +int smblite_lib_get_prop_input_suspend(struct smb_charger *chg, + union power_supply_propval *val); +int smblite_lib_get_prop_batt_present(struct smb_charger *chg, + union power_supply_propval *val); +int smblite_lib_get_prop_batt_capacity(struct smb_charger *chg, + union power_supply_propval *val); +int smblite_lib_get_prop_batt_status(struct smb_charger *chg, + union power_supply_propval *val); +int smblite_lib_get_prop_batt_charge_type(struct smb_charger *chg, + union power_supply_propval *val); +int smblite_lib_get_prop_batt_charge_done(struct smb_charger *chg, + union power_supply_propval *val); +int smblite_lib_get_batt_current_now(struct smb_charger *chg, + union power_supply_propval *val); +int smblite_lib_get_prop_batt_health(struct smb_charger *chg, + union power_supply_propval *val); +int smblite_lib_get_prop_system_temp_level(struct smb_charger *chg, + union power_supply_propval *val); +int smblite_lib_get_prop_system_temp_level_max(struct smb_charger *chg, + union power_supply_propval *val); +int smblite_lib_get_prop_input_current_limited(struct smb_charger *chg, + union power_supply_propval *val); +int smblite_lib_get_prop_batt_iterm(struct smb_charger *chg, + union power_supply_propval *val); +int smblite_lib_set_prop_input_suspend(struct smb_charger *chg, + const union power_supply_propval *val); +int smblite_lib_set_prop_batt_capacity(struct smb_charger *chg, + const union power_supply_propval *val); +int smblite_lib_set_prop_batt_status(struct smb_charger *chg, + const union power_supply_propval *val); +int smblite_lib_set_prop_system_temp_level(struct smb_charger *chg, + const union power_supply_propval *val); +int smblite_lib_get_prop_usb_present(struct smb_charger *chg, + union power_supply_propval *val); +int smblite_lib_get_prop_usb_online(struct smb_charger *chg, + union power_supply_propval *val); +int smblite_lib_get_usb_online(struct smb_charger *chg, + union power_supply_propval *val); +int smblite_lib_get_prop_usb_suspend(struct smb_charger *chg, + union power_supply_propval *val); +int smblite_lib_get_prop_usb_voltage_now(struct smb_charger *chg, + union power_supply_propval *val); +int smblite_lib_get_usb_prop_typec_mode(struct smb_charger *chg, + union power_supply_propval *val); +int smblite_lib_get_prop_typec_cc_orientation(struct smb_charger *chg, + union power_supply_propval *val); +int smblite_lib_get_prop_scope(struct smb_charger *chg, + union power_supply_propval *val); +int smblite_lib_get_prop_typec_power_role(struct smb_charger *chg, + union power_supply_propval *val); +int smblite_lib_get_prop_input_current_settled(struct smb_charger *chg, + union power_supply_propval *val); +int smblite_lib_get_prop_input_voltage_settled(struct smb_charger *chg, + union power_supply_propval *val); +int smblite_lib_get_prop_charger_temp(struct smb_charger *chg, + union power_supply_propval *val); +int smblite_lib_get_prop_die_health(struct smb_charger *chg); +int smblite_lib_get_die_health(struct smb_charger *chg, + union power_supply_propval *val); +int smblite_lib_set_prop_current_max(struct smb_charger *chg, + const union power_supply_propval *val); +int smblite_lib_set_prop_typec_power_role(struct smb_charger *chg, + const union power_supply_propval *val); +int smblite_lib_set_prop_ship_mode(struct smb_charger *chg, + const union power_supply_propval *val); +int smblite_lib_set_prop_rechg_soc_thresh(struct smb_charger *chg, + const union power_supply_propval *val); +void smblite_lib_suspend_on_debug_battery(struct smb_charger *chg); +int smblite_lib_get_prop_fcc_delta(struct smb_charger *chg, + union power_supply_propval *val); +int smblite_lib_get_thermal_threshold(struct smb_charger *chg, u16 addr, + int *val); +int smblite_lib_run_aicl(struct smb_charger *chg, int type); +int smblite_lib_set_icl_current(struct smb_charger *chg, int icl_ua); +int smblite_lib_get_icl_current(struct smb_charger *chg, int *icl_ua); +int smblite_lib_get_charge_current(struct smb_charger *chg, + int *total_current_ua); +int smblite_lib_get_hw_current_max(struct smb_charger *chg, + int *total_current_ua); +int smblite_lib_get_prop_pr_swap_in_progress(struct smb_charger *chg, + union power_supply_propval *val); +int smblite_lib_set_prop_pr_swap_in_progress(struct smb_charger *chg, + const union power_supply_propval *val); +int smblite_lib_typec_port_type_set(const struct typec_capability *cap, + enum typec_port_type type); +int smblite_lib_get_prop_from_bms(struct smb_charger *chg, + enum power_supply_property psp, + union power_supply_propval *val); +int smblite_lib_get_iio_channel(struct smb_charger *chg, const char *propname, + struct iio_channel **chan); +int smblite_lib_read_iio_channel(struct smb_charger *chg, + struct iio_channel *chan, int div, int *data); +int smblite_lib_icl_override(struct smb_charger *chg, + enum icl_override_mode mode); +int smblite_lib_get_irq_status(struct smb_charger *chg, + union power_supply_propval *val); +int smblite_lib_set_prop_usb_type(struct smb_charger *chg, + const union power_supply_propval *val); +void smblite_update_usb_desc(struct smb_charger *chg); +int smblite_lib_init(struct smb_charger *chg); +int smblite_lib_deinit(struct smb_charger *chg); +#endif /* __SMBLITE_LIB_H */