power: supply: introduce pmi8998 fuel guage driver

This commit is contained in:
Joel Selvaraj 2020-12-25 12:55:46 +05:30 committed by Jami Kettunen
parent 992bfaddcd
commit 227f2b7108
4 changed files with 675 additions and 0 deletions

View file

@ -934,4 +934,14 @@ config CHARGER_QCOM_SMB2
Say Y or M here to enable reporting the charger status and rate on
supported platforms such as Snapdragon 845 and 835 based phones.
config BATTERY_PMI8998_FG
tristate "Qualcomm PMI8998 fuel gauge driver"
depends on MFD_SPMI_PMIC
help
Say Y here to enable the Qualcomm PMI8998 Fuel Gauge driver. This
adds support for battery fuel gauging and state of charge of
battery connected tothe fuel gauge. The state of charge is
reported through a BMS power supply property and also sends
uevents when the capacity is updated.
endif # POWER_SUPPLY

View file

@ -111,3 +111,4 @@ obj-$(CONFIG_BATTERY_ACER_A500) += acer_a500_battery.o
obj-$(CONFIG_BATTERY_SURFACE) += surface_battery.o
obj-$(CONFIG_CHARGER_SURFACE) += surface_charger.o
obj-$(CONFIG_BATTERY_UG3105) += ug3105_battery.o
obj-$(CONFIG_BATTERY_PMI8998_FG) += pmi8998_fg.o

View file

