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:
Shyam Kumar Thella 2020-04-20 10:05:10 +05:30
parent 21b3dfffb4
commit 3be2db0032
27 changed files with 11114 additions and 0 deletions

View File

@ -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/

View 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

View 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

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

View 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 */

File diff suppressed because it is too large Load Diff

View 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

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

View 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;
}

View 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__ */

View 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__ */

View 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__ */

View 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;
}

View 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__ */

View 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

View 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;
}

View 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

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

View 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__ */

View 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, &reg[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, &reg, 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,
&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,
&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,
&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;
}

View 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

File diff suppressed because it is too large Load Diff

View File

@ -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",

View File

@ -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",

View 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 */

View 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
View 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