Merge "drivers: qpnp-qg: Add snapshot of the QPNP QG driver"

This commit is contained in:
qctecmdr Service 2018-05-25 06:24:58 -07:00 committed by Gerrit - the friendly Code Review server
commit 0d4f78b359
19 changed files with 5680 additions and 0 deletions

View File

@ -0,0 +1,319 @@
Qualcomm Techonologies, Inc. QPNP PMIC QGAUGE (QG) Device
QPNP PMIC QGAUGE device provides the ability to gauge the State-of-Charge
of the battery. It provides an interface to the clients to read various
battery related parameters.
=======================
Required Node Structure
=======================
Qgauge device must be described in two level of nodes. The first level
describes the properties of the Qgauge device and the second level
describes the peripherals managed/used of the module.
====================================
First Level Node - QGAUGE device
====================================
- compatible
Usage: required
Value type: <string>
Definition: Should be "qcom,qpnp-qg".
- qcom,pmic-revid
Usage: required
Value type: <phandle>
Definition: Should specify the phandle of PMIC revid module. This is
used to identify the PMIC subtype.
- qcom,qg-vadc
Usage: required
Value type: <phandle>
Definition: Phandle for the VADC node, it is used for BATT_ID and
BATT_THERM readings.
- qcom,vbatt-empty-mv
Usage: optional
Value type: <u32>
Definition: The battery voltage threshold (in mV) at which the
vbatt-empty interrupt fires. The SOC is forced to 0
when this interrupt fires. If not specified, the
default value is 3200 mV.
- qcom,vbatt-empty-cold-mv
Usage: optional
Value type: <u32>
Definition: The battery voltage threshold (in mV) at which the
vbatt-empty interrupt fires. This threshold is only
applied at cold temperature specified by
'qcom,cold-temp-threshold'. The SOC is forced to 0
when this interrupt fires. If not specified, the
default value is 3000 mV.
- qcom,vbatt-cutoff-mv
Usage: optional
Value type: <u32>
Definition: The battery voltage threshold (in mV) at which the
the Qgauge algorithm converges to 0 SOC. If not specified
the default value is 3400 mV.
- qcom,vbatt-low-mv
Usage: optional
Value type: <u32>
Definition: The battery voltage threshold (in mV) at which the
the VBAT_LOW interrupt fires. Software can take necessary
the action when this interrupt fires. If not specified
the default value is 3500 mV.
- qcom,vbatt-low-cold-mv
Usage: optional
Value type: <u32>
Definition: The battery voltage threshold (in mV) at which the
the VBAT_LOW interrupt fires. The threshold is only
applied at cold temperature specified by
'qcom,cold-temp-threshold'. Software can take necessary
the action when this interrupt fires. If not specified
the default value is 3800 mV.
- qcom,qg-iterm-ma
Usage: optional
Value type: <u32>
Definition: The battery current (in mA) at which the the QG algorithm
converges the SOC to 100% during charging and can be used to
terminate charging. If not specified, the default value is
100mA.
- qcom,delta-soc
Usage: optional
Value type: <u32>
Definition: The SOC percentage increase at which the SOC is
periodically reported to the userspace. If not specified,
the value defaults to 1%.
- qcom,s2-fifo-length
Usage: optional
Value type: <u32>
Definition: The total number if FIFO samples which need to be filled up
in S2 state of QG to fire the FIFO DONE interrupt.
Minimum value = 1 Maximum Value = 8. If not specified,
the default value is 5.
- qcom,s2-acc-length
Usage: optional
Value type: <u32>
Definition: The number of distinct V & I samples to be accumulated
in each FIFO in the S2 state of QG.
Minimum Value = 0 Maximum Value = 256. If not specified,
the default value is 128.
- qcom,s2-acc-interval-ms
Usage: optional
Value type: <u32>
Definition: The time (in ms) between each of the V & I samples being
accumulated in FIFO.
Minimum Value = 0 ms Maximum Value = 2550 ms. If not
specified the default value is 100 ms.
- qcom,ocv-timer-expiry-min
Usage: optional
Value type: <u32>
Definition: The maximum time (in minutes) for the QG to transition from
S3 to S2 state.
Minimum Value = 2 min Maximum Value = 30 min. If not
specified the hardware default is set to 14 min.
- qcom,ocv-tol-threshold-uv
Usage: optional
Value type: <u32>
Definition: The OCV detection error tolerance (in uV). The maximum
voltage allowed between 2 VBATT readings in the S3 state
to qualify for a valid OCV.
Minimum Value = 0 uV Maximum Value = 12262 uV Step = 195 uV
- qcom,s3-entry-fifo-length
Usage: optional
Value type: <u32>
Definition: The minimum number if FIFO samples which have to qualify the
S3 IBAT entry threshold (qcom,s3-entry-ibat-ua) for QG
to enter into S3 state.
Minimum Value = 1 Maximum Value = 8. The hardware default
is configured to 3.
- qcom,s3-entry-ibat-ua
Usage: optional
Value type: <u32>
Definition: The battery current (in uA) for the QG to enter into the S3
state. The QG algorithm enters into S3 if the battery
current is lower than this threshold consecutive for
the FIFO length specified in 'qcom,s3-entry-fifo-length'.
Minimum Value = 0 uA Maximum Value = 155550 uA
Step = 610 uA.
- qcom,s3-exit-ibat-ua
Usage: optional
Value type: <u32>
Definition: The battery current (in uA) for the QG to exit S3 state.
If the battery current is higher than this threshold QG
exists S3 state.
Minimum Value = 0 uA Maximum Value = 155550 uA
Step = 610 uA.
- qcom,rbat-conn-mohm
Usage: optional
Value type: <u32>
Definition: Resistance of the battery connectors in mOhms.
- qcom,ignore-shutdown-soc-secs
Usage: optional
Value type: <u32>
Definition: Time in seconds beyond which shutdown SOC is ignored.
If not specified the default value is 360 secs.
- qcom,hold-soc-while-full
Usage: optional
Value type: <empty>
Definition: A boolean property that when defined holds SOC at 100% when
the battery is full until recharge starts.
- qcom,linearize-soc
Usage: optional
Value type: <empty>
Definition: A boolean property that when defined linearizes SOC when
the SOC drops after charge termination monotonically to
improve the user experience. This is applicable only if
"qcom,hold-soc-while-full" is specified.
- qcom,cold-temp-threshold
Usage: optional
Value type: <u32>
Definition: Temperature threshold in decidegree at which the low
temperature specific configuration as applied. If not
specified, the default value is 0 degree centigrade.
- qcom,cl-disable
Usage: optional
Value type: <empty>
Definition: A boolean property to disable the battery capacity
learning when charging.
- qcom,cl-feedback-on
Usage: optional
Value type: <empty>
Definition: A boolean property to feedback the learned capacity into
the capacity lerning algorithm. This has to be used only if the
property "qcom,cl-disable" is not specified.
- qcom,cl-max-start-soc
Usage: optional
Value type: <u32>
Definition: Battery SOC has to be below or equal to this value at the
start of a charge cycle to start the capacity learning.
If this is not specified, then the default value used
will be 15. Unit is in percentage.
- qcom,cl-min-start-soc
Usage: optional
Value type: <u32>
Definition: Battery SOC has to be above or equal to this value at the
start of a charge cycle to start the capacity learning.
If this is not specified, then the default value used
will be 10. Unit is in percentage.
- qcom,cl-min-temp
Usage: optional
Value type: <u32>
Definition: Lower limit of battery temperature to start the capacity
learning. If this is not specified, then the default value
used will be 150 (15 C). Unit is in decidegC.
- qcom,cl-max-temp
Usage: optional
Value type: <u32>
Definition: Upper limit of battery temperature to start the capacity
learning. If this is not specified, then the default value
used will be 500 (50 C). Unit is in decidegC.
- qcom,cl-max-increment
Usage: optional
Value type: <u32>
Definition: Maximum capacity increment allowed per capacity learning
cycle. If this is not specified, then the default value
used will be 5 (0.5%). Unit is in decipercentage.
- qcom,cl-max-decrement
Usage: optional
Value type: <u32>
Definition: Maximum capacity decrement allowed per capacity learning
cycle. If this is not specified, then the default value
used will be 100 (10%). Unit is in decipercentage.
- qcom,cl-min-limit
Usage: optional
Value type: <u32>
Definition: Minimum limit that the capacity cannot go below in a
capacity learning cycle. If this is not specified, then
the default value is 0. Unit is in decipercentage.
- qcom,cl-max-limit
Usage: optional
Value type: <u32>
Definition: Maximum limit that the capacity cannot go above in a
capacity learning cycle. If this is not specified, then
the default value is 0. Unit is in decipercentage.
==========================================================
Second Level Nodes - Peripherals managed by QGAUGE driver
==========================================================
- reg
Usage: required
Value type: <prop-encoded-array>
Definition: Addresses and sizes for the specified peripheral
- interrupts
Usage: optional
Value type: <prop-encoded-array>
Definition: Interrupt mapping as per the interrupt encoding
- interrupt-names
Usage: optional
Value type: <stringlist>
Definition: Interrupt names. This list must match up 1-to-1 with the
interrupts specified in the 'interrupts' property.
========
Example
========
pmi632_qg: qpnp,qg {
compatible = "qcom,qpnp-qg";
qcom,pmic-revid = <&pmi632_revid>;
qcom,qg-vadc = <&pmi632_vadc>;
qcom,vbatt-empty-mv = <3200>;
qcom,vbatt-low-mv = <3500>;
qcom,vbatt-cutoff-mv = <3400>;
qcom,qg-iterm-ma = <100>;
qcom,qgauge@4800 {
status = "okay";
reg = <0x4800 0x100>;
interrupts = <0x2 0x48 0x0 IRQ_TYPE_EDGE_BOTH>,
<0x2 0x48 0x1 IRQ_TYPE_EDGE_BOTH>,
<0x2 0x48 0x2 IRQ_TYPE_EDGE_BOTH>,
<0x2 0x48 0x4 IRQ_TYPE_EDGE_BOTH>,
<0x2 0x48 0x5 IRQ_TYPE_EDGE_BOTH>,
<0x2 0x48 0x6 IRQ_TYPE_EDGE_BOTH>;
interrupt-names = "qg-batt-missing",
"qg-vbat-low",
"qg-vbat-empty",
"qg-fifo-done",
"qg-good-ocv",
"qg-fsm-state-chg",
"qg-event";
};
qcom,qg-sdam@b000 {
status = "okay";
reg = <0xb000 0x100>;
};
};

