3003 lines
77 KiB
C
3003 lines
77 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (c) 2021, The Linux Foundation. All rights reserved.
|
|
* Copyright (c) 2022-2024, Qualcomm Innovation Center, Inc. All rights reserved.
|
|
*/
|
|
|
|
#define pr_fmt(fmt) "QBG_K: %s: " fmt, __func__
|
|
|
|
#include <linux/cdev.h>
|
|
#include <linux/device.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/iio/iio.h>
|
|
#include <linux/iio/consumer.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/ioctl.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/math64.h>
|
|
#include <linux/module.h>
|
|
#include <linux/nvmem-consumer.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_irq.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/poll.h>
|
|
#include <linux/power_supply.h>
|
|
#include <linux/pm_wakeup.h>
|
|
#include <linux/qti_power_supply.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/rtc.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/suspend.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/workqueue.h>
|
|
#include <linux/time64.h>
|
|
|
|
#include <dt-bindings/iio/qti_power_supply_iio.h>
|
|
|
|
#include <uapi/linux/qbg.h>
|
|
#include <uapi/linux/qbg-profile.h>
|
|
#include "qbg-iio.h"
|
|
#include "qbg-battery-profile.h"
|
|
#include "qbg-core.h"
|
|
#include "qbg-sdam.h"
|
|
#include "battery-profile-loader.h"
|
|
|
|
/* QBG register definitions */
|
|
#define QBG_MAIN_STATUS1 0x08
|
|
#define QBG_OK_BIT BIT(7)
|
|
|
|
#define QBG_MAIN_STATUS2 0x09
|
|
#define QBG_BATTERY_MISSING_BIT BIT(2)
|
|
#define QBG_ADC_CONV_FAULT_OCCURRED_BIT BIT(1)
|
|
#define QBG_DATA_RESET_BIT BIT(0)
|
|
|
|
#define QBG_MAIN_STATUS3 0x0a
|
|
#define QBG_FSM_STATE_MASK GENMASK(3, 0)
|
|
|
|
#define QBG_MAIN_QBG_STATE_FORCE_CMD 0x41
|
|
#define FORCE_FAST_CHAR_MODE_BIT BIT(3)
|
|
#define FORCE_FAST_CHAR_SHIFT 3
|
|
|
|
#define QBG_MAIN_EN_CTL 0x46
|
|
#define QBG_EN_BIT BIT(7)
|
|
|
|
#define QBG_MAIN_MPM_TO_LPM_IBAT_THRESH 0x50
|
|
|
|
#define QBG_MAIN_LPM_TO_MPM_IBAT_THRESH 0x51
|
|
|
|
#define QBG_MAIN_HPM_TO_MPM_IBAT_THRESH 0x52
|
|
|
|
#define QBG_MAIN_MPM_TO_HPM_IBAT_THRESH 0x53
|
|
|
|
#define QBG_MAIN_VBAT_EMPTY_THRESH 0x56
|
|
|
|
#define QBG_MAIN_HPM_MEAS_CTL2 0x5d
|
|
|
|
#define QBG_MAIN_MPM_MEAS_CTL2 0x61
|
|
|
|
#define QBG_MAIN_LPM_MEAS_CTL2 0x65
|
|
#define QBG_NUM_OF_ACCUM_MASK GENMASK(7, 5)
|
|
#define QBG_NUM_OF_ACCUM_SHIFT 5
|
|
#define QBG_ACCUM_INTERVAL_MASK GENMASK(2, 0)
|
|
|
|
#define QBG_MAIN_FAST_CHAR_MEAS_CTL2 0x69
|
|
|
|
#define QBG_MAIN_PON_OCV_ACC0_DATA0 0x70
|
|
|
|
#define QBG_MAIN_LAST_ADC_ACC0_DATA0 0x9A
|
|
|
|
#define QBG_MAIN_LAST_BURST_AVG_ACC0_DATA0 0xA0
|
|
|
|
#define QBG_MAIN_LAST_BURST_AVG_ACC2_DATA0 0xA4
|
|
|
|
/* PM5100 REVID */
|
|
#define REVID_REVISION4 0x103
|
|
#define REVISION_V1 0x1
|
|
|
|
#define QBG_FAST_CHAR_DELTA_MS 100000
|
|
#define VBATT_1S_LSB 19463
|
|
#define IBATT_10A_LSB 6103
|
|
#define VBAT_0PCT_OCV 300000000ULL
|
|
#define ICHG_FS_10A 1
|
|
#define TBAT_LSB 7500
|
|
#define TBAT_DENOMINATOR 115600
|
|
|
|
#define BOOST_COMPENSATION_UV 400000
|
|
#define OCV_BOOST_MIN_UV 4100000
|
|
#define OCV_BOOST_MAX_UV 5600000
|
|
#define OCV_BOOST_STEP_UV 100000
|
|
|
|
#define BATT_HOT_DECIDEGREE_MAX 600
|
|
|
|
#define MILLI_TO_10NANO 100000
|
|
#define MICRO_TO_10NANO 100
|
|
#define MILLI_TO_MICRO 1000
|
|
|
|
/* PM5100 ADC_CMN and ADC_CMN_WB register definitions */
|
|
#define BAT_THERM_IBAT_CTL_2 0x62
|
|
#define VADC_ANA_PARAM_4 0x46
|
|
|
|
static int qbg_debug_mask;
|
|
|
|
enum qbg_esr_fcc_reduction {
|
|
ESR_FCC_150MA = 0x6,
|
|
};
|
|
|
|
#define ESR_PULSE_10NA(x) (-(x * 25 * MILLI_TO_10NANO))
|
|
#define ESR_IBAT_QUALIFY_OFFSET_10NA (-5000000)
|
|
|
|
static const unsigned int qbg_accum_interval[8] = {
|
|
10000, 20000, 50000, 100000, 150000, 200000, 500000, 1000000};
|
|
static const unsigned int qbg_lpm_accum_interval[8] = {
|
|
100000, 200000, 500000, 1000000, 2000000, 5000000, 10000000, 100000000};
|
|
static const unsigned int qbg_fast_char_avg_interval[8] = {
|
|
6250, 12500, 25000, 50000, 100000, 200000, 400000, 800000};
|
|
|
|
static const struct qbg_iio_channels qbg_iio_psy_channels[] = {
|
|
QBG_CHAN_INDEX("debug_battery", PSY_IIO_DEBUG_BATTERY)
|
|
QBG_CHAN_ENERGY("capacity", PSY_IIO_CAPACITY)
|
|
QBG_CHAN_ENERGY("real_capacity", PSY_IIO_REAL_CAPACITY)
|
|
QBG_CHAN_TEMP("temp", PSY_IIO_TEMP)
|
|
QBG_CHAN_VOLT("voltage_max", PSY_IIO_VOLTAGE_MAX)
|
|
QBG_CHAN_VOLT("voltage_now", PSY_IIO_VOLTAGE_NOW)
|
|
QBG_CHAN_VOLT("voltage_ocv", PSY_IIO_VOLTAGE_OCV)
|
|
QBG_CHAN_VOLT("voltage_avg", PSY_IIO_VOLTAGE_AVG)
|
|
QBG_CHAN_CUR("current_now", PSY_IIO_CURRENT_NOW)
|
|
QBG_CHAN_RES("resistance_id", PSY_IIO_RESISTANCE_ID)
|
|
QBG_CHAN_TSTAMP("time_to_full_avg", PSY_IIO_TIME_TO_FULL_AVG)
|
|
QBG_CHAN_TSTAMP("time_to_full_now", PSY_IIO_TIME_TO_FULL_NOW)
|
|
QBG_CHAN_TSTAMP("time_to_empty_avg", PSY_IIO_TIME_TO_EMPTY_AVG)
|
|
QBG_CHAN_RES("esr", PSY_IIO_ESR_ACTUAL)
|
|
QBG_CHAN_INDEX("soh", PSY_IIO_SOH)
|
|
QBG_CHAN_COUNT("cycle_count", PSY_IIO_CYCLE_COUNT)
|
|
QBG_CHAN_ENERGY("charge_full", PSY_IIO_CHARGE_FULL)
|
|
QBG_CHAN_ENERGY("charge_full_design", PSY_IIO_CHARGE_FULL_DESIGN)
|
|
QBG_CHAN_ENERGY("charge_counter", PSY_IIO_CHARGE_COUNTER)
|
|
};
|
|
|
|
int qbg_read(struct qti_qbg *chip, u32 addr, u8 *val, int len)
|
|
{
|
|
int rc;
|
|
|
|
rc = regmap_bulk_read(chip->regmap, chip->base + addr, val, len);
|
|
if (rc < 0) {
|
|
pr_err("Failed to read from address %04x rc=%d\n", chip->base + addr, rc);
|
|
return rc;
|
|
}
|
|
|
|
if (*chip->debug_mask & QBG_DEBUG_BUS_READ)
|
|
pr_info("length %d addr=%#x data:%*ph\n", len, chip->base + addr, len, val);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int qbg_write(struct qti_qbg *chip, u32 addr, u8 *val, int len)
|
|
{
|
|
int rc;
|
|
|
|
rc = regmap_bulk_write(chip->regmap, chip->base + addr, val, len);
|
|
if (rc < 0) {
|
|
pr_err("Failed to write to address %04x rc=%d\n",
|
|
chip->base + addr, rc);
|
|
return rc;
|
|
}
|
|
|
|
if (*chip->debug_mask & QBG_DEBUG_BUS_WRITE)
|
|
pr_info("length %d addr=%#x data:%*ph\n", len, chip->base + addr, len, val);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool is_chan_valid(struct qti_qbg *chip, enum qbg_ext_iio_channels chan)
|
|
{
|
|
int rc;
|
|
|
|
if (IS_ERR(chip->ext_iio_chans[chan]))
|
|
return false;
|
|
|
|
if (!chip->ext_iio_chans[chan]) {
|
|
chip->ext_iio_chans[chan] = iio_channel_get(chip->dev,
|
|
qbg_ext_iio_chan_name[chan]);
|
|
if (IS_ERR(chip->ext_iio_chans[chan])) {
|
|
rc = PTR_ERR(chip->ext_iio_chans[chan]);
|
|
if (rc == -EPROBE_DEFER)
|
|
chip->ext_iio_chans[chan] = NULL;
|
|
|
|
pr_err("Failed to get IIO channel %s, rc=%d\n",
|
|
qbg_ext_iio_chan_name[chan], rc);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static int qbg_read_iio_chan(struct qti_qbg *chip, enum qbg_ext_iio_channels chan,
|
|
int *val)
|
|
{
|
|
int rc;
|
|
|
|
if (is_chan_valid(chip, chan)) {
|
|
rc = iio_read_channel_processed(chip->ext_iio_chans[chan], val);
|
|
return (rc < 0) ? rc : 0;
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int qbg_write_iio_chan(struct qti_qbg *chip, enum qbg_ext_iio_channels chan,
|
|
int val)
|
|
{
|
|
if (is_chan_valid(chip, chan))
|
|
return iio_write_channel_raw(chip->ext_iio_chans[chan], val);
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static void qbg_notify_charger(struct qti_qbg *chip)
|
|
{
|
|
union power_supply_propval prop = {0, };
|
|
int rc;
|
|
|
|
if (!chip->batt_psy || !chip->profile_loaded)
|
|
return;
|
|
|
|
prop.intval = chip->float_volt_uv;
|
|
rc = power_supply_set_property(chip->batt_psy,
|
|
POWER_SUPPLY_PROP_VOLTAGE_MAX, &prop);
|
|
if (rc < 0) {
|
|
pr_err("Failed to set voltage_max property on batt_psy, rc=%d\n",
|
|
rc);
|
|
return;
|
|
}
|
|
|
|
prop.intval = chip->fastchg_curr_ma * 1000;
|
|
rc = power_supply_set_property(chip->batt_psy,
|
|
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, &prop);
|
|
if (rc < 0) {
|
|
pr_err("Failed to set constant_charge_current_max property on batt_psy, rc=%d\n",
|
|
rc);
|
|
return;
|
|
}
|
|
|
|
if (chip->default_iterm_ma != -EINVAL) {
|
|
prop.intval = chip->default_iterm_ma;
|
|
rc = power_supply_set_property(chip->batt_psy,
|
|
POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, &prop);
|
|
if (rc < 0) {
|
|
pr_err("Failed to set charge_current_max property on batt_psy, rc=%d\n",
|
|
rc);
|
|
return;
|
|
}
|
|
}
|
|
|
|
pr_debug("Notified charger on float voltage:%d uV and FCC:%d mA iterm:%d mA\n",
|
|
chip->float_volt_uv, chip->fastchg_curr_ma, chip->default_iterm_ma);
|
|
}
|
|
|
|
static bool is_batt_available(struct qti_qbg *chip)
|
|
{
|
|
int rc;
|
|
union power_supply_propval prop = {0, };
|
|
|
|
if (chip->batt_psy)
|
|
return true;
|
|
|
|
chip->batt_psy = power_supply_get_by_name("battery");
|
|
if (!chip->batt_psy)
|
|
return false;
|
|
|
|
/* batt_psy is initialized, set the fcc and fv */
|
|
qbg_notify_charger(chip);
|
|
|
|
/*
|
|
* Read termination current from charger,
|
|
* Use it to configure iterm after recharge upon USB removal.
|
|
*/
|
|
rc = power_supply_get_property(chip->batt_psy,
|
|
POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, &prop);
|
|
if (!rc)
|
|
chip->default_iterm_ma = prop.intval;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool is_usb_available(struct qti_qbg *chip)
|
|
{
|
|
if (chip->usb_psy)
|
|
return true;
|
|
|
|
chip->usb_psy = power_supply_get_by_name("usb");
|
|
if (!chip->usb_psy)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
#define DEFAULT_RECHARGE_SOC 95
|
|
static int qbg_charge_full_update(struct qti_qbg *chip)
|
|
{
|
|
union power_supply_propval prop = {0, };
|
|
int rc, recharge_soc, health, val;
|
|
|
|
/* Nothing to process */
|
|
if (!chip->charger_present || !chip->charge_done)
|
|
return 0;
|
|
|
|
rc = power_supply_get_property(chip->batt_psy,
|
|
POWER_SUPPLY_PROP_HEALTH, &prop);
|
|
if (rc < 0) {
|
|
pr_err("Failed to get battery health, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
health = prop.intval;
|
|
|
|
rc = qbg_read_iio_chan(chip, RECHARGE_SOC, &val);
|
|
if (rc < 0 || val < 0) {
|
|
pr_debug("Failed to get recharge-soc, rc=%d\n", rc);
|
|
recharge_soc = DEFAULT_RECHARGE_SOC;
|
|
} else {
|
|
recharge_soc = val;
|
|
}
|
|
chip->recharge_soc = recharge_soc;
|
|
|
|
qbg_dbg(chip, QBG_DEBUG_STATUS, "msoc=%d sys_soc:%d charge_done:%d charge_status=%d recharge_soc=%d in_recharge:%u\n",
|
|
chip->soc, chip->sys_soc, chip->charge_done,
|
|
chip->charge_status, chip->recharge_soc, chip->in_recharge);
|
|
|
|
if (chip->charge_status == POWER_SUPPLY_STATUS_CHARGING) {
|
|
/* Charger is charging in recharge */
|
|
return 0;
|
|
}
|
|
|
|
/* System SOC has not dropped below recharge SOC to trigger recharge */
|
|
if (chip->sys_soc > recharge_soc)
|
|
return 0;
|
|
|
|
if (!chip->in_recharge) {
|
|
if (chip->recharge_vflt_delta_mv && chip->recharge_vflt_delta_mv != -EINVAL) {
|
|
prop.intval = (chip->float_volt_uv) - (chip->recharge_vflt_delta_mv * 1000);
|
|
rc = power_supply_set_property(chip->batt_psy,
|
|
POWER_SUPPLY_PROP_VOLTAGE_MAX, &prop);
|
|
if (rc < 0) {
|
|
pr_err("Failed to set recharge voltage_max property on batt_psy, rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
if (chip->recharge_iterm_ma && chip->recharge_iterm_ma != -EINVAL) {
|
|
prop.intval = chip->recharge_iterm_ma;
|
|
prop.intval = (-1 * prop.intval);
|
|
rc = power_supply_set_property(chip->batt_psy,
|
|
POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, &prop);
|
|
if (rc < 0) {
|
|
pr_err("Failed to set recharge charge_term_current property on batt_psy, rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
}
|
|
chip->in_recharge = true;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static void status_change_work(struct work_struct *work)
|
|
{
|
|
struct qti_qbg *chip = container_of(work, struct qti_qbg,
|
|
status_change_work);
|
|
union power_supply_propval prop = {0, };
|
|
int rc, val, charger_present = 0;
|
|
|
|
if (!is_batt_available(chip))
|
|
return;
|
|
|
|
if (!is_usb_available(chip))
|
|
return;
|
|
|
|
rc = power_supply_get_property(chip->batt_psy,
|
|
POWER_SUPPLY_PROP_CHARGE_TYPE, &prop);
|
|
if (rc < 0)
|
|
pr_err("Failed to get charge-type, rc=%d\n", rc);
|
|
else
|
|
chip->charge_type = prop.intval;
|
|
|
|
rc = power_supply_get_property(chip->batt_psy,
|
|
POWER_SUPPLY_PROP_STATUS, &prop);
|
|
if (rc < 0)
|
|
pr_err("Failed to get charger status, rc=%d\n", rc);
|
|
else
|
|
chip->charge_status = prop.intval;
|
|
|
|
rc = qbg_read_iio_chan(chip, CHARGE_DONE, &val);
|
|
if (rc < 0)
|
|
pr_err("Failed to get charge done status, rc=%d\n", rc);
|
|
else
|
|
chip->charge_done = val;
|
|
|
|
rc = power_supply_get_property(chip->usb_psy,
|
|
POWER_SUPPLY_PROP_PRESENT, &prop);
|
|
if (rc < 0)
|
|
pr_err("Failed to get charger present, rc=%d\n", rc);
|
|
else
|
|
charger_present = prop.intval;
|
|
|
|
qbg_dbg(chip, QBG_DEBUG_STATUS, "charge_status=%d charge_done=%d charger_present:%d\n",
|
|
chip->charge_status, chip->charge_done, chip->charger_present);
|
|
|
|
/*
|
|
* Set FCC and iterm back to default value on charger removal
|
|
* as they could have been updated during recharge.
|
|
*/
|
|
if (chip->charger_present && !charger_present) {
|
|
qbg_notify_charger(chip);
|
|
chip->in_recharge = false;
|
|
}
|
|
chip->charger_present = charger_present;
|
|
|
|
rc = qbg_charge_full_update(chip);
|
|
if (rc < 0) {
|
|
pr_err("Failed in charge full update, rc=%d\n", rc);
|
|
return;
|
|
}
|
|
}
|
|
|
|
static int qbg_get_max_fifo_count(struct qti_qbg *chip)
|
|
{
|
|
int rc = 0;
|
|
u8 val[2];
|
|
|
|
rc = qbg_sdam_read(chip,
|
|
QBG_SDAM_BASE(chip, SDAM_CTRL0) + QBG_SDAM_MAX_FIFO_COUNT_OFFSET,
|
|
val, 2);
|
|
if (rc < 0) {
|
|
pr_err("Failed to read QBG SDAM_MAX_FIFO_COUNT_OFFSET, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
chip->max_fifo_count = (val[1] << 8) | val[0];
|
|
qbg_dbg(chip, QBG_DEBUG_SDAM, "max FIFO count=%d\n", chip->max_fifo_count);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int qbg_get_fifo_count(struct qti_qbg *chip, u32 *fifo_count)
|
|
{
|
|
int rc = 0;
|
|
u8 val[2];
|
|
|
|
rc = qbg_sdam_read(chip,
|
|
QBG_SDAM_BASE(chip, SDAM_CTRL0) + QBG_SDAM_FIFO_COUNT_OFFSET,
|
|
val, 2);
|
|
if (rc < 0) {
|
|
pr_err("Failed to read QBG SDAM, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
*fifo_count = (val[1] << 8) | val[0];
|
|
qbg_dbg(chip, QBG_DEBUG_SDAM, "FIFO count=%d\n", *fifo_count);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int qbg_hpm_fifo_depth_half(struct qti_qbg *chip, int current_fifo_count, bool enable)
|
|
{
|
|
int rc = 0;
|
|
u8 val = 0;
|
|
|
|
if (enable)
|
|
val = current_fifo_count / 2;
|
|
else
|
|
val = current_fifo_count * 2;
|
|
|
|
if (val > chip->max_fifo_count)
|
|
val = chip->max_fifo_count;
|
|
|
|
rc = qbg_sdam_write(chip, QBG_SDAM_BASE(chip, SDAM_CTRL0) +
|
|
QBG_SDAM_HPM_FIFO_COUNT_OFFSET, &val, 1);
|
|
if (rc < 0)
|
|
pr_err("Failed to write QBG SDAM, rc=%d\n", rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static void update_ocv_for_boost(struct qti_qbg *chip)
|
|
{
|
|
int ocv_for_boost = 0x00;
|
|
|
|
/*
|
|
* Every time when OCV gets updated, add 0.4V headroom compensation
|
|
* and round it to a value between 4.1V to 5.6V, then map it to a
|
|
* setting between [0x00 - 0x0F] and set it in SDAM to notify charger-boost.
|
|
*/
|
|
ocv_for_boost = chip->ocv_uv + BOOST_COMPENSATION_UV;
|
|
if (ocv_for_boost < OCV_BOOST_MIN_UV)
|
|
ocv_for_boost = OCV_BOOST_MIN_UV;
|
|
else if (ocv_for_boost > OCV_BOOST_MAX_UV)
|
|
ocv_for_boost = OCV_BOOST_MAX_UV;
|
|
|
|
ocv_for_boost -= OCV_BOOST_MIN_UV;
|
|
ocv_for_boost /= OCV_BOOST_STEP_UV;
|
|
|
|
qbg_dbg(chip, QBG_DEBUG_STATUS, "Boost OCV value written =%d\n", ocv_for_boost);
|
|
qbg_sdam_write(chip,
|
|
QBG_SDAM_BASE(chip, SDAM_CTRL0) + QBG_SDAM_BHARGER_OCV_HDRM_OFFSET,
|
|
(u8 *)&ocv_for_boost, 1);
|
|
}
|
|
|
|
static void process_udata_work(struct work_struct *work)
|
|
{
|
|
struct qti_qbg *chip = container_of(work, struct qti_qbg, udata_work);
|
|
int rc;
|
|
|
|
if (chip->udata.param[QBG_PARAM_BATT_SOC].valid)
|
|
chip->batt_soc = chip->udata.param[QBG_PARAM_BATT_SOC].data;
|
|
|
|
if (chip->udata.param[QBG_PARAM_SOC].valid ||
|
|
chip->udata.param[QBG_PARAM_SYS_SOC].valid) {
|
|
|
|
qbg_dbg(chip, QBG_DEBUG_SOC, "udata update: QBG_SOC=%d QBG_SYS_SOC=%d\n",
|
|
chip->udata.param[QBG_PARAM_SOC].valid ?
|
|
chip->udata.param[QBG_PARAM_SOC].data : -EINVAL,
|
|
chip->udata.param[QBG_PARAM_SYS_SOC].valid ?
|
|
chip->udata.param[QBG_PARAM_SYS_SOC].data : -EINVAL);
|
|
|
|
if (chip->udata.param[QBG_PARAM_SYS_SOC].valid) {
|
|
chip->sys_soc = chip->udata.param[QBG_PARAM_SYS_SOC].data;
|
|
|
|
rc = qbg_write_iio_chan(chip, SYS_SOC, chip->sys_soc);
|
|
if (rc < 0)
|
|
pr_err("Failed to write battery sys_soc, rc=%d\n", rc);
|
|
}
|
|
|
|
chip->soc = chip->udata.param[QBG_PARAM_SOC].data;
|
|
}
|
|
|
|
if (chip->udata.param[QBG_PARAM_ESR].valid) {
|
|
chip->esr = chip->udata.param[QBG_PARAM_ESR].data;
|
|
|
|
if (chip->qbg_psy)
|
|
power_supply_changed(chip->qbg_psy);
|
|
}
|
|
|
|
if (chip->udata.param[QBG_PARAM_OCV_UV].valid) {
|
|
chip->ocv_uv = chip->udata.param[QBG_PARAM_OCV_UV].data;
|
|
update_ocv_for_boost(chip);
|
|
}
|
|
|
|
if (chip->udata.param[QBG_PARAM_CHARGE_CYCLE_COUNT].valid)
|
|
chip->charge_cycle_count =
|
|
chip->udata.param[QBG_PARAM_CHARGE_CYCLE_COUNT].data;
|
|
|
|
if (chip->udata.param[QBG_PARAM_LEARNED_CAPACITY].valid)
|
|
chip->learned_capacity =
|
|
chip->udata.param[QBG_PARAM_LEARNED_CAPACITY].data;
|
|
|
|
if (chip->udata.param[QBG_PARAM_TTF_100MS].valid)
|
|
chip->ttf = chip->udata.param[QBG_PARAM_TTF_100MS].data;
|
|
|
|
if (chip->udata.param[QBG_PARAM_TTE_100MS].valid)
|
|
chip->tte = chip->udata.param[QBG_PARAM_TTE_100MS].data;
|
|
|
|
if (chip->udata.param[QBG_PARAM_SOH].valid)
|
|
chip->soh = chip->udata.param[QBG_PARAM_SOH].data;
|
|
|
|
if (chip->udata.param[QBG_PARAM_TBAT].valid)
|
|
chip->tbat = chip->udata.param[QBG_PARAM_TBAT].data;
|
|
|
|
if (chip->udata.param[QBG_PARAM_ESSENTIAL_PARAM_REVID].valid) {
|
|
chip->essential_param_revid =
|
|
chip->udata.param[QBG_PARAM_ESSENTIAL_PARAM_REVID].data;
|
|
|
|
rc = qbg_sdam_set_essential_param_revid(chip, chip->essential_param_revid);
|
|
if (rc < 0)
|
|
pr_err("Failed to set essential param revid in sdam, rc=%d\n",
|
|
rc);
|
|
}
|
|
|
|
rc = qbg_sdam_set_battery_id(chip, chip->batt_id_ohm / 1000);
|
|
if (rc < 0)
|
|
pr_err("Failed to set battid in sdam, rc=%d\n", rc);
|
|
|
|
__pm_relax(chip->qbg_ws);
|
|
|
|
qbg_dbg(chip, QBG_DEBUG_STATUS, "udata update: batt_soc=%d sys_soc=%d soc=%d qbg_esr=%d\n",
|
|
(chip->batt_soc != INT_MIN) ? chip->batt_soc : -EINVAL,
|
|
(chip->sys_soc != INT_MIN) ? chip->sys_soc : -EINVAL,
|
|
chip->soc, chip->esr);
|
|
}
|
|
|
|
static int qbg_decode_fifo_data(struct fifo_data fifo, unsigned int *vbat1,
|
|
unsigned int *vbat2, int *ibat,
|
|
unsigned int *tbat, unsigned int *ibat_t)
|
|
{
|
|
unsigned short int acc0_data = 0, acc1_data = 0, acc2_data = 0,
|
|
tbat_data = 0;
|
|
unsigned int ibat_temp;
|
|
|
|
if (!vbat1 || !vbat2 || !ibat || !tbat || !ibat_t)
|
|
return -EINVAL;
|
|
|
|
acc0_data = fifo.v1;
|
|
acc1_data = fifo.v2;
|
|
acc2_data = fifo.i;
|
|
tbat_data = fifo.tbat;
|
|
|
|
ibat_temp = acc2_data;
|
|
|
|
*vbat1 = acc0_data * VBATT_1S_LSB;
|
|
*ibat = ((int16_t)ibat_temp) * IBATT_10A_LSB * ICHG_FS_10A;
|
|
*tbat = tbat_data;
|
|
*ibat_t = fifo.ibat;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int get_rtc_time(struct qti_qbg *chip, time64_t *rtc_time)
|
|
{
|
|
struct rtc_time tm;
|
|
int rc;
|
|
|
|
if (!chip->rtc) {
|
|
pr_err("Invalid RTC handle\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
rc = rtc_read_time(chip->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_time = rtc_tm_to_time64(&tm);
|
|
|
|
return rc;
|
|
|
|
close_time:
|
|
rtc_class_close(chip->rtc);
|
|
chip->rtc = NULL;
|
|
|
|
return rc;
|
|
}
|
|
|
|
#define BAT_THERM_CAL_DENOM 2080
|
|
#define BAT_THERM_CAL_NUM 10000
|
|
#define BAT_THERM_SCALE 100
|
|
static int qbg_set_therm_trace_resistance(struct qti_qbg *chip, int vref_adc,
|
|
int tbat_adc, int ibat_10na, int ibat_esr_10na, int tbat_esr_adc)
|
|
{
|
|
int rc = 0;
|
|
int64_t r_pack_nom = 0;
|
|
int64_t r_pack = 0;
|
|
int64_t r_pack_denom = 0;
|
|
u8 reg_val = 0;
|
|
|
|
int tbat_decidegree = div64_s64(((int64_t)tbat_adc * TBAT_LSB * BAT_THERM_SCALE),
|
|
TBAT_DENOMINATOR);
|
|
int tbat_esr_decidegree = div64_s64(((int64_t)tbat_esr_adc * TBAT_LSB * BAT_THERM_SCALE),
|
|
TBAT_DENOMINATOR);
|
|
int vref_tbat_decidegree = div64_s64(((int64_t)vref_adc * TBAT_LSB * BAT_THERM_SCALE),
|
|
TBAT_DENOMINATOR);
|
|
|
|
r_pack_nom = tbat_decidegree * (vref_tbat_decidegree - tbat_esr_decidegree)
|
|
- tbat_esr_decidegree * (vref_tbat_decidegree - tbat_decidegree);
|
|
|
|
r_pack_denom = DIV_ROUND_CLOSEST(ibat_esr_10na, MILLI_TO_10NANO)
|
|
* (vref_tbat_decidegree - tbat_decidegree)
|
|
- DIV_ROUND_CLOSEST(ibat_10na, MILLI_TO_10NANO)
|
|
* (vref_tbat_decidegree - tbat_esr_decidegree);
|
|
|
|
if (r_pack_denom != 0) {
|
|
r_pack = div64_s64(r_pack_nom * BAT_THERM_CAL_NUM, r_pack_denom);
|
|
r_pack = div64_s64(r_pack, BAT_THERM_CAL_DENOM);
|
|
reg_val = (u8)r_pack;
|
|
|
|
qbg_dbg(chip, QBG_DEBUG_SDAM, "tbat=%d, tbat_esr=%d vref=%d 10uv, therm trace resistance is %#x\n",
|
|
tbat_decidegree, tbat_esr_decidegree, vref_tbat_decidegree, reg_val);
|
|
|
|
rc = regmap_write(chip->regmap, chip->adc_cmn_wb_base + BAT_THERM_IBAT_CTL_2,
|
|
reg_val);
|
|
if (rc < 0)
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int qbg_process_fifo(struct qti_qbg *chip, u32 fifo_count)
|
|
{
|
|
struct fifo_data *fifo;
|
|
int rc = 0, i = 0, ibat = 0, ibat_esr = 0;
|
|
unsigned char data_tag;
|
|
unsigned int vbat1 = 0, vbat2 = 0, tbat = 0, ibat_t = 0, esr = 0;
|
|
unsigned int vbat1_esr = 0, vbat2_esr = 0, tbat_esr = 0, ibat_t_esr = 0;
|
|
time64_t timestamp = 0;
|
|
|
|
if (!fifo_count) {
|
|
qbg_dbg(chip, QBG_DEBUG_SDAM, "No FIFO data\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
fifo = kcalloc(fifo_count, sizeof(*fifo), GFP_KERNEL);
|
|
if (!fifo)
|
|
return -ENOMEM;
|
|
|
|
rc = get_rtc_time(chip, ×tamp);
|
|
if (rc < 0) {
|
|
pr_err("Failed to read rtc time, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
chip->kdata.param[QBG_PARAM_FIFO_TIMESTAMP].valid = true;
|
|
chip->kdata.param[QBG_PARAM_FIFO_TIMESTAMP].data = timestamp;
|
|
|
|
/* Read raw FIFO data from SDAM */
|
|
rc = qbg_sdam_get_fifo_data(chip, fifo, fifo_count);
|
|
if (rc < 0) {
|
|
pr_err("Failed to get QBG FIFO data, rc=%d\n", rc);
|
|
goto ret;
|
|
}
|
|
|
|
data_tag = fifo[0].data_tag;
|
|
if (fifo_count > QBG_SDAM_ESR_PULSE_FIFO_INDEX)
|
|
data_tag = fifo[QBG_SDAM_ESR_PULSE_FIFO_INDEX].data_tag;
|
|
|
|
data_tag = (data_tag & 0xC0) >> 6;
|
|
|
|
if (data_tag == QBG_FAST_CHAR) {
|
|
if (fifo_count <= QBG_SDAM_ESR_PULSE_FIFO_INDEX) {
|
|
pr_err("Not enough FIFO samples in fast char mode\n");
|
|
goto qbg_cal;
|
|
}
|
|
|
|
rc = qbg_decode_fifo_data(fifo[QBG_SDAM_ESR_PULSE_FIFO_INDEX - 1],
|
|
&vbat1, &vbat2, &ibat, &tbat,
|
|
&ibat_t);
|
|
if (rc < 0) {
|
|
pr_err("Couldn't decode FIFO before ESR pulse in fast char mode, rc=%d\n",
|
|
rc);
|
|
goto qbg_cal;
|
|
}
|
|
|
|
rc = qbg_decode_fifo_data(fifo[QBG_SDAM_ESR_PULSE_FIFO_INDEX],
|
|
&vbat1_esr, &vbat2_esr,
|
|
&ibat_esr, &tbat_esr,
|
|
&ibat_t_esr);
|
|
if (rc < 0) {
|
|
pr_err("Couldn't decode FIFO before ESR pulse in fast char mode, rc=%d\n",
|
|
rc);
|
|
goto qbg_cal;
|
|
}
|
|
|
|
if ((ibat < ibat_esr)
|
|
&& (ibat < (ESR_PULSE_10NA(ESR_FCC_150MA) + ESR_IBAT_QUALIFY_OFFSET_10NA))
|
|
&& (vbat1 > vbat1_esr) && (tbat > tbat_esr)) {
|
|
esr = (vbat1 - vbat1_esr) / ((ibat_esr - ibat) / 10000);
|
|
esr *= 10000;
|
|
chip->esr = esr;
|
|
qbg_dbg(chip, QBG_DEBUG_SDAM, "ESR:%u, ibat=%d ibat_esr=%d vbat1:%u vbat1_esr:%u\n",
|
|
esr, ibat, ibat_esr, vbat1, vbat1_esr);
|
|
|
|
rc = qbg_set_therm_trace_resistance(chip,
|
|
fifo[QBG_SDAM_ESR_PULSE_FIFO_INDEX - 1].vref,
|
|
tbat, ibat, ibat_esr, tbat_esr);
|
|
if (rc < 0) {
|
|
pr_err("Failed to set therm trace resistance, rc=%d\n", rc);
|
|
goto qbg_cal;
|
|
}
|
|
} else {
|
|
pr_err("Couldn't calculate ESR, ibat=%d ibat_esr=%d vbat1:%u vbat1_esr:%u\n",
|
|
ibat, ibat_esr, vbat1, vbat1_esr);
|
|
}
|
|
}
|
|
|
|
qbg_cal:
|
|
for (i = 0; i < fifo_count; i++) {
|
|
rc = qbg_decode_fifo_data(fifo[i], &vbat1, &vbat2, &ibat, &tbat,
|
|
&ibat_t);
|
|
if (rc < 0) {
|
|
pr_err("Failed to decode fifo %d, rc=%d\n", i, rc);
|
|
goto ret;
|
|
}
|
|
|
|
qbg_dbg(chip, QBG_DEBUG_SDAM, "vbat1:%u ibat:%d tbat:%u ibat_t:%u\n",
|
|
vbat1, ibat, tbat, ibat_t);
|
|
|
|
chip->kdata.fifo[i].v1 = vbat1;
|
|
chip->kdata.fifo[i].v2 = vbat2;
|
|
chip->kdata.fifo[i].i = ibat;
|
|
chip->kdata.fifo[i].tbat = tbat;
|
|
chip->kdata.fifo[i].ibat = ibat_t;
|
|
chip->kdata.fifo[i].data_tag = fifo[i].data_tag;
|
|
chip->kdata.fifo[i].qg_sts = fifo[i].qg_sts;
|
|
}
|
|
|
|
if (!chip->enable_fifo_depth_half && ((-1 * ibat) > chip->rated_capacity)) {
|
|
chip->enable_fifo_depth_half = true;
|
|
rc = qbg_hpm_fifo_depth_half(chip, fifo_count, chip->enable_fifo_depth_half);
|
|
qbg_dbg(chip, QBG_DEBUG_SDAM, "HPM FIFO count reduced by half ibat = %d rated_capacity=%d\n",
|
|
ibat, chip->rated_capacity);
|
|
} else if (chip->enable_fifo_depth_half && ((-1 * ibat) < chip->rated_capacity)) {
|
|
chip->enable_fifo_depth_half = false;
|
|
rc = qbg_hpm_fifo_depth_half(chip, fifo_count, chip->enable_fifo_depth_half);
|
|
qbg_dbg(chip, QBG_DEBUG_SDAM, "HPM FIFO count restored ibat = %d rated_capacity=%d\n",
|
|
ibat, chip->rated_capacity);
|
|
}
|
|
|
|
if (rc < 0)
|
|
pr_err("Failed to update fifo depth, rc=%d\n", rc);
|
|
|
|
ret:
|
|
kfree(fifo);
|
|
return rc;
|
|
}
|
|
|
|
static int qbg_clear_fifo_data(struct qti_qbg *chip)
|
|
{
|
|
int rc = 0, i;
|
|
u8 val[2] = {0, 0};
|
|
|
|
rc = qbg_sdam_write(chip,
|
|
QBG_SDAM_BASE(chip, SDAM_CTRL0) + QBG_SDAM_FIFO_COUNT_OFFSET,
|
|
val, 2);
|
|
if (rc < 0) {
|
|
pr_err("Failed to clear QBG FIFO Count, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
for (i = 0; i < 10; i++) {
|
|
val[0] = 0;
|
|
rc = qbg_sdam_write(chip,
|
|
QBG_SDAM_BASE(chip, SDAM_CTRL0) + QBG_SDAM_INT_TEST_VAL,
|
|
val, 1);
|
|
if (rc < 0) {
|
|
pr_err("Failed to SDAM0 test val to 0, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
/* Handshake with PBS to access FIFO data */
|
|
rc = qbg_sdam_read(chip,
|
|
QBG_SDAM_BASE(chip, SDAM_CTRL0) + QBG_SDAM_INT_TEST_VAL,
|
|
val, 1);
|
|
if (rc < 0) {
|
|
pr_err("Failed to read QBG SDAM, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
if (val[0] == 0)
|
|
break;
|
|
}
|
|
if (i == 10)
|
|
return -EINVAL;
|
|
|
|
val[0] = QBG_SDAM_START_OFFSET;
|
|
val[1] = 0x0;
|
|
rc = qbg_sdam_write(chip,
|
|
QBG_SDAM_BASE(chip, SDAM_DATA0) + QBG_SDAM_DATA_PUSH_COUNTER_OFFSET,
|
|
val, 2);
|
|
if (rc < 0) {
|
|
pr_err("Failed to configure QBG data push counter, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
val[0] = 0x0;
|
|
rc = qbg_sdam_write(chip,
|
|
QBG_SDAM_BASE(chip, SDAM_CTRL0) + QBG_SDAM_PBS_STATUS_OFFSET,
|
|
val, 1);
|
|
if (rc < 0) {
|
|
pr_err("Failed to set QBG PBS status, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int qbg_enable_data_full_int(struct qti_qbg *chip, bool enable)
|
|
{
|
|
/* Writing 0x80 while FIFO is full will raise the interrupt */
|
|
u8 val = (enable ? 0x80 : 0x00);
|
|
|
|
return qbg_sdam_write(chip,
|
|
QBG_SDAM_BASE(chip, SDAM_CTRL0) + QBG_SDAM_INT_TEST1,
|
|
&val, 1);
|
|
}
|
|
|
|
static int qbg_init_sdam(struct qti_qbg *chip)
|
|
{
|
|
int rc = 0;
|
|
u8 val[2] = {0};
|
|
|
|
val[0] = 0x80;
|
|
rc = qbg_sdam_write(chip,
|
|
QBG_SDAM_BASE(chip, SDAM_CTRL0) + QBG_SDAM_INT_TEST1, val, 1);
|
|
if (rc < 0) {
|
|
pr_err("Failed to write QBG SDAM, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
val[0] = 0;
|
|
rc = qbg_sdam_read(chip,
|
|
QBG_SDAM_BASE(chip, SDAM_CTRL0) + QBG_SDAM_INT_TEST_VAL,
|
|
val, 1);
|
|
if (rc < 0) {
|
|
pr_err("Faiiled to read QBG SDAM, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
if (val[0] == QBG_SDAM_TEST_VAL_1_SET) {
|
|
rc = qbg_clear_fifo_data(chip);
|
|
if (rc < 0) {
|
|
pr_err("Failed to clear QBG FIFO data, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int qbg_init_esr(struct qti_qbg *chip)
|
|
{
|
|
int rc = 0;
|
|
u8 val = ESR_FCC_150MA;
|
|
|
|
rc = qbg_sdam_write(chip,
|
|
QBG_SDAM_BASE(chip, SDAM_CTRL0) + QBG_ESR_PULSE_FCC_REDUCE_OFFSET, &val, 1);
|
|
if (rc < 0)
|
|
pr_err("Failed to write QBG ESR pulse FCC reduction offset, rc=%d\n", rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
#define QBG_VBAT_EMPTY_STEP_THRES_UV 49920
|
|
static void qbg_init_vbatt_empty_threshold(struct qti_qbg *chip)
|
|
{
|
|
int rc = 0;
|
|
u8 val = 0;
|
|
u32 vbatt_in_uv = 0;
|
|
|
|
/* Check for Vbatt empty threshold limits -- Should be b/w 0 to 6V */
|
|
if (chip->vbatt_empty_threshold_mv == 0 || chip->vbatt_empty_threshold_mv > 6000) {
|
|
pr_info("Invalid Vbatt_empty threshold, value: %d\n",
|
|
chip->vbatt_empty_threshold_mv);
|
|
return;
|
|
}
|
|
|
|
vbatt_in_uv = chip->vbatt_empty_threshold_mv * 1000;
|
|
|
|
val = (u8) (vbatt_in_uv / QBG_VBAT_EMPTY_STEP_THRES_UV);
|
|
|
|
rc = qbg_write(chip, QBG_MAIN_VBAT_EMPTY_THRESH,
|
|
&val, 1);
|
|
if (rc < 0)
|
|
pr_err("Failed to write QBG VBATT EMPTY THRESHOLD offset, rc=%d\n", rc);
|
|
|
|
}
|
|
|
|
static int qbg_force_fast_char(struct qti_qbg *chip, bool force)
|
|
{
|
|
int rc = 0;
|
|
/* The default ground used for BAT_THERM measurement is GND_XOADC*/
|
|
u8 batt_therm_gnd_sel = 0x80;
|
|
u8 val = force ? (1 << FORCE_FAST_CHAR_SHIFT) : 0;
|
|
|
|
rc = qbg_write(chip, QBG_MAIN_QBG_STATE_FORCE_CMD, &val, 1);
|
|
if (rc < 0) {
|
|
pr_err("Failed to %s QBG fast char mode, rc=%d\n",
|
|
force ? "enable" : "disable", rc);
|
|
return rc;
|
|
}
|
|
|
|
/* Set the ground of BAT_THERM measurement to PACK_SNS_M */
|
|
if (force)
|
|
batt_therm_gnd_sel = 0x40;
|
|
|
|
rc = regmap_write(chip->regmap, chip->adc_cmn_base + VADC_ANA_PARAM_4, batt_therm_gnd_sel);
|
|
if (rc < 0)
|
|
pr_err("Failed to set batt therm gnd sel rc=%d\n", rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int qbg_handle_fast_char(struct qti_qbg *chip)
|
|
{
|
|
int rc = 0;
|
|
ktime_t now;
|
|
u8 *data;
|
|
ssize_t len = 0;
|
|
|
|
if (chip->skip_esr_state) {
|
|
data = nvmem_cell_read(chip->skip_esr_state, &len);
|
|
if (IS_ERR(data)) {
|
|
pr_err("Failed to read skip_esr_state from SDAM\n");
|
|
return PTR_ERR(data);
|
|
}
|
|
|
|
if (*data) {
|
|
qbg_dbg(chip, QBG_DEBUG_STATUS, "skip_esr=1 in_fast_char=%d\n",
|
|
chip->in_fast_char);
|
|
|
|
if (chip->in_fast_char) {
|
|
rc = qbg_force_fast_char(chip, false);
|
|
if (rc < 0) {
|
|
pr_err("Failed to get out of fast char mode, rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
chip->in_fast_char = false;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (chip->in_fast_char) {
|
|
qbg_dbg(chip, QBG_DEBUG_STATUS, "QBG ESR pulse disabled\n");
|
|
rc = qbg_force_fast_char(chip, false);
|
|
if (rc < 0) {
|
|
pr_err("Failed to get out of fast char mode, rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
chip->in_fast_char = false;
|
|
} else if (chip->charge_type == POWER_SUPPLY_CHARGE_TYPE_ADAPTIVE) {
|
|
now = ktime_get();
|
|
if ((ktime_ms_delta(now, chip->last_fast_char_time) >
|
|
QBG_FAST_CHAR_DELTA_MS) && !chip->in_fast_char) {
|
|
qbg_dbg(chip, QBG_DEBUG_STATUS, "QBG ESR pulse enabled\n");
|
|
rc = qbg_force_fast_char(chip, true);
|
|
if (rc < 0) {
|
|
pr_err("Failed to put QBG to fast char mode, rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
chip->in_fast_char = true;
|
|
chip->last_fast_char_time = now;
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static irqreturn_t qbg_data_full_irq_handler(int irq, void *_chip)
|
|
{
|
|
struct qti_qbg *chip = _chip;
|
|
int rc;
|
|
u32 fifo_count = 0;
|
|
|
|
qbg_dbg(chip, QBG_DEBUG_IRQ, "DATA FULL IRQ triggered\n");
|
|
|
|
/* Disable fast char for PM5100 V1 */
|
|
if (chip->rev4 != REVISION_V1) {
|
|
rc = qbg_handle_fast_char(chip);
|
|
if (rc < 0) {
|
|
pr_err("Failed to handle QBG fast char, rc=%d\n", rc);
|
|
return IRQ_HANDLED;
|
|
}
|
|
}
|
|
|
|
mutex_lock(&chip->fifo_lock);
|
|
memset(&chip->kdata.fifo, 0, sizeof(struct k_fifo_data));
|
|
|
|
rc = qbg_get_fifo_count(chip, &fifo_count);
|
|
if (rc < 0) {
|
|
pr_err("Failed to get QBG FIFO length, rc=%d\n", rc);
|
|
goto done;
|
|
}
|
|
|
|
rc = qbg_process_fifo(chip, fifo_count);
|
|
if (rc < 0) {
|
|
pr_err("Failed to process QBG FIFO, rc=%d\n", rc);
|
|
goto done;
|
|
}
|
|
chip->kdata.fifo_count = fifo_count;
|
|
|
|
rc = qbg_init_sdam(chip);
|
|
if (rc < 0) {
|
|
pr_err("Failed to initialize QBG sdam, rc=%d\n", rc);
|
|
goto done;
|
|
}
|
|
|
|
chip->data_ready = true;
|
|
/* Wakeup the read thread */
|
|
wake_up_interruptible(&chip->qbg_wait_q);
|
|
done:
|
|
mutex_unlock(&chip->fifo_lock);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t qbg_vbatt_empty_irq_handler(int irq, void *_chip)
|
|
{
|
|
struct qti_qbg *chip = _chip;
|
|
|
|
qbg_dbg(chip, QBG_DEBUG_IRQ, "Vbatt empty IRQ Triggered\n");
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
|
|
static int qbg_notifier_cb(struct notifier_block *nb,
|
|
unsigned long event, void *data)
|
|
{
|
|
struct power_supply *psy = data;
|
|
struct qti_qbg *chip = container_of(nb, struct qti_qbg, nb);
|
|
|
|
if (event != PSY_EVENT_PROP_CHANGED)
|
|
return NOTIFY_OK;
|
|
|
|
if (work_pending(&chip->status_change_work))
|
|
return NOTIFY_OK;
|
|
|
|
if ((strcmp(psy->desc->name, "battery") == 0) ||
|
|
(strcmp(psy->desc->name, "usb") == 0) ||
|
|
(strcmp(psy->desc->name, "dc") == 0)) {
|
|
schedule_work(&chip->status_change_work);
|
|
}
|
|
|
|
return NOTIFY_OK;
|
|
}
|
|
|
|
static int qbg_read_range_data_from_node(struct device_node *node,
|
|
const char *prop_str, struct ranges *ranges,
|
|
int max_threshold, u32 max_value)
|
|
{
|
|
int rc = 0, i, length, per_tuple_length, tuples;
|
|
|
|
if (!node || !prop_str || !ranges) {
|
|
pr_err("Invalid parameters passed\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
rc = of_property_count_elems_of_size(node, prop_str, sizeof(u32));
|
|
if (rc < 0) {
|
|
pr_err("Count %s failed, rc=%d\n", prop_str, rc);
|
|
return rc;
|
|
}
|
|
|
|
length = rc;
|
|
per_tuple_length = sizeof(struct range_data) / sizeof(u32);
|
|
if (length % per_tuple_length) {
|
|
pr_err("%s length (%d) should be multiple of %d\n",
|
|
prop_str, length, per_tuple_length);
|
|
return -EINVAL;
|
|
}
|
|
|
|
tuples = length / per_tuple_length;
|
|
if (tuples > QBG_MAX_STEP_CHG_ENTRIES) {
|
|
pr_err("too many entries(%d), only %d allowed\n",
|
|
tuples, QBG_MAX_STEP_CHG_ENTRIES);
|
|
return -EINVAL;
|
|
}
|
|
|
|
rc = of_property_read_u32_array(node, prop_str, (u32 *)ranges->data, length);
|
|
if (rc < 0) {
|
|
pr_err("Read %s failed, rc=%d\n", prop_str, rc);
|
|
return rc;
|
|
}
|
|
|
|
for (i = 0; i < tuples; i++) {
|
|
if (ranges->data[i].low_threshold >
|
|
ranges->data[i].high_threshold) {
|
|
pr_err("%s thresholds should be in ascendant ranges data\n",
|
|
prop_str);
|
|
rc = -EINVAL;
|
|
goto clean;
|
|
}
|
|
|
|
if (i != 0) {
|
|
if (ranges->data[i - 1].high_threshold >
|
|
ranges->data[i].low_threshold) {
|
|
pr_err("%s thresholds should be in ascendant ranges data\n",
|
|
prop_str);
|
|
rc = -EINVAL;
|
|
goto clean;
|
|
}
|
|
}
|
|
|
|
if (ranges->data[i].low_threshold > max_threshold)
|
|
ranges->data[i].low_threshold = max_threshold;
|
|
if (ranges->data[i].high_threshold > max_threshold)
|
|
ranges->data[i].high_threshold = max_threshold;
|
|
if (ranges->data[i].value > max_value)
|
|
ranges->data[i].value = max_value;
|
|
}
|
|
|
|
ranges->range_count = tuples;
|
|
ranges->valid = true;
|
|
return rc;
|
|
clean:
|
|
memset(ranges, 0, tuples * sizeof(struct range_data));
|
|
return rc;
|
|
}
|
|
|
|
#define QBG_DEFAULT_JEITA_FULL_FV_10NV 420000000
|
|
#define QBG_DEFAULT_BATTERY_BETA 4250
|
|
#define QBG_DEFAULT_BATTERY_THERM_KOHM 100
|
|
#define QBG_DEFAULT_JEITA_WARM_ADC_DATA 0x25e3 /* WARM = 40 DegC */
|
|
#define QBG_DEFAULT_JEITA_COOL_ADC_DATA 0x5314 /* COOL = 5 DegC */
|
|
static int parse_step_chg_jeita_params(struct qti_qbg *chip, struct device_node *profile_node)
|
|
{
|
|
struct qbg_step_chg_jeita_params *step_chg_jeita;
|
|
bool soc_based_step_chg = false;
|
|
bool ocv_based_step_chg = false;
|
|
int rc;
|
|
int i = 0;
|
|
u32 temp[2];
|
|
bool jeita_thresh_valid = true;
|
|
|
|
step_chg_jeita = devm_kzalloc(chip->dev, sizeof(*step_chg_jeita),
|
|
GFP_KERNEL);
|
|
if (!step_chg_jeita)
|
|
return -ENOMEM;
|
|
|
|
/* choose the lower of the two soft threaholds as the jeita-full-fv */
|
|
step_chg_jeita->jeita_full_fv_10nv = QBG_DEFAULT_JEITA_FULL_FV_10NV;
|
|
rc = of_property_read_u32_array(profile_node, "qcom,jeita-soft-fv-uv",
|
|
temp, 2);
|
|
if (!rc)
|
|
step_chg_jeita->jeita_full_fv_10nv = min(temp[0], temp[1]) * MICRO_TO_10NANO;
|
|
else
|
|
pr_debug("Failed to read jeita full fv from battery profile, rc=%d\n", rc);
|
|
|
|
step_chg_jeita->jeita_full_iterm_10na = chip->iterm_ma * MILLI_TO_10NANO;
|
|
|
|
soc_based_step_chg = of_property_read_bool(profile_node, "qcom,soc-based-step-chg");
|
|
ocv_based_step_chg = of_property_read_bool(profile_node, "qcom,ocv-based-step-chg");
|
|
if (soc_based_step_chg)
|
|
step_chg_jeita->ttf_calc_mode = TTF_MODE_SOC_STEP_CHG;
|
|
else if (ocv_based_step_chg)
|
|
step_chg_jeita->ttf_calc_mode = TTF_MODE_OCV_STEP_CHG;
|
|
|
|
step_chg_jeita->battery_beta = QBG_DEFAULT_BATTERY_BETA;
|
|
rc = of_property_read_u32(profile_node, "qcom,battery-beta",
|
|
&step_chg_jeita->battery_beta);
|
|
if (rc < 0)
|
|
pr_debug("Failed to read battery beta form battery profile, rc=%d\n", rc);
|
|
|
|
step_chg_jeita->battery_therm_kohm = QBG_DEFAULT_BATTERY_THERM_KOHM;
|
|
rc = of_property_read_u32(profile_node, "qcom,battery-therm-kohm",
|
|
&step_chg_jeita->battery_therm_kohm);
|
|
if (rc < 0)
|
|
pr_debug("Failed to read battery therm from battery profile, rc=%d\n", rc);
|
|
|
|
step_chg_jeita->jeita_cool_adc_value = QBG_DEFAULT_JEITA_COOL_ADC_DATA;
|
|
step_chg_jeita->jeita_warm_adc_value = QBG_DEFAULT_JEITA_WARM_ADC_DATA;
|
|
rc = of_property_read_u32_array(profile_node, "qcom,jeita-soft-thresholds",
|
|
temp, 2);
|
|
if (!rc) {
|
|
step_chg_jeita->jeita_cool_adc_value = temp[0];
|
|
step_chg_jeita->jeita_warm_adc_value = temp[1];
|
|
} else {
|
|
jeita_thresh_valid = false;
|
|
pr_debug("Failed to read jeita cool and warm value from battery profile, rc=%d\n",
|
|
rc);
|
|
}
|
|
|
|
rc = qbg_read_range_data_from_node(profile_node,
|
|
"qcom,jeita-fcc-ranges",
|
|
&step_chg_jeita->jeita_fcc_cfg,
|
|
BATT_HOT_DECIDEGREE_MAX,
|
|
chip->fastchg_curr_ma * MILLI_TO_MICRO);
|
|
if (rc < 0)
|
|
pr_debug("Failed to read qcom,jeita-fcc-ranges from battery profile, rc=%d\n",
|
|
rc);
|
|
|
|
rc = qbg_read_range_data_from_node(profile_node,
|
|
"qcom,jeita-fv-ranges",
|
|
&step_chg_jeita->jeita_fv_cfg,
|
|
BATT_HOT_DECIDEGREE_MAX, chip->float_volt_uv);
|
|
if (rc < 0)
|
|
pr_debug("Failed to read qcom,jeita-fv-ranges from battery profile, rc=%d\n",
|
|
rc);
|
|
|
|
if ((step_chg_jeita->jeita_fcc_cfg.valid != step_chg_jeita->jeita_fv_cfg.valid)
|
|
|| (jeita_thresh_valid != step_chg_jeita->jeita_fcc_cfg.valid)) {
|
|
pr_err("battery JEITA configuration is not valid\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
rc = qbg_read_range_data_from_node(profile_node,
|
|
"qcom,step-chg-ranges",
|
|
&step_chg_jeita->step_fcc_cfg,
|
|
soc_based_step_chg ? 100 : chip->float_volt_uv,
|
|
chip->fastchg_curr_ma * MILLI_TO_MICRO);
|
|
if (rc < 0)
|
|
pr_debug("Failed to read qcom,step-chg-ranges from battery profile, rc=%d\n",
|
|
rc);
|
|
|
|
qbg_dbg(chip, QBG_DEBUG_PROFILE, "jeita_full_fv = %dnv, jeita_full_iterm = %dna, jeita_warm_adc = 0x%x, jeita_cool_adc = 0x%x, battery_beta = %d, battery_therm = %dkohm\n",
|
|
step_chg_jeita->jeita_full_fv_10nv,
|
|
step_chg_jeita->jeita_full_iterm_10na,
|
|
step_chg_jeita->jeita_warm_adc_value,
|
|
step_chg_jeita->jeita_cool_adc_value,
|
|
step_chg_jeita->battery_beta,
|
|
step_chg_jeita->battery_therm_kohm);
|
|
|
|
pr_debug("step-chg-ranges:\n");
|
|
for (i = 0; i < step_chg_jeita->step_fcc_cfg.range_count; i++) {
|
|
pr_debug("%d %d %d\n",
|
|
step_chg_jeita->step_fcc_cfg.data[i].low_threshold,
|
|
step_chg_jeita->step_fcc_cfg.data[i].high_threshold,
|
|
step_chg_jeita->step_fcc_cfg.data[i].value);
|
|
}
|
|
|
|
pr_debug("jeita-fcc-ranges:\n");
|
|
for (i = 0; i < step_chg_jeita->jeita_fcc_cfg.range_count; i++) {
|
|
pr_debug("%d %d %d\n",
|
|
step_chg_jeita->jeita_fcc_cfg.data[i].low_threshold,
|
|
step_chg_jeita->jeita_fcc_cfg.data[i].high_threshold,
|
|
step_chg_jeita->jeita_fcc_cfg.data[i].value);
|
|
}
|
|
|
|
pr_debug("jeita-fv-ranges:\n");
|
|
for (i = 0; i < step_chg_jeita->jeita_fv_cfg.range_count; i++) {
|
|
pr_debug("%d %d %d\n",
|
|
step_chg_jeita->jeita_fv_cfg.data[i].low_threshold,
|
|
step_chg_jeita->jeita_fv_cfg.data[i].high_threshold,
|
|
step_chg_jeita->jeita_fv_cfg.data[i].value);
|
|
}
|
|
|
|
chip->step_chg_jeita_params = step_chg_jeita;
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define QBG_DEFAULT_RECHARGE_ITERM_MA 150
|
|
#define QBG_DEFAULT_RECHARGE_SOC_DELTA 5
|
|
#define QBG_DEFAULT_RECHARGE_VFLT_DELTA 100
|
|
static int qbg_load_battery_profile(struct qti_qbg *chip)
|
|
{
|
|
struct device_node *node = chip->dev->of_node;
|
|
struct device_node *profile_node;
|
|
int rc;
|
|
u32 temp[2];
|
|
|
|
chip->batt_node = of_find_node_by_name(node, "qcom,battery-data");
|
|
if (!chip->batt_node) {
|
|
pr_err("Batterydata not available\n");
|
|
return -ENXIO;
|
|
}
|
|
|
|
profile_node = of_batterydata_get_best_profile(chip->batt_node,
|
|
chip->batt_id_ohm / 1000, NULL);
|
|
if (IS_ERR_OR_NULL(profile_node)) {
|
|
rc = profile_node ? PTR_ERR(profile_node) : -EINVAL;
|
|
pr_err("Failed to detect valid QBG battery profile, rc=%d\n",
|
|
rc);
|
|
goto out;
|
|
}
|
|
|
|
chip->battery = devm_kzalloc(chip->dev, sizeof(*chip->battery), GFP_KERNEL);
|
|
if (!chip->battery) {
|
|
rc = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
rc = qbg_batterydata_init(profile_node, chip->battery);
|
|
if (rc < 0) {
|
|
pr_err("Failed to load batterydata, rc=%d\n", rc);
|
|
chip->profile_loaded = false;
|
|
goto out;
|
|
}
|
|
|
|
rc = of_property_read_string(profile_node, "qcom,battery-type",
|
|
&chip->batt_type_str);
|
|
if (rc < 0) {
|
|
pr_err("Failed to get battery name, rc=%d\n", rc);
|
|
goto out;
|
|
}
|
|
|
|
rc = of_property_read_u32(profile_node, "qcom,max-voltage-uv",
|
|
&chip->float_volt_uv);
|
|
if (rc < 0) {
|
|
pr_err("Failed to read battery float-voltage, rc:%d\n", rc);
|
|
chip->float_volt_uv = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
rc = of_property_read_u32(profile_node, "qcom,fastchg-current-ma",
|
|
&chip->fastchg_curr_ma);
|
|
if (rc < 0) {
|
|
pr_err("Failed to read battery fastcharge current, rc:%d\n", rc);
|
|
chip->fastchg_curr_ma = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
rc = of_property_read_u32(profile_node, "qcom,capacity", &chip->rated_capacity);
|
|
if (rc < 0) {
|
|
pr_err("Failed to read battery rated capacity, rc:%d\n", rc);
|
|
chip->rated_capacity = -EINVAL;
|
|
goto out;
|
|
}
|
|
chip->rated_capacity *= MILLI_TO_10NANO;
|
|
|
|
rc = of_property_read_u32_array(profile_node, "qcom,battery-capacity", temp, 2);
|
|
if (rc) {
|
|
pr_err("Failed to read battery nominal capacity, rc=%d\n", rc);
|
|
goto out;
|
|
}
|
|
chip->nominal_capacity = temp[0];
|
|
|
|
chip->recharge_iterm_ma = QBG_DEFAULT_RECHARGE_ITERM_MA;
|
|
rc = of_property_read_u32(profile_node, "qcom,recharge-iterm-ma", &temp[0]);
|
|
if (!rc)
|
|
chip->recharge_iterm_ma = temp[0];
|
|
|
|
chip->recharge_soc = 100 - QBG_DEFAULT_RECHARGE_SOC_DELTA;
|
|
rc = of_property_read_u32(profile_node, "qcom,recharge-soc-delta", &temp[0]);
|
|
if (!rc)
|
|
chip->recharge_soc = 100 - temp[0];
|
|
|
|
chip->recharge_vflt_delta_mv = QBG_DEFAULT_RECHARGE_VFLT_DELTA;
|
|
rc = of_property_read_u32(profile_node, "qcom,recharge-vflt-delta", &temp[0]);
|
|
if (!rc)
|
|
chip->recharge_vflt_delta_mv = temp[0];
|
|
|
|
qbg_dbg(chip, QBG_DEBUG_SDAM, "Recharge SOC=% Recharge-Vflt=%duV Recharge-Ibat=%dmA\n",
|
|
chip->recharge_soc, chip->recharge_vflt_delta_mv, chip->recharge_iterm_ma);
|
|
|
|
rc = parse_step_chg_jeita_params(chip, profile_node);
|
|
out:
|
|
of_node_put(chip->batt_node);
|
|
return rc;
|
|
}
|
|
|
|
|
|
#define DEBUG_BATT_ID_LOW 6000
|
|
#define DEBUG_BATT_ID_HIGH 8500
|
|
static bool is_debug_batt_id(struct qti_qbg *chip)
|
|
{
|
|
if (is_between(DEBUG_BATT_ID_LOW, DEBUG_BATT_ID_HIGH,
|
|
chip->batt_id_ohm))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
#define DEFAULT_BATT_TYPE "Unknown Battery"
|
|
#define MISSING_BATT_TYPE "Missing Battery"
|
|
#define DEBUG_BATT_TYPE "Debug Board"
|
|
static const char *qbg_get_battery_type(struct qti_qbg *chip)
|
|
{
|
|
if (chip->battery_missing)
|
|
return MISSING_BATT_TYPE;
|
|
|
|
if (is_debug_batt_id(chip))
|
|
return DEBUG_BATT_TYPE;
|
|
|
|
if (chip->batt_type_str && chip->profile_loaded)
|
|
return chip->batt_type_str;
|
|
|
|
return DEFAULT_BATT_TYPE;
|
|
}
|
|
|
|
#define DEBUG_BATT_SOC 67
|
|
#define BATT_MISSING_SOC 50
|
|
static int qbg_get_battery_capacity(struct qti_qbg *chip, int *soc)
|
|
{
|
|
if (is_debug_batt_id(chip)) {
|
|
*soc = DEBUG_BATT_SOC;
|
|
return 0;
|
|
}
|
|
|
|
if (chip->battery_missing || !chip->profile_loaded) {
|
|
*soc = BATT_MISSING_SOC;
|
|
return 0;
|
|
}
|
|
|
|
*soc = chip->soc;
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define TEN_NANO_TO_MICRO 100
|
|
static int qbg_get_battery_voltage(struct qti_qbg *chip, int *vbat_uv)
|
|
{
|
|
int rc;
|
|
unsigned short acc0_data;
|
|
u8 buf[2];
|
|
|
|
if (chip->battery_missing) {
|
|
*vbat_uv = 3700000;
|
|
return 0;
|
|
}
|
|
|
|
rc = qbg_read(chip, QBG_MAIN_LAST_BURST_AVG_ACC0_DATA0, buf, 2);
|
|
if (rc < 0) {
|
|
pr_err("Failed to read average vbatt, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
acc0_data = buf[0] | (buf[1] << 8);
|
|
*vbat_uv = acc0_data * VBATT_1S_LSB;
|
|
*vbat_uv = *vbat_uv / TEN_NANO_TO_MICRO;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int qbg_get_battery_current(struct qti_qbg *chip, int *ibat_ua)
|
|
{
|
|
int rc;
|
|
unsigned short acc2_data;
|
|
u8 buf[2];
|
|
|
|
if (chip->battery_missing) {
|
|
*ibat_ua = 0;
|
|
return 0;
|
|
}
|
|
|
|
rc = qbg_read(chip, QBG_MAIN_LAST_BURST_AVG_ACC2_DATA0, buf, 2);
|
|
if (rc < 0) {
|
|
pr_err("Failed to read average ibatt, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
acc2_data = buf[0] | (buf[1] << 8);
|
|
*ibat_ua = ((int16_t)acc2_data) * IBATT_10A_LSB * ICHG_FS_10A;
|
|
*ibat_ua = *ibat_ua / TEN_NANO_TO_MICRO;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int qbg_get_battery_temp(struct qti_qbg *chip, int *temp)
|
|
{
|
|
int rc;
|
|
|
|
if (!chip->batt_temp_chan)
|
|
return -EINVAL;
|
|
|
|
rc = iio_read_channel_processed(chip->batt_temp_chan, temp);
|
|
if (rc < 0) {
|
|
pr_err("Failed to read BATT_TEMP over ADC, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
chip->tbat = *temp;
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define UAH_FACTOR 10
|
|
static int qbg_get_charge_counter(struct qti_qbg *chip, int *charge_count)
|
|
{
|
|
|
|
if (is_debug_batt_id(chip) || chip->battery_missing || chip->battery_unknown) {
|
|
*charge_count = -EINVAL;
|
|
return 0;
|
|
}
|
|
|
|
*charge_count = chip->learned_capacity * chip->soc * UAH_FACTOR;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool is_battery_present(struct qti_qbg *chip)
|
|
{
|
|
bool present = false;
|
|
u8 reg;
|
|
int rc;
|
|
|
|
rc = qbg_read(chip, QBG_MAIN_STATUS2, ®, 1);
|
|
if (rc < 0)
|
|
pr_err("Failed to read battery presence, rc=%d\n", rc);
|
|
else
|
|
present = !(reg & QBG_BATTERY_MISSING_BIT);
|
|
|
|
return present;
|
|
}
|
|
|
|
static int get_batt_id_ohm(struct qti_qbg *chip, u32 *batt_id_ohm)
|
|
{
|
|
int rc, batt_id;
|
|
|
|
if (!chip->batt_id_chan)
|
|
return -EINVAL;
|
|
|
|
/* Read battery-id */
|
|
rc = iio_read_channel_processed(chip->batt_id_chan, &batt_id);
|
|
if (rc < 0) {
|
|
pr_err("Failed to read BATT_ID over ADC, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
*batt_id_ohm = (u32)batt_id;
|
|
|
|
qbg_dbg(chip, QBG_DEBUG_PROFILE, "batt_id_ohm=%u\n", *batt_id_ohm);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int qbg_setup_battery(struct qti_qbg *chip)
|
|
{
|
|
int rc = 0;
|
|
u8 ocv_boost_val = 0x02;
|
|
|
|
chip->profile_loaded = false;
|
|
chip->battery_unknown = false;
|
|
|
|
if (!is_battery_present(chip)) {
|
|
qbg_dbg(chip, QBG_DEBUG_PROFILE, "Battery Missing!\n");
|
|
chip->battery_missing = true;
|
|
} else {
|
|
/* battery present */
|
|
rc = get_batt_id_ohm(chip, &chip->batt_id_ohm);
|
|
if (rc < 0) {
|
|
pr_err("Failed to detect batt_id rc=%d\n", rc);
|
|
} else {
|
|
rc = qbg_load_battery_profile(chip);
|
|
if (rc < 0)
|
|
pr_err("Failed to load battery-profile, rc=%d\n", rc);
|
|
else
|
|
chip->profile_loaded = true;
|
|
}
|
|
|
|
/* Update OCV boost headroom compensation value to 4.3V in debug battery case */
|
|
if (is_debug_batt_id(chip)) {
|
|
rc = qbg_sdam_write(chip,
|
|
QBG_SDAM_BASE(chip, SDAM_CTRL0) +
|
|
QBG_SDAM_BHARGER_OCV_HDRM_OFFSET, &ocv_boost_val, 1);
|
|
}
|
|
|
|
if (!strcmp(qbg_get_battery_type(chip), DEFAULT_BATT_TYPE)) {
|
|
chip->batt_type_str = DEFAULT_BATT_TYPE;
|
|
chip->battery_unknown = true;
|
|
chip->soc = BATT_MISSING_SOC;
|
|
rc = 0;
|
|
}
|
|
}
|
|
|
|
qbg_dbg(chip, QBG_DEBUG_PROFILE, "battery_missing=%s batt_id=%d Ohm profile_loaded=%d battery_type=%s\n",
|
|
chip->battery_missing ? "true" : "false", chip->batt_id_ohm,
|
|
chip->profile_loaded, chip->batt_type_str);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int qbg_get_pon_reading(struct qti_qbg *chip, unsigned int *ocv,
|
|
unsigned int *ibat)
|
|
{
|
|
int rc;
|
|
unsigned short int ocv0_data, ibat_data;
|
|
u8 acc_data[6];
|
|
|
|
rc = qbg_read(chip, QBG_MAIN_PON_OCV_ACC0_DATA0, (u8 *)acc_data, 6);
|
|
if (rc < 0) {
|
|
pr_err("Failed to read PON accumulator data, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
ocv0_data = (acc_data[1] << 8) | acc_data[0];
|
|
ibat_data = (acc_data[5] << 8) | acc_data[4];
|
|
|
|
*ocv = ocv0_data * VBATT_1S_LSB;
|
|
if (*ocv < VBAT_0PCT_OCV)
|
|
*ocv = VBAT_0PCT_OCV;
|
|
|
|
*ibat = ibat_data * IBATT_10A_LSB * ICHG_FS_10A;
|
|
qbg_dbg(chip, QBG_DEBUG_PON, "ocv:%u ibat:%u\n", ocv, ibat);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int qbg_determine_pon_soc(struct qti_qbg *chip)
|
|
{
|
|
int rc, batt_temp;
|
|
union power_supply_propval prop = {0, };
|
|
unsigned long time_diff = 0;
|
|
unsigned int pon_ocv;
|
|
time64_t rtc_sec;
|
|
|
|
/* Get RTC time here */
|
|
rc = get_rtc_time(chip, &rtc_sec);
|
|
if (rc < 0) {
|
|
pr_err("Failed to read rtc time, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
if (!chip->profile_loaded) {
|
|
qbg_dbg(chip, QBG_DEBUG_SOC, "No Profile, skipping PON soc\n");
|
|
return 0;
|
|
}
|
|
|
|
rc = qbg_sdam_get_essential_params(chip, (u8 *)&chip->essential_params);
|
|
if (rc < 0) {
|
|
pr_err("Failed to read essential params, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = qbg_get_pon_reading(chip, &chip->pon_ocv, &chip->pon_ibat);
|
|
if (rc < 0) {
|
|
pr_err("Failed to get QBG PON reading, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
/* Read battery-temp */
|
|
rc = iio_read_channel_processed(chip->batt_temp_chan, &batt_temp);
|
|
if (rc < 0) {
|
|
pr_err("Failed to read BATT_TEMP over ADC, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
chip->pon_tbat = (unsigned int)batt_temp;
|
|
|
|
if (is_batt_available(chip)) {
|
|
rc = power_supply_get_property(chip->batt_psy,
|
|
POWER_SUPPLY_PROP_STATUS, &prop);
|
|
if (rc < 0)
|
|
pr_err("Failed to get charger status, rc=%d\n", rc);
|
|
}
|
|
|
|
pon_ocv = chip->pon_ocv / 10000;
|
|
rc = qbg_lookup_soc_ocv(chip->battery, &chip->pon_soc, pon_ocv,
|
|
prop.intval == POWER_SUPPLY_STATUS_CHARGING);
|
|
if (rc < 0) {
|
|
pr_err("Failed to lookup PON SOC, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
chip->pon_soc = DIV_ROUND_CLOSEST(chip->pon_soc, 100);
|
|
chip->soc = chip->pon_soc;
|
|
|
|
qbg_dbg(chip, QBG_DEBUG_PON, "Shutdown: SOC=%d OCV=%duV Ibat:%duA timediff=%lusecs, time_now=%lusecs\n",
|
|
chip->pon_soc, chip->pon_ocv, chip->pon_ibat, time_diff, rtc_sec);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int qbg_psy_get_prop(struct power_supply *psy,
|
|
enum power_supply_property psp,
|
|
union power_supply_propval *pval)
|
|
{
|
|
if (psp == POWER_SUPPLY_PROP_TYPE)
|
|
pval->intval = POWER_SUPPLY_TYPE_MAINS;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static enum power_supply_property qbg_psy_props[] = {
|
|
POWER_SUPPLY_PROP_TYPE,
|
|
};
|
|
|
|
static struct power_supply_desc qbg_psy_desc = {
|
|
.name = "bms",
|
|
.type = POWER_SUPPLY_TYPE_MAINS,
|
|
.properties = qbg_psy_props,
|
|
.num_properties = ARRAY_SIZE(qbg_psy_props),
|
|
.get_property = qbg_psy_get_prop,
|
|
};
|
|
|
|
static int qbg_init_psy(struct qti_qbg *chip)
|
|
{
|
|
struct power_supply_config qbg_psy_cfg = {};
|
|
int rc;
|
|
|
|
qbg_psy_cfg.drv_data = chip;
|
|
chip->qbg_psy = devm_power_supply_register(chip->dev, &qbg_psy_desc,
|
|
&qbg_psy_cfg);
|
|
if (IS_ERR_OR_NULL(chip->qbg_psy)) {
|
|
pr_err("Failed to register qbg_psy, rc = %d\n",
|
|
PTR_ERR(chip->qbg_psy));
|
|
return PTR_ERR(chip->qbg_psy);
|
|
}
|
|
|
|
chip->nb.notifier_call = qbg_notifier_cb;
|
|
rc = power_supply_reg_notifier(&chip->nb);
|
|
if (rc < 0)
|
|
pr_err("Failed to register psy notifier rc = %d\n", rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int qbg_iio_write_raw(struct iio_dev *indio_dev,
|
|
struct iio_chan_spec const *chan, int val1,
|
|
int val2, long mask)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int qbg_iio_read_raw(struct iio_dev *indio_dev,
|
|
struct iio_chan_spec const *chan, int *val1,
|
|
int *val2, long mask)
|
|
{
|
|
struct qti_qbg *chip = iio_priv(indio_dev);
|
|
int rc = 0;
|
|
|
|
*val1 = 0;
|
|
|
|
switch (chan->channel) {
|
|
case PSY_IIO_DEBUG_BATTERY:
|
|
*val1 = is_debug_batt_id(chip);
|
|
break;
|
|
case PSY_IIO_CAPACITY:
|
|
rc = qbg_get_battery_capacity(chip, val1);
|
|
break;
|
|
case PSY_IIO_REAL_CAPACITY:
|
|
rc = qbg_get_battery_capacity(chip, val1);
|
|
break;
|
|
case PSY_IIO_TEMP:
|
|
rc = qbg_get_battery_temp(chip, val1);
|
|
break;
|
|
case PSY_IIO_VOLTAGE_MAX:
|
|
*val1 = chip->float_volt_uv;
|
|
break;
|
|
case PSY_IIO_VOLTAGE_OCV:
|
|
*val1 = chip->ocv_uv;
|
|
break;
|
|
case PSY_IIO_VOLTAGE_AVG:
|
|
rc = qbg_get_battery_voltage(chip, val1);
|
|
break;
|
|
case PSY_IIO_VOLTAGE_NOW:
|
|
rc = qbg_get_battery_voltage(chip, val1);
|
|
break;
|
|
case PSY_IIO_CURRENT_NOW:
|
|
rc = qbg_get_battery_current(chip, val1);
|
|
break;
|
|
case PSY_IIO_RESISTANCE_ID:
|
|
*val1 = chip->batt_id_ohm;
|
|
break;
|
|
case PSY_IIO_TIME_TO_FULL_AVG:
|
|
*val1 = chip->ttf;
|
|
break;
|
|
case PSY_IIO_TIME_TO_FULL_NOW:
|
|
*val1 = chip->ttf;
|
|
break;
|
|
case PSY_IIO_TIME_TO_EMPTY_AVG:
|
|
*val1 = chip->tte;
|
|
break;
|
|
case PSY_IIO_ESR_ACTUAL:
|
|
*val1 = chip->esr;
|
|
break;
|
|
case PSY_IIO_SOH:
|
|
*val1 = chip->soh;
|
|
break;
|
|
case PSY_IIO_CHARGE_COUNTER:
|
|
rc = qbg_get_charge_counter(chip, val1);
|
|
break;
|
|
case PSY_IIO_CYCLE_COUNT:
|
|
*val1 = chip->charge_cycle_count;
|
|
break;
|
|
case PSY_IIO_CHARGE_FULL:
|
|
*val1 = chip->learned_capacity * MILLI_TO_MICRO;
|
|
break;
|
|
case PSY_IIO_CHARGE_FULL_DESIGN:
|
|
*val1 = chip->nominal_capacity * MILLI_TO_MICRO;
|
|
break;
|
|
default:
|
|
pr_err_ratelimited("Unsupported property %d\n", chan->channel);
|
|
rc = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
return (rc < 0) ? rc : IIO_VAL_INT;
|
|
}
|
|
|
|
static int qbg_iio_of_xlate(struct iio_dev *indio_dev,
|
|
const struct of_phandle_args *iiospec)
|
|
{
|
|
struct qti_qbg *chip = iio_priv(indio_dev);
|
|
struct iio_chan_spec *iio_chan = chip->iio_chan;
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(qbg_iio_psy_channels); i++, iio_chan++) {
|
|
if (iio_chan->channel == iiospec->args[0])
|
|
return i;
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static const struct iio_info qbg_iio_info = {
|
|
.read_raw = qbg_iio_read_raw,
|
|
.write_raw = qbg_iio_write_raw,
|
|
.of_xlate = qbg_iio_of_xlate,
|
|
};
|
|
|
|
static int qbg_init_iio(struct qti_qbg *chip,
|
|
struct platform_device *pdev)
|
|
{
|
|
struct iio_dev *indio_dev = chip->indio_dev;
|
|
struct iio_chan_spec *chan;
|
|
int qbg_num_iio_channels = ARRAY_SIZE(qbg_iio_psy_channels);
|
|
int rc, i;
|
|
|
|
chip->iio_chan = devm_kcalloc(chip->dev, qbg_num_iio_channels,
|
|
sizeof(*chip->iio_chan), GFP_KERNEL);
|
|
if (!chip->iio_chan)
|
|
return -ENOMEM;
|
|
|
|
indio_dev->info = &qbg_iio_info;
|
|
indio_dev->dev.parent = chip->dev;
|
|
indio_dev->dev.of_node = chip->dev->of_node;
|
|
indio_dev->name = pdev->name;
|
|
indio_dev->modes = INDIO_DIRECT_MODE;
|
|
indio_dev->channels = chip->iio_chan;
|
|
indio_dev->num_channels = qbg_num_iio_channels;
|
|
|
|
for (i = 0; i < qbg_num_iio_channels; i++) {
|
|
chan = &chip->iio_chan[i];
|
|
chan->address = i;
|
|
chan->channel = qbg_iio_psy_channels[i].channel_num;
|
|
chan->type = qbg_iio_psy_channels[i].type;
|
|
chan->datasheet_name =
|
|
qbg_iio_psy_channels[i].datasheet_name;
|
|
chan->extend_name =
|
|
qbg_iio_psy_channels[i].datasheet_name;
|
|
chan->info_mask_separate =
|
|
qbg_iio_psy_channels[i].info_mask;
|
|
}
|
|
|
|
rc = devm_iio_device_register(chip->dev, indio_dev);
|
|
if (rc < 0)
|
|
pr_err("Failed to register QBG IIO device, rc=%d\n", rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int qbg_get_accumulator_properties(struct qti_qbg *chip,
|
|
enum QBG_STATE state,
|
|
enum QBG_SAMPLE_NUM_TYPE *num_of_accum,
|
|
enum QBG_ACCUM_INTERVAL_TYPE *accum_interval)
|
|
{
|
|
int rc = 0;
|
|
unsigned int reg = QBG_MAIN_LPM_MEAS_CTL2;
|
|
unsigned char data = 0;
|
|
|
|
if (state >= QBG_STATE_MAX || !num_of_accum || !accum_interval)
|
|
return -EINVAL;
|
|
|
|
switch (state) {
|
|
case QBG_LPM:
|
|
reg = QBG_MAIN_LPM_MEAS_CTL2;
|
|
break;
|
|
case QBG_MPM:
|
|
reg = QBG_MAIN_MPM_MEAS_CTL2;
|
|
break;
|
|
case QBG_HPM:
|
|
reg = QBG_MAIN_HPM_MEAS_CTL2;
|
|
break;
|
|
case QBG_FAST_CHAR:
|
|
reg = QBG_MAIN_FAST_CHAR_MEAS_CTL2;
|
|
break;
|
|
default:
|
|
pr_err("Invalid QBG state requested for accumulator properties %u\n",
|
|
state);
|
|
return rc;
|
|
}
|
|
|
|
rc = qbg_read(chip, reg, &data, 1);
|
|
if (rc < 0) {
|
|
pr_err("Failed to MEAS_CTL2 %u, rc=%d\n", reg, rc);
|
|
return rc;
|
|
}
|
|
|
|
if (state == QBG_FAST_CHAR)
|
|
*num_of_accum = 0;
|
|
else
|
|
*num_of_accum = (data & QBG_NUM_OF_ACCUM_MASK) >> QBG_NUM_OF_ACCUM_SHIFT;
|
|
|
|
*accum_interval = data & QBG_ACCUM_INTERVAL_MASK;
|
|
|
|
qbg_dbg(chip, QBG_DEBUG_DEVICE, "state:%u num_of_accum:%u accum_interval:%u\n",
|
|
state, *num_of_accum, *accum_interval);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int qbg_get_sample_time_us(struct qti_qbg *chip)
|
|
{
|
|
int rc = 0, index;
|
|
unsigned int interval;
|
|
enum QBG_ACCUM_INTERVAL_TYPE accum_interval = ACCUM_INTERVAL_100MS;
|
|
enum QBG_SAMPLE_NUM_TYPE num_accum = SAMPLE_NUM_1;
|
|
|
|
for (index = 0; index < QBG_STATE_MAX; index++) {
|
|
if (index == QBG_PON_OCV)
|
|
continue;
|
|
|
|
rc = qbg_get_accumulator_properties(chip, index, &num_accum,
|
|
&accum_interval);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
if (index == QBG_FAST_CHAR)
|
|
interval = qbg_fast_char_avg_interval[accum_interval];
|
|
else if (index == QBG_LPM)
|
|
interval = qbg_lpm_accum_interval[accum_interval];
|
|
else
|
|
interval = qbg_accum_interval[accum_interval];
|
|
|
|
chip->sample_time_us[index] = interval;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static ssize_t qbg_context_show(struct class *c,
|
|
struct class_attribute *attr, char *buf)
|
|
{
|
|
struct qti_qbg *chip = container_of(c, struct qti_qbg, qbg_class);
|
|
int count;
|
|
|
|
if (!chip)
|
|
return -ENODEV;
|
|
|
|
if (!chip->context_count) {
|
|
qbg_dbg(chip, QBG_DEBUG_DEVICE, "Empty context buffer, nothing to show\n");
|
|
return 0;
|
|
}
|
|
|
|
mutex_lock(&chip->context_lock);
|
|
memcpy(buf, chip->context, chip->context_count);
|
|
count = chip->context_count;
|
|
chip->context_count = 0;
|
|
memset(chip->context, 0, QBG_CONTEXT_LOCAL_BUF_SIZE);
|
|
mutex_unlock(&chip->context_lock);
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t qbg_context_store(struct class *c,
|
|
struct class_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct qti_qbg *chip = container_of(c, struct qti_qbg, qbg_class);
|
|
|
|
if (!chip)
|
|
return -ENODEV;
|
|
|
|
if (count > QBG_CONTEXT_LOCAL_BUF_SIZE) {
|
|
qbg_dbg(chip, QBG_DEBUG_DEVICE, "Context dump is greater than %d bytes\n",
|
|
QBG_CONTEXT_LOCAL_BUF_SIZE);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!chip->context) {
|
|
chip->context = devm_kcalloc(chip->dev, 1,
|
|
QBG_CONTEXT_LOCAL_BUF_SIZE,
|
|
GFP_KERNEL);
|
|
if (!chip->context)
|
|
return -ENOMEM;
|
|
}
|
|
|
|
mutex_lock(&chip->context_lock);
|
|
memcpy(chip->context, buf, count);
|
|
chip->context_count = count;
|
|
mutex_unlock(&chip->context_lock);
|
|
|
|
return count;
|
|
}
|
|
static CLASS_ATTR_RW(qbg_context);
|
|
|
|
static struct attribute *qbg_class_attrs[] = {
|
|
&class_attr_qbg_context.attr,
|
|
NULL,
|
|
};
|
|
ATTRIBUTE_GROUPS(qbg_class);
|
|
|
|
static ssize_t qbg_device_read(struct file *file, char __user *buf, size_t count,
|
|
loff_t *ppos)
|
|
{
|
|
int rc;
|
|
struct qti_qbg *chip = file->private_data;
|
|
unsigned long data_size = sizeof(chip->kdata);
|
|
|
|
if (count < data_size) {
|
|
pr_err("Invalid datasize %lu, expected lesser then %zu\n",
|
|
data_size, count);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* non-blocking access, return */
|
|
if (!chip->data_ready && (file->f_flags & O_NONBLOCK))
|
|
return 0;
|
|
|
|
/* blocking access wait on data_ready */
|
|
if (!(file->f_flags & O_NONBLOCK)) {
|
|
rc = wait_event_interruptible(chip->qbg_wait_q,
|
|
chip->data_ready);
|
|
if (rc < 0) {
|
|
pr_debug("Failed wait! rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
mutex_lock(&chip->data_lock);
|
|
if (!chip->data_ready) {
|
|
pr_debug("No Data, false wakeup\n");
|
|
rc = -ENODATA;
|
|
goto fail_read;
|
|
}
|
|
|
|
if (copy_to_user(buf, &chip->kdata, data_size)) {
|
|
pr_err("Failed to copy_to_user\n");
|
|
rc = -EFAULT;
|
|
goto fail_read;
|
|
}
|
|
chip->data_ready = false;
|
|
|
|
qbg_dbg(chip, QBG_DEBUG_DEVICE, "QBG device read complete Size=%ld\n",
|
|
data_size);
|
|
|
|
/* clear data */
|
|
memset(&chip->kdata, 0, sizeof(chip->kdata));
|
|
|
|
rc = data_size;
|
|
|
|
fail_read:
|
|
mutex_unlock(&chip->data_lock);
|
|
return rc;
|
|
}
|
|
|
|
static ssize_t qbg_device_write(struct file *file, const char __user *buf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
int rc = -EINVAL;
|
|
struct qti_qbg *chip = file->private_data;
|
|
unsigned long data_size = sizeof(chip->udata);
|
|
|
|
mutex_lock(&chip->data_lock);
|
|
if (count == 0) {
|
|
pr_err("No data!\n");
|
|
rc = -ENODATA;
|
|
goto fail;
|
|
}
|
|
|
|
if (count < data_size) {
|
|
pr_err("Invalid datasize %zu expected %lu\n", count, data_size);
|
|
goto fail;
|
|
}
|
|
|
|
if (copy_from_user(&chip->udata, buf, data_size)) {
|
|
pr_err("Failed to copy_from_user\n");
|
|
rc = -EFAULT;
|
|
goto fail;
|
|
}
|
|
|
|
__pm_stay_awake(chip->qbg_ws);
|
|
rc = data_size;
|
|
schedule_work(&chip->udata_work);
|
|
|
|
qbg_dbg(chip, QBG_DEBUG_DEVICE, "QBG write complete size=%d\n", rc);
|
|
fail:
|
|
mutex_unlock(&chip->data_lock);
|
|
return rc;
|
|
}
|
|
|
|
static unsigned int qbg_device_poll(struct file *file, poll_table *wait)
|
|
{
|
|
struct qti_qbg *chip = file->private_data;
|
|
unsigned int mask = 0;
|
|
|
|
poll_wait(file, &chip->qbg_wait_q, wait);
|
|
|
|
if (chip->data_ready)
|
|
mask = POLLIN | POLLRDNORM;
|
|
|
|
return mask;
|
|
}
|
|
|
|
static int qbg_device_open(struct inode *inode, struct file *file)
|
|
{
|
|
struct qti_qbg *chip = container_of(inode->i_cdev,
|
|
struct qti_qbg, qbg_cdev);
|
|
|
|
file->private_data = chip;
|
|
|
|
qbg_dbg(chip, QBG_DEBUG_DEVICE, "QBG device opened!\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int qbg_device_release(struct inode *inode, struct file *file)
|
|
{
|
|
struct qti_qbg *chip = container_of(inode->i_cdev,
|
|
struct qti_qbg, qbg_cdev);
|
|
|
|
file->private_data = NULL;
|
|
|
|
qbg_dbg(chip, QBG_DEBUG_DEVICE, "QBG device closed!\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static long qbg_device_ioctl(struct file *file, unsigned int cmd,
|
|
unsigned long arg)
|
|
{
|
|
struct qti_qbg *chip = file->private_data;
|
|
struct qbg_config __user *config_user;
|
|
struct qbg_config config;
|
|
struct qbg_essential_params __user *params_user;
|
|
struct qbg_step_chg_jeita_params __user *step_chg_params_user;
|
|
time64_t rtc_sec;
|
|
int rc = 0, i;
|
|
|
|
if (!chip) {
|
|
pr_err("Device private data not set!\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!arg) {
|
|
pr_err("Invalid user pointer\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Get RTC time here */
|
|
rc = get_rtc_time(chip, &rtc_sec);
|
|
if (rc < 0) {
|
|
pr_err("Failed to read rtc time, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
switch (cmd) {
|
|
case QBGIOCXCFG:
|
|
case QBG_QBGIOCXCFG:
|
|
config_user = (struct qbg_config __user *)arg;
|
|
|
|
rc = qbg_get_sample_time_us(chip);
|
|
if (rc < 0) {
|
|
pr_err("Failed to calculate sample time us, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = qbg_sdam_get_battery_id(chip, &chip->sdam_batt_id);
|
|
if (rc < 0) {
|
|
pr_err("Failed to get battid from sdam, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = qbg_sdam_get_essential_param_revid(chip,
|
|
(u8 *)&chip->essential_param_revid);
|
|
if (rc < 0) {
|
|
pr_err("Failed to get essential param revid, rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
config.current_time = rtc_sec;
|
|
config.batt_id = chip->batt_id_ohm / 1000;
|
|
config.pon_ocv = chip->pon_ocv;
|
|
config.pon_ibat = chip->pon_ibat;
|
|
config.pon_tbat = chip->pon_tbat;
|
|
config.pon_soc = chip->pon_soc;
|
|
config.float_volt_uv = chip->float_volt_uv;
|
|
config.fastchg_curr_ma = chip->fastchg_curr_ma;
|
|
config.vbat_cutoff_mv = chip->vbat_cutoff_mv;
|
|
config.ibat_cutoff_ma = chip->ibat_cutoff_ma;
|
|
config.vph_min_mv = chip->vph_min_mv;
|
|
config.iterm_ma = chip->iterm_ma;
|
|
config.rconn_mohm = chip->rconn_mohm;
|
|
config.sdam_batt_id = chip->sdam_batt_id;
|
|
config.essential_param_revid = chip->essential_param_revid;
|
|
for (i = 0; i < QBG_STATE_MAX; i++) {
|
|
config.sample_time_us[i] = chip->sample_time_us[i];
|
|
qbg_dbg(chip, QBG_DEBUG_DEVICE, "QBGIOCXCFG: sample_time_us[%d]:%u\n",
|
|
i, config.sample_time_us[i]);
|
|
}
|
|
|
|
if (copy_to_user(config_user, (void *)&config, sizeof(config))) {
|
|
pr_err("Failed to copy QBG config to user\n");
|
|
return -EFAULT;
|
|
}
|
|
|
|
qbg_dbg(chip, QBG_DEBUG_DEVICE, "QBGIOCXCFG: sdam_battid:%u essential param revid:%u battid:%u pon_ocv:%u pon_ibat:%u pon_soc:%u vbatt_cutoff_mv:%u iterm_ma:%u\n",
|
|
config.sdam_batt_id, config.essential_param_revid,
|
|
config.batt_id, config.pon_ocv,
|
|
config.pon_ibat, config.pon_soc,
|
|
config.vbat_cutoff_mv, config.iterm_ma);
|
|
|
|
break;
|
|
case QBGIOCXEPR:
|
|
case QBG_QBGIOCXEPR:
|
|
params_user = (struct qbg_essential_params __user *)arg;
|
|
rc = qbg_sdam_get_essential_params(chip,
|
|
(u8 *)&chip->essential_params);
|
|
if (rc < 0) {
|
|
pr_err("Failed to read essential params, rc=%d\n", rc);
|
|
return -EFAULT;
|
|
}
|
|
|
|
if (copy_to_user(params_user, (void *)&chip->essential_params,
|
|
sizeof(chip->essential_params))) {
|
|
pr_err("Failed to copy QBG essential params to user\n");
|
|
return -EFAULT;
|
|
}
|
|
|
|
break;
|
|
case QBGIOCXEPW:
|
|
case QBG_QBGIOCXEPW:
|
|
params_user = (struct qbg_essential_params __user *)arg;
|
|
if (copy_from_user((void *)&chip->essential_params, params_user,
|
|
sizeof(chip->essential_params))) {
|
|
pr_err("Failed to copy QBG essential params from user\n");
|
|
return -EFAULT;
|
|
}
|
|
chip->previous_ep_time = rtc_sec;
|
|
chip->essential_params.rtc_time = rtc_sec;
|
|
|
|
rc = qbg_sdam_set_essential_params(chip,
|
|
(u8 *)&chip->essential_params);
|
|
if (rc < 0) {
|
|
pr_err("Failed to write essential params, rc=%d\n", rc);
|
|
return -EFAULT;
|
|
}
|
|
qbg_dbg(chip, QBG_DEBUG_SDAM, "Essential params written, time:%lu secs\n",
|
|
rtc_sec);
|
|
|
|
break;
|
|
case QBGIOCXSTEPCHGCFG:
|
|
case QBG_QBGIOCXSTEPCHGCFG:
|
|
step_chg_params_user = (struct qbg_step_chg_jeita_params __user *)arg;
|
|
|
|
if (copy_to_user(step_chg_params_user, (void *)chip->step_chg_jeita_params,
|
|
sizeof(struct qbg_step_chg_jeita_params))) {
|
|
pr_err("Failed to copy QBG step and jeita charge params to user\n");
|
|
return -EFAULT;
|
|
}
|
|
qbg_dbg(chip, QBG_DEBUG_DEVICE, "QBGIOCXSTEPCHGCFG: jeita_full_fv_10nv:%d jeita_warm_adc_value:0x%x jeita_cool_adc_value:0x%x ttf_calc_mode:%u\n",
|
|
chip->step_chg_jeita_params->jeita_full_fv_10nv,
|
|
chip->step_chg_jeita_params->jeita_warm_adc_value,
|
|
chip->step_chg_jeita_params->jeita_cool_adc_value,
|
|
chip->step_chg_jeita_params->ttf_calc_mode);
|
|
|
|
break;
|
|
default:
|
|
pr_err("IOCTL %d not supported\n", cmd);
|
|
rc = -EINVAL;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static long qbg_device_compat_ioctl(struct file *file, unsigned int cmd,
|
|
unsigned long arg)
|
|
{
|
|
return qbg_device_ioctl(file, _IOC_NR(cmd), arg);
|
|
}
|
|
|
|
static const struct file_operations qbg_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = qbg_device_open,
|
|
.release = qbg_device_release,
|
|
.read = qbg_device_read,
|
|
.write = qbg_device_write,
|
|
.poll = qbg_device_poll,
|
|
.unlocked_ioctl = qbg_device_ioctl,
|
|
.compat_ioctl = qbg_device_compat_ioctl,
|
|
};
|
|
|
|
static int qbg_register_device(struct qti_qbg *chip)
|
|
{
|
|
int rc;
|
|
|
|
rc = alloc_chrdev_region(&chip->dev_no, 0, 1, "qbg");
|
|
if (rc < 0) {
|
|
pr_err("Failed to allocate chardev rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
cdev_init(&chip->qbg_cdev, &qbg_fops);
|
|
rc = cdev_add(&chip->qbg_cdev, chip->dev_no, 1);
|
|
if (rc < 0) {
|
|
pr_err("Failed to cdev_add rc=%d\n", rc);
|
|
goto unregister_chrdev;
|
|
}
|
|
|
|
chip->qbg_class.name = "qbg";
|
|
chip->qbg_class.class_groups = qbg_class_groups;
|
|
rc = class_register(&chip->qbg_class);
|
|
if (rc < 0) {
|
|
pr_err("Failed to create qbg_class rc=%d\n", rc);
|
|
goto delete_cdev;
|
|
}
|
|
|
|
chip->qbg_device = device_create(&chip->qbg_class, NULL, chip->dev_no,
|
|
NULL, "qbg");
|
|
if (IS_ERR(chip->qbg_device)) {
|
|
pr_err("Failed to create qbg_device\n");
|
|
rc = -EINVAL;
|
|
goto destroy_class;
|
|
}
|
|
|
|
qbg_dbg(chip, QBG_DEBUG_DEVICE, "'/dev/qbg' successfully created\n");
|
|
|
|
return 0;
|
|
|
|
destroy_class:
|
|
class_unregister(&chip->qbg_class);
|
|
delete_cdev:
|
|
cdev_del(&chip->qbg_cdev);
|
|
unregister_chrdev:
|
|
unregister_chrdev_region(chip->dev_no, 1);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int qbg_register_interrupts(struct qti_qbg *chip)
|
|
{
|
|
int rc = 0;
|
|
|
|
/*
|
|
* Do not register for data-full to skip processing QBG
|
|
* data if a valid battery or debug battery is not detected
|
|
*/
|
|
if (chip->battery_unknown || is_debug_batt_id(chip))
|
|
return rc;
|
|
|
|
/*
|
|
* Turn off data full interrupt from PMIC side. After IRQ handler
|
|
* registration, we re-enable the interrupt which guarantees the IRQ
|
|
* handler will fire if the FIFO is already full.
|
|
*/
|
|
rc = qbg_enable_data_full_int(chip, false);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev,
|
|
"Failed to disable interrupt from PMIC side, rc=%d\n",
|
|
rc);
|
|
}
|
|
|
|
rc = devm_request_threaded_irq(chip->dev, chip->irq, NULL,
|
|
qbg_data_full_irq_handler, IRQF_ONESHOT,
|
|
"qbg-sdam", chip);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Failed to request IRQ(qbg-sdam), rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
/* Enable the interrupt, it will get raised if FIFO is already full */
|
|
rc = qbg_enable_data_full_int(chip, true);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev,
|
|
"Failed to enable interrupt from PMIC side, rc=%d\n",
|
|
rc);
|
|
}
|
|
|
|
rc = enable_irq_wake(chip->irq);
|
|
if (rc < 0)
|
|
dev_err(chip->dev, "Failed to set IRQ(qbg-sdam) wake-able, rc=%d\n",
|
|
rc);
|
|
|
|
/* Register for Vbatt_empty INT only if valid value is defined in DT */
|
|
if (chip->vbatt_empty_threshold_mv != 0) {
|
|
rc = devm_request_threaded_irq(chip->dev, chip->vbatt_empty_irq, NULL,
|
|
qbg_vbatt_empty_irq_handler, IRQF_ONESHOT,
|
|
"qbg-vbatt-empty", chip);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Failed to request IRQ(qbg-vbatt-empty), rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = enable_irq_wake(chip->vbatt_empty_irq);
|
|
if (rc < 0)
|
|
dev_err(chip->dev, "Failed to set IRQ(qbg-vbatt-empty) wake-able, rc=%d\n",
|
|
rc);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
static ssize_t qbg_debug_mask_read(struct file *filp, char __user *buffer,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
char *buf;
|
|
ssize_t len = 0;
|
|
|
|
if (*ppos != 0)
|
|
return 0;
|
|
|
|
buf = kasprintf(GFP_KERNEL, "%d\n", qbg_debug_mask);
|
|
if (!buf)
|
|
return -ENOMEM;
|
|
|
|
if (count < strlen(buf)) {
|
|
kfree(buf);
|
|
return -ENOSPC;
|
|
}
|
|
|
|
len = simple_read_from_buffer(buffer, count, ppos, buf, strlen(buf));
|
|
kfree(buf);
|
|
|
|
return len;
|
|
}
|
|
|
|
static ssize_t qbg_debug_mask_write(struct file *filp, const char __user *buffer,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
struct qti_qbg *chip = filp->private_data;
|
|
int rc = 0;
|
|
char data[2];
|
|
|
|
rc = kstrtou32_from_user(buffer, count, 10, &qbg_debug_mask);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
if (!chip->debug_mask_nvmem_low || !chip->debug_mask_nvmem_high)
|
|
return count;
|
|
|
|
data[0] = qbg_debug_mask & 0xff;
|
|
data[1] = (qbg_debug_mask >> 8) & 0xff;
|
|
|
|
rc = nvmem_cell_write(chip->debug_mask_nvmem_low, &data[0], sizeof(data[0]));
|
|
if (rc < 0) {
|
|
pr_err("Failed to write qbg debug mask low byte, rc = %d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = nvmem_cell_write(chip->debug_mask_nvmem_high, &data[1], sizeof(data[1]));
|
|
if (rc < 0) {
|
|
pr_err("Failed to write qbg debug mask high byte, rc = %d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
static const struct file_operations qbg_debug_mask_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = simple_open,
|
|
.read = qbg_debug_mask_read,
|
|
.write = qbg_debug_mask_write,
|
|
};
|
|
|
|
static void qbg_create_debugfs(struct qti_qbg *chip)
|
|
{
|
|
struct dentry *entry;
|
|
|
|
pr_err("%s:%u\n", __func__, __LINE__);
|
|
chip->dfs_root = debugfs_create_dir("qbg", NULL);
|
|
if (IS_ERR_OR_NULL(chip->dfs_root)) {
|
|
pr_err("Failed to create debugfs directory rc=%ld\n",
|
|
(long)chip->dfs_root);
|
|
return;
|
|
}
|
|
|
|
entry = debugfs_create_file("debug_mask", 0600, chip->dfs_root, chip,
|
|
&qbg_debug_mask_fops);
|
|
if (IS_ERR_OR_NULL(entry)) {
|
|
pr_err("Failed to create debug_mask rc=%ld\n", (long)entry);
|
|
debugfs_remove_recursive(chip->dfs_root);
|
|
}
|
|
}
|
|
#else
|
|
static void qbg_create_debugfs(struct qti_qbg *chip)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
static void get_qbg_debug_mask(struct qti_qbg *chip)
|
|
{
|
|
ssize_t len;
|
|
char *data[2];
|
|
|
|
if (!chip->debug_mask_nvmem_low || !chip->debug_mask_nvmem_high)
|
|
return;
|
|
|
|
data[0] = nvmem_cell_read(chip->debug_mask_nvmem_low, &len);
|
|
if (IS_ERR(data[0])) {
|
|
pr_err("Failed to read qbg debug mask low byte from SDAM\n");
|
|
return;
|
|
}
|
|
|
|
data[1] = nvmem_cell_read(chip->debug_mask_nvmem_high, &len);
|
|
if (IS_ERR(data[1])) {
|
|
pr_err("Failed to read qbg debug mask high byte from SDAM\n");
|
|
return;
|
|
}
|
|
|
|
qbg_debug_mask = *data[1] & 0xff;
|
|
qbg_debug_mask = (qbg_debug_mask << 8) | (*data[0]);
|
|
|
|
kfree(data[0]);
|
|
kfree(data[1]);
|
|
}
|
|
|
|
#define SOC_UPDATE_FREQUENCY_MS 60000
|
|
static void soc_update_work(struct work_struct *work)
|
|
{
|
|
struct qti_qbg *chip = container_of(work, struct qti_qbg, soc_update_work.work);
|
|
int rc = 0;
|
|
|
|
if (chip->sys_soc != INT_MIN) {
|
|
qbg_dbg(chip, QBG_DEBUG_STATUS, "write soc = %d to register\n",
|
|
chip->sys_soc);
|
|
rc = qbg_write_iio_chan(chip, SYS_SOC, chip->sys_soc);
|
|
if (rc < 0) {
|
|
pr_err("Failed to write battery sys_soc, rc=%d\n", rc);
|
|
return;
|
|
}
|
|
}
|
|
|
|
schedule_delayed_work(&chip->soc_update_work, msecs_to_jiffies(SOC_UPDATE_FREQUENCY_MS));
|
|
}
|
|
|
|
static int qbg_parse_sdam_dt(struct qti_qbg *chip, struct device_node *node)
|
|
{
|
|
int rc;
|
|
|
|
chip->irq = of_irq_get_byname(node, "qbg-sdam");
|
|
if (chip->irq < 0) {
|
|
pr_err("Failed to get irq for QBG, rc=%d\n", chip->irq);
|
|
return -EINVAL;
|
|
}
|
|
|
|
rc = of_property_read_u32(node, "qcom,num-data-sdams",
|
|
&chip->num_data_sdams);
|
|
if (rc < 0) {
|
|
pr_err("Failed to read number of DATA SDAMs for QBG, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = of_property_read_u32(node, "qcom,sdam-base", &chip->sdam_base);
|
|
if (rc < 0) {
|
|
pr_err("Failed to read SDAM base address, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
#define QBG_DEFAULT_VBAT_CUTOFF_MV 3100
|
|
#define QBG_DEFAULT_IBAT_CUTOFF_MA 150
|
|
#define QBG_DEFAULT_VPH_MIN_MV 2700
|
|
#define QBG_DEFAULT_ITERM_MA 100
|
|
#define QBG_DEFAULT_RCONN_MOHM 0
|
|
static int qbg_parse_dt(struct qti_qbg *chip)
|
|
{
|
|
struct device_node *node = chip->dev->of_node;
|
|
int rc = 0;
|
|
u32 val;
|
|
|
|
rc = qbg_parse_sdam_dt(chip, node);
|
|
if (rc < 0) {
|
|
pr_err("Failed to QBG SDAM DT, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = of_property_read_u32(node, "qcom,adc-cmn-wb-base", &chip->adc_cmn_wb_base);
|
|
if (rc) {
|
|
pr_err("Failed to get adc cmn wb addr, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = of_property_read_u32(node, "qcom,adc-cmn-base", &chip->adc_cmn_base);
|
|
if (rc) {
|
|
pr_err("Failed to get adc cmn addr, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
chip->vbat_cutoff_mv = QBG_DEFAULT_VBAT_CUTOFF_MV;
|
|
rc = of_property_read_u32(node, "qcom,vbat-cutoff-mv", &val);
|
|
if (!rc)
|
|
chip->vbat_cutoff_mv = val;
|
|
|
|
chip->ibat_cutoff_ma = QBG_DEFAULT_IBAT_CUTOFF_MA;
|
|
rc = of_property_read_u32(node, "qcom,ibat-cutoff-ma", &val);
|
|
if (!rc)
|
|
chip->ibat_cutoff_ma = val;
|
|
|
|
chip->vph_min_mv = QBG_DEFAULT_VPH_MIN_MV;
|
|
rc = of_property_read_u32(node, "qcom,vph-min-mv", &val);
|
|
if (!rc)
|
|
chip->vph_min_mv = val;
|
|
|
|
chip->iterm_ma = QBG_DEFAULT_ITERM_MA;
|
|
rc = of_property_read_u32(node, "qcom,iterm-ma", &val);
|
|
if (!rc)
|
|
chip->iterm_ma = val;
|
|
|
|
chip->rconn_mohm = QBG_DEFAULT_RCONN_MOHM;
|
|
rc = of_property_read_u32(node, "qcom,rconn-mohm", &val);
|
|
if (!rc)
|
|
chip->rconn_mohm = val;
|
|
|
|
chip->vbatt_empty_threshold_mv = 0;
|
|
rc = of_property_read_u32(node, "qcom,vbatt-empty-threshold-mv",
|
|
&val);
|
|
|
|
if (!rc)
|
|
chip->vbatt_empty_threshold_mv = val;
|
|
|
|
chip->vbatt_empty_irq = of_irq_get_byname(node, "qbg-vbatt-empty");
|
|
if (chip->vbatt_empty_irq < 0) {
|
|
pr_err("Failed to get Vbatt_empty IRQ, rc=%d\n", chip->vbatt_empty_irq);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (of_find_property(node, "nvmem-cells", NULL)) {
|
|
chip->debug_mask_nvmem_low = devm_nvmem_cell_get(chip->dev, "qbg_debug_mask_low");
|
|
if (IS_ERR(chip->debug_mask_nvmem_low)) {
|
|
rc = PTR_ERR(chip->debug_mask_nvmem_low);
|
|
if (rc != -EPROBE_DEFER)
|
|
dev_err(chip->dev, "Failed to get nvmem-cells, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
chip->debug_mask_nvmem_high = devm_nvmem_cell_get(chip->dev, "qbg_debug_mask_high");
|
|
if (IS_ERR(chip->debug_mask_nvmem_high)) {
|
|
rc = PTR_ERR(chip->debug_mask_nvmem_high);
|
|
if (rc != -EPROBE_DEFER)
|
|
dev_err(chip->dev, "Failed to get nvmem-cells, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
chip->skip_esr_state = devm_nvmem_cell_get(chip->dev, "skip_esr_state");
|
|
if (IS_ERR(chip->skip_esr_state)) {
|
|
rc = PTR_ERR(chip->skip_esr_state);
|
|
if (rc != -EPROBE_DEFER) {
|
|
dev_dbg(chip->dev, "Failed to get skip_esr_state, rc=%d\n", rc);
|
|
chip->skip_esr_state = NULL;
|
|
goto exit;
|
|
}
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
exit:
|
|
return 0;
|
|
}
|
|
|
|
static int qti_qbg_probe(struct platform_device *pdev)
|
|
{
|
|
struct qti_qbg *chip;
|
|
struct iio_dev *indio_dev;
|
|
int rc;
|
|
u32 val;
|
|
|
|
indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*chip));
|
|
if (!indio_dev)
|
|
return -ENOMEM;
|
|
|
|
chip = iio_priv(indio_dev);
|
|
chip->dev = &pdev->dev;
|
|
chip->indio_dev = indio_dev;
|
|
|
|
chip->ext_iio_chans = devm_kcalloc(chip->dev,
|
|
ARRAY_SIZE(qbg_ext_iio_chan_name),
|
|
sizeof(*chip->ext_iio_chans),
|
|
GFP_KERNEL);
|
|
if (!chip->ext_iio_chans)
|
|
return -ENOMEM;
|
|
|
|
chip->regmap = dev_get_regmap(pdev->dev.parent, NULL);
|
|
if (!chip->regmap) {
|
|
dev_err(&pdev->dev, "Failed to get regmap\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
rc = of_property_read_u32(pdev->dev.of_node, "reg", &val);
|
|
if (rc < 0) {
|
|
pr_err("Failed to get base address for QBG, rc = %d\n", rc);
|
|
return rc;
|
|
}
|
|
chip->base = val;
|
|
|
|
INIT_WORK(&chip->status_change_work, status_change_work);
|
|
INIT_WORK(&chip->udata_work, process_udata_work);
|
|
INIT_DELAYED_WORK(&chip->soc_update_work, soc_update_work);
|
|
mutex_init(&chip->fifo_lock);
|
|
mutex_init(&chip->data_lock);
|
|
mutex_init(&chip->context_lock);
|
|
dev_set_drvdata(chip->dev, chip);
|
|
init_waitqueue_head(&chip->qbg_wait_q);
|
|
|
|
chip->debug_mask = &qbg_debug_mask;
|
|
|
|
chip->qbg_ws = wakeup_source_register(chip->dev, "qcom-qbg");
|
|
if (!chip->qbg_ws)
|
|
return -EINVAL;
|
|
|
|
chip->default_iterm_ma = -EINVAL;
|
|
chip->soc = INT_MIN;
|
|
chip->batt_soc = INT_MIN;
|
|
chip->sys_soc = INT_MIN;
|
|
chip->esr = INT_MIN;
|
|
chip->ocv_uv = INT_MIN;
|
|
chip->pon_ocv = INT_MIN;
|
|
chip->charge_cycle_count = INT_MIN;
|
|
chip->nominal_capacity = INT_MIN;
|
|
chip->learned_capacity = INT_MIN;
|
|
chip->ttf = INT_MIN;
|
|
chip->tte = INT_MIN;
|
|
chip->soh = INT_MIN;
|
|
chip->tbat = INT_MIN;
|
|
|
|
rc = qbg_parse_dt(chip);
|
|
if (rc < 0) {
|
|
dev_err(&pdev->dev, "Failed to parse QBG devicetree, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
get_qbg_debug_mask(chip);
|
|
|
|
/* ADC for Battery-ID */
|
|
chip->batt_id_chan = devm_iio_channel_get(&pdev->dev, "batt-id");
|
|
if (IS_ERR(chip->batt_id_chan)) {
|
|
rc = PTR_ERR(chip->batt_id_chan);
|
|
if (rc != -EPROBE_DEFER)
|
|
dev_err(&pdev->dev, "batt-id channel unavailable, rc=%d\n", rc);
|
|
chip->batt_id_chan = NULL;
|
|
return rc;
|
|
}
|
|
|
|
/* ADC for Battery-Temp */
|
|
chip->batt_temp_chan = devm_iio_channel_get(&pdev->dev, "batt-temp");
|
|
if (IS_ERR(chip->batt_temp_chan)) {
|
|
rc = PTR_ERR(chip->batt_temp_chan);
|
|
if (rc != -EPROBE_DEFER)
|
|
dev_err(&pdev->dev, "batt-temp channel unavailable, rc=%d\n", rc);
|
|
chip->batt_temp_chan = NULL;
|
|
return rc;
|
|
}
|
|
|
|
qbg_create_debugfs(chip);
|
|
|
|
chip->rtc = rtc_class_open(CONFIG_RTC_HCTOSYS_DEVICE);
|
|
if (chip->rtc == NULL)
|
|
return -EPROBE_DEFER;
|
|
|
|
rc = regmap_read(chip->regmap, REVID_REVISION4, &chip->rev4);
|
|
if (rc < 0) {
|
|
pr_err("Failed to read REVID_REVISION4, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = qbg_init_sdam(chip);
|
|
if (rc < 0) {
|
|
dev_err(&pdev->dev, "Failed to initialize QBG sdam, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = qbg_get_max_fifo_count(chip);
|
|
if (rc < 0) {
|
|
dev_err(&pdev->dev, "Failed to get fifo count, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = qbg_init_esr(chip);
|
|
if (rc < 0) {
|
|
dev_err(&pdev->dev, "Failed to initialize QBG ESR, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = qbg_setup_battery(chip);
|
|
if (rc < 0) {
|
|
dev_err(&pdev->dev, "Failed to setup battery for QBG, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = qbg_determine_pon_soc(chip);
|
|
if (rc < 0) {
|
|
dev_err(&pdev->dev, "Failed to determine initial state, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = qbg_register_device(chip);
|
|
if (rc < 0) {
|
|
dev_err(&pdev->dev, "Failed to register QBG device, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = qbg_init_iio(chip, pdev);
|
|
if (rc < 0) {
|
|
dev_err(&pdev->dev, "Failed to initialize QBG IIO device, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = qbg_init_psy(chip);
|
|
if (rc < 0) {
|
|
dev_err(&pdev->dev, "Failed to initialize QBG PSY, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
qbg_init_vbatt_empty_threshold(chip);
|
|
|
|
rc = qbg_register_interrupts(chip);
|
|
if (rc < 0) {
|
|
dev_err(&pdev->dev, "Failed to register QBG interrupts, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
schedule_delayed_work(&chip->soc_update_work, msecs_to_jiffies(SOC_UPDATE_FREQUENCY_MS));
|
|
|
|
dev_info(&pdev->dev, "QBG initialized! battery_profile=%s SOC=%d\n",
|
|
qbg_get_battery_type(chip), chip->soc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int qti_qbg_remove(struct platform_device *pdev)
|
|
{
|
|
struct qti_qbg *chip = platform_get_drvdata(pdev);
|
|
|
|
if (chip->rtc)
|
|
rtc_class_close(chip->rtc);
|
|
cancel_work_sync(&chip->status_change_work);
|
|
cancel_work_sync(&chip->udata_work);
|
|
cancel_delayed_work_sync(&chip->soc_update_work);
|
|
mutex_destroy(&chip->fifo_lock);
|
|
mutex_destroy(&chip->data_lock);
|
|
mutex_destroy(&chip->context_lock);
|
|
cdev_del(&chip->qbg_cdev);
|
|
unregister_chrdev_region(chip->dev_no, 1);
|
|
class_unregister(&chip->qbg_class);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int qbg_freeze(struct device *dev)
|
|
{
|
|
struct qti_qbg *chip = dev_get_drvdata(dev);
|
|
/*free irq*/
|
|
if (chip->irq > 0)
|
|
devm_free_irq(dev, chip->irq, chip);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int qbg_restore(struct device *dev)
|
|
{
|
|
int ret = 0;
|
|
struct qti_qbg *chip = dev_get_drvdata(dev);
|
|
|
|
/* Init & clear SDAM to kick-start QBG sampling */
|
|
ret = qbg_init_sdam(chip);
|
|
if (ret < 0) {
|
|
dev_err(dev, "Failed to init qbg sdam rc = %d\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = qbg_register_interrupts(chip);
|
|
if (ret < 0)
|
|
dev_err(dev, "Failed to register qbg interrupt rc = %d\n");
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int qbg_suspend(struct device *dev)
|
|
{
|
|
#ifdef CONFIG_DEEPSLEEP
|
|
if (pm_suspend_via_firmware())
|
|
return qbg_freeze(dev);
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static int qbg_resume(struct device *dev)
|
|
{
|
|
#ifdef CONFIG_DEEPSLEEP
|
|
if (pm_suspend_via_firmware())
|
|
return qbg_restore(dev);
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static const struct dev_pm_ops qbg_pm_ops = {
|
|
.freeze = qbg_freeze,
|
|
.restore = qbg_restore,
|
|
.suspend = qbg_suspend,
|
|
.resume = qbg_resume,
|
|
};
|
|
|
|
static const struct of_device_id qbg_match_table[] = {
|
|
{ .compatible = "qcom,qbg", },
|
|
{ },
|
|
};
|
|
|
|
static struct platform_driver qti_qbg_driver = {
|
|
.driver = {
|
|
.name = "qti_qbg",
|
|
.of_match_table = qbg_match_table,
|
|
.pm = &qbg_pm_ops,
|
|
},
|
|
.probe = qti_qbg_probe,
|
|
.remove = qti_qbg_remove,
|
|
};
|
|
module_platform_driver(qti_qbg_driver);
|
|
|
|
MODULE_DESCRIPTION("QTI QBG (Qualcomm Battery Gauging) driver");
|
|
MODULE_LICENSE("GPL v2");
|