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