Merge "power: supply: qcom: Add snapshot of SMB5 charger driver"

This commit is contained in:
qctecmdr 2020-06-08 04:30:42 -07:00 • committed by Gerrit - the friendly Code Review server
commit 240ed0260c
39 changed files with 28003 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,31 @@
# 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.
config QPNP_SMB5
tristate "SMB5 Battery Charger"
depends on MFD_SPMI_PMIC
help
Say Y to enables support for the SMB5 charging peripheral.
The QPNP SMB5 charger driver supports the charger peripheral
present in the chip.
The power supply framework is used to communicate battery and
usb properties to userspace and other driver consumers such
as fuel gauge, USB, and USB-PD.
VBUS and VCONN regulators are registered for supporting OTG,
and powered Type-C cables respectively.
endif

View file

@ -0,0 +1,4 @@
# 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
obj-$(CONFIG_QPNP_SMB5) += step-chg-jeita.o battery.o qpnp-smb5.o smb5-lib.o pmic-voter.o storm-watch.o schgm-flash.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,19 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2017, 2019-2020 The Linux Foundation. All rights reserved.
*/
#ifndef __BATTERY_H
#define __BATTERY_H
struct charger_param {
u32 fcc_step_delay_ms;
u32 fcc_step_size_ua;
u32 smb_version;
u32 hvdcp2_max_icl_ua;
u32 hvdcp3_max_icl_ua;
u32 forced_main_fcc;
};
int qcom_batt_init(struct charger_param *param);
void qcom_batt_deinit(void);
#endif /* __BATTERY_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

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,239 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2018 The Linux Foundation. All rights reserved.
*/
#define pr_fmt(fmt) "SCHG-FLASH: %s: " fmt, __func__
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/regmap.h>
#include <linux/power_supply.h>
#include <linux/interrupt.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/printk.h>
#include <linux/pmic-voter.h>
#include "smb5-lib.h"
#include "schgm-flash.h"
#define IS_BETWEEN(left, right, value) \
(((left) >= (right) && (left) >= (value) \
&& (value) >= (right)) \
|| ((left) <= (right) && (left) <= (value) \
&& (value) <= (right)))
irqreturn_t schgm_flash_default_irq_handler(int irq, void *data)
{
struct smb_irq_data *irq_data = data;
pr_debug("IRQ: %s\n", irq_data->name);
return IRQ_HANDLED;
}
irqreturn_t schgm_flash_ilim2_irq_handler(int irq, void *data)
{
struct smb_irq_data *irq_data = data;
struct smb_charger *chg = irq_data->parent_data;
int rc;
rc = smblib_write(chg, SCHGM_FLASH_S2_LATCH_RESET_CMD_REG,
FLASH_S2_LATCH_RESET_BIT);
if (rc < 0)
pr_err("Couldn't reset S2_LATCH reset rc=%d\n", rc);
return IRQ_HANDLED;
}
irqreturn_t schgm_flash_state_change_irq_handler(int irq, void *data)
{
struct smb_irq_data *irq_data = data;
struct smb_charger *chg = irq_data->parent_data;
int rc;
u8 reg;
rc = smblib_read(chg, SCHGM_FLASH_STATUS_3_REG, &reg);
if (rc < 0)
pr_err("Couldn't read flash status_3 rc=%d\n", rc);
else
pr_debug("Flash status changed state=[%x]\n",
(reg && FLASH_STATE_MASK));
return IRQ_HANDLED;
}
#define FIXED_MODE 0
#define ADAPTIVE_MODE 1
static void schgm_flash_parse_dt(struct smb_charger *chg)
{
struct device_node *node = chg->dev->of_node;
u32 val;
int rc;
chg->flash_derating_soc = -EINVAL;
rc = of_property_read_u32(node, "qcom,flash-derating-soc", &val);
if (!rc) {
if (IS_BETWEEN(0, 100, val))
chg->flash_derating_soc = (val * 255) / 100;
}
chg->flash_disable_soc = -EINVAL;
rc = of_property_read_u32(node, "qcom,flash-disable-soc", &val);
if (!rc) {
if (IS_BETWEEN(0, 100, val))
chg->flash_disable_soc = (val * 255) / 100;
}
chg->headroom_mode = -EINVAL;
rc = of_property_read_u32(node, "qcom,headroom-mode", &val);
if (!rc) {
if (IS_BETWEEN(FIXED_MODE, ADAPTIVE_MODE, val))
chg->headroom_mode = val;
}
}
bool is_flash_active(struct smb_charger *chg)
{
return chg->flash_active ? true : false;
}
int schgm_flash_get_vreg_ok(struct smb_charger *chg, int *val)
{
int rc, vreg_state;
u8 stat = 0;
if (!chg->flash_init_done)
return -EPERM;
rc = smblib_read(chg, SCHGM_FLASH_STATUS_2_REG, &stat);
if (rc < 0) {
pr_err("Couldn't read FLASH STATUS_2 rc=%d\n", rc);
return rc;
}
vreg_state = !!(stat & VREG_OK_BIT);
/* If VREG_OK is not set check for flash error */
if (!vreg_state) {
rc = smblib_read(chg, SCHGM_FLASH_STATUS_3_REG, &stat);
if (rc < 0) {
pr_err("Couldn't read FLASH_STATUS_3 rc=%d\n", rc);
return rc;
}
if ((stat & FLASH_STATE_MASK) == FLASH_ERROR_VAL) {
vreg_state = -EFAULT;
rc = smblib_read(chg, SCHGM_FLASH_STATUS_5_REG,
&stat);
if (rc < 0) {
pr_err("Couldn't read FLASH_STATUS_5 rc=%d\n",
rc);
return rc;
}
pr_debug("Flash error: status=%x\n", stat);
}
}
/*
* val can be one of the following:
* 1 - VREG_OK is set.
* 0 - VREG_OK is 0 but no Flash error.
* -EFAULT - Flash Error is set.
*/
*val = vreg_state;
return 0;
}
void schgm_flash_torch_priority(struct smb_charger *chg, enum torch_mode mode)
{
int rc;
u8 reg;
/*
* If torch is configured in default BOOST mode, skip any update in the
* mode configuration.
*/
if (chg->headroom_mode == FIXED_MODE)
return;
if ((mode != TORCH_BOOST_MODE) && (mode != TORCH_BUCK_MODE))
return;
reg = mode;
rc = smblib_masked_write(chg, SCHGM_TORCH_PRIORITY_CONTROL_REG,
TORCH_PRIORITY_CONTROL_BIT, reg);
if (rc < 0)
pr_err("Couldn't configure Torch priority control rc=%d\n",
rc);
pr_debug("Torch priority changed to: %d\n", mode);
}
int schgm_flash_init(struct smb_charger *chg)
{
int rc;
u8 reg;
schgm_flash_parse_dt(chg);
if (chg->flash_derating_soc != -EINVAL) {
rc = smblib_write(chg, SCHGM_SOC_BASED_FLASH_DERATE_TH_CFG_REG,
chg->flash_derating_soc);
if (rc < 0) {
pr_err("Couldn't configure SOC for flash derating rc=%d\n",
rc);
return rc;
}
}
if (chg->flash_disable_soc != -EINVAL) {
rc = smblib_write(chg, SCHGM_SOC_BASED_FLASH_DISABLE_TH_CFG_REG,
chg->flash_disable_soc);
if (rc < 0) {
pr_err("Couldn't configure SOC for flash disable rc=%d\n",
rc);
return rc;
}
}
if (chg->headroom_mode != -EINVAL) {
/*
* configure headroom management policy for
* flash and torch mode.
*/
reg = (chg->headroom_mode == FIXED_MODE)
? FORCE_FLASH_BOOST_5V_BIT : 0;
rc = smblib_write(chg, SCHGM_FORCE_BOOST_CONTROL, reg);
if (rc < 0) {
pr_err("Couldn't write force boost control reg rc=%d\n",
rc);
return rc;
}
reg = (chg->headroom_mode == FIXED_MODE)
? TORCH_PRIORITY_CONTROL_BIT : 0;
rc = smblib_write(chg, SCHGM_TORCH_PRIORITY_CONTROL_REG, reg);
if (rc < 0) {
pr_err("Couldn't force 5V boost in torch mode rc=%d\n",
rc);
return rc;
}
}
if ((chg->flash_derating_soc != -EINVAL)
|| (chg->flash_disable_soc != -EINVAL)) {
/* Check if SOC based derating/disable is enabled */
rc = smblib_read(chg, SCHGM_FLASH_CONTROL_REG, &reg);
if (rc < 0) {
pr_err("Couldn't read flash control reg rc=%d\n", rc);
return rc;
}
if (!(reg & SOC_LOW_FOR_FLASH_EN_BIT))
pr_warn("Soc based flash derating not enabled\n");
}
chg->flash_init_done = true;
return 0;
}

View file