View File

@ -19,6 +19,15 @@ config QPNP_FG_GEN4
reported through a BMS power supply property and also sends uevents
when the capacity is updated.
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 SMB1351_USB_CHARGER
tristate "smb1351 usb charger (with VBUS detection)"
depends on I2C

View File

@ -1,5 +1,6 @@
obj-$(CONFIG_QPNP_FG_GEN3) += qpnp-fg-gen3.o fg-memif.o fg-util.o
obj-$(CONFIG_QPNP_FG_GEN4) += qpnp-fg-gen4.o fg-memif.o fg-util.o fg-alg.o pmic-voter.o
obj-$(CONFIG_QPNP_QG) += qpnp-qg.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_SMB1351_USB_CHARGER) += smb1351-charger.o pmic-voter.o battery.o
obj-$(CONFIG_SMB1355_SLAVE_CHARGER) += smb1355-charger.o pmic-voter.o
obj-$(CONFIG_QPNP_SMB2) += step-chg-jeita.o battery.o qpnp-smb2.o smb-lib.o pmic-voter.o storm-watch.o

View File

@ -0,0 +1,524 @@
/* Copyright (c) 2018 The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#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 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 = 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 = 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 = 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 = 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 = 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) {
pr_err("Battery profile not loaded\n");
return -ENODEV;
}
*soc = 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) {
pr_err("Battery profile not loaded\n");
return -ENODEV;
}
fcc_mah = 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;
battery = kzalloc(sizeof(*battery), GFP_KERNEL);
if (!battery)
return -ENOMEM;
battery->profile_node = profile_node;
/* 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 delete_cdev;
}
/* 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;
}
the_battery = battery;
pr_info("QG Battery-profile loaded, '/dev/qg_battery' created!\n");
return 0;
destroy_device:
device_destroy(battery->battery_class, battery->dev_no);
delete_cdev:
cdev_del(&battery->battery_cdev);
unregister_chrdev:
unregister_chrdev_region(battery->dev_no, 1);
free_battery:
kfree(battery);
return rc;
}
void qg_batterydata_exit(void)
{
int i, j;
if (the_battery) {
/* unregister the device node */
device_destroy(the_battery->battery_class, the_battery->dev_no);
cdev_del(&the_battery->battery_cdev);
unregister_chrdev_region(the_battery->dev_no, 1);
/* 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);
}
}
kfree(the_battery);
the_battery = NULL;
}

View File

@ -0,0 +1,20 @@
/* Copyright (c) 2018 The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#ifndef __QG_BATTERY_PROFILE_H__
#define __QG_BATTERY_PROFILE_H__
int qg_batterydata_init(struct device_node *node);
void qg_batterydata_exit(void);
int lookup_soc_ocv(u32 *soc, u32 ocv_uv, int batt_temp, bool charging);
int qg_get_nominal_capacity(u32 *nom_cap_uah, int batt_temp, bool charging);
#endif /* __QG_BATTERY_PROFILE_H__ */