@ -0,0 +1,526 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2020, The Linux Foundation. All rights reserved. */
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/of_address.h>
#include <linux/regmap.h>
#include <linux/device.h>
#include <linux/mutex.h>
#include <linux/types.h>
#include <linux/power_supply.h>
#include <linux/module.h>
#include <linux/math64.h>
#include "pmi8998_fg.h"
/************************
* IO FUNCTIONS
* **********************/
/**
* pmi8998_read() - Read multiple registers with regmap_bulk_read
*
* @param map The regmap to read
* @param val Pointer to read values into
* @param addr Address to read from
* @param len Number of registers (bytes) to read
* @return int 0 on success, negative errno on error
*/
static int pmi8998_read(struct regmap *map, u8 *val, u16 addr, int len)
{
if ((addr & 0xff00) == 0) {
pr_err("base cannot be zero base=0x%02x\n", addr);
return -EINVAL;
}
pr_info("%s: reading 0x%x bytes from 0x%x", __func__, len, addr);
return regmap_bulk_read(map, addr, val, len);
}
/**
* @brief pmi8998_write() - Write multiple registers with regmap_bulk_write
*
* @param map The regmap to write
* @param val Pointer to write values into
* @param addr Address to write from
* @param len Number of registers (bytes) to write
* @return int 0 on success, negative errno on error
*/
static int pmi8998_write(struct regmap *map, u8 *val, u16 addr, int len)
{
int rc;
bool sec_access = (addr & 0xff) > 0xd0;
u8 sec_addr_val = 0xa5;
if (sec_access) {
rc = regmap_bulk_write(map,
(addr & 0xff00) | 0xd0,
&sec_addr_val, 1);
}
if ((addr & 0xff00) == 0) {
pr_err("addr cannot be zero base=0x%02x\n", addr);
return -EINVAL;
}
return regmap_bulk_write(map, addr, val, len);
}
/**
* @brief pmi8998_masked_write() - like pmi8998_write but applies
* a mask first.
*
* @param map The regmap to write
* @param val Pointer to write values into
* @param addr Address to write from
* @param len Number of registers (bytes) to write
* @return int 0 on success, negative errno on error
*/
static int pmi8998_masked_write(struct regmap *map, u16 addr,
u8 mask, u8 val)
{
int error;
u8 reg;
error = pmi8998_read(map, &reg, addr, 1);
if (error)
return error;
reg &= ~mask;
reg |= val & mask;
error = pmi8998_write(map, &reg, addr, 1);
return error;
}
static int64_t twos_compliment_extend(int64_t val, int nbytes)
{
int i;
int64_t mask;
mask = 0x80LL << ((nbytes - 1) * 8);
if (val & mask) {
for (i = 8; i > nbytes; i--) {
mask = 0xFFLL << ((i - 1) * 8);
val |= mask;
}
}
return val;
}
/*************************
* Battery Status RW
* ***********************/
static int pmi8998_fg_get_capacity(struct pmi8998_fg_chip *chip, int *val)
{
u8 cap[2];
int error = pmi8998_read(chip->regmap, cap, REG_BASE(chip) + BATT_MONOTONIC_SOC, 2);
if (error)
return error;
if (cap[0] != cap[1]) {
cap[0] = cap[0] < cap[1] ? cap[0] : cap[1];
}
*val = DIV_ROUND_CLOSEST((cap[0] - 1) * 98, 0xff - 2) + 1;
return 0;
}
static bool pmi8998_battery_missing(struct pmi8998_fg_chip *chip)
{
int rc;
u8 fg_batt_sts;
rc = pmi8998_read(chip->regmap, &fg_batt_sts,
REG_BATT(chip) + INT_RT_STS, 1);
if (rc) {
pr_warn("read read failed: addr=%03X, rc=%d\n",
REG_BATT(chip) + INT_RT_STS, rc);
return false;
}
// Bit 6 is set if the battery is missing
return (fg_batt_sts & BIT(6)) ? true : false;
}
static int pmi8998_fg_get_temperature(struct pmi8998_fg_chip *chip, int *val)
{
int rc, temp;
u8 readval[2];
rc = pmi8998_read(chip->regmap, readval, REG_BATT(chip) + PARAM_ADDR_BATT_TEMP, 2);
if (rc) {
pr_err("Failed to read temperature\n");
return rc;
}
temp = ((readval[1] & BATT_TEMP_MSB_MASK) << 8) |
(readval[0] & BATT_TEMP_LSB_MASK);
temp = DIV_ROUND_CLOSEST(temp * 10, 4);
*val = temp -2730;
return 0;
}
static int pmi8998_fg_get_current(struct pmi8998_fg_chip *chip, int *val)
{
int rc, temp;
u8 readval[2];
rc = pmi8998_read(chip->regmap, readval, REG_BATT(chip) + PARAM_ADDR_BATT_CURRENT, 2);
if (rc) {
pr_err("Failed to read current\n");
return rc;
}
temp = readval[1] << 8 | readval[0];
temp = twos_compliment_extend(temp, 2);
*val = div_s64((s64)temp * 488281,
1000);
return 0;
}
static int pmi8998_fg_get_voltage(struct pmi8998_fg_chip *chip, int *val)
{
int rc, temp;
u8 readval[2];
rc = pmi8998_read(chip->regmap, readval, REG_BATT(chip) + PARAM_ADDR_BATT_VOLTAGE, 2);
if (rc) {
pr_err("Failed to read voltage\n");
return rc;
}
temp = readval[1] << 8 | readval[0];
temp = twos_compliment_extend(temp, 2);
*val = div_s64((s64)temp * 122070,
1000);
return 0;
}
static int pmi8998_fg_get_max_charge_design(struct pmi8998_fg_chip *chip, int *val)
{
int rc, temp;
u8 readval[2];
rc = pmi8998_read(chip->regmap, readval, REG_BATT(chip) + BATT_INFO_CHARGE_MAX_DESIGN, 2);
if (rc) {
pr_err("Failed to read voltage\n");
return rc;
}
temp = readval[1] << 8 | readval[0];
temp = twos_compliment_extend(temp, 2);
*val = div_s64((s64)temp * 122070,
1000);
return 0;
}
/********************
* Init stuff
* ******************/
static int pmi8998_iacs_clear_sequence(struct pmi8998_fg_chip *chip)
{
int rc = 0;
u8 temp;
/* clear the error */
rc = pmi8998_masked_write(chip->regmap, REG_MEM(chip) + MEM_INTF_IMA_CFG,
BIT(2), BIT(2));
if (rc) {
pr_err("Error writing to IMA_CFG, rc=%d\n", rc);
return rc;
}
temp = 0x4;
rc = pmi8998_write(chip->regmap, &temp, REG_MEM(chip) + MEM_INTF_ADDR_LSB + 1, 1);
if (rc) {
pr_err("Error writing to MEM_INTF_ADDR_MSB, rc=%d\n", rc);
return rc;
}
temp = 0x0;
rc = pmi8998_write(chip->regmap, &temp, REG_MEM(chip) + MEM_INTF_WR_DATA0 + 3, 1);
if (rc) {
pr_err("Error writing to WR_DATA3, rc=%d\n", rc);
return rc;
}
rc = pmi8998_read(chip->regmap, &temp, REG_MEM(chip) + MEM_INTF_RD_DATA0 + 3, 1);
if (rc) {
pr_err("Error writing to RD_DATA3, rc=%d\n", rc);
return rc;
}
rc = pmi8998_masked_write(chip->regmap, REG_MEM(chip) + MEM_INTF_IMA_CFG,
BIT(2), 0);
if (rc) {
pr_err("Error writing to IMA_CFG, rc=%d\n", rc);
return rc;
}
return rc;
}
static int pmi8998_clear_ima(struct pmi8998_fg_chip *chip,
bool check_hw_sts)
{
int rc = 0, ret = 0;
u8 err_sts = 0, exp_sts = 0, hw_sts = 0;
bool run_err_clr_seq = false;
rc = pmi8998_read(chip->regmap, &err_sts,
REG_MEM(chip) + MEM_INTF_IMA_ERR_STS, 1);
if (rc) {
dev_err(chip->dev, "failed to read IMA_ERR_STS, rc=%d\n", rc);
return rc;
}
rc = pmi8998_read(chip->regmap, &exp_sts,
REG_MEM(chip) + MEM_INTF_IMA_EXP_STS, 1);
if (rc) {
dev_err(chip->dev, "Error in reading IMA_EXP_STS, rc=%d\n", rc);
return rc;
}
if (check_hw_sts) {
rc = pmi8998_read(chip->regmap, &hw_sts,
REG_MEM(chip) + MEM_INTF_IMA_HW_STS, 1);
if (rc) {
dev_err(chip->dev, "Error in reading IMA_HW_STS, rc=%d\n", rc);
return rc;
}
/*
* Lower nibble should be equal to upper nibble before SRAM
* transactions begins from SW side.
*/
if ((hw_sts & 0x0f) != hw_sts >> 4) {
dev_err(chip->dev, "IMA HW not in correct state, hw_sts=%x\n",
hw_sts);
run_err_clr_seq = true;
}
}
if (exp_sts & (BIT(0) | BIT(1) | BIT(3) |
BIT(4) | BIT(5) | BIT(6) |
BIT(7))) {
dev_warn(chip->dev, "IMA exception bit set, exp_sts=%x\n", exp_sts);
run_err_clr_seq = true;
}
if (run_err_clr_seq) {
ret = pmi8998_iacs_clear_sequence(chip);
if (!ret)
return -EAGAIN;
else
dev_err(chip->dev, "Error clearing IMA exception ret=%d\n", ret);
}
return rc;
}
static int fg_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct pmi8998_fg_chip *chip = power_supply_get_drvdata(psy);
int error = 0;
dev_info(chip->dev, "Getting property: %d", psp);
switch (psp) {
case POWER_SUPPLY_PROP_MANUFACTURER:
val->strval = "QCOM";
break;
case POWER_SUPPLY_PROP_MODEL_NAME:
val->strval = "PMI8998 Battery";
break;
case POWER_SUPPLY_PROP_TECHNOLOGY:
val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
break;
case POWER_SUPPLY_PROP_CAPACITY:
error = pmi8998_fg_get_capacity(chip, &val->intval);
break;
case POWER_SUPPLY_PROP_CURRENT_NOW:
error = pmi8998_fg_get_current(chip, &val->intval);
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
error = pmi8998_fg_get_voltage(chip, &val->intval);
break;
case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
val->intval = chip->batt_info.batt_max_voltage_uv;
break;
case POWER_SUPPLY_PROP_TEMP:
error = pmi8998_fg_get_temperature(chip, &val->intval);
break;
case POWER_SUPPLY_PROP_VOLTAGE_MIN:
val->intval = 3370000;
break;
case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
case POWER_SUPPLY_PROP_CHARGE_FULL: /* TODO: Implement learning */
val->intval = chip->batt_info.nom_cap_uah;
break;
case POWER_SUPPLY_PROP_STATUS:
val->intval = POWER_SUPPLY_STATUS_CHARGING;
//error = smb2_chg_get_status(chip, &val->intval);
break;
case POWER_SUPPLY_PROP_HEALTH:
val->intval = POWER_SUPPLY_HEALTH_GOOD;
break;
default:
pr_err("invalid property: %d\n", psp);
return -EINVAL;
}
return error;
}
static const struct power_supply_desc bms_psy_desc = {
.name = "pmi8998-bms",
.type = POWER_SUPPLY_TYPE_BATTERY,
.properties = fg_properties,
.num_properties = ARRAY_SIZE(fg_properties),
.get_property = fg_get_property,
};
static int pmi8998_fg_of_battery_init(struct pmi8998_fg_chip *chip){
struct device_node *batt_node;
struct device_node *node = chip->dev->of_node;
int rc = 0;
batt_node = of_find_node_by_name(node, "qcom,battery-data");
if (!batt_node) {
pr_err("No available batterydata\n");
return rc;
}
of_property_read_u32(batt_node, "qcom,max-voltage-uv",
&chip->batt_info.batt_max_voltage_uv_design);
// Can be read from SRAM, hardcode in DTS for now as reading SRAM is HARD!
of_property_read_u32(batt_node, "qcom,design-capacity",
&chip->batt_info.nom_cap_uah);
return rc;
}
static int pmi8998_fg_probe(struct platform_device *pdev)
{
struct power_supply_config supply_config = {};
struct pmi8998_fg_chip *chip;
const __be32 *prop_addr;
int rc = 0;
u8 dma_status;
bool error_present;
chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
if (!chip) {
return -ENOMEM;
}
chip->dev = &pdev->dev;
mutex_init(&chip->lock);
chip->regmap = dev_get_regmap(pdev->dev.parent, NULL);
if (!chip->regmap) {
dev_err(chip->dev, "failed to locate the regmap\n");
return -ENODEV;
}
// Get base address
prop_addr = of_get_address(pdev->dev.of_node, 0, NULL, NULL);
if (!prop_addr) {
dev_err(chip->dev, "Couldn't read SOC base address from dt\n");
return -EINVAL;
}
chip->base = be32_to_cpu(*prop_addr);
// Init memif fn inlined here (chip hardware info)
rc = pmi8998_read(chip->regmap, chip->revision, REG_MEM(chip) + DIG_MINOR, 4);
if (rc) {
dev_err(chip->dev, "Unable to read FG revision rc=%d\n", rc);
return rc;
}
dev_dbg(chip->dev, "pmi8998 revision DIG:%d.%d ANA:%d.%d\n",
chip->revision[DIG_MAJOR], chip->revision[DIG_MINOR],
chip->revision[ANA_MAJOR], chip->revision[ANA_MINOR]);
/*
* Change the FG_MEM_INT interrupt to track IACS_READY
* condition instead of end-of-transaction. This makes sure
* that the next transaction starts only after the hw is ready.
* IACS_INTR_SRC_SLCT is BIT(3)
*/
rc = pmi8998_masked_write(chip->regmap,
REG_MEM(chip) + MEM_INTF_IMA_CFG, BIT(3), BIT(3));
if (rc) {
dev_err(chip->dev,
"failed to configure interrupt source %d\n",
rc);
return rc;
}
rc = pmi8998_clear_ima(chip, true);
if (rc && rc != -EAGAIN) {
dev_err(chip->dev, "Error clearing IMA, exception rc=%d", rc);
return rc;
}
// Check and clear DMA errors
rc = pmi8998_read(chip->regmap, &dma_status, REG_MEM(chip) + 0x70, 1);
if (rc < 0) {
pr_err("failed to read dma_status, rc=%d\n", rc);
return rc;
}
error_present = dma_status & (BIT(1) | BIT(2));
rc = pmi8998_masked_write(chip->regmap, REG_MEM(chip) + 0x71, BIT(0),
error_present ? BIT(0) : 0);
if (rc < 0) {
pr_err("failed to write dma_ctl, rc=%d\n", rc);
return rc;
}
dev_dbg(chip->dev, "probed revision DIG:%d.%d ANA:%d.%d\n",
chip->revision[DIG_MAJOR], chip->revision[DIG_MINOR],
chip->revision[ANA_MAJOR], chip->revision[ANA_MINOR]);
supply_config.drv_data = chip;
supply_config.of_node = pdev->dev.of_node;
chip->bms_psy = devm_power_supply_register(chip->dev,
&bms_psy_desc, &supply_config);
if (IS_ERR(chip->bms_psy)) {
dev_err(&pdev->dev, "failed to register battery\n");
return PTR_ERR(chip->bms_psy);
}
platform_set_drvdata(pdev, chip);
return 0;
}
static int pmi8998_fg_remove(struct platform_device *pdev)
{
return 0;
}
static const struct of_device_id fg_match_id_table[] = {
{ .compatible = "qcom,pmi8998-fg" },
{ /* sentinal */ }
};
MODULE_DEVICE_TABLE(of, fg_match_id_table);
static struct platform_driver qcom_fg_driver = {
.probe = pmi8998_fg_probe,
.remove = pmi8998_fg_remove,
.driver = {
.name = "pmi8998-fg",
.of_match_table = fg_match_id_table,
},
};
module_platform_driver(qcom_fg_driver);
MODULE_AUTHOR("Caleb Connolly <caleb@connolly.tech>");
MODULE_AUTHOR("Joel Selvaraj <jo@jsfamily.in>");
MODULE_DESCRIPTION("Qualcomm PMI8998 Fuel Guage Driver");
MODULE_LICENSE("GPL v2");

