123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337 |
- // SPDX-License-Identifier: GPL-2.0-only
- /*
- * Copyright (c) 2013-2020, The Linux Foundation. All rights reserved.
- * Copyright (c) 2022-2023, Qualcomm Innovation Center, Inc. All rights reserved.
- */
- #define pr_fmt(fmt) "%s: " fmt, __func__
- #include <linux/err.h>
- #include <linux/of.h>
- #include <linux/slab.h>
- #include <linux/module.h>
- #include <linux/types.h>
- #include <linux/power_supply.h>
- #include "battery-profile-loader.h"
- static int of_batterydata_read_batt_id_kohm(const struct device_node *np,
- const char *propname, struct batt_ids *batt_ids)
- {
- struct property *prop;
- const __be32 *data;
- int num, i, *id_kohm = batt_ids->kohm;
- prop = of_find_property(np, "qcom,batt-id-kohm", NULL);
- if (!prop) {
- pr_err("%s: No battery id resistor found\n", np->name);
- return -EINVAL;
- } else if (!prop->value) {
- pr_err("%s: No battery id resistor value found, np->name\n",
- np->name);
- return -ENODATA;
- } else if (prop->length > MAX_BATT_ID_NUM * sizeof(__be32)) {
- pr_err("%s: Too many battery id resistors\n", np->name);
- return -EINVAL;
- }
- num = prop->length/sizeof(__be32);
- batt_ids->num = num;
- data = prop->value;
- for (i = 0; i < num; i++)
- *id_kohm++ = be32_to_cpup(data++);
- return 0;
- }
- struct device_node *of_batterydata_get_best_profile(
- const struct device_node *batterydata_container_node,
- int batt_id_kohm, const char *batt_type)
- {
- struct batt_ids batt_ids;
- struct device_node *node, *best_node = NULL;
- const char *battery_type = NULL;
- int delta = 0, best_delta = 0, best_id_kohm = 0, id_range_pct,
- i = 0, rc = 0, limit = 0;
- bool in_range = false;
- /* read battery id range percentage for best profile */
- rc = of_property_read_u32(batterydata_container_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 ERR_PTR(-ENXIO);
- }
- }
- /*
- * Find the battery data with a battery id resistor closest to this one
- */
- for_each_child_of_node(batterydata_container_node, node) {
- if (batt_type != NULL) {
- rc = of_property_read_string(node, "qcom,battery-type",
- &battery_type);
- if (!rc && strcmp(battery_type, batt_type) == 0) {
- best_node = node;
- best_id_kohm = batt_id_kohm;
- break;
- }
- } else {
- rc = of_batterydata_read_batt_id_kohm(node,
- "qcom,batt-id-kohm",
- &batt_ids);
- if (rc)
- continue;
- for (i = 0; i < batt_ids.num; i++) {
- delta = abs(batt_ids.kohm[i] - batt_id_kohm);
- limit = (batt_ids.kohm[i] * id_range_pct) / 100;
- in_range = (delta <= limit);
- /*
- * Check if the delta is the lowest one
- * and also if the limits are in range
- * before selecting the best node.
- */
- if ((delta < best_delta || !best_node)
- && in_range) {
- best_node = node;
- best_delta = delta;
- best_id_kohm = batt_ids.kohm[i];
- }
- }
- }
- }
- if (best_node == NULL) {
- pr_err("No battery data found\n");
- return best_node;
- }
- /* check that profile id is in range of the measured batt_id */
- if (abs(best_id_kohm - batt_id_kohm) >
- ((best_id_kohm * id_range_pct) / 100)) {
- pr_err("out of range: profile id %d batt id %d pct %d\n",
- best_id_kohm, batt_id_kohm, id_range_pct);
- return NULL;
- }
- rc = of_property_read_string(best_node, "qcom,battery-type",
- &battery_type);
- if (!rc)
- pr_info("%s found\n", battery_type);
- else
- pr_info("%s found\n", best_node->name);
- return best_node;
- }
- struct device_node *of_batterydata_get_best_aged_profile(
- const struct device_node *batterydata_container_node,
- int batt_id_kohm, int batt_age_level, int *avail_age_level)
- {
- struct batt_ids batt_ids;
- struct device_node *node, *best_node = NULL;
- const char *battery_type = NULL;
- int delta = 0, best_id_kohm = 0, id_range_pct, i = 0, rc = 0, limit = 0;
- u32 val;
- bool in_range = false;
- /* read battery id range percentage for best profile */
- rc = of_property_read_u32(batterydata_container_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 ERR_PTR(-ENXIO);
- }
- }
- /*
- * Find the battery data with a battery id resistor closest to this one
- */
- for_each_available_child_of_node(batterydata_container_node, node) {
- val = 0;
- of_property_read_u32(node, "qcom,batt-age-level", &val);
- rc = of_batterydata_read_batt_id_kohm(node,
- "qcom,batt-id-kohm", &batt_ids);
- if (rc)
- continue;
- for (i = 0; i < batt_ids.num; i++) {
- delta = abs(batt_ids.kohm[i] - batt_id_kohm);
- limit = (batt_ids.kohm[i] * id_range_pct) / 100;
- in_range = (delta <= limit);
- /*
- * Check if the battery aging level matches and the
- * limits are in range before selecting the best node.
- */
- if ((batt_age_level == val || !best_node) && in_range) {
- best_node = node;
- best_id_kohm = batt_ids.kohm[i];
- *avail_age_level = val;
- break;
- }
- }
- }
- if (best_node == NULL) {
- pr_err("No battery data found\n");
- return best_node;
- }
- /* check that profile id is in range of the measured batt_id */
- if (abs(best_id_kohm - batt_id_kohm) >
- ((best_id_kohm * id_range_pct) / 100)) {
- pr_err("out of range: profile id %d batt id %d pct %d\n",
- best_id_kohm, batt_id_kohm, id_range_pct);
- return NULL;
- }
- rc = of_property_read_string(best_node, "qcom,battery-type",
- &battery_type);
- if (!rc)
- pr_info("%s age level %d found\n", battery_type,
- *avail_age_level);
- else
- pr_info("%s age level %d found\n", best_node->name,
- *avail_age_level);
- 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;
- }
- MODULE_LICENSE("GPL");
|