View File

@ -0,0 +1,171 @@
/* Copyright (c) 2018 The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#ifndef __QG_CORE_H__
#define __QG_CORE_H__
#include <linux/kernel.h>
#include "fg-alg.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 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 cold_temp_threshold;
bool hold_soc_while_full;
bool linearize_soc;
bool cl_disable;
bool cl_feedback_on;
};
struct qpnp_qg {
struct device *dev;
struct pmic_revid_data *pmic_rev_id;
struct regmap *regmap;
struct qpnp_vadc_chip *vadc_dev;
struct power_supply *qg_psy;
struct class *qg_class;
struct device *qg_device;
struct cdev qg_cdev;
dev_t dev_no;
struct work_struct udata_work;
struct work_struct scale_soc_work;
struct work_struct qg_status_change_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;
/* 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 *parallel_psy;
/* status variable */
u32 *debug_mask;
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 charge_full;
int charge_status;
int charge_type;
int next_wakeup_ms;
u32 wa_flags;
u32 seq_no;
u32 charge_counter_uah;
ktime_t last_user_update_time;
ktime_t last_fifo_update_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;
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;
};
enum ocv_type {
S7_PON_OCV,
S3_GOOD_OCV,
S3_LAST_OCV,
SDAM_PON_OCV,
};
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),
};
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),
};
#endif /* __QG_CORE_H__ */

