summaryrefslogtreecommitdiff
path: root/drivers/power
diff options
context:
space:
mode:
authorHarry Yang <harryy@codeaurora.org>2016-06-15 12:09:42 -0700
committerKyle Yan <kyan@codeaurora.org>2016-06-22 14:41:04 -0700
commit5fbfb727e3e6dbb01002a9a49fab627bb60554eb (patch)
tree9b72f161802db5a519e09001083229594e3d0855 /drivers/power
parent7d1fabcef82e138c38c84144457a5e962381b0b1 (diff)
qcom-charger: introduce parallel charging support
Parallel charging increases charging capacity and efficiency by distributing the current between two charging chips. PMI8998 feeds the parallel charger via its MID input, and handles input current limiting in its front-porch FET. As master charger, PMI8998 is responsible for enabling/disabling the parallel charger, and the FCC distribution. To enable parallel charging in software, the following conditions must be met: - Strong USBIN input - Battery present - In fast or taper charging state - Attached UFP source While the enabling/disabling is always under the control of software the disabling can also be done by hardware in case of fault. Battery current is usually fixed to the battery rating. The FCC distribution is simple, a split of 50/50 by default, which can be changed in runtime. When taper irq kicks in, the algorithm reduces parallel FCC by 25%. This puts the charging back in constant current phase until the next one happens where again the algorithm reduces the FCC by 25%. This continues until the parallel FCC drops to 500mA. At that time parallel charging is disabled and master continues charging the rest of constant voltage phase. CRs-Fixed: 1023703 1030934 Change-Id: Ied7c31d5913df94a288d36ecf06d081d32e07396 Signed-off-by: Harry Yang <harryy@codeaurora.org> Signed-off-by: Abhijeet Dharmapurikar <adharmap@codeaurora.org>
Diffstat (limited to 'drivers/power')
-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;