summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/power/qcom-charger/qpnp-smb2.c70
-rw-r--r--drivers/power/qcom-charger/smb-lib.c213
-rw-r--r--drivers/power/qcom-charger/smb-lib.h21
3 files changed, 272 insertions, 32 deletions
diff --git a/drivers/power/qcom-charger/qpnp-smb2.c b/drivers/power/qcom-charger/qpnp-smb2.c
index dec35a053778..9e752799affe 100644
--- a/drivers/power/qcom-charger/qpnp-smb2.c
+++ b/drivers/power/qcom-charger/qpnp-smb2.c
@@ -25,38 +25,45 @@
#include "smb-lib.h"
#include "pmic-voter.h"
-#define SMB2_DEFAULT_FCC_UA 1000000
+#define SMB2_DEFAULT_FCC_UA 3000000
#define SMB2_DEFAULT_FV_UV 4350000
-#define SMB2_DEFAULT_ICL_UA 1500000
+#define SMB2_DEFAULT_ICL_UA 3000000
static struct smb_params v1_params = {
- .fcc = {
- .name = "fast charge current",
- .reg = FAST_CHARGE_CURRENT_CFG_REG,
- .min_u = 0,
- .max_u = 4500000,
- .step_u = 25000,
+ .fcc = {
+ .name = "fast charge current",
+ .reg = FAST_CHARGE_CURRENT_CFG_REG,
+ .min_u = 0,
+ .max_u = 4500000,
+ .step_u = 25000,
},
- .fv = {
- .name = "float voltage",
- .reg = FLOAT_VOLTAGE_CFG_REG,
- .min_u = 2500000,
- .max_u = 5000000,
- .step_u = 10000,
+ .fv = {
+ .name = "float voltage",
+ .reg = FLOAT_VOLTAGE_CFG_REG,
+ .min_u = 3487500,
+ .max_u = 4920000,
+ .step_u = 7500,
},
- .usb_icl = {
- .name = "usb input current limit",
- .reg = USBIN_CURRENT_LIMIT_CFG_REG,
- .min_u = 0,
- .max_u = 6000000,
- .step_u = 25000,
+ .usb_icl = {
+ .name = "usb input current limit",
+ .reg = USBIN_CURRENT_LIMIT_CFG_REG,
+ .min_u = 0,
+ .max_u = 4800000,
+ .step_u = 25000,
},
- .dc_icl = {
- .name = "dc input current limit",
- .reg = DCIN_CURRENT_LIMIT_CFG_REG,
- .min_u = 0,
- .max_u = 6000000,
- .step_u = 25000,
+ .icl_stat = {
+ .name = "input current limit status",
+ .reg = ICL_STATUS_REG,
+ .min_u = 0,
+ .max_u = 4800000,
+ .step_u = 25000,
+ },
+ .dc_icl = {
+ .name = "dc input current limit",
+ .reg = DCIN_CURRENT_LIMIT_CFG_REG,
+ .min_u = 0,
+ .max_u = 3000000,
+ .step_u = 25000,
},
};
@@ -79,6 +86,11 @@ module_param_named(
debug_mask, __debug_mask, int, S_IRUSR | S_IWUSR
);
+static int __pl_master_percent = 50;
+module_param_named(
+ pl_master_percent, __pl_master_percent, int, S_IRUSR | S_IWUSR
+);
+
static int smb2_parse_dt(struct smb2 *chip)
{
struct smb_charger *chg = &chip->chg;
@@ -477,6 +489,10 @@ static int smb2_init_hw(struct smb2 *chip)
int rc;
/* votes must be cast before configuring software control */
+ vote(chg->pl_disable_votable,
+ USBIN_ICL_VOTER, true, 0);
+ vote(chg->pl_disable_votable,
+ CHG_STATE_VOTER, true, 0);
vote(chg->usb_suspend_votable,
DEFAULT_VOTER, chip->dt.suspend_input, 0);
vote(chg->dc_suspend_votable,
@@ -725,6 +741,8 @@ static int smb2_probe(struct platform_device *pdev)
chg->dev = &pdev->dev;
chg->param = v1_params;
chg->debug_mask = &__debug_mask;
+ chg->mode = PARALLEL_MASTER;
+ chg->pl.master_percent = &__pl_master_percent;
chg->regmap = dev_get_regmap(chg->dev->parent, NULL);
if (!chg->regmap) {
diff --git a/drivers/power/qcom-charger/smb-lib.c b/drivers/power/qcom-charger/smb-lib.c
index 4412918aa1bb..46b824376847 100644
--- a/drivers/power/qcom-charger/smb-lib.c
+++ b/drivers/power/qcom-charger/smb-lib.c
@@ -82,6 +82,15 @@ unlock:
return rc;
}
+static void smblib_fcc_split_ua(struct smb_charger *chg, int total_fcc,
+ int *master_ua, int *slave_ua)
+{
+ int master_percent = min(max(*chg->pl.master_percent, 0), 100);
+
+ *master_ua = (total_fcc * master_percent) / 100;
+ *slave_ua = (total_fcc - *master_ua) * chg->pl.taper_percent / 100;
+}
+
/********************
* REGISTER GETTERS *
********************/
@@ -330,6 +339,18 @@ static int smblib_detach_usb(struct smb_charger *chg)
return rc;
}
+static struct power_supply *get_parallel_psy(struct smb_charger *chg)
+{
+ if (chg->pl.psy)
+ return chg->pl.psy;
+
+ chg->pl.psy = power_supply_get_by_name("parallel");
+ if (!chg->pl.psy)
+ smblib_dbg(chg, PR_MISC, "parallel charger not found\n");
+
+ return chg->pl.psy;
+}
+
/*********************
* VOTABLE CALLBACKS *
*********************/
@@ -358,20 +379,49 @@ static int smblib_fcc_vote_callback(struct votable *votable, void *data,
{
struct smb_charger *chg = data;
int rc = 0;
+ union power_supply_propval pval = {0, };
+ int master_ua = fcc_ua, slave_ua;
if (fcc_ua < 0) {
smblib_dbg(chg, PR_MISC, "No Voter\n");
return 0;
}
- rc = smblib_set_charge_param(chg, &chg->param.fcc, fcc_ua);
- return rc;
+ if (chg->mode == PARALLEL_MASTER
+ && !get_effective_result_locked(chg->pl_disable_votable)) {
+ smblib_fcc_split_ua(chg, fcc_ua, &master_ua, &slave_ua);
+
+ /*
+ * parallel charger is not disabled, implying that
+ * chg->pl.psy exists
+ */
+ pval.intval = slave_ua;
+ rc = power_supply_set_property(chg->pl.psy,
+ POWER_SUPPLY_PROP_CURRENT_MAX, &pval);
+ if (rc < 0) {
+ dev_err(chg->dev, "Could not set parallel fcc, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ chg->pl.slave_fcc = slave_ua;
+ }
+
+ rc = smblib_set_charge_param(chg, &chg->param.fcc, master_ua);
+ if (rc < 0) {
+ dev_err(chg->dev, "Error in setting fcc, rc=%d\n", rc);
+ return rc;
+ }
+
+ return 0;
}
+#define PARALLEL_FLOAT_VOLTAGE_DELTA_UV 50000
static int smblib_fv_vote_callback(struct votable *votable, void *data,
int fv_uv, const char *client)
{
struct smb_charger *chg = data;
+ union power_supply_propval pval = {0, };
int rc = 0;
if (fv_uv < 0) {
@@ -380,7 +430,24 @@ static int smblib_fv_vote_callback(struct votable *votable, void *data,
}
rc = smblib_set_charge_param(chg, &chg->param.fv, fv_uv);
- return rc;
+ if (rc < 0) {
+ dev_err(chg->dev,
+ "Couldn't set floating voltage rc=%d\n", rc);
+ return rc;
+ }
+
+ if (chg->mode == PARALLEL_MASTER && get_parallel_psy(chg)) {
+ pval.intval = fv_uv + PARALLEL_FLOAT_VOLTAGE_DELTA_UV;
+ rc = power_supply_set_property(chg->pl.psy,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX, &pval);
+ if (rc < 0) {
+ dev_err(chg->dev,
+ "Couldn't set float on parallel rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ return 0;
}
#define USBIN_25MA 25000
@@ -474,6 +541,26 @@ static int smblib_awake_vote_callback(struct votable *votable, void *data,
static int smblib_pl_disable_vote_callback(struct votable *votable, void *data,
int pl_disable, const char *client)
{
+ struct smb_charger *chg = data;
+ union power_supply_propval pval = {0, };
+ int rc;
+
+ if (chg->mode != PARALLEL_MASTER || !get_parallel_psy(chg))
+ return 0;
+
+ chg->pl.taper_percent = 100;
+ rerun_election(chg->fv_votable);
+ rerun_election(chg->fcc_votable);
+
+ pval.intval = pl_disable;
+ rc = power_supply_set_property(chg->pl.psy,
+ POWER_SUPPLY_PROP_INPUT_SUSPEND, &pval);
+ if (rc < 0) {
+ dev_err(chg->dev,
+ "Couldn't change slave suspend state rc=%d\n", rc);
+ return rc;
+ }
+
return 0;
}
@@ -633,7 +720,7 @@ int smblib_get_prop_batt_status(struct smb_charger *chg,
stat);
stat = stat & BATTERY_CHARGER_STATUS_MASK;
- if (stat >= COMPLETED_CHARGE)
+ if (stat == COMPLETED_CHARGE || stat == INHIBIT_CHARGE)
val->intval = POWER_SUPPLY_STATUS_FULL;
else
val->intval = POWER_SUPPLY_STATUS_CHARGING;
@@ -1107,9 +1194,37 @@ irqreturn_t smblib_handle_chg_state_change(int irq, void *data)
{
struct smb_irq_data *irq_data = data;
struct smb_charger *chg = irq_data->parent_data;
+ union power_supply_propval pval = {0, };
+ int rc;
smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name);
+ if (chg->mode != PARALLEL_MASTER || !get_parallel_psy(chg))
+ return IRQ_HANDLED;
+
+ rc = smblib_get_prop_batt_charge_type(chg, &pval);
+ if (rc < 0) {
+ dev_err(chg->dev, "Couldn't get batt charge type rc=%d\n", rc);
+ return IRQ_HANDLED;
+ }
+
+ if (pval.intval == POWER_SUPPLY_CHARGE_TYPE_FAST)
+ vote(chg->pl_disable_votable, CHG_STATE_VOTER, false, 0);
+
+ if (pval.intval == POWER_SUPPLY_CHARGE_TYPE_TAPER
+ && !get_effective_result_locked(chg->pl_disable_votable)) {
+ cancel_delayed_work_sync(&chg->pl_taper_work);
+ schedule_delayed_work(&chg->pl_taper_work, 0);
+ }
+
+ rc = smblib_get_prop_batt_status(chg, &pval);
+ if (rc < 0) {
+ dev_err(chg->dev, "Couldn't get batt status type rc=%d\n", rc);
+ return IRQ_HANDLED;
+ }
+ if (pval.intval == POWER_SUPPLY_STATUS_FULL)
+ vote(chg->pl_disable_votable, TAPER_END_VOTER, false, 0);
+
return IRQ_HANDLED;
}
@@ -1183,11 +1298,50 @@ skip_dpdm_float:
return IRQ_HANDLED;
}
+#define MICRO_5P5V 5500000
+#define USB_WEAK_INPUT_MA 1500000
+static bool is_icl_pl_ready(struct smb_charger *chg)
+{
+ union power_supply_propval pval = {0, };
+ int icl_ma;
+ int rc;
+
+ rc = smblib_get_prop_usb_voltage_now(chg, &pval);
+ if (rc < 0) {
+ dev_err(chg->dev, "Couldn't get prop usb voltage rc=%d\n", rc);
+ return false;
+ }
+
+ if (pval.intval <= MICRO_5P5V) {
+ rc = smblib_get_charge_param(chg,
+ &chg->param.icl_stat, &icl_ma);
+ if (rc < 0) {
+ dev_err(chg->dev, "Couldn't get ICL status rc=%d\n",
+ rc);
+ return false;
+ }
+
+ if (icl_ma < USB_WEAK_INPUT_MA)
+ return false;
+ }
+
+ /*
+ * Always enable parallel charging when USB INPUT is higher than 5V
+ * regardless of the AICL results. Assume chargers above 5V are strong
+ */
+
+ return true;
+}
+
irqreturn_t smblib_handle_icl_change(int irq, void *data)
{
struct smb_irq_data *irq_data = data;
struct smb_charger *chg = irq_data->parent_data;
+ if (chg->mode == PARALLEL_MASTER)
+ vote(chg->pl_disable_votable, USBIN_ICL_VOTER,
+ !is_icl_pl_ready(chg), 0);
+
smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name);
return IRQ_HANDLED;
@@ -1340,6 +1494,17 @@ static void smblib_handle_typec_debounce_done(struct smb_charger *chg,
if (rc < 0)
dev_err(chg->dev, "Couldn't get prop typec mode rc=%d\n", rc);
+ /*
+ * vote to enable parallel charging if a source is attached, and disable
+ * otherwise
+ */
+ vote(chg->pl_disable_votable, TYPEC_SRC_VOTER,
+ !rising || sink_attached, 0);
+
+ /* reset taper_end voter here */
+ if (!rising || sink_attached)
+ vote(chg->pl_disable_votable, TAPER_END_VOTER, false, 0);
+
smblib_dbg(chg, PR_INTERRUPT, "IRQ: debounce-done %s; Type-C %s detected\n",
rising ? "rising" : "falling",
smblib_typec_mode_name[pval.intval]);
@@ -1394,6 +1559,45 @@ static void smblib_hvdcp_detect_work(struct work_struct *work)
}
}
+#define MINIMUM_PARALLEL_FCC_UA 500000
+#define PL_TAPER_WORK_DELAY_MS 100
+#define TAPER_RESIDUAL_PERCENT 75
+static void smblib_pl_taper_work(struct work_struct *work)
+{
+ struct smb_charger *chg = container_of(work, struct smb_charger,
+ pl_taper_work.work);
+ union power_supply_propval pval = {0, };
+ int rc;
+
+ if (chg->pl.slave_fcc < MINIMUM_PARALLEL_FCC_UA) {
+ vote(chg->pl_disable_votable, TAPER_END_VOTER, true, 0);
+ goto done;
+ }
+
+ rc = smblib_get_prop_batt_charge_type(chg, &pval);
+ if (rc < 0) {
+ dev_err(chg->dev, "Couldn't get batt charge type rc=%d\n", rc);
+ goto done;
+ }
+
+ if (pval.intval == POWER_SUPPLY_CHARGE_TYPE_TAPER) {
+ vote(chg->awake_votable, PL_VOTER, true, 0);
+ /* Reduce the taper percent by 25 percent */
+ chg->pl.taper_percent = chg->pl.taper_percent
+ * TAPER_RESIDUAL_PERCENT / 100;
+ rerun_election(chg->fcc_votable);
+ schedule_delayed_work(&chg->pl_taper_work,
+ msecs_to_jiffies(PL_TAPER_WORK_DELAY_MS));
+ return;
+ }
+
+ /*
+ * Master back to Fast Charge, get out of this round of taper reduction
+ */
+done:
+ vote(chg->awake_votable, PL_VOTER, false, 0);
+}
+
int smblib_create_votables(struct smb_charger *chg)
{
int rc = 0;
@@ -1478,6 +1682,7 @@ int smblib_init(struct smb_charger *chg)
mutex_init(&chg->write_lock);
INIT_DELAYED_WORK(&chg->hvdcp_detect_work, smblib_hvdcp_detect_work);
+ INIT_DELAYED_WORK(&chg->pl_taper_work, smblib_pl_taper_work);
switch (chg->mode) {
case PARALLEL_MASTER:
diff --git a/drivers/power/qcom-charger/smb-lib.h b/drivers/power/qcom-charger/smb-lib.h
index 628b76252530..fbadf02d9958 100644
--- a/drivers/power/qcom-charger/smb-lib.h
+++ b/drivers/power/qcom-charger/smb-lib.h
@@ -26,6 +26,11 @@ enum print_reason {
#define DEFAULT_VOTER "DEFAULT_VOTER"
#define USER_VOTER "USER_VOTER"
#define PD_VOTER "PD_VOTER"
+#define PL_VOTER "PL_VOTER"
+#define USBIN_ICL_VOTER "USBIN_ICL_VOTER"
+#define CHG_STATE_VOTER "CHG_STATE_VOTER"
+#define TYPEC_SRC_VOTER "TYPEC_SRC_VOTER"
+#define TAPER_END_VOTER "TAPER_END_VOTER"
enum smb_mode {
PARALLEL_MASTER = 0,
@@ -55,9 +60,17 @@ struct smb_params {
struct smb_chg_param fcc;
struct smb_chg_param fv;
struct smb_chg_param usb_icl;
+ struct smb_chg_param icl_stat;
struct smb_chg_param dc_icl;
};
+struct parallel_params {
+ struct power_supply *psy;
+ int *master_percent;
+ int taper_percent;
+ int slave_fcc;
+};
+
struct smb_charger {
struct device *dev;
struct regmap *regmap;
@@ -74,6 +87,9 @@ struct smb_charger {
struct power_supply *usb_psy;
struct power_supply_desc usb_psy_desc;
+ /* parallel charging */
+ struct parallel_params pl;
+
/* regulators */
struct smb_regulator *vbus_vreg;
struct smb_regulator *vconn_vreg;
@@ -87,12 +103,13 @@ struct smb_charger {
struct votable *usb_icl_votable;
struct votable *dc_icl_votable;
struct votable *pd_allowed_votable;
- struct votable *awake_votable;
- struct votable *pl_disable_votable;
+ struct votable *awake_votable;
+ struct votable *pl_disable_votable;
/* work */
struct delayed_work hvdcp_detect_work;
struct delayed_work ps_change_timeout_work;
+ struct delayed_work pl_taper_work;
/* cached status */
int voltage_min_uv;