@ -0,0 +1,54 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2018, 2020 The Linux Foundation. All rights reserved.
*/
#ifndef __SCHGM_FLASH_H__
#define __SCHGM_FLASH_H__
#include <linux/bitops.h>
#define SCHGM_FLASH_BASE 0xA600
#define SCHGM_FLASH_STATUS_2_REG (SCHGM_FLASH_BASE + 0x07)
#define VREG_OK_BIT BIT(4)
#define SCHGM_FLASH_STATUS_3_REG (SCHGM_FLASH_BASE + 0x08)
#define FLASH_STATE_MASK GENMASK(2, 0)
#define FLASH_ERROR_VAL 0x7
#define SCHGM_FLASH_INT_RT_STS_REG (SCHGM_FLASH_BASE + 0x10)
#define SCHGM_FLASH_STATUS_5_REG (SCHGM_FLASH_BASE + 0x0B)
#define SCHGM_FORCE_BOOST_CONTROL (SCHGM_FLASH_BASE + 0x41)
#define FORCE_FLASH_BOOST_5V_BIT BIT(0)
#define SCHGM_FLASH_S2_LATCH_RESET_CMD_REG (SCHGM_FLASH_BASE + 0x44)
#define FLASH_S2_LATCH_RESET_BIT BIT(0)
#define SCHGM_FLASH_CONTROL_REG (SCHGM_FLASH_BASE + 0x60)
#define SOC_LOW_FOR_FLASH_EN_BIT BIT(7)
#define SCHGM_TORCH_PRIORITY_CONTROL_REG (SCHGM_FLASH_BASE + 0x63)
#define TORCH_PRIORITY_CONTROL_BIT BIT(0)
#define SCHGM_SOC_BASED_FLASH_DERATE_TH_CFG_REG (SCHGM_FLASH_BASE + 0x67)
#define SCHGM_SOC_BASED_FLASH_DISABLE_TH_CFG_REG \
(SCHGM_FLASH_BASE + 0x68)
enum torch_mode {
TORCH_BUCK_MODE = 0,
TORCH_BOOST_MODE,
};
int schgm_flash_get_vreg_ok(struct smb_charger *chg, int *val);
void schgm_flash_torch_priority(struct smb_charger *chg, enum torch_mode mode);
int schgm_flash_init(struct smb_charger *chg);
bool is_flash_active(struct smb_charger *chg);
irqreturn_t schgm_flash_default_irq_handler(int irq, void *data);
irqreturn_t schgm_flash_ilim2_irq_handler(int irq, void *data);
irqreturn_t schgm_flash_state_change_irq_handler(int irq, void *data);
#endif /* __SCHGM_FLASH_H__ */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,841 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2018-2020 The Linux Foundation. All rights reserved.
*/
#ifndef __SMB5_CHARGER_H
#define __SMB5_CHARGER_H
#include <linux/alarmtimer.h>
#include <linux/ktime.h>
#include <linux/types.h>
#include <linux/timer.h>
#include <linux/interrupt.h>
#include <linux/irqreturn.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/consumer.h>
#include <linux/extcon-provider.h>
#include <linux/usb/typec.h>
#include "storm-watch.h"
#include "battery.h"
enum print_reason {
PR_INTERRUPT = BIT(0),
PR_REGISTER = BIT(1),
PR_MISC = BIT(2),
PR_PARALLEL = BIT(3),
PR_OTG = BIT(4),
PR_WLS = BIT(5),
};
#define DEFAULT_VOTER "DEFAULT_VOTER"
#define USER_VOTER "USER_VOTER"
#define PD_VOTER "PD_VOTER"
#define DCP_VOTER "DCP_VOTER"
#define QC_VOTER "QC_VOTER"
#define USB_PSY_VOTER "USB_PSY_VOTER"
#define PL_TAPER_WORK_RUNNING_VOTER "PL_TAPER_WORK_RUNNING_VOTER"
#define USBIN_V_VOTER "USBIN_V_VOTER"
#define CHG_STATE_VOTER "CHG_STATE_VOTER"
#define TAPER_END_VOTER "TAPER_END_VOTER"
#define THERMAL_DAEMON_VOTER "THERMAL_DAEMON_VOTER"
#define DIE_TEMP_VOTER "DIE_TEMP_VOTER"
#define BOOST_BACK_VOTER "BOOST_BACK_VOTER"
#define MICRO_USB_VOTER "MICRO_USB_VOTER"
#define DEBUG_BOARD_VOTER "DEBUG_BOARD_VOTER"
#define PD_SUSPEND_SUPPORTED_VOTER "PD_SUSPEND_SUPPORTED_VOTER"
#define PL_DELAY_VOTER "PL_DELAY_VOTER"
#define CTM_VOTER "CTM_VOTER"
#define SW_QC3_VOTER "SW_QC3_VOTER"
#define AICL_RERUN_VOTER "AICL_RERUN_VOTER"
#define SW_ICL_MAX_VOTER "SW_ICL_MAX_VOTER"
#define PL_QNOVO_VOTER "PL_QNOVO_VOTER"
#define QNOVO_VOTER "QNOVO_VOTER"
#define BATT_PROFILE_VOTER "BATT_PROFILE_VOTER"
#define OTG_DELAY_VOTER "OTG_DELAY_VOTER"
#define USBIN_I_VOTER "USBIN_I_VOTER"
#define WEAK_CHARGER_VOTER "WEAK_CHARGER_VOTER"
#define PL_FCC_LOW_VOTER "PL_FCC_LOW_VOTER"
#define WBC_VOTER "WBC_VOTER"
#define HW_LIMIT_VOTER "HW_LIMIT_VOTER"
#define PL_SMB_EN_VOTER "PL_SMB_EN_VOTER"
#define FORCE_RECHARGE_VOTER "FORCE_RECHARGE_VOTER"
#define LPD_VOTER "LPD_VOTER"
#define FCC_STEPPER_VOTER "FCC_STEPPER_VOTER"
#define SW_THERM_REGULATION_VOTER "SW_THERM_REGULATION_VOTER"
#define JEITA_ARB_VOTER "JEITA_ARB_VOTER"
#define MOISTURE_VOTER "MOISTURE_VOTER"
#define HVDCP2_ICL_VOTER "HVDCP2_ICL_VOTER"
#define AICL_THRESHOLD_VOTER "AICL_THRESHOLD_VOTER"
#define USBOV_DBC_VOTER "USBOV_DBC_VOTER"
#define CHG_TERMINATION_VOTER "CHG_TERMINATION_VOTER"
#define THERMAL_THROTTLE_VOTER "THERMAL_THROTTLE_VOTER"
#define VOUT_VOTER "VOUT_VOTER"
#define USB_SUSPEND_VOTER "USB_SUSPEND_VOTER"
#define CHARGER_TYPE_VOTER "CHARGER_TYPE_VOTER"
#define HDC_IRQ_VOTER "HDC_IRQ_VOTER"
#define DETACH_DETECT_VOTER "DETACH_DETECT_VOTER"
#define CC_MODE_VOTER "CC_MODE_VOTER"
#define MAIN_FCC_VOTER "MAIN_FCC_VOTER"
#define DCIN_AICL_VOTER "DCIN_AICL_VOTER"
#define WLS_PL_CHARGING_VOTER "WLS_PL_CHARGING_VOTER"
#define ICL_CHANGE_VOTER "ICL_CHANGE_VOTER"
#define OVERHEAT_LIMIT_VOTER "OVERHEAT_LIMIT_VOTER"
#define TYPEC_SWAP_VOTER "TYPEC_SWAP_VOTER"
#define BOOST_BACK_STORM_COUNT 3
#define WEAK_CHG_STORM_COUNT 8
#define VBAT_TO_VRAW_ADC(v) div_u64((u64)v * 1000000UL, 194637UL)
#define ITERM_LIMITS_PMI632_MA 5000
#define ITERM_LIMITS_PM8150B_MA 10000
#define ADC_CHG_ITERM_MASK 32767
#define SDP_100_MA 100000
#define SDP_CURRENT_UA 500000
#define CDP_CURRENT_UA 1500000
#define DCP_CURRENT_UA 1500000
#define HVDCP_CURRENT_UA 3000000
#define TYPEC_DEFAULT_CURRENT_UA 900000
#define TYPEC_MEDIUM_CURRENT_UA 1500000
#define TYPEC_HIGH_CURRENT_UA 3000000
#define DCIN_ICL_MIN_UA 100000
#define DCIN_ICL_MAX_UA 1500000
#define DCIN_ICL_STEP_UA 100000
#define ROLE_REVERSAL_DELAY_MS 500
enum smb_mode {
PARALLEL_MASTER = 0,
PARALLEL_SLAVE,
NUM_MODES,
};
enum sink_src_mode {
SINK_MODE,
SRC_MODE,
AUDIO_ACCESS_MODE,
UNATTACHED_MODE,
};
enum qc2_non_comp_voltage {
QC2_COMPLIANT,
QC2_NON_COMPLIANT_9V,
QC2_NON_COMPLIANT_12V
};
enum {
BOOST_BACK_WA = BIT(0),
SW_THERM_REGULATION_WA = BIT(1),
WEAK_ADAPTER_WA = BIT(2),
USBIN_OV_WA = BIT(3),
CHG_TERMINATION_WA = BIT(4),
USBIN_ADC_WA = BIT(5),
SKIP_MISC_PBS_IRQ_WA = BIT(6),
};
enum jeita_cfg_stat {
JEITA_CFG_NONE = 0,
JEITA_CFG_FAILURE,
JEITA_CFG_COMPLETE,
};
enum {
RERUN_AICL = 0,
RESTART_AICL,
};
enum smb_irq_index {
/* CHGR */
CHGR_ERROR_IRQ = 0,
CHG_STATE_CHANGE_IRQ,
STEP_CHG_STATE_CHANGE_IRQ,
STEP_CHG_SOC_UPDATE_FAIL_IRQ,
STEP_CHG_SOC_UPDATE_REQ_IRQ,
FG_FVCAL_QUALIFIED_IRQ,
VPH_ALARM_IRQ,
VPH_DROP_PRECHG_IRQ,
/* DCDC */
OTG_FAIL_IRQ,
OTG_OC_DISABLE_SW_IRQ,
OTG_OC_HICCUP_IRQ,
BSM_ACTIVE_IRQ,
HIGH_DUTY_CYCLE_IRQ,
INPUT_CURRENT_LIMITING_IRQ,
CONCURRENT_MODE_DISABLE_IRQ,
SWITCHER_POWER_OK_IRQ,
/* BATIF */
BAT_TEMP_IRQ,
ALL_CHNL_CONV_DONE_IRQ,
BAT_OV_IRQ,
BAT_LOW_IRQ,
BAT_THERM_OR_ID_MISSING_IRQ,
BAT_TERMINAL_MISSING_IRQ,
BUCK_OC_IRQ,
VPH_OV_IRQ,
/* USB */
USBIN_COLLAPSE_IRQ,
USBIN_VASHDN_IRQ,
USBIN_UV_IRQ,
USBIN_OV_IRQ,
USBIN_PLUGIN_IRQ,
USBIN_REVI_CHANGE_IRQ,
USBIN_SRC_CHANGE_IRQ,
USBIN_ICL_CHANGE_IRQ,
/* DC */
DCIN_VASHDN_IRQ,
DCIN_UV_IRQ,
DCIN_OV_IRQ,
DCIN_PLUGIN_IRQ,
DCIN_REVI_IRQ,
DCIN_PON_IRQ,
DCIN_EN_IRQ,
/* TYPEC */
TYPEC_OR_RID_DETECTION_CHANGE_IRQ,
TYPEC_VPD_DETECT_IRQ,
TYPEC_CC_STATE_CHANGE_IRQ,
TYPEC_VCONN_OC_IRQ,
TYPEC_VBUS_CHANGE_IRQ,
TYPEC_ATTACH_DETACH_IRQ,
TYPEC_LEGACY_CABLE_DETECT_IRQ,
TYPEC_TRY_SNK_SRC_DETECT_IRQ,
/* MISC */
WDOG_SNARL_IRQ,
WDOG_BARK_IRQ,
AICL_FAIL_IRQ,
AICL_DONE_IRQ,
SMB_EN_IRQ,
IMP_TRIGGER_IRQ,
TEMP_CHANGE_IRQ,
TEMP_CHANGE_SMB_IRQ,
/* FLASH */
VREG_OK_IRQ,
ILIM_S2_IRQ,
ILIM_S1_IRQ,
VOUT_DOWN_IRQ,
VOUT_UP_IRQ,
FLASH_STATE_CHANGE_IRQ,
TORCH_REQ_IRQ,
FLASH_EN_IRQ,
SDAM_STS_IRQ,
/* END */
SMB_IRQ_MAX,
};
enum float_options {
FLOAT_DCP = 1,
FLOAT_SDP = 2,
DISABLE_CHARGING = 3,
SUSPEND_INPUT = 4,
};
enum chg_term_config_src {
ITERM_SRC_UNSPECIFIED,
ITERM_SRC_ADC,
ITERM_SRC_ANALOG
};
enum comp_clamp_levels {
CLAMP_LEVEL_DEFAULT = 0,
CLAMP_LEVEL_1,
MAX_CLAMP_LEVEL,
};
struct clamp_config {
u16 reg[3];
u16 val[3];
};
struct smb_irq_info {
const char *name;
const irq_handler_t handler;
const bool wake;
const struct storm_watch storm_data;
struct smb_irq_data *irq_data;
int irq;
bool enabled;
};
static const unsigned int smblib_extcon_cable[] = {
EXTCON_USB,
EXTCON_USB_HOST,
EXTCON_NONE,
};
enum lpd_reason {
LPD_NONE,
LPD_MOISTURE_DETECTED,
LPD_FLOATING_CABLE,
};
/* Following states are applicable only for floating cable during LPD */
enum lpd_stage {
/* initial stage */
LPD_STAGE_NONE,
/* started and ongoing */
LPD_STAGE_FLOAT,
/* cancel if started, or don't start */
LPD_STAGE_FLOAT_CANCEL,
/* confirmed and mitigation measures taken for 60 s */
LPD_STAGE_COMMIT,
};
enum thermal_status_levels {
TEMP_SHUT_DOWN = 0,
TEMP_SHUT_DOWN_SMB,
TEMP_ALERT_LEVEL,
TEMP_ABOVE_RANGE,
TEMP_WITHIN_RANGE,
TEMP_BELOW_RANGE,
};
enum icl_override_mode {
/* APSD/Type-C/QC auto */
HW_AUTO_MODE,
/* 100/150/500/900mA */
SW_OVERRIDE_USB51_MODE,
/* ICL other than USB51 */
SW_OVERRIDE_HC_MODE,
};
/* EXTCON_USB and EXTCON_USB_HOST are mutually exclusive */
static const u32 smblib_extcon_exclusive[] = {0x3, 0};
struct smb_regulator {
struct regulator_dev *rdev;
struct regulator_desc rdesc;
};
struct smb_irq_data {
void *parent_data;
const char *name;
struct storm_watch storm_data;
};
struct smb_chg_param {
const char *name;
u16 reg;
int min_u;
int max_u;
int step_u;
int (*get_proc)(struct smb_chg_param *param,
u8 val_raw);
int (*set_proc)(struct smb_chg_param *param,
int val_u,
u8 *val_raw);
};
struct buck_boost_freq {
int freq_khz;
u8 val;
};
struct smb_chg_freq {
unsigned int freq_5V;
unsigned int freq_6V_8V;
unsigned int freq_9V;
unsigned int freq_12V;
unsigned int freq_removal;
unsigned int freq_below_otg_threshold;
unsigned int freq_above_otg_threshold;
};
struct smb_params {
struct smb_chg_param fcc;
struct smb_chg_param fv;
struct smb_chg_param usb_icl;
struct smb_chg_param icl_max_stat;
struct smb_chg_param icl_stat;
struct smb_chg_param otg_cl;
struct smb_chg_param dc_icl;
struct smb_chg_param jeita_cc_comp_hot;
struct smb_chg_param jeita_cc_comp_cold;
struct smb_chg_param freq_switcher;
struct smb_chg_param aicl_5v_threshold;
struct smb_chg_param aicl_cont_threshold;
};
struct parallel_params {
struct power_supply *psy;
};
struct smb_iio {
struct iio_channel *temp_chan;
struct iio_channel *usbin_i_chan;
struct iio_channel *usbin_v_chan;
struct iio_channel *mid_chan;
struct iio_channel *batt_i_chan;
struct iio_channel *connector_temp_chan;
struct iio_channel *sbux_chan;
struct iio_channel *vph_v_chan;
struct iio_channel *die_temp_chan;
struct iio_channel *skin_temp_chan;
struct iio_channel *smb_temp_chan;
};
struct smb_charger {
struct device *dev;
char *name;
struct regmap *regmap;
struct smb_irq_info *irq_info;
struct smb_params param;
struct smb_iio iio;
int *debug_mask;
int pd_disabled;
enum smb_mode mode;
struct smb_chg_freq chg_freq;
int otg_delay_ms;
int weak_chg_icl_ua;
u32 sdam_base;
bool pd_not_supported;
/* locks */
struct mutex smb_lock;
struct mutex ps_change_lock;
struct mutex irq_status_lock;
struct mutex dcin_aicl_lock;
spinlock_t typec_pr_lock;
struct mutex adc_lock;
struct mutex dpdm_lock;
struct mutex typec_lock;
/* power supplies */
struct power_supply *batt_psy;
struct power_supply *usb_psy;
struct power_supply *dc_psy;
struct power_supply *bms_psy;
struct power_supply *usb_main_psy;
struct power_supply *usb_port_psy;
struct power_supply *wls_psy;
struct power_supply *cp_psy;
enum power_supply_type real_charger_type;
/* notifiers */
struct notifier_block nb;
/* parallel charging */
struct parallel_params pl;
/* CC Mode */
int adapter_cc_mode;
int thermal_overheat;
/* regulators */
struct smb_regulator *vbus_vreg;
struct smb_regulator *vconn_vreg;
struct regulator *dpdm_reg;
/* typec */
struct typec_port *typec_port;
struct typec_capability typec_caps;
struct typec_partner *typec_partner;
struct typec_partner_desc typec_partner_desc;
/* votables */
struct votable *dc_suspend_votable;
struct votable *fcc_votable;
struct votable *fcc_main_votable;
struct votable *fv_votable;
struct votable *usb_icl_votable;
struct votable *awake_votable;
struct votable *pl_disable_votable;
struct votable *chg_disable_votable;
struct votable *pl_enable_votable_indirect;
struct votable *cp_disable_votable;
struct votable *cp_ilim_votable;
struct votable *smb_override_votable;
struct votable *icl_irq_disable_votable;
struct votable *limited_irq_disable_votable;
struct votable *hdc_irq_disable_votable;
struct votable *temp_change_irq_disable_votable;
struct votable *qnovo_disable_votable;
/* work */
struct work_struct bms_update_work;
struct work_struct pl_update_work;
struct work_struct jeita_update_work;
struct work_struct moisture_protection_work;
struct work_struct chg_termination_work;
struct work_struct dcin_aicl_work;
struct work_struct cp_status_change_work;
struct delayed_work ps_change_timeout_work;
struct delayed_work clear_hdc_work;
struct delayed_work icl_change_work;
struct delayed_work pl_enable_work;
struct delayed_work uusb_otg_work;
struct delayed_work bb_removal_work;
struct delayed_work lpd_ra_open_work;
struct delayed_work lpd_detach_work;
struct delayed_work thermal_regulation_work;
struct delayed_work usbov_dbc_work;
struct delayed_work pr_swap_detach_work;
struct delayed_work pr_lock_clear_work;
struct delayed_work role_reversal_check;
struct alarm lpd_recheck_timer;
struct alarm moisture_protection_alarm;
struct alarm chg_termination_alarm;
struct alarm dcin_aicl_alarm;
struct timer_list apsd_timer;
struct charger_param chg_param;
/* secondary charger config */
bool sec_pl_present;
bool sec_cp_present;
int sec_chg_selected;
int cp_reason;
int cp_topo;
/* pd */
int voltage_min_uv;
int voltage_max_uv;
int pd_active;
bool pd_hard_reset;
bool pr_lock_in_progress;
bool pr_swap_in_progress;
bool early_usb_attach;
bool ok_to_pd;
bool typec_legacy;
bool typec_irq_en;
bool typec_role_swap_failed;
/* cached status */
bool system_suspend_supported;
int boost_threshold_ua;
int system_temp_level;
int thermal_levels;
int *thermal_mitigation;
int dcp_icl_ua;
int fake_capacity;
int fake_batt_status;
bool step_chg_enabled;
bool sw_jeita_enabled;
bool typec_legacy_use_rp_icl;
bool is_hdc;
bool chg_done;
int connector_type;
bool otg_en;
bool suspend_input_on_debug_batt;
bool fake_chg_status_on_debug_batt;
int default_icl_ua;
int otg_cl_ua;
bool uusb_apsd_rerun_done;
bool typec_present;
int fake_input_current_limited;
int typec_mode;
int dr_mode;
int usb_icl_change_irq_enabled;
u32 jeita_status;
u8 float_cfg;
bool jeita_arb_flag;
bool use_extcon;
bool otg_present;
bool hvdcp_disable;
int hw_max_icl_ua;
int auto_recharge_soc;
enum sink_src_mode sink_src_mode;
enum power_supply_typec_power_role power_role;
enum jeita_cfg_stat jeita_configured;
int charger_temp_max;
int smb_temp_max;
u8 typec_try_mode;
enum lpd_stage lpd_stage;
bool lpd_disabled;
enum lpd_reason lpd_reason;
bool fcc_stepper_enable;
int die_temp;
int smb_temp;
int skin_temp;
int connector_temp;
int thermal_status;
int main_fcc_max;
u32 jeita_soft_thlds[2];
u32 jeita_soft_hys_thlds[2];
int jeita_soft_fcc[2];
int jeita_soft_fv[2];
bool moisture_present;
bool uusb_moisture_protection_capable;
bool uusb_moisture_protection_enabled;
bool hw_die_temp_mitigation;
bool hw_connector_mitigation;
bool hw_skin_temp_mitigation;
bool en_skin_therm_mitigation;
int connector_pull_up;
int smb_pull_up;
int aicl_5v_threshold_mv;
int default_aicl_5v_threshold_mv;
int aicl_cont_threshold_mv;
int default_aicl_cont_threshold_mv;
bool aicl_max_reached;
int charge_full_cc;
int cc_soc_ref;
int last_cc_soc;
int term_vbat_uv;
int usbin_forced_max_uv;
int init_thermal_ua;
u32 comp_clamp_level;
int wls_icl_ua;
int cutoff_count;
bool dcin_aicl_done;
bool hvdcp3_standalone_config;
bool dcin_icl_user_set;
bool dpdm_enabled;
bool apsd_ext_timeout;
bool qc3p5_detected;
/* workaround flag */
u32 wa_flags;
int boost_current_ua;
int qc2_max_pulses;
enum qc2_non_comp_voltage qc2_unsupported_voltage;
bool dbc_usbov;
/* extcon for VBUS / ID notification to USB for uUSB */
struct extcon_dev *extcon;
/* battery profile */
int batt_profile_fcc_ua;
int batt_profile_fv_uv;
int usb_icl_delta_ua;
int pulse_cnt;
int die_health;
int connector_health;
/* flash */
u32 flash_derating_soc;
u32 flash_disable_soc;
u32 headroom_mode;
bool flash_init_done;
bool flash_active;
u32 irq_status;
/* wireless */
int dcin_uv_count;
ktime_t dcin_uv_last_time;
int last_wls_vout;
};
int smblib_read(struct smb_charger *chg, u16 addr, u8 *val);
int smblib_masked_write(struct smb_charger *chg, u16 addr, u8 mask, u8 val);
int smblib_write(struct smb_charger *chg, u16 addr, u8 val);
int smblib_batch_write(struct smb_charger *chg, u16 addr, u8 *val, int count);
int smblib_batch_read(struct smb_charger *chg, u16 addr, u8 *val, int count);
int smblib_get_charge_param(struct smb_charger *chg,
struct smb_chg_param *param, int *val_u);
int smblib_get_usb_suspend(struct smb_charger *chg, int *suspend);
int smblib_get_aicl_cont_threshold(struct smb_chg_param *param, u8 val_raw);
int smblib_enable_charging(struct smb_charger *chg, bool enable);
int smblib_set_charge_param(struct smb_charger *chg,
struct smb_chg_param *param, int val_u);
int smblib_set_usb_suspend(struct smb_charger *chg, bool suspend);
int smblib_set_dc_suspend(struct smb_charger *chg, bool suspend);
int smblib_mapping_soc_from_field_value(struct smb_chg_param *param,
int val_u, u8 *val_raw);
int smblib_mapping_cc_delta_to_field_value(struct smb_chg_param *param,
u8 val_raw);
int smblib_mapping_cc_delta_from_field_value(struct smb_chg_param *param,
int val_u, u8 *val_raw);
int smblib_set_chg_freq(struct smb_chg_param *param,
int val_u, u8 *val_raw);
int smblib_set_prop_boost_current(struct smb_charger *chg,
const union power_supply_propval *val);
int smblib_set_aicl_cont_threshold(struct smb_chg_param *param,
int val_u, u8 *val_raw);
int smblib_vbus_regulator_enable(struct regulator_dev *rdev);
int smblib_vbus_regulator_disable(struct regulator_dev *rdev);
int smblib_vbus_regulator_is_enabled(struct regulator_dev *rdev);
int smblib_vconn_regulator_enable(struct regulator_dev *rdev);
int smblib_vconn_regulator_disable(struct regulator_dev *rdev);
int smblib_vconn_regulator_is_enabled(struct regulator_dev *rdev);
irqreturn_t default_irq_handler(int irq, void *data);
irqreturn_t smb_en_irq_handler(int irq, void *data);
irqreturn_t chg_state_change_irq_handler(int irq, void *data);
irqreturn_t batt_temp_changed_irq_handler(int irq, void *data);
irqreturn_t batt_psy_changed_irq_handler(int irq, void *data);
irqreturn_t usbin_uv_irq_handler(int irq, void *data);
irqreturn_t usb_plugin_irq_handler(int irq, void *data);
irqreturn_t usb_source_change_irq_handler(int irq, void *data);
irqreturn_t icl_change_irq_handler(int irq, void *data);
irqreturn_t typec_state_change_irq_handler(int irq, void *data);
irqreturn_t typec_attach_detach_irq_handler(int irq, void *data);
irqreturn_t dcin_uv_irq_handler(int irq, void *data);
irqreturn_t dc_plugin_irq_handler(int irq, void *data);
irqreturn_t high_duty_cycle_irq_handler(int irq, void *data);
irqreturn_t switcher_power_ok_irq_handler(int irq, void *data);
irqreturn_t wdog_snarl_irq_handler(int irq, void *data);
irqreturn_t wdog_bark_irq_handler(int irq, void *data);
irqreturn_t typec_or_rid_detection_change_irq_handler(int irq, void *data);
irqreturn_t temp_change_irq_handler(int irq, void *data);
irqreturn_t usbin_ov_irq_handler(int irq, void *data);
irqreturn_t sdam_sts_change_irq_handler(int irq, void *data);
int smblib_get_prop_input_suspend(struct smb_charger *chg,
union power_supply_propval *val);
int smblib_get_prop_batt_present(struct smb_charger *chg,
union power_supply_propval *val);
int smblib_get_prop_batt_capacity(struct smb_charger *chg,
union power_supply_propval *val);
int smblib_get_prop_batt_status(struct smb_charger *chg,
union power_supply_propval *val);
int smblib_get_prop_batt_charge_type(struct smb_charger *chg,
union power_supply_propval *val);
int smblib_get_prop_batt_charge_done(struct smb_charger *chg,
union power_supply_propval *val);
int smblib_get_batt_current_now(struct smb_charger *chg,
union power_supply_propval *val);
int smblib_get_prop_batt_health(struct smb_charger *chg,
union power_supply_propval *val);
int smblib_get_prop_system_temp_level(struct smb_charger *chg,
union power_supply_propval *val);
int smblib_get_prop_system_temp_level_max(struct smb_charger *chg,
union power_supply_propval *val);
int smblib_get_prop_input_current_limited(struct smb_charger *chg,
union power_supply_propval *val);
int smblib_get_prop_batt_iterm(struct smb_charger *chg,
union power_supply_propval *val);
int smblib_set_prop_input_suspend(struct smb_charger *chg,
const union power_supply_propval *val);
int smblib_set_prop_batt_capacity(struct smb_charger *chg,
const union power_supply_propval *val);
int smblib_set_prop_batt_status(struct smb_charger *chg,
const union power_supply_propval *val);
int smblib_set_prop_system_temp_level(struct smb_charger *chg,
const union power_supply_propval *val);
int smblib_set_prop_input_current_limited(struct smb_charger *chg,
const union power_supply_propval *val);
int smblib_get_prop_dc_present(struct smb_charger *chg,
union power_supply_propval *val);
int smblib_get_prop_dc_online(struct smb_charger *chg,
union power_supply_propval *val);
int smblib_get_prop_dc_current_max(struct smb_charger *chg,
union power_supply_propval *val);
int smblib_set_prop_dc_current_max(struct smb_charger *chg,
const union power_supply_propval *val);
int smblib_get_prop_dc_voltage_now(struct smb_charger *chg,
union power_supply_propval *val);
int smblib_get_prop_dc_voltage_max(struct smb_charger *chg,
union power_supply_propval *val);
int smblib_get_prop_voltage_wls_output(struct smb_charger *chg,
union power_supply_propval *val);
int smblib_set_prop_voltage_wls_output(struct smb_charger *chg,
const union power_supply_propval *val);
int smblib_set_prop_dc_reset(struct smb_charger *chg);
int smblib_get_prop_usb_present(struct smb_charger *chg,
union power_supply_propval *val);
int smblib_get_prop_usb_online(struct smb_charger *chg,
union power_supply_propval *val);
int smblib_get_usb_online(struct smb_charger *chg,
union power_supply_propval *val);
int smblib_get_prop_usb_suspend(struct smb_charger *chg,
union power_supply_propval *val);
int smblib_get_prop_usb_voltage_max(struct smb_charger *chg,
union power_supply_propval *val);
int smblib_get_prop_usb_voltage_max_design(struct smb_charger *chg,
union power_supply_propval *val);
int smblib_set_prop_usb_voltage_max_limit(struct smb_charger *chg,
const union power_supply_propval *val);
int smblib_get_prop_usb_voltage_now(struct smb_charger *chg,
union power_supply_propval *val);
int smblib_get_prop_low_power(struct smb_charger *chg,
union power_supply_propval *val);
int smblib_get_prop_usb_current_now(struct smb_charger *chg,
union power_supply_propval *val);
int smblib_get_usb_prop_typec_mode(struct smb_charger *chg,
union power_supply_propval *val);
int smblib_get_prop_typec_cc_orientation(struct smb_charger *chg,
union power_supply_propval *val);
int smblib_get_prop_scope(struct smb_charger *chg,
union power_supply_propval *val);
int smblib_get_prop_typec_select_rp(struct smb_charger *chg,
union power_supply_propval *val);
int smblib_get_prop_typec_power_role(struct smb_charger *chg,
union power_supply_propval *val);
int smblib_get_prop_input_current_settled(struct smb_charger *chg,
union power_supply_propval *val);
int smblib_get_prop_input_voltage_settled(struct smb_charger *chg,
union power_supply_propval *val);
int smblib_get_prop_pd_in_hard_reset(struct smb_charger *chg,
union power_supply_propval *val);
int smblib_get_pe_start(struct smb_charger *chg,
union power_supply_propval *val);
int smblib_get_prop_charger_temp(struct smb_charger *chg,
union power_supply_propval *val);
int smblib_get_prop_die_health(struct smb_charger *chg);
int smblib_get_die_health(struct smb_charger *chg,
union power_supply_propval *val);
int smblib_get_prop_smb_health(struct smb_charger *chg);
int smblib_get_prop_connector_health(struct smb_charger *chg);
int smblib_set_prop_thermal_overheat(struct smb_charger *chg,
int therm_overheat);
int smblib_get_skin_temp_status(struct smb_charger *chg);
int smblib_get_prop_vph_voltage_now(struct smb_charger *chg,
union power_supply_propval *val);
int smblib_set_prop_pd_current_max(struct smb_charger *chg,
const union power_supply_propval *val);
int smblib_set_prop_sdp_current_max(struct smb_charger *chg,
const union power_supply_propval *val);
int smblib_set_prop_pd_voltage_max(struct smb_charger *chg,
const union power_supply_propval *val);
int smblib_set_prop_pd_voltage_min(struct smb_charger *chg,
const union power_supply_propval *val);
int smblib_set_prop_typec_power_role(struct smb_charger *chg,
const union power_supply_propval *val);
int smblib_set_prop_typec_select_rp(struct smb_charger *chg,
const union power_supply_propval *val);
int smblib_set_prop_pd_active(struct smb_charger *chg,
const union power_supply_propval *val);
int smblib_set_prop_pd_in_hard_reset(struct smb_charger *chg,
const union power_supply_propval *val);
int smblib_set_prop_ship_mode(struct smb_charger *chg,
const union power_supply_propval *val);
int smblib_set_prop_rechg_soc_thresh(struct smb_charger *chg,
const union power_supply_propval *val);
void smblib_suspend_on_debug_battery(struct smb_charger *chg);
int smblib_rerun_apsd_if_required(struct smb_charger *chg);
void smblib_rerun_apsd(struct smb_charger *chg);
int smblib_get_prop_fcc_delta(struct smb_charger *chg,
union power_supply_propval *val);
int smblib_get_thermal_threshold(struct smb_charger *chg, u16 addr, int *val);
int smblib_dp_dm(struct smb_charger *chg, int val);
int smblib_disable_hw_jeita(struct smb_charger *chg, bool disable);
int smblib_run_aicl(struct smb_charger *chg, int type);
int smblib_set_icl_current(struct smb_charger *chg, int icl_ua);
int smblib_get_icl_current(struct smb_charger *chg, int *icl_ua);
int smblib_get_charge_current(struct smb_charger *chg, int *total_current_ua);
int smblib_get_prop_pr_swap_in_progress(struct smb_charger *chg,
union power_supply_propval *val);
int smblib_set_prop_pr_swap_in_progress(struct smb_charger *chg,
const union power_supply_propval *val);
int smblib_typec_port_type_set(const struct typec_capability *cap,
enum typec_port_type type);
int smblib_get_prop_from_bms(struct smb_charger *chg,
enum power_supply_property psp,
union power_supply_propval *val);
int smblib_get_iio_channel(struct smb_charger *chg, const char *propname,
struct iio_channel **chan);
int smblib_read_iio_channel(struct smb_charger *chg, struct iio_channel *chan,
int div, int *data);
int smblib_configure_hvdcp_apsd(struct smb_charger *chg, bool enable);
int smblib_icl_override(struct smb_charger *chg, enum icl_override_mode mode);
enum alarmtimer_restart smblib_lpd_recheck_timer(struct alarm *alarm,
ktime_t time);
int smblib_toggle_smb_en(struct smb_charger *chg, int toggle);
void smblib_hvdcp_detect_enable(struct smb_charger *chg, bool enable);
void smblib_hvdcp_hw_inov_enable(struct smb_charger *chg, bool enable);
void smblib_hvdcp_exit_config(struct smb_charger *chg);
void smblib_apsd_enable(struct smb_charger *chg, bool enable);
int smblib_force_vbus_voltage(struct smb_charger *chg, u8 val);
int smblib_get_irq_status(struct smb_charger *chg,
union power_supply_propval *val);
int smblib_get_qc3_main_icl_offset(struct smb_charger *chg, int *offset_ua);
int smblib_init(struct smb_charger *chg);
int smblib_deinit(struct smb_charger *chg);
#endif /* __SMB5_CHARGER_H */