View File

@ -0,0 +1,53 @@
/* Copyright (c) 2018 The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#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 V_RAW_TO_UV(V_RAW) div_u64(194637ULL * (u64)V_RAW, 1000)
#define I_RAW_TO_UA(I_RAW) div_s64(152588LL * (s64)I_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 CAP(min, max, value) \
((min > value) ? min : ((value > max) ? max : value))
#define QG_SOC_FULL 10000
#define BATT_SOC_32BIT GENMASK(31, 0)
#endif /* __QG_DEFS_H__ */

View File

@ -0,0 +1,311 @@
/* Copyright (c) 2018 The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/ratelimit.h>
#include "qg-profile-lib.h"
#include "qg-defs.h"
static int 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 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 = 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 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 = 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 = 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 = 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 = 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 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 = linear_interpolate(
lut->data[row1][i],
lut->row_entries[row1],
lut->data[row2][i],
lut->row_entries[row2],
soc);
return var;
}
var1 = 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 = 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 = linear_interpolate(
var1,
lut->row_entries[row1],
var2,
lut->row_entries[row2],
soc);
return var;
}
int 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 = 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 = linear_interpolate(
lut->data[row2][i - 1],
lut->col_entries[i - 1] * DEGC_SCALE,
lut->data[row2][i],
lut->col_entries[i] * DEGC_SCALE,
batt_temp);
slope = (ocvrow1 - ocvrow2);
if (slope <= 0) {
pr_warn_ratelimited("Slope=%d for soc=%d, using 1\n",
slope, soc);
slope = 1;
}
slope *= 10000;
slope /= (lut->row_entries[row1] - lut->row_entries[row2]);
return slope;
}

View File

@ -0,0 +1,34 @@
/* Copyright (c) 2018 The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#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 interpolate_single_row_lut(struct profile_table_data *lut,
int x, int scale);
int interpolate_soc(struct profile_table_data *lut,
int batt_temp, int ocv);
int interpolate_var(struct profile_table_data *lut,
int batt_temp, int soc);
int interpolate_slope(struct profile_table_data *lut,
int batt_temp, int soc);
#endif /*__QG_PROFILE_LIB_H__ */

View File

