google_charger: add parameters to control stop/start charge level charge_stop_level and charge_start_level controls charging based on SoC level. For test: DEV_PATH=/sys/devices/platform/soc/soc:google,charger adb shell "echo 35 > $DEV_PATH/charge_stop_level" adb shell "echo 30 > $DEV_PATH/charge_start_level" charging will stop when capacity is above 35%, and charging resume when capacity becomes lower than 30%. Bug: 77786281 Change-Id: I2e5a115afaccd431925aa1f9ebef3cedb0aaee88 Signed-off-by: Jack Wu <wjack@google.com> Signed-off-by: Thierry Strudel <tstrudel@google.com>
diff --git a/drivers/power/supply/google_charger.c b/drivers/power/supply/google_charger.c index 2bac2b6..51e6876 100644 --- a/drivers/power/supply/google_charger.c +++ b/drivers/power/supply/google_charger.c
@@ -26,6 +26,9 @@ #define CHG_VOLT_NB_LIMITS_MAX 5 #define CHG_DELAY_INIT_MS 250 +#define DEFAULT_CHARGE_STOP_LEVEL 100 +#define DEFAULT_CHARGE_START_LEVEL 0 + struct chg_profile { u32 update_interval; u32 battery_capacity; @@ -63,6 +66,11 @@ struct chg_drv { int vbatt_idx; int checked_cv_cnt; int fv_uv; + int disable_charging; + int disable_pwrsrc; + bool lowerdb_reached; + int charge_stop_level; + int charge_start_level; }; /* Used as left operand also */ @@ -145,12 +153,15 @@ static inline int psy_set_prop(struct power_supply *psy, return 0; } -static inline void init_chg_drv(struct chg_drv *chg_drv) +static inline void reset_chg_drv_state(struct chg_drv *chg_drv) { chg_drv->temp_idx = -1; chg_drv->vbatt_idx = -1; chg_drv->checked_cv_cnt = 0; chg_drv->fv_uv = -1; + chg_drv->disable_charging = 0; + chg_drv->disable_pwrsrc = 0; + chg_drv->lowerdb_reached = true; PSY_SET_PROP(chg_drv->chg_psy, POWER_SUPPLY_PROP_TAPER_CONTROL_ENABLED, 0); } @@ -194,6 +205,44 @@ static void pr_info_states(struct power_supply *chg_psy, PSY_GET_PROP(wlc_psy, POWER_SUPPLY_PROP_TEMP)); } +/* returns 1 if charging should be disabled given the current battery capacity + * given in percent, return 0 if charging should happen + */ +static int is_charging_disabled(struct chg_drv *chg_drv, int capacity) +{ + int disable_charging = 0; + int upperbd = chg_drv->charge_stop_level; + int lowerbd = chg_drv->charge_start_level; + + if ((upperbd == DEFAULT_CHARGE_STOP_LEVEL) && + (lowerbd == DEFAULT_CHARGE_START_LEVEL)) + return 0; + + if ((upperbd > lowerbd) && + (upperbd <= DEFAULT_CHARGE_STOP_LEVEL) && + (lowerbd >= DEFAULT_CHARGE_START_LEVEL)) { + if (chg_drv->lowerdb_reached && upperbd <= capacity) { + pr_info("%s: lowerbd=%d, upperbd=%d, capacity=%d, lowerdb_reached=1->0, charging off\n", + __func__, lowerbd, upperbd, capacity); + disable_charging = 1; + chg_drv->lowerdb_reached = false; + } else if (!chg_drv->lowerdb_reached && lowerbd < capacity) { + pr_info("%s: lowerbd=%d, upperbd=%d, capacity=%d, charging off\n", + __func__, lowerbd, upperbd, capacity); + disable_charging = 1; + } else if (!chg_drv->lowerdb_reached && capacity <= lowerbd) { + pr_info("%s: lowerbd=%d, upperbd=%d, capacity=%d, lowerdb_reached=0->1, charging on\n", + __func__, lowerbd, upperbd, capacity); + chg_drv->lowerdb_reached = true; + } else { + pr_info("%s: lowerbd=%d, upperbd=%d, capacity=%d, charging on\n", + __func__, lowerbd, upperbd, capacity); + } + } + + return disable_charging; +} + #define CHG_WORK_ERROR_RETRY_MS 1000 static void chg_work(struct work_struct *work) { @@ -211,6 +260,8 @@ static void chg_work(struct work_struct *work) int batt_status, cv_delta; bool rerun_work = false; int vcell_delta = 0; + int disable_charging; + int disable_pwrsrc; __pm_stay_awake(&chg_drv->chg_ws); pr_debug("battery charging work item\n"); @@ -240,9 +291,9 @@ static void chg_work(struct work_struct *work) if (!chg_drv->stop_charging) { pr_info("batt. temp. off limits, disabling charging\n"); PSY_SET_PROP(chg_psy, - POWER_SUPPLY_PROP_INPUT_SUSPEND, 1); + POWER_SUPPLY_PROP_CHARGE_DISABLE, 1); chg_drv->stop_charging = true; - init_chg_drv(chg_drv); + reset_chg_drv_state(chg_drv); } /* status will be discharging when disabled but we want to keep * monitoring temperature to re-enable charging @@ -251,10 +302,36 @@ static void chg_work(struct work_struct *work) goto handle_rerun; } else if (chg_drv->stop_charging) { pr_info("batt. temp. ok, enabling charging\n"); - PSY_SET_PROP(chg_psy, POWER_SUPPLY_PROP_INPUT_SUSPEND, 0); + PSY_SET_PROP(chg_psy, POWER_SUPPLY_PROP_CHARGE_DISABLE, 0); chg_drv->stop_charging = false; } + disable_charging = is_charging_disabled(chg_drv, soc); + if (disable_charging && soc > chg_drv->charge_stop_level) + disable_pwrsrc = 1; + else + disable_pwrsrc = 0; + + if (disable_charging != chg_drv->disable_charging) { + pr_info("set disable_charging(%d)", disable_charging); + PSY_SET_PROP(chg_psy, POWER_SUPPLY_PROP_CHARGE_DISABLE, + disable_charging); + } + chg_drv->disable_charging = disable_charging; + + if (disable_pwrsrc != chg_drv->disable_pwrsrc) { + pr_info("set disable_pwrsrc(%d)", disable_pwrsrc); + PSY_SET_PROP(chg_psy, POWER_SUPPLY_PROP_INPUT_SUSPEND, + disable_pwrsrc); + } + chg_drv->disable_pwrsrc = disable_pwrsrc; + + /* no need to reschedule, battery psy event will reschedule work item */ + if (chg_drv->disable_charging || chg_drv->disable_pwrsrc) { + rerun_work = false; + goto exit_chg_work; + } + /* 1. charge profile idx based on the battery temperature */ while (temp_idx < profile->temp_nb_limits - 1 && temp >= profile->temp_limits[temp_idx + 1]) @@ -364,7 +441,7 @@ static void chg_work(struct work_struct *work) } else { pr_info("stop battery charging work: batt_status=%d\n", batt_status); - init_chg_drv(chg_drv); + reset_chg_drv_state(chg_drv); } goto exit_chg_work; @@ -528,6 +605,71 @@ static int chg_init_chg_profile(struct chg_drv *chg_drv) return ret; } +static ssize_t show_charge_stop_level(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct chg_drv *chg_drv = dev_get_drvdata(dev); + + return scnprintf(buf, PAGE_SIZE, "%d\n", chg_drv->charge_stop_level); +} + +static ssize_t set_charge_stop_level(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct chg_drv *chg_drv = dev_get_drvdata(dev); + int ret = 0, val; + + ret = kstrtoint(buf, 0, &val); + if (ret < 0) + return ret; + + if ((val == chg_drv->charge_stop_level) || + (val <= chg_drv->charge_start_level) || + (val < 0)) + return count; + + chg_drv->charge_stop_level = val; + power_supply_changed(chg_drv->bat_psy); + return count; +} + +static DEVICE_ATTR(charge_stop_level, 0660, + show_charge_stop_level, set_charge_stop_level); + +static ssize_t +show_charge_start_level(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct chg_drv *chg_drv = dev_get_drvdata(dev); + + return scnprintf(buf, PAGE_SIZE, "%d\n", chg_drv->charge_start_level); +} + +static ssize_t set_charge_start_level(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct chg_drv *chg_drv = dev_get_drvdata(dev); + int ret = 0, val; + + ret = kstrtoint(buf, 0, &val); + if (ret < 0) + return ret; + + if ((val == chg_drv->charge_start_level) || + (val >= chg_drv->charge_stop_level) || + (val < 0)) + return count; + + chg_drv->charge_start_level = val; + power_supply_changed(chg_drv->bat_psy); + return count; +} + +static DEVICE_ATTR(charge_start_level, 0660, + show_charge_start_level, set_charge_start_level); + static void google_charger_init_work(struct work_struct *work) { struct chg_drv *chg_drv = container_of(work, struct chg_drv, @@ -575,7 +717,10 @@ static void google_charger_init_work(struct work_struct *work) chg_drv->usb_psy = usb_psy; chg_drv->bat_psy = bat_psy; - init_chg_drv(chg_drv); + chg_drv->charge_stop_level = DEFAULT_CHARGE_STOP_LEVEL; + chg_drv->charge_start_level = DEFAULT_CHARGE_START_LEVEL; + + reset_chg_drv_state(chg_drv); chg_drv->psy_nb.notifier_call = psy_changed; ret = power_supply_reg_notifier(&chg_drv->psy_nb); @@ -645,6 +790,20 @@ static int google_charger_probe(struct platform_device *pdev) return ret; } + ret = device_create_file(&pdev->dev, &dev_attr_charge_stop_level); + if (ret != 0) { + pr_err("Failed to create charge_stop_level files, ret=%d\n", + ret); + return ret; + } + + ret = device_create_file(&pdev->dev, &dev_attr_charge_start_level); + if (ret != 0) { + pr_err("Failed to create charge_start_level files, ret=%d\n", + ret); + return ret; + } + INIT_DELAYED_WORK(&chg_drv->init_work, google_charger_init_work); INIT_DELAYED_WORK(&chg_drv->chg_work, chg_work); platform_set_drvdata(pdev, chg_drv);