View file

@ -0,0 +1,557 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2018-2020 The Linux Foundation. All rights reserved.
*/
#ifndef __SMB5_CHARGER_REG_H
#define __SMB5_CHARGER_REG_H
#include <linux/bitops.h>
#define CHGR_BASE 0x1000
#define DCDC_BASE 0x1100
#define BATIF_BASE 0x1200
#define USBIN_BASE 0x1300
#define DCIN_BASE 0x1400
#define TYPEC_BASE 0X1500
#define MISC_BASE 0x1600
#define MISC_PBS_BASE 0x7500
#define PERPH_TYPE_OFFSET 0x04
#define TYPE_MASK GENMASK(7, 0)
#define PERPH_SUBTYPE_OFFSET 0x05
#define SUBTYPE_MASK GENMASK(7, 0)
#define INT_RT_STS_OFFSET 0x10
#define SDAM_TYPE 0x2E
/********************************
* CHGR Peripheral Registers *
********************************/
#define BATTERY_CHARGER_STATUS_1_REG (CHGR_BASE + 0x06)
#define BATTERY_CHARGER_STATUS_MASK GENMASK(2, 0)
enum {
INHIBIT_CHARGE = 0,
TRICKLE_CHARGE,
PRE_CHARGE,
FULLON_CHARGE,
TAPER_CHARGE,
TERMINATE_CHARGE,
PAUSE_CHARGE,
DISABLE_CHARGE,
};
#define BATTERY_CHARGER_STATUS_2_REG (CHGR_BASE + 0x07)
#define CHARGER_ERROR_STATUS_BAT_OV_BIT BIT(1)
#define BATTERY_CHARGER_STATUS_5_REG (CHGR_BASE + 0x0B)
#define ENABLE_TRICKLE_BIT BIT(2)
#define ENABLE_PRE_CHARGING_BIT BIT(1)
#define ENABLE_FULLON_MODE_BIT BIT(0)
#define BATTERY_CHARGER_STATUS_7_REG (CHGR_BASE + 0x0D)
#define BAT_TEMP_STATUS_SOFT_LIMIT_MASK GENMASK(5, 4)
#define BAT_TEMP_STATUS_HOT_SOFT_BIT BIT(5)
#define BAT_TEMP_STATUS_COLD_SOFT_BIT BIT(4)
#define BAT_TEMP_STATUS_HARD_LIMIT_MASK GENMASK(3, 2)
#define BAT_TEMP_STATUS_TOO_HOT_BIT BIT(3)
#define BAT_TEMP_STATUS_TOO_COLD_BIT BIT(2)
#define BAT_TEMP_STATUS_TOO_HOT_AFP_BIT BIT(1)
#define BAT_TEMP_STATUS_TOO_COLD_AFP_BIT BIT(0)
#define CHARGING_ENABLE_CMD_REG (CHGR_BASE + 0x42)
#define CHARGING_ENABLE_CMD_BIT BIT(0)
#define CHARGING_PAUSE_CMD_REG (CHGR_BASE + 0x43)
#define CHARGING_PAUSE_CMD_BIT BIT(0)
#define CHGR_CFG2_REG (CHGR_BASE + 0x51)
#define RECHG_MASK GENMASK(2, 1)
#define VBAT_BASED_RECHG_BIT BIT(2)
#define SOC_BASED_RECHG_BIT GENMASK(2, 1)
#define CHARGER_INHIBIT_BIT BIT(0)
#define CHGR_FAST_CHARGE_CURRENT_CFG_REG (CHGR_BASE + 0x61)
#define CHGR_ADC_ITERM_UP_THD_MSB_REG (CHGR_BASE + 0x67)
#define CHGR_ADC_ITERM_UP_THD_LSB_REG (CHGR_BASE + 0x68)
#define CHGR_ADC_ITERM_LO_THD_MSB_REG (CHGR_BASE + 0x69)
#define CHGR_ADC_ITERM_LO_THD_LSB_REG (CHGR_BASE + 0x6A)
#define CHGR_NO_SAMPLE_TERM_RCHG_CFG_REG (CHGR_BASE + 0x6B)
#define NO_OF_SAMPLE_FOR_RCHG_SHIFT 2
#define NO_OF_SAMPLE_FOR_RCHG GENMASK(3, 2)
#define CHGR_ADC_TERM_CFG_REG (CHGR_BASE + 0x6C)
#define TERM_BASED_ON_SYNC_CONV_OR_SAMPLE_CNT BIT(0)
#define TERM_BASED_ON_SYNC_CONV 0
#define TERM_BASED_ON_SAMPLE_CNT 1
#define CHGR_FLOAT_VOLTAGE_CFG_REG (CHGR_BASE + 0x70)
#define CHARGE_INHIBIT_THRESHOLD_CFG_REG (CHGR_BASE + 0x72)
#define CHARGE_INHIBIT_THRESHOLD_MASK GENMASK(1, 0)
#define INHIBIT_ANALOG_VFLT_MINUS_50MV 0
#define INHIBIT_ANALOG_VFLT_MINUS_100MV 1
#define INHIBIT_ANALOG_VFLT_MINUS_200MV 2
#define INHIBIT_ANALOG_VFLT_MINUS_300MV 3
#define CHARGE_RCHG_SOC_THRESHOLD_CFG_REG (CHGR_BASE + 0x7D)
#define CHGR_ADC_RECHARGE_THRESHOLD_MSB_REG (CHGR_BASE + 0x7E)
#define CHGR_ADC_RECHARGE_THRESHOLD_LSB_REG (CHGR_BASE + 0x7F)
#define JEITA_EN_CFG_REG (CHGR_BASE + 0x90)
#define JEITA_EN_HOT_SL_FCV_BIT BIT(3)
#define JEITA_EN_COLD_SL_FCV_BIT BIT(2)
#define JEITA_EN_HOT_SL_CCC_BIT BIT(1)
#define JEITA_EN_COLD_SL_CCC_BIT BIT(0)
#define JEITA_CCCOMP_CFG_HOT_REG (CHGR_BASE + 0x92)
#define JEITA_CCCOMP_CFG_COLD_REG (CHGR_BASE + 0x93)
#define CHGR_JEITA_THRESHOLD_BASE_REG(i) (CHGR_BASE + 0x94 + (i * 4))
#define CHGR_JEITA_HOT_THRESHOLD_MSB_REG CHGR_JEITA_THRESHOLD_BASE_REG(0)
#define CHGR_FAST_CHARGE_SAFETY_TIMER_CFG_REG (CHGR_BASE + 0xA2)
#define FAST_CHARGE_SAFETY_TIMER_192_MIN 0x0
#define FAST_CHARGE_SAFETY_TIMER_384_MIN 0x1
#define FAST_CHARGE_SAFETY_TIMER_768_MIN 0x2
#define FAST_CHARGE_SAFETY_TIMER_1536_MIN 0x3
#define CHGR_ENG_CHARGING_CFG_REG (CHGR_BASE + 0xC0)
#define CHGR_ITERM_USE_ANALOG_BIT BIT(3)
/********************************
* DCDC Peripheral Registers *
********************************/
#define ICL_MAX_STATUS_REG (DCDC_BASE + 0x06)
#define ICL_STATUS_REG (DCDC_BASE + 0x07)
#define AICL_ICL_STATUS_REG (DCDC_BASE + 0x08)
#define AICL_STATUS_REG (DCDC_BASE + 0x0A)
#define SOFT_ILIMIT_BIT BIT(6)
#define AICL_DONE_BIT BIT(0)
#define POWER_PATH_STATUS_REG (DCDC_BASE + 0x0B)
#define USBIN_SUSPEND_STS_BIT BIT(6)
#define USE_USBIN_BIT BIT(4)
#define USE_DCIN_BIT BIT(3)
#define POWER_PATH_MASK GENMASK(2, 1)
#define VALID_INPUT_POWER_SOURCE_STS_BIT BIT(0)
#define DCDC_CMD_OTG_REG (DCDC_BASE + 0x40)
#define OTG_EN_BIT BIT(0)
#define DCDC_FSW_SEL_REG (DCDC_BASE + 0x50)
#define DCDC_OTG_CURRENT_LIMIT_CFG_REG (DCDC_BASE + 0x52)
#define DCDC_OTG_CFG_REG (DCDC_BASE + 0x53)
#define OTG_EN_SRC_CFG_BIT BIT(1)
#define OTG_FAULT_CONDITION_CFG_REG (DCDC_BASE + 0x56)
#define USBIN_MID_COMP_FAULT_EN_BIT BIT(5)
#define USBIN_COLLAPSE_FAULT_EN_BIT BIT(4)
#define DCDC_CFG_REF_MAX_PSNS_REG (DCDC_BASE + 0x8C)
#define DCDC_ENG_SDCDC_CFG5_REG (DCDC_BASE + 0xC4)
#define ENG_SDCDC_BAT_HPWR_MASK GENMASK(7, 6)
enum {
BOOST_MODE_THRESH_3P3_V,
BOOST_MODE_THRESH_3P4_V = 0x40,
BOOST_MODE_THRESH_3P5_V = 0x80,
BOOST_MODE_THRESH_3P6_V = 0xC0
};
/********************************
* BATIF Peripheral Registers *
********************************/
/* BATIF Interrupt Bits */
#define VPH_OV_RT_STS_BIT BIT(7)
#define BUCK_OC_RT_STS_BIT BIT(6)
#define BAT_TERMINAL_MISSING_RT_STS_BIT BIT(5)
#define BAT_THERM_OR_ID_MISSING_RT_STS_BIT BIT(4)
#define BAT_LOW_RT_STS_BIT BIT(3)
#define BAT_OV_RT_STS_BIT BIT(2)
#define ALL_CHNL_CONV_DONE_RT_STS BIT(1)
#define BAT_TEMP_RT_STS_BIT BIT(0)
#define SHIP_MODE_REG (BATIF_BASE + 0x40)
#define SHIP_MODE_EN_BIT BIT(0)
#define BATIF_ADC_CHANNEL_EN_REG (BATIF_BASE + 0x82)
#define IBATT_CHANNEL_EN_BIT BIT(6)
#define CONN_THM_CHANNEL_EN_BIT BIT(4)
#define DIE_TEMP_CHANNEL_EN_BIT BIT(2)
#define MISC_THM_CHANNEL_EN_BIT BIT(1)
#define BATIF_ADC_INTERNAL_PULL_UP_REG (BATIF_BASE + 0x86)
#define INTERNAL_PULL_UP_CONN_THM_MASK GENMASK(5, 4)
#define CONN_THM_SHIFT 4
#define INTERNAL_PULL_NO_PULL 0x00
#define INTERNAL_PULL_30K_PULL 0x01
#define INTERNAL_PULL_100K_PULL 0x02
#define INTERNAL_PULL_400K_PULL 0x03
/********************************
* USBIN Peripheral Registers *
********************************/
#define APSD_STATUS_REG (USBIN_BASE + 0x07)
#define APSD_STATUS_7_BIT BIT(7)
#define HVDCP_CHECK_TIMEOUT_BIT BIT(6)
#define SLOW_PLUGIN_TIMEOUT_BIT BIT(5)
#define ENUMERATION_DONE_BIT BIT(4)
#define VADP_CHANGE_DONE_AFTER_AUTH_BIT BIT(3)
#define QC_AUTH_DONE_STATUS_BIT BIT(2)
#define QC_CHARGER_BIT BIT(1)
#define APSD_DTC_STATUS_DONE_BIT BIT(0)
#define APSD_RESULT_STATUS_REG (USBIN_BASE + 0x08)
#define APSD_RESULT_STATUS_7_BIT BIT(7)
#define APSD_RESULT_STATUS_MASK GENMASK(6, 0)
#define QC_3P0_BIT BIT(6)
#define QC_2P0_BIT BIT(5)
#define FLOAT_CHARGER_BIT BIT(4)
#define DCP_CHARGER_BIT BIT(3)
#define CDP_CHARGER_BIT BIT(2)
#define OCP_CHARGER_BIT BIT(1)
#define SDP_CHARGER_BIT BIT(0)
#define QC_CHANGE_STATUS_REG (USBIN_BASE + 0x09)
#define QC_12V_BIT BIT(2)
#define QC_9V_BIT BIT(1)
#define QC_5V_BIT BIT(0)
#define QC_2P0_STATUS_MASK GENMASK(2, 0)
/* USBIN Interrupt Bits */
#define USBIN_ICL_CHANGE_RT_STS_BIT BIT(7)
#define USBIN_SOURCE_CHANGE_RT_STS_BIT BIT(6)
#define USBIN_REVI_RT_STS_BIT BIT(5)
#define USBIN_PLUGIN_RT_STS_BIT BIT(4)
#define USBIN_OV_RT_STS_BIT BIT(3)
#define USBIN_UV_RT_STS_BIT BIT(2)
#define USBIN_VASHDN_RT_STS_BIT BIT(1)
#define USBIN_COLLAPSE_RT_STS_BIT BIT(0)
#define USBIN_CMD_IL_REG (USBIN_BASE + 0x40)
#define USBIN_SUSPEND_BIT BIT(0)
#define CMD_APSD_REG (USBIN_BASE + 0x41)
#define APSD_RERUN_BIT BIT(0)
#define CMD_ICL_OVERRIDE_REG (USBIN_BASE + 0x42)
#define ICL_OVERRIDE_BIT BIT(0)
#define CMD_HVDCP_2_REG (USBIN_BASE + 0x43)
#define FORCE_12V_BIT BIT(5)
#define FORCE_9V_BIT BIT(4)
#define FORCE_5V_BIT BIT(3)
#define IDLE_BIT BIT(2)
#define SINGLE_DECREMENT_BIT BIT(1)
#define SINGLE_INCREMENT_BIT BIT(0)
#define USBIN_ADAPTER_ALLOW_OVERRIDE_REG (USBIN_BASE + 0x44)
#define CONTINUOUS BIT(3)
#define FORCE_12V BIT(2)
#define FORCE_9V BIT(1)
#define FORCE_5V BIT(0)
#define FORCE_NULL 0
#define USB_CMD_PULLDOWN_REG (USBIN_BASE + 0x45)
#define EN_PULLDOWN_USB_IN_BIT BIT(0)
#define TYPE_C_CFG_REG (USBIN_BASE + 0x58)
#define BC1P2_START_ON_CC_BIT BIT(7)
#define HVDCP_PULSE_COUNT_MAX_REG (USBIN_BASE + 0x5B)
#define HVDCP_PULSE_COUNT_MAX_QC2_MASK GENMASK(7, 6)
enum {
HVDCP_PULSE_COUNT_MAX_QC2_5V = 0,
HVDCP_PULSE_COUNT_MAX_QC2_9V = 0x40,
HVDCP_PULSE_COUNT_MAX_QC2_12V = 0x80,
HVDCP_PULSE_COUNT_MAX_QC2_INVALID = 0xC0
};
#define USBIN_OPTIONS_1_CFG_REG (USBIN_BASE + 0x62)
#define HVDCP_AUTH_ALG_EN_CFG_BIT BIT(6)
#define HVDCP_AUTONOMOUS_MODE_EN_CFG_BIT BIT(5)
#define BC1P2_SRC_DETECT_BIT BIT(3)
#define HVDCP_EN_BIT BIT(2)
#define USBIN_OPTIONS_2_CFG_REG (USBIN_BASE + 0x63)
#define DCD_TIMEOUT_SEL_BIT BIT(5)
#define FLOAT_OPTIONS_MASK GENMASK(2, 0)
#define FLOAT_DIS_CHGING_CFG_BIT BIT(2)
#define SUSPEND_FLOAT_CFG_BIT BIT(1)
#define FORCE_FLOAT_SDP_CFG_BIT BIT(0)
#define USBIN_LOAD_CFG_REG (USBIN_BASE + 0x65)
#define ICL_OVERRIDE_AFTER_APSD_BIT BIT(4)
#define USBIN_AICL_STEP_TIMING_SEL_MASK GENMASK(3, 2)
#define USBIN_IN_COLLAPSE_GF_SEL_MASK GENMASK(1, 0)
#define USBIN_ICL_OPTIONS_REG (USBIN_BASE + 0x66)
#define CFG_USB3P0_SEL_BIT BIT(2)
#define USB51_MODE_BIT BIT(1)
#define USBIN_MODE_CHG_BIT BIT(0)
#define USBIN_CURRENT_LIMIT_CFG_REG (USBIN_BASE + 0x70)
#define USBIN_AICL_OPTIONS_CFG_REG (USBIN_BASE + 0x80)
#define SUSPEND_ON_COLLAPSE_USBIN_BIT BIT(7)
#define USBIN_AICL_PERIODIC_RERUN_EN_BIT BIT(4)
#define USBIN_AICL_ADC_EN_BIT BIT(3)
#define USBIN_AICL_EN_BIT BIT(2)
#define USB_ENG_SSUPPLY_USB2_REG (USBIN_BASE + 0xC0)
#define ENG_SSUPPLY_12V_OV_OPT_BIT BIT(1)
#define USBIN_5V_AICL_THRESHOLD_REG (USBIN_BASE + 0x81)
#define USBIN_CONT_AICL_THRESHOLD_REG (USBIN_BASE + 0x84)
/********************************
* DCIN Peripheral Registers *
********************************/
/* DCIN Interrupt Bits */
#define DCIN_PLUGIN_RT_STS_BIT BIT(4)
#define DCIN_CMD_IL_REG (DCIN_BASE + 0x40)
#define DCIN_SUSPEND_BIT BIT(0)
#define DCIN_EN_OVERRIDE_BIT BIT(1)
#define DCIN_EN_MASK GENMASK(2, 1)
#define DCIN_CMD_PON_REG (DCIN_BASE + 0x45)
#define DCIN_PON_BIT BIT(0)
#define MID_CHG_BIT BIT(1)
#define DCIN_LOAD_CFG_REG (DCIN_BASE + 0x65)
#define INPUT_MISS_POLL_EN_BIT BIT(5)
/********************************
* TYPEC Peripheral Registers *
********************************/
#define TYPE_C_SNK_STATUS_REG (TYPEC_BASE + 0x06)
#define DETECTED_SRC_TYPE_MASK GENMASK(6, 0)
#define SNK_DAM_500MA_BIT BIT(6)
#define SNK_DAM_1500MA_BIT BIT(5)
#define SNK_DAM_3000MA_BIT BIT(4)
#define SNK_RP_STD_BIT BIT(3)
#define SNK_RP_1P5_BIT BIT(2)
#define SNK_RP_3P0_BIT BIT(1)
#define SNK_RP_SHORT_BIT BIT(0)
#define TYPE_C_SRC_STATUS_REG (TYPEC_BASE + 0x08)
#define DETECTED_SNK_TYPE_MASK GENMASK(4, 0)
#define SRC_HIGH_BATT_BIT BIT(5)
#define SRC_DEBUG_ACCESS_BIT BIT(4)
#define SRC_RD_OPEN_BIT BIT(3)
#define SRC_RD_RA_VCONN_BIT BIT(2)
#define SRC_RA_OPEN_BIT BIT(1)
#define AUDIO_ACCESS_RA_RA_BIT BIT(0)
#define TYPE_C_STATE_MACHINE_STATUS_REG (TYPEC_BASE + 0x09)
#define TYPEC_ATTACH_DETACH_STATE_BIT BIT(5)
#define TYPE_C_MISC_STATUS_REG (TYPEC_BASE + 0x0B)
#define TYPEC_WATER_DETECTION_STATUS_BIT BIT(7)
#define SNK_SRC_MODE_BIT BIT(6)
#define TYPEC_VBUS_ERROR_STATUS_BIT BIT(4)
#define TYPEC_TCCDEBOUNCE_DONE_STATUS_BIT BIT(3)
#define CC_ORIENTATION_BIT BIT(1)
#define CC_ATTACHED_BIT BIT(0)
#define LEGACY_CABLE_STATUS_REG (TYPEC_BASE + 0x0D)
#define TYPEC_LEGACY_CABLE_STATUS_BIT BIT(1)
#define TYPEC_NONCOMP_LEGACY_CABLE_STATUS_BIT BIT(0)
#define TYPEC_U_USB_STATUS_REG (TYPEC_BASE + 0x0F)
#define U_USB_GROUND_NOVBUS_BIT BIT(6)
#define U_USB_GROUND_BIT BIT(4)
#define U_USB_FMB1_BIT BIT(3)
#define U_USB_FLOAT1_BIT BIT(2)
#define U_USB_FMB2_BIT BIT(1)
#define U_USB_FLOAT2_BIT BIT(0)
#define TYPE_C_MODE_CFG_REG (TYPEC_BASE + 0x44)
#define TYPEC_TRY_MODE_MASK GENMASK(4, 3)
#define EN_TRY_SNK_BIT BIT(4)
#define EN_TRY_SRC_BIT BIT(3)
#define TYPEC_POWER_ROLE_CMD_MASK GENMASK(2, 0)
#define EN_SRC_ONLY_BIT BIT(2)
#define EN_SNK_ONLY_BIT BIT(1)
#define TYPEC_DISABLE_CMD_BIT BIT(0)
#define TYPE_C_VCONN_CONTROL_REG (TYPEC_BASE + 0x46)
#define VCONN_EN_ORIENTATION_BIT BIT(2)
#define VCONN_EN_VALUE_BIT BIT(1)
#define VCONN_EN_SRC_BIT BIT(0)
#define TYPE_C_CCOUT_CONTROL_REG (TYPEC_BASE + 0x48)
#define TYPEC_CCOUT_BUFFER_EN_BIT BIT(2)
#define TYPEC_CCOUT_VALUE_BIT BIT(1)
#define TYPEC_CCOUT_SRC_BIT BIT(0)
#define DEBUG_ACCESS_SRC_CFG_REG (TYPEC_BASE + 0x4C)
#define EN_UNORIENTED_DEBUG_ACCESS_SRC_BIT BIT(0)
#define TYPE_C_CRUDE_SENSOR_CFG_REG (TYPEC_BASE + 0x4e)
#define EN_SRC_CRUDE_SENSOR_BIT BIT(1)
#define EN_SNK_CRUDE_SENSOR_BIT BIT(0)
#define TYPE_C_EXIT_STATE_CFG_REG (TYPEC_BASE + 0x50)
#define BYPASS_VSAFE0V_DURING_ROLE_SWAP_BIT BIT(3)
#define SEL_SRC_UPPER_REF_BIT BIT(2)
#define EXIT_SNK_BASED_ON_CC_BIT BIT(0)
#define TYPE_C_CURRSRC_CFG_REG (TYPEC_BASE + 0x52)
#define TYPEC_SRC_RP_SEL_MASK GENMASK(1, 0)
enum {
TYPEC_SRC_RP_STD,
TYPEC_SRC_RP_1P5A,
TYPEC_SRC_RP_3A,
TYPEC_SRC_RP_3A_DUPLICATE,
TYPEC_SRC_RP_MAX_ELEMENTS
};
#define TYPE_C_INTERRUPT_EN_CFG_1_REG (TYPEC_BASE + 0x5E)
#define TYPEC_LEGACY_CABLE_INT_EN_BIT BIT(7)
#define TYPEC_NONCOMPLIANT_LEGACY_CABLE_INT_EN_BIT BIT(6)
#define TYPEC_TRYSOURCE_DETECT_INT_EN_BIT BIT(5)
#define TYPEC_TRYSINK_DETECT_INT_EN_BIT BIT(4)
#define TYPEC_CCOUT_DETACH_INT_EN_BIT BIT(3)
#define TYPEC_CCOUT_ATTACH_INT_EN_BIT BIT(2)
#define TYPEC_VBUS_DEASSERT_INT_EN_BIT BIT(1)
#define TYPEC_VBUS_ASSERT_INT_EN_BIT BIT(0)
#define TYPE_C_INTERRUPT_EN_CFG_2_REG (TYPEC_BASE + 0x60)
#define TYPEC_SRC_BATT_HPWR_INT_EN_BIT BIT(6)
#define MICRO_USB_STATE_CHANGE_INT_EN_BIT BIT(5)
#define TYPEC_STATE_MACHINE_CHANGE_INT_EN_BIT BIT(4)
#define TYPEC_DEBUG_ACCESS_DETECT_INT_EN_BIT BIT(3)
#define TYPEC_WATER_DETECTION_INT_EN_BIT BIT(2)
#define TYPEC_VBUS_ERROR_INT_EN_BIT BIT(1)
#define TYPEC_DEBOUNCE_DONE_INT_EN_BIT BIT(0)
#define TYPE_C_DEBOUNCE_OPTION_REG (TYPEC_BASE + 0x62)
#define REDUCE_TCCDEBOUNCE_TO_2MS_BIT BIT(2)
#define TYPE_C_SBU_CFG_REG (TYPEC_BASE + 0x6A)
#define SEL_SBU1_ISRC_VAL 0x04
#define SEL_SBU2_ISRC_VAL 0x01
#define TYPEC_U_USB_CFG_REG (TYPEC_BASE + 0x70)
#define EN_MICRO_USB_FACTORY_MODE_BIT BIT(1)
#define EN_MICRO_USB_MODE_BIT BIT(0)
#define PMI632_TYPEC_U_USB_WATER_PROTECTION_CFG_REG (TYPEC_BASE + 0x72)
#define TYPEC_U_USB_WATER_PROTECTION_CFG_REG (TYPEC_BASE + 0x73)
#define EN_MICRO_USB_WATER_PROTECTION_BIT BIT(4)
#define MICRO_USB_DETECTION_ON_TIME_CFG_MASK GENMASK(3, 2)
#define MICRO_USB_DETECTION_PERIOD_CFG_MASK GENMASK(1, 0)
#define PMI632_TYPEC_MICRO_USB_MODE_REG (TYPEC_BASE + 0x73)
#define MICRO_USB_MODE_ONLY_BIT BIT(0)
/********************************
* MISC Peripheral Registers *
********************************/
#define TEMP_RANGE_STATUS_REG (MISC_BASE + 0x06)
#define THERM_REG_ACTIVE_BIT BIT(6)
#define TLIM_BIT BIT(5)
#define TEMP_RANGE_MASK GENMASK(4, 1)
#define ALERT_LEVEL_BIT BIT(4)
#define TEMP_ABOVE_RANGE_BIT BIT(3)
#define TEMP_WITHIN_RANGE_BIT BIT(2)
#define TEMP_BELOW_RANGE_BIT BIT(1)
#define THERMREG_DISABLED_BIT BIT(0)
#define DIE_TEMP_STATUS_REG (MISC_BASE + 0x07)
#define DIE_TEMP_SHDN_BIT BIT(3)
#define DIE_TEMP_RST_BIT BIT(2)
#define DIE_TEMP_UB_BIT BIT(1)
#define DIE_TEMP_LB_BIT BIT(0)
#define SKIN_TEMP_STATUS_REG (MISC_BASE + 0x08)
#define SKIN_TEMP_SHDN_BIT BIT(3)
#define SKIN_TEMP_RST_BIT BIT(2)
#define SKIN_TEMP_UB_BIT BIT(1)
#define SKIN_TEMP_LB_BIT BIT(0)
#define CONNECTOR_TEMP_STATUS_REG (MISC_BASE + 0x09)
#define CONNECTOR_TEMP_SHDN_BIT BIT(3)
#define CONNECTOR_TEMP_RST_BIT BIT(2)
#define CONNECTOR_TEMP_UB_BIT BIT(1)
#define CONNECTOR_TEMP_LB_BIT BIT(0)
#define SMB_TEMP_STATUS_REG (MISC_BASE + 0x0A)
#define SMB_TEMP_SHDN_BIT BIT(3)
#define SMB_TEMP_RST_BIT BIT(2)
#define SMB_TEMP_UB_BIT BIT(1)
#define SMB_TEMP_LB_BIT BIT(0)
#define BARK_BITE_WDOG_PET_REG (MISC_BASE + 0x43)
#define BARK_BITE_WDOG_PET_BIT BIT(0)
#define AICL_CMD_REG (MISC_BASE + 0x44)
#define RESTART_AICL_BIT BIT(1)
#define RERUN_AICL_BIT BIT(0)
#define MISC_SMB_EN_CMD_REG (MISC_BASE + 0x48)
#define SMB_EN_OVERRIDE_VALUE_BIT BIT(4)
#define SMB_EN_OVERRIDE_BIT BIT(3)
#define EN_STAT_CMD_BIT BIT(2)
#define EN_CP_FPF_CMD_BIT BIT(1)
#define EN_CP_CMD_BIT BIT(0)
#define WD_CFG_REG (MISC_BASE + 0x51)
#define WATCHDOG_TRIGGER_AFP_EN_BIT BIT(7)
#define BARK_WDOG_INT_EN_BIT BIT(6)
#define WDOG_TIMER_EN_ON_PLUGIN_BIT BIT(1)
#define SNARL_BARK_BITE_WD_CFG_REG (MISC_BASE + 0x53)
#define BITE_WDOG_DISABLE_CHARGING_CFG_BIT BIT(7)
#define SNARL_WDOG_TIMEOUT_MASK GENMASK(6, 4)
#define SNARL_WDOG_TIMEOUT_SHIFT 4
#define SNARL_WDOG_TMOUT_62P5MS 0x00
#define SNARL_WDOG_TMOUT_1S 0x40
#define SNARL_WDOG_TMOUT_8S 0x70
#define BARK_WDOG_TIMEOUT_MASK GENMASK(3, 2)
#define BARK_WDOG_TIMEOUT_SHIFT 2
#define BITE_WDOG_TIMEOUT_MASK GENMASK(1, 0)
#define BITE_WDOG_TIMEOUT_8S 0x3
#define MIN_WD_BARK_TIME 16
#define AICL_RERUN_TIME_CFG_REG (MISC_BASE + 0x61)
#define AICL_RERUN_TIME_12S_VAL 0x01
#define MISC_THERMREG_SRC_CFG_REG (MISC_BASE + 0x70)
#define THERMREG_SW_ICL_ADJUST_BIT BIT(7)
#define DIE_ADC_SEL_BIT BIT(6)
#define THERMREG_SMB_ADC_SRC_EN_BIT BIT(5)
#define THERMREG_CONNECTOR_ADC_SRC_EN_BIT BIT(4)
#define SKIN_ADC_CFG_BIT BIT(3)
#define THERMREG_SKIN_ADC_SRC_EN_BIT BIT(2)
#define THERMREG_DIE_ADC_SRC_EN_BIT BIT(1)
#define THERMREG_DIE_CMP_SRC_EN_BIT BIT(0)
#define MISC_SMB_CFG_REG (MISC_BASE + 0x90)
#define SMB_EN_SEL_BIT BIT(4)
#define CP_EN_POLARITY_CFG_BIT BIT(3)
#define STAT_POLARITY_CFG_BIT BIT(2)
#define STAT_FUNCTION_CFG_BIT BIT(1)
#define STAT_IRQ_PULSING_EN_BIT BIT(0)
#define DIE_REG_H_THRESHOLD_MSB_REG (MISC_BASE + 0xA0)
#define SMB_REG_H_THRESHOLD_MSB_REG (MISC_BASE + 0XBC)
/* SDAM regs */
#define MISC_PBS_RT_STS_REG (MISC_PBS_BASE + 0x10)
#define PULSE_SKIP_IRQ_BIT BIT(4)
#define SDAM_QC_DET_STATUS_REG 0x58
#define SDAM_QC_ADC_LSB_REG 0x54
#endif /* __SMB5_CHARGER_REG_H */