@ -0,0 +1,94 @@
/* Copyright (c) 2018 The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#ifndef __QG_REG_H__
#define __QG_REG_H__
#define PERPH_TYPE_REG 0x04
#define QG_TYPE 0x0D
#define QG_STATUS1_REG 0x08
#define BATTERY_PRESENT_BIT BIT(0)
#define QG_STATUS2_REG 0x09
#define GOOD_OCV_BIT BIT(1)
#define QG_STATUS3_REG 0x0A
#define COUNT_FIFO_RT_MASK GENMASK(3, 0)
#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 QG_INT_LATCHED_STS_REG 0x18
#define FIFO_UPDATE_DONE_INT_LAT_STS_BIT BIT(3)
#define QG_DATA_CTL1_REG 0x41
#define MASTER_HOLD_OR_CLR_BIT BIT(0)
#define QG_MODE_CTL1_REG 0x43
#define PARALLEL_IBAT_SENSE_EN_BIT BIT(7)
#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_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_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_S3_SLEEP_V_DATA0_REG 0xCC
/* SDAM offsets */
#define QG_SDAM_VALID_OFFSET 0x46
#define QG_SDAM_SOC_OFFSET 0x47
#define QG_SDAM_TEMP_OFFSET 0x48
#define QG_SDAM_RBAT_OFFSET 0x4A
#define QG_SDAM_OCV_OFFSET 0x4C
#define QG_SDAM_IBAT_OFFSET 0x50
#define QG_SDAM_TIME_OFFSET 0x54
#define QG_SDAM_CYCLE_COUNT_OFFSET 0x58
#define QG_SDAM_LEARNED_CAPACITY_OFFSET 0x68
#define QG_SDAM_PON_OCV_OFFSET 0x7C
#endif

View File

@ -0,0 +1,271 @@
/* Copyright (c) 2018 The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#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,
},
};
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=%0x4x 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;
}
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=%0x4x 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=%0x4x value=%d\n",
offset, *data);
} else {
for (i = 0; i < length; i++)
pr_debug("QG SDAM write offset=%0x4x 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=%0x4x\n", offset);
} else {
for (i = 0; i < length; i++)
pr_debug("QG SDAM read offset=%0x4x 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_init(struct device *dev)
{
int rc;
u32 base = 0, type = 0;
struct qg_sdam *chip;
struct device_node *child, *node = dev->of_node;
chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
if (!chip)
return 0;
chip->regmap = dev_get_regmap(dev->parent, NULL);
if (!chip->regmap) {
pr_err("Parent regmap is unavailable\n");
return -ENXIO;
}
/* get the SDAM base address */
for_each_available_child_of_node(node, child) {
rc = of_property_read_u32(child, "reg", &base);
if (rc < 0) {
pr_err("Failed to read base address rc=%d\n", rc);
return rc;
}
rc = regmap_read(chip->regmap, base + PERPH_TYPE_REG, &type);
if (rc < 0) {
pr_err("Failed to read type rc=%d\n", rc);
return rc;
}
switch (type) {
case SDAM_TYPE:
chip->sdam_base = base;
break;
default:
break;
}
}
if (!chip->sdam_base) {
pr_err("QG SDAM node not defined\n");
return -EINVAL;
}
the_chip = chip;
return 0;
}

View File

