Merge "power: qcom: Add QTI QBG(Qualcomm Battery Gauging) driver"

This commit is contained in:
qctecmdr 2021-05-06 22:22:58 -07:00 • committed by Gerrit - the friendly Code Review server
commit e3e00bfbe0
11 changed files with 2971 additions and 0 deletions

View file

@ -101,4 +101,16 @@ config SMB358_CHARGER
The driver supports charger enable/disable.
The driver reports the charger status via the power supply framework.
A charger status change triggers an IRQ via the device STAT pin.
config QTI_QBG
tristate "QTI Battery Gauge"
depends on MFD_SPMI_PMIC && IIO
help
Say Y here to enable the Qualcomm Technologies, Inc. Battery Gauge
driver which uses the periodic samples of the battery voltage and
current to determine the battery state-of-charge (SOC) and supports
other battery management features.
To compile this driver as a module, choose M here: the
module will be called qti-qbg-main.
endif

View file

@ -13,3 +13,5 @@ qcom-smb1355-charger-y += smb1355-charger.o pmic-voter.o
obj-$(CONFIG_SMB1351_USB_CHARGER) += smb1351-charger.o pmic-voter.o
obj-$(CONFIG_SMB1390_CHARGE_PUMP_PSY) += qcom-smb1390-charger.o
qcom-smb1390-charger-y += smb1390-charger-psy.o pmic-voter.o
obj-$(CONFIG_QTI_QBG) += qti-qbg-main.o
qti-qbg-main-y += qti-qbg.o qbg-sdam.o qbg-battery-profile.o battery-profile-loader.o

View file