View file

@ -0,0 +1,914 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2017-2020 The Linux Foundation. All rights reserved.
*/
#define pr_fmt(fmt) "QCOM-STEPCHG: %s: " fmt, __func__
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/power_supply.h>
#include <linux/slab.h>
#include <linux/pmic-voter.h>
#include "step-chg-jeita.h"
#include "battery-profile-loader.h"
#define STEP_CHG_VOTER "STEP_CHG_VOTER"
#define JEITA_VOTER "JEITA_VOTER"
#define is_between(left, right, value) \
(((left) >= (right) && (left) >= (value) \
&& (value) >= (right)) \
|| ((left) <= (right) && (left) <= (value) \
&& (value) <= (right)))
struct step_chg_cfg {
struct step_chg_jeita_param param;
struct range_data fcc_cfg[MAX_STEP_CHG_ENTRIES];
};
struct jeita_fcc_cfg {
struct step_chg_jeita_param param;
struct range_data fcc_cfg[MAX_STEP_CHG_ENTRIES];
};
struct jeita_fv_cfg {
struct step_chg_jeita_param param;
struct range_data fv_cfg[MAX_STEP_CHG_ENTRIES];
};
struct step_chg_info {
struct device *dev;
ktime_t step_last_update_time;
ktime_t jeita_last_update_time;
bool step_chg_enable;
bool sw_jeita_enable;
bool jeita_arb_en;
bool config_is_read;
bool step_chg_cfg_valid;
bool sw_jeita_cfg_valid;
bool soc_based_step_chg;
bool ocv_based_step_chg;
bool vbat_avg_based_step_chg;
bool batt_missing;
bool taper_fcc;
int jeita_fcc_index;
int jeita_fv_index;
int step_index;
int get_config_retry_count;
struct step_chg_cfg *step_chg_config;
struct jeita_fcc_cfg *jeita_fcc_config;
struct jeita_fv_cfg *jeita_fv_config;
struct votable *fcc_votable;
struct votable *fv_votable;
struct votable *usb_icl_votable;
struct wakeup_source *step_chg_ws;
struct power_supply *batt_psy;
struct power_supply *bms_psy;
struct power_supply *usb_psy;
struct power_supply *dc_psy;
struct delayed_work status_change_work;
struct delayed_work get_config_work;
struct notifier_block nb;
};
static struct step_chg_info *the_chip;
#define STEP_CHG_HYSTERISIS_DELAY_US 5000000 /* 5 secs */
#define BATT_HOT_DECIDEGREE_MAX 600
#define GET_CONFIG_DELAY_MS 2000
#define GET_CONFIG_RETRY_COUNT 50
#define WAIT_BATT_ID_READY_MS 200
static bool is_batt_available(struct step_chg_info *chip)
{
if (!chip->batt_psy)
chip->batt_psy = power_supply_get_by_name("battery");
if (!chip->batt_psy)
return false;
return true;
}
static bool is_bms_available(struct step_chg_info *chip)
{
if (!chip->bms_psy)
chip->bms_psy = power_supply_get_by_name("bms");
if (!chip->bms_psy)
return false;
return true;
}
static bool is_usb_available(struct step_chg_info *chip)
{
if (!chip->usb_psy)
chip->usb_psy = power_supply_get_by_name("usb");
if (!chip->usb_psy)
return false;
return true;
}
static bool is_input_present(struct step_chg_info *chip)
{
int rc = 0, input_present = 0;
union power_supply_propval pval = {0, };
if (!chip->usb_psy)
chip->usb_psy = power_supply_get_by_name("usb");
if (chip->usb_psy) {
rc = power_supply_get_property(chip->usb_psy,
POWER_SUPPLY_PROP_PRESENT, &pval);
if (rc < 0)
pr_err("Couldn't read USB Present status, rc=%d\n", rc);
else
input_present |= pval.intval;
}
if (!chip->dc_psy)
chip->dc_psy = power_supply_get_by_name("dc");
if (chip->dc_psy) {
rc = power_supply_get_property(chip->dc_psy,
POWER_SUPPLY_PROP_PRESENT, &pval);
if (rc < 0)
pr_err("Couldn't read DC Present status, rc=%d\n", rc);
else
input_present |= pval.intval;
}
if (input_present)
return true;
return false;
}
int read_range_data_from_node(struct device_node *node,
const char *prop_str, struct range_data *ranges,
int max_threshold, u32 max_value)
{
int rc = 0, i, length, per_tuple_length, tuples;
if (!node || !prop_str || !ranges) {
pr_err("Invalid parameters passed\n");
return -EINVAL;
}
rc = of_property_count_elems_of_size(node, prop_str, sizeof(u32));
if (rc < 0) {
pr_err("Count %s failed, rc=%d\n", prop_str, rc);
return rc;
}
length = rc;
per_tuple_length = sizeof(struct range_data) / sizeof(u32);
if (length % per_tuple_length) {
pr_err("%s length (%d) should be multiple of %d\n",
prop_str, length, per_tuple_length);
return -EINVAL;
}
tuples = length / per_tuple_length;
if (tuples > MAX_STEP_CHG_ENTRIES) {
pr_err("too many entries(%d), only %d allowed\n",
tuples, MAX_STEP_CHG_ENTRIES);
return -EINVAL;
}
rc = of_property_read_u32_array(node, prop_str,
(u32 *)ranges, length);
if (rc) {
pr_err("Read %s failed, rc=%d\n", prop_str, rc);
return rc;
}
for (i = 0; i < tuples; i++) {
if (ranges[i].low_threshold >
ranges[i].high_threshold) {
pr_err("%s thresholds should be in ascendant ranges\n",
prop_str);
rc = -EINVAL;
goto clean;
}
if (i != 0) {
if (ranges[i - 1].high_threshold >
ranges[i].low_threshold) {
pr_err("%s thresholds should be in ascendant ranges\n",
prop_str);
rc = -EINVAL;
goto clean;
}
}
if (ranges[i].low_threshold > max_threshold)
ranges[i].low_threshold = max_threshold;
if (ranges[i].high_threshold > max_threshold)
ranges[i].high_threshold = max_threshold;
if (ranges[i].value > max_value)
ranges[i].value = max_value;
}
return rc;
clean:
memset(ranges, 0, tuples * sizeof(struct range_data));
return rc;
}
EXPORT_SYMBOL(read_range_data_from_node);
static int get_step_chg_jeita_setting_from_profile(struct step_chg_info *chip)
{
struct device_node *batt_node, *profile_node;
u32 max_fv_uv, max_fcc_ma;
const char *batt_type_str;
const __be32 *handle;
int batt_id_ohms, rc;
union power_supply_propval prop = {0, };
handle = of_get_property(chip->dev->of_node,
"qcom,battery-data", NULL);
if (!handle) {
pr_debug("ignore getting sw-jeita/step charging settings from profile\n");
return 0;
}
batt_node = of_find_node_by_phandle(be32_to_cpup(handle));
if (!batt_node) {
pr_err("Get battery data node failed\n");
return -EINVAL;
}
if (!is_bms_available(chip))
return -ENODEV;
power_supply_get_property(chip->bms_psy,
POWER_SUPPLY_PROP_RESISTANCE_ID, &prop);
batt_id_ohms = prop.intval;
/* bms_psy has not yet read the batt_id */
if (batt_id_ohms < 0)
return -EBUSY;
profile_node = of_batterydata_get_best_profile(batt_node,
batt_id_ohms / 1000, NULL);
if (IS_ERR(profile_node))
return PTR_ERR(profile_node);
if (!profile_node) {
pr_err("Couldn't find profile\n");
return -ENODATA;
}
rc = of_property_read_string(profile_node, "qcom,battery-type",
&batt_type_str);
if (rc < 0) {
pr_err("battery type unavailable, rc:%d\n", rc);
return rc;
}
pr_debug("battery: %s detected, getting sw-jeita/step charging settings\n",
batt_type_str);
rc = of_property_read_u32(profile_node, "qcom,max-voltage-uv",
&max_fv_uv);
if (rc < 0) {
pr_err("max-voltage_uv reading failed, rc=%d\n", rc);
return rc;
}
rc = of_property_read_u32(profile_node, "qcom,fastchg-current-ma",
&max_fcc_ma);
if (rc < 0) {
pr_err("max-fastchg-current-ma reading failed, rc=%d\n", rc);
return rc;
}
chip->taper_fcc = of_property_read_bool(profile_node, "qcom,taper-fcc");
chip->soc_based_step_chg =
of_property_read_bool(profile_node, "qcom,soc-based-step-chg");
if (chip->soc_based_step_chg) {
chip->step_chg_config->param.psy_prop =
POWER_SUPPLY_PROP_CAPACITY;
chip->step_chg_config->param.prop_name = "SOC";
chip->step_chg_config->param.hysteresis = 0;
}
chip->ocv_based_step_chg =
of_property_read_bool(profile_node, "qcom,ocv-based-step-chg");
if (chip->ocv_based_step_chg) {
chip->step_chg_config->param.psy_prop =
POWER_SUPPLY_PROP_VOLTAGE_OCV;
chip->step_chg_config->param.prop_name = "OCV";
chip->step_chg_config->param.hysteresis = 0;
chip->step_chg_config->param.use_bms = true;
}
chip->vbat_avg_based_step_chg =
of_property_read_bool(profile_node,
"qcom,vbat-avg-based-step-chg");
if (chip->vbat_avg_based_step_chg) {
chip->step_chg_config->param.psy_prop =
POWER_SUPPLY_PROP_VOLTAGE_AVG;
chip->step_chg_config->param.prop_name = "VBAT_AVG";
chip->step_chg_config->param.hysteresis = 0;
chip->step_chg_config->param.use_bms = true;
}
chip->step_chg_cfg_valid = true;
rc = read_range_data_from_node(profile_node,
"qcom,step-chg-ranges",
chip->step_chg_config->fcc_cfg,
chip->soc_based_step_chg ? 100 : max_fv_uv,
max_fcc_ma * 1000);
if (rc < 0) {
pr_debug("Read qcom,step-chg-ranges failed from battery profile, rc=%d\n",
rc);
chip->step_chg_cfg_valid = false;
}
chip->sw_jeita_cfg_valid = true;
rc = read_range_data_from_node(profile_node,
"qcom,jeita-fcc-ranges",
chip->jeita_fcc_config->fcc_cfg,
BATT_HOT_DECIDEGREE_MAX, max_fcc_ma * 1000);
if (rc < 0) {
pr_debug("Read qcom,jeita-fcc-ranges failed from battery profile, rc=%d\n",
rc);
chip->sw_jeita_cfg_valid = false;
}
rc = read_range_data_from_node(profile_node,
"qcom,jeita-fv-ranges",
chip->jeita_fv_config->fv_cfg,
BATT_HOT_DECIDEGREE_MAX, max_fv_uv);
if (rc < 0) {
pr_debug("Read qcom,jeita-fv-ranges failed from battery profile, rc=%d\n",
rc);
chip->sw_jeita_cfg_valid = false;
}
return rc;
}
static void get_config_work(struct work_struct *work)
{
struct step_chg_info *chip = container_of(work,
struct step_chg_info, get_config_work.work);
int i, rc;
chip->config_is_read = false;
rc = get_step_chg_jeita_setting_from_profile(chip);
if (rc < 0) {
if (rc == -ENODEV || rc == -EBUSY) {
if (chip->get_config_retry_count++
< GET_CONFIG_RETRY_COUNT) {
pr_debug("bms_psy is not ready, retry: %d\n",
chip->get_config_retry_count);
goto reschedule;
}
}
}
chip->config_is_read = true;
for (i = 0; i < MAX_STEP_CHG_ENTRIES; i++)
pr_debug("step-chg-cfg: %duV(SoC) ~ %duV(SoC), %duA\n",
chip->step_chg_config->fcc_cfg[i].low_threshold,
chip->step_chg_config->fcc_cfg[i].high_threshold,
chip->step_chg_config->fcc_cfg[i].value);
for (i = 0; i < MAX_STEP_CHG_ENTRIES; i++)
pr_debug("jeita-fcc-cfg: %ddecidegree ~ %ddecidegre, %duA\n",
chip->jeita_fcc_config->fcc_cfg[i].low_threshold,
chip->jeita_fcc_config->fcc_cfg[i].high_threshold,
chip->jeita_fcc_config->fcc_cfg[i].value);
for (i = 0; i < MAX_STEP_CHG_ENTRIES; i++)
pr_debug("jeita-fv-cfg: %ddecidegree ~ %ddecidegre, %duV\n",
chip->jeita_fv_config->fv_cfg[i].low_threshold,
chip->jeita_fv_config->fv_cfg[i].high_threshold,
chip->jeita_fv_config->fv_cfg[i].value);
return;
reschedule:
schedule_delayed_work(&chip->get_config_work,
msecs_to_jiffies(GET_CONFIG_DELAY_MS));
}
static int get_val(struct range_data *range, int hysteresis, int current_index,
int threshold,
int *new_index, int *val)
{
int i;
*new_index = -EINVAL;
/*
* If the threshold is lesser than the minimum allowed range,
* return -ENODATA.
*/
if (threshold < range[0].low_threshold)
return -ENODATA;
/* First try to find the matching index without hysteresis */
for (i = 0; i < MAX_STEP_CHG_ENTRIES; i++) {
if (!range[i].high_threshold && !range[i].low_threshold) {
/* First invalid table entry; exit loop */
break;
}
if (is_between(range[i].low_threshold,
range[i].high_threshold, threshold)) {
*new_index = i;
*val = range[i].value;
break;
}
}
/*
* If nothing was found, the threshold exceeds the max range for sure
* as the other case where it is lesser than the min range is handled
* at the very beginning of this function. Therefore, clip it to the
* max allowed range value, which is the one corresponding to the last
* valid entry in the battery profile data array.
*/
if (*new_index == -EINVAL) {
if (i == 0) {
/* Battery profile data array is completely invalid */
return -ENODATA;
}
*new_index = (i - 1);
*val = range[*new_index].value;
}
/*
* If we don't have a current_index return this
* newfound value. There is no hysterisis from out of range
* to in range transition
*/
if (current_index == -EINVAL)
return 0;
/*
* Check for hysteresis if it in the neighbourhood
* of our current index.
*/
if (*new_index == current_index + 1) {
if (threshold < range[*new_index].low_threshold + hysteresis) {
/*
* Stay in the current index, threshold is not higher
* by hysteresis amount
*/
*new_index = current_index;
*val = range[current_index].value;
}
} else if (*new_index == current_index - 1) {
if (threshold > range[*new_index].high_threshold - hysteresis) {
/*
* stay in the current index, threshold is not lower
* by hysteresis amount
*/
*new_index = current_index;
*val = range[current_index].value;
}
}
return 0;
}
#define TAPERED_STEP_CHG_FCC_REDUCTION_STEP_MA 50000 /* 50 mA */
static void taper_fcc_step_chg(struct step_chg_info *chip, int index,
int current_voltage)
{
u32 current_fcc, target_fcc;
if (index < 0) {
pr_err("Invalid STEP CHG index\n");
return;
}
current_fcc = get_effective_result(chip->fcc_votable);
target_fcc = chip->step_chg_config->fcc_cfg[index].value;
if (index == 0) {
vote(chip->fcc_votable, STEP_CHG_VOTER, true, target_fcc);
} else if (current_voltage >
(chip->step_chg_config->fcc_cfg[index - 1].high_threshold +
chip->step_chg_config->param.hysteresis)) {
/*
* Ramp down FCC in pre-configured steps till the current index
* FCC configuration is reached, whenever the step charging
* control parameter exceeds the high threshold of previous
* step charging index configuration.
*/
vote(chip->fcc_votable, STEP_CHG_VOTER, true, max(target_fcc,
current_fcc - TAPERED_STEP_CHG_FCC_REDUCTION_STEP_MA));
} else if ((current_fcc >
chip->step_chg_config->fcc_cfg[index - 1].value) &&
(current_voltage >
chip->step_chg_config->fcc_cfg[index - 1].low_threshold +
chip->step_chg_config->param.hysteresis)) {
/*
* In case the step charging index switch to the next higher
* index without FCCs saturation for the previous index, ramp
* down FCC till previous index FCC configuration is reached.
*/
vote(chip->fcc_votable, STEP_CHG_VOTER, true,
max(chip->step_chg_config->fcc_cfg[index - 1].value,
current_fcc - TAPERED_STEP_CHG_FCC_REDUCTION_STEP_MA));
}
}
static int handle_step_chg_config(struct step_chg_info *chip)
{
union power_supply_propval pval = {0, };
int rc = 0, fcc_ua = 0, current_index;
u64 elapsed_us;
elapsed_us = ktime_us_delta(ktime_get(), chip->step_last_update_time);
/* skip processing, event too early */
if (elapsed_us < STEP_CHG_HYSTERISIS_DELAY_US)
return 0;
rc = power_supply_get_property(chip->batt_psy,
POWER_SUPPLY_PROP_STEP_CHARGING_ENABLED, &pval);
if (rc < 0)
chip->step_chg_enable = false;
else
chip->step_chg_enable = pval.intval;
if (!chip->step_chg_enable || !chip->step_chg_cfg_valid) {
if (chip->fcc_votable)
vote(chip->fcc_votable, STEP_CHG_VOTER, false, 0);
goto update_time;
}
if (chip->step_chg_config->param.use_bms)
rc = power_supply_get_property(chip->bms_psy,
chip->step_chg_config->param.psy_prop, &pval);
else
rc = power_supply_get_property(chip->batt_psy,
chip->step_chg_config->param.psy_prop, &pval);
if (rc < 0) {
pr_err("Couldn't read %s property rc=%d\n",
chip->step_chg_config->param.prop_name, rc);
return rc;
}
current_index = chip->step_index;
rc = get_val(chip->step_chg_config->fcc_cfg,
chip->step_chg_config->param.hysteresis,
chip->step_index,
pval.intval,
&chip->step_index,
&fcc_ua);
if (rc < 0) {
/* remove the vote if no step-based fcc is found */
if (chip->fcc_votable)
vote(chip->fcc_votable, STEP_CHG_VOTER, false, 0);
goto update_time;
}
/* Do not drop step-chg index, if input supply is present */
if (is_input_present(chip)) {
if (chip->step_index < current_index)
chip->step_index = current_index;
} else {
chip->step_index = 0;
}
if (!chip->fcc_votable)
chip->fcc_votable = find_votable("FCC");
if (!chip->fcc_votable)
return -EINVAL;
if (chip->taper_fcc) {
taper_fcc_step_chg(chip, chip->step_index, pval.intval);
} else {
fcc_ua = chip->step_chg_config->fcc_cfg[chip->step_index].value;
vote(chip->fcc_votable, STEP_CHG_VOTER, true, fcc_ua);
}
pr_debug("%s = %d Step-FCC = %duA taper-fcc: %d\n",
chip->step_chg_config->param.prop_name, pval.intval,
get_client_vote(chip->fcc_votable, STEP_CHG_VOTER),
chip->taper_fcc);
update_time:
chip->step_last_update_time = ktime_get();
return 0;
}
#define JEITA_SUSPEND_HYST_UV 50000
static int handle_jeita(struct step_chg_info *chip)
{
union power_supply_propval pval = {0, };
int rc = 0, fcc_ua = 0, fv_uv = 0;
u64 elapsed_us;
rc = power_supply_get_property(chip->batt_psy,
POWER_SUPPLY_PROP_SW_JEITA_ENABLED, &pval);
if (rc < 0)
chip->sw_jeita_enable = false;
else
chip->sw_jeita_enable = pval.intval;
if (!chip->sw_jeita_enable || !chip->sw_jeita_cfg_valid) {
if (chip->fcc_votable)
vote(chip->fcc_votable, JEITA_VOTER, false, 0);
if (chip->fv_votable)
vote(chip->fv_votable, JEITA_VOTER, false, 0);
if (chip->usb_icl_votable)
vote(chip->usb_icl_votable, JEITA_VOTER, false, 0);
return 0;
}
elapsed_us = ktime_us_delta(ktime_get(), chip->jeita_last_update_time);
/* skip processing, event too early */
if (elapsed_us < STEP_CHG_HYSTERISIS_DELAY_US)
return 0;
if (chip->jeita_fcc_config->param.use_bms)
rc = power_supply_get_property(chip->bms_psy,
chip->jeita_fcc_config->param.psy_prop, &pval);
else
rc = power_supply_get_property(chip->batt_psy,
chip->jeita_fcc_config->param.psy_prop, &pval);
if (rc < 0) {
pr_err("Couldn't read %s property rc=%d\n",
chip->jeita_fcc_config->param.prop_name, rc);
return rc;
}
rc = get_val(chip->jeita_fcc_config->fcc_cfg,
chip->jeita_fcc_config->param.hysteresis,
chip->jeita_fcc_index,
pval.intval,
&chip->jeita_fcc_index,
&fcc_ua);
if (rc < 0)
fcc_ua = 0;
if (!chip->fcc_votable)
chip->fcc_votable = find_votable("FCC");
if (!chip->fcc_votable)
/* changing FCC is a must */
return -EINVAL;
vote(chip->fcc_votable, JEITA_VOTER, fcc_ua ? true : false, fcc_ua);
rc = get_val(chip->jeita_fv_config->fv_cfg,
chip->jeita_fv_config->param.hysteresis,
chip->jeita_fv_index,
pval.intval,
&chip->jeita_fv_index,
&fv_uv);
if (rc < 0)
fv_uv = 0;
chip->fv_votable = find_votable("FV");
if (!chip->fv_votable)
goto update_time;
if (!chip->usb_icl_votable)
chip->usb_icl_votable = find_votable("USB_ICL");
if (!chip->usb_icl_votable)
goto set_jeita_fv;
/*
* If JEITA float voltage is same as max-vfloat of battery then
* skip any further VBAT specific checks.
*/
rc = power_supply_get_property(chip->batt_psy,
POWER_SUPPLY_PROP_VOLTAGE_MAX, &pval);
if (rc || (pval.intval == fv_uv)) {
vote(chip->usb_icl_votable, JEITA_VOTER, false, 0);
goto set_jeita_fv;
}
/*
* Suspend USB input path if battery voltage is above
* JEITA VFLOAT threshold.
*/
if (chip->jeita_arb_en && fv_uv > 0) {
rc = power_supply_get_property(chip->batt_psy,
POWER_SUPPLY_PROP_VOLTAGE_NOW, &pval);
if (!rc && (pval.intval > fv_uv))
vote(chip->usb_icl_votable, JEITA_VOTER, true, 0);
else if (pval.intval < (fv_uv - JEITA_SUSPEND_HYST_UV))
vote(chip->usb_icl_votable, JEITA_VOTER, false, 0);
}
set_jeita_fv:
vote(chip->fv_votable, JEITA_VOTER, fv_uv ? true : false, fv_uv);
update_time:
chip->jeita_last_update_time = ktime_get();
return 0;
}
static int handle_battery_insertion(struct step_chg_info *chip)
{
int rc;
union power_supply_propval pval = {0, };
rc = power_supply_get_property(chip->batt_psy,
POWER_SUPPLY_PROP_PRESENT, &pval);
if (rc < 0) {
pr_err("Get battery present status failed, rc=%d\n", rc);
return rc;
}
if (chip->batt_missing != (!pval.intval)) {
chip->batt_missing = !pval.intval;
pr_debug("battery %s detected\n",
chip->batt_missing ? "removal" : "insertion");
if (chip->batt_missing) {
chip->step_chg_cfg_valid = false;
chip->sw_jeita_cfg_valid = false;
chip->get_config_retry_count = 0;
} else {
/*
* Get config for the new inserted battery, delay
* to make sure BMS has read out the batt_id.
*/
schedule_delayed_work(&chip->get_config_work,
msecs_to_jiffies(WAIT_BATT_ID_READY_MS));
}
}
return rc;
}
static void status_change_work(struct work_struct *work)
{
struct step_chg_info *chip = container_of(work,
struct step_chg_info, status_change_work.work);
int rc = 0;
union power_supply_propval prop = {0, };
if (!is_batt_available(chip) || !is_bms_available(chip))
goto exit_work;
handle_battery_insertion(chip);
/* skip elapsed_us debounce for handling battery temperature */
rc = handle_jeita(chip);
if (rc < 0)
pr_err("Couldn't handle sw jeita rc = %d\n", rc);
rc = handle_step_chg_config(chip);
if (rc < 0)
pr_err("Couldn't handle step rc = %d\n", rc);
/* Remove stale votes on USB removal */
if (is_usb_available(chip)) {
prop.intval = 0;
power_supply_get_property(chip->usb_psy,
POWER_SUPPLY_PROP_PRESENT, &prop);
if (!prop.intval) {
if (chip->usb_icl_votable)
vote(chip->usb_icl_votable, JEITA_VOTER,
false, 0);
}
}
exit_work:
__pm_relax(chip->step_chg_ws);
}
static int step_chg_notifier_call(struct notifier_block *nb,
unsigned long ev, void *v)
{
struct power_supply *psy = v;
struct step_chg_info *chip = container_of(nb, struct step_chg_info, nb);
if (ev != PSY_EVENT_PROP_CHANGED)
return NOTIFY_OK;
if ((strcmp(psy->desc->name, "battery") == 0)
|| (strcmp(psy->desc->name, "usb") == 0)) {
__pm_stay_awake(chip->step_chg_ws);
schedule_delayed_work(&chip->status_change_work, 0);
}
if ((strcmp(psy->desc->name, "bms") == 0)) {
if (chip->bms_psy == NULL)
chip->bms_psy = psy;
if (!chip->config_is_read)
schedule_delayed_work(&chip->get_config_work, 0);
}
return NOTIFY_OK;
}
static int step_chg_register_notifier(struct step_chg_info *chip)
{
int rc;
chip->nb.notifier_call = step_chg_notifier_call;
rc = power_supply_reg_notifier(&chip->nb);
if (rc < 0) {
pr_err("Couldn't register psy notifier rc = %d\n", rc);
return rc;
}
return 0;
}
int qcom_step_chg_init(struct device *dev,
bool step_chg_enable, bool sw_jeita_enable, bool jeita_arb_en)
{
int rc;
struct step_chg_info *chip;
if (the_chip) {
pr_err("Already initialized\n");
return -EINVAL;
}
chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
if (!chip)
return -ENOMEM;
chip->step_chg_ws = wakeup_source_register("qcom-step-chg");
if (!chip->step_chg_ws)
return -EINVAL;
chip->dev = dev;
chip->step_chg_enable = step_chg_enable;
chip->sw_jeita_enable = sw_jeita_enable;
chip->jeita_arb_en = jeita_arb_en;
chip->step_index = -EINVAL;
chip->jeita_fcc_index = -EINVAL;
chip->jeita_fv_index = -EINVAL;
chip->step_chg_config = devm_kzalloc(dev,
sizeof(struct step_chg_cfg), GFP_KERNEL);
if (!chip->step_chg_config)
return -ENOMEM;
chip->step_chg_config->param.psy_prop = POWER_SUPPLY_PROP_VOLTAGE_NOW;
chip->step_chg_config->param.prop_name = "VBATT";
chip->step_chg_config->param.hysteresis = 100000;
chip->jeita_fcc_config = devm_kzalloc(dev,
sizeof(struct jeita_fcc_cfg), GFP_KERNEL);
chip->jeita_fv_config = devm_kzalloc(dev,
sizeof(struct jeita_fv_cfg), GFP_KERNEL);
if (!chip->jeita_fcc_config || !chip->jeita_fv_config)
return -ENOMEM;
chip->jeita_fcc_config->param.psy_prop = POWER_SUPPLY_PROP_TEMP;
chip->jeita_fcc_config->param.prop_name = "BATT_TEMP";
chip->jeita_fcc_config->param.hysteresis = 10;
chip->jeita_fv_config->param.psy_prop = POWER_SUPPLY_PROP_TEMP;
chip->jeita_fv_config->param.prop_name = "BATT_TEMP";
chip->jeita_fv_config->param.hysteresis = 10;
INIT_DELAYED_WORK(&chip->status_change_work, status_change_work);
INIT_DELAYED_WORK(&chip->get_config_work, get_config_work);
rc = step_chg_register_notifier(chip);
if (rc < 0) {
pr_err("Couldn't register psy notifier rc = %d\n", rc);
goto release_wakeup_source;
}
schedule_delayed_work(&chip->get_config_work,
msecs_to_jiffies(GET_CONFIG_DELAY_MS));
the_chip = chip;
return 0;
release_wakeup_source:
wakeup_source_unregister(chip->step_chg_ws);
return rc;
}
void qcom_step_chg_deinit(void)
{
struct step_chg_info *chip = the_chip;
if (!chip)
return;
cancel_delayed_work_sync(&chip->status_change_work);
cancel_delayed_work_sync(&chip->get_config_work);
power_supply_unreg_notifier(&chip->nb);
wakeup_source_unregister(chip->step_chg_ws);
the_chip = NULL;
}