@ -0,0 +1,43 @@
/* Copyright (c) 2018 The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#ifndef __QG_SDAM_H__
#define __QG_SDAM_H__
#define SDAM_TYPE 0x2E
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_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);
#endif

View File

@ -0,0 +1,284 @@
/* Copyright (c) 2018 The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#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 "fg-alg.h"
#include "qg-sdam.h"
#include "qg-core.h"
#include "qg-reg.h"
#include "qg-util.h"
#include "qg-defs.h"
#define DEFAULT_UPDATE_TIME_MS 64000
#define SOC_SCALE_HYST_MS 2000
static int qg_delta_soc_interval_ms = 20000;
module_param_named(
soc_interval_ms, qg_delta_soc_interval_ms, int, 0600
);
static int qg_delta_soc_cold_interval_ms = 4000;
module_param_named(
soc_cold_interval_ms, qg_delta_soc_cold_interval_ms, int, 0600
);
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 < 0)
pr_err("Failed to read battery temperature rc=%d\n", rc);
else if (batt_temp < chip->dt.cold_temp_threshold)
min_delta_soc_interval_ms = qg_delta_soc_cold_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)
{
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 && !is_usb_present(chip))
/* USB is not present and SOC has increased */
return false;
return true;
}
static void update_msoc(struct qpnp_qg *chip)
{
int rc = 0, batt_temp = 0, batt_soc_32bit = 0;
bool usb_present = is_usb_present(chip);
if (chip->catch_up_soc > chip->msoc) {
/* SOC increased */
if (usb_present) /* Increment if USB 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) {
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 */
chip->sdam_data[SDAM_SOC] = chip->msoc;
rc = qg_sdam_write(SDAM_SOC, chip->msoc);
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 {
batt_soc_32bit = div64_u64(
chip->batt_soc * BATT_SOC_32BIT,
QG_SOC_FULL);
cap_learning_update(chip->cl, batt_temp, batt_soc_32bit,
chip->charge_status, chip->charge_done,
usb_present, false);
}
}
cycle_count_update(chip->counter,
DIV_ROUND_CLOSEST(chip->msoc * 255, 100),
chip->charge_status, chip->charge_done,
usb_present);
qg_dbg(chip, QG_DEBUG_SOC,
"SOC scale: Update maint_soc=%d msoc=%d catch_up_soc=%d delta_soc=%d\n",
chip->maint_soc, chip->msoc,
chip->catch_up_soc, chip->dt.delta_soc);
}
static void scale_soc_stop(struct qpnp_qg *chip)
{
chip->next_wakeup_ms = 0;
alarm_cancel(&chip->alarm_timer);
qg_dbg(chip, QG_DEBUG_SOC,
"SOC scale stopped: msoc=%d catch_up_soc=%d\n",
chip->msoc, chip->catch_up_soc);
}
static void scale_soc_work(struct work_struct *work)
{
struct qpnp_qg *chip = container_of(work,
struct qpnp_qg, scale_soc_work);
mutex_lock(&chip->soc_lock);
if (!is_scaling_required(chip)) {
scale_soc_stop(chip);
goto done;
}
update_msoc(chip);
if (is_scaling_required(chip)) {
alarm_start_relative(&chip->alarm_timer,
ms_to_ktime(chip->next_wakeup_ms));
} else {
scale_soc_stop(chip);
goto done_psy;
}
qg_dbg(chip, QG_DEBUG_SOC,
"SOC scale: Work msoc=%d catch_up_soc=%d delta_soc=%d next_wakeup=%d sec\n",
chip->msoc, chip->catch_up_soc, chip->dt.delta_soc,
chip->next_wakeup_ms / 1000);
done_psy:
power_supply_changed(chip->qg_psy);
done:
pm_relax(chip->dev);
mutex_unlock(&chip->soc_lock);
}
static enum alarmtimer_restart
qpnp_msoc_timer(struct alarm *alarm, ktime_t now)
{
struct qpnp_qg *chip = container_of(alarm,
struct qpnp_qg, alarm_timer);
/* timer callback runs in atomic context, cannot use voter */
pm_stay_awake(chip->dev);
schedule_work(&chip->scale_soc_work);
return ALARMTIMER_NORESTART;
}
int qg_scale_soc(struct qpnp_qg *chip, bool force_soc)
{
int rc = 0;
mutex_lock(&chip->soc_lock);
qg_dbg(chip, QG_DEBUG_SOC,
"SOC scale: Start msoc=%d catch_up_soc=%d delta_soc=%d\n",
chip->msoc, chip->catch_up_soc, chip->dt.delta_soc);
if (force_soc) {
chip->msoc = chip->catch_up_soc;
rc = qg_write_monotonic_soc(chip, chip->msoc);
if (rc < 0)
pr_err("Failed to update MSOC register rc=%d\n", rc);
qg_dbg(chip, QG_DEBUG_SOC,
"SOC scale: Forced msoc=%d\n", chip->msoc);
goto done_psy;
}
if (!is_scaling_required(chip)) {
scale_soc_stop(chip);
goto done;
}
update_msoc(chip);
if (is_scaling_required(chip)) {
get_next_update_time(chip);
alarm_start_relative(&chip->alarm_timer,
ms_to_ktime(chip->next_wakeup_ms));
} else {
scale_soc_stop(chip);
goto done_psy;
}
qg_dbg(chip, QG_DEBUG_SOC,
"SOC scale: msoc=%d catch_up_soc=%d delta_soc=%d next_wakeup=%d sec\n",
chip->msoc, chip->catch_up_soc, chip->dt.delta_soc,
chip->next_wakeup_ms / 1000);
done_psy:
power_supply_changed(chip->qg_psy);
done:
mutex_unlock(&chip->soc_lock);
return rc;
}
int qg_soc_init(struct qpnp_qg *chip)
{
if (alarmtimer_get_rtcdev()) {
alarm_init(&chip->alarm_timer, ALARM_BOOTTIME,
qpnp_msoc_timer);
} else {
pr_err("Failed to get soc alarm-timer\n");
return -EINVAL;
}
INIT_WORK(&chip->scale_soc_work, scale_soc_work);
return 0;
}
void qg_soc_exit(struct qpnp_qg *chip)
{
alarm_cancel(&chip->alarm_timer);
}

