mirror of
https://github.com/rd-stuffs/msm-4.14.git
synced 2025-02-20 11:45:48 +08:00
Merge "drivers: qpnp-qg: Add snapshot of the QPNP QG driver"
This commit is contained in:
commit
0d4f78b359
319
Documentation/devicetree/bindings/power/supply/qcom/qpnp-qg.txt
Normal file
319
Documentation/devicetree/bindings/power/supply/qcom/qpnp-qg.txt
Normal 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>;
|
||||
};
|
||||
};
|
@ -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
|
||||
|
@ -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
|
||||
|
524
drivers/power/supply/qcom/qg-battery-profile.c
Normal file
524
drivers/power/supply/qcom/qg-battery-profile.c
Normal 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;
|
||||
}
|
20
drivers/power/supply/qcom/qg-battery-profile.h
Normal file
20
drivers/power/supply/qcom/qg-battery-profile.h
Normal 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__ */
|
171
drivers/power/supply/qcom/qg-core.h
Normal file
171
drivers/power/supply/qcom/qg-core.h
Normal 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__ */
|
53
drivers/power/supply/qcom/qg-defs.h
Normal file
53
drivers/power/supply/qcom/qg-defs.h
Normal 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__ */
|
311
drivers/power/supply/qcom/qg-profile-lib.c
Normal file
311
drivers/power/supply/qcom/qg-profile-lib.c
Normal 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;
|
||||
}
|
34
drivers/power/supply/qcom/qg-profile-lib.h
Normal file
34
drivers/power/supply/qcom/qg-profile-lib.h
Normal 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__ */
|
94
drivers/power/supply/qcom/qg-reg.h
Normal file
94
drivers/power/supply/qcom/qg-reg.h
Normal 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
|
271
drivers/power/supply/qcom/qg-sdam.c
Normal file
271
drivers/power/supply/qcom/qg-sdam.c
Normal 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;
|
||||
}
|
43
drivers/power/supply/qcom/qg-sdam.h
Normal file
43
drivers/power/supply/qcom/qg-sdam.h
Normal 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
|
284
drivers/power/supply/qcom/qg-soc.c
Normal file
284
drivers/power/supply/qcom/qg-soc.c
Normal 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);
|
||||
}
|
20
drivers/power/supply/qcom/qg-soc.h
Normal file
20
drivers/power/supply/qcom/qg-soc.h
Normal 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__ */
|
313
drivers/power/supply/qcom/qg-util.c
Normal file
313
drivers/power/supply/qcom/qg-util.c
Normal 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, ®, 1);
|
||||
if (rc < 0) {
|
||||
pr_err("Failed to read FIFO length rc=%d\n", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (rt) {
|
||||
*fifo_length = reg & COUNT_FIFO_RT_MASK;
|
||||
} else {
|
||||
*fifo_length = (reg & FIFO_LENGTH_MASK) >> FIFO_LENGTH_SHIFT;
|
||||
*fifo_length += 1;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int get_sample_count(struct qpnp_qg *chip, u32 *sample_count)
|
||||
{
|
||||
int rc;
|
||||
u8 reg = 0;
|
||||
|
||||
rc = qg_read(chip, chip->qg_base + QG_S2_NORMAL_MEAS_CTL2_REG,
|
||||
®, 1);
|
||||
if (rc < 0) {
|
||||
pr_err("Failed to read FIFO sample count rc=%d\n", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
*sample_count = 1 << ((reg & NUM_OF_ACCUM_MASK) + 1);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int get_sample_interval(struct qpnp_qg *chip, u32 *sample_interval)
|
||||
{
|
||||
int rc;
|
||||
u8 reg = 0;
|
||||
|
||||
rc = qg_read(chip, chip->qg_base + QG_S2_NORMAL_MEAS_CTL3_REG,
|
||||
®, 1);
|
||||
if (rc < 0) {
|
||||
pr_err("Failed to read FIFO sample interval rc=%d\n", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
*sample_interval = reg * 10;
|
||||
|
||||
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,
|
||||
®, 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;
|
||||
}
|
28
drivers/power/supply/qcom/qg-util.h
Normal file
28
drivers/power/supply/qcom/qg-util.h
Normal 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
|
3064
drivers/power/supply/qcom/qpnp-qg.c
Normal file
3064
drivers/power/supply/qcom/qpnp-qg.c
Normal file
File diff suppressed because it is too large
Load Diff
66
include/uapi/linux/qg-profile.h
Normal file
66
include/uapi/linux/qg-profile.h
Normal 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
55
include/uapi/linux/qg.h
Normal 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
|
Loading…
x
Reference in New Issue
Block a user