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);