@ -0,0 +1,589 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2021 The Linux Foundation. All rights reserved.
*/
#define pr_fmt(fmt) "QBG-K: %s: " fmt, __func__
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/fcntl.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <uapi/linux/qbg-profile.h>
#include "qbg-battery-profile.h"
static int table_temperatures[] = { -20, -10, 0, 10, 25, 40, 50 };
static int qbg_battery_data_open(struct inode *inode, struct file *file)
{
struct qbg_battery_data *battery = container_of(inode->i_cdev,
struct qbg_battery_data, battery_cdev);
pr_debug("battery_data device opened\n");
file->private_data = battery;
return 0;
}
static long qbg_battery_data_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
struct qbg_battery_data *battery = file->private_data;
struct battery_config __user *profile_user;
struct battery_profile_table bp_table;
struct battery_profile_table __user *bp_table_user;
struct battery_data_table *table;
int rc = 0, table_index, table_type;
if (!battery->profile_node) {
pr_err("Invalid battery profile node\n");
return -EINVAL;
}
if (!arg) {
pr_err("Invalid user pointer\n");
return -EINVAL;
}
switch (cmd) {
case BPIOCXBP:
profile_user = (struct battery_config __user *)arg;
if (copy_to_user(profile_user, &battery->bp, sizeof(battery->bp))) {
pr_err("Failed to copy battery profile to user\n");
return -EFAULT;
}
break;
case BPIOCXBPTABLE:
bp_table_user = (struct battery_profile_table __user *)arg;
if (copy_from_user(&bp_table, bp_table_user, sizeof(bp_table))) {
pr_err("Failed to copy battery_profile_table from user\n");
return -EFAULT;
}
table_index = bp_table.table_index;
table_type = bp_table.table_type;
if ((table_type != CHARGE_TABLE) &&
(table_type != DISCHARGE_TABLE))
return -EFAULT;
if (((table_type == CHARGE_TABLE) &&
(table_index >= battery->num_ctables)) ||
((table_type == DISCHARGE_TABLE) &&
(table_index >= battery->num_dtables)))
return -EFAULT;
table = (table_type == CHARGE_TABLE) ?
battery->bp_charge_tables[table_index] :
battery->bp_discharge_tables[table_index];
if (copy_to_user(bp_table.table, table, sizeof(*table))) {
pr_err("Failed to copy battery profile table to user\n");
return -EFAULT;
}
pr_debug("Copied %s table %d to user\n",
table_type == CHARGE_TABLE ? "Charge" : "Discharge", table_index);
break;
default:
pr_err_ratelimited("IOCTL %u not supported\n", cmd);
rc = -EINVAL;
break;
}
return rc;
}
static int qbg_battery_data_release(struct inode *inode, struct file *file)
{
pr_debug("battery_data device closed\n");
return 0;
}
static const struct file_operations qbg_battery_data_fops = {
.owner = THIS_MODULE,
.open = qbg_battery_data_open,
.unlocked_ioctl = qbg_battery_data_ioctl,
.compat_ioctl = qbg_battery_data_ioctl,
.release = qbg_battery_data_release,
};
#define QBG_PON_TEMPERATURE 25
static int qbg_parse_table0(struct device_node *profile_node,
char *table_name, struct battery_data_table0 *table)
{
struct device_node *node;
int rc = 0, temperature;
node = of_find_node_by_name(profile_node, table_name);
if (!node) {
pr_err("%s not found\n", table_name);
return -ENODEV;
}
rc = of_property_read_s32(node, "qcom,temperature", &temperature);
if (rc < 0) {
pr_err("Failed to read %s temperature\n", table_name);
goto out;
}
if (temperature != QBG_PON_TEMPERATURE) {
pr_err("Invalid table0 found, temperature:%d\n",
temperature);
rc = -EINVAL;
goto out;
}
rc = of_property_count_elems_of_size(node, "qcom,soc", sizeof(int));
if (rc < 0) {
pr_err("Failed to get soc-length for %s, rc=%d\n",
table_name, rc);
goto out;
}
table->soc_length = rc;
table->soc = kcalloc(table->soc_length, sizeof(*table->soc), GFP_KERNEL);
if (!table->soc) {
rc = -ENOMEM;
goto out;
}
rc = of_property_read_u32_array(node, "qcom,soc", table->soc,
table->soc_length);
if (rc < 0) {
pr_err("Failed to read qcom,soc\n");
rc = -EINVAL;
goto cleanup_soc;
}
rc = of_property_count_elems_of_size(node, "qcom,ocv", sizeof(int));
if (rc < 0) {
pr_err("Failed to get ocv-length for %s, rc=%d\n",
table_name, rc);
goto cleanup_soc;
}
table->ocv_length = rc;
table->ocv = kcalloc(table->ocv_length, sizeof(*table->ocv), GFP_KERNEL);
if (!table->ocv) {
rc = -ENOMEM;
goto cleanup_soc;
}
rc = of_property_read_u32_array(node, "qcom,ocv", table->ocv,
table->ocv_length);
if (rc < 0) {
pr_err("Failed to read qcom,ocv\n");
rc = -EINVAL;
goto cleanup_ocv;
}
return 0;
cleanup_ocv:
kfree(table->ocv);
cleanup_soc:
kfree(table->soc);
out:
of_node_put(node);
return rc;
}
static int qbg_parse_table(struct device_node *profile_node,
int index, char *table_name, struct battery_data_table *bp_table)
{
struct device_node *node;
struct property *prop;
const __be32 *data;
int rc = 0, j, k, temperature;
u32 rows, cols;
node = of_find_node_by_name(profile_node, table_name);
if (!node) {
pr_err("%s not found\n", table_name);
return -ENODEV;
}
rc = of_property_read_s32(node, "qcom,temperature", &temperature);
if (rc < 0) {
pr_err("Failed to read %s temperature\n", table_name);
goto out;
}
if (temperature != table_temperatures[index]) {
pr_err("Invalid table at wrong index %d temperature:%d\n",
index, temperature);
rc = -EINVAL;
goto out;
}
rc = of_property_read_u32(node, "qcom,nrows", &rows);
if (rc < 0) {
pr_err("Failed to read %s\n", table_name);
goto out;
}
bp_table->nrows = rows;
rc = of_property_read_u32(node, "qcom,ncols", &cols);
if (rc < 0) {
pr_err("Failed to read %s\n", table_name);
goto out;
}
bp_table->ncols = cols;
rc = of_property_read_u32_array(node, "qcom,conv-factor",
bp_table->unit_conv_factor,
MAX_BP_LUT_COLS);
if (rc < 0) {
pr_err("Failed to read conv-factor\n");
rc = -EINVAL;
goto out;
}
prop = of_find_property(node, "qcom,data", NULL);
if (!prop) {
pr_err("Failed to find lut-data\n");
rc = -EINVAL;
goto out;
}
data = prop->value;
for (j = 0; j < bp_table->nrows; j++) {
for (k = 0; k < bp_table->ncols; k++)
bp_table->table[j][k] = be32_to_cpup(data++);
}
pr_debug("Profile %s parsed rows=%d cols=%d\n", table_name,
bp_table->nrows, bp_table->ncols);
out:
of_node_put(node);
return rc;
}
static int qbg_parse_u32_dt_array(struct device_node *node,
const char *prop_name, int *buf, int len)
{
int rc;
rc = of_property_count_elems_of_size(node, prop_name, sizeof(u32));
if (rc < 0) {
pr_err("Property %s not found, rc=%d\n", prop_name, rc);
return rc;
} else if (rc != len) {
pr_err("Incorrect length %d for %s, rc=%d\n", len, prop_name,
rc);
return -EINVAL;
}
rc = of_property_read_u32_array(node, prop_name, buf, len);
if (rc < 0) {
pr_err("Error in reading %s, rc=%d\n", prop_name, rc);
return rc;
}
return 0;
}
static int qbg_parse_battery_profile(struct qbg_battery_data *battery)
{
struct device_node *node = battery->profile_node;
struct device_node *child;
struct battery_config *bp = &battery->bp;
char buf[32];
const char *battery_name = NULL;
int rc, i = 0;
u32 temp[2];
rc = of_property_read_string(node, "qcom,battery-type", &battery_name);
if (rc < 0) {
pr_err("Failed to get battery type, rc=%d\n", rc);
return rc;
}
strlcpy(bp->bp_profile_name, battery_name, MAX_PROFILE_NAME_LENGTH);
rc = of_property_read_u32(node, "qcom,batt-id-kohm", &bp->bp_batt_id);
if (rc < 0) {
pr_err("Failed to get battery id, rc=%d\n", rc);
return rc;
}
rc = of_property_read_u32(node, "qcom,capacity", &bp->capacity);
if (rc < 0) {
pr_err("Failed to get battery capacity, rc=%d\n", rc);
return rc;
}
rc = of_property_read_u32(node, "qcom,checksum", &bp->bp_checksum);
if (rc < 0) {
pr_err("Failed to get checksum, rc=%d\n", rc);
return rc;
}
rc = qbg_parse_u32_dt_array(node, "qcom,soh-range", temp, 2);
if (rc < 0)
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;
}
bp->soh_range_low = temp[0];
bp->soh_range_high = temp[1];
rc = qbg_parse_u32_dt_array(node, "qcom,battery-impedance", temp, 2);
if (rc < 0)
return rc;
bp->normal_impedance = temp[0];
bp->aged_impedance = temp[1];
rc = qbg_parse_u32_dt_array(node, "qcom,battery-capacity", temp, 2);
if (rc < 0)
return rc;
bp->normal_capacity = temp[0];
bp->aged_capacity = temp[1];
rc = of_property_read_u32(node, "qcom,recharge-soc-delta",
&bp->recharge_soc_delta);
if (rc < 0) {
pr_err("Failed to get recharege soc delta, rc=%d\n",
rc);
return rc;
}
rc = of_property_read_u32(node, "qcom,recharge-vflt-delta",
&bp->recharge_vflt_delta);
if (rc < 0) {
pr_err("Failed to get recharge vflt delta, rc=%d\n",
rc);
return rc;
}
rc = of_property_read_u32(node, "qcom,recharge-iterm-ma",
&bp->recharge_iterm);
if (rc < 0) {
pr_err("Failed to get recharge iterm, rc=%d\n", rc);
return rc;
}
for_each_available_child_of_node(battery->profile_node, child) {
if (of_node_name_prefix(child, "qcom,bp-c-table"))
battery->num_ctables++;
else if (of_node_name_prefix(child, "qcom,bp-d-table"))
battery->num_dtables++;
}
if (!battery->num_ctables || !battery->num_dtables) {
pr_err("ctable or dtable missing\n");
return -EINVAL;
}
/* Battery profile contains additional table (table0) */
if (battery->num_ctables)
battery->num_ctables--;
if (battery->num_dtables)
battery->num_dtables--;
battery->bp_discharge_tables = kcalloc(battery->num_dtables,
sizeof(*battery->bp_discharge_tables), GFP_KERNEL);
if (!battery->bp_discharge_tables)
return -ENOMEM;
battery->bp_charge_tables = kcalloc(battery->num_ctables,
sizeof(*battery->bp_charge_tables), GFP_KERNEL);
if (!battery->bp_charge_tables)
return -ENOMEM;
/* Parse c-table-0 */
scnprintf(buf, sizeof(buf), "qcom,bp-c-table-0");
rc = qbg_parse_table0(battery->profile_node, buf, &battery->table0[0]);
if (rc < 0) {
pr_err("Failed to parse %s, rc=%d\n", buf, rc);
return rc;
}
/* Parse d-table-0 */
scnprintf(buf, sizeof(buf), "qcom,bp-d-table-0");
rc = qbg_parse_table0(battery->profile_node, buf, &battery->table0[1]);
if (rc < 0) {
pr_err("Failed to parse %s, rc=%d\n", buf, rc);
goto cleanup_ctable;
}
for (i = 0; i < battery->num_dtables; i++) {
battery->bp_discharge_tables[i] = kzalloc(
sizeof(*battery->bp_discharge_tables[i]), GFP_KERNEL);
if (!battery->bp_discharge_tables[i]) {
rc = -ENOMEM;
goto cleanup_ctable;
}
scnprintf(buf, sizeof(buf), "qcom,bp-d-table-%d", i + 1);
rc = qbg_parse_table(battery->profile_node, i, buf,
battery->bp_discharge_tables[i]);
if (rc < 0) {
pr_err("Failed to parse %s, rc=%d\n", buf, rc);
goto cleanup_dtable;
}
}
for (i = 0; i < battery->num_ctables; i++) {
battery->bp_charge_tables[i] = kzalloc(
sizeof(*battery->bp_charge_tables[i]), GFP_KERNEL);
if (!battery->bp_charge_tables[i]) {
rc = -ENOMEM;
goto cleanup_ctable;
}
scnprintf(buf, sizeof(buf), "qcom,bp-c-table-%d", i + 1);
rc = qbg_parse_table(battery->profile_node, i, buf,
battery->bp_charge_tables[i]);
if (rc < 0) {
pr_err("Failed to parse %s, rc=%d\n", buf, rc);
goto cleanup_ctable;
}
}
return 0;
cleanup_ctable:
for (; i > 0; i--)
kfree(battery->bp_charge_tables[i]);
i = battery->num_dtables;
cleanup_dtable:
for (; i > 0; i--)
kfree(battery->bp_discharge_tables[i]);
return rc;
}
int qbg_batterydata_init(struct device_node *profile_node,
struct qbg_battery_data *battery)
{
int rc = 0;
/* char device to access battery-profile data */
rc = alloc_chrdev_region(&battery->dev_no, 0, 1, "qbg_battery");
if (rc < 0) {
pr_err("Failed to allocate chrdev, rc=%d\n", rc);
goto free_battery;
}
cdev_init(&battery->battery_cdev, &qbg_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, "qbg_battery");
if (IS_ERR_OR_NULL(battery->battery_class)) {
pr_err("Failed to create qbg-battery class (%d)\n",
PTR_ERR(battery->battery_class));
rc = -ENODEV;
goto delete_cdev;
}
battery->battery_device = device_create(battery->battery_class, NULL,
battery->dev_no, NULL,
"qbg_battery");
if (IS_ERR_OR_NULL(battery->battery_device)) {
pr_err("Failed to create battery_device device (%d)\n",
PTR_ERR(battery->battery_device));
rc = -ENODEV;
goto destroy_class;
}
battery->profile_node = profile_node;
/* parse the battery profile */
rc = qbg_parse_battery_profile(battery);
if (rc < 0) {
pr_err("Failed to parse battery profile, rc=%d\n", rc);
goto destroy_device;
}
pr_info("QBG Battery-profile loaded, id:%d name:%s\n",
battery->bp.bp_batt_id, battery->bp.bp_profile_name);
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;
}
void qbg_batterydata_exit(struct qbg_battery_data *battery)
{
int i;
if (!battery) {
pr_err("Battery cannot be null\n");
return;
}
/* unregister the device node */
device_destroy(battery->battery_class, battery->dev_no);
class_destroy(battery->battery_class);
cdev_del(&battery->battery_cdev);
unregister_chrdev_region(battery->dev_no, 1);
/* delete all the battery profile memory */
for (i = 0; i < battery->num_ctables; i++)
kfree(battery->bp_charge_tables[i]);
for (i = 0; i < battery->num_dtables; i++)
kfree(battery->bp_discharge_tables[i]);
}
int qbg_lookup_soc_ocv(struct qbg_battery_data *battery, int *pon_soc, int ocv, bool charging)
{
struct battery_data_table0 *lut;
int i;
*pon_soc = -EINVAL;
lut = charging ? &battery->table0[0] : &battery->table0[1];
for (i = 0; i < lut->ocv_length; i++) {
if (ocv == lut->ocv[i]) {
*pon_soc = lut->soc[i];
break;
} else if (is_between(lut->ocv[i], lut->ocv[i+1], ocv)) {
*pon_soc = (lut->soc[i] + lut->soc[i+1]) / 2;
break;
}
}
if (*pon_soc == -EINVAL) {
pr_debug("%d ocv wasn't found in the LUT returning 100%\n", ocv);
*pon_soc = 10000;
}
return 0;
}