View File

@ -0,0 +1,20 @@
/* Copyright (c) 2018 The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#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);
#endif /* __QG_SOC_H__ */

View File

@ -0,0 +1,313 @@
/* Copyright (c) 2018 The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#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 get_fifo_length(struct qpnp_qg *chip, u32 *fifo_length, bool rt)
{
int rc;
u8 reg = 0;
u32 addr;
addr = rt ? QG_STATUS3_REG : QG_S2_NORMAL_MEAS_CTL2_REG;
rc = qg_read(chip, chip->qg_base + addr, &reg, 1);
if (rc < 0) {
pr_err("Failed to read FIFO length rc=%d\n", rc);
return rc;
}
if (rt) {
*fifo_length = reg & COUNT_FIFO_RT_MASK;
} else {
*fifo_length = (reg & FIFO_LENGTH_MASK) >> FIFO_LENGTH_SHIFT;
*fifo_length += 1;
}
return rc;
}
int get_sample_count(struct qpnp_qg *chip, u32 *sample_count)
{
int rc;
u8 reg = 0;
rc = qg_read(chip, chip->qg_base + QG_S2_NORMAL_MEAS_CTL2_REG,
&reg, 1);
if (rc < 0) {
pr_err("Failed to read FIFO sample count rc=%d\n", rc);
return rc;
}
*sample_count = 1 << ((reg & NUM_OF_ACCUM_MASK) + 1);
return rc;
}
int get_sample_interval(struct qpnp_qg *chip, u32 *sample_interval)
{
int rc;
u8 reg = 0;
rc = qg_read(chip, chip->qg_base + QG_S2_NORMAL_MEAS_CTL3_REG,
&reg, 1);
if (rc < 0) {
pr_err("Failed to read FIFO sample interval rc=%d\n", rc);
return rc;
}
*sample_interval = reg * 10;
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;
}
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;
}
static bool is_parallel_available(struct qpnp_qg *chip)
{
if (chip->parallel_psy)
return true;
chip->parallel_psy = power_supply_get_by_name("parallel");
if (!chip->parallel_psy)
return false;
return true;
}
bool is_parallel_enabled(struct qpnp_qg *chip)
{
union power_supply_propval pval = {0, };
if (is_parallel_available(chip)) {
power_supply_get_property(chip->parallel_psy,
POWER_SUPPLY_PROP_CHARGING_ENABLED, &pval);
}
return pval.intval ? true : false;
}
int qg_write_monotonic_soc(struct qpnp_qg *chip, int msoc)
{
u8 reg = 0;
int rc;
reg = (msoc * 255) / 100;
rc = qg_write(chip, chip->qg_base + QG_SOC_MONOTONIC_REG,
&reg, 1);
if (rc < 0)
pr_err("Failed to update QG_SOC_MONOTINIC reg rc=%d\n", rc);
return rc;
}
int qg_get_battery_temp(struct qpnp_qg *chip, int *temp)
{
int rc = 0;
if (chip->battery_missing) {
*temp = 250;
return 0;
}
rc = iio_read_channel_processed(chip->batt_therm_chan, temp);
if (rc < 0) {
pr_err("Failed reading BAT_TEMP over ADC rc=%d\n", rc);
return rc;
}
pr_debug("batt_temp = %d\n", *temp);
return rc;
}

View File

@ -0,0 +1,28 @@
/* Copyright (c) 2018 The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#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 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_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);
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,66 @@
#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 3000000
#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
/* 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__ */

55
include/uapi/linux/qg.h Normal file
View File

@ -0,0 +1,55 @@
#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_RESERVED_3,
QG_RESERVED_4,
QG_RESERVED_5,
QG_RESERVED_6,
QG_RESERVED_7,
QG_RESERVED_8,
QG_RESERVED_9,
QG_RESERVED_10,
QG_MAX,
};
#define QG_BATT_SOC QG_BATT_SOC
#define QG_CC_SOC QG_CC_SOC
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