Loading drivers/of/of_batterydata.c +132 −1 Original line number Diff line number Diff line /* Copyright (c) 2013-2018, The Linux Foundation. All rights reserved. /* Copyright (c) 2013-2019, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and Loading @@ -18,6 +18,7 @@ #include <linux/module.h> #include <linux/types.h> #include <linux/batterydata-lib.h> #include <linux/of_batterydata.h> #include <linux/power_supply.h> static int of_batterydata_read_lut(const struct device_node *np, Loading Loading @@ -475,6 +476,136 @@ struct device_node *of_batterydata_get_best_aged_profile( return best_node; } int of_batterydata_get_aged_profile_count( const struct device_node *batterydata_node, int batt_id_kohm, int *count) { struct device_node *node; int id_range_pct, i = 0, rc = 0, limit = 0, delta = 0; bool in_range = false; u32 batt_id; /* read battery id range percentage for best profile */ rc = of_property_read_u32(batterydata_node, "qcom,batt-id-range-pct", &id_range_pct); if (rc) { if (rc == -EINVAL) { id_range_pct = 0; } else { pr_err("failed to read battery id range\n"); return -ENXIO; } } for_each_available_child_of_node(batterydata_node, node) { if (!of_find_property(node, "qcom,batt-age-level", NULL)) continue; if (!of_find_property(node, "qcom,soh-range", NULL)) continue; rc = of_property_read_u32(node, "qcom,batt-id-kohm", &batt_id); if (rc) continue; delta = abs(batt_id_kohm - batt_id); limit = (batt_id_kohm * id_range_pct) / 100; in_range = (delta <= limit); if (!in_range) { pr_debug("not in range batt_id: %d\n", batt_id); continue; } i++; } if (i <= 1) { pr_err("Less number of profiles to support SOH\n"); return -EINVAL; } *count = i; return 0; } int of_batterydata_read_soh_aged_profiles( const struct device_node *batterydata_node, int batt_id_kohm, struct soh_range *soh_data) { struct device_node *node; u32 val, temp[2], i = 0; int rc, batt_id, id_range_pct, limit = 0, delta = 0; bool in_range = false; if (!batterydata_node || !soh_data) return -ENODEV; /* read battery id range percentage for best profile */ rc = of_property_read_u32(batterydata_node, "qcom,batt-id-range-pct", &id_range_pct); if (rc) { if (rc == -EINVAL) { id_range_pct = 0; } else { pr_err("failed to read battery id range\n"); return -ENXIO; } } for_each_available_child_of_node(batterydata_node, node) { rc = of_property_read_u32(node, "qcom,batt-age-level", &val); if (rc) continue; rc = of_property_read_u32(node, "qcom,batt-id-kohm", &batt_id); if (rc) continue; delta = abs(batt_id_kohm - batt_id); limit = (batt_id_kohm * id_range_pct) / 100; in_range = (delta <= limit); if (!in_range) { pr_debug("not in range batt_id: %d\n", batt_id); continue; } if (!of_find_property(node, "qcom,soh-range", NULL)) continue; rc = of_property_count_elems_of_size(node, "qcom,soh-range", sizeof(u32)); if (rc != 2) { pr_err("Incorrect element size for qcom,soh-range, rc=%d\n", rc); return -EINVAL; } rc = of_property_read_u32_array(node, "qcom,soh-range", temp, 2); if (rc < 0) { pr_err("Error in reading qcom,soh-range, rc=%d\n", rc); return rc; } if (temp[0] > 100 || temp[1] > 100 || (temp[0] > temp[1])) { pr_err("Incorrect SOH range [%d %d]\n", temp[0], temp[1]); return -ERANGE; } pr_debug("batt_age_level: %d soh: [%d %d]\n", val, temp[0], temp[1]); soh_data[i].batt_age_level = val; soh_data[i].soh_min = temp[0]; soh_data[i].soh_max = temp[1]; i++; } return 0; } int of_batterydata_read_data(struct device_node *batterydata_container_node, struct bms_battery_data *batt_data, int batt_id_uv) Loading drivers/power/supply/qcom/fg-alg.c +118 −0 Original line number Diff line number Diff line Loading @@ -794,6 +794,124 @@ int cap_learning_init(struct cap_learning *cl) return 0; } /* SOH based profile loading */ /** * soh_get_batt_age_level - * @sp: SOH profile object * @soh: SOH level * @batt_age_level: Battery age level if exists for the SOH passed * */ static int soh_get_batt_age_level(struct soh_profile *sp, int soh, int *batt_age_level) { struct soh_range *range = sp->soh_data; int i; for (i = 0; i < sp->profile_count; i++) { if (is_between(range[i].soh_min, range[i].soh_max, soh)) { *batt_age_level = range[i].batt_age_level; return 0; } } return -ENOENT; } /** * soh_profile_update - * @sp: SOH profile object * @new_soh: SOH level that is updated and notified to FG/QG driver * * FG/QG have to call this whenever SOH is notified by the userspace. * */ int soh_profile_update(struct soh_profile *sp, int new_soh) { union power_supply_propval pval = {0, }; int rc, batt_age_level = 0; if (!sp->bms_psy) return -ENODEV; if (new_soh <= 0) return 0; if (new_soh != sp->last_soh) pr_debug("SOH changed from %d to %d\n", sp->last_soh, new_soh); if (sp->last_soh <= 0) { sp->last_soh = new_soh; pr_debug("SOH initialized to %d\n", sp->last_soh); } rc = soh_get_batt_age_level(sp, new_soh, &batt_age_level); if (rc < 0) return rc; if (batt_age_level != sp->last_batt_age_level) { pval.intval = batt_age_level; rc = power_supply_set_property(sp->bms_psy, POWER_SUPPLY_PROP_BATT_AGE_LEVEL, &pval); if (rc < 0) { pr_err("Couldn't set batt_age_level rc=%d\n", rc); return rc; } sp->last_batt_age_level = batt_age_level; pr_info("Batt_age_level set to %d for SOH %d\n", batt_age_level, new_soh); } return 0; } /** * soh_profile_init - * @dev: Device node of FG/QG * @sp: SOH profile object * * FG/QG have to call this after parsing battery profile node and multiple * profile load feature is enabled. SOH profile object should have atleast * the power supply of FG/QG and battery profile node. SOH specific range * data is allocated by this function. * */ int soh_profile_init(struct device *dev, struct soh_profile *sp) { int rc, profile_count = 0; if (!dev || !sp || !sp->bp_node || !sp->bms_psy) return -ENODEV; rc = of_batterydata_get_aged_profile_count(sp->bp_node, sp->batt_id_kohms, &profile_count); if (rc < 0) { pr_err("Couldn't get profile count rc=%d\n", rc); return rc; } sp->soh_data = devm_kcalloc(dev, profile_count, sizeof(*sp->soh_data), GFP_KERNEL); if (!sp->soh_data) return -ENOMEM; rc = of_batterydata_read_soh_aged_profiles(sp->bp_node, sp->batt_id_kohms, sp->soh_data); if (rc < 0) { pr_err("Couldn't read SOH data for profile loading, rc=%d\n", rc); devm_kfree(dev, sp->soh_data); return rc; } sp->profile_count = profile_count; sp->last_soh = -EINVAL; sp->initialized = true; return 0; } /* Time to full/empty algorithm helper functions */ static void ttf_circ_buf_add(struct ttf_circ_buf *buf, int val) Loading drivers/power/supply/qcom/fg-alg.h +14 −0 Original line number Diff line number Diff line Loading @@ -13,6 +13,7 @@ #ifndef __FG_ALG_H__ #define __FG_ALG_H__ #include <linux/of_batterydata.h> #include "step-chg-jeita.h" #define BUCKET_COUNT 8 Loading Loading @@ -136,6 +137,17 @@ struct ttf { int (*awake_voter)(void *data, bool vote); }; struct soh_profile { struct device_node *bp_node; struct power_supply *bms_psy; struct soh_range *soh_data; int batt_id_kohms; int profile_count; int last_soh; int last_batt_age_level; bool initialized; }; int restore_cycle_count(struct cycle_counter *counter); void clear_cycle_count(struct cycle_counter *counter); void cycle_count_update(struct cycle_counter *counter, int batt_soc, Loading @@ -154,5 +166,7 @@ void ttf_update(struct ttf *ttf, bool input_present); int ttf_get_time_to_empty(struct ttf *ttf, int *val); int ttf_get_time_to_full(struct ttf *ttf, int *val); int ttf_tte_init(struct ttf *ttf); int soh_profile_init(struct device *dev, struct soh_profile *sp); int soh_profile_update(struct soh_profile *sp, int soh); #endif drivers/power/supply/qcom/qpnp-fg-gen4.c +30 −5 Original line number Diff line number Diff line Loading @@ -256,6 +256,7 @@ struct fg_gen4_chip { struct cycle_counter *counter; struct cap_learning *cl; struct ttf *ttf; struct soh_profile *sp; struct device_node *pbs_dev; struct nvmem_device *fg_nvmem; struct votable *delta_esr_irq_en_votable; Loading Loading @@ -1628,13 +1629,35 @@ static int fg_gen4_get_batt_profile(struct fg_dev *fg) return -ENODATA; } if (chip->dt.multi_profile_load && chip->batt_age_level != avail_age_level) { if (chip->dt.multi_profile_load) { if (chip->batt_age_level != avail_age_level) { fg_dbg(fg, FG_STATUS, "Batt_age_level %d doesn't exist, using %d\n", chip->batt_age_level, avail_age_level); chip->batt_age_level = avail_age_level; } if (!chip->sp) chip->sp = devm_kzalloc(fg->dev, sizeof(*chip->sp), GFP_KERNEL); if (!chip->sp) return -ENOMEM; if (!chip->sp->initialized) { chip->sp->batt_id_kohms = fg->batt_id_ohms / 1000; chip->sp->last_batt_age_level = chip->batt_age_level; chip->sp->bp_node = batt_node; chip->sp->bms_psy = fg->fg_psy; rc = soh_profile_init(fg->dev, chip->sp); if (rc < 0) { devm_kfree(fg->dev, chip->sp); chip->sp = NULL; } else { fg_dbg(fg, FG_STATUS, "SOH profile count: %d\n", chip->sp->profile_count); } } } rc = of_property_read_string(profile_node, "qcom,battery-type", &fg->bp.batt_type_str); if (rc < 0) { Loading Loading @@ -4351,6 +4374,8 @@ static int fg_psy_set_property(struct power_supply *psy, break; case POWER_SUPPLY_PROP_SOH: chip->soh = pval->intval; if (chip->sp) soh_profile_update(chip->sp, chip->soh); break; case POWER_SUPPLY_PROP_CLEAR_SOH: if (chip->first_profile_load && !pval->intval) { Loading include/linux/batterydata-lib.h +15 −1 Original line number Diff line number Diff line /* Copyright (c) 2012-2015, 2017 The Linux Foundation. All rights reserved. /* Copyright (c) 2012-2015, 2017, 2019 The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and Loading Loading @@ -144,6 +144,20 @@ struct bms_battery_data { const char *battery_type; }; /** * struct soh_range - * @batt_age_level: Battery age level (e.g. 0, 1 etc.,) * @soh_min: Minimum SOH (state of health) level that this battery * profile can support. * @soh_max: Maximum SOH (state of health) level that this battery * profile can support. */ struct soh_range { int batt_age_level; int soh_min; int soh_max; }; #define is_between(left, right, value) \ (((left) >= (right) && (left) >= (value) \ && (value) >= (right)) \ Loading Loading
drivers/of/of_batterydata.c +132 −1 Original line number Diff line number Diff line /* Copyright (c) 2013-2018, The Linux Foundation. All rights reserved. /* Copyright (c) 2013-2019, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and Loading @@ -18,6 +18,7 @@ #include <linux/module.h> #include <linux/types.h> #include <linux/batterydata-lib.h> #include <linux/of_batterydata.h> #include <linux/power_supply.h> static int of_batterydata_read_lut(const struct device_node *np, Loading Loading @@ -475,6 +476,136 @@ struct device_node *of_batterydata_get_best_aged_profile( return best_node; } int of_batterydata_get_aged_profile_count( const struct device_node *batterydata_node, int batt_id_kohm, int *count) { struct device_node *node; int id_range_pct, i = 0, rc = 0, limit = 0, delta = 0; bool in_range = false; u32 batt_id; /* read battery id range percentage for best profile */ rc = of_property_read_u32(batterydata_node, "qcom,batt-id-range-pct", &id_range_pct); if (rc) { if (rc == -EINVAL) { id_range_pct = 0; } else { pr_err("failed to read battery id range\n"); return -ENXIO; } } for_each_available_child_of_node(batterydata_node, node) { if (!of_find_property(node, "qcom,batt-age-level", NULL)) continue; if (!of_find_property(node, "qcom,soh-range", NULL)) continue; rc = of_property_read_u32(node, "qcom,batt-id-kohm", &batt_id); if (rc) continue; delta = abs(batt_id_kohm - batt_id); limit = (batt_id_kohm * id_range_pct) / 100; in_range = (delta <= limit); if (!in_range) { pr_debug("not in range batt_id: %d\n", batt_id); continue; } i++; } if (i <= 1) { pr_err("Less number of profiles to support SOH\n"); return -EINVAL; } *count = i; return 0; } int of_batterydata_read_soh_aged_profiles( const struct device_node *batterydata_node, int batt_id_kohm, struct soh_range *soh_data) { struct device_node *node; u32 val, temp[2], i = 0; int rc, batt_id, id_range_pct, limit = 0, delta = 0; bool in_range = false; if (!batterydata_node || !soh_data) return -ENODEV; /* read battery id range percentage for best profile */ rc = of_property_read_u32(batterydata_node, "qcom,batt-id-range-pct", &id_range_pct); if (rc) { if (rc == -EINVAL) { id_range_pct = 0; } else { pr_err("failed to read battery id range\n"); return -ENXIO; } } for_each_available_child_of_node(batterydata_node, node) { rc = of_property_read_u32(node, "qcom,batt-age-level", &val); if (rc) continue; rc = of_property_read_u32(node, "qcom,batt-id-kohm", &batt_id); if (rc) continue; delta = abs(batt_id_kohm - batt_id); limit = (batt_id_kohm * id_range_pct) / 100; in_range = (delta <= limit); if (!in_range) { pr_debug("not in range batt_id: %d\n", batt_id); continue; } if (!of_find_property(node, "qcom,soh-range", NULL)) continue; rc = of_property_count_elems_of_size(node, "qcom,soh-range", sizeof(u32)); if (rc != 2) { pr_err("Incorrect element size for qcom,soh-range, rc=%d\n", rc); return -EINVAL; } rc = of_property_read_u32_array(node, "qcom,soh-range", temp, 2); if (rc < 0) { pr_err("Error in reading qcom,soh-range, rc=%d\n", rc); return rc; } if (temp[0] > 100 || temp[1] > 100 || (temp[0] > temp[1])) { pr_err("Incorrect SOH range [%d %d]\n", temp[0], temp[1]); return -ERANGE; } pr_debug("batt_age_level: %d soh: [%d %d]\n", val, temp[0], temp[1]); soh_data[i].batt_age_level = val; soh_data[i].soh_min = temp[0]; soh_data[i].soh_max = temp[1]; i++; } return 0; } int of_batterydata_read_data(struct device_node *batterydata_container_node, struct bms_battery_data *batt_data, int batt_id_uv) Loading
drivers/power/supply/qcom/fg-alg.c +118 −0 Original line number Diff line number Diff line Loading @@ -794,6 +794,124 @@ int cap_learning_init(struct cap_learning *cl) return 0; } /* SOH based profile loading */ /** * soh_get_batt_age_level - * @sp: SOH profile object * @soh: SOH level * @batt_age_level: Battery age level if exists for the SOH passed * */ static int soh_get_batt_age_level(struct soh_profile *sp, int soh, int *batt_age_level) { struct soh_range *range = sp->soh_data; int i; for (i = 0; i < sp->profile_count; i++) { if (is_between(range[i].soh_min, range[i].soh_max, soh)) { *batt_age_level = range[i].batt_age_level; return 0; } } return -ENOENT; } /** * soh_profile_update - * @sp: SOH profile object * @new_soh: SOH level that is updated and notified to FG/QG driver * * FG/QG have to call this whenever SOH is notified by the userspace. * */ int soh_profile_update(struct soh_profile *sp, int new_soh) { union power_supply_propval pval = {0, }; int rc, batt_age_level = 0; if (!sp->bms_psy) return -ENODEV; if (new_soh <= 0) return 0; if (new_soh != sp->last_soh) pr_debug("SOH changed from %d to %d\n", sp->last_soh, new_soh); if (sp->last_soh <= 0) { sp->last_soh = new_soh; pr_debug("SOH initialized to %d\n", sp->last_soh); } rc = soh_get_batt_age_level(sp, new_soh, &batt_age_level); if (rc < 0) return rc; if (batt_age_level != sp->last_batt_age_level) { pval.intval = batt_age_level; rc = power_supply_set_property(sp->bms_psy, POWER_SUPPLY_PROP_BATT_AGE_LEVEL, &pval); if (rc < 0) { pr_err("Couldn't set batt_age_level rc=%d\n", rc); return rc; } sp->last_batt_age_level = batt_age_level; pr_info("Batt_age_level set to %d for SOH %d\n", batt_age_level, new_soh); } return 0; } /** * soh_profile_init - * @dev: Device node of FG/QG * @sp: SOH profile object * * FG/QG have to call this after parsing battery profile node and multiple * profile load feature is enabled. SOH profile object should have atleast * the power supply of FG/QG and battery profile node. SOH specific range * data is allocated by this function. * */ int soh_profile_init(struct device *dev, struct soh_profile *sp) { int rc, profile_count = 0; if (!dev || !sp || !sp->bp_node || !sp->bms_psy) return -ENODEV; rc = of_batterydata_get_aged_profile_count(sp->bp_node, sp->batt_id_kohms, &profile_count); if (rc < 0) { pr_err("Couldn't get profile count rc=%d\n", rc); return rc; } sp->soh_data = devm_kcalloc(dev, profile_count, sizeof(*sp->soh_data), GFP_KERNEL); if (!sp->soh_data) return -ENOMEM; rc = of_batterydata_read_soh_aged_profiles(sp->bp_node, sp->batt_id_kohms, sp->soh_data); if (rc < 0) { pr_err("Couldn't read SOH data for profile loading, rc=%d\n", rc); devm_kfree(dev, sp->soh_data); return rc; } sp->profile_count = profile_count; sp->last_soh = -EINVAL; sp->initialized = true; return 0; } /* Time to full/empty algorithm helper functions */ static void ttf_circ_buf_add(struct ttf_circ_buf *buf, int val) Loading
drivers/power/supply/qcom/fg-alg.h +14 −0 Original line number Diff line number Diff line Loading @@ -13,6 +13,7 @@ #ifndef __FG_ALG_H__ #define __FG_ALG_H__ #include <linux/of_batterydata.h> #include "step-chg-jeita.h" #define BUCKET_COUNT 8 Loading Loading @@ -136,6 +137,17 @@ struct ttf { int (*awake_voter)(void *data, bool vote); }; struct soh_profile { struct device_node *bp_node; struct power_supply *bms_psy; struct soh_range *soh_data; int batt_id_kohms; int profile_count; int last_soh; int last_batt_age_level; bool initialized; }; int restore_cycle_count(struct cycle_counter *counter); void clear_cycle_count(struct cycle_counter *counter); void cycle_count_update(struct cycle_counter *counter, int batt_soc, Loading @@ -154,5 +166,7 @@ void ttf_update(struct ttf *ttf, bool input_present); int ttf_get_time_to_empty(struct ttf *ttf, int *val); int ttf_get_time_to_full(struct ttf *ttf, int *val); int ttf_tte_init(struct ttf *ttf); int soh_profile_init(struct device *dev, struct soh_profile *sp); int soh_profile_update(struct soh_profile *sp, int soh); #endif
drivers/power/supply/qcom/qpnp-fg-gen4.c +30 −5 Original line number Diff line number Diff line Loading @@ -256,6 +256,7 @@ struct fg_gen4_chip { struct cycle_counter *counter; struct cap_learning *cl; struct ttf *ttf; struct soh_profile *sp; struct device_node *pbs_dev; struct nvmem_device *fg_nvmem; struct votable *delta_esr_irq_en_votable; Loading Loading @@ -1628,13 +1629,35 @@ static int fg_gen4_get_batt_profile(struct fg_dev *fg) return -ENODATA; } if (chip->dt.multi_profile_load && chip->batt_age_level != avail_age_level) { if (chip->dt.multi_profile_load) { if (chip->batt_age_level != avail_age_level) { fg_dbg(fg, FG_STATUS, "Batt_age_level %d doesn't exist, using %d\n", chip->batt_age_level, avail_age_level); chip->batt_age_level = avail_age_level; } if (!chip->sp) chip->sp = devm_kzalloc(fg->dev, sizeof(*chip->sp), GFP_KERNEL); if (!chip->sp) return -ENOMEM; if (!chip->sp->initialized) { chip->sp->batt_id_kohms = fg->batt_id_ohms / 1000; chip->sp->last_batt_age_level = chip->batt_age_level; chip->sp->bp_node = batt_node; chip->sp->bms_psy = fg->fg_psy; rc = soh_profile_init(fg->dev, chip->sp); if (rc < 0) { devm_kfree(fg->dev, chip->sp); chip->sp = NULL; } else { fg_dbg(fg, FG_STATUS, "SOH profile count: %d\n", chip->sp->profile_count); } } } rc = of_property_read_string(profile_node, "qcom,battery-type", &fg->bp.batt_type_str); if (rc < 0) { Loading Loading @@ -4351,6 +4374,8 @@ static int fg_psy_set_property(struct power_supply *psy, break; case POWER_SUPPLY_PROP_SOH: chip->soh = pval->intval; if (chip->sp) soh_profile_update(chip->sp, chip->soh); break; case POWER_SUPPLY_PROP_CLEAR_SOH: if (chip->first_profile_load && !pval->intval) { Loading
include/linux/batterydata-lib.h +15 −1 Original line number Diff line number Diff line /* Copyright (c) 2012-2015, 2017 The Linux Foundation. All rights reserved. /* Copyright (c) 2012-2015, 2017, 2019 The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and Loading Loading @@ -144,6 +144,20 @@ struct bms_battery_data { const char *battery_type; }; /** * struct soh_range - * @batt_age_level: Battery age level (e.g. 0, 1 etc.,) * @soh_min: Minimum SOH (state of health) level that this battery * profile can support. * @soh_max: Maximum SOH (state of health) level that this battery * profile can support. */ struct soh_range { int batt_age_level; int soh_min; int soh_max; }; #define is_between(left, right, value) \ (((left) >= (right) && (left) >= (value) \ && (value) >= (right)) \ Loading