View file

@ -0,0 +1,55 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2021 The Linux Foundation. All rights reserved.
*/
#ifndef __QBG_BATTERY_PROFILE_H__
#define __QBG_BATTERY_PROFILE_H__
#define is_between(left, right, value) \
(((left) >= (right) && (left) >= (value) \
&& (value) >= (right)) \
|| ((left) <= (right) && (left) <= (value) \
&& (value) <= (right)))
struct battery_data_table0 {
int soc_length;
int ocv_length;
int *soc;
int *ocv;
};
/**
* struct qbg_battery_data - Structure for QBG battery data
* @dev_no: Device number for QBG battery char device
* @profile_node: Pointer to devicetree node handle of profile
* @battery_class: Pointer to battery class
* @battery_device: Pointer to battery class device
* @battery_cdev: QBG battery char device
* @bp: QBG battery configuration
* @bp_charge_tables: Charge tables in QBG battery profile
* @bp_discharge_tables: Discharge tables in QBG battery profile
* @table0: Two tables for PON OCV to SOC mapping
* @num_ctables: Number of charge tables
* @num_dtables: Number of discharge tables
*/
struct qbg_battery_data {
dev_t dev_no;
struct device_node *profile_node;
struct class *battery_class;
struct device *battery_device;
struct cdev battery_cdev;
struct battery_config bp;
struct battery_data_table **bp_charge_tables;
struct battery_data_table **bp_discharge_tables;
struct battery_data_table0 table0[2];
int num_ctables;
int num_dtables;
};
int qbg_batterydata_init(struct device_node *node,
struct qbg_battery_data *battery);
void qbg_batterydata_exit(struct qbg_battery_data *battery);
int qbg_lookup_soc_ocv(struct qbg_battery_data *battery, int *pon_soc, int ocv, bool charging);
#endif /* __QBG_BATTERY_PROFILE_H__ */

