summaryrefslogtreecommitdiff
path: root/drivers/power/supply/power_supply_core.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/power/supply/power_supply_core.c')
-rw-r--r--drivers/power/supply/power_supply_core.c141
1 files changed, 140 insertions, 1 deletions
diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c
index e85361878450..569790ea6917 100644
--- a/drivers/power/supply/power_supply_core.c
+++ b/drivers/power/supply/power_supply_core.c
@@ -570,7 +570,7 @@ int power_supply_get_battery_info(struct power_supply *psy,
{
struct device_node *battery_np;
const char *value;
- int err;
+ int err, len, index;
info->energy_full_design_uwh = -EINVAL;
info->charge_full_design_uah = -EINVAL;
@@ -579,6 +579,13 @@ int power_supply_get_battery_info(struct power_supply *psy,
info->charge_term_current_ua = -EINVAL;
info->constant_charge_current_max_ua = -EINVAL;
info->constant_charge_voltage_max_uv = -EINVAL;
+ info->factory_internal_resistance_uohm = -EINVAL;
+
+ for (index = 0; index < POWER_SUPPLY_OCV_TEMP_MAX; index++) {
+ info->ocv_table[index] = NULL;
+ info->ocv_temp[index] = -EINVAL;
+ info->ocv_table_size[index] = -EINVAL;
+ }
if (!psy->of_node) {
dev_warn(&psy->dev, "%s currently only supports devicetree\n",
@@ -616,11 +623,142 @@ int power_supply_get_battery_info(struct power_supply *psy,
&info->constant_charge_current_max_ua);
of_property_read_u32(battery_np, "constant_charge_voltage_max_microvolt",
&info->constant_charge_voltage_max_uv);
+ of_property_read_u32(battery_np, "factory-internal-resistance-micro-ohms",
+ &info->factory_internal_resistance_uohm);
+
+ len = of_property_count_u32_elems(battery_np, "ocv-capacity-celsius");
+ if (len < 0 && len != -EINVAL) {
+ return len;
+ } else if (len > POWER_SUPPLY_OCV_TEMP_MAX) {
+ dev_err(&psy->dev, "Too many temperature values\n");
+ return -EINVAL;
+ } else if (len > 0) {
+ of_property_read_u32_array(battery_np, "ocv-capacity-celsius",
+ info->ocv_temp, len);
+ }
+
+ for (index = 0; index < len; index++) {
+ struct power_supply_battery_ocv_table *table;
+ char *propname;
+ const __be32 *list;
+ int i, tab_len, size;
+
+ propname = kasprintf(GFP_KERNEL, "ocv-capacity-table-%d", index);
+ list = of_get_property(battery_np, propname, &size);
+ if (!list || !size) {
+ dev_err(&psy->dev, "failed to get %s\n", propname);
+ kfree(propname);
+ power_supply_put_battery_info(psy, info);
+ return -EINVAL;
+ }
+
+ kfree(propname);
+ tab_len = size / (2 * sizeof(__be32));
+ info->ocv_table_size[index] = tab_len;
+
+ table = info->ocv_table[index] =
+ devm_kcalloc(&psy->dev, tab_len, sizeof(*table), GFP_KERNEL);
+ if (!info->ocv_table[index]) {
+ power_supply_put_battery_info(psy, info);
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < tab_len; i++) {
+ table[i].ocv = be32_to_cpu(*list++);
+ table[i].capacity = be32_to_cpu(*list++);
+ }
+ }
return 0;
}
EXPORT_SYMBOL_GPL(power_supply_get_battery_info);
+void power_supply_put_battery_info(struct power_supply *psy,
+ struct power_supply_battery_info *info)
+{
+ int i;
+
+ for (i = 0; i < POWER_SUPPLY_OCV_TEMP_MAX; i++) {
+ if (info->ocv_table[i])
+ devm_kfree(&psy->dev, info->ocv_table[i]);
+ }
+}
+EXPORT_SYMBOL_GPL(power_supply_put_battery_info);
+
+/**
+ * power_supply_ocv2cap_simple() - find the battery capacity
+ * @table: Pointer to battery OCV lookup table
+ * @table_len: OCV table length
+ * @ocv: Current OCV value
+ *
+ * This helper function is used to look up battery capacity according to
+ * current OCV value from one OCV table, and the OCV table must be ordered
+ * descending.
+ *
+ * Return: the battery capacity.
+ */
+int power_supply_ocv2cap_simple(struct power_supply_battery_ocv_table *table,
+ int table_len, int ocv)
+{
+ int i, cap, tmp;
+
+ for (i = 0; i < table_len; i++)
+ if (ocv > table[i].ocv)
+ break;
+
+ if (i > 0 && i < table_len) {
+ tmp = (table[i - 1].capacity - table[i].capacity) *
+ (ocv - table[i].ocv);
+ tmp /= table[i - 1].ocv - table[i].ocv;
+ cap = tmp + table[i].capacity;
+ } else if (i == 0) {
+ cap = table[0].capacity;
+ } else {
+ cap = table[table_len - 1].capacity;
+ }
+
+ return cap;
+}
+EXPORT_SYMBOL_GPL(power_supply_ocv2cap_simple);
+
+struct power_supply_battery_ocv_table *
+power_supply_find_ocv2cap_table(struct power_supply_battery_info *info,
+ int temp, int *table_len)
+{
+ int best_temp_diff = INT_MAX, temp_diff;
+ u8 i, best_index = 0;
+
+ if (!info->ocv_table[0])
+ return NULL;
+
+ for (i = 0; i < POWER_SUPPLY_OCV_TEMP_MAX; i++) {
+ temp_diff = abs(info->ocv_temp[i] - temp);
+
+ if (temp_diff < best_temp_diff) {
+ best_temp_diff = temp_diff;
+ best_index = i;
+ }
+ }
+
+ *table_len = info->ocv_table_size[best_index];
+ return info->ocv_table[best_index];
+}
+EXPORT_SYMBOL_GPL(power_supply_find_ocv2cap_table);
+
+int power_supply_batinfo_ocv2cap(struct power_supply_battery_info *info,
+ int ocv, int temp)
+{
+ struct power_supply_battery_ocv_table *table;
+ int table_len;
+
+ table = power_supply_find_ocv2cap_table(info, temp, &table_len);
+ if (!table)
+ return -EINVAL;
+
+ return power_supply_ocv2cap_simple(table, table_len, ocv);
+}
+EXPORT_SYMBOL_GPL(power_supply_batinfo_ocv2cap);
+
int power_supply_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
@@ -880,6 +1018,7 @@ __power_supply_register(struct device *parent,
dev_set_drvdata(dev, psy);
psy->desc = desc;
if (cfg) {
+ dev->groups = cfg->attr_grp;
psy->drv_data = cfg->drv_data;
psy->of_node =
cfg->fwnode ? to_of_node(cfg->fwnode) : cfg->of_node;