View file

@ -0,0 +1,138 @@
// #define FG_PARAM_MAX 49
/**** Registers *****/
// pmi8998 v2 specific
#define BATT_INFO_CHARGE_MAX_DESIGN 0x4a
#define MEM_INTF_CFG 0x50
#define MEM_INTF_ADDR_LSB 0x61
#define MEM_INTF_RD_DATA0 0x67
#define MEM_INTF_WR_DATA0 0x63
#define SMB2_CABLE_CONNECTED 0x06
// pm8950 / pm89988 common
#define MEM_INTF_IMA_CFG 0x52
#define MEM_INTF_IMA_OPR_STS 0x54
#define MEM_INTF_IMA_EXP_STS 0x55
#define MEM_INTF_IMA_HW_STS 0x56
#define MEM_INTF_BEAT_COUNT 0x57
#define MEM_INTF_IMA_ERR_STS 0x5f
#define MEM_INTF_IMA_BYTE_EN 0x60
#define BATT_INFO_THERM_C1 0x5c
#define BATT_INFO_VBATT_LSB 0xa0
#define BATT_INFO_VBATT_MSB 0xa1
#define BATT_INFO_IBATT_LSB 0xa2
#define BATT_INFO_IBATT_MSB 0xa3
#define BATT_INFO_BATT_TEMP_LSB 0x50
#define BATT_INFO_BATT_TEMP_MSB 0x51
#define BATT_MONOTONIC_SOC 0x09
#define BATT_TEMP_LSB_MASK GENMASK(7, 0)
#define BATT_TEMP_MSB_MASK GENMASK(2, 0)
#define REG_BASE(chip) (chip->base)
#define REG_BATT(chip) (chip->base + 0x100)
#define REG_MEM(chip) (chip->base + 0x400)
/* Interrupt offsets */
#define INT_RT_STS 0x10
#define INT_EN_CLR 0x16
// Param addresses
#define PARAM_ADDR_BATT_TEMP 0x50
#define PARAM_ADDR_BATT_VOLTAGE 0xa0
#define PARAM_ADDR_BATT_CURRENT 0xa2
enum wa_flags {
PMI8998_V1_REV_WA,
PMI8998_V2_REV_WA,
};
enum pmi8998_rev_offsets {
DIG_MINOR = 0x0,
DIG_MAJOR = 0x1,
ANA_MINOR = 0x2,
ANA_MAJOR = 0x3,
};
enum pmi8998_rev {
DIG_REV_1 = 0x1,
DIG_REV_2 = 0x2,
DIG_REV_3 = 0x3,
};
enum charger_status{
TRICKLE_CHARGE = 0,
PRE_CHARGE,
FAST_CHARGE,
FULLON_CHARGE,
TAPER_CHARGE,
TERMINATE_CHARGE,
INHIBIT_CHARGE,
DISABLE_CHARGE,
};
static enum power_supply_property fg_properties[] = {
POWER_SUPPLY_PROP_MANUFACTURER,
POWER_SUPPLY_PROP_MODEL_NAME,
POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
POWER_SUPPLY_PROP_VOLTAGE_MIN,
POWER_SUPPLY_PROP_TEMP,
// POWER_SUPPLY_PROP_CHARGE_NOW,
// POWER_SUPPLY_PROP_CHARGE_FULL,
POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_STATUS,
};
struct fg_learning_data {
struct mutex learning_lock;
bool active;
int64_t cc_uah;
int learned_cc_uah;
int init_cc_pc_val;
int max_start_soc;
int max_increment;
int max_decrement;
int vbat_est_thr_uv;
int max_cap_limit;
int min_cap_limit;
int min_temp;
int max_temp;
};
struct battery_info {
const char *manufacturer;
const char *model;
const char *serial_num;
int nom_cap_uah;
int batt_max_voltage_uv_design;
int batt_max_voltage_uv;
};
struct pmi8998_fg_chip {
struct device *dev;
unsigned int base;
struct regmap *regmap;
struct mutex lock;
struct power_supply *bms_psy;
u8 revision[4];
bool ima_supported;
struct battery_info batt_info;
struct fg_learning_data learning_data;
int health;
int status;
};