View file

@ -0,0 +1,178 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2021 The Linux Foundation. All rights reserved.
*/
#ifndef __QBG_CORE_H__
#define __QBG_CORE_H__
#define qbg_dbg(chip, reason, fmt, ...) \
do { \
if (*chip->debug_mask & (reason)) \
pr_err(fmt, ##__VA_ARGS__); \
else \
pr_debug(fmt, ##__VA_ARGS__); \
} while (0)
enum debug_mask {
QBG_DEBUG_BUS_READ = BIT(0),
QBG_DEBUG_BUS_WRITE = BIT(1),
QBG_DEBUG_FIFO = BIT(2),
QBG_DEBUG_IRQ = BIT(3),
QBG_DEBUG_DEVICE = BIT(4),
QBG_DEBUG_PROFILE = BIT(5),
QBG_DEBUG_SOC = BIT(6),
QBG_DEBUG_SDAM = BIT(7),
QBG_DEBUG_STATUS = BIT(8),
QBG_DEBUG_PON = BIT(9),
};
enum qbg_sdam {
SDAM_CTRL0 = 0,
SDAM_CTRL1,
SDAM_DATA0,
SDAM_DATA1,
SDAM_DATA2,
SDAM_DATA3,
SDAM_DATA4,
};
/**
* struct qti_qbg - Structure for QTI QBG device
* @dev: Pointer to QBG device structure
* @regmap: Pointer to regmap structure
* @qbg_psy: Pointer to QBG power supply
* @batt_psy: Pointer to Battery power supply
* @qbg_class: Pointer to QBG class
* @qbg_device: Pointer to QBG device
* @qbg_cdev: Member for QBG char device
* @dev_no: Device number for QBG char device
* @batt_node: Pointer to battery device node
* @indio_dev: Pointer to QBG IIO device
* @iio_chan: Pointer to QBG IIO channels
* @sdam: Pointer to multiple QBG SDAMs
* @fifo: QBG FIFO data
* @essential_params: QBG essential params
* @status_change_work: Power supply status change work
* @udata_work: User space data change work
* @nb: Power supply notifier block
* @kdata: QBG Kernel space data structure
* @udata: QBG user space data structure
* @battery: Pointer to QBG battery data structure
* @fifo_lock: Lock for reading FIFO data
* @data_lock: Lock for reading kdata from QBG char device
* @batt_id_chan: IIO channel to read battery ID
* @batt_temp_chan: IIO channel to read battery temperature
* @rtc: RTC device to read real time
* @last_fast_char_time: Timestamp of last time QBG in fast char mode
* @qbg_wait_q: Wait queue for reads to QBG char device
* @irq_name: QBG interrupt name
* @batt_type_str: String array denoting battery type
* @irq: QBG irq number
* @base: Base address of QBG HW
* @num_sdams: Number of sdams used for QBG
* @batt_id_ohm: Battery resistance in ohms
* @debug_mask: Debug mask to enable/disable debug prints
* @pon_ocv: Power-on OCV of QBG device
* @pon_ibat: Power-on current of QBG device
* @pon_soc: Power-on SOC of QBG device
* @soc: Monotonic SOC of QBG device
* @batt_soc: Battery SOC
* @sys_soc: Battery system SOC
* @esr: Battery equivalent series resistance
* @ocv_uv: Battery open circuit voltage
* @voltage_now: Battery voltage
* @current_now: Battery current
* @tbat: Battery temperature
* @charge_cycle_count: Battery charge cycle count
* @nominal_capacity: Battery nominal capacity
* @learned_capacity: Battery learned capacity
* @ttf: Time to full
* @tte: Time to empty
* @soh: Battery state of health
* @charge_type: Charging type
* @float_volt_uv: Battery maximum voltage
* @fastchg_curr_ma: Battery fast charge current
* @vbat_cutoff_mv: Battery cutoff voltage
* @ibat_cutoff_ma: Battery cutoff current
* @vph_min_mv: Battery minimum power
* @iterm_ma: Charge Termination current
* @rconn_mohm: Battery connector resistance
* @previous_ep_time: Previous timestamp when essential params stored
* @current_time: Current time stamp
* @profile_loaded: Flag to indicated battery profile is loaded
* @battery_missing: Flag to indicate battery is missing
* @data_ready: Flag to indicate QBG data is ready
* @in_fast_char: Flag to indicate QBG is in fast char mode
*/
struct qti_qbg {
struct device *dev;
struct regmap *regmap;
struct power_supply *qbg_psy;
struct power_supply *batt_psy;
struct class *qbg_class;
struct device *qbg_device;
struct cdev qbg_cdev;
dev_t dev_no;
struct device_node *batt_node;
struct iio_dev *indio_dev;
struct iio_chan_spec *iio_chan;
struct nvmem_device **sdam;
struct fifo_data fifo[MAX_FIFO_COUNT];
struct qbg_essential_params essential_params;
struct work_struct status_change_work;
struct work_struct udata_work;
struct notifier_block nb;
struct qbg_kernel_data kdata;
struct qbg_user_data udata;
struct qbg_battery_data *battery;
struct mutex fifo_lock;
struct mutex data_lock;
struct iio_channel *batt_id_chan;
struct iio_channel *batt_temp_chan;
struct rtc_device *rtc;
ktime_t last_fast_char_time;
wait_queue_head_t qbg_wait_q;
const char *irq_name;
const char *batt_type_str;
int irq;
u32 base;
u32 sdam_base;
u32 num_sdams;
u32 num_data_sdams;
u32 batt_id_ohm;
u32 *debug_mask;
int pon_ocv;
int pon_ibat;
int pon_tbat;
int pon_soc;
int soc;
int batt_soc;
int sys_soc;
int esr;
int ocv_uv;
int voltage_now;
int current_now;
int tbat;
int charge_cycle_count;
int nominal_capacity;
int learned_capacity;
int ttf;
int tte;
int soh;
int charge_type;
int float_volt_uv;
int fastchg_curr_ma;
int vbat_cutoff_mv;
int ibat_cutoff_ma;
int vph_min_mv;
int iterm_ma;
int rconn_mohm;
unsigned long previous_ep_time;
unsigned long current_time;
bool profile_loaded;
bool battery_missing;
bool data_ready;
bool in_fast_char;
};
#endif /* __QBG_CORE_H__ */

View file

@ -0,0 +1,56 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2021 The Linux Foundation. All rights reserved.
*/
#ifndef __QBG_IIO_H__
#define __QBG_IIO_H__
struct qbg_iio_channels {
const char *datasheet_name;
int channel_num;
enum iio_chan_type type;
long info_mask;
};
#define QBG_IIO_CHAN(_name, _num, _type, _mask) \
{ \
.datasheet_name = _name, \
.channel_num = _num, \
.type = _type, \
.info_mask = _mask, \
},
#define QBG_CHAN_ENERGY(_name, _num) \
QBG_IIO_CHAN(_name, _num, IIO_ENERGY, \
BIT(IIO_CHAN_INFO_PROCESSED))
#define QBG_CHAN_TEMP(_name, _num) \
QBG_IIO_CHAN(_name, _num, IIO_TEMP, \
BIT(IIO_CHAN_INFO_PROCESSED))
#define QBG_CHAN_VOLT(_name, _num) \
QBG_IIO_CHAN(_name, _num, IIO_VOLTAGE, \
BIT(IIO_CHAN_INFO_PROCESSED))
#define QBG_CHAN_TSTAMP(_name, _num) \
QBG_IIO_CHAN(_name, _num, IIO_TIMESTAMP, \
BIT(IIO_CHAN_INFO_PROCESSED))
#define QBG_CHAN_RES(_name, _num) \
QBG_IIO_CHAN(_name, _num, IIO_RESISTANCE, \
BIT(IIO_CHAN_INFO_PROCESSED))
#define QBG_CHAN_INDEX(_name, _num) \
QBG_IIO_CHAN(_name, _num, IIO_INDEX, \
BIT(IIO_CHAN_INFO_PROCESSED))
#define QBG_CHAN_CUR(_name, _num) \
QBG_IIO_CHAN(_name, _num, IIO_CURRENT, \
BIT(IIO_CHAN_INFO_PROCESSED))
#define QBG_CHAN_COUNT(_name, _num) \
QBG_IIO_CHAN(_name, _num, IIO_COUNT, \
BIT(IIO_CHAN_INFO_PROCESSED))
#endif /* __QBG_IIO_H__ */

View file

@ -0,0 +1,137 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2021, The Linux Foundation. All rights reserved.
*/
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/nvmem-consumer.h>
#include <linux/regmap.h>
#include <uapi/linux/qbg.h>
#include "qbg-core.h"
#include "qbg-sdam.h"
int qbg_sdam_read(struct qti_qbg *chip, int offset, u8 *data, int len)
{
int rc;
rc = regmap_bulk_read(chip->regmap, offset, data, len);
if (rc < 0)
pr_err("Failed to read QBG SDAM offset:%04x rc=%d\n",
offset, rc);
return rc;
}
int qbg_sdam_write(struct qti_qbg *chip, int offset, u8 *data, int len)
{
int rc;
rc = regmap_bulk_write(chip->regmap, offset, data, len);
if (rc < 0)
pr_err("Failed to write QBG SDAM offset:%04x rc=%d\n",
offset, rc);
return rc;
}
int qbg_sdam_get_fifo_data(struct qti_qbg *chip, struct fifo_data *fifo,
u32 fifo_count)
{
int rc, i;
u32 num_sdams, fifo_data_length, bytes_read = 0, bytes_remaining;
fifo_data_length = fifo_count * QBG_ONE_FIFO_LENGTH;
num_sdams = fifo_data_length / QBG_SDAM_ONE_FIFO_REGION_SIZE;
if (num_sdams > chip->num_sdams) {
pr_err("Fifo count exceeds QBG SDAMs\n");
return -EINVAL;
}
qbg_dbg(chip, QBG_DEBUG_FIFO, "Completely filled sdams=%d\n",
num_sdams);
/* First read SDAMs that are completely filled */
for (i = 0; i < num_sdams; i++) {
if (!chip->sdam[i]) {
pr_err("Number of SDAMs not sufficient to read FIFOs\n");
return -ENODEV;
}
rc = qbg_sdam_read(chip,
QBG_SDAM_DATA_START_OFFSET(chip, SDAM_DATA0 + i),
(((u8 *)fifo) + bytes_read),
QBG_SDAM_ONE_FIFO_REGION_SIZE);
if (rc < 0) {
pr_err("Failed to read QBG SDAM%d, rc=%d\n", i, rc);
return rc;
}
bytes_read += QBG_SDAM_ONE_FIFO_REGION_SIZE;
}
/* Next read SDAM that is partially filled */
bytes_remaining = fifo_data_length - bytes_read;
qbg_dbg(chip, QBG_DEBUG_FIFO, "Valid data in partially filled sdam:%d bytes\n",
bytes_remaining);
if (bytes_remaining) {
rc = qbg_sdam_read(chip,
QBG_SDAM_DATA_START_OFFSET(chip, SDAM_DATA0 + i),
(((u8 *)fifo) + bytes_read),
bytes_remaining);
if (rc < 0) {
pr_err("Failed to read remaining bytes from QBG SDAM%d, rc=%d\n",
i, rc);
return rc;
}
bytes_read += bytes_remaining;
}
qbg_dbg(chip, QBG_DEBUG_FIFO, "Total bytes read=%d\n", bytes_read);
return rc;
}
int qbg_sdam_get_essential_params(struct qti_qbg *chip, u8 *params)
{
int rc;
if (!chip || !params)
return -EINVAL;
rc = qbg_sdam_read(chip,
chip->sdam_base + (SDAM_CTRL1 * 0x100) +
QBG_ESSENTIAL_PARAMS_START_OFFSET,
(u8 *)params, sizeof(struct qbg_essential_params));
if (rc < 0)
pr_err("Failed to read QBG essential params, rc=%d\n", rc);
else
qbg_dbg(chip, QBG_DEBUG_FIFO, "Read QBG essential params\n");
return rc;
}
int qbg_sdam_set_essential_params(struct qti_qbg *chip, u8 *params)
{
int rc;
if (!chip || !params)
return -EINVAL;
rc = qbg_sdam_write(chip,
chip->sdam_base + (SDAM_CTRL1 * 0x100) +
QBG_ESSENTIAL_PARAMS_START_OFFSET,
(u8 *)params, sizeof(struct qbg_essential_params));
if (rc < 0)
pr_err("Failed to write QBG essential params, rc=%d\n", rc);
else
qbg_dbg(chip, QBG_DEBUG_FIFO, "Written QBG essential params\n");
return rc;
}

View file

@ -0,0 +1,31 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2021 The Linux Foundation. All rights reserved.
*/
#ifndef __QBG_SDAM_H__
#define __QBG_SDAM_H__
#define QBG_SDAM_PBS_STATUS_OFFSET 0x45
#define QBG_SDAM_FIFO_COUNT_OFFSET 0x46
#define QBG_ESSENTIAL_PARAMS_START_OFFSET 0x47
#define QBG_SDAM_START_OFFSET 0x4b
#define QBG_SDAM_INT_TEST1 0xe0
#define QBG_SDAM_INT_TEST_VAL 0xe1
#define QBG_SDAM_DATA_PUSH_COUNTER_OFFSET 0x46
#define QBG_SDAM_TEST_VAL_1_SET 0x2
#define QBG_SDAM_ONE_FIFO_REGION_SIZE 117 /* 117 bytes for FIFO starting from 0x4B */
#define QBG_SDAM_ESR_PULSE_FIFO_INDEX 11
#define QBG_SDAM_DATA_START_OFFSET(chip, index) \
(chip->sdam_base + (index * 0x100) + QBG_SDAM_START_OFFSET)
int qbg_sdam_read(struct qti_qbg *chip, int offset, u8 *data, int length);
int qbg_sdam_write(struct qti_qbg *chip, int offset, u8 *data, int length);
int qbg_sdam_get_fifo_data(struct qti_qbg *chip, struct fifo_data *fifo,
u32 fifo_count);
int qbg_sdam_get_essential_params(struct qti_qbg *chip, u8 *params);
int qbg_sdam_set_essential_params(struct qti_qbg *chip, u8 *params);
#endif /* __QBG_SDAM_H__ */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,51 @@
/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */
/*
* Copyright (c) 2021, The Linux Foundation. All rights reserved.
*/
#ifndef __QBG_PROFILE_H__
#define __QBG_PROFILE_H__
#define MAX_BP_LUT_ROWS 35
#define MAX_BP_LUT_COLS 8
#define MAX_PROFILE_NAME_LENGTH 256
enum profile_table_type {
CHARGE_TABLE = 0,
DISCHARGE_TABLE,
};
struct battery_data_table {
unsigned short int table[MAX_BP_LUT_ROWS][MAX_BP_LUT_COLS];
int unit_conv_factor[MAX_BP_LUT_COLS];
unsigned short int nrows;
unsigned short int ncols;
};
struct battery_config {
char bp_profile_name[MAX_PROFILE_NAME_LENGTH];
int bp_batt_id;
int capacity;
int bp_checksum;
int soh_range_high;
int soh_range_low;
int normal_impedance;
int aged_impedance;
int normal_capacity;
int aged_capacity;
int recharge_soc_delta;
int recharge_vflt_delta;
int recharge_iterm;
};
struct battery_profile_table {
enum profile_table_type table_type;
int table_index;
struct battery_data_table *table;
};
/* IOCTLs to query battery profile data */
#define BPIOCXBP _IOWR('B', 0x01, struct battery_config) /* Battery configuration */
#define BPIOCXBPTABLE _IOWR('B', 0x02, struct battery_profile_table) /* Battery profile table */
#endif

142
include/uapi/linux/qbg.h Normal file
View file

@ -0,0 +1,142 @@
/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */
/*
* Copyright (c) 2021, The Linux Foundation. All rights reserved.
*/
#ifndef __QBG_H__
#define __QBG_H__
#define MAX_FIFO_COUNT 36
enum QBG_SDAM_DATA_OFFSET {
QBG_ACC0_OFFSET = 0,
QBG_ACC1_OFFSET = 2,
QBG_ACC2_OFFSET = 4,
QBG_TBAT_OFFSET = 6,
QBG_IBAT_OFFSET = 8,
QBG_VREF_OFFSET = 10,
QBG_DATA_TAG_OFFSET = 12,
QBG_QG_STS_OFFSET,
QBG_STS1_OFFSET,
QBG_STS2_OFFSET,
QBG_STS3_OFFSET,
QBG_ONE_FIFO_LENGTH,
};
enum qbg {
QBG_PARAM_SOC,
QBG_PARAM_BATT_SOC,
QBG_PARAM_SYS_SOC,
QBG_PARAM_ESR,
QBG_PARAM_OCV_UV,
QBG_PARAM_MAX_LOAD_NOW,
QBG_PARAM_MAX_LOAD_AVG,
QBG_PARAM_HOLD_SOC_100PCT,
QBG_PARAM_CHARGE_CYCLE_COUNT,
QBG_PARAM_LEARNED_CAPACITY,
QBG_PARAM_TTF_100MS,
QBG_PARAM_TTE_100MS,
QBG_PARAM_SOH,
QBG_PARAM_TBAT,
QBG_PARAM_SYS_SOC_HOLD_100PCT,
QBG_PARAM_JEITA_COOL_THRESHOLD,
QBG_PARAM_TOTAL_IMPEDANCE,
QBG_PARAM_PON_OCV,
QBG_PARAM_PON_IBAT,
QBG_PARAM_PON_SOC,
QBG_PARAM_BATT_ID,
QBG_PARAM_FIFO_TIMESTAMP,
QBG_PARAM_MAX,
};
struct qbg_essential_params {
short int msoc;
short int cutoff_soc;
short int full_soc;
short int x0;
short int x1;
short int x2;
short int soh_r;
short int soh_c;
short int theta0;
short int theta1;
short int theta2;
short int i1full;
short int i2full;
short int i1cutoff;
short int i2cutoff;
short int syssoc;
int discharge_cycle_count;
int charge_cycle_count;
unsigned int rtc_time;
short int batt_therm;
unsigned short int ocv;
} __attribute__ ((__packed__));
struct fifo_data {
unsigned short int v1;
unsigned short int v2;
unsigned short int i;
unsigned short int tbat;
unsigned short int ibat;
unsigned short int vref;
char data_tag;
char qg_sts;
char sts1;
char sts2;
char sts3;
} __attribute__ ((__packed__));
struct k_fifo_data {
unsigned int v1;
unsigned int v2;
unsigned int i;
unsigned int tbat;
unsigned int ibat;
unsigned int vref;
unsigned int data_tag;
unsigned int qg_sts;
unsigned int sts1;
unsigned int sts2;
unsigned int sts3;
} __attribute__ ((__packed__));
struct qbg_config {
unsigned int batt_id;
unsigned int pon_ocv;
unsigned int pon_ibat;
unsigned int pon_tbat;
unsigned int pon_soc;
unsigned int float_volt_uv;
unsigned int fastchg_curr_ma;
unsigned int vbat_cutoff_mv;
unsigned int ibat_cutoff_ma;
unsigned int vph_min_mv;
unsigned int iterm_ma;
unsigned int rconn_mohm;
unsigned long current_time;
} __attribute__ ((__packed__));
struct qbg_param {
unsigned int data;
_Bool valid;
};
struct qbg_kernel_data {
unsigned int seq_no;
unsigned int fifo_time;
unsigned int fifo_count;
struct k_fifo_data fifo[MAX_FIFO_COUNT];
struct qbg_param param[QBG_PARAM_MAX];
} __attribute__ ((__packed__));
struct qbg_user_data {
struct qbg_param param[QBG_PARAM_MAX];
} __attribute__ ((__packed__));
/* IOCTLs to read & write QBG config and essential params */
#define QBGIOCXCFG _IOR('B', 0x01, struct qbg_config)
#define QBGIOCXEPR _IOR('B', 0x02, struct qbg_essential_params)
#define QBGIOCXEPW _IOWR('B', 0x03, struct qbg_essential_params)
#endif