View file

@ -0,0 +1,30 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2017-2020 The Linux Foundation. All rights reserved.
*/
#ifndef __STEP_CHG_H__
#define __STEP_CHG_H__
#define MAX_STEP_CHG_ENTRIES 8
struct step_chg_jeita_param {
u32 psy_prop;
char *prop_name;
int hysteresis;
bool use_bms;
};
struct range_data {
int low_threshold;
int high_threshold;
u32 value;
};
int qcom_step_chg_init(struct device *dev,
bool step_chg_enable, bool sw_jeita_enable, bool jeita_arb_en);
void qcom_step_chg_deinit(void);
int read_range_data_from_node(struct device_node *node,
const char *prop_str, struct range_data *ranges,
int max_threshold, u32 max_value);
#endif /* __STEP_CHG_H__ */

View file

@ -0,0 +1,69 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2016-2017 The Linux Foundation. All rights reserved.
*/
#include "storm-watch.h"
/**
* is_storming(): Check if an event is storming
*
* @data: Data for tracking an event storm
*
* The return value will be true if a storm has been detected and
* false if a storm was not detected.
*/
bool is_storming(struct storm_watch *data)
{
ktime_t curr_kt, delta_kt;
bool is_storming = false;
if (!data)
return false;
if (!data->enabled)
return false;
/* max storm count must be greater than 0 */
if (data->max_storm_count <= 0)
return false;
/* the period threshold must be greater than 0ms */
if (data->storm_period_ms <= 0)
return false;
mutex_lock(&data->storm_lock);
curr_kt = ktime_get_boottime();
delta_kt = ktime_sub(curr_kt, data->last_kt);
if (ktime_to_ms(delta_kt) < data->storm_period_ms)
data->storm_count++;
else
data->storm_count = 0;
if (data->storm_count > data->max_storm_count) {
is_storming = true;
data->storm_count = 0;
}
data->last_kt = curr_kt;
mutex_unlock(&data->storm_lock);
return is_storming;
}
void reset_storm_count(struct storm_watch *data)
{
mutex_lock(&data->storm_lock);
data->storm_count = 0;
mutex_unlock(&data->storm_lock);
}
void update_storm_count(struct storm_watch *data, int max_count)
{
if (!data)
return;
mutex_lock(&data->storm_lock);
data->max_storm_count = max_count;
mutex_unlock(&data->storm_lock);
}

View file

@ -0,0 +1,34 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2016-2017, 2020 The Linux Foundation. All rights reserved.
*/
#ifndef __STORM_WATCH_H
#define __STORM_WATCH_H
#include <linux/ktime.h>
#include <linux/mutex.h>
/**
* Data used to track an event storm.
*
* @storm_period_ms: The maximum time interval between two events. If this limit
* is exceeded then the event chain will be broken and removed
* from consideration for a storm.
* @max_storm_count: The number of chained events required to trigger a storm.
* @storm_count: The current number of chained events.
* @last_kt: Kernel time of the last event seen.
* @storm_lock: Mutex lock to protect storm_watch data.
*/
struct storm_watch {
bool enabled;
int storm_period_ms;
int max_storm_count;
int storm_count;
ktime_t last_kt;
struct mutex storm_lock;
};
bool is_storming(struct storm_watch *data);
void reset_storm_count(struct storm_watch *data);
void update_storm_count(struct storm_watch *data, int max_count);
#endif

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