power: supply: Add snapshot of QPNP QG driver and its dependencies
Add snapshot of the qpnp-qg driver as of msm-4.19 'commit 7bbb67403d95 ("Merge: msm_geni_serial: Correct the DMA RX interrupt logic")'. Change-Id: If8df8bc75705676006be7172c20469baa1c1dae6 Signed-off-by: Shyam Kumar Thella <sthella@codeaurora.org>
This commit is contained in:
parent
21b3dfffb4
commit
3be2db0032
@ -93,3 +93,4 @@ obj-$(CONFIG_CHARGER_UCS1002) += ucs1002_power.o
|
||||
obj-$(CONFIG_CHARGER_BD70528) += bd70528-charger.o
|
||||
obj-$(CONFIG_CHARGER_WILCO) += wilco-charger.o
|
||||
obj-$(CONFIG_QTI_BATTERY_CHARGER) += qti_battery_charger.o
|
||||
obj-$(CONFIG_QCOM_POWER_SUPPLY) += qcom/
|
||||
|
18
drivers/power/supply/qcom/Kconfig
Normal file
18
drivers/power/supply/qcom/Kconfig
Normal file
@ -0,0 +1,18 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
menuconfig QCOM_POWER_SUPPLY
|
||||
tristate "Support for Qualcomm Technologies, Inc. power supply"
|
||||
depends on ARCH_QCOM
|
||||
|
||||
if QCOM_POWER_SUPPLY
|
||||
|
||||
config QPNP_QG
|
||||
bool "QPNP Qgauge driver"
|
||||
depends on MFD_SPMI_PMIC
|
||||
help
|
||||
Say Y here to enable the Qualcomm Technologies, Inc. QGauge driver
|
||||
which uses the periodic sampling of the battery voltage and current
|
||||
to determine the battery state-of-charge (SOC) and supports other
|
||||
battery management features.
|
||||
|
||||
endif
|
3
drivers/power/supply/qcom/Makefile
Normal file
3
drivers/power/supply/qcom/Makefile
Normal file
@ -0,0 +1,3 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
obj-$(CONFIG_QPNP_QG) += qpnp-qg.o battery-profile-loader.o pmic-voter.o qg-util.o qg-soc.o qg-sdam.o qg-battery-profile.o qg-profile-lib.o fg-alg.o
|
336
drivers/power/supply/qcom/battery-profile-loader.c
Normal file
336
drivers/power/supply/qcom/battery-profile-loader.c
Normal file
@ -0,0 +1,336 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2013-2020, The Linux Foundation. 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 v2");
|
89
drivers/power/supply/qcom/battery-profile-loader.h
Normal file
89
drivers/power/supply/qcom/battery-profile-loader.h
Normal file
@ -0,0 +1,89 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (c) 2013-2014, 2016-2020 The Linux Foundation. All rights reserved.
|
||||
*/
|
||||
|
||||
#ifndef __BATTERY_PROFILE_LOADER_H
|
||||
#define __BATTERY_PROFILE_LOADER_H
|
||||
|
||||
#include <linux/of.h>
|
||||
|
||||
#define MAX_BATT_ID_NUM 4
|
||||
#define DEGC_SCALE 10
|
||||
|
||||
struct batt_ids {
|
||||
int kohm[MAX_BATT_ID_NUM];
|
||||
int num;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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;
|
||||
};
|
||||
|
||||
/**
|
||||
* of_batterydata_get_best_profile() - Find matching battery data device node
|
||||
* @batterydata_container_node: pointer to the battery-data container device
|
||||
* node containing the profile nodes.
|
||||
* @batt_id_kohm: Battery ID in KOhms for which we want to find the profile.
|
||||
* @batt_type: Battery type which we want to force load the profile.
|
||||
*
|
||||
* This routine returns a device_node pointer to the closest match battery data
|
||||
* from device tree based on the battery id reading.
|
||||
*/
|
||||
struct device_node *of_batterydata_get_best_profile(
|
||||
const struct device_node *batterydata_container_node,
|
||||
int batt_id_kohm, const char *batt_type);
|
||||
|
||||
/**
|
||||
* of_batterydata_get_best_aged_profile() - Find best aged battery profile
|
||||
* @batterydata_container_node: pointer to the battery-data container device
|
||||
* node containing the profile nodes.
|
||||
* @batt_id_kohm: Battery ID in KOhms for which we want to find the profile.
|
||||
* @batt_age_level: Battery age level.
|
||||
* @avail_age_level: Available battery age level.
|
||||
*
|
||||
* This routine returns a device_node pointer to the closest match battery data
|
||||
* from device tree based on the battery id reading and age level.
|
||||
*/
|
||||
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);
|
||||
|
||||
/**
|
||||
* of_batterydata_get_aged_profile_count() - Gets the number of aged profiles
|
||||
* @batterydata_node: pointer to the battery-data container device
|
||||
* node containing the profile nodes.
|
||||
* @batt_id_kohm: Battery ID in KOhms for which we want to find the profile.
|
||||
* @count: Number of aged profiles available to support SOH based profile
|
||||
* loading.
|
||||
*
|
||||
* This routine returns zero if valid number of aged profiles are available.
|
||||
*/
|
||||
int of_batterydata_get_aged_profile_count(
|
||||
const struct device_node *batterydata_node,
|
||||
int batt_id_kohm, int *count);
|
||||
|
||||
/**
|
||||
* of_batterydata_read_soh_aged_profiles() - Reads the data from aged profiles
|
||||
* @batterydata_node: pointer to the battery-data container device
|
||||
* node containing the profile nodes.
|
||||
* @batt_id_kohm: Battery ID in KOhms for which we want to find the profile.
|
||||
* @soh_data: SOH data from the profile if it is found to be valid.
|
||||
*
|
||||
* This routine returns zero if SOH data of aged profiles is valid.
|
||||
*/
|
||||
int of_batterydata_read_soh_aged_profiles(
|
||||
const struct device_node *batterydata_node,
|
||||
int batt_id_kohm, struct soh_range *soh_data);
|
||||
|
||||
#endif /* __BATTERY_PROFILE_LOADER_H */
|
1646
drivers/power/supply/qcom/fg-alg.c
Normal file
1646
drivers/power/supply/qcom/fg-alg.c
Normal file
File diff suppressed because it is too large
Load Diff
170
drivers/power/supply/qcom/fg-alg.h
Normal file
170
drivers/power/supply/qcom/fg-alg.h
Normal file
@ -0,0 +1,170 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (c) 2016-2020, The Linux Foundation. All rights reserved.
|
||||
*/
|
||||
|
||||
#ifndef __FG_ALG_H__
|
||||
#define __FG_ALG_H__
|
||||
|
||||
#include "battery-profile-loader.h"
|
||||
#include "step-chg-jeita.h"
|
||||
|
||||
#define BUCKET_COUNT 8
|
||||
#define BUCKET_SOC_PCT (256 / BUCKET_COUNT)
|
||||
#define MAX_CC_STEPS 20
|
||||
#define MAX_TTF_SAMPLES 10
|
||||
|
||||
#define is_between(left, right, value) \
|
||||
(((left) >= (right) && (left) >= (value) \
|
||||
&& (value) >= (right)) \
|
||||
|| ((left) <= (right) && (left) <= (value) \
|
||||
&& (value) <= (right)))
|
||||
struct cycle_counter {
|
||||
void *data;
|
||||
char str_buf[BUCKET_COUNT * 8];
|
||||
bool started[BUCKET_COUNT];
|
||||
u16 count[BUCKET_COUNT];
|
||||
u8 last_soc[BUCKET_COUNT];
|
||||
int id;
|
||||
int last_bucket;
|
||||
struct mutex lock;
|
||||
int (*restore_count)(void *data, u16 *buf, int num_bytes);
|
||||
int (*store_count)(void *data, u16 *buf, int id, int num_bytes);
|
||||
};
|
||||
|
||||
struct cl_params {
|
||||
int min_start_soc;
|
||||
int max_start_soc;
|
||||
int max_temp;
|
||||
int min_temp;
|
||||
int max_cap_inc;
|
||||
int max_cap_dec;
|
||||
int max_cap_limit;
|
||||
int min_cap_limit;
|
||||
int skew_decipct;
|
||||
int min_delta_batt_soc;
|
||||
int ibat_flt_thr_ma;
|
||||
bool cl_wt_enable;
|
||||
};
|
||||
|
||||
struct cap_learning {
|
||||
void *data;
|
||||
int init_cc_soc_sw;
|
||||
int cc_soc_max;
|
||||
int init_batt_soc;
|
||||
int init_batt_soc_cp;
|
||||
int64_t nom_cap_uah;
|
||||
int64_t init_cap_uah;
|
||||
int64_t final_cap_uah;
|
||||
int64_t learned_cap_uah;
|
||||
int64_t delta_cap_uah;
|
||||
bool active;
|
||||
struct mutex lock;
|
||||
struct cl_params dt;
|
||||
bool (*ok_to_begin)(void *data);
|
||||
int (*get_learned_capacity)(void *data, int64_t *learned_cap_uah);
|
||||
int (*store_learned_capacity)(void *data, int64_t learned_cap_uah);
|
||||
int (*get_cc_soc)(void *data, int *cc_soc_sw);
|
||||
int (*prime_cc_soc)(void *data, u32 cc_soc_sw);
|
||||
};
|
||||
|
||||
enum ttf_mode {
|
||||
TTF_MODE_NORMAL = 0,
|
||||
TTF_MODE_QNOVO,
|
||||
TTF_MODE_VBAT_STEP_CHG,
|
||||
TTF_MODE_OCV_STEP_CHG,
|
||||
};
|
||||
|
||||
enum ttf_param {
|
||||
TTF_MSOC = 0,
|
||||
TTF_VBAT,
|
||||
TTF_OCV,
|
||||
TTF_IBAT,
|
||||
TTF_FCC,
|
||||
TTF_MODE,
|
||||
TTF_ITERM,
|
||||
TTF_RBATT,
|
||||
TTF_VFLOAT,
|
||||
TTF_CHG_TYPE,
|
||||
TTF_CHG_STATUS,
|
||||
TTF_TTE_VALID,
|
||||
TTF_CHG_DONE,
|
||||
};
|
||||
|
||||
struct ttf_circ_buf {
|
||||
int arr[MAX_TTF_SAMPLES];
|
||||
int size;
|
||||
int head;
|
||||
};
|
||||
|
||||
struct ttf_cc_step_data {
|
||||
int arr[MAX_CC_STEPS];
|
||||
int sel;
|
||||
};
|
||||
|
||||
struct ttf_pt {
|
||||
s32 x;
|
||||
s32 y;
|
||||
};
|
||||
|
||||
struct step_chg_data {
|
||||
int ocv;
|
||||
int soc;
|
||||
};
|
||||
|
||||
struct ttf {
|
||||
void *data;
|
||||
struct ttf_circ_buf ibatt;
|
||||
struct ttf_circ_buf vbatt;
|
||||
struct ttf_cc_step_data cc_step;
|
||||
struct mutex lock;
|
||||
struct step_chg_data *step_chg_data;
|
||||
struct range_data *step_chg_cfg;
|
||||
bool step_chg_cfg_valid;
|
||||
bool ocv_step_chg_cfg_valid;
|
||||
bool clear_ibatt;
|
||||
int step_chg_num_params;
|
||||
int mode;
|
||||
int last_ttf;
|
||||
int input_present;
|
||||
int iterm_delta;
|
||||
int period_ms;
|
||||
s64 last_ms;
|
||||
struct delayed_work ttf_work;
|
||||
int (*get_ttf_param)(void *data, enum ttf_param, int *val);
|
||||
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,
|
||||
int charge_status, bool charge_done, bool input_present);
|
||||
int get_cycle_count(struct cycle_counter *counter, int *count);
|
||||
int get_cycle_counts(struct cycle_counter *counter, const char **buf);
|
||||
int cycle_count_init(struct cycle_counter *counter);
|
||||
void cap_learning_abort(struct cap_learning *cl);
|
||||
void cap_learning_update(struct cap_learning *cl, int batt_temp,
|
||||
int batt_soc, int charge_status, bool charge_done,
|
||||
bool input_present, bool qnovo_en);
|
||||
int cap_learning_init(struct cap_learning *cl);
|
||||
int cap_learning_post_profile_init(struct cap_learning *cl,
|
||||
int64_t nom_cap_uah);
|
||||
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
|
832
drivers/power/supply/qcom/pmic-voter.c
Normal file
832
drivers/power/supply/qcom/pmic-voter.c
Normal file
@ -0,0 +1,832 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2015-2017, 2019-2020, The Linux Foundation. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/printk.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/string.h>
|
||||
|
||||
#include <linux/pmic-voter.h>
|
||||
|
||||
#define NUM_MAX_CLIENTS 32
|
||||
#define DEBUG_FORCE_CLIENT "DEBUG_FORCE_CLIENT"
|
||||
|
||||
static DEFINE_SPINLOCK(votable_list_slock);
|
||||
static LIST_HEAD(votable_list);
|
||||
|
||||
static struct dentry *debug_root;
|
||||
|
||||
struct client_vote {
|
||||
bool enabled;
|
||||
int value;
|
||||
};
|
||||
|
||||
struct votable {
|
||||
const char *name;
|
||||
const char *override_client;
|
||||
struct list_head list;
|
||||
struct client_vote votes[NUM_MAX_CLIENTS];
|
||||
int num_clients;
|
||||
int type;
|
||||
int effective_client_id;
|
||||
int effective_result;
|
||||
int override_result;
|
||||
struct mutex vote_lock;
|
||||
void *data;
|
||||
int (*callback)(struct votable *votable,
|
||||
void *data,
|
||||
int effective_result,
|
||||
const char *effective_client);
|
||||
char *client_strs[NUM_MAX_CLIENTS];
|
||||
bool voted_on;
|
||||
struct dentry *root;
|
||||
struct dentry *status_ent;
|
||||
u32 force_val;
|
||||
struct dentry *force_val_ent;
|
||||
bool force_active;
|
||||
struct dentry *force_active_ent;
|
||||
};
|
||||
|
||||
/**
|
||||
* vote_set_any()
|
||||
* @votable: votable object
|
||||
* @client_id: client number of the latest voter
|
||||
* @eff_res: sets 0 or 1 based on the voting
|
||||
* @eff_id: Always returns the client_id argument
|
||||
*
|
||||
* Note that for SET_ANY voter, the value is always same as enabled. There is
|
||||
* no idea of a voter abstaining from the election. Hence there is never a
|
||||
* situation when the effective_id will be invalid, during election.
|
||||
*
|
||||
* Context:
|
||||
* Must be called with the votable->lock held
|
||||
*/
|
||||
static void vote_set_any(struct votable *votable, int client_id,
|
||||
int *eff_res, int *eff_id)
|
||||
{
|
||||
int i;
|
||||
|
||||
*eff_res = 0;
|
||||
|
||||
for (i = 0; i < votable->num_clients && votable->client_strs[i]; i++)
|
||||
*eff_res |= votable->votes[i].enabled;
|
||||
|
||||
*eff_id = client_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* vote_min() -
|
||||
* @votable: votable object
|
||||
* @client_id: client number of the latest voter
|
||||
* @eff_res: sets this to the min. of all the values amongst enabled voters.
|
||||
* If there is no enabled client, this is set to INT_MAX
|
||||
* @eff_id: sets this to the client id that has the min value amongst all
|
||||
* the enabled clients. If there is no enabled client, sets this
|
||||
* to -EINVAL
|
||||
*
|
||||
* Context:
|
||||
* Must be called with the votable->lock held
|
||||
*/
|
||||
static void vote_min(struct votable *votable, int client_id,
|
||||
int *eff_res, int *eff_id)
|
||||
{
|
||||
int i;
|
||||
|
||||
*eff_res = INT_MAX;
|
||||
*eff_id = -EINVAL;
|
||||
for (i = 0; i < votable->num_clients && votable->client_strs[i]; i++) {
|
||||
if (votable->votes[i].enabled
|
||||
&& *eff_res > votable->votes[i].value) {
|
||||
*eff_res = votable->votes[i].value;
|
||||
*eff_id = i;
|
||||
}
|
||||
}
|
||||
if (*eff_id == -EINVAL)
|
||||
*eff_res = -EINVAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* vote_max() -
|
||||
* @votable: votable object
|
||||
* @client_id: client number of the latest voter
|
||||
* @eff_res: sets this to the max. of all the values amongst enabled voters.
|
||||
* If there is no enabled client, this is set to -EINVAL
|
||||
* @eff_id: sets this to the client id that has the max value amongst all
|
||||
* the enabled clients. If there is no enabled client, sets this to
|
||||
* -EINVAL
|
||||
*
|
||||
* Context:
|
||||
* Must be called with the votable->lock held
|
||||
*/
|
||||
static void vote_max(struct votable *votable, int client_id,
|
||||
int *eff_res, int *eff_id)
|
||||
{
|
||||
int i;
|
||||
|
||||
*eff_res = INT_MIN;
|
||||
*eff_id = -EINVAL;
|
||||
for (i = 0; i < votable->num_clients && votable->client_strs[i]; i++) {
|
||||
if (votable->votes[i].enabled &&
|
||||
*eff_res < votable->votes[i].value) {
|
||||
*eff_res = votable->votes[i].value;
|
||||
*eff_id = i;
|
||||
}
|
||||
}
|
||||
if (*eff_id == -EINVAL)
|
||||
*eff_res = -EINVAL;
|
||||
}
|
||||
|
||||
static int get_client_id(struct votable *votable, const char *client_str)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < votable->num_clients; i++) {
|
||||
if (votable->client_strs[i]
|
||||
&& (strcmp(votable->client_strs[i], client_str) == 0))
|
||||
return i;
|
||||
}
|
||||
|
||||
/* new client */
|
||||
for (i = 0; i < votable->num_clients; i++) {
|
||||
if (!votable->client_strs[i]) {
|
||||
votable->client_strs[i]
|
||||
= kstrdup(client_str, GFP_KERNEL);
|
||||
if (!votable->client_strs[i])
|
||||
return -ENOMEM;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static char *get_client_str(struct votable *votable, int client_id)
|
||||
{
|
||||
if (!votable || (client_id == -EINVAL))
|
||||
return NULL;
|
||||
|
||||
return votable->client_strs[client_id];
|
||||
}
|
||||
|
||||
void lock_votable(struct votable *votable)
|
||||
{
|
||||
mutex_lock(&votable->vote_lock);
|
||||
}
|
||||
|
||||
void unlock_votable(struct votable *votable)
|
||||
{
|
||||
mutex_unlock(&votable->vote_lock);
|
||||
}
|
||||
|
||||
/**
|
||||
* is_override_vote_enabled() -
|
||||
* is_override_vote_enabled_locked() -
|
||||
* The unlocked and locked variants of getting whether override
|
||||
vote is enabled.
|
||||
* @votable: the votable object
|
||||
*
|
||||
* Returns:
|
||||
* True if the client's vote is enabled; false otherwise.
|
||||
*/
|
||||
bool is_override_vote_enabled_locked(struct votable *votable)
|
||||
{
|
||||
if (!votable)
|
||||
return false;
|
||||
|
||||
return votable->override_result != -EINVAL;
|
||||
}
|
||||
|
||||
bool is_override_vote_enabled(struct votable *votable)
|
||||
{
|
||||
bool enable;
|
||||
|
||||
if (!votable)
|
||||
return false;
|
||||
|
||||
lock_votable(votable);
|
||||
enable = is_override_vote_enabled_locked(votable);
|
||||
unlock_votable(votable);
|
||||
|
||||
return enable;
|
||||
}
|
||||
|
||||
/**
|
||||
* is_client_vote_enabled() -
|
||||
* is_client_vote_enabled_locked() -
|
||||
* The unlocked and locked variants of getting whether a client's
|
||||
vote is enabled.
|
||||
* @votable: the votable object
|
||||
* @client_str: client of interest
|
||||
*
|
||||
* Returns:
|
||||
* True if the client's vote is enabled; false otherwise.
|
||||
*/
|
||||
bool is_client_vote_enabled_locked(struct votable *votable,
|
||||
const char *client_str)
|
||||
{
|
||||
|
||||
int client_id;
|
||||
|
||||
if (!votable || !client_str)
|
||||
return false;
|
||||
|
||||
client_id = get_client_id(votable, client_str);
|
||||
if (client_id < 0)
|
||||
return false;
|
||||
|
||||
return votable->votes[client_id].enabled;
|
||||
}
|
||||
|
||||
bool is_client_vote_enabled(struct votable *votable, const char *client_str)
|
||||
{
|
||||
bool enabled;
|
||||
|
||||
if (!votable || !client_str)
|
||||
return false;
|
||||
|
||||
lock_votable(votable);
|
||||
enabled = is_client_vote_enabled_locked(votable, client_str);
|
||||
unlock_votable(votable);
|
||||
return enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* get_client_vote() -
|
||||
* get_client_vote_locked() -
|
||||
* The unlocked and locked variants of getting a client's voted
|
||||
* value.
|
||||
* @votable: the votable object
|
||||
* @client_str: client of interest
|
||||
*
|
||||
* Returns:
|
||||
* The value the client voted for. -EINVAL is returned if the client
|
||||
* is not enabled or the client is not found.
|
||||
*/
|
||||
int get_client_vote_locked(struct votable *votable, const char *client_str)
|
||||
{
|
||||
int client_id;
|
||||
|
||||
if (!votable || !client_str)
|
||||
return -EINVAL;
|
||||
|
||||
client_id = get_client_id(votable, client_str);
|
||||
if (client_id < 0)
|
||||
return -EINVAL;
|
||||
|
||||
if ((votable->type != VOTE_SET_ANY)
|
||||
&& !votable->votes[client_id].enabled)
|
||||
return -EINVAL;
|
||||
|
||||
return votable->votes[client_id].value;
|
||||
}
|
||||
|
||||
int get_client_vote(struct votable *votable, const char *client_str)
|
||||
{
|
||||
int value;
|
||||
|
||||
if (!votable || !client_str)
|
||||
return -EINVAL;
|
||||
|
||||
lock_votable(votable);
|
||||
value = get_client_vote_locked(votable, client_str);
|
||||
unlock_votable(votable);
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* get_effective_result() -
|
||||
* get_effective_result_locked() -
|
||||
* The unlocked and locked variants of getting the effective value
|
||||
* amongst all the enabled voters.
|
||||
*
|
||||
* @votable: the votable object
|
||||
*
|
||||
* Returns:
|
||||
* The effective result.
|
||||
* For MIN and MAX votable, returns -EINVAL when the votable
|
||||
* object has been created but no clients have casted their votes or
|
||||
* the last enabled client disables its vote.
|
||||
* For SET_ANY votable it returns 0 when no clients have casted their votes
|
||||
* because for SET_ANY there is no concept of abstaining from election. The
|
||||
* votes for all the clients of SET_ANY votable is defaulted to false.
|
||||
*/
|
||||
int get_effective_result_locked(struct votable *votable)
|
||||
{
|
||||
if (!votable)
|
||||
return -EINVAL;
|
||||
|
||||
if (votable->force_active)
|
||||
return votable->force_val;
|
||||
|
||||
if (votable->override_result != -EINVAL)
|
||||
return votable->override_result;
|
||||
|
||||
return votable->effective_result;
|
||||
}
|
||||
|
||||
int get_effective_result(struct votable *votable)
|
||||
{
|
||||
int value;
|
||||
|
||||
if (!votable)
|
||||
return -EINVAL;
|
||||
|
||||
lock_votable(votable);
|
||||
value = get_effective_result_locked(votable);
|
||||
unlock_votable(votable);
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* get_effective_client() -
|
||||
* get_effective_client_locked() -
|
||||
* The unlocked and locked variants of getting the effective client
|
||||
* amongst all the enabled voters.
|
||||
*
|
||||
* @votable: the votable object
|
||||
*
|
||||
* Returns:
|
||||
* The effective client.
|
||||
* For MIN and MAX votable, returns NULL when the votable
|
||||
* object has been created but no clients have casted their votes or
|
||||
* the last enabled client disables its vote.
|
||||
* For SET_ANY votable it returns NULL too when no clients have casted
|
||||
* their votes. But for SET_ANY since there is no concept of abstaining
|
||||
* from election, the only client that casts a vote or the client that
|
||||
* caused the result to change is returned.
|
||||
*/
|
||||
const char *get_effective_client_locked(struct votable *votable)
|
||||
{
|
||||
if (!votable)
|
||||
return NULL;
|
||||
|
||||
if (votable->force_active)
|
||||
return DEBUG_FORCE_CLIENT;
|
||||
|
||||
if (votable->override_result != -EINVAL)
|
||||
return votable->override_client;
|
||||
|
||||
return get_client_str(votable, votable->effective_client_id);
|
||||
}
|
||||
|
||||
const char *get_effective_client(struct votable *votable)
|
||||
{
|
||||
const char *client_str;
|
||||
|
||||
if (!votable)
|
||||
return NULL;
|
||||
|
||||
lock_votable(votable);
|
||||
client_str = get_effective_client_locked(votable);
|
||||
unlock_votable(votable);
|
||||
return client_str;
|
||||
}
|
||||
|
||||
/**
|
||||
* vote() -
|
||||
*
|
||||
* @votable: the votable object
|
||||
* @client_str: the voting client
|
||||
* @enabled: This provides a means for the client to exclude himself from
|
||||
* election. This clients val (the next argument) will be
|
||||
* considered only when he has enabled his participation.
|
||||
* Note that this takes a differnt meaning for SET_ANY type, as
|
||||
* there is no concept of abstaining from participation.
|
||||
* Enabled is treated as the boolean value the client is voting.
|
||||
* @val: The vote value. This is ignored for SET_ANY votable types.
|
||||
* For MIN, MAX votable types this value is used as the
|
||||
* clients vote value when the enabled is true, this value is
|
||||
* ignored if enabled is false.
|
||||
*
|
||||
* The callback is called only when there is a change in the election results or
|
||||
* if it is the first time someone is voting.
|
||||
*
|
||||
* Returns:
|
||||
* The return from the callback when present and needs to be called
|
||||
* or zero.
|
||||
*/
|
||||
int vote(struct votable *votable, const char *client_str, bool enabled, int val)
|
||||
{
|
||||
int effective_id = -EINVAL;
|
||||
int effective_result;
|
||||
int client_id;
|
||||
int rc = 0;
|
||||
bool similar_vote = false;
|
||||
|
||||
if (!votable || !client_str)
|
||||
return -EINVAL;
|
||||
|
||||
lock_votable(votable);
|
||||
|
||||
client_id = get_client_id(votable, client_str);
|
||||
if (client_id < 0) {
|
||||
rc = client_id;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* for SET_ANY the val is to be ignored, set it
|
||||
* to enabled so that the election still works based on
|
||||
* value regardless of the type
|
||||
*/
|
||||
if (votable->type == VOTE_SET_ANY)
|
||||
val = enabled;
|
||||
|
||||
if ((votable->votes[client_id].enabled == enabled) &&
|
||||
(votable->votes[client_id].value == val)) {
|
||||
pr_debug("%s: %s,%d same vote %s of val=%d\n",
|
||||
votable->name,
|
||||
client_str, client_id,
|
||||
enabled ? "on" : "off",
|
||||
val);
|
||||
similar_vote = true;
|
||||
}
|
||||
|
||||
votable->votes[client_id].enabled = enabled;
|
||||
votable->votes[client_id].value = val;
|
||||
|
||||
if (similar_vote && votable->voted_on) {
|
||||
pr_debug("%s: %s,%d Ignoring similar vote %s of val=%d\n",
|
||||
votable->name,
|
||||
client_str, client_id, enabled ? "on" : "off", val);
|
||||
goto out;
|
||||
}
|
||||
|
||||
pr_debug("%s: %s,%d voting %s of val=%d\n",
|
||||
votable->name,
|
||||
client_str, client_id, enabled ? "on" : "off", val);
|
||||
switch (votable->type) {
|
||||
case VOTE_MIN:
|
||||
vote_min(votable, client_id, &effective_result, &effective_id);
|
||||
break;
|
||||
case VOTE_MAX:
|
||||
vote_max(votable, client_id, &effective_result, &effective_id);
|
||||
break;
|
||||
case VOTE_SET_ANY:
|
||||
vote_set_any(votable, client_id,
|
||||
&effective_result, &effective_id);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Note that the callback is called with a NULL string and -EINVAL
|
||||
* result when there are no enabled votes
|
||||
*/
|
||||
if (!votable->voted_on
|
||||
|| (effective_result != votable->effective_result)) {
|
||||
votable->effective_client_id = effective_id;
|
||||
votable->effective_result = effective_result;
|
||||
pr_debug("%s: effective vote is now %d voted by %s,%d\n",
|
||||
votable->name, effective_result,
|
||||
get_client_str(votable, effective_id),
|
||||
effective_id);
|
||||
if (votable->callback && !votable->force_active
|
||||
&& (votable->override_result == -EINVAL))
|
||||
rc = votable->callback(votable, votable->data,
|
||||
effective_result,
|
||||
get_client_str(votable, effective_id));
|
||||
}
|
||||
|
||||
votable->voted_on = true;
|
||||
out:
|
||||
unlock_votable(votable);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* vote_override() -
|
||||
*
|
||||
* @votable: The votable object
|
||||
* @override_client: The voting client that will override other client's
|
||||
* votes, that are already present. When force_active
|
||||
* and override votes are set on a votable, force_active's
|
||||
* client will have the higher priority and it's vote will
|
||||
* be the effective one.
|
||||
* @enabled: This provides a means for the override client to exclude
|
||||
* itself from election. This client's vote
|
||||
* (the next argument) will be considered only when
|
||||
* it has enabled its participation. When this is
|
||||
* set true, this will force a value on a MIN/MAX votable
|
||||
* irrespective of its current value.
|
||||
* @val: The vote value. This will be effective only if enabled
|
||||
* is set true.
|
||||
* Returns:
|
||||
* The result of vote. 0 is returned if the vote
|
||||
* is successfully set by the overriding client, when enabled is set.
|
||||
*/
|
||||
int vote_override(struct votable *votable, const char *override_client,
|
||||
bool enabled, int val)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
if (!votable || !override_client)
|
||||
return -EINVAL;
|
||||
|
||||
lock_votable(votable);
|
||||
if (votable->force_active) {
|
||||
votable->override_result = enabled ? val : -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (enabled) {
|
||||
rc = votable->callback(votable, votable->data,
|
||||
val, override_client);
|
||||
if (!rc) {
|
||||
votable->override_client = override_client;
|
||||
votable->override_result = val;
|
||||
}
|
||||
} else {
|
||||
rc = votable->callback(votable, votable->data,
|
||||
votable->effective_result,
|
||||
get_client_str(votable, votable->effective_client_id));
|
||||
votable->override_result = -EINVAL;
|
||||
}
|
||||
|
||||
out:
|
||||
unlock_votable(votable);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int rerun_election(struct votable *votable)
|
||||
{
|
||||
int rc = 0;
|
||||
int effective_result;
|
||||
|
||||
if (!votable)
|
||||
return -EINVAL;
|
||||
|
||||
lock_votable(votable);
|
||||
effective_result = get_effective_result_locked(votable);
|
||||
if (votable->callback)
|
||||
rc = votable->callback(votable,
|
||||
votable->data,
|
||||
effective_result,
|
||||
get_client_str(votable, votable->effective_client_id));
|
||||
unlock_votable(votable);
|
||||
return rc;
|
||||
}
|
||||
|
||||
struct votable *find_votable(const char *name)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct votable *v;
|
||||
bool found = false;
|
||||
|
||||
if (!name)
|
||||
return NULL;
|
||||
|
||||
spin_lock_irqsave(&votable_list_slock, flags);
|
||||
if (list_empty(&votable_list))
|
||||
goto out;
|
||||
|
||||
list_for_each_entry(v, &votable_list, list) {
|
||||
if (strcmp(v->name, name) == 0) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
out:
|
||||
spin_unlock_irqrestore(&votable_list_slock, flags);
|
||||
|
||||
if (found)
|
||||
return v;
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int force_active_get(void *data, u64 *val)
|
||||
{
|
||||
struct votable *votable = data;
|
||||
|
||||
*val = votable->force_active;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int force_active_set(void *data, u64 val)
|
||||
{
|
||||
struct votable *votable = data;
|
||||
int rc = 0;
|
||||
int effective_result;
|
||||
const char *client;
|
||||
|
||||
lock_votable(votable);
|
||||
votable->force_active = !!val;
|
||||
|
||||
if (!votable->callback)
|
||||
goto out;
|
||||
|
||||
if (votable->force_active) {
|
||||
rc = votable->callback(votable, votable->data,
|
||||
votable->force_val,
|
||||
DEBUG_FORCE_CLIENT);
|
||||
} else {
|
||||
if (votable->override_result != -EINVAL) {
|
||||
effective_result = votable->override_result;
|
||||
client = votable->override_client;
|
||||
} else {
|
||||
effective_result = votable->effective_result;
|
||||
client = get_client_str(votable,
|
||||
votable->effective_client_id);
|
||||
}
|
||||
rc = votable->callback(votable, votable->data, effective_result,
|
||||
client);
|
||||
}
|
||||
out:
|
||||
unlock_votable(votable);
|
||||
return rc;
|
||||
}
|
||||
DEFINE_DEBUGFS_ATTRIBUTE(votable_force_ops, force_active_get, force_active_set,
|
||||
"%lld\n");
|
||||
|
||||
static int show_votable_clients(struct seq_file *m, void *data)
|
||||
{
|
||||
struct votable *votable = m->private;
|
||||
int i;
|
||||
char *type_str = "Unkonwn";
|
||||
const char *effective_client_str;
|
||||
|
||||
lock_votable(votable);
|
||||
|
||||
for (i = 0; i < votable->num_clients; i++) {
|
||||
if (votable->client_strs[i]) {
|
||||
seq_printf(m, "%s: %s:\t\t\ten=%d v=%d\n",
|
||||
votable->name,
|
||||
votable->client_strs[i],
|
||||
votable->votes[i].enabled,
|
||||
votable->votes[i].value);
|
||||
}
|
||||
}
|
||||
|
||||
switch (votable->type) {
|
||||
case VOTE_MIN:
|
||||
type_str = "Min";
|
||||
break;
|
||||
case VOTE_MAX:
|
||||
type_str = "Max";
|
||||
break;
|
||||
case VOTE_SET_ANY:
|
||||
type_str = "Set_any";
|
||||
break;
|
||||
}
|
||||
|
||||
effective_client_str = get_effective_client_locked(votable);
|
||||
seq_printf(m, "%s: effective=%s type=%s v=%d\n",
|
||||
votable->name,
|
||||
effective_client_str ? effective_client_str : "none",
|
||||
type_str,
|
||||
get_effective_result_locked(votable));
|
||||
unlock_votable(votable);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int votable_status_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct votable *votable = inode->i_private;
|
||||
|
||||
return single_open(file, show_votable_clients, votable);
|
||||
}
|
||||
|
||||
static const struct file_operations votable_status_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = votable_status_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = single_release,
|
||||
};
|
||||
|
||||
struct votable *create_votable(const char *name,
|
||||
int votable_type,
|
||||
int (*callback)(struct votable *votable,
|
||||
void *data,
|
||||
int effective_result,
|
||||
const char *effective_client),
|
||||
void *data)
|
||||
{
|
||||
struct votable *votable;
|
||||
unsigned long flags;
|
||||
|
||||
if (!name)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
votable = find_votable(name);
|
||||
if (votable)
|
||||
return ERR_PTR(-EEXIST);
|
||||
|
||||
if (debug_root == NULL) {
|
||||
debug_root = debugfs_create_dir("pmic-votable", NULL);
|
||||
if (!debug_root) {
|
||||
pr_err("Couldn't create debug dir\n");
|
||||
return ERR_PTR(-ENOMEM);
|
||||
}
|
||||
}
|
||||
|
||||
if (votable_type >= NUM_VOTABLE_TYPES) {
|
||||
pr_err("Invalid votable_type specified for voter\n");
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
votable = kzalloc(sizeof(struct votable), GFP_KERNEL);
|
||||
if (!votable)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
votable->name = kstrdup(name, GFP_KERNEL);
|
||||
if (!votable->name) {
|
||||
kfree(votable);
|
||||
return ERR_PTR(-ENOMEM);
|
||||
}
|
||||
|
||||
votable->num_clients = NUM_MAX_CLIENTS;
|
||||
votable->callback = callback;
|
||||
votable->type = votable_type;
|
||||
votable->data = data;
|
||||
votable->override_result = -EINVAL;
|
||||
mutex_init(&votable->vote_lock);
|
||||
|
||||
/*
|
||||
* Because effective_result and client states are invalid
|
||||
* before the first vote, initialize them to -EINVAL
|
||||
*/
|
||||
votable->effective_result = -EINVAL;
|
||||
if (votable->type == VOTE_SET_ANY)
|
||||
votable->effective_result = 0;
|
||||
votable->effective_client_id = -EINVAL;
|
||||
|
||||
spin_lock_irqsave(&votable_list_slock, flags);
|
||||
list_add(&votable->list, &votable_list);
|
||||
spin_unlock_irqrestore(&votable_list_slock, flags);
|
||||
|
||||
votable->root = debugfs_create_dir(name, debug_root);
|
||||
if (!votable->root) {
|
||||
pr_err("Couldn't create debug dir %s\n", name);
|
||||
kfree(votable->name);
|
||||
kfree(votable);
|
||||
return ERR_PTR(-ENOMEM);
|
||||
}
|
||||
|
||||
votable->status_ent = debugfs_create_file("status", S_IFREG | 0444,
|
||||
votable->root, votable,
|
||||
&votable_status_ops);
|
||||
if (!votable->status_ent) {
|
||||
pr_err("Couldn't create status dbg file for %s\n", name);
|
||||
debugfs_remove_recursive(votable->root);
|
||||
kfree(votable->name);
|
||||
kfree(votable);
|
||||
return ERR_PTR(-EEXIST);
|
||||
}
|
||||
|
||||
votable->force_val_ent = debugfs_create_u32("force_val",
|
||||
S_IFREG | 0644,
|
||||
votable->root,
|
||||
&(votable->force_val));
|
||||
|
||||
if (!votable->force_val_ent) {
|
||||
pr_err("Couldn't create force_val dbg file for %s\n", name);
|
||||
debugfs_remove_recursive(votable->root);
|
||||
kfree(votable->name);
|
||||
kfree(votable);
|
||||
return ERR_PTR(-EEXIST);
|
||||
}
|
||||
|
||||
votable->force_active_ent = debugfs_create_file("force_active",
|
||||
S_IFREG | 0444,
|
||||
votable->root, votable,
|
||||
&votable_force_ops);
|
||||
if (!votable->force_active_ent) {
|
||||
pr_err("Couldn't create force_active dbg file for %s\n", name);
|
||||
debugfs_remove_recursive(votable->root);
|
||||
kfree(votable->name);
|
||||
kfree(votable);
|
||||
return ERR_PTR(-EEXIST);
|
||||
}
|
||||
|
||||
return votable;
|
||||
}
|
||||
|
||||
void destroy_votable(struct votable *votable)
|
||||
{
|
||||
unsigned long flags;
|
||||
int i;
|
||||
|
||||
if (!votable)
|
||||
return;
|
||||
|
||||
spin_lock_irqsave(&votable_list_slock, flags);
|
||||
list_del(&votable->list);
|
||||
spin_unlock_irqrestore(&votable_list_slock, flags);
|
||||
|
||||
debugfs_remove_recursive(votable->root);
|
||||
|
||||
for (i = 0; i < votable->num_clients && votable->client_strs[i]; i++)
|
||||
kfree(votable->client_strs[i]);
|
||||
|
||||
kfree(votable->name);
|
||||
kfree(votable);
|
||||
}
|
536
drivers/power/supply/qcom/qg-battery-profile.c
Normal file
536
drivers/power/supply/qcom/qg-battery-profile.c
Normal file
@ -0,0 +1,536 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2018-2020 The Linux Foundation. All rights reserved.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) "QG-K: %s: " fmt, __func__
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/cdev.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/fcntl.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <uapi/linux/qg-profile.h>
|
||||
#include "qg-battery-profile.h"
|
||||
#include "qg-profile-lib.h"
|
||||
#include "qg-defs.h"
|
||||
|
||||
struct qg_battery_data {
|
||||
/* battery-data class node */
|
||||
dev_t dev_no;
|
||||
struct class *battery_class;
|
||||
struct device *battery_device;
|
||||
struct cdev battery_cdev;
|
||||
|
||||
/* profile */
|
||||
struct device_node *profile_node;
|
||||
struct profile_table_data profile[TABLE_MAX];
|
||||
};
|
||||
|
||||
struct tables {
|
||||
int table_index;
|
||||
char *table_name;
|
||||
};
|
||||
|
||||
static struct tables table[] = {
|
||||
{TABLE_SOC_OCV1, "qcom,pc-temp-v1-lut"},
|
||||
{TABLE_SOC_OCV2, "qcom,pc-temp-v2-lut"},
|
||||
{TABLE_FCC1, "qcom,fcc1-temp-lut"},
|
||||
{TABLE_FCC2, "qcom,fcc2-temp-lut"},
|
||||
{TABLE_Z1, "qcom,pc-temp-z1-lut"},
|
||||
{TABLE_Z2, "qcom,pc-temp-z2-lut"},
|
||||
{TABLE_Z3, "qcom,pc-temp-z3-lut"},
|
||||
{TABLE_Z4, "qcom,pc-temp-z4-lut"},
|
||||
{TABLE_Z5, "qcom,pc-temp-z5-lut"},
|
||||
{TABLE_Z6, "qcom,pc-temp-z6-lut"},
|
||||
{TABLE_Y1, "qcom,pc-temp-y1-lut"},
|
||||
{TABLE_Y2, "qcom,pc-temp-y2-lut"},
|
||||
{TABLE_Y3, "qcom,pc-temp-y3-lut"},
|
||||
{TABLE_Y4, "qcom,pc-temp-y4-lut"},
|
||||
{TABLE_Y5, "qcom,pc-temp-y5-lut"},
|
||||
{TABLE_Y6, "qcom,pc-temp-y6-lut"},
|
||||
};
|
||||
|
||||
static struct qg_battery_data *the_battery;
|
||||
|
||||
static void qg_battery_profile_free(void);
|
||||
|
||||
static int qg_battery_data_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct qg_battery_data *battery = container_of(inode->i_cdev,
|
||||
struct qg_battery_data, battery_cdev);
|
||||
|
||||
file->private_data = battery;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static long qg_battery_data_ioctl(struct file *file, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
struct qg_battery_data *battery = file->private_data;
|
||||
struct battery_params __user *bp_user =
|
||||
(struct battery_params __user *)arg;
|
||||
struct battery_params bp;
|
||||
int rc = 0, soc, ocv_uv, fcc_mah, var, slope;
|
||||
|
||||
if (!battery->profile_node) {
|
||||
pr_err("Battery data not set!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!bp_user) {
|
||||
pr_err("Invalid battery-params user pointer\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (copy_from_user(&bp, bp_user, sizeof(bp))) {
|
||||
pr_err("Failed in copy_from_user\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
switch (cmd) {
|
||||
case BPIOCXSOC:
|
||||
if (bp.table_index != TABLE_SOC_OCV1 &&
|
||||
bp.table_index != TABLE_SOC_OCV2) {
|
||||
pr_err("Invalid table index %d for SOC-OCV lookup\n",
|
||||
bp.table_index);
|
||||
rc = -EINVAL;
|
||||
} else {
|
||||
/* OCV is passed as deci-uV - 10^-4 V */
|
||||
soc = qg_interpolate_soc(
|
||||
&battery->profile[bp.table_index],
|
||||
bp.batt_temp, UV_TO_DECIUV(bp.ocv_uv));
|
||||
soc = CAP(QG_MIN_SOC, QG_MAX_SOC, soc);
|
||||
rc = put_user(soc, &bp_user->soc);
|
||||
if (rc < 0) {
|
||||
pr_err("BPIOCXSOC: Failed rc=%d\n", rc);
|
||||
goto ret_err;
|
||||
}
|
||||
pr_debug("BPIOCXSOC: lut=%s ocv=%d batt_temp=%d soc=%d\n",
|
||||
battery->profile[bp.table_index].name,
|
||||
bp.ocv_uv, bp.batt_temp, soc);
|
||||
}
|
||||
break;
|
||||
case BPIOCXOCV:
|
||||
if (bp.table_index != TABLE_SOC_OCV1 &&
|
||||
bp.table_index != TABLE_SOC_OCV2) {
|
||||
pr_err("Invalid table index %d for SOC-OCV lookup\n",
|
||||
bp.table_index);
|
||||
rc = -EINVAL;
|
||||
} else {
|
||||
ocv_uv = qg_interpolate_var(
|
||||
&battery->profile[bp.table_index],
|
||||
bp.batt_temp, bp.soc);
|
||||
ocv_uv = DECIUV_TO_UV(ocv_uv);
|
||||
ocv_uv = CAP(QG_MIN_OCV_UV, QG_MAX_OCV_UV, ocv_uv);
|
||||
rc = put_user(ocv_uv, &bp_user->ocv_uv);
|
||||
if (rc < 0) {
|
||||
pr_err("BPIOCXOCV: Failed rc=%d\n", rc);
|
||||
goto ret_err;
|
||||
}
|
||||
pr_debug("BPIOCXOCV: lut=%s ocv=%d batt_temp=%d soc=%d\n",
|
||||
battery->profile[bp.table_index].name,
|
||||
ocv_uv, bp.batt_temp, bp.soc);
|
||||
}
|
||||
break;
|
||||
case BPIOCXFCC:
|
||||
if (bp.table_index != TABLE_FCC1 &&
|
||||
bp.table_index != TABLE_FCC2) {
|
||||
pr_err("Invalid table index %d for FCC lookup\n",
|
||||
bp.table_index);
|
||||
rc = -EINVAL;
|
||||
} else {
|
||||
fcc_mah = qg_interpolate_single_row_lut(
|
||||
&battery->profile[bp.table_index],
|
||||
bp.batt_temp, DEGC_SCALE);
|
||||
fcc_mah = CAP(QG_MIN_FCC_MAH, QG_MAX_FCC_MAH, fcc_mah);
|
||||
rc = put_user(fcc_mah, &bp_user->fcc_mah);
|
||||
if (rc) {
|
||||
pr_err("BPIOCXFCC: Failed rc=%d\n", rc);
|
||||
goto ret_err;
|
||||
}
|
||||
pr_debug("BPIOCXFCC: lut=%s batt_temp=%d fcc_mah=%d\n",
|
||||
battery->profile[bp.table_index].name,
|
||||
bp.batt_temp, fcc_mah);
|
||||
}
|
||||
break;
|
||||
case BPIOCXVAR:
|
||||
if (bp.table_index < TABLE_Z1 || bp.table_index >= TABLE_MAX) {
|
||||
pr_err("Invalid table index %d for VAR lookup\n",
|
||||
bp.table_index);
|
||||
rc = -EINVAL;
|
||||
} else {
|
||||
var = qg_interpolate_var(
|
||||
&battery->profile[bp.table_index],
|
||||
bp.batt_temp, bp.soc);
|
||||
var = CAP(QG_MIN_VAR, QG_MAX_VAR, var);
|
||||
rc = put_user(var, &bp_user->var);
|
||||
if (rc < 0) {
|
||||
pr_err("BPIOCXVAR: Failed rc=%d\n", rc);
|
||||
goto ret_err;
|
||||
}
|
||||
pr_debug("BPIOCXVAR: lut=%s var=%d batt_temp=%d soc=%d\n",
|
||||
battery->profile[bp.table_index].name,
|
||||
var, bp.batt_temp, bp.soc);
|
||||
}
|
||||
break;
|
||||
case BPIOCXSLOPE:
|
||||
if (bp.table_index != TABLE_SOC_OCV1 &&
|
||||
bp.table_index != TABLE_SOC_OCV2) {
|
||||
pr_err("Invalid table index %d for Slope lookup\n",
|
||||
bp.table_index);
|
||||
rc = -EINVAL;
|
||||
} else {
|
||||
slope = qg_interpolate_slope(
|
||||
&battery->profile[bp.table_index],
|
||||
bp.batt_temp, bp.soc);
|
||||
slope = CAP(QG_MIN_SLOPE, QG_MAX_SLOPE, slope);
|
||||
rc = put_user(slope, &bp_user->slope);
|
||||
if (rc) {
|
||||
pr_err("BPIOCXSLOPE: Failed rc=%d\n", rc);
|
||||
goto ret_err;
|
||||
}
|
||||
pr_debug("BPIOCXSLOPE: lut=%s soc=%d batt_temp=%d slope=%d\n",
|
||||
battery->profile[bp.table_index].name,
|
||||
bp.soc, bp.batt_temp, slope);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
pr_err("IOCTL %d not supported\n", cmd);
|
||||
rc = -EINVAL;
|
||||
}
|
||||
ret_err:
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int qg_battery_data_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
pr_debug("battery_data device closed\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct file_operations qg_battery_data_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = qg_battery_data_open,
|
||||
.unlocked_ioctl = qg_battery_data_ioctl,
|
||||
.compat_ioctl = qg_battery_data_ioctl,
|
||||
.release = qg_battery_data_release,
|
||||
};
|
||||
|
||||
static int get_length(struct device_node *node,
|
||||
int *length, char *prop_name, bool ignore_null)
|
||||
{
|
||||
struct property *prop;
|
||||
|
||||
prop = of_find_property(node, prop_name, NULL);
|
||||
if (!prop) {
|
||||
if (ignore_null) {
|
||||
*length = 1;
|
||||
return 0;
|
||||
}
|
||||
pr_err("Failed to find %s property\n", prop_name);
|
||||
return -ENODATA;
|
||||
} else if (!prop->value) {
|
||||
pr_err("Failed to find value for %s property\n", prop_name);
|
||||
return -ENODATA;
|
||||
}
|
||||
|
||||
*length = prop->length / sizeof(u32);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int qg_parse_battery_profile(struct qg_battery_data *battery)
|
||||
{
|
||||
int i, j, k, rows = 0, cols = 0, lut_length = 0, rc = 0;
|
||||
struct device_node *node;
|
||||
struct property *prop;
|
||||
const __be32 *data;
|
||||
|
||||
for (i = 0; i < TABLE_MAX; i++) {
|
||||
node = of_find_node_by_name(battery->profile_node,
|
||||
table[i].table_name);
|
||||
if (!node) {
|
||||
pr_err("%s table not found\n", table[i].table_name);
|
||||
rc = -ENODEV;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
rc = get_length(node, &cols, "qcom,lut-col-legend", false);
|
||||
if (rc < 0) {
|
||||
pr_err("Failed to get col-length for %s table rc=%d\n",
|
||||
table[i].table_name, rc);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
rc = get_length(node, &rows, "qcom,lut-row-legend", true);
|
||||
if (rc < 0) {
|
||||
pr_err("Failed to get row-length for %s table rc=%d\n",
|
||||
table[i].table_name, rc);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
rc = get_length(node, &lut_length, "qcom,lut-data", false);
|
||||
if (rc < 0) {
|
||||
pr_err("Failed to get lut-length for %s table rc=%d\n",
|
||||
table[i].table_name, rc);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (lut_length != cols * rows) {
|
||||
pr_err("Invalid lut-length for %s table\n",
|
||||
table[i].table_name);
|
||||
rc = -EINVAL;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
battery->profile[i].name = kzalloc(strlen(table[i].table_name)
|
||||
+ 1, GFP_KERNEL);
|
||||
if (!battery->profile[i].name) {
|
||||
rc = -ENOMEM;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
strlcpy(battery->profile[i].name, table[i].table_name,
|
||||
strlen(table[i].table_name));
|
||||
battery->profile[i].rows = rows;
|
||||
battery->profile[i].cols = cols;
|
||||
|
||||
if (rows != 1) {
|
||||
battery->profile[i].row_entries = kcalloc(rows,
|
||||
sizeof(*battery->profile[i].row_entries),
|
||||
GFP_KERNEL);
|
||||
if (!battery->profile[i].row_entries) {
|
||||
rc = -ENOMEM;
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
battery->profile[i].col_entries = kcalloc(cols,
|
||||
sizeof(*battery->profile[i].col_entries),
|
||||
GFP_KERNEL);
|
||||
if (!battery->profile[i].col_entries) {
|
||||
rc = -ENOMEM;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
battery->profile[i].data = kcalloc(rows,
|
||||
sizeof(*battery->profile[i].data), GFP_KERNEL);
|
||||
if (!battery->profile[i].data) {
|
||||
rc = -ENOMEM;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
for (j = 0; j < rows; j++) {
|
||||
battery->profile[i].data[j] = kcalloc(cols,
|
||||
sizeof(**battery->profile[i].data),
|
||||
GFP_KERNEL);
|
||||
if (!battery->profile[i].data[j]) {
|
||||
rc = -ENOMEM;
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
/* read profile data */
|
||||
rc = of_property_read_u32_array(node, "qcom,lut-col-legend",
|
||||
battery->profile[i].col_entries, cols);
|
||||
if (rc < 0) {
|
||||
pr_err("Failed to read cols values for table %s rc=%d\n",
|
||||
table[i].table_name, rc);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (rows != 1) {
|
||||
rc = of_property_read_u32_array(node,
|
||||
"qcom,lut-row-legend",
|
||||
battery->profile[i].row_entries, rows);
|
||||
if (rc < 0) {
|
||||
pr_err("Failed to read row values for table %s rc=%d\n",
|
||||
table[i].table_name, rc);
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
prop = of_find_property(node, "qcom,lut-data", NULL);
|
||||
if (!prop) {
|
||||
pr_err("Failed to find lut-data\n");
|
||||
rc = -EINVAL;
|
||||
goto cleanup;
|
||||
}
|
||||
data = prop->value;
|
||||
for (j = 0; j < rows; j++) {
|
||||
for (k = 0; k < cols; k++)
|
||||
battery->profile[i].data[j][k] =
|
||||
be32_to_cpup(data++);
|
||||
}
|
||||
|
||||
pr_debug("Profile table %s parsed rows=%d cols=%d\n",
|
||||
battery->profile[i].name, battery->profile[i].rows,
|
||||
battery->profile[i].cols);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
cleanup:
|
||||
for (; i >= 0; i++) {
|
||||
kfree(battery->profile[i].name);
|
||||
kfree(battery->profile[i].row_entries);
|
||||
kfree(battery->profile[i].col_entries);
|
||||
for (j = 0; j < battery->profile[i].rows; j++) {
|
||||
if (battery->profile[i].data)
|
||||
kfree(battery->profile[i].data[j]);
|
||||
}
|
||||
kfree(battery->profile[i].data);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
int lookup_soc_ocv(u32 *soc, u32 ocv_uv, int batt_temp, bool charging)
|
||||
{
|
||||
u8 table_index = charging ? TABLE_SOC_OCV1 : TABLE_SOC_OCV2;
|
||||
|
||||
if (!the_battery || !the_battery->profile_node)
|
||||
return -ENODEV;
|
||||
|
||||
*soc = qg_interpolate_soc(&the_battery->profile[table_index],
|
||||
batt_temp, UV_TO_DECIUV(ocv_uv));
|
||||
|
||||
*soc = CAP(0, 100, DIV_ROUND_CLOSEST(*soc, 100));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int qg_get_nominal_capacity(u32 *nom_cap_uah, int batt_temp, bool charging)
|
||||
{
|
||||
u8 table_index = charging ? TABLE_FCC1 : TABLE_FCC2;
|
||||
u32 fcc_mah;
|
||||
|
||||
if (!the_battery || !the_battery->profile_node)
|
||||
return -ENODEV;
|
||||
|
||||
fcc_mah = qg_interpolate_single_row_lut(
|
||||
&the_battery->profile[table_index],
|
||||
batt_temp, DEGC_SCALE);
|
||||
fcc_mah = CAP(QG_MIN_FCC_MAH, QG_MAX_FCC_MAH, fcc_mah);
|
||||
|
||||
*nom_cap_uah = fcc_mah * 1000;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int qg_batterydata_init(struct device_node *profile_node)
|
||||
{
|
||||
int rc = 0;
|
||||
struct qg_battery_data *battery;
|
||||
|
||||
/*
|
||||
* If a battery profile is already initialized, free the existing
|
||||
* profile data and re-allocate and load the new profile. This is
|
||||
* required for multi-profile load support.
|
||||
*/
|
||||
if (the_battery) {
|
||||
battery = the_battery;
|
||||
battery->profile_node = NULL;
|
||||
qg_battery_profile_free();
|
||||
} else {
|
||||
battery = kzalloc(sizeof(*battery), GFP_KERNEL);
|
||||
if (!battery)
|
||||
return -ENOMEM;
|
||||
/* char device to access battery-profile data */
|
||||
rc = alloc_chrdev_region(&battery->dev_no, 0, 1,
|
||||
"qg_battery");
|
||||
if (rc < 0) {
|
||||
pr_err("Failed to allocate chrdev rc=%d\n", rc);
|
||||
goto free_battery;
|
||||
}
|
||||
|
||||
cdev_init(&battery->battery_cdev, &qg_battery_data_fops);
|
||||
rc = cdev_add(&battery->battery_cdev,
|
||||
battery->dev_no, 1);
|
||||
if (rc) {
|
||||
pr_err("Failed to add battery_cdev rc=%d\n", rc);
|
||||
goto unregister_chrdev;
|
||||
}
|
||||
|
||||
battery->battery_class = class_create(THIS_MODULE,
|
||||
"qg_battery");
|
||||
if (IS_ERR_OR_NULL(battery->battery_class)) {
|
||||
pr_err("Failed to create qg-battery class\n");
|
||||
rc = -ENODEV;
|
||||
goto delete_cdev;
|
||||
}
|
||||
|
||||
battery->battery_device = device_create(
|
||||
battery->battery_class,
|
||||
NULL, battery->dev_no,
|
||||
NULL, "qg_battery");
|
||||
if (IS_ERR_OR_NULL(battery->battery_device)) {
|
||||
pr_err("Failed to create battery_device device\n");
|
||||
rc = -ENODEV;
|
||||
goto destroy_class;
|
||||
}
|
||||
the_battery = battery;
|
||||
}
|
||||
|
||||
battery->profile_node = profile_node;
|
||||
/* parse the battery profile */
|
||||
rc = qg_parse_battery_profile(battery);
|
||||
if (rc < 0) {
|
||||
pr_err("Failed to parse battery profile rc=%d\n", rc);
|
||||
goto destroy_device;
|
||||
}
|
||||
|
||||
pr_info("QG Battery-profile loaded\n");
|
||||
|
||||
return 0;
|
||||
|
||||
destroy_device:
|
||||
device_destroy(battery->battery_class, battery->dev_no);
|
||||
destroy_class:
|
||||
class_destroy(battery->battery_class);
|
||||
delete_cdev:
|
||||
cdev_del(&battery->battery_cdev);
|
||||
unregister_chrdev:
|
||||
unregister_chrdev_region(battery->dev_no, 1);
|
||||
free_battery:
|
||||
kfree(battery);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void qg_battery_profile_free(void)
|
||||
{
|
||||
int i, j;
|
||||
|
||||
/* delete all the battery profile memory */
|
||||
for (i = 0; i < TABLE_MAX; i++) {
|
||||
kfree(the_battery->profile[i].name);
|
||||
kfree(the_battery->profile[i].row_entries);
|
||||
kfree(the_battery->profile[i].col_entries);
|
||||
for (j = 0; j < the_battery->profile[i].rows; j++) {
|
||||
if (the_battery->profile[i].data)
|
||||
kfree(the_battery->profile[i].data[j]);
|
||||
}
|
||||
kfree(the_battery->profile[i].data);
|
||||
}
|
||||
}
|
||||
|
||||
void qg_batterydata_exit(void)
|
||||
{
|
||||
if (the_battery) {
|
||||
/* unregister the device node */
|
||||
device_destroy(the_battery->battery_class, the_battery->dev_no);
|
||||
class_destroy(the_battery->battery_class);
|
||||
cdev_del(&the_battery->battery_cdev);
|
||||
unregister_chrdev_region(the_battery->dev_no, 1);
|
||||
qg_battery_profile_free();
|
||||
}
|
||||
|
||||
kfree(the_battery);
|
||||
the_battery = NULL;
|
||||
}
|
14
drivers/power/supply/qcom/qg-battery-profile.h
Normal file
14
drivers/power/supply/qcom/qg-battery-profile.h
Normal file
@ -0,0 +1,14 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (c) 2018, 2020, The Linux Foundation. All rights reserved.
|
||||
*/
|
||||
|
||||
#ifndef __QG_BATTERY_PROFILE_H__
|
||||
#define __QG_BATTERY_PROFILE_H__
|
||||
|
||||
int qg_batterydata_init(struct device_node *node);
|
||||
void qg_batterydata_exit(void);
|
||||
int lookup_soc_ocv(u32 *soc, u32 ocv_uv, int batt_temp, bool charging);
|
||||
int qg_get_nominal_capacity(u32 *nom_cap_uah, int batt_temp, bool charging);
|
||||
|
||||
#endif /* __QG_BATTERY_PROFILE_H__ */
|
271
drivers/power/supply/qcom/qg-core.h
Normal file
271
drivers/power/supply/qcom/qg-core.h
Normal file
@ -0,0 +1,271 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (c) 2018-2020 The Linux Foundation. All rights reserved.
|
||||
*/
|
||||
|
||||
#ifndef __QG_CORE_H__
|
||||
#define __QG_CORE_H__
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include "fg-alg.h"
|
||||
#include "qg-defs.h"
|
||||
|
||||
struct qg_batt_props {
|
||||
const char *batt_type_str;
|
||||
int float_volt_uv;
|
||||
int vbatt_full_mv;
|
||||
int fastchg_curr_ma;
|
||||
int qg_profile_version;
|
||||
};
|
||||
|
||||
struct qg_irq_info {
|
||||
const char *name;
|
||||
const irq_handler_t handler;
|
||||
const bool wake;
|
||||
int irq;
|
||||
};
|
||||
|
||||
struct qg_dt {
|
||||
int vbatt_empty_mv;
|
||||
int vbatt_empty_cold_mv;
|
||||
int vbatt_low_mv;
|
||||
int vbatt_low_cold_mv;
|
||||
int vbatt_cutoff_mv;
|
||||
int iterm_ma;
|
||||
int s2_fifo_length;
|
||||
int s2_vbat_low_fifo_length;
|
||||
int s2_acc_length;
|
||||
int s2_acc_intvl_ms;
|
||||
int sleep_s2_fifo_length;
|
||||
int sleep_s2_acc_length;
|
||||
int sleep_s2_acc_intvl_ms;
|
||||
int fast_chg_s2_fifo_length;
|
||||
int ocv_timer_expiry_min;
|
||||
int ocv_tol_threshold_uv;
|
||||
int s3_entry_fifo_length;
|
||||
int s3_entry_ibat_ua;
|
||||
int s3_exit_ibat_ua;
|
||||
int delta_soc;
|
||||
int rbat_conn_mohm;
|
||||
int ignore_shutdown_soc_secs;
|
||||
int shutdown_temp_diff;
|
||||
int cold_temp_threshold;
|
||||
int esr_qual_i_ua;
|
||||
int esr_qual_v_uv;
|
||||
int esr_disable_soc;
|
||||
int esr_min_ibat_ua;
|
||||
int shutdown_soc_threshold;
|
||||
int min_sleep_time_secs;
|
||||
int sys_min_volt_mv;
|
||||
int fvss_vbat_mv;
|
||||
int tcss_entry_soc;
|
||||
bool hold_soc_while_full;
|
||||
bool linearize_soc;
|
||||
bool cl_disable;
|
||||
bool cl_feedback_on;
|
||||
bool esr_disable;
|
||||
bool esr_discharge_enable;
|
||||
bool qg_ext_sense;
|
||||
bool use_s7_ocv;
|
||||
bool qg_sleep_config;
|
||||
bool qg_fast_chg_cfg;
|
||||
bool fvss_enable;
|
||||
bool multi_profile_load;
|
||||
bool tcss_enable;
|
||||
bool bass_enable;
|
||||
};
|
||||
|
||||
struct qg_esr_data {
|
||||
u32 pre_esr_v;
|
||||
u32 pre_esr_i;
|
||||
u32 post_esr_v;
|
||||
u32 post_esr_i;
|
||||
u32 esr;
|
||||
bool valid;
|
||||
};
|
||||
|
||||
struct qpnp_qg {
|
||||
struct device *dev;
|
||||
struct pmic_revid_data *pmic_rev_id;
|
||||
struct regmap *regmap;
|
||||
struct qpnp_vadc_chip *vadc_dev;
|
||||
struct soh_profile *sp;
|
||||
struct power_supply *qg_psy;
|
||||
struct class *qg_class;
|
||||
struct device *qg_device;
|
||||
struct cdev qg_cdev;
|
||||
struct device_node *batt_node;
|
||||
struct dentry *dfs_root;
|
||||
dev_t dev_no;
|
||||
struct work_struct udata_work;
|
||||
struct work_struct scale_soc_work;
|
||||
struct work_struct qg_status_change_work;
|
||||
struct delayed_work qg_sleep_exit_work;
|
||||
struct notifier_block nb;
|
||||
struct mutex bus_lock;
|
||||
struct mutex data_lock;
|
||||
struct mutex soc_lock;
|
||||
wait_queue_head_t qg_wait_q;
|
||||
struct votable *awake_votable;
|
||||
struct votable *vbatt_irq_disable_votable;
|
||||
struct votable *fifo_irq_disable_votable;
|
||||
struct votable *good_ocv_irq_disable_votable;
|
||||
u32 qg_base;
|
||||
u8 qg_subtype;
|
||||
u8 qg_mode;
|
||||
|
||||
/* local data variables */
|
||||
u32 batt_id_ohm;
|
||||
struct qg_kernel_data kdata;
|
||||
struct qg_user_data udata;
|
||||
struct power_supply *batt_psy;
|
||||
struct power_supply *usb_psy;
|
||||
struct power_supply *dc_psy;
|
||||
struct power_supply *parallel_psy;
|
||||
struct qg_esr_data esr_data[QG_MAX_ESR_COUNT];
|
||||
|
||||
/* status variable */
|
||||
u32 *debug_mask;
|
||||
u32 qg_version;
|
||||
bool qg_device_open;
|
||||
bool profile_loaded;
|
||||
bool battery_missing;
|
||||
bool data_ready;
|
||||
bool suspend_data;
|
||||
bool vbat_low;
|
||||
bool charge_done;
|
||||
bool parallel_enabled;
|
||||
bool usb_present;
|
||||
bool dc_present;
|
||||
bool charge_full;
|
||||
bool force_soc;
|
||||
bool fvss_active;
|
||||
bool tcss_active;
|
||||
bool bass_active;
|
||||
int charge_status;
|
||||
int charge_type;
|
||||
int chg_iterm_ma;
|
||||
int next_wakeup_ms;
|
||||
int esr_actual;
|
||||
int esr_nominal;
|
||||
int soh;
|
||||
int soc_reporting_ready;
|
||||
int last_fifo_v_uv;
|
||||
int last_fifo_i_ua;
|
||||
int prev_fifo_i_ua;
|
||||
int soc_tcss_entry;
|
||||
int ibat_tcss_entry;
|
||||
int soc_tcss;
|
||||
int tcss_entry_count;
|
||||
int max_fcc_limit_ma;
|
||||
int bsoc_bass_entry;
|
||||
int qg_v_ibat;
|
||||
u32 fifo_done_count;
|
||||
u32 wa_flags;
|
||||
u32 seq_no;
|
||||
u32 charge_counter_uah;
|
||||
u32 esr_avg;
|
||||
u32 esr_last;
|
||||
u32 s2_state;
|
||||
u32 s2_state_mask;
|
||||
u32 soc_fvss_entry;
|
||||
u32 vbat_fvss_entry;
|
||||
u32 max_fifo_length;
|
||||
ktime_t last_user_update_time;
|
||||
ktime_t last_fifo_update_time;
|
||||
unsigned long last_maint_soc_update_time;
|
||||
unsigned long suspend_time;
|
||||
struct iio_channel *batt_therm_chan;
|
||||
struct iio_channel *batt_id_chan;
|
||||
|
||||
/* soc params */
|
||||
int catch_up_soc;
|
||||
int maint_soc;
|
||||
int msoc;
|
||||
int pon_soc;
|
||||
int batt_soc;
|
||||
int cc_soc;
|
||||
int full_soc;
|
||||
int sys_soc;
|
||||
int last_adj_ssoc;
|
||||
int recharge_soc;
|
||||
int batt_age_level;
|
||||
struct alarm alarm_timer;
|
||||
u32 sdam_data[SDAM_MAX];
|
||||
|
||||
/* DT */
|
||||
struct qg_dt dt;
|
||||
struct qg_batt_props bp;
|
||||
/* capacity learning */
|
||||
struct cap_learning *cl;
|
||||
/* charge counter */
|
||||
struct cycle_counter *counter;
|
||||
/* ttf */
|
||||
struct ttf *ttf;
|
||||
};
|
||||
|
||||
struct ocv_all {
|
||||
u32 ocv_uv;
|
||||
u32 ocv_raw;
|
||||
char ocv_type[20];
|
||||
};
|
||||
|
||||
enum ocv_type {
|
||||
S7_PON_OCV,
|
||||
S3_GOOD_OCV,
|
||||
S3_LAST_OCV,
|
||||
SDAM_PON_OCV,
|
||||
PON_OCV_MAX,
|
||||
};
|
||||
|
||||
enum s2_state {
|
||||
S2_FAST_CHARGING = BIT(0),
|
||||
S2_LOW_VBAT = BIT(1),
|
||||
S2_SLEEP = BIT(2),
|
||||
S2_DEFAULT = BIT(3),
|
||||
};
|
||||
|
||||
enum debug_mask {
|
||||
QG_DEBUG_PON = BIT(0),
|
||||
QG_DEBUG_PROFILE = BIT(1),
|
||||
QG_DEBUG_DEVICE = BIT(2),
|
||||
QG_DEBUG_STATUS = BIT(3),
|
||||
QG_DEBUG_FIFO = BIT(4),
|
||||
QG_DEBUG_IRQ = BIT(5),
|
||||
QG_DEBUG_SOC = BIT(6),
|
||||
QG_DEBUG_PM = BIT(7),
|
||||
QG_DEBUG_BUS_READ = BIT(8),
|
||||
QG_DEBUG_BUS_WRITE = BIT(9),
|
||||
QG_DEBUG_ALG_CL = BIT(10),
|
||||
QG_DEBUG_ESR = BIT(11),
|
||||
};
|
||||
|
||||
enum qg_irq {
|
||||
QG_BATT_MISSING_IRQ,
|
||||
QG_VBATT_LOW_IRQ,
|
||||
QG_VBATT_EMPTY_IRQ,
|
||||
QG_FIFO_UPDATE_DONE_IRQ,
|
||||
QG_GOOD_OCV_IRQ,
|
||||
QG_FSM_STAT_CHG_IRQ,
|
||||
QG_EVENT_IRQ,
|
||||
QG_MAX_IRQ,
|
||||
};
|
||||
|
||||
enum qg_wa_flags {
|
||||
QG_VBAT_LOW_WA = BIT(0),
|
||||
QG_RECHARGE_SOC_WA = BIT(1),
|
||||
QG_CLK_ADJUST_WA = BIT(2),
|
||||
QG_PON_OCV_WA = BIT(3),
|
||||
};
|
||||
|
||||
enum qg_version {
|
||||
QG_PMIC5,
|
||||
QG_LITE,
|
||||
};
|
||||
|
||||
enum qg_mode {
|
||||
QG_V_I_MODE,
|
||||
QG_V_MODE,
|
||||
};
|
||||
|
||||
#endif /* __QG_CORE_H__ */
|
51
drivers/power/supply/qcom/qg-defs.h
Normal file
51
drivers/power/supply/qcom/qg-defs.h
Normal file
@ -0,0 +1,51 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (c) 2018-2020 The Linux Foundation. All rights reserved.
|
||||
*/
|
||||
|
||||
#ifndef __QG_DEFS_H__
|
||||
#define __QG_DEFS_H__
|
||||
|
||||
#define qg_dbg(chip, reason, fmt, ...) \
|
||||
do { \
|
||||
if (*chip->debug_mask & (reason)) \
|
||||
pr_info(fmt, ##__VA_ARGS__); \
|
||||
else \
|
||||
pr_debug(fmt, ##__VA_ARGS__); \
|
||||
} while (0)
|
||||
|
||||
#define is_between(left, right, value) \
|
||||
(((left) >= (right) && (left) >= (value) \
|
||||
&& (value) >= (right)) \
|
||||
|| ((left) <= (right) && (left) <= (value) \
|
||||
&& (value) <= (right)))
|
||||
|
||||
#define UDATA_READY_VOTER "UDATA_READY_VOTER"
|
||||
#define FIFO_DONE_VOTER "FIFO_DONE_VOTER"
|
||||
#define FIFO_RT_DONE_VOTER "FIFO_RT_DONE_VOTER"
|
||||
#define SUSPEND_DATA_VOTER "SUSPEND_DATA_VOTER"
|
||||
#define GOOD_OCV_VOTER "GOOD_OCV_VOTER"
|
||||
#define PROFILE_IRQ_DISABLE "NO_PROFILE_IRQ_DISABLE"
|
||||
#define QG_INIT_STATE_IRQ_DISABLE "QG_INIT_STATE_IRQ_DISABLE"
|
||||
#define TTF_AWAKE_VOTER "TTF_AWAKE_VOTER"
|
||||
#define SLEEP_EXIT_DATA_VOTER "SLEEP_EXIT_DATA_VOTER"
|
||||
#define SLEEP_EXIT_VOTER "SLEEP_EXIT_VOTER"
|
||||
|
||||
#define V_RAW_TO_UV(V_RAW) div_u64(194637ULL * (u64)V_RAW, 1000)
|
||||
#define FIFO_V_RESET_VAL 0x8000
|
||||
#define FIFO_I_RESET_VAL 0x8000
|
||||
|
||||
#define DEGC_SCALE 10
|
||||
#define UV_TO_DECIUV(a) (a / 100)
|
||||
#define DECIUV_TO_UV(a) (a * 100)
|
||||
|
||||
#define QG_MAX_ESR_COUNT 10
|
||||
#define QG_MIN_ESR_COUNT 2
|
||||
|
||||
#define CAP(min, max, value) \
|
||||
((min > value) ? min : ((value > max) ? max : value))
|
||||
|
||||
#define QG_SOC_FULL 10000
|
||||
#define BATT_SOC_32BIT GENMASK(31, 0)
|
||||
|
||||
#endif /* __QG_DEFS_H__ */
|
304
drivers/power/supply/qcom/qg-profile-lib.c
Normal file
304
drivers/power/supply/qcom/qg-profile-lib.c
Normal file
@ -0,0 +1,304 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2018-2020 The Linux Foundation. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/printk.h>
|
||||
#include <linux/ratelimit.h>
|
||||
#include "qg-profile-lib.h"
|
||||
#include "qg-defs.h"
|
||||
|
||||
int qg_linear_interpolate(int y0, int x0, int y1, int x1, int x)
|
||||
{
|
||||
if (y0 == y1 || x == x0)
|
||||
return y0;
|
||||
if (x1 == x0 || x == x1)
|
||||
return y1;
|
||||
|
||||
return y0 + ((y1 - y0) * (x - x0) / (x1 - x0));
|
||||
}
|
||||
|
||||
int qg_interpolate_single_row_lut(struct profile_table_data *lut,
|
||||
int x, int scale)
|
||||
{
|
||||
int i, result;
|
||||
int cols = lut->cols;
|
||||
|
||||
if (x < lut->col_entries[0] * scale) {
|
||||
pr_debug("x %d less than known range return y = %d lut = %s\n",
|
||||
x, lut->data[0][0], lut->name);
|
||||
return lut->data[0][0];
|
||||
}
|
||||
|
||||
if (x > lut->col_entries[cols-1] * scale) {
|
||||
pr_debug("x %d more than known range return y = %d lut = %s\n",
|
||||
x, lut->data[0][cols-1], lut->name);
|
||||
return lut->data[0][cols-1];
|
||||
}
|
||||
|
||||
for (i = 0; i < cols; i++) {
|
||||
if (x <= lut->col_entries[i] * scale)
|
||||
break;
|
||||
}
|
||||
|
||||
if (x == lut->col_entries[i] * scale) {
|
||||
result = lut->data[0][i];
|
||||
} else {
|
||||
result = qg_linear_interpolate(
|
||||
lut->data[0][i-1],
|
||||
lut->col_entries[i-1] * scale,
|
||||
lut->data[0][i],
|
||||
lut->col_entries[i] * scale,
|
||||
x);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int qg_interpolate_soc(struct profile_table_data *lut,
|
||||
int batt_temp, int ocv)
|
||||
{
|
||||
int i, j, soc_high, soc_low, soc;
|
||||
int rows = lut->rows;
|
||||
int cols = lut->cols;
|
||||
|
||||
if (batt_temp < lut->col_entries[0] * DEGC_SCALE) {
|
||||
pr_debug("batt_temp %d < known temp range\n", batt_temp);
|
||||
batt_temp = lut->col_entries[0] * DEGC_SCALE;
|
||||
}
|
||||
|
||||
if (batt_temp > lut->col_entries[cols - 1] * DEGC_SCALE) {
|
||||
pr_debug("batt_temp %d > known temp range\n", batt_temp);
|
||||
batt_temp = lut->col_entries[cols - 1] * DEGC_SCALE;
|
||||
}
|
||||
|
||||
for (j = 0; j < cols; j++)
|
||||
if (batt_temp <= lut->col_entries[j] * DEGC_SCALE)
|
||||
break;
|
||||
|
||||
if (batt_temp == lut->col_entries[j] * DEGC_SCALE) {
|
||||
/* found an exact match for temp in the table */
|
||||
if (ocv >= lut->data[0][j])
|
||||
return lut->row_entries[0];
|
||||
if (ocv <= lut->data[rows - 1][j])
|
||||
return lut->row_entries[rows - 1];
|
||||
for (i = 0; i < rows; i++) {
|
||||
if (ocv >= lut->data[i][j]) {
|
||||
if (ocv == lut->data[i][j])
|
||||
return lut->row_entries[i];
|
||||
soc = qg_linear_interpolate(
|
||||
lut->row_entries[i],
|
||||
lut->data[i][j],
|
||||
lut->row_entries[i - 1],
|
||||
lut->data[i - 1][j],
|
||||
ocv);
|
||||
return soc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* batt_temp is within temperature for column j-1 and j */
|
||||
if (ocv >= lut->data[0][j])
|
||||
return lut->row_entries[0];
|
||||
if (ocv <= lut->data[rows - 1][j - 1])
|
||||
return lut->row_entries[rows - 1];
|
||||
|
||||
soc_low = soc_high = 0;
|
||||
for (i = 0; i < rows-1; i++) {
|
||||
if (soc_high == 0 && is_between(lut->data[i][j],
|
||||
lut->data[i+1][j], ocv)) {
|
||||
soc_high = qg_linear_interpolate(
|
||||
lut->row_entries[i],
|
||||
lut->data[i][j],
|
||||
lut->row_entries[i + 1],
|
||||
lut->data[i+1][j],
|
||||
ocv);
|
||||
}
|
||||
|
||||
if (soc_low == 0 && is_between(lut->data[i][j-1],
|
||||
lut->data[i+1][j-1], ocv)) {
|
||||
soc_low = qg_linear_interpolate(
|
||||
lut->row_entries[i],
|
||||
lut->data[i][j-1],
|
||||
lut->row_entries[i + 1],
|
||||
lut->data[i+1][j-1],
|
||||
ocv);
|
||||
}
|
||||
|
||||
if (soc_high && soc_low) {
|
||||
soc = qg_linear_interpolate(
|
||||
soc_low,
|
||||
lut->col_entries[j-1] * DEGC_SCALE,
|
||||
soc_high,
|
||||
lut->col_entries[j] * DEGC_SCALE,
|
||||
batt_temp);
|
||||
return soc;
|
||||
}
|
||||
}
|
||||
|
||||
if (soc_high)
|
||||
return soc_high;
|
||||
|
||||
if (soc_low)
|
||||
return soc_low;
|
||||
|
||||
pr_debug("%d ocv wasn't found for temp %d in the LUT %s returning 100%%\n",
|
||||
ocv, batt_temp, lut->name);
|
||||
return 10000;
|
||||
}
|
||||
|
||||
int qg_interpolate_var(struct profile_table_data *lut,
|
||||
int batt_temp, int soc)
|
||||
{
|
||||
int i, var1, var2, var, rows, cols;
|
||||
int row1 = 0;
|
||||
int row2 = 0;
|
||||
|
||||
rows = lut->rows;
|
||||
cols = lut->cols;
|
||||
if (soc > lut->row_entries[0]) {
|
||||
pr_debug("soc %d greater than known soc ranges for %s lut\n",
|
||||
soc, lut->name);
|
||||
row1 = 0;
|
||||
row2 = 0;
|
||||
} else if (soc < lut->row_entries[rows - 1]) {
|
||||
pr_debug("soc %d less than known soc ranges for %s lut\n",
|
||||
soc, lut->name);
|
||||
row1 = rows - 1;
|
||||
row2 = rows - 1;
|
||||
} else {
|
||||
for (i = 0; i < rows; i++) {
|
||||
if (soc == lut->row_entries[i]) {
|
||||
row1 = i;
|
||||
row2 = i;
|
||||
break;
|
||||
}
|
||||
if (soc > lut->row_entries[i]) {
|
||||
row1 = i - 1;
|
||||
row2 = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (batt_temp < lut->col_entries[0] * DEGC_SCALE)
|
||||
batt_temp = lut->col_entries[0] * DEGC_SCALE;
|
||||
if (batt_temp > lut->col_entries[cols - 1] * DEGC_SCALE)
|
||||
batt_temp = lut->col_entries[cols - 1] * DEGC_SCALE;
|
||||
|
||||
for (i = 0; i < cols; i++)
|
||||
if (batt_temp <= lut->col_entries[i] * DEGC_SCALE)
|
||||
break;
|
||||
|
||||
if (batt_temp == lut->col_entries[i] * DEGC_SCALE) {
|
||||
var = qg_linear_interpolate(
|
||||
lut->data[row1][i],
|
||||
lut->row_entries[row1],
|
||||
lut->data[row2][i],
|
||||
lut->row_entries[row2],
|
||||
soc);
|
||||
return var;
|
||||
}
|
||||
|
||||
var1 = qg_linear_interpolate(
|
||||
lut->data[row1][i - 1],
|
||||
lut->col_entries[i - 1] * DEGC_SCALE,
|
||||
lut->data[row1][i],
|
||||
lut->col_entries[i] * DEGC_SCALE,
|
||||
batt_temp);
|
||||
|
||||
var2 = qg_linear_interpolate(
|
||||
lut->data[row2][i - 1],
|
||||
lut->col_entries[i - 1] * DEGC_SCALE,
|
||||
lut->data[row2][i],
|
||||
lut->col_entries[i] * DEGC_SCALE,
|
||||
batt_temp);
|
||||
|
||||
var = qg_linear_interpolate(
|
||||
var1,
|
||||
lut->row_entries[row1],
|
||||
var2,
|
||||
lut->row_entries[row2],
|
||||
soc);
|
||||
|
||||
return var;
|
||||
}
|
||||
|
||||
int qg_interpolate_slope(struct profile_table_data *lut,
|
||||
int batt_temp, int soc)
|
||||
{
|
||||
int i, ocvrow1, ocvrow2, rows, cols;
|
||||
int row1 = 0;
|
||||
int row2 = 0;
|
||||
int slope;
|
||||
|
||||
rows = lut->rows;
|
||||
cols = lut->cols;
|
||||
if (soc >= lut->row_entries[0]) {
|
||||
pr_debug("soc %d >= max soc range - use the slope at soc=%d for lut %s\n",
|
||||
soc, lut->row_entries[0], lut->name);
|
||||
row1 = 0;
|
||||
row2 = 1;
|
||||
} else if (soc <= lut->row_entries[rows - 1]) {
|
||||
pr_debug("soc %d is <= min soc range - use the slope at soc=%d for lut %s\n",
|
||||
soc, lut->row_entries[rows - 1], lut->name);
|
||||
row1 = rows - 2;
|
||||
row2 = rows - 1;
|
||||
} else {
|
||||
for (i = 0; i < rows; i++) {
|
||||
if (soc >= lut->row_entries[i]) {
|
||||
row1 = i - 1;
|
||||
row2 = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (batt_temp < lut->col_entries[0] * DEGC_SCALE)
|
||||
batt_temp = lut->col_entries[0] * DEGC_SCALE;
|
||||
if (batt_temp > lut->col_entries[cols - 1] * DEGC_SCALE)
|
||||
batt_temp = lut->col_entries[cols - 1] * DEGC_SCALE;
|
||||
|
||||
for (i = 0; i < cols; i++) {
|
||||
if (batt_temp <= lut->col_entries[i] * DEGC_SCALE)
|
||||
break;
|
||||
}
|
||||
|
||||
if (batt_temp == lut->col_entries[i] * DEGC_SCALE) {
|
||||
slope = (lut->data[row1][i] - lut->data[row2][i]);
|
||||
if (slope <= 0) {
|
||||
pr_warn_ratelimited("Slope=%d for soc=%d, using 1\n",
|
||||
slope, soc);
|
||||
slope = 1;
|
||||
}
|
||||
slope *= 10000;
|
||||
slope /= (lut->row_entries[row1] -
|
||||
lut->row_entries[row2]);
|
||||
return slope;
|
||||
}
|
||||
ocvrow1 = qg_linear_interpolate(
|
||||
lut->data[row1][i - 1],
|
||||
lut->col_entries[i - 1] * DEGC_SCALE,
|
||||
lut->data[row1][i],
|
||||
lut->col_entries[i] * DEGC_SCALE,
|
||||
batt_temp);
|
||||
|
||||
ocvrow2 = qg_linear_interpolate(
|
||||
lut->data[row2][i - 1],
|
||||
lut->col_entries[i - 1] * DEGC_SCALE,
|
||||
lut->data[row2][i],
|
||||
lut->col_entries[i] * DEGC_SCALE,
|
||||
batt_temp);
|
||||
|
||||
slope = (ocvrow1 - ocvrow2);
|
||||
if (slope <= 0) {
|
||||
pr_warn_ratelimited("Slope=%d for soc=%d, using 1\n",
|
||||
slope, soc);
|
||||
slope = 1;
|
||||
}
|
||||
slope *= 10000;
|
||||
slope /= (lut->row_entries[row1] - lut->row_entries[row2]);
|
||||
|
||||
return slope;
|
||||
}
|
28
drivers/power/supply/qcom/qg-profile-lib.h
Normal file
28
drivers/power/supply/qcom/qg-profile-lib.h
Normal file
@ -0,0 +1,28 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (c) 2018-2020 The Linux Foundation. All rights reserved.
|
||||
*/
|
||||
|
||||
#ifndef __QG_PROFILE_LIB_H__
|
||||
#define __QG_PROFILE_LIB_H__
|
||||
|
||||
struct profile_table_data {
|
||||
char *name;
|
||||
int rows;
|
||||
int cols;
|
||||
int *row_entries;
|
||||
int *col_entries;
|
||||
int **data;
|
||||
};
|
||||
|
||||
int qg_linear_interpolate(int y0, int x0, int y1, int x1, int x);
|
||||
int qg_interpolate_single_row_lut(struct profile_table_data *lut,
|
||||
int x, int scale);
|
||||
int qg_interpolate_soc(struct profile_table_data *lut,
|
||||
int batt_temp, int ocv);
|
||||
int qg_interpolate_var(struct profile_table_data *lut,
|
||||
int batt_temp, int soc);
|
||||
int qg_interpolate_slope(struct profile_table_data *lut,
|
||||
int batt_temp, int soc);
|
||||
|
||||
#endif /*__QG_PROFILE_LIB_H__ */
|
136
drivers/power/supply/qcom/qg-reg.h
Normal file
136
drivers/power/supply/qcom/qg-reg.h
Normal file
@ -0,0 +1,136 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (c) 2018-2020 The Linux Foundation. All rights reserved.
|
||||
*/
|
||||
|
||||
#ifndef __QG_REG_H__
|
||||
#define __QG_REG_H__
|
||||
|
||||
#define PERPH_TYPE_REG 0x04
|
||||
|
||||
#define PERPH_SUBTYPE_REG 0x05
|
||||
#define QG_ADC_IBAT_5A 0x3
|
||||
#define QG_ADC_IBAT_10A 0x4
|
||||
|
||||
#define QG_TYPE 0x0D
|
||||
|
||||
#define QG_STATUS1_REG 0x08
|
||||
#define QG_OK_BIT BIT(7)
|
||||
#define BATTERY_PRESENT_BIT BIT(0)
|
||||
#define ESR_MEAS_DONE_BIT BIT(4)
|
||||
|
||||
#define QG_STATUS2_REG 0x09
|
||||
#define BATTERY_MISSING_BIT BIT(3)
|
||||
#define GOOD_OCV_BIT BIT(1)
|
||||
|
||||
#define QG_STATUS3_REG 0x0A
|
||||
#define COUNT_FIFO_RT_MASK GENMASK(3, 0)
|
||||
|
||||
#define QG_STATUS4_REG 0x0B
|
||||
#define ESR_MEAS_IN_PROGRESS_BIT BIT(4)
|
||||
|
||||
#define QG_INT_RT_STS_REG 0x10
|
||||
#define FIFO_UPDATE_DONE_RT_STS_BIT BIT(3)
|
||||
#define VBAT_LOW_INT_RT_STS_BIT BIT(1)
|
||||
#define BATTERY_MISSING_INT_RT_STS_BIT BIT(0)
|
||||
|
||||
#define QG_INT_LATCHED_STS_REG 0x18
|
||||
#define FIFO_UPDATE_DONE_INT_LAT_STS_BIT BIT(3)
|
||||
|
||||
#define QG_STATE_TRIG_CMD_REG 0x40
|
||||
#define S7_PON_OCV_START BIT(3)
|
||||
|
||||
#define QG_DATA_CTL1_REG 0x41
|
||||
#define MASTER_HOLD_OR_CLR_BIT BIT(0)
|
||||
|
||||
#define QG_DATA_CTL2_REG 0x42
|
||||
#define BURST_AVG_HOLD_FOR_READ_BIT BIT(0)
|
||||
|
||||
#define QG_MODE_CTL1_REG 0x43
|
||||
#define PARALLEL_IBAT_SENSE_EN_BIT BIT(7)
|
||||
|
||||
#define QG_MODE_CTL2_REG 0x44
|
||||
#define VI_MODE_BIT BIT(0)
|
||||
|
||||
#define QG_VBAT_EMPTY_THRESHOLD_REG 0x4B
|
||||
#define QG_VBAT_LOW_THRESHOLD_REG 0x4C
|
||||
|
||||
#define QG_S2_NORMAL_MEAS_CTL2_REG 0x51
|
||||
#define FIFO_LENGTH_MASK GENMASK(5, 3)
|
||||
#define FIFO_LENGTH_SHIFT 3
|
||||
#define NUM_OF_ACCUM_MASK GENMASK(2, 0)
|
||||
|
||||
#define QG_S2_NORMAL_MEAS_CTL3_REG 0x52
|
||||
|
||||
#define QG_S3_SLEEP_OCV_MEAS_CTL4_REG 0x59
|
||||
#define S3_SLEEP_OCV_TIMER_MASK GENMASK(2, 0)
|
||||
|
||||
#define QG_S3_SLEEP_OCV_TREND_CTL2_REG 0x5C
|
||||
#define TREND_TOL_MASK GENMASK(5, 0)
|
||||
|
||||
#define QG_S3_SLEEP_OCV_IBAT_CTL1_REG 0x5D
|
||||
#define SLEEP_IBAT_QUALIFIED_LENGTH_MASK GENMASK(2, 0)
|
||||
|
||||
#define QG_S3_ENTRY_IBAT_THRESHOLD_REG 0x5E
|
||||
#define QG_S3_EXIT_IBAT_THRESHOLD_REG 0x5F
|
||||
|
||||
#define QG_S5_OCV_VALIDATE_MEAS_CTL1_REG 0x60
|
||||
#define ALLOW_S5_BIT BIT(7)
|
||||
|
||||
#define QG_S7_PON_OCV_MEAS_CTL1_REG 0x64
|
||||
#define ADC_CONV_DLY_MASK GENMASK(3, 0)
|
||||
|
||||
#define QG_ESR_MEAS_TRIG_REG 0x68
|
||||
#define HW_ESR_MEAS_START_BIT BIT(0)
|
||||
|
||||
#define QG_S7_PON_OCV_V_DATA0_REG 0x70
|
||||
#define QG_S7_PON_OCV_I_DATA0_REG 0x72
|
||||
#define QG_S3_GOOD_OCV_V_DATA0_REG 0x74
|
||||
#define QG_S3_GOOD_OCV_I_DATA0_REG 0x76
|
||||
|
||||
#define QG_PRE_ESR_V_DATA0_REG 0x78
|
||||
#define QG_PRE_ESR_I_DATA0_REG 0x7A
|
||||
#define QG_POST_ESR_V_DATA0_REG 0x7C
|
||||
#define QG_POST_ESR_I_DATA0_REG 0x7E
|
||||
|
||||
#define QG_S2_NORMAL_AVG_V_DATA0_REG 0x80
|
||||
#define QG_S2_NORMAL_AVG_I_DATA0_REG 0x82
|
||||
|
||||
#define QG_V_ACCUM_DATA0_RT_REG 0x88
|
||||
#define QG_I_ACCUM_DATA0_RT_REG 0x8B
|
||||
#define QG_ACCUM_CNT_RT_REG 0x8E
|
||||
|
||||
#define QG_V_FIFO0_DATA0_REG 0x90
|
||||
#define QG_I_FIFO0_DATA0_REG 0xA0
|
||||
|
||||
#define QG_SOC_MONOTONIC_REG 0xBF
|
||||
|
||||
#define QG_LAST_ADC_V_DATA0_REG 0xC0
|
||||
#define QG_LAST_ADC_I_DATA0_REG 0xC2
|
||||
|
||||
#define QG_LAST_BURST_AVG_I_DATA0_REG 0xC6
|
||||
|
||||
#define QG_LAST_S3_SLEEP_V_DATA0_REG 0xCC
|
||||
|
||||
/* SDAM offsets */
|
||||
#define QG_SDAM_VALID_OFFSET 0x46 /* 1-byte 0x46 */
|
||||
#define QG_SDAM_SOC_OFFSET 0x47 /* 1-byte 0x47 */
|
||||
#define QG_SDAM_TEMP_OFFSET 0x48 /* 2-byte 0x48-0x49 */
|
||||
#define QG_SDAM_RBAT_OFFSET 0x4A /* 2-byte 0x4A-0x4B */
|
||||
#define QG_SDAM_OCV_OFFSET 0x4C /* 4-byte 0x4C-0x4F */
|
||||
#define QG_SDAM_IBAT_OFFSET 0x50 /* 4-byte 0x50-0x53 */
|
||||
#define QG_SDAM_TIME_OFFSET 0x54 /* 4-byte 0x54-0x57 */
|
||||
#define QG_SDAM_CYCLE_COUNT_OFFSET 0x58 /* 16-byte 0x58-0x67 */
|
||||
#define QG_SDAM_LEARNED_CAPACITY_OFFSET 0x68 /* 2-byte 0x68-0x69 */
|
||||
#define QG_SDAM_ESR_CHARGE_DELTA_OFFSET 0x6A /* 4-byte 0x6A-0x6D */
|
||||
#define QG_SDAM_ESR_DISCHARGE_DELTA_OFFSET 0x6E /* 4-byte 0x6E-0x71 */
|
||||
#define QG_SDAM_ESR_CHARGE_SF_OFFSET 0x72 /* 2-byte 0x72-0x73 */
|
||||
#define QG_SDAM_ESR_DISCHARGE_SF_OFFSET 0x74 /* 2-byte 0x74-0x75 */
|
||||
#define QG_SDAM_BATT_AGE_LEVEL_OFFSET 0x76 /* 1-byte 0x76 */
|
||||
#define QG_SDAM_MAGIC_OFFSET 0x80 /* 4-byte 0x80-0x83 */
|
||||
#define QG_SDAM_MAX_OFFSET 0xA4
|
||||
|
||||
/* Below offset is used by PBS */
|
||||
#define QG_SDAM_PON_OCV_OFFSET 0xBC /* 2-byte 0xBC-0xBD */
|
||||
|
||||
#endif
|
312
drivers/power/supply/qcom/qg-sdam.c
Normal file
312
drivers/power/supply/qcom/qg-sdam.c
Normal file
@ -0,0 +1,312 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2018-2020 The Linux Foundation. All rights reserved.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) "QG-K: %s: " fmt, __func__
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/regmap.h>
|
||||
#include "qg-sdam.h"
|
||||
#include "qg-reg.h"
|
||||
|
||||
static struct qg_sdam *the_chip;
|
||||
|
||||
struct qg_sdam_info {
|
||||
char *name;
|
||||
u32 offset;
|
||||
u32 length;
|
||||
};
|
||||
|
||||
static struct qg_sdam_info sdam_info[] = {
|
||||
[SDAM_VALID] = {
|
||||
.name = "VALID",
|
||||
.offset = QG_SDAM_VALID_OFFSET,
|
||||
.length = 1,
|
||||
},
|
||||
[SDAM_SOC] = {
|
||||
.name = "SOC",
|
||||
.offset = QG_SDAM_SOC_OFFSET,
|
||||
.length = 1,
|
||||
},
|
||||
[SDAM_TEMP] = {
|
||||
.name = "BATT_TEMP",
|
||||
.offset = QG_SDAM_TEMP_OFFSET,
|
||||
.length = 2,
|
||||
},
|
||||
[SDAM_RBAT_MOHM] = {
|
||||
.name = "RBAT_MOHM",
|
||||
.offset = QG_SDAM_RBAT_OFFSET,
|
||||
.length = 2,
|
||||
},
|
||||
[SDAM_OCV_UV] = {
|
||||
.name = "OCV_UV",
|
||||
.offset = QG_SDAM_OCV_OFFSET,
|
||||
.length = 4,
|
||||
},
|
||||
[SDAM_IBAT_UA] = {
|
||||
.name = "IBAT_UA",
|
||||
.offset = QG_SDAM_IBAT_OFFSET,
|
||||
.length = 4,
|
||||
},
|
||||
[SDAM_TIME_SEC] = {
|
||||
.name = "TIME_SEC",
|
||||
.offset = QG_SDAM_TIME_OFFSET,
|
||||
.length = 4,
|
||||
},
|
||||
[SDAM_PON_OCV_UV] = {
|
||||
.name = "SDAM_PON_OCV",
|
||||
.offset = QG_SDAM_PON_OCV_OFFSET,
|
||||
.length = 2,
|
||||
},
|
||||
[SDAM_ESR_CHARGE_DELTA] = {
|
||||
.name = "SDAM_ESR_CHARGE_DELTA",
|
||||
.offset = QG_SDAM_ESR_CHARGE_DELTA_OFFSET,
|
||||
.length = 4,
|
||||
},
|
||||
[SDAM_ESR_DISCHARGE_DELTA] = {
|
||||
.name = "SDAM_ESR_DISCHARGE_DELTA",
|
||||
.offset = QG_SDAM_ESR_DISCHARGE_DELTA_OFFSET,
|
||||
.length = 4,
|
||||
},
|
||||
[SDAM_ESR_CHARGE_SF] = {
|
||||
.name = "SDAM_ESR_CHARGE_SF_OFFSET",
|
||||
.offset = QG_SDAM_ESR_CHARGE_SF_OFFSET,
|
||||
.length = 2,
|
||||
},
|
||||
[SDAM_ESR_DISCHARGE_SF] = {
|
||||
.name = "SDAM_ESR_DISCHARGE_SF_OFFSET",
|
||||
.offset = QG_SDAM_ESR_DISCHARGE_SF_OFFSET,
|
||||
.length = 2,
|
||||
},
|
||||
[SDAM_BATT_AGE_LEVEL] = {
|
||||
.name = "SDAM_BATT_AGE_LEVEL_OFFSET",
|
||||
.offset = QG_SDAM_BATT_AGE_LEVEL_OFFSET,
|
||||
.length = 1,
|
||||
},
|
||||
[SDAM_MAGIC] = {
|
||||
.name = "SDAM_MAGIC_OFFSET",
|
||||
.offset = QG_SDAM_MAGIC_OFFSET,
|
||||
.length = 4,
|
||||
},
|
||||
};
|
||||
|
||||
int qg_sdam_write(u8 param, u32 data)
|
||||
{
|
||||
int rc;
|
||||
struct qg_sdam *chip = the_chip;
|
||||
u32 offset;
|
||||
size_t length;
|
||||
|
||||
if (!chip) {
|
||||
pr_err("Invalid sdam-chip pointer\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (param >= SDAM_MAX) {
|
||||
pr_err("Invalid SDAM param %d\n", param);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
offset = chip->sdam_base + sdam_info[param].offset;
|
||||
length = sdam_info[param].length;
|
||||
rc = regmap_bulk_write(chip->regmap, offset, (u8 *)&data, length);
|
||||
if (rc < 0)
|
||||
pr_err("Failed to write offset=%0x4 param=%d value=%d\n",
|
||||
offset, param, data);
|
||||
else
|
||||
pr_debug("QG SDAM write param=%s value=%d\n",
|
||||
sdam_info[param].name, data);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int qg_sdam_read(u8 param, u32 *data)
|
||||
{
|
||||
int rc;
|
||||
struct qg_sdam *chip = the_chip;
|
||||
u32 offset;
|
||||
size_t length;
|
||||
|
||||
if (!chip) {
|
||||
pr_err("Invalid sdam-chip pointer\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (param >= SDAM_MAX) {
|
||||
pr_err("Invalid SDAM param %d\n", param);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
*data = 0;
|
||||
offset = chip->sdam_base + sdam_info[param].offset;
|
||||
length = sdam_info[param].length;
|
||||
rc = regmap_raw_read(chip->regmap, offset, (u8 *)data, length);
|
||||
if (rc < 0)
|
||||
pr_err("Failed to read offset=%0x4 param=%d\n",
|
||||
offset, param);
|
||||
else
|
||||
pr_debug("QG SDAM read param=%s value=%d\n",
|
||||
sdam_info[param].name, *data);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int qg_sdam_multibyte_write(u32 offset, u8 *data, u32 length)
|
||||
{
|
||||
int rc, i;
|
||||
struct qg_sdam *chip = the_chip;
|
||||
|
||||
if (!chip) {
|
||||
pr_err("Invalid sdam-chip pointer\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
offset = chip->sdam_base + offset;
|
||||
rc = regmap_bulk_write(chip->regmap, offset, data, (size_t)length);
|
||||
if (rc < 0) {
|
||||
pr_err("Failed to write offset=%0x4 value=%d\n",
|
||||
offset, *data);
|
||||
} else {
|
||||
for (i = 0; i < length; i++)
|
||||
pr_debug("QG SDAM write offset=%0x4 value=%d\n",
|
||||
offset++, data[i]);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int qg_sdam_multibyte_read(u32 offset, u8 *data, u32 length)
|
||||
{
|
||||
int rc, i;
|
||||
struct qg_sdam *chip = the_chip;
|
||||
|
||||
if (!chip) {
|
||||
pr_err("Invalid sdam-chip pointer\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
offset = chip->sdam_base + offset;
|
||||
rc = regmap_raw_read(chip->regmap, offset, (u8 *)data, (size_t)length);
|
||||
if (rc < 0) {
|
||||
pr_err("Failed to read offset=%0x4\n", offset);
|
||||
} else {
|
||||
for (i = 0; i < length; i++)
|
||||
pr_debug("QG SDAM read offset=%0x4 value=%d\n",
|
||||
offset++, data[i]);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int qg_sdam_read_all(u32 *sdam_data)
|
||||
{
|
||||
int i, rc;
|
||||
struct qg_sdam *chip = the_chip;
|
||||
|
||||
if (!chip) {
|
||||
pr_err("Invalid sdam-chip pointer\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
for (i = 0; i < SDAM_MAX; i++) {
|
||||
rc = qg_sdam_read(i, &sdam_data[i]);
|
||||
if (rc < 0) {
|
||||
pr_err("Failed to read SDAM param=%s rc=%d\n",
|
||||
sdam_info[i].name, rc);
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int qg_sdam_write_all(u32 *sdam_data)
|
||||
{
|
||||
int i, rc;
|
||||
struct qg_sdam *chip = the_chip;
|
||||
|
||||
if (!chip) {
|
||||
pr_err("Invalid sdam-chip pointer\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
for (i = 0; i < SDAM_MAX; i++) {
|
||||
rc = qg_sdam_write(i, sdam_data[i]);
|
||||
if (rc < 0) {
|
||||
pr_err("Failed to write SDAM param=%s rc=%d\n",
|
||||
sdam_info[i].name, rc);
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int qg_sdam_clear(void)
|
||||
{
|
||||
int i, rc = 0;
|
||||
struct qg_sdam *chip = the_chip;
|
||||
u8 data = 0;
|
||||
|
||||
if (!chip) {
|
||||
pr_err("Invalid sdam-chip pointer\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
for (i = SDAM_MIN_OFFSET; i <= SDAM_MAX_OFFSET; i++)
|
||||
rc |= qg_sdam_multibyte_write(i, &data, 1);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int qg_sdam_init(struct device *dev)
|
||||
{
|
||||
int rc;
|
||||
u32 base = 0, type = 0;
|
||||
struct qg_sdam *chip;
|
||||
struct device_node *child, *node = dev->of_node;
|
||||
|
||||
chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
|
||||
if (!chip)
|
||||
return 0;
|
||||
|
||||
chip->regmap = dev_get_regmap(dev->parent, NULL);
|
||||
if (!chip->regmap) {
|
||||
pr_err("Parent regmap is unavailable\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
/* get the SDAM base address */
|
||||
for_each_available_child_of_node(node, child) {
|
||||
rc = of_property_read_u32(child, "reg", &base);
|
||||
if (rc < 0) {
|
||||
pr_err("Failed to read base address rc=%d\n", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = regmap_read(chip->regmap, base + PERPH_TYPE_REG, &type);
|
||||
if (rc < 0) {
|
||||
pr_err("Failed to read type rc=%d\n", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case SDAM_TYPE:
|
||||
chip->sdam_base = base;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!chip->sdam_base) {
|
||||
pr_err("QG SDAM node not defined\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
the_chip = chip;
|
||||
|
||||
return 0;
|
||||
}
|
45
drivers/power/supply/qcom/qg-sdam.h
Normal file
45
drivers/power/supply/qcom/qg-sdam.h
Normal file
@ -0,0 +1,45 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (c) 2018-2020 The Linux Foundation. All rights reserved.
|
||||
*/
|
||||
|
||||
#ifndef __QG_SDAM_H__
|
||||
#define __QG_SDAM_H__
|
||||
|
||||
#define SDAM_TYPE 0x2E
|
||||
#define SDAM_MIN_OFFSET 0x45
|
||||
#define SDAM_MAX_OFFSET 0xB3
|
||||
|
||||
enum qg_sdam_param {
|
||||
SDAM_VALID,
|
||||
SDAM_SOC,
|
||||
SDAM_TEMP,
|
||||
SDAM_RBAT_MOHM,
|
||||
SDAM_OCV_UV,
|
||||
SDAM_IBAT_UA,
|
||||
SDAM_TIME_SEC,
|
||||
SDAM_PON_OCV_UV,
|
||||
SDAM_ESR_CHARGE_DELTA,
|
||||
SDAM_ESR_DISCHARGE_DELTA,
|
||||
SDAM_ESR_CHARGE_SF,
|
||||
SDAM_ESR_DISCHARGE_SF,
|
||||
SDAM_MAGIC,
|
||||
SDAM_BATT_AGE_LEVEL,
|
||||
SDAM_MAX,
|
||||
};
|
||||
|
||||
struct qg_sdam {
|
||||
struct regmap *regmap;
|
||||
u16 sdam_base;
|
||||
};
|
||||
|
||||
int qg_sdam_init(struct device *dev);
|
||||
int qg_sdam_write(u8 param, u32 data);
|
||||
int qg_sdam_read(u8 param, u32 *data);
|
||||
int qg_sdam_write_all(u32 *sdam_data);
|
||||
int qg_sdam_read_all(u32 *sdam_data);
|
||||
int qg_sdam_multibyte_write(u32 offset, u8 *sdam_data, u32 length);
|
||||
int qg_sdam_multibyte_read(u32 offset, u8 *sdam_data, u32 length);
|
||||
int qg_sdam_clear(void);
|
||||
|
||||
#endif
|
703
drivers/power/supply/qcom/qg-soc.c
Normal file
703
drivers/power/supply/qcom/qg-soc.c
Normal file
@ -0,0 +1,703 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2018-2020 The Linux Foundation. All rights reserved.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) "QG-K: %s: " fmt, __func__
|
||||
|
||||
#include <linux/alarmtimer.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/power_supply.h>
|
||||
#include <uapi/linux/qg.h>
|
||||
#include <uapi/linux/qg-profile.h>
|
||||
#include "fg-alg.h"
|
||||
#include "qg-sdam.h"
|
||||
#include "qg-core.h"
|
||||
#include "qg-reg.h"
|
||||
#include "qg-util.h"
|
||||
#include "qg-defs.h"
|
||||
#include "qg-profile-lib.h"
|
||||
#include "qg-soc.h"
|
||||
|
||||
enum soc_scaling_feature {
|
||||
QG_FVSS = BIT(0),
|
||||
QG_TCSS = BIT(1),
|
||||
QG_BASS = BIT(2),
|
||||
};
|
||||
|
||||
#define DEFAULT_UPDATE_TIME_MS 64000
|
||||
#define SOC_SCALE_HYST_MS 2000
|
||||
#define VBAT_LOW_HYST_UV 50000
|
||||
#define FULL_SOC 100
|
||||
|
||||
static int qg_ss_feature;
|
||||
static ssize_t qg_ss_feature_show(struct device *dev, struct device_attribute
|
||||
*attr, char *buf)
|
||||
{
|
||||
return scnprintf(buf, PAGE_SIZE, "0x%4x\n", qg_ss_feature);
|
||||
}
|
||||
|
||||
static ssize_t qg_ss_feature_store(struct device *dev,
|
||||
struct device_attribute *attr, const char *buf, size_t count)
|
||||
{
|
||||
int val;
|
||||
|
||||
if (kstrtos32(buf, 0, &val))
|
||||
return -EINVAL;
|
||||
|
||||
qg_ss_feature = val;
|
||||
|
||||
return count;
|
||||
}
|
||||
DEVICE_ATTR_RW(qg_ss_feature);
|
||||
|
||||
static int qg_delta_soc_interval_ms = 20000;
|
||||
static ssize_t soc_interval_ms_show(struct device *dev, struct device_attribute
|
||||
*attr, char *buf)
|
||||
{
|
||||
return scnprintf(buf, PAGE_SIZE, "%d\n", qg_delta_soc_interval_ms);
|
||||
}
|
||||
|
||||
static ssize_t soc_interval_ms_store(struct device *dev,
|
||||
struct device_attribute *attr, const char *buf, size_t count)
|
||||
{
|
||||
int val;
|
||||
|
||||
if (kstrtos32(buf, 0, &val))
|
||||
return -EINVAL;
|
||||
|
||||
qg_delta_soc_interval_ms = val;
|
||||
|
||||
return count;
|
||||
}
|
||||
DEVICE_ATTR_RW(soc_interval_ms);
|
||||
|
||||
static int qg_fvss_delta_soc_interval_ms = 10000;
|
||||
static ssize_t fvss_delta_soc_interval_ms_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
return scnprintf(buf, PAGE_SIZE, "%d\n", qg_fvss_delta_soc_interval_ms);
|
||||
}
|
||||
|
||||
static ssize_t fvss_delta_soc_interval_ms_store(struct device *dev,
|
||||
struct device_attribute *attr, const char *buf, size_t count)
|
||||
{
|
||||
int val;
|
||||
|
||||
if (kstrtos32(buf, 0, &val))
|
||||
return -EINVAL;
|
||||
|
||||
qg_fvss_delta_soc_interval_ms = val;
|
||||
|
||||
return count;
|
||||
}
|
||||
DEVICE_ATTR_RW(fvss_delta_soc_interval_ms);
|
||||
|
||||
static int qg_delta_soc_cold_interval_ms = 4000;
|
||||
static ssize_t soc_cold_interval_ms_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
return scnprintf(buf, PAGE_SIZE, "%d\n", qg_delta_soc_cold_interval_ms);
|
||||
}
|
||||
|
||||
static ssize_t soc_cold_interval_ms_store(struct device *dev,
|
||||
struct device_attribute *attr, const char *buf, size_t count)
|
||||
{
|
||||
int val;
|
||||
|
||||
if (kstrtos32(buf, 0, &val))
|
||||
return -EINVAL;
|
||||
|
||||
qg_delta_soc_cold_interval_ms = val;
|
||||
|
||||
return count;
|
||||
}
|
||||
DEVICE_ATTR_RW(soc_cold_interval_ms);
|
||||
|
||||
static int qg_maint_soc_update_ms = 120000;
|
||||
static ssize_t maint_soc_update_ms_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
return scnprintf(buf, PAGE_SIZE, "%d\n", qg_maint_soc_update_ms);
|
||||
}
|
||||
|
||||
static ssize_t maint_soc_update_ms_store(struct device *dev,
|
||||
struct device_attribute *attr, const char *buf, size_t count)
|
||||
{
|
||||
int val;
|
||||
|
||||
if (kstrtos32(buf, 0, &val))
|
||||
return -EINVAL;
|
||||
|
||||
qg_maint_soc_update_ms = val;
|
||||
|
||||
return count;
|
||||
}
|
||||
DEVICE_ATTR_RW(maint_soc_update_ms);
|
||||
|
||||
/* FVSS scaling only based on VBAT */
|
||||
static int qg_fvss_vbat_scaling = 1;
|
||||
static ssize_t fvss_vbat_scaling_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
return scnprintf(buf, PAGE_SIZE, "%d\n", qg_fvss_vbat_scaling);
|
||||
}
|
||||
|
||||
static ssize_t fvss_vbat_scaling_store(struct device *dev,
|
||||
struct device_attribute *attr, const char *buf, size_t count)
|
||||
{
|
||||
int val;
|
||||
|
||||
if (kstrtos32(buf, 0, &val))
|
||||
return -EINVAL;
|
||||
|
||||
qg_fvss_vbat_scaling = val;
|
||||
|
||||
return count;
|
||||
}
|
||||
DEVICE_ATTR_RW(fvss_vbat_scaling);
|
||||
|
||||
static int qg_process_fvss_soc(struct qpnp_qg *chip, int sys_soc)
|
||||
{
|
||||
int rc, vbat_uv = 0, vbat_cutoff_uv = chip->dt.vbatt_cutoff_mv * 1000;
|
||||
int soc_vbat = 0, wt_vbat = 0, wt_sys = 0, soc_fvss = 0;
|
||||
|
||||
if (!chip->dt.fvss_enable && !(qg_ss_feature & QG_FVSS))
|
||||
goto exit_soc_scale;
|
||||
|
||||
if (chip->charge_status == POWER_SUPPLY_STATUS_CHARGING)
|
||||
goto exit_soc_scale;
|
||||
|
||||
rc = qg_get_battery_voltage(chip, &vbat_uv);
|
||||
if (rc < 0)
|
||||
goto exit_soc_scale;
|
||||
|
||||
if (!chip->last_fifo_v_uv)
|
||||
chip->last_fifo_v_uv = vbat_uv;
|
||||
|
||||
if (chip->last_fifo_v_uv > (chip->dt.fvss_vbat_mv * 1000)) {
|
||||
qg_dbg(chip, QG_DEBUG_SOC, "FVSS: last_fifo_v=%d fvss_entry_uv=%d - exit\n",
|
||||
chip->last_fifo_v_uv, chip->dt.fvss_vbat_mv * 1000);
|
||||
goto exit_soc_scale;
|
||||
}
|
||||
|
||||
/* Enter FVSS */
|
||||
if (!chip->fvss_active) {
|
||||
chip->vbat_fvss_entry = CAP(vbat_cutoff_uv,
|
||||
chip->dt.fvss_vbat_mv * 1000,
|
||||
chip->last_fifo_v_uv);
|
||||
chip->soc_fvss_entry = sys_soc;
|
||||
chip->fvss_active = true;
|
||||
} else if (chip->last_fifo_v_uv > chip->vbat_fvss_entry) {
|
||||
/* VBAT has gone beyond the entry voltage */
|
||||
chip->vbat_fvss_entry = chip->last_fifo_v_uv;
|
||||
chip->soc_fvss_entry = sys_soc;
|
||||
}
|
||||
|
||||
soc_vbat = qg_linear_interpolate(chip->soc_fvss_entry,
|
||||
chip->vbat_fvss_entry,
|
||||
0,
|
||||
vbat_cutoff_uv,
|
||||
chip->last_fifo_v_uv);
|
||||
soc_vbat = CAP(0, 100, soc_vbat);
|
||||
|
||||
if (qg_fvss_vbat_scaling) {
|
||||
wt_vbat = 100;
|
||||
wt_sys = 0;
|
||||
} else {
|
||||
wt_sys = qg_linear_interpolate(100,
|
||||
chip->soc_fvss_entry,
|
||||
0,
|
||||
0,
|
||||
sys_soc);
|
||||
wt_sys = CAP(0, 100, wt_sys);
|
||||
wt_vbat = 100 - wt_sys;
|
||||
}
|
||||
|
||||
soc_fvss = ((soc_vbat * wt_vbat) + (sys_soc * wt_sys)) / 100;
|
||||
soc_fvss = CAP(0, 100, soc_fvss);
|
||||
|
||||
qg_dbg(chip, QG_DEBUG_SOC, "FVSS: vbat_fvss_entry=%d soc_fvss_entry=%d cutoff_uv=%d vbat_uv=%d fifo_avg_v=%d soc_vbat=%d sys_soc=%d wt_vbat=%d wt_sys=%d soc_fvss=%d\n",
|
||||
chip->vbat_fvss_entry, chip->soc_fvss_entry,
|
||||
vbat_cutoff_uv, vbat_uv, chip->last_fifo_v_uv,
|
||||
soc_vbat, sys_soc, wt_vbat, wt_sys, soc_fvss);
|
||||
|
||||
return soc_fvss;
|
||||
|
||||
exit_soc_scale:
|
||||
chip->fvss_active = false;
|
||||
return sys_soc;
|
||||
}
|
||||
|
||||
#define IBAT_HYST_PC 10
|
||||
#define TCSS_ENTRY_COUNT 2
|
||||
static int qg_process_tcss_soc(struct qpnp_qg *chip, int sys_soc)
|
||||
{
|
||||
int rc, ibatt_diff = 0, ibat_inc_hyst = 0;
|
||||
int qg_iterm_ua = (-1 * chip->dt.iterm_ma * 1000);
|
||||
int soc_ibat, wt_ibat, wt_sys;
|
||||
union power_supply_propval prop = {0, };
|
||||
|
||||
if (!chip->dt.tcss_enable && !(qg_ss_feature & QG_TCSS))
|
||||
goto exit_soc_scale;
|
||||
|
||||
if (chip->sys_soc < (chip->dt.tcss_entry_soc * 100))
|
||||
goto exit_soc_scale;
|
||||
|
||||
if (chip->sys_soc >= QG_MAX_SOC && chip->soc_tcss >= QG_MAX_SOC)
|
||||
goto exit_soc_scale;
|
||||
|
||||
rc = power_supply_get_property(chip->batt_psy,
|
||||
POWER_SUPPLY_PROP_HEALTH, &prop);
|
||||
if (!rc && (prop.intval == POWER_SUPPLY_HEALTH_COOL ||
|
||||
prop.intval == POWER_SUPPLY_HEALTH_WARM))
|
||||
goto exit_soc_scale;
|
||||
|
||||
if (chip->last_fifo_i_ua >= 0)
|
||||
goto exit_soc_scale;
|
||||
else if (++chip->tcss_entry_count < TCSS_ENTRY_COUNT)
|
||||
goto skip_entry_count;
|
||||
|
||||
if (!chip->tcss_active) {
|
||||
chip->soc_tcss = sys_soc;
|
||||
chip->soc_tcss_entry = sys_soc;
|
||||
chip->ibat_tcss_entry = min(chip->last_fifo_i_ua, qg_iterm_ua);
|
||||
chip->prev_fifo_i_ua = chip->last_fifo_i_ua;
|
||||
chip->tcss_active = true;
|
||||
}
|
||||
|
||||
rc = power_supply_get_property(chip->batt_psy,
|
||||
POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED, &prop);
|
||||
if (!rc && prop.intval) {
|
||||
qg_dbg(chip, QG_DEBUG_SOC,
|
||||
"Input limited sys_soc=%d soc_tcss=%d\n",
|
||||
sys_soc, chip->soc_tcss);
|
||||
if (chip->soc_tcss > sys_soc)
|
||||
sys_soc = chip->soc_tcss;
|
||||
goto exit_soc_scale;
|
||||
}
|
||||
|
||||
ibatt_diff = chip->last_fifo_i_ua - chip->prev_fifo_i_ua;
|
||||
if (ibatt_diff > 0) {
|
||||
/*
|
||||
* if the battery charge current has suddendly dropped, allow it
|
||||
* to decrease only by a small fraction to avoid a SOC jump.
|
||||
*/
|
||||
ibat_inc_hyst = (chip->prev_fifo_i_ua * IBAT_HYST_PC) / 100;
|
||||
if (ibatt_diff > abs(ibat_inc_hyst))
|
||||
chip->prev_fifo_i_ua -= ibat_inc_hyst;
|
||||
else
|
||||
chip->prev_fifo_i_ua = chip->last_fifo_i_ua;
|
||||
}
|
||||
|
||||
chip->prev_fifo_i_ua = min(chip->prev_fifo_i_ua, qg_iterm_ua);
|
||||
soc_ibat = qg_linear_interpolate(chip->soc_tcss_entry,
|
||||
chip->ibat_tcss_entry,
|
||||
QG_MAX_SOC,
|
||||
qg_iterm_ua,
|
||||
chip->prev_fifo_i_ua);
|
||||
soc_ibat = CAP(QG_MIN_SOC, QG_MAX_SOC, soc_ibat);
|
||||
|
||||
wt_ibat = qg_linear_interpolate(1, chip->soc_tcss_entry,
|
||||
10000, 10000, soc_ibat);
|
||||
wt_ibat = CAP(QG_MIN_SOC, QG_MAX_SOC, wt_ibat);
|
||||
wt_sys = 10000 - wt_ibat;
|
||||
|
||||
chip->soc_tcss = DIV_ROUND_CLOSEST((soc_ibat * wt_ibat) +
|
||||
(wt_sys * sys_soc), 10000);
|
||||
chip->soc_tcss = CAP(QG_MIN_SOC, QG_MAX_SOC, chip->soc_tcss);
|
||||
|
||||
qg_dbg(chip, QG_DEBUG_SOC,
|
||||
"TCSS: fifo_i=%d prev_fifo_i=%d ibatt_tcss_entry=%d qg_term=%d soc_tcss_entry=%d sys_soc=%d soc_ibat=%d wt_ibat=%d wt_sys=%d soc_tcss=%d\n",
|
||||
chip->last_fifo_i_ua, chip->prev_fifo_i_ua,
|
||||
chip->ibat_tcss_entry, qg_iterm_ua,
|
||||
chip->soc_tcss_entry, sys_soc, soc_ibat,
|
||||
wt_ibat, wt_sys, chip->soc_tcss);
|
||||
|
||||
return chip->soc_tcss;
|
||||
|
||||
exit_soc_scale:
|
||||
chip->tcss_entry_count = 0;
|
||||
skip_entry_count:
|
||||
chip->tcss_active = false;
|
||||
if (chip->dt.tcss_enable || (qg_ss_feature & QG_TCSS))
|
||||
qg_dbg(chip, QG_DEBUG_SOC, "TCSS: Quit - enabled=%d sys_soc=%d tcss_entry_count=%d fifo_i_ua=%d\n",
|
||||
chip->dt.tcss_enable, sys_soc, chip->tcss_entry_count,
|
||||
chip->last_fifo_i_ua);
|
||||
return sys_soc;
|
||||
}
|
||||
|
||||
#define BASS_SYS_MSOC_DELTA 2
|
||||
static int qg_process_bass_soc(struct qpnp_qg *chip, int sys_soc)
|
||||
{
|
||||
int bass_soc = sys_soc, msoc = chip->msoc;
|
||||
int batt_soc = CAP(0, 100, DIV_ROUND_CLOSEST(chip->batt_soc, 100));
|
||||
|
||||
if (!chip->dt.bass_enable && !(qg_ss_feature & QG_BASS))
|
||||
goto exit_soc_scale;
|
||||
|
||||
qg_dbg(chip, QG_DEBUG_SOC, "BASS Entry: fifo_i=%d sys_soc=%d msoc=%d batt_soc=%d fvss_active=%d\n",
|
||||
chip->last_fifo_i_ua, sys_soc, msoc,
|
||||
batt_soc, chip->fvss_active);
|
||||
|
||||
/* Skip BASS if FVSS is active */
|
||||
if (chip->fvss_active)
|
||||
goto exit_soc_scale;
|
||||
|
||||
if (((sys_soc - msoc) < BASS_SYS_MSOC_DELTA) ||
|
||||
chip->last_fifo_i_ua <= 0)
|
||||
goto exit_soc_scale;
|
||||
|
||||
if (!chip->bass_active) {
|
||||
chip->bass_active = true;
|
||||
chip->bsoc_bass_entry = batt_soc;
|
||||
}
|
||||
|
||||
/* Drop the sys_soc by 1% if batt_soc has dropped */
|
||||
if ((chip->bsoc_bass_entry - batt_soc) >= 1) {
|
||||
bass_soc = (msoc > 0) ? msoc - 1 : 0;
|
||||
chip->bass_active = false;
|
||||
}
|
||||
|
||||
qg_dbg(chip, QG_DEBUG_SOC, "BASS Exit: fifo_i_ua=%d sys_soc=%d msoc=%d bsoc_bass_entry=%d batt_soc=%d bass_soc=%d\n",
|
||||
chip->last_fifo_i_ua, sys_soc, msoc,
|
||||
chip->bsoc_bass_entry, chip->batt_soc, bass_soc);
|
||||
|
||||
return bass_soc;
|
||||
|
||||
exit_soc_scale:
|
||||
chip->bass_active = false;
|
||||
if (chip->dt.bass_enable || (qg_ss_feature & QG_BASS))
|
||||
qg_dbg(chip, QG_DEBUG_SOC, "BASS Quit: enabled=%d fifo_i_ua=%d sys_soc=%d msoc=%d batt_soc=%d\n",
|
||||
chip->dt.bass_enable, chip->last_fifo_i_ua,
|
||||
sys_soc, msoc, chip->batt_soc);
|
||||
return sys_soc;
|
||||
}
|
||||
|
||||
int qg_adjust_sys_soc(struct qpnp_qg *chip)
|
||||
{
|
||||
int soc, vbat_uv, rc;
|
||||
int vcutoff_uv = chip->dt.vbatt_cutoff_mv * 1000;
|
||||
|
||||
chip->sys_soc = CAP(QG_MIN_SOC, QG_MAX_SOC, chip->sys_soc);
|
||||
|
||||
/* TCSS */
|
||||
chip->sys_soc = qg_process_tcss_soc(chip, chip->sys_soc);
|
||||
|
||||
if (chip->sys_soc == QG_MAX_SOC) {
|
||||
soc = FULL_SOC;
|
||||
} else if (chip->sys_soc >= (QG_MAX_SOC - 100)) {
|
||||
/* Hold SOC to 100% if we are dropping from 100 to 99 */
|
||||
if (chip->last_adj_ssoc == FULL_SOC)
|
||||
soc = FULL_SOC;
|
||||
else /* Hold SOC at 99% until we hit 100% */
|
||||
soc = FULL_SOC - 1;
|
||||
} else {
|
||||
soc = DIV_ROUND_CLOSEST(chip->sys_soc, 100);
|
||||
}
|
||||
|
||||
/* FVSS */
|
||||
soc = qg_process_fvss_soc(chip, soc);
|
||||
|
||||
/* BASS */
|
||||
soc = qg_process_bass_soc(chip, soc);
|
||||
|
||||
if (soc == 0) {
|
||||
/* Hold SOC to 1% if we have not dropped below cutoff */
|
||||
rc = qg_get_vbat_avg(chip, &vbat_uv);
|
||||
if (!rc && (vbat_uv >= (vcutoff_uv + VBAT_LOW_HYST_UV))) {
|
||||
soc = 1;
|
||||
qg_dbg(chip, QG_DEBUG_SOC, "vbat_uv=%duV holding SOC to 1%\n",
|
||||
vbat_uv);
|
||||
}
|
||||
}
|
||||
|
||||
qg_dbg(chip, QG_DEBUG_SOC, "sys_soc=%d adjusted sys_soc=%d\n",
|
||||
chip->sys_soc, soc);
|
||||
|
||||
chip->last_adj_ssoc = soc;
|
||||
|
||||
return soc;
|
||||
}
|
||||
|
||||
static void get_next_update_time(struct qpnp_qg *chip)
|
||||
{
|
||||
int soc_points = 0, batt_temp = 0;
|
||||
int min_delta_soc_interval_ms = qg_delta_soc_interval_ms;
|
||||
int rc = 0, rt_time_ms = 0, full_time_ms = DEFAULT_UPDATE_TIME_MS;
|
||||
|
||||
get_fifo_done_time(chip, false, &full_time_ms);
|
||||
get_fifo_done_time(chip, true, &rt_time_ms);
|
||||
|
||||
full_time_ms = CAP(0, DEFAULT_UPDATE_TIME_MS,
|
||||
full_time_ms - rt_time_ms);
|
||||
|
||||
soc_points = abs(chip->msoc - chip->catch_up_soc);
|
||||
if (chip->maint_soc > 0)
|
||||
soc_points = max(abs(chip->msoc - chip->maint_soc), soc_points);
|
||||
soc_points /= chip->dt.delta_soc;
|
||||
|
||||
/* Lower the delta soc interval by half at cold */
|
||||
rc = qg_get_battery_temp(chip, &batt_temp);
|
||||
if (!rc && batt_temp < chip->dt.cold_temp_threshold)
|
||||
min_delta_soc_interval_ms = qg_delta_soc_cold_interval_ms;
|
||||
else if (chip->maint_soc > 0 && chip->maint_soc >= chip->recharge_soc)
|
||||
/* if in maintenance mode scale slower */
|
||||
min_delta_soc_interval_ms = qg_maint_soc_update_ms;
|
||||
else if (chip->fvss_active)
|
||||
min_delta_soc_interval_ms = qg_fvss_delta_soc_interval_ms;
|
||||
|
||||
if (!min_delta_soc_interval_ms)
|
||||
min_delta_soc_interval_ms = 1000; /* 1 second */
|
||||
|
||||
chip->next_wakeup_ms = (full_time_ms / (soc_points + 1))
|
||||
- SOC_SCALE_HYST_MS;
|
||||
chip->next_wakeup_ms = max(chip->next_wakeup_ms,
|
||||
min_delta_soc_interval_ms);
|
||||
|
||||
qg_dbg(chip, QG_DEBUG_SOC, "fifo_full_time=%d secs fifo_real_time=%d secs soc_scale_points=%d\n",
|
||||
full_time_ms / 1000, rt_time_ms / 1000, soc_points);
|
||||
}
|
||||
|
||||
static bool is_scaling_required(struct qpnp_qg *chip)
|
||||
{
|
||||
bool input_present = is_input_present(chip);
|
||||
|
||||
if (!chip->profile_loaded)
|
||||
return false;
|
||||
|
||||
if (chip->maint_soc > 0 &&
|
||||
(abs(chip->maint_soc - chip->msoc) >= chip->dt.delta_soc))
|
||||
return true;
|
||||
|
||||
if ((abs(chip->catch_up_soc - chip->msoc) < chip->dt.delta_soc) &&
|
||||
chip->catch_up_soc != 0 && chip->catch_up_soc != 100)
|
||||
return false;
|
||||
|
||||
if (chip->catch_up_soc == chip->msoc)
|
||||
/* SOC has not changed */
|
||||
return false;
|
||||
|
||||
|
||||
if (chip->catch_up_soc > chip->msoc && !input_present)
|
||||
/* input is not present and SOC has increased */
|
||||
return false;
|
||||
|
||||
if (chip->catch_up_soc > chip->msoc && input_present &&
|
||||
(chip->charge_status != POWER_SUPPLY_STATUS_CHARGING &&
|
||||
chip->charge_status != POWER_SUPPLY_STATUS_FULL))
|
||||
/* USB is present, but not charging */
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool maint_soc_timeout(struct qpnp_qg *chip)
|
||||
{
|
||||
unsigned long now;
|
||||
int rc;
|
||||
|
||||
if (chip->maint_soc < 0)
|
||||
return false;
|
||||
|
||||
rc = get_rtc_time(&now);
|
||||
if (rc < 0)
|
||||
return true;
|
||||
|
||||
/* Do not scale if we have dropped below recharge-soc */
|
||||
if (chip->maint_soc < chip->recharge_soc)
|
||||
return true;
|
||||
|
||||
if ((now - chip->last_maint_soc_update_time) >=
|
||||
(qg_maint_soc_update_ms / 1000)) {
|
||||
chip->last_maint_soc_update_time = now;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void update_msoc(struct qpnp_qg *chip)
|
||||
{
|
||||
int rc = 0, sdam_soc, batt_temp = 0;
|
||||
bool input_present = is_input_present(chip);
|
||||
|
||||
if (chip->catch_up_soc > chip->msoc) {
|
||||
/* SOC increased */
|
||||
if (input_present) /* Increment if input is present */
|
||||
chip->msoc += chip->dt.delta_soc;
|
||||
} else if (chip->catch_up_soc < chip->msoc) {
|
||||
/* SOC dropped */
|
||||
chip->msoc -= chip->dt.delta_soc;
|
||||
}
|
||||
chip->msoc = CAP(0, 100, chip->msoc);
|
||||
|
||||
if (chip->maint_soc > 0 && chip->msoc < chip->maint_soc
|
||||
&& maint_soc_timeout(chip)) {
|
||||
chip->maint_soc -= chip->dt.delta_soc;
|
||||
chip->maint_soc = CAP(0, 100, chip->maint_soc);
|
||||
}
|
||||
|
||||
/* maint_soc dropped below msoc, skip using it */
|
||||
if (chip->maint_soc <= chip->msoc)
|
||||
chip->maint_soc = -EINVAL;
|
||||
|
||||
/* update the SOC register */
|
||||
rc = qg_write_monotonic_soc(chip, chip->msoc);
|
||||
if (rc < 0)
|
||||
pr_err("Failed to update MSOC register rc=%d\n", rc);
|
||||
|
||||
/* update SDAM with the new MSOC */
|
||||
sdam_soc = (chip->maint_soc > 0) ? chip->maint_soc : chip->msoc;
|
||||
chip->sdam_data[SDAM_SOC] = sdam_soc;
|
||||
rc = qg_sdam_write(SDAM_SOC, sdam_soc);
|
||||
if (rc < 0)
|
||||
pr_err("Failed to update SDAM with MSOC rc=%d\n", rc);
|
||||
|
||||
if (!chip->dt.cl_disable && chip->cl->active) {
|
||||
rc = qg_get_battery_temp(chip, &batt_temp);
|
||||
if (rc < 0) {
|
||||
pr_err("Failed to read BATT_TEMP rc=%d\n", rc);
|
||||
} else if (chip->batt_soc >= 0) {
|
||||
cap_learning_update(chip->cl, batt_temp, chip->batt_soc,
|
||||
chip->charge_status, chip->charge_done,
|
||||
input_present, false);
|
||||
}
|
||||
}
|
||||
|
||||
cycle_count_update(chip->counter,
|
||||
DIV_ROUND_CLOSEST(chip->msoc * 255, 100),
|
||||
chip->charge_status, chip->charge_done,
|
||||
input_present);
|
||||
|
||||
qg_dbg(chip, QG_DEBUG_SOC,
|
||||
"SOC scale: Update maint_soc=%d msoc=%d catch_up_soc=%d delta_soc=%d\n",
|
||||
chip->maint_soc, chip->msoc,
|
||||
chip->catch_up_soc, chip->dt.delta_soc);
|
||||
}
|
||||
|
||||
static void scale_soc_stop(struct qpnp_qg *chip)
|
||||
{
|
||||
chip->next_wakeup_ms = 0;
|
||||
alarm_cancel(&chip->alarm_timer);
|
||||
|
||||
qg_dbg(chip, QG_DEBUG_SOC,
|
||||
"SOC scale stopped: msoc=%d catch_up_soc=%d\n",
|
||||
chip->msoc, chip->catch_up_soc);
|
||||
}
|
||||
|
||||
static void scale_soc_work(struct work_struct *work)
|
||||
{
|
||||
struct qpnp_qg *chip = container_of(work,
|
||||
struct qpnp_qg, scale_soc_work);
|
||||
|
||||
mutex_lock(&chip->soc_lock);
|
||||
|
||||
if (!is_scaling_required(chip)) {
|
||||
scale_soc_stop(chip);
|
||||
goto done;
|
||||
}
|
||||
|
||||
update_msoc(chip);
|
||||
|
||||
if (is_scaling_required(chip)) {
|
||||
alarm_start_relative(&chip->alarm_timer,
|
||||
ms_to_ktime(chip->next_wakeup_ms));
|
||||
} else {
|
||||
scale_soc_stop(chip);
|
||||
goto done_psy;
|
||||
}
|
||||
|
||||
qg_dbg(chip, QG_DEBUG_SOC,
|
||||
"SOC scale: Work msoc=%d catch_up_soc=%d delta_soc=%d next_wakeup=%d sec\n",
|
||||
chip->msoc, chip->catch_up_soc, chip->dt.delta_soc,
|
||||
chip->next_wakeup_ms / 1000);
|
||||
|
||||
done_psy:
|
||||
power_supply_changed(chip->qg_psy);
|
||||
done:
|
||||
pm_relax(chip->dev);
|
||||
mutex_unlock(&chip->soc_lock);
|
||||
}
|
||||
|
||||
static enum alarmtimer_restart
|
||||
qpnp_msoc_timer(struct alarm *alarm, ktime_t now)
|
||||
{
|
||||
struct qpnp_qg *chip = container_of(alarm,
|
||||
struct qpnp_qg, alarm_timer);
|
||||
|
||||
/* timer callback runs in atomic context, cannot use voter */
|
||||
pm_stay_awake(chip->dev);
|
||||
schedule_work(&chip->scale_soc_work);
|
||||
|
||||
return ALARMTIMER_NORESTART;
|
||||
}
|
||||
|
||||
int qg_scale_soc(struct qpnp_qg *chip, bool force_soc)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
mutex_lock(&chip->soc_lock);
|
||||
|
||||
qg_dbg(chip, QG_DEBUG_SOC,
|
||||
"SOC scale: Start msoc=%d catch_up_soc=%d delta_soc=%d\n",
|
||||
chip->msoc, chip->catch_up_soc, chip->dt.delta_soc);
|
||||
|
||||
if (force_soc) {
|
||||
chip->msoc = chip->catch_up_soc;
|
||||
rc = qg_write_monotonic_soc(chip, chip->msoc);
|
||||
if (rc < 0)
|
||||
pr_err("Failed to update MSOC register rc=%d\n", rc);
|
||||
|
||||
qg_dbg(chip, QG_DEBUG_SOC,
|
||||
"SOC scale: Forced msoc=%d\n", chip->msoc);
|
||||
goto done_psy;
|
||||
}
|
||||
|
||||
if (!is_scaling_required(chip)) {
|
||||
scale_soc_stop(chip);
|
||||
goto done;
|
||||
}
|
||||
|
||||
update_msoc(chip);
|
||||
|
||||
if (is_scaling_required(chip)) {
|
||||
get_next_update_time(chip);
|
||||
alarm_start_relative(&chip->alarm_timer,
|
||||
ms_to_ktime(chip->next_wakeup_ms));
|
||||
} else {
|
||||
scale_soc_stop(chip);
|
||||
goto done_psy;
|
||||
}
|
||||
|
||||
qg_dbg(chip, QG_DEBUG_SOC,
|
||||
"SOC scale: msoc=%d catch_up_soc=%d delta_soc=%d next_wakeup=%d sec\n",
|
||||
chip->msoc, chip->catch_up_soc, chip->dt.delta_soc,
|
||||
chip->next_wakeup_ms / 1000);
|
||||
|
||||
done_psy:
|
||||
power_supply_changed(chip->qg_psy);
|
||||
done:
|
||||
mutex_unlock(&chip->soc_lock);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int qg_soc_init(struct qpnp_qg *chip)
|
||||
{
|
||||
if (alarmtimer_get_rtcdev()) {
|
||||
alarm_init(&chip->alarm_timer, ALARM_BOOTTIME,
|
||||
qpnp_msoc_timer);
|
||||
} else {
|
||||
pr_err("Failed to get soc alarm-timer\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
INIT_WORK(&chip->scale_soc_work, scale_soc_work);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void qg_soc_exit(struct qpnp_qg *chip)
|
||||
{
|
||||
alarm_cancel(&chip->alarm_timer);
|
||||
}
|
21
drivers/power/supply/qcom/qg-soc.h
Normal file
21
drivers/power/supply/qcom/qg-soc.h
Normal file
@ -0,0 +1,21 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (c) 2018, 2020 The Linux Foundation. All rights reserved.
|
||||
*/
|
||||
|
||||
#ifndef __QG_SOC_H__
|
||||
#define __QG_SOC_H__
|
||||
|
||||
int qg_scale_soc(struct qpnp_qg *chip, bool force_soc);
|
||||
int qg_soc_init(struct qpnp_qg *chip);
|
||||
void qg_soc_exit(struct qpnp_qg *chip);
|
||||
int qg_adjust_sys_soc(struct qpnp_qg *chip);
|
||||
|
||||
extern struct device_attribute dev_attr_soc_interval_ms;
|
||||
extern struct device_attribute dev_attr_soc_cold_interval_ms;
|
||||
extern struct device_attribute dev_attr_maint_soc_update_ms;
|
||||
extern struct device_attribute dev_attr_fvss_delta_soc_interval_ms;
|
||||
extern struct device_attribute dev_attr_fvss_vbat_scaling;
|
||||
extern struct device_attribute dev_attr_qg_ss_feature;
|
||||
|
||||
#endif /* __QG_SOC_H__ */
|
471
drivers/power/supply/qcom/qg-util.c
Normal file
471
drivers/power/supply/qcom/qg-util.c
Normal file
@ -0,0 +1,471 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2018-2020 The Linux Foundation. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <linux/alarmtimer.h>
|
||||
#include <linux/cdev.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/power_supply.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/rtc.h>
|
||||
#include <linux/iio/consumer.h>
|
||||
#include <uapi/linux/qg.h>
|
||||
#include "qg-sdam.h"
|
||||
#include "qg-core.h"
|
||||
#include "qg-reg.h"
|
||||
#include "qg-defs.h"
|
||||
#include "qg-util.h"
|
||||
|
||||
static inline bool is_sticky_register(u32 addr)
|
||||
{
|
||||
if ((addr & 0xFF) == QG_STATUS2_REG)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int qg_read(struct qpnp_qg *chip, u32 addr, u8 *val, int len)
|
||||
{
|
||||
int rc, i;
|
||||
u32 dummy = 0;
|
||||
|
||||
rc = regmap_bulk_read(chip->regmap, addr, val, len);
|
||||
if (rc < 0) {
|
||||
pr_err("Failed regmap_read for address %04x rc=%d\n", addr, rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (is_sticky_register(addr)) {
|
||||
/* write to the sticky register to clear it */
|
||||
rc = regmap_write(chip->regmap, addr, dummy);
|
||||
if (rc < 0) {
|
||||
pr_err("Failed regmap_write for %04x rc=%d\n",
|
||||
addr, rc);
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
if (*chip->debug_mask & QG_DEBUG_BUS_READ) {
|
||||
pr_info("length %d addr=%04x\n", len, addr);
|
||||
for (i = 0; i < len; i++)
|
||||
pr_info("val[%d]: %02x\n", i, val[i]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int qg_write(struct qpnp_qg *chip, u32 addr, u8 *val, int len)
|
||||
{
|
||||
int rc, i;
|
||||
|
||||
mutex_lock(&chip->bus_lock);
|
||||
|
||||
if (len > 1)
|
||||
rc = regmap_bulk_write(chip->regmap, addr, val, len);
|
||||
else
|
||||
rc = regmap_write(chip->regmap, addr, *val);
|
||||
|
||||
if (rc < 0) {
|
||||
pr_err("Failed regmap_write for address %04x rc=%d\n",
|
||||
addr, rc);
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (*chip->debug_mask & QG_DEBUG_BUS_WRITE) {
|
||||
pr_info("length %d addr=%04x\n", len, addr);
|
||||
for (i = 0; i < len; i++)
|
||||
pr_info("val[%d]: %02x\n", i, val[i]);
|
||||
}
|
||||
out:
|
||||
mutex_unlock(&chip->bus_lock);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int qg_masked_write(struct qpnp_qg *chip, int addr, u32 mask, u32 val)
|
||||
{
|
||||
int rc;
|
||||
|
||||
mutex_lock(&chip->bus_lock);
|
||||
|
||||
rc = regmap_update_bits(chip->regmap, addr, mask, val);
|
||||
if (rc < 0) {
|
||||
pr_err("Failed regmap_update_bits for address %04x rc=%d\n",
|
||||
addr, rc);
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (*chip->debug_mask & QG_DEBUG_BUS_WRITE)
|
||||
pr_info("addr=%04x mask: %02x val: %02x\n", addr, mask, val);
|
||||
|
||||
out:
|
||||
mutex_unlock(&chip->bus_lock);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int qg_read_raw_data(struct qpnp_qg *chip, int addr, u32 *data)
|
||||
{
|
||||
int rc;
|
||||
u8 reg[2] = {0};
|
||||
|
||||
rc = qg_read(chip, chip->qg_base + addr, ®[0], 2);
|
||||
if (rc < 0) {
|
||||
pr_err("Failed to read QG addr %d rc=%d\n", addr, rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
*data = reg[0] | (reg[1] << 8);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
s64 qg_iraw_to_ua(struct qpnp_qg *chip, int iraw)
|
||||
{
|
||||
if (chip->qg_subtype == QG_ADC_IBAT_5A)
|
||||
return div_s64(152588LL * (s64)iraw, 1000);
|
||||
else
|
||||
return div_s64(305176LL * (s64)iraw, 1000);
|
||||
}
|
||||
|
||||
int get_fifo_length(struct qpnp_qg *chip, u32 *fifo_length, bool rt)
|
||||
{
|
||||
int rc;
|
||||
u8 reg = 0;
|
||||
u32 addr;
|
||||
|
||||
addr = rt ? QG_STATUS3_REG : QG_S2_NORMAL_MEAS_CTL2_REG;
|
||||
rc = qg_read(chip, chip->qg_base + addr, ®, 1);
|
||||
if (rc < 0) {
|
||||
pr_err("Failed to read FIFO length rc=%d\n", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (rt) {
|
||||
*fifo_length = reg & COUNT_FIFO_RT_MASK;
|
||||
} else {
|
||||
*fifo_length = (reg & FIFO_LENGTH_MASK) >> FIFO_LENGTH_SHIFT;
|
||||
*fifo_length += 1;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int get_sample_count(struct qpnp_qg *chip, u32 *sample_count)
|
||||
{
|
||||
int rc;
|
||||
u8 reg = 0;
|
||||
|
||||
rc = qg_read(chip, chip->qg_base + QG_S2_NORMAL_MEAS_CTL2_REG,
|
||||
®, 1);
|
||||
if (rc < 0) {
|
||||
pr_err("Failed to read FIFO sample count rc=%d\n", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
*sample_count = 1 << ((reg & NUM_OF_ACCUM_MASK) + 1);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
#define QG_CLK_RATE 32000
|
||||
#define QG_ACTUAL_CLK_RATE 32764
|
||||
int get_sample_interval(struct qpnp_qg *chip, u32 *sample_interval)
|
||||
{
|
||||
int rc;
|
||||
u8 reg = 0;
|
||||
|
||||
rc = qg_read(chip, chip->qg_base + QG_S2_NORMAL_MEAS_CTL3_REG,
|
||||
®, 1);
|
||||
if (rc < 0) {
|
||||
pr_err("Failed to read FIFO sample interval rc=%d\n", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
*sample_interval = reg * 10;
|
||||
|
||||
if (chip->wa_flags & QG_CLK_ADJUST_WA) {
|
||||
*sample_interval = DIV_ROUND_CLOSEST(
|
||||
*sample_interval * QG_CLK_RATE, QG_ACTUAL_CLK_RATE);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int get_rtc_time(unsigned long *rtc_time)
|
||||
{
|
||||
struct rtc_time tm;
|
||||
struct rtc_device *rtc;
|
||||
int rc;
|
||||
|
||||
rtc = rtc_class_open(CONFIG_RTC_HCTOSYS_DEVICE);
|
||||
if (rtc == NULL) {
|
||||
pr_err("Failed to open rtc device (%s)\n",
|
||||
CONFIG_RTC_HCTOSYS_DEVICE);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
rc = rtc_read_time(rtc, &tm);
|
||||
if (rc) {
|
||||
pr_err("Failed to read rtc time (%s) : %d\n",
|
||||
CONFIG_RTC_HCTOSYS_DEVICE, rc);
|
||||
goto close_time;
|
||||
}
|
||||
|
||||
rc = rtc_valid_tm(&tm);
|
||||
if (rc) {
|
||||
pr_err("Invalid RTC time (%s): %d\n",
|
||||
CONFIG_RTC_HCTOSYS_DEVICE, rc);
|
||||
goto close_time;
|
||||
}
|
||||
rtc_tm_to_time(&tm, rtc_time);
|
||||
|
||||
close_time:
|
||||
rtc_class_close(rtc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int get_fifo_done_time(struct qpnp_qg *chip, bool rt, int *time_ms)
|
||||
{
|
||||
int rc, length = 0;
|
||||
u32 sample_count = 0, sample_interval = 0, acc_count = 0;
|
||||
|
||||
rc = get_fifo_length(chip, &length, rt ? true : false);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
rc = get_sample_count(chip, &sample_count);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
rc = get_sample_interval(chip, &sample_interval);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
*time_ms = length * sample_count * sample_interval;
|
||||
|
||||
if (rt) {
|
||||
rc = qg_read(chip, chip->qg_base + QG_ACCUM_CNT_RT_REG,
|
||||
(u8 *)&acc_count, 1);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
*time_ms += ((sample_count - acc_count) * sample_interval);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool is_usb_available(struct qpnp_qg *chip)
|
||||
{
|
||||
if (chip->usb_psy)
|
||||
return true;
|
||||
|
||||
chip->usb_psy = power_supply_get_by_name("usb");
|
||||
if (!chip->usb_psy)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool is_dc_available(struct qpnp_qg *chip)
|
||||
{
|
||||
if (chip->dc_psy)
|
||||
return true;
|
||||
|
||||
chip->dc_psy = power_supply_get_by_name("dc");
|
||||
if (!chip->dc_psy)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool is_usb_present(struct qpnp_qg *chip)
|
||||
{
|
||||
union power_supply_propval pval = {0, };
|
||||
|
||||
if (is_usb_available(chip))
|
||||
power_supply_get_property(chip->usb_psy,
|
||||
POWER_SUPPLY_PROP_PRESENT, &pval);
|
||||
|
||||
return pval.intval ? true : false;
|
||||
}
|
||||
|
||||
bool is_dc_present(struct qpnp_qg *chip)
|
||||
{
|
||||
union power_supply_propval pval = {0, };
|
||||
|
||||
if (is_dc_available(chip))
|
||||
power_supply_get_property(chip->dc_psy,
|
||||
POWER_SUPPLY_PROP_PRESENT, &pval);
|
||||
|
||||
return pval.intval ? true : false;
|
||||
}
|
||||
|
||||
bool is_input_present(struct qpnp_qg *chip)
|
||||
{
|
||||
return is_usb_present(chip) || is_dc_present(chip);
|
||||
}
|
||||
|
||||
static bool is_parallel_available(struct qpnp_qg *chip)
|
||||
{
|
||||
if (chip->parallel_psy)
|
||||
return true;
|
||||
|
||||
chip->parallel_psy = power_supply_get_by_name("parallel");
|
||||
if (!chip->parallel_psy)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool is_parallel_enabled(struct qpnp_qg *chip)
|
||||
{
|
||||
union power_supply_propval pval = {0, };
|
||||
|
||||
if (is_parallel_available(chip)) {
|
||||
power_supply_get_property(chip->parallel_psy,
|
||||
POWER_SUPPLY_PROP_CHARGING_ENABLED, &pval);
|
||||
}
|
||||
|
||||
return pval.intval ? true : false;
|
||||
}
|
||||
|
||||
int qg_write_monotonic_soc(struct qpnp_qg *chip, int msoc)
|
||||
{
|
||||
u8 reg = 0;
|
||||
int rc;
|
||||
|
||||
reg = (msoc * 255) / 100;
|
||||
rc = qg_write(chip, chip->qg_base + QG_SOC_MONOTONIC_REG,
|
||||
®, 1);
|
||||
if (rc < 0)
|
||||
pr_err("Failed to update QG_SOC_MONOTINIC reg rc=%d\n", rc);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int qg_get_battery_temp(struct qpnp_qg *chip, int *temp)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
if (chip->battery_missing) {
|
||||
*temp = 250;
|
||||
return 0;
|
||||
}
|
||||
|
||||
rc = iio_read_channel_processed(chip->batt_therm_chan, temp);
|
||||
if (rc < 0) {
|
||||
pr_err("Failed reading BAT_TEMP over ADC rc=%d\n", rc);
|
||||
return rc;
|
||||
}
|
||||
pr_debug("batt_temp = %d\n", *temp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int qg_get_battery_current(struct qpnp_qg *chip, int *ibat_ua)
|
||||
{
|
||||
int rc = 0, last_ibat = 0;
|
||||
|
||||
if (chip->battery_missing) {
|
||||
*ibat_ua = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (chip->qg_mode == QG_V_MODE) {
|
||||
*ibat_ua = chip->qg_v_ibat;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* hold data */
|
||||
rc = qg_masked_write(chip, chip->qg_base + QG_DATA_CTL2_REG,
|
||||
BURST_AVG_HOLD_FOR_READ_BIT,
|
||||
BURST_AVG_HOLD_FOR_READ_BIT);
|
||||
if (rc < 0) {
|
||||
pr_err("Failed to hold burst-avg data rc=%d\n", rc);
|
||||
goto release;
|
||||
}
|
||||
|
||||
rc = qg_read(chip, chip->qg_base + QG_LAST_BURST_AVG_I_DATA0_REG,
|
||||
(u8 *)&last_ibat, 2);
|
||||
if (rc < 0) {
|
||||
pr_err("Failed to read LAST_BURST_AVG_I reg, rc=%d\n", rc);
|
||||
goto release;
|
||||
}
|
||||
|
||||
last_ibat = sign_extend32(last_ibat, 15);
|
||||
*ibat_ua = qg_iraw_to_ua(chip, last_ibat);
|
||||
|
||||
release:
|
||||
/* release */
|
||||
qg_masked_write(chip, chip->qg_base + QG_DATA_CTL2_REG,
|
||||
BURST_AVG_HOLD_FOR_READ_BIT, 0);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int qg_get_battery_voltage(struct qpnp_qg *chip, int *vbat_uv)
|
||||
{
|
||||
int rc = 0;
|
||||
u64 last_vbat = 0;
|
||||
|
||||
if (chip->battery_missing) {
|
||||
*vbat_uv = 3700000;
|
||||
return 0;
|
||||
}
|
||||
|
||||
rc = qg_read(chip, chip->qg_base + QG_LAST_ADC_V_DATA0_REG,
|
||||
(u8 *)&last_vbat, 2);
|
||||
if (rc < 0) {
|
||||
pr_err("Failed to read LAST_ADV_V reg, rc=%d\n", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
*vbat_uv = V_RAW_TO_UV(last_vbat);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int qg_get_vbat_avg(struct qpnp_qg *chip, int *vbat_uv)
|
||||
{
|
||||
int rc = 0;
|
||||
u64 last_vbat = 0;
|
||||
|
||||
rc = qg_read(chip, chip->qg_base + QG_S2_NORMAL_AVG_V_DATA0_REG,
|
||||
(u8 *)&last_vbat, 2);
|
||||
if (rc < 0) {
|
||||
pr_err("Failed to read S2_NORMAL_AVG_V reg, rc=%d\n", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
*vbat_uv = V_RAW_TO_UV(last_vbat);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int qg_get_ibat_avg(struct qpnp_qg *chip, int *ibat_ua)
|
||||
{
|
||||
int rc = 0;
|
||||
int last_ibat = 0;
|
||||
|
||||
rc = qg_read(chip, chip->qg_base + QG_S2_NORMAL_AVG_I_DATA0_REG,
|
||||
(u8 *)&last_ibat, 2);
|
||||
if (rc < 0) {
|
||||
pr_err("Failed to read S2_NORMAL_AVG_I reg, rc=%d\n", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (last_ibat == FIFO_I_RESET_VAL) {
|
||||
/* First FIFO is not complete, read instantaneous IBAT */
|
||||
rc = qg_get_battery_current(chip, ibat_ua);
|
||||
if (rc < 0)
|
||||
pr_err("Failed to read inst. IBAT rc=%d\n", rc);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
last_ibat = sign_extend32(last_ibat, 15);
|
||||
*ibat_ua = qg_iraw_to_ua(chip, last_ibat);
|
||||
|
||||
return 0;
|
||||
}
|
30
drivers/power/supply/qcom/qg-util.h
Normal file
30
drivers/power/supply/qcom/qg-util.h
Normal file
@ -0,0 +1,30 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (c) 2018-2020 The Linux Foundation. All rights reserved.
|
||||
*/
|
||||
|
||||
#ifndef __QG_UTIL_H__
|
||||
#define __QG_UTIL_H__
|
||||
|
||||
int qg_read(struct qpnp_qg *chip, u32 addr, u8 *val, int len);
|
||||
int qg_write(struct qpnp_qg *chip, u32 addr, u8 *val, int len);
|
||||
int qg_masked_write(struct qpnp_qg *chip, int addr, u32 mask, u32 val);
|
||||
int qg_read_raw_data(struct qpnp_qg *chip, int addr, u32 *data);
|
||||
int get_fifo_length(struct qpnp_qg *chip, u32 *fifo_length, bool rt);
|
||||
int get_sample_count(struct qpnp_qg *chip, u32 *sample_count);
|
||||
int get_sample_interval(struct qpnp_qg *chip, u32 *sample_interval);
|
||||
int get_fifo_done_time(struct qpnp_qg *chip, bool rt, int *time_ms);
|
||||
int get_rtc_time(unsigned long *rtc_time);
|
||||
bool is_usb_present(struct qpnp_qg *chip);
|
||||
bool is_dc_present(struct qpnp_qg *chip);
|
||||
bool is_input_present(struct qpnp_qg *chip);
|
||||
bool is_parallel_enabled(struct qpnp_qg *chip);
|
||||
int qg_write_monotonic_soc(struct qpnp_qg *chip, int msoc);
|
||||
int qg_get_battery_temp(struct qpnp_qg *chip, int *batt_temp);
|
||||
int qg_get_battery_current(struct qpnp_qg *chip, int *ibat_ua);
|
||||
int qg_get_battery_voltage(struct qpnp_qg *chip, int *vbat_uv);
|
||||
int qg_get_vbat_avg(struct qpnp_qg *chip, int *vbat_uv);
|
||||
s64 qg_iraw_to_ua(struct qpnp_qg *chip, int iraw);
|
||||
int qg_get_ibat_avg(struct qpnp_qg *chip, int *ibat_ua);
|
||||
|
||||
#endif
|
4905
drivers/power/supply/qcom/qpnp-qg.c
Normal file
4905
drivers/power/supply/qcom/qpnp-qg.c
Normal file
File diff suppressed because it is too large
Load Diff
@ -470,6 +470,8 @@ gen_headers_out_arm = [
|
||||
"linux/qcedev.h",
|
||||
"linux/qcota.h",
|
||||
"linux/qemu_fw_cfg.h",
|
||||
"linux/qg.h",
|
||||
"linux/qg-profile.h",
|
||||
"linux/qnx4_fs.h",
|
||||
"linux/qnxtypes.h",
|
||||
"linux/qrng.h",
|
||||
|
@ -465,6 +465,8 @@ gen_headers_out_arm64 = [
|
||||
"linux/qcedev.h",
|
||||
"linux/qcota.h",
|
||||
"linux/qemu_fw_cfg.h",
|
||||
"linux/qg.h",
|
||||
"linux/qg-profile.h",
|
||||
"linux/qnx4_fs.h",
|
||||
"linux/qnxtypes.h",
|
||||
"linux/qrng.h",
|
||||
|
47
include/linux/pmic-voter.h
Normal file
47
include/linux/pmic-voter.h
Normal file
@ -0,0 +1,47 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (c) 2016-2020 The Linux Foundation. All rights reserved.
|
||||
*/
|
||||
|
||||
#ifndef __PMIC_VOTER_H
|
||||
#define __PMIC_VOTER_H
|
||||
|
||||
#include <linux/mutex.h>
|
||||
|
||||
struct votable;
|
||||
|
||||
enum votable_type {
|
||||
VOTE_MIN,
|
||||
VOTE_MAX,
|
||||
VOTE_SET_ANY,
|
||||
NUM_VOTABLE_TYPES,
|
||||
};
|
||||
|
||||
bool is_client_vote_enabled(struct votable *votable, const char *client_str);
|
||||
bool is_client_vote_enabled_locked(struct votable *votable,
|
||||
const char *client_str);
|
||||
bool is_override_vote_enabled(struct votable *votable);
|
||||
bool is_override_vote_enabled_locked(struct votable *votable);
|
||||
int get_client_vote(struct votable *votable, const char *client_str);
|
||||
int get_client_vote_locked(struct votable *votable, const char *client_str);
|
||||
int get_effective_result(struct votable *votable);
|
||||
int get_effective_result_locked(struct votable *votable);
|
||||
const char *get_effective_client(struct votable *votable);
|
||||
const char *get_effective_client_locked(struct votable *votable);
|
||||
int vote(struct votable *votable, const char *client_str, bool state, int val);
|
||||
int vote_override(struct votable *votable, const char *override_client,
|
||||
bool state, int val);
|
||||
int rerun_election(struct votable *votable);
|
||||
struct votable *find_votable(const char *name);
|
||||
struct votable *create_votable(const char *name,
|
||||
int votable_type,
|
||||
int (*callback)(struct votable *votable,
|
||||
void *data,
|
||||
int effective_result,
|
||||
const char *effective_client),
|
||||
void *data);
|
||||
void destroy_votable(struct votable *votable);
|
||||
void lock_votable(struct votable *votable);
|
||||
void unlock_votable(struct votable *votable);
|
||||
|
||||
#endif /* __PMIC_VOTER_H */
|
73
include/uapi/linux/qg-profile.h
Normal file
73
include/uapi/linux/qg-profile.h
Normal file
@ -0,0 +1,73 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */
|
||||
/*
|
||||
* Copyright (c) 2018-2020, The Linux Foundation. All rights reserved.
|
||||
*/
|
||||
|
||||
#ifndef __QG_PROFILE_H__
|
||||
#define __QG_PROFILE_H__
|
||||
|
||||
#include <linux/ioctl.h>
|
||||
|
||||
/**
|
||||
* enum profile_table - Table index for battery profile data
|
||||
*/
|
||||
enum profile_table {
|
||||
TABLE_SOC_OCV1,
|
||||
TABLE_SOC_OCV2,
|
||||
TABLE_FCC1,
|
||||
TABLE_FCC2,
|
||||
TABLE_Z1,
|
||||
TABLE_Z2,
|
||||
TABLE_Z3,
|
||||
TABLE_Z4,
|
||||
TABLE_Z5,
|
||||
TABLE_Z6,
|
||||
TABLE_Y1,
|
||||
TABLE_Y2,
|
||||
TABLE_Y3,
|
||||
TABLE_Y4,
|
||||
TABLE_Y5,
|
||||
TABLE_Y6,
|
||||
TABLE_MAX,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct battery_params - Battery profile data to be exchanged
|
||||
* @soc: SOC (state of charge) of the battery
|
||||
* @ocv_uv: OCV (open circuit voltage) of the battery
|
||||
* @batt_temp: Battery temperature in deci-degree
|
||||
* @var: 'X' axis param for interpolation
|
||||
* @table_index:Table index to be used for interpolation
|
||||
*/
|
||||
struct battery_params {
|
||||
int soc;
|
||||
int ocv_uv;
|
||||
int fcc_mah;
|
||||
int slope;
|
||||
int var;
|
||||
int batt_temp;
|
||||
int table_index;
|
||||
};
|
||||
|
||||
/* Profile MIN / MAX values */
|
||||
#define QG_MIN_SOC 0
|
||||
#define QG_MAX_SOC 10000
|
||||
#define QG_MIN_OCV_UV 2000000
|
||||
#define QG_MAX_OCV_UV 5000000
|
||||
#define QG_MIN_VAR 0
|
||||
#define QG_MAX_VAR 65535
|
||||
#define QG_MIN_FCC_MAH 100
|
||||
#define QG_MAX_FCC_MAH 16000
|
||||
#define QG_MIN_SLOPE 1
|
||||
#define QG_MAX_SLOPE 50000
|
||||
#define QG_ESR_SF_MIN 5000
|
||||
#define QG_ESR_SF_MAX 20000
|
||||
|
||||
/* IOCTLs to query battery profile data */
|
||||
#define BPIOCXSOC _IOWR('B', 0x01, struct battery_params) /* SOC */
|
||||
#define BPIOCXOCV _IOWR('B', 0x02, struct battery_params) /* OCV */
|
||||
#define BPIOCXFCC _IOWR('B', 0x03, struct battery_params) /* FCC */
|
||||
#define BPIOCXSLOPE _IOWR('B', 0x04, struct battery_params) /* Slope */
|
||||
#define BPIOCXVAR _IOWR('B', 0x05, struct battery_params) /* All-other */
|
||||
|
||||
#endif /* __QG_PROFILE_H__ */
|
68
include/uapi/linux/qg.h
Normal file
68
include/uapi/linux/qg.h
Normal file
@ -0,0 +1,68 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */
|
||||
/*
|
||||
* Copyright (c) 2018, 2020, The Linux Foundation. All rights reserved.
|
||||
*/
|
||||
|
||||
#ifndef __QG_H__
|
||||
#define __QG_H__
|
||||
|
||||
#define MAX_FIFO_LENGTH 16
|
||||
|
||||
enum qg {
|
||||
QG_SOC,
|
||||
QG_OCV_UV,
|
||||
QG_RBAT_MOHM,
|
||||
QG_PON_OCV_UV,
|
||||
QG_GOOD_OCV_UV,
|
||||
QG_ESR,
|
||||
QG_CHARGE_COUNTER,
|
||||
QG_FIFO_TIME_DELTA,
|
||||
QG_BATT_SOC,
|
||||
QG_CC_SOC,
|
||||
QG_ESR_CHARGE_DELTA,
|
||||
QG_ESR_DISCHARGE_DELTA,
|
||||
QG_ESR_CHARGE_SF,
|
||||
QG_ESR_DISCHARGE_SF,
|
||||
QG_FULL_SOC,
|
||||
QG_CLEAR_LEARNT_DATA,
|
||||
QG_SYS_SOC,
|
||||
QG_V_IBAT,
|
||||
QG_MAX,
|
||||
};
|
||||
|
||||
#define QG_BATT_SOC QG_BATT_SOC
|
||||
#define QG_CC_SOC QG_CC_SOC
|
||||
#define QG_ESR_CHARGE_DELTA QG_ESR_CHARGE_DELTA
|
||||
#define QG_ESR_DISCHARGE_DELTA QG_ESR_DISCHARGE_DELTA
|
||||
#define QG_ESR_CHARGE_SF QG_ESR_CHARGE_SF
|
||||
#define QG_ESR_DISCHARGE_SF QG_ESR_DISCHARGE_SF
|
||||
#define QG_FULL_SOC QG_FULL_SOC
|
||||
#define QG_CLEAR_LEARNT_DATA QG_CLEAR_LEARNT_DATA
|
||||
#define QG_SYS_SOC QG_SYS_SOC
|
||||
#define QG_V_IBAT QG_V_IBAT
|
||||
|
||||
struct fifo_data {
|
||||
unsigned int v;
|
||||
unsigned int i;
|
||||
unsigned int count;
|
||||
unsigned int interval;
|
||||
};
|
||||
|
||||
struct qg_param {
|
||||
unsigned int data;
|
||||
bool valid;
|
||||
};
|
||||
|
||||
struct qg_kernel_data {
|
||||
unsigned int seq_no;
|
||||
unsigned int fifo_time;
|
||||
unsigned int fifo_length;
|
||||
struct fifo_data fifo[MAX_FIFO_LENGTH];
|
||||
struct qg_param param[QG_MAX];
|
||||
};
|
||||
|
||||
struct qg_user_data {
|
||||
struct qg_param param[QG_MAX];
|
||||
};
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue
Block a user