blob: 82fc142dc0d59b189602c17a0d0070b232bd4dc3 [file] [log] [blame] [edit]
/*
* Fuel gauge driver for Maxim 77779
*
* Copyright (C) 2023 Google Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": %s " fmt, __func__
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/iio/consumer.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/time.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/fs.h> /* register_chrdev, unregister_chrdev */
#include <linux/seq_file.h> /* seq_read, seq_lseek, single_release */
#include "gbms_power_supply.h"
#include "google_bms.h"
#include "max77779_fg.h"
#include <linux/debugfs.h>
#define MAX77779_FG_TPOR_MS 800
#define MAX77779_FG_TICLR_MS 500
#define MAX77779_FG_I2C_DRIVER_NAME "max_fg_irq"
#define MAX77779_FG_DELAY_INIT_MS 1000
#define FULLCAPNOM_STABILIZE_CYCLES 5
#define BHI_IMPEDANCE_SOC_LO 50
#define BHI_IMPEDANCE_SOC_HI 55
#define BHI_IMPEDANCE_TEMP_LO 250
#define BHI_IMPEDANCE_TEMP_HI 300
#define BHI_IMPEDANCE_CYCLE_CNT 5
#define BHI_IMPEDANCE_TIMERH 50 /* 7*24 / 3.2hr */
enum max77779_fg_command_bits {
MAX77779_FG_COMMAND_HARDWARE_RESET = 0x000F,
};
/* Capacity Estimation */
struct gbatt_capacity_estimation {
const struct max17x0x_reg *bcea;
struct mutex batt_ce_lock;
struct delayed_work settle_timer;
int cap_tsettle;
int cap_filt_length;
int estimate_state;
bool cable_in;
int delta_cc_sum;
int delta_vfsoc_sum;
int cap_filter_count;
int start_cc;
int start_vfsoc;
};
#define DEFAULT_BATTERY_ID 0
#define DEFAULT_BATTERY_ID_RETRIES 20
#define DUMMY_BATTERY_ID 170
#define ESTIMATE_DONE 2
#define ESTIMATE_PENDING 1
#define ESTIMATE_NONE 0
#define CE_CAP_FILTER_COUNT 0
#define CE_DELTA_CC_SUM_REG 1
#define CE_DELTA_VFSOC_SUM_REG 2
#define CE_FILTER_COUNT_MAX 15
#define BHI_CAP_FCN_COUNT 3
#define DEFAULT_STATUS_CHARGE_MA 100
/* No longer used in 79, used for taskperiod re-scaling in 59 */
#define MAX77779_LSB 1
#define MAX77779_FG_NDGB_ADDRESS 0x37
#pragma pack(1)
struct max77779_fg_eeprom_history {
u16 tempco;
u16 rcomp0;
u8 timerh;
unsigned fullcapnom:10;
unsigned fullcaprep:10;
unsigned mixsoc:6;
unsigned vfsoc:6;
unsigned maxvolt:4;
unsigned minvolt:4;
unsigned maxtemp:4;
unsigned mintemp:4;
unsigned maxchgcurr:4;
unsigned maxdischgcurr:4;
};
#pragma pack()
struct max77779_fg_chip {
struct device *dev;
struct i2c_client *primary;
struct i2c_client *secondary;
int gauge_type; /* -1 not present, 0=max1720x, 1=max1730x */
struct max17x0x_regmap regmap;
struct max17x0x_regmap regmap_debug;
struct power_supply *psy;
struct delayed_work init_work;
struct device_node *batt_node;
u16 devname;
/* config */
void *model_data;
struct mutex model_lock;
struct delayed_work model_work;
int model_next_update;
/* also used to restore model state from permanent storage */
u16 reg_prop_capacity_raw;
bool model_state_valid; /* state read from persistent */
int model_reload;
bool model_ok; /* model is running */
int fake_battery;
u16 RSense;
u16 RConfig;
int batt_id;
int batt_id_defer_cnt;
int cycle_count;
int cycle_count_offset;
u16 eeprom_cycle;
bool init_complete;
bool resume_complete;
bool irq_disabled;
u16 health_status;
int fake_capacity;
int previous_qh;
int current_capacity;
int prev_charge_status;
char serial_number[30];
bool offmode_charger;
bool por;
unsigned int debug_irq_none_cnt;
/* Capacity Estimation */
struct gbatt_capacity_estimation cap_estimate;
struct logbuffer *ce_log;
/* debug interface, register to read or write */
u32 debug_reg_address;
u32 debug_dbg_reg_address;
/* dump data to logbuffer periodically */
struct logbuffer *monitor_log;
u16 pre_repsoc;
struct power_supply_desc max77779_fg_psy_desc;
int bhi_fcn_count;
int bhi_acim;
/* battery current criteria for report status charge */
u32 status_charge_threshold_ma;
};
static irqreturn_t max77779_fg_irq_thread_fn(int irq, void *obj);
static int max77779_fg_set_next_update(struct max77779_fg_chip *chip);
static int max77779_fg_monitor_log_data(struct max77779_fg_chip *chip, bool force_log);
static bool max77779_fg_reglog_init(struct max77779_fg_chip *chip)
{
chip->regmap.reglog = devm_kzalloc(chip->dev, sizeof(*chip->regmap.reglog), GFP_KERNEL);
return chip->regmap.reglog;
}
/* TODO: b/285191823 - Validate all conversion helper functions */
/* ------------------------------------------------------------------------- */
static inline int reg_to_percentage(u16 val)
{
/* LSB: 1/256% */
return val >> 8;
}
static inline int reg_to_twos_comp_int(u16 val)
{
/* Convert u16 to twos complement */
return -(val & 0x8000) + (val & 0x7FFF);
}
static inline int reg_to_micro_amp(s16 val, u16 rsense)
{
/* LSB: 1.5625μV/RSENSE ; Rsense LSB is 2μΩ */
return div_s64((s64) val * 156250, rsense);
}
static inline int reg_to_deci_deg_cel(s16 val)
{
/* LSB: 1/256°C */
return div_s64((s64) val * 10, 256);
}
static inline int reg_to_resistance_micro_ohms(s16 val, u16 rsense)
{
/* LSB: 1/4096 Ohm */
return div_s64((s64) val * 1000 * rsense, 4096);
}
static inline int reg_to_cycles(u32 val)
{
/* LSB: 1% of one cycle */
return DIV_ROUND_CLOSEST(val, 100);
}
static inline int reg_to_seconds(s16 val)
{
/* LSB: 5.625 seconds */
return DIV_ROUND_CLOSEST((int) val * 5625, 1000);
}
static inline int reg_to_vempty(u16 val)
{
return ((val >> 7) & 0x1FF) * 20;
}
static inline int reg_to_vrecovery(u16 val)
{
return (val & 0x7F) * 40;
}
static inline int reg_to_capacity_uah(u16 val, struct max77779_fg_chip *chip)
{
return reg_to_micro_amp_h(val, chip->RSense, MAX77779_LSB);
}
static inline int reg_to_time_hr(u16 val, struct max77779_fg_chip *chip)
{
return (val * 32) / 10;
}
/* log ----------------------------------------------------------------- */
static int format_battery_history_entry(char *temp, int size, int page_size, u16 *line)
{
int length = 0, i;
for (i = 0; i < page_size; i++) {
length += scnprintf(temp + length,
size - length, "%04x ",
line[i]);
}
if (length > 0)
temp[--length] = 0;
return length;
}
/*
* Removed the following properties:
* POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG
* POWER_SUPPLY_PROP_TIME_TO_FULL_AVG
* POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
* POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
* Need to keep the number of properies under UEVENT_NUM_ENVP (minus # of
* standard uevent variables).
*/
static enum power_supply_property max77779_fg_battery_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_CAPACITY, /* replace with _RAW */
POWER_SUPPLY_PROP_CHARGE_COUNTER,
POWER_SUPPLY_PROP_CHARGE_FULL,
POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, /* used from gbattery */
POWER_SUPPLY_PROP_CURRENT_AVG, /* candidate for tier switch */
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_CYCLE_COUNT,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_TEMP,
POWER_SUPPLY_PROP_VOLTAGE_AVG,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_VOLTAGE_OCV,
POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_SERIAL_NUMBER,
};
/* ------------------------------------------------------------------------- */
static ssize_t offmode_charger_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct power_supply *psy = container_of(dev, struct power_supply, dev);
struct max77779_fg_chip *chip = power_supply_get_drvdata(psy);
return scnprintf(buf, PAGE_SIZE, "%hhd\n", chip->offmode_charger);
}
static ssize_t offmode_charger_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct power_supply *psy = container_of(dev, struct power_supply, dev);
struct max77779_fg_chip *chip = power_supply_get_drvdata(psy);
if (kstrtobool(buf, &chip->offmode_charger))
return -EINVAL;
return count;
}
static DEVICE_ATTR_RW(offmode_charger);
int max77779_fg_usr_lock(const struct max17x0x_regmap *map, bool enabled)
{
int ret, i;
const u16 code = enabled ? 0x111 : 0;
/* Requires write twice */
for (i = 0; i < 2; i++) {
ret = REGMAP_WRITE(map, MAX77779_FG_USR, code);
if (ret)
return ret;
}
return 0;
}
/* TODO: b/283487421 - Add NDGB reg write function */
int max77779_fg_register_write(const struct max17x0x_regmap *map,
unsigned int reg, u16 value, bool verify)
{
int ret;
/* TODO: b/285938678 - Lock/unlock specific register areas around transactions */
ret = max77779_fg_usr_lock(map, false);
if (ret) {
pr_err("Failed to unlock ret=%d\n", ret);
return ret;
}
if (verify)
ret = REGMAP_WRITE_VERIFY(map, reg, value);
else
ret = REGMAP_WRITE(map, reg, value);
if (ret) {
pr_err("Failed to write reg verify=%d ret=%d\n", verify, ret);
max77779_fg_usr_lock(map, true);
return ret;
}
ret = max77779_fg_usr_lock(map, true);
if (ret)
pr_err("Failed to lock ret=%d\n", ret);
return ret;
}
/*
* force is true when changing the model via debug props.
* NOTE: call holding model_lock
*/
static int max77779_fg_model_reload(struct max77779_fg_chip *chip, bool force)
{
const bool disabled = chip->model_reload == MAX77779_LOAD_MODEL_DISABLED;
const bool pending = chip->model_reload != MAX77779_LOAD_MODEL_IDLE;
int version_now, version_load;
pr_debug("model_reload=%d force=%d pending=%d disabled=%d\n",
chip->model_reload, force, pending, disabled);
if (!force && (pending || disabled))
return -EEXIST;
version_now = max77779_model_read_version(chip->model_data);
version_load = max77779_fg_model_version(chip->model_data);
if (!force && version_now == version_load)
return -EEXIST;
/* REQUEST -> IDLE or set to the number of retries */
dev_info(chip->dev, "Schedule Load FG Model, ID=%d, ver:%d->%d\n",
chip->batt_id, version_now, version_load);
chip->model_reload = MAX77779_LOAD_MODEL_REQUEST;
chip->model_ok = false;
mod_delayed_work(system_wq, &chip->model_work, 0);
return 0;
}
/* resistance and impedance ------------------------------------------------ */
static int max77779_fg_read_resistance_avg(struct max77779_fg_chip *chip)
{
u16 ravg;
int ret = 0;
ret = gbms_storage_read(GBMS_TAG_RAVG, &ravg, sizeof(ravg));
if (ret < 0)
return ret;
return reg_to_resistance_micro_ohms(ravg, chip->RSense);
}
static int max77779_fg_read_resistance_raw(struct max77779_fg_chip *chip)
{
u16 data;
int ret;
ret = REGMAP_READ(&chip->regmap, MAX77779_FG_RSlow, &data);
if (ret < 0)
return ret;
return data;
}
static int max77779_fg_read_resistance(struct max77779_fg_chip *chip)
{
int rslow;
rslow = max77779_fg_read_resistance_raw(chip);
if (rslow < 0)
return rslow;
return reg_to_resistance_micro_ohms(rslow, chip->RSense);
}
/* ----------------------------------------------------------------------- */
static ssize_t max77779_fg_model_state_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct power_supply *psy = container_of(dev, struct power_supply, dev);
struct max77779_fg_chip *chip = power_supply_get_drvdata(psy);
ssize_t len = 0;
if (!chip->model_data)
return -EINVAL;
mutex_lock(&chip->model_lock);
len += scnprintf(&buf[len], PAGE_SIZE, "ModelNextUpdate: %d\n",
chip->model_next_update);
len += max77779_model_state_cstr(&buf[len], PAGE_SIZE - len,
chip->model_data);
mutex_unlock(&chip->model_lock);
return len;
}
static ssize_t gmsr_show(struct device *dev,
struct device_attribute *attr,
char *buff)
{
struct power_supply *psy = container_of(dev, struct power_supply, dev);
struct max77779_fg_chip *chip = power_supply_get_drvdata(psy);
ssize_t len = 0;
mutex_lock(&chip->model_lock);
len = max77779_gmsr_state_cstr(&buff[len], PAGE_SIZE);
mutex_unlock(&chip->model_lock);
return len;
}
static DEVICE_ATTR_RO(gmsr);
/* Was POWER_SUPPLY_PROP_RESISTANCE_ID */
static ssize_t resistance_id_show(struct device *dev,
struct device_attribute *attr,
char *buff)
{
struct power_supply *psy = container_of(dev, struct power_supply, dev);
struct max77779_fg_chip *chip = power_supply_get_drvdata(psy);
return scnprintf(buff, PAGE_SIZE, "%d\n", chip->batt_id);
}
static DEVICE_ATTR_RO(resistance_id);
/* Was POWER_SUPPLY_PROP_RESISTANCE */
static ssize_t resistance_show(struct device *dev,
struct device_attribute *attr,
char *buff)
{
struct power_supply *psy = container_of(dev, struct power_supply, dev);
struct max77779_fg_chip *chip = power_supply_get_drvdata(psy);
return scnprintf(buff, PAGE_SIZE, "%d\n", max77779_fg_read_resistance(chip));
}
static DEVICE_ATTR_RO(resistance);
/* lsb 1/256, race with max77779_fg_model_work() */
static int max77779_fg_get_capacity_raw(struct max77779_fg_chip *chip, u16 *data)
{
return REGMAP_READ(&chip->regmap, chip->reg_prop_capacity_raw, data);
}
static int max77779_fg_get_battery_soc(struct max77779_fg_chip *chip)
{
u16 data;
int capacity, err;
if (chip->fake_capacity >= 0 && chip->fake_capacity <= 100)
return chip->fake_capacity;
err = REGMAP_READ(&chip->regmap, MAX77779_FG_RepSOC, &data);
if (err)
return err;
capacity = reg_to_percentage(data);
if (capacity == 100 && chip->offmode_charger)
chip->fake_capacity = 100;
return capacity;
}
static int max77779_fg_get_battery_vfsoc(struct max77779_fg_chip *chip)
{
u16 data;
int capacity, err;
err = REGMAP_READ(&chip->regmap, MAX77779_FG_VFSOC, &data);
if (err)
return err;
capacity = reg_to_percentage(data);
return capacity;
}
static void max77779_fg_prime_battery_qh_capacity(struct max77779_fg_chip *chip,
int status)
{
u16 mcap = 0, data = 0;
(void)REGMAP_READ(&chip->regmap, MAX77779_FG_MixCap, &mcap);
chip->current_capacity = mcap;
(void)REGMAP_READ(&chip->regmap, MAX77779_FG_QH, &data);
chip->previous_qh = reg_to_twos_comp_int(data);
}
/* NOTE: the gauge doesn't know if we are current limited to */
static int max77779_fg_get_battery_status(struct max77779_fg_chip *chip)
{
u16 data = 0;
int current_now, current_avg, ichgterm, vfsoc, soc, fullsocthr;
int status = POWER_SUPPLY_STATUS_UNKNOWN, err;
err = REGMAP_READ(&chip->regmap, MAX77779_FG_Current, &data);
if (err)
return -EIO;
current_now = -reg_to_micro_amp(data, chip->RSense);
err = REGMAP_READ(&chip->regmap, MAX77779_FG_AvgCurrent, &data);
if (err)
return -EIO;
current_avg = -reg_to_micro_amp(data, chip->RSense);
if (chip->status_charge_threshold_ma) {
ichgterm = chip->status_charge_threshold_ma * 1000;
} else {
err = REGMAP_READ(&chip->regmap, MAX77779_FG_IChgTerm, &data);
if (err)
return -EIO;
ichgterm = reg_to_micro_amp(data, chip->RSense);
}
err = REGMAP_READ(&chip->regmap, MAX77779_FG_FullSocThr, &data);
if (err)
return -EIO;
fullsocthr = reg_to_percentage(data);
soc = max77779_fg_get_battery_soc(chip);
if (soc < 0)
return -EIO;
vfsoc = max77779_fg_get_battery_vfsoc(chip);
if (vfsoc < 0)
return -EIO;
if (current_avg > -ichgterm && current_avg <= 0) {
if (soc >= fullsocthr) {
const bool needs_prime = (chip->prev_charge_status ==
POWER_SUPPLY_STATUS_CHARGING);
status = POWER_SUPPLY_STATUS_FULL;
if (needs_prime)
max77779_fg_prime_battery_qh_capacity(chip,
status);
} else {
status = POWER_SUPPLY_STATUS_NOT_CHARGING;
}
} else if (current_now >= -ichgterm) {
status = POWER_SUPPLY_STATUS_DISCHARGING;
} else {
status = POWER_SUPPLY_STATUS_CHARGING;
if (chip->prev_charge_status == POWER_SUPPLY_STATUS_DISCHARGING
&& current_avg < -ichgterm)
max77779_fg_prime_battery_qh_capacity(chip, status);
}
if (status != chip->prev_charge_status)
dev_dbg(chip->dev, "s=%d->%d c=%d avg_c=%d ichgt=%d vfsoc=%d soc=%d fullsocthr=%d\n",
chip->prev_charge_status,
status, current_now, current_avg,
ichgterm, vfsoc, soc, fullsocthr);
chip->prev_charge_status = status;
return status;
}
static int max77779_fg_get_battery_health(struct max77779_fg_chip *chip)
{
/* For health report what ever was recently alerted and clear it */
/* TODO: 284191042 - Remove this and move to chargin stats */
if (chip->health_status & MAX77779_FG_Status_Vmx_MASK) {
chip->health_status &= MAX77779_FG_Status_Vmx_CLEAR;
return POWER_SUPPLY_HEALTH_OVERVOLTAGE;
}
if ((chip->health_status & MAX77779_FG_Status_Tmn_MASK) &&
(chip->RConfig & MAX77779_FG_Config_TS_MASK)) {
chip->health_status &= MAX77779_FG_Status_Tmn_CLEAR;
return POWER_SUPPLY_HEALTH_COLD;
}
if ((chip->health_status & MAX77779_FG_Status_Tmx_MASK) &&
(chip->RConfig & MAX77779_FG_Config_TS_MASK)) {
chip->health_status &= MAX77779_FG_Status_Tmx_CLEAR;
return POWER_SUPPLY_HEALTH_HOT;
}
return POWER_SUPPLY_HEALTH_GOOD;
}
static int max77779_fg_update_battery_qh_based_capacity(struct max77779_fg_chip *chip)
{
u16 data;
int current_qh, err = 0;
err = REGMAP_READ(&chip->regmap, MAX77779_FG_QH, &data);
if (err)
return err;
current_qh = reg_to_twos_comp_int(data);
/* QH value accumulates as battery charges */
chip->current_capacity -= (chip->previous_qh - current_qh);
chip->previous_qh = current_qh;
return 0;
}
#define EEPROM_CC_OVERFLOW_BIT BIT(15)
#define MAXIM_CYCLE_COUNT_RESET 655
static void max77779_fg_restore_battery_cycle(struct max77779_fg_chip *chip)
{
int ret = 0;
u16 eeprom_cycle, reg_cycle;
ret = REGMAP_READ(&chip->regmap, MAX77779_FG_Cycles, &reg_cycle);
if (ret < 0) {
dev_info(chip->dev, "Fail to read reg %#x (%d)",
MAX77779_FG_Cycles, ret);
return;
}
ret = gbms_storage_read(GBMS_TAG_CNHS, &chip->eeprom_cycle,
sizeof(chip->eeprom_cycle));
if (ret < 0) {
dev_info(chip->dev, "Fail to read eeprom cycle count (%d)", ret);
return;
}
if (chip->eeprom_cycle == 0xFFFF) { /* empty storage */
reg_cycle /= 2; /* save half value to record over 655 cycles case */
ret = gbms_storage_write(GBMS_TAG_CNHS, &reg_cycle, sizeof(reg_cycle));
if (ret < 0)
dev_info(chip->dev, "Fail to write eeprom cycle (%d)", ret);
else
chip->eeprom_cycle = reg_cycle;
return;
}
if (chip->eeprom_cycle & EEPROM_CC_OVERFLOW_BIT)
chip->cycle_count_offset = MAXIM_CYCLE_COUNT_RESET;
eeprom_cycle = (chip->eeprom_cycle & 0x7FFF) << 1;
dev_info(chip->dev, "reg_cycle:%d, eeprom_cycle:%d, update:%c",
reg_cycle, eeprom_cycle, eeprom_cycle > reg_cycle ? 'Y' : 'N');
if (eeprom_cycle > reg_cycle) {
ret = MAX77779_FG_REGMAP_WRITE_VERIFY(&chip->regmap, MAX77779_FG_Cycles,
eeprom_cycle);
if (ret < 0)
dev_warn(chip->dev, "fail to update cycles (%d)", ret);
}
}
static u16 max77779_fg_save_battery_cycle(const struct max77779_fg_chip *chip,
u16 reg_cycle)
{
int ret = 0;
u16 eeprom_cycle = chip->eeprom_cycle;
if (chip->por || reg_cycle == 0)
return eeprom_cycle;
/* save half value to record over 655 cycles case */
reg_cycle /= 2;
/* Over 655 cycles */
if (reg_cycle < eeprom_cycle)
reg_cycle |= EEPROM_CC_OVERFLOW_BIT;
if (reg_cycle <= eeprom_cycle)
return eeprom_cycle;
ret = gbms_storage_write(GBMS_TAG_CNHS, &reg_cycle,
sizeof(reg_cycle));
if (ret < 0) {
dev_info(chip->dev, "Fail to write %d eeprom cycle count (%d)", reg_cycle, ret);
} else {
dev_info(chip->dev, "update saved cycle:%d -> %d\n", eeprom_cycle, reg_cycle);
eeprom_cycle = reg_cycle;
}
return eeprom_cycle;
}
#define MAX17201_HIST_CYCLE_COUNT_OFFSET 0x4
#define MAX17201_HIST_TIME_OFFSET 0xf
/* WA for cycle count reset.
* max17201 fuel gauge rolls over the cycle count to 0 and burns
* an history entry with 0 cycles when the cycle count exceeds
* 655. This code workaround the issue adding 655 to the cycle
* count if the fuel gauge history has an entry with 0 cycles and
* non 0 time-in-field.
*/
static int max77779_fg_get_cycle_count_offset(struct max77779_fg_chip *chip)
{
int offset = 0;
if (chip->eeprom_cycle & EEPROM_CC_OVERFLOW_BIT)
offset = MAXIM_CYCLE_COUNT_RESET;
return offset;
}
static int max77779_fg_get_cycle_count(struct max77779_fg_chip *chip)
{
int err, cycle_count;
u16 reg_cycle;
/*
* Corner case: battery under 3V hit POR without irq.
* cycles reset in this situation, incorrect data
*/
if (chip->por)
return -ECANCELED;
err = REGMAP_READ(&chip->regmap, MAX77779_FG_Cycles, &reg_cycle);
if (err < 0)
return err;
cycle_count = reg_to_cycles((u32)reg_cycle);
if ((chip->cycle_count == -1) ||
((cycle_count + chip->cycle_count_offset) < chip->cycle_count))
chip->cycle_count_offset =
max77779_fg_get_cycle_count_offset(chip);
chip->eeprom_cycle = max77779_fg_save_battery_cycle(chip, reg_cycle);
chip->cycle_count = cycle_count + chip->cycle_count_offset;
if (chip->model_ok && reg_cycle >= chip->model_next_update) {
err = max77779_fg_set_next_update(chip);
if (err < 0)
dev_err(chip->dev, "%s cannot set next update (%d)\n",
__func__, err);
}
return chip->cycle_count;
}
/* TODO: 284191528 - Add these batt_ce functions to common max file */
/* Capacity Estimation functions*/
static int batt_ce_regmap_read(struct max17x0x_regmap *map,
const struct max17x0x_reg *bcea,
u32 reg, u16 *data)
{
int err;
u16 val;
if (!bcea)
return -EINVAL;
err = REGMAP_READ(map, bcea->map[reg], &val);
if (err)
return err;
switch(reg) {
case CE_DELTA_CC_SUM_REG:
case CE_DELTA_VFSOC_SUM_REG:
*data = val;
break;
case CE_CAP_FILTER_COUNT:
val = val & 0x0F00;
*data = val >> 8;
break;
default:
break;
}
return err;
}
static int batt_ce_regmap_write(struct max17x0x_regmap *map,
const struct max17x0x_reg *bcea,
u32 reg, u16 data)
{
int err = -EINVAL;
u16 val;
if (!bcea)
return -EINVAL;
switch(reg) {
case CE_DELTA_CC_SUM_REG:
case CE_DELTA_VFSOC_SUM_REG:
err = MAX77779_FG_REGMAP_WRITE(map, bcea->map[reg], data);
break;
case CE_CAP_FILTER_COUNT:
err = REGMAP_READ(map, bcea->map[reg], &val);
if (err)
return err;
val = val & 0xF0FF;
if (data > CE_FILTER_COUNT_MAX)
val = val | 0x0F00;
else
val = val | (data << 8);
err = MAX77779_FG_REGMAP_WRITE(map, bcea->map[reg], val);
break;
default:
break;
}
return err;
}
static void batt_ce_dump_data(const struct gbatt_capacity_estimation *cap_esti,
struct logbuffer *log)
{
logbuffer_log(log, "cap_filter_count: %d"
" start_cc: %d"
" start_vfsoc: %d"
" delta_cc_sum: %d"
" delta_vfsoc_sum: %d"
" state: %d"
" cable: %d",
cap_esti->cap_filter_count,
cap_esti->start_cc,
cap_esti->start_vfsoc,
cap_esti->delta_cc_sum,
cap_esti->delta_vfsoc_sum,
cap_esti->estimate_state,
cap_esti->cable_in);
}
static int batt_ce_load_data(struct max17x0x_regmap *map,
struct gbatt_capacity_estimation *cap_esti)
{
u16 data;
const struct max17x0x_reg *bcea = cap_esti->bcea;
cap_esti->estimate_state = ESTIMATE_NONE;
if (batt_ce_regmap_read(map, bcea, CE_DELTA_CC_SUM_REG, &data) == 0)
cap_esti->delta_cc_sum = data;
else
cap_esti->delta_cc_sum = 0;
if (batt_ce_regmap_read(map, bcea, CE_DELTA_VFSOC_SUM_REG, &data) == 0)
cap_esti->delta_vfsoc_sum = data;
else
cap_esti->delta_vfsoc_sum = 0;
if (batt_ce_regmap_read(map, bcea, CE_CAP_FILTER_COUNT, &data) == 0)
cap_esti->cap_filter_count = data;
else
cap_esti->cap_filter_count = 0;
return 0;
}
/* call holding &cap_esti->batt_ce_lock */
static void batt_ce_store_data(struct max17x0x_regmap *map,
struct gbatt_capacity_estimation *cap_esti)
{
if (cap_esti->cap_filter_count <= CE_FILTER_COUNT_MAX) {
batt_ce_regmap_write(map, cap_esti->bcea,
CE_CAP_FILTER_COUNT,
cap_esti->cap_filter_count);
}
batt_ce_regmap_write(map, cap_esti->bcea,
CE_DELTA_VFSOC_SUM_REG,
cap_esti->delta_vfsoc_sum);
batt_ce_regmap_write(map, cap_esti->bcea,
CE_DELTA_CC_SUM_REG,
cap_esti->delta_cc_sum);
}
/* call holding &cap_esti->batt_ce_lock */
static void batt_ce_stop_estimation(struct gbatt_capacity_estimation *cap_esti,
int reason)
{
cap_esti->estimate_state = reason;
cap_esti->start_vfsoc = 0;
cap_esti->start_cc = 0;
}
static int batt_ce_full_estimate(struct gbatt_capacity_estimation *ce)
{
return (ce->cap_filter_count > 0) && (ce->delta_vfsoc_sum > 0) ?
ce->delta_cc_sum / ce->delta_vfsoc_sum : -1;
}
/* Measure the deltaCC, deltaVFSOC and CapacityFiltered */
static void batt_ce_capacityfiltered_work(struct work_struct *work)
{
struct max77779_fg_chip *chip = container_of(work, struct max77779_fg_chip,
cap_estimate.settle_timer.work);
struct gbatt_capacity_estimation *cap_esti = &chip->cap_estimate;
int settle_cc = 0, settle_vfsoc = 0;
int delta_cc = 0, delta_vfsoc = 0;
int cc_sum = 0, vfsoc_sum = 0;
bool valid_estimate = false;
int rc = 0;
int data;
mutex_lock(&cap_esti->batt_ce_lock);
/* race with disconnect */
if (!cap_esti->cable_in ||
cap_esti->estimate_state != ESTIMATE_PENDING) {
goto exit;
}
rc = max77779_fg_update_battery_qh_based_capacity(chip);
if (rc < 0)
goto ioerr;
settle_cc = reg_to_micro_amp_h(chip->current_capacity, chip->RSense, MAX77779_LSB);
data = max77779_fg_get_battery_vfsoc(chip);
if (data < 0)
goto ioerr;
settle_vfsoc = data;
settle_cc = settle_cc / 1000;
delta_cc = settle_cc - cap_esti->start_cc;
delta_vfsoc = settle_vfsoc - cap_esti->start_vfsoc;
if ((delta_cc > 0) && (delta_vfsoc > 0)) {
cc_sum = delta_cc + cap_esti->delta_cc_sum;
vfsoc_sum = delta_vfsoc + cap_esti->delta_vfsoc_sum;
if (cap_esti->cap_filter_count >= cap_esti->cap_filt_length) {
const int filter_divisor = cap_esti->cap_filt_length;
cc_sum -= cap_esti->delta_cc_sum/filter_divisor;
vfsoc_sum -= cap_esti->delta_vfsoc_sum/filter_divisor;
}
cap_esti->cap_filter_count++;
cap_esti->delta_cc_sum = cc_sum;
cap_esti->delta_vfsoc_sum = vfsoc_sum;
/* batt_ce_store_data(&chip->regmap_nvram, &chip->cap_estimate); */
valid_estimate = true;
}
ioerr:
batt_ce_stop_estimation(cap_esti, ESTIMATE_DONE);
exit:
logbuffer_log(chip->ce_log,
"valid=%d settle[cc=%d, vfsoc=%d], delta[cc=%d,vfsoc=%d] ce[%d]=%d",
valid_estimate,
settle_cc, settle_vfsoc, delta_cc, delta_vfsoc,
cap_esti->cap_filter_count,
batt_ce_full_estimate(cap_esti));
mutex_unlock(&cap_esti->batt_ce_lock);
/* force to update uevent to framework side. */
if (valid_estimate)
power_supply_changed(chip->psy);
}
/*
* batt_ce_init(): estimate_state = ESTIMATE_NONE
* batt_ce_start(): estimate_state = ESTIMATE_NONE -> ESTIMATE_PENDING
* batt_ce_capacityfiltered_work(): ESTIMATE_PENDING->ESTIMATE_DONE
*/
static int batt_ce_start(struct gbatt_capacity_estimation *cap_esti,
int cap_tsettle_ms)
{
mutex_lock(&cap_esti->batt_ce_lock);
/* Still has cable and estimate is not pending or cancelled */
if (!cap_esti->cable_in || cap_esti->estimate_state != ESTIMATE_NONE)
goto done;
pr_info("EOC: Start the settle timer\n");
cap_esti->estimate_state = ESTIMATE_PENDING;
schedule_delayed_work(&cap_esti->settle_timer,
msecs_to_jiffies(cap_tsettle_ms));
done:
mutex_unlock(&cap_esti->batt_ce_lock);
return 0;
}
static int batt_ce_init(struct gbatt_capacity_estimation *cap_esti,
struct max77779_fg_chip *chip)
{
int rc, vfsoc;
rc = max77779_fg_update_battery_qh_based_capacity(chip);
if (rc < 0)
return -EIO;
vfsoc = max77779_fg_get_battery_vfsoc(chip);
if (vfsoc < 0)
return -EIO;
cap_esti->start_vfsoc = vfsoc;
cap_esti->start_cc = reg_to_micro_amp_h(chip->current_capacity,
chip->RSense, MAX77779_LSB) / 1000;
/* Capacity Estimation starts only when the state is NONE */
cap_esti->estimate_state = ESTIMATE_NONE;
return 0;
}
/* TODO b/284191528 - Add to common code file */
/* ------------------------------------------------------------------------- */
static int max77779_fg_health_write_ai(u16 act_impedance, u16 act_timerh)
{
int ret;
ret = gbms_storage_write(GBMS_TAG_ACIM, &act_impedance, sizeof(act_impedance));
if (ret < 0)
return -EIO;
ret = gbms_storage_write(GBMS_TAG_THAS, &act_timerh, sizeof(act_timerh));
if (ret < 0)
return -EIO;
return ret;
}
/* TODO b/284191528 - Add to common driver */
/* call holding chip->model_lock */
static int max77779_fg_check_impedance(struct max77779_fg_chip *chip, u16 *th)
{
struct max17x0x_regmap *map = &chip->regmap;
int soc, temp, cycle_count, ret;
u16 data, timerh;
if (!chip->model_state_valid)
return -EAGAIN;
soc = max77779_fg_get_battery_soc(chip);
if (soc < BHI_IMPEDANCE_SOC_LO || soc > BHI_IMPEDANCE_SOC_HI)
return -EAGAIN;
ret = REGMAP_READ(map, MAX77779_FG_Temp, &data);
if (ret < 0)
return -EIO;
temp = reg_to_deci_deg_cel(data);
if (temp < BHI_IMPEDANCE_TEMP_LO || temp > BHI_IMPEDANCE_TEMP_HI)
return -EAGAIN;
cycle_count = max77779_fg_get_cycle_count(chip);
if (cycle_count < 0)
return -EINVAL;
ret = REGMAP_READ(&chip->regmap, MAX77779_FG_TimerH, &timerh);
if (ret < 0 || timerh == 0)
return -EINVAL;
/* wait for a few cyles and time in field before validating the value */
if (cycle_count < BHI_IMPEDANCE_CYCLE_CNT || timerh < BHI_IMPEDANCE_TIMERH)
return -ENODATA;
*th = timerh;
return 0;
}
/* TODO b/284191528 - Add to common code file */
/* will return error if the value is not valid */
static int max77779_fg_health_get_ai(struct max77779_fg_chip *chip)
{
u16 act_impedance, act_timerh;
int ret;
if (chip->bhi_acim != 0)
return chip->bhi_acim;
/* read both and recalculate for compatibility */
ret = gbms_storage_read(GBMS_TAG_ACIM, &act_impedance, sizeof(act_impedance));
if (ret < 0)
return -EIO;
ret = gbms_storage_read(GBMS_TAG_THAS, &act_timerh, sizeof(act_timerh));
if (ret < 0)
return -EIO;
/* need to get starting impedance (if qualified) */
if (act_impedance == 0xffff || act_timerh == 0xffff)
return -EINVAL;
/* not zero, not negative */
chip->bhi_acim = reg_to_resistance_micro_ohms(act_impedance, chip->RSense);;
/* TODO: corrrect impedance with timerh */
dev_info(chip->dev, "%s: chip->bhi_acim =%d act_impedance=%x act_timerh=%x\n",
__func__, chip->bhi_acim, act_impedance, act_timerh);
return chip->bhi_acim;
}
/* TODO b/284191528 - Add to common code file */
/* will return negative if the value is not qualified */
static int max77779_fg_health_read_impedance(struct max77779_fg_chip *chip)
{
u16 timerh;
int ret;
ret = max77779_fg_check_impedance(chip, &timerh);
if (ret < 0)
return -EINVAL;
return max77779_fg_read_resistance(chip);
}
/* in hours */
static int max77779_fg_get_age(struct max77779_fg_chip *chip)
{
u16 timerh;
int ret;
ret = REGMAP_READ(&chip->regmap, MAX77779_FG_TimerH, &timerh);
if (ret < 0 || timerh == 0)
return -ENODATA;
return reg_to_time_hr(timerh, chip);
}
/* TODO b/284191528 - Add to common code file */
#define MAX_HIST_FULLCAP 0x3FF
static int max77779_fg_get_fade_rate(struct max77779_fg_chip *chip)
{
struct max77779_fg_eeprom_history hist = { 0 };
int bhi_fcn_count = chip->bhi_fcn_count;
int ret, ratio, i, fcn_sum = 0;
u16 hist_idx;
ret = gbms_storage_read(GBMS_TAG_HCNT, &hist_idx, sizeof(hist_idx));
if (ret < 0) {
dev_err(chip->dev, "failed to get history index (%d)\n", ret);
return -EIO;
}
dev_info(chip->dev, "%s: hist_idx=%d\n", __func__, hist_idx);
/* no fade for new battery (less than 30 cycles) */
if (hist_idx < bhi_fcn_count)
return 0;
while (hist_idx >= BATT_MAX_HIST_CNT && bhi_fcn_count > 1) {
hist_idx--;
bhi_fcn_count--;
if (bhi_fcn_count == 1) {
hist_idx = BATT_MAX_HIST_CNT - 1;
break;
}
}
for (i = bhi_fcn_count; i ; i--, hist_idx--) {
ret = gbms_storage_read_data(GBMS_TAG_HIST, &hist,
sizeof(hist), hist_idx);
dev_info(chip->dev, "%s: idx=%d hist.fc=%d (%x) ret=%d\n", __func__,
hist_idx, hist.fullcapnom, hist.fullcapnom, ret);
if (ret < 0)
return -EINVAL;
/* hist.fullcapnom = fullcapnom * 800 / designcap */
fcn_sum += hist.fullcapnom;
}
/* convert from max77779_fg_eeprom_history to percent */
ratio = fcn_sum / (bhi_fcn_count * 8);
if (ratio > 100)
ratio = 100;
return 100 - ratio;
}
static int max77779_fg_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct max77779_fg_chip *chip = (struct max77779_fg_chip *)
power_supply_get_drvdata(psy);
struct max17x0x_regmap *map = &chip->regmap;
int rc, err = 0;
u16 data = 0;
int idata;
mutex_lock(&chip->model_lock);
pm_runtime_get_sync(chip->dev);
if (!chip->init_complete || !chip->resume_complete) {
pm_runtime_put_sync(chip->dev);
mutex_unlock(&chip->model_lock);
return -EAGAIN;
}
pm_runtime_put_sync(chip->dev);
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
err = max77779_fg_get_battery_status(chip);
if (err < 0)
break;
/*
* Capacity estimation must run only once.
* NOTE: this is a getter with a side effect
*/
val->intval = err;
if (err == POWER_SUPPLY_STATUS_FULL)
batt_ce_start(&chip->cap_estimate,
chip->cap_estimate.cap_tsettle);
/* return data ok */
err = 0;
break;
case POWER_SUPPLY_PROP_HEALTH:
val->intval = max77779_fg_get_battery_health(chip);
break;
case GBMS_PROP_CAPACITY_RAW:
err = max77779_fg_get_capacity_raw(chip, &data);
if (err == 0)
val->intval = (int)data;
break;
case POWER_SUPPLY_PROP_CAPACITY:
idata = max77779_fg_get_battery_soc(chip);
if (idata < 0) {
err = idata;
break;
}
val->intval = idata;
break;
case POWER_SUPPLY_PROP_CHARGE_COUNTER:
err = max77779_fg_update_battery_qh_based_capacity(chip);
if (err < 0)
break;
val->intval = reg_to_capacity_uah(chip->current_capacity, chip);
break;
case POWER_SUPPLY_PROP_CHARGE_FULL:
/*
* Snap charge_full to DESIGNCAP during early charge cycles to
* prevent large fluctuations in FULLCAPNOM. MAX77779_FG_Cycles LSB
* is 1%
*/
err = max77779_fg_get_cycle_count(chip);
if (err < 0)
break;
/* err is cycle_count */
if (err <= FULLCAPNOM_STABILIZE_CYCLES)
err = REGMAP_READ(map, MAX77779_FG_DesignCap, &data);
else
err = REGMAP_READ(map, MAX77779_FG_FullCapNom, &data);
if (err == 0)
val->intval = reg_to_capacity_uah(data, chip);
break;
case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
err = REGMAP_READ(map, MAX77779_FG_DesignCap, &data);
if (err == 0)
val->intval = reg_to_capacity_uah(data, chip);
break;
/* current is positive value when flowing to device */
case POWER_SUPPLY_PROP_CURRENT_AVG:
err = REGMAP_READ(map, MAX77779_FG_AvgCurrent, &data);
if (err == 0)
val->intval = -reg_to_micro_amp(data, chip->RSense);
break;
/* current is positive value when flowing to device */
case POWER_SUPPLY_PROP_CURRENT_NOW:
err = REGMAP_READ(map, MAX77779_FG_Current, &data);
if (err == 0)
val->intval = -reg_to_micro_amp(data, chip->RSense);
break;
case POWER_SUPPLY_PROP_CYCLE_COUNT:
err = max77779_fg_get_cycle_count(chip);
if (err < 0)
break;
/* err is cycle_count */
val->intval = err;
/* return data ok */
err = 0;
break;
case POWER_SUPPLY_PROP_PRESENT:
if (chip->fake_battery != -1) {
val->intval = chip->fake_battery;
} else if (chip->gauge_type == -1) {
val->intval = 0;
} else {
err = REGMAP_READ(map, MAX77779_FG_Status, &data);
if (err < 0)
break;
/* BST is 0 when the battery is present */
val->intval = !(data & MAX77779_FG_Status_Bst_MASK);
if (!val->intval)
break;
/* chip->por prevent garbage in cycle count */
chip->por = (data & MAX77779_FG_Status_PONR_MASK) != 0;
if (chip->por && chip->model_ok &&
chip->model_reload != MAX77779_LOAD_MODEL_REQUEST) {
/* trigger reload model and clear of POR */
mutex_unlock(&chip->model_lock);
max77779_fg_irq_thread_fn(-1, chip);
return err;
}
}
break;
case POWER_SUPPLY_PROP_TEMP:
err = REGMAP_READ(map, MAX77779_FG_Temp, &data);
if (err < 0)
break;
val->intval = reg_to_deci_deg_cel(data);
break;
case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG:
err = REGMAP_READ(map, MAX77779_FG_TTE, &data);
if (err == 0)
val->intval = reg_to_seconds(data);
break;
case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG:
err = REGMAP_READ(map, MAX77779_FG_TTF, &data);
if (err == 0)
val->intval = reg_to_seconds(data);
break;
case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW:
val->intval = -1;
break;
case POWER_SUPPLY_PROP_VOLTAGE_AVG:
err = REGMAP_READ(map, MAX77779_FG_AvgVCell, &data);
if (err == 0)
val->intval = reg_to_micro_volt(data);
break;
case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
/* LSB: 20mV */
err = REGMAP_READ(map, MAX77779_FG_MaxMinVolt, &data);
if (err == 0)
val->intval = ((data >> 8) & 0xFF) * 20000;
break;
case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
/* LSB: 20mV */
err = REGMAP_READ(map, MAX77779_FG_MaxMinVolt, &data);
if (err == 0)
val->intval = (data & 0xFF) * 20000;
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
err = REGMAP_READ(map, MAX77779_FG_VCell, &data);
if (err == 0)
val->intval = reg_to_micro_volt(data);
break;
case POWER_SUPPLY_PROP_VOLTAGE_OCV:
rc = REGMAP_READ(map, MAX77779_FG_VFOCV, &data);
if (rc == -EINVAL) {
val->intval = -1;
break;
}
val->intval = reg_to_micro_volt(data);
break;
case POWER_SUPPLY_PROP_TECHNOLOGY:
val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
break;
case POWER_SUPPLY_PROP_SERIAL_NUMBER:
val->strval = chip->serial_number;
break;
case GBMS_PROP_HEALTH_ACT_IMPEDANCE:
val->intval = max77779_fg_health_get_ai(chip);
break;
case GBMS_PROP_HEALTH_IMPEDANCE:
val->intval = max77779_fg_health_read_impedance(chip);
break;
case GBMS_PROP_RESISTANCE:
val->intval = max77779_fg_read_resistance(chip);
break;
case GBMS_PROP_RESISTANCE_RAW:
val->intval = max77779_fg_read_resistance_raw(chip);
break;
case GBMS_PROP_RESISTANCE_AVG:
val->intval = max77779_fg_read_resistance_avg(chip);
break;
case GBMS_PROP_BATTERY_AGE:
val->intval = max77779_fg_get_age(chip);
break;
case GBMS_PROP_CHARGE_FULL_ESTIMATE:
val->intval = batt_ce_full_estimate(&chip->cap_estimate);
break;
case GBMS_PROP_CAPACITY_FADE_RATE:
val->intval = max77779_fg_get_fade_rate(chip);
break;
default:
err = -EINVAL;
break;
}
if (err < 0)
pr_debug("error %d reading prop %d\n", err, psp);
mutex_unlock(&chip->model_lock);
return err;
}
/* needs mutex_lock(&chip->model_lock); */
static int max77779_fg_health_update_ai(struct max77779_fg_chip *chip, int impedance)
{
const u16 act_impedance = impedance / 100;
unsigned int rcell = 0xffff;
u16 timerh = 0xffff;
int ret;
if (impedance) {
/* mOhms to reg */
rcell = (impedance * 4096) / (1000 * chip->RSense);
if (rcell > 0xffff) {
pr_err("value=%d, rcell=%d out of bounds\n", impedance, rcell);
return -ERANGE;
}
ret = REGMAP_READ(&chip->regmap, MAX77779_FG_TimerH, &timerh);
if (ret < 0 || timerh == 0)
return -EIO;
}
ret = max77779_fg_health_write_ai(act_impedance, timerh);
if (ret == 0)
chip->bhi_acim = 0;
return ret;
}
static int max77779_fg_set_property(struct power_supply *psy,
enum power_supply_property psp,
const union power_supply_propval *val)
{
struct max77779_fg_chip *chip = (struct max77779_fg_chip *)
power_supply_get_drvdata(psy);
struct gbatt_capacity_estimation *ce = &chip->cap_estimate;
int rc = 0;
mutex_lock(&chip->model_lock);
pm_runtime_get_sync(chip->dev);
if (!chip->init_complete || !chip->resume_complete) {
pm_runtime_put_sync(chip->dev);
mutex_unlock(&chip->model_lock);
return -EAGAIN;
}
pm_runtime_put_sync(chip->dev);
mutex_unlock(&chip->model_lock);
switch (psp) {
case GBMS_PROP_BATT_CE_CTRL:
mutex_lock(&ce->batt_ce_lock);
if (!chip->model_state_valid) {
mutex_unlock(&ce->batt_ce_lock);
return -EAGAIN;
}
if (val->intval) {
if (!ce->cable_in) {
rc = batt_ce_init(ce, chip);
ce->cable_in = (rc == 0);
}
} else if (ce->cable_in) {
if (ce->estimate_state == ESTIMATE_PENDING)
cancel_delayed_work_sync(&ce->settle_timer);
/* race with batt_ce_capacityfiltered_work() */
batt_ce_stop_estimation(ce, ESTIMATE_NONE);
batt_ce_dump_data(ce, chip->ce_log);
ce->cable_in = false;
}
mutex_unlock(&ce->batt_ce_lock);
mod_delayed_work(system_wq, &chip->model_work, msecs_to_jiffies(351));
break;
case GBMS_PROP_HEALTH_ACT_IMPEDANCE:
mutex_lock(&chip->model_lock);
rc = max77779_fg_health_update_ai(chip, val->intval);
mutex_unlock(&chip->model_lock);
break;
case GBMS_PROP_FG_REG_LOGGING:
max77779_fg_monitor_log_data(chip, !!val->intval);
break;
default:
return -EINVAL;
}
if (rc < 0)
return rc;
return 0;
}
static int max77779_fg_property_is_writeable(struct power_supply *psy,
enum power_supply_property psp)
{
switch (psp) {
case GBMS_PROP_BATT_CE_CTRL:
case GBMS_PROP_HEALTH_ACT_IMPEDANCE:
return 1;
default:
break;
}
return 0;
}
/* TODO: b/284191528 - Add to common code file, take in array of registers as input */
static int max77779_fg_monitor_log_data(struct max77779_fg_chip *chip, bool force_log)
{
u16 data, repsoc, vfsoc, avcap, repcap, fullcap, fullcaprep;
u16 fullcapnom, qh0, qh, dqacc, dpacc, qresidual, fstat;
u16 learncfg, tempco, mixcap, vfremcap, vcell, ibat;
int ret = 0, charge_counter = -1;
ret = REGMAP_READ(&chip->regmap, MAX77779_FG_RepSOC, &data);
if (ret < 0)
return ret;
repsoc = (data >> 8) & 0x00FF;
if (repsoc == chip->pre_repsoc && !force_log)
return ret;
ret = REGMAP_READ(&chip->regmap, MAX77779_FG_VFSOC, &vfsoc);
if (ret < 0)
return ret;
ret = REGMAP_READ(&chip->regmap, MAX77779_FG_AvCap, &avcap);
if (ret < 0)
return ret;
ret = REGMAP_READ(&chip->regmap, MAX77779_FG_RepCap, &repcap);
if (ret < 0)
return ret;
ret = REGMAP_READ(&chip->regmap, MAX77779_FG_FullCap, &fullcap);
if (ret < 0)
return ret;
ret = REGMAP_READ(&chip->regmap, MAX77779_FG_FullCapRep, &fullcaprep);
if (ret < 0)
return ret;
ret = REGMAP_READ(&chip->regmap, MAX77779_FG_FullCapNom, &fullcapnom);
if (ret < 0)
return ret;
ret = REGMAP_READ(&chip->regmap, MAX77779_FG_QH0, &qh0);
if (ret < 0)
return ret;
ret = REGMAP_READ(&chip->regmap, MAX77779_FG_QH, &qh);
if (ret < 0)
return ret;
ret = REGMAP_READ(&chip->regmap, MAX77779_FG_dQAcc, &dqacc);
if (ret < 0)
return ret;
ret = REGMAP_READ(&chip->regmap, MAX77779_FG_dPAcc, &dpacc);
if (ret < 0)
return ret;
ret = REGMAP_READ(&chip->regmap, MAX77779_FG_QResidual, &qresidual);
if (ret < 0)
return ret;
ret = REGMAP_READ(&chip->regmap, MAX77779_FG_FStat, &fstat);
if (ret < 0)
return ret;
ret = REGMAP_READ(&chip->regmap, MAX77779_FG_LearnCfg, &learncfg);
if (ret < 0)
return ret;
ret = REGMAP_READ(&chip->regmap_debug, MAX77779_FG_DBG_nTempCo, &tempco);
if (ret < 0)
return ret;
ret = REGMAP_READ(&chip->regmap, MAX77779_FG_MixCap, &mixcap);
if (ret < 0)
return ret;
ret = REGMAP_READ(&chip->regmap, MAX77779_FG_VFRemCap, &vfremcap);
if (ret < 0)
return ret;
ret = REGMAP_READ(&chip->regmap, MAX77779_FG_VCell, &vcell);
if (ret < 0)
return ret;
ret = REGMAP_READ(&chip->regmap, MAX77779_FG_Current, &ibat);
if (ret < 0)
return ret;
ret = max77779_fg_update_battery_qh_based_capacity(chip);
if (ret == 0)
charge_counter = reg_to_capacity_uah(chip->current_capacity, chip);
gbms_logbuffer_prlog(chip->monitor_log, LOGLEVEL_INFO, 0, LOGLEVEL_INFO,
"%s %02X:%04X %02X:%04X %02X:%04X %02X:%04X %02X:%04X"
" %02X:%04X %02X:%04X %02X:%04X %02X:%04X %02X:%04X %02X:%04X"
" %02X:%04X %02X:%04X %02X:%04X %02X:%04X %02X:%04X %02X:%04X"
" %02X:%04X %02X:%04X CC:%d",
chip->max77779_fg_psy_desc.name, MAX77779_FG_RepSOC, data, MAX77779_FG_VFSOC,
vfsoc, MAX77779_FG_AvCap, avcap, MAX77779_FG_RepCap, repcap,
MAX77779_FG_FullCap, fullcap, MAX77779_FG_FullCapRep, fullcaprep,
MAX77779_FG_FullCapNom, fullcapnom, MAX77779_FG_QH0, qh0,
MAX77779_FG_QH, qh, MAX77779_FG_dQAcc, dqacc, MAX77779_FG_dPAcc, dpacc,
MAX77779_FG_QResidual, qresidual, MAX77779_FG_FStat, fstat,
MAX77779_FG_LearnCfg, learncfg, MAX77779_FG_DBG_nTempCo, tempco,
MAX77779_FG_MixCap, mixcap, MAX77779_FG_VFRemCap, vfremcap,
MAX77779_FG_VCell, vcell, MAX77779_FG_Current, ibat, charge_counter);
chip->pre_repsoc = repsoc;
return ret;
}
/*
* A full reset restores the ICs to their power-up state the same as if power
* had been cycled.
*/
static int max77779_fg_full_reset(struct max77779_fg_chip *chip)
{
/* TODO: b/283488742 - porting reset command */
/* REGMAP_WRITE(&chip->regmap, MAX17XXX_COMMAND,
max77779_fg_COMMAND_HARDWARE_RESET); */
msleep(MAX77779_FG_TPOR_MS);
return 0;
}
static irqreturn_t max77779_fg_irq_thread_fn(int irq, void *obj)
{
struct max77779_fg_chip *chip = (struct max77779_fg_chip *)obj;
u16 fg_status, fg_status_clr;
int err = 0;
if (!chip || (irq != -1 && irq != chip->primary->irq)) {
WARN_ON_ONCE(1);
return IRQ_NONE;
}
if (chip->gauge_type == -1)
return IRQ_NONE;
pm_runtime_get_sync(chip->dev);
if (!chip->init_complete || !chip->resume_complete) {
if (chip->init_complete && !chip->irq_disabled) {
chip->irq_disabled = true;
disable_irq_nosync(chip->primary->irq);
}
pm_runtime_put_sync(chip->dev);
return IRQ_HANDLED;
}
pm_runtime_put_sync(chip->dev);
err = REGMAP_READ(&chip->regmap, MAX77779_FG_Status, &fg_status);
if (err)
return IRQ_NONE;
if (fg_status == 0) {
/*
* Disable rate limiting for when interrupt is shared.
* NOTE: this might need to be re-evaluated at some later point
*/
return IRQ_NONE;
}
/* only used to report health */
chip->health_status |= fg_status;
/*
* write 0 to clear will loose interrupts when we don't write 1 to the
* bits that are not set. Oonly setting the bits marked as "host must clear"
* in the DS seems to work eg:
*
* fg_status_clr = fg_status
* fg_status_clr |= MAX77779_FG_Status_PONR_MASK | MAX77779_FG_Status_dSOCi_MASK
* | MAX77779_FG_Status_Bi_MASK;
*
* If the above logic is sound, we probably need to set also the bits
* that config mark as "host must clear". Maxim to confirm.
*/
fg_status_clr = fg_status;
if (fg_status & MAX77779_FG_Status_PONR_MASK) {
const bool no_battery = chip->fake_battery == 0;
mutex_lock(&chip->model_lock);
chip->por = true;
if (no_battery) {
fg_status_clr &= MAX77779_FG_Status_PONR_CLEAR;
} else {
dev_warn(chip->dev, "POR is set(%04x), model reload:%d\n",
fg_status, chip->model_reload);
/* trigger model load if not on-going */
if (chip->model_reload != MAX77779_LOAD_MODEL_REQUEST) {
/* TODO: implement model loading when spec ready b/271044091 */
/* err = max77779_fg_model_reload(chip, true);
if (err < 0) */
fg_status_clr &= MAX77779_FG_Status_PONR_CLEAR;
}
}
mutex_unlock(&chip->model_lock);
}
if (fg_status & MAX77779_FG_Status_Imn_MASK)
pr_debug("IMN is set\n");
if (fg_status & MAX77779_FG_Status_Bst_MASK)
pr_debug("BST is set\n");
if (fg_status & MAX77779_FG_Status_Imx_MASK)
pr_debug("IMX is set\n");
if (fg_status & MAX77779_FG_Status_dSOCi_MASK) {
fg_status_clr &= MAX77779_FG_Status_dSOCi_CLEAR;
pr_debug("DSOCI is set\n");
}
if (fg_status & MAX77779_FG_Status_Vmn_MASK) {
if (chip->RConfig & MAX77779_FG_Config_VS_MASK)
fg_status_clr &= MAX77779_FG_Status_Vmn_CLEAR;
pr_debug("VMN is set\n");
}
if (fg_status & MAX77779_FG_Status_Tmn_MASK) {
if (chip->RConfig & MAX77779_FG_Config_TS_MASK)
fg_status_clr &= MAX77779_FG_Status_Tmn_CLEAR;
pr_debug("TMN is set\n");
}
if (fg_status & MAX77779_FG_Status_Smn_MASK) {
if (chip->RConfig & MAX77779_FG_Config_SS_MASK)
fg_status_clr &= MAX77779_FG_Status_Smn_CLEAR;
pr_debug("SMN is set\n");
}
if (fg_status & MAX77779_FG_Status_Bi_MASK)
pr_debug("BI is set\n");
if (fg_status & MAX77779_FG_Status_Vmx_MASK) {
if (chip->RConfig & MAX77779_FG_Config_VS_MASK)
fg_status_clr &= MAX77779_FG_Status_Vmx_CLEAR;
pr_debug("VMX is set\n");
}
if (fg_status & MAX77779_FG_Status_Tmx_MASK) {
if (chip->RConfig & MAX77779_FG_Config_TS_MASK)
fg_status_clr &= MAX77779_FG_Status_Tmx_CLEAR;
pr_debug("TMX is set\n");
}
if (fg_status & MAX77779_FG_Status_Smx_MASK) {
if (chip->RConfig & MAX77779_FG_Config_SS_MASK)
fg_status_clr &= MAX77779_FG_Status_Smx_CLEAR;
pr_debug("SMX is set\n");
}
if (fg_status & MAX77779_FG_Status_Br_MASK)
pr_debug("BR is set\n");
/* NOTE: should always clear everything even if we lose state */
MAX77779_FG_REGMAP_WRITE(&chip->regmap, MAX77779_FG_Status, fg_status_clr);
/* SOC interrupts need to go through all the time */
if (fg_status & MAX77779_FG_Status_dSOCi_MASK)
max77779_fg_monitor_log_data(chip, false);
if (chip->psy)
power_supply_changed(chip->psy);
/*
* oneshot w/o filter will unmask on return but gauge will take up
* to 351 ms to clear ALRM1.
* NOTE: can do this masking on gauge side (Config, 0x1D) and using a
* workthread to re-enable.
*/
if (irq != -1)
msleep(MAX77779_FG_TICLR_MS);
return IRQ_HANDLED;
}
/* used to find batt_node and chemistry dependent FG overrides */
static int max77779_fg_read_batt_id(int *batt_id, const struct max77779_fg_chip *chip)
{
bool defer;
int rc = 0;
struct device_node *node = chip->dev->of_node;
u32 temp_id = 0;
/* force the value in kohm */
rc = of_property_read_u32(node, "max77779,force-batt-id", &temp_id);
if (rc == 0) {
dev_warn(chip->dev, "forcing battery RID %d\n", temp_id);
*batt_id = temp_id;
return 0;
}
/* return the value in kohm */
rc = gbms_storage_read(GBMS_TAG_BRID, &temp_id, sizeof(temp_id));
defer = (rc == -EPROBE_DEFER) ||
(rc == -EINVAL) ||
((rc == 0) && (temp_id == -EINVAL));
if (defer)
return -EPROBE_DEFER;
if (rc < 0) {
dev_err(chip->dev, "failed to get batt-id rc=%d\n", rc);
*batt_id = -1;
return -EPROBE_DEFER;
}
*batt_id = temp_id;
return 0;
}
static struct device_node *max77779_fg_find_batt_node(struct max77779_fg_chip *chip)
{
const int batt_id = chip->batt_id;
const struct device *dev = chip->dev;
struct device_node *config_node, *child_node;
u32 batt_id_kohm;
int ret;
config_node = of_find_node_by_name(dev->of_node, "max77779,config");
if (!config_node) {
dev_warn(dev, "Failed to find max77779,config setting\n");
return NULL;
}
for_each_child_of_node(config_node, child_node) {
ret = of_property_read_u32(child_node, "max77779,batt-id-kohm", &batt_id_kohm);
if (ret != 0)
continue;
if (batt_id == batt_id_kohm)
return child_node;
}
return NULL;
}
static int get_irq_none_cnt(void *data, u64 *val)
{
struct max77779_fg_chip *chip = (struct max77779_fg_chip *)data;
*val = chip->debug_irq_none_cnt;
return 0;
}
static int set_irq_none_cnt(void *data, u64 val)
{
struct max77779_fg_chip *chip = (struct max77779_fg_chip *)data;
if (val == 0)
chip->debug_irq_none_cnt = 0;
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(irq_none_cnt_fops, get_irq_none_cnt,
set_irq_none_cnt, "%llu\n");
static int debug_fg_reset(void *data, u64 val)
{
struct max77779_fg_chip *chip = data;
int ret;
if (val == 1)
ret = max77779_fg_full_reset(chip);
else
ret = -EINVAL;
return ret;
}
DEFINE_SIMPLE_ATTRIBUTE(debug_fg_reset_fops, NULL, debug_fg_reset, "%llu\n");
static int debug_ce_start(void *data, u64 val)
{
struct max77779_fg_chip *chip = (struct max77779_fg_chip *)data;
batt_ce_start(&chip->cap_estimate, val);
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(debug_ce_start_fops, NULL, debug_ce_start, "%llu\n");
/* Model reload will be disabled if the node is not found */
static int max77779_fg_init_model(struct max77779_fg_chip *chip)
{
const bool no_battery = chip->fake_battery == 0;
void *model_data;
if (no_battery)
return 0;
/* ->batt_id negative for no lookup */
if (chip->batt_id >= 0) {
chip->batt_node = max77779_fg_find_batt_node(chip);
pr_debug("node found=%d for ID=%d\n",
!!chip->batt_node, chip->batt_id);
}
/* TODO: split allocation and initialization */
model_data = max77779_init_data(chip->dev, chip->batt_node ?
chip->batt_node : chip->dev->of_node,
&chip->regmap);
if (IS_ERR(model_data))
return PTR_ERR(model_data);
chip->model_data = model_data;
if (!chip->batt_node) {
dev_warn(chip->dev, "No child node for ID=%d\n", chip->batt_id);
chip->model_reload = MAX77779_LOAD_MODEL_DISABLED;
} else {
pr_debug("model_data ok for ID=%d\n", chip->batt_id);
chip->model_reload = MAX77779_LOAD_MODEL_IDLE;
}
return 0;
}
/* change battery_id and cause reload of the FG model */
static int debug_batt_id_set(void *data, u64 val)
{
struct max77779_fg_chip *chip = (struct max77779_fg_chip *)data;
int ret;
mutex_lock(&chip->model_lock);
/* reset state (if needed) */
if (chip->model_data)
max77779_free_data(chip->model_data);
chip->batt_id = val;
/* re-init the model data (lookup in DT) */
ret = max77779_fg_init_model(chip);
if (ret == 0)
max77779_fg_model_reload(chip, true);
mutex_unlock(&chip->model_lock);
dev_info(chip->dev, "Force model for batt_id=%llu (%d)\n", val, ret);
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(debug_batt_id_fops, NULL, debug_batt_id_set, "%llu\n");
static int debug_fake_battery_set(void *data, u64 val)
{
struct max77779_fg_chip *chip = (struct max77779_fg_chip *)data;
chip->fake_battery = (int)val;
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(debug_fake_battery_fops, NULL,
debug_fake_battery_set, "%llu\n");
static void max77779_fg_reglog_dump(struct max17x0x_reglog *regs,
size_t size, char *buff)
{
int i, len = 0;
for (i = 0; i < NB_REGMAP_MAX; i++) {
if (size <= len)
break;
if (test_bit(i, regs->valid))
len += scnprintf(&buff[len], size - len, "%02X:%04X\n",
i, regs->data[i]);
}
if (len == 0)
scnprintf(buff, size, "No record\n");
}
static ssize_t debug_get_reglog_writes(struct file *filp, char __user *buf,
size_t count, loff_t *ppos)
{
char *buff;
ssize_t rc = 0;
struct max17x0x_reglog *reglog = (struct max17x0x_reglog *)filp->private_data;
buff = kmalloc(count, GFP_KERNEL);
if (!buff)
return -ENOMEM;
max77779_fg_reglog_dump(reglog, count, buff);
rc = simple_read_from_buffer(buf, count, ppos, buff, strlen(buff));
kfree(buff);
return rc;
}
BATTERY_DEBUG_ATTRIBUTE(debug_reglog_writes_fops,
debug_get_reglog_writes, NULL);
static int debug_sync_model(void *data, u64 val)
{
struct max77779_fg_chip *chip = data;
int ret;
if (!chip->model_data)
return -EINVAL;
/* re-read new state from Fuel gauge, save to storage */
ret = max77779_model_read_state(chip->model_data);
if (ret == 0) {
ret = max77779_model_check_state(chip->model_data);
if (ret < 0)
pr_warn("%s: warning invalid state %d\n", __func__, ret);
ret = max77779_save_state_data(chip->model_data);
}
return ret;
}
DEFINE_SIMPLE_ATTRIBUTE(debug_sync_model_fops, NULL, debug_sync_model, "%llu\n");
static ssize_t max77779_fg_show_debug_data(struct file *filp, char __user *buf,
size_t count, loff_t *ppos)
{
struct max77779_fg_chip *chip = (struct max77779_fg_chip *)filp->private_data;
char msg[8];
u16 data;
int ret;
ret = REGMAP_READ(&chip->regmap, chip->debug_reg_address, &data);
if (ret < 0)
return ret;
ret = scnprintf(msg, sizeof(msg), "%x\n", data);
return simple_read_from_buffer(buf, count, ppos, msg, ret);
}
static ssize_t max77779_fg_set_debug_data(struct file *filp,
const char __user *user_buf,
size_t count, loff_t *ppos)
{
struct max77779_fg_chip *chip = (struct max77779_fg_chip *)filp->private_data;
char temp[8] = { };
u16 data;
int ret;
ret = simple_write_to_buffer(temp, sizeof(temp) - 1, ppos, user_buf, count);
if (!ret)
return -EFAULT;
ret = kstrtou16(temp, 16, &data);
if (ret < 0)
return ret;
ret = MAX77779_FG_REGMAP_WRITE(&chip->regmap, chip->debug_reg_address, data);
if (ret < 0)
return ret;
return count;
}
BATTERY_DEBUG_ATTRIBUTE(debug_reg_data_fops, max77779_fg_show_debug_data,
max77779_fg_set_debug_data);
static ssize_t max77779_fg_show_dbg_debug_data(struct file *filp, char __user *buf,
size_t count, loff_t *ppos)
{
struct max77779_fg_chip *chip = (struct max77779_fg_chip *)filp->private_data;
char msg[8];
u16 data;
int ret;
ret = REGMAP_READ(&chip->regmap_debug, chip->debug_dbg_reg_address, &data);
if (ret < 0)
return ret;
ret = scnprintf(msg, sizeof(msg), "%x\n", data);
return simple_read_from_buffer(buf, count, ppos, msg, ret);
}
static ssize_t max77779_fg_set_dbg_debug_data(struct file *filp,
const char __user *user_buf,
size_t count, loff_t *ppos)
{
struct max77779_fg_chip *chip = (struct max77779_fg_chip *)filp->private_data;
char temp[8] = { };
u16 data;
int ret;
ret = simple_write_to_buffer(temp, sizeof(temp) - 1, ppos, user_buf, count);
if (!ret)
return -EFAULT;
ret = kstrtou16(temp, 16, &data);
if (ret < 0)
return ret;
ret = MAX77779_FG_REGMAP_WRITE(&chip->regmap_debug, chip->debug_dbg_reg_address, data);
if (ret < 0)
return ret;
return count;
}
BATTERY_DEBUG_ATTRIBUTE(debug_reg_dbg_data_fops, max77779_fg_show_dbg_debug_data,
max77779_fg_set_dbg_debug_data);
static ssize_t max77779_fg_show_reg_all(struct file *filp, char __user *buf,
size_t count, loff_t *ppos)
{
struct max77779_fg_chip *chip = (struct max77779_fg_chip *)filp->private_data;
const struct max17x0x_regmap *map = &chip->regmap;
u32 reg_address;
unsigned int data;
char *tmp;
int ret = 0, len = 0;
if (!map->regmap) {
pr_err("Failed to read, no regmap\n");
return -EIO;
}
tmp = kmalloc(PAGE_SIZE, GFP_KERNEL);
if (!tmp)
return -ENOMEM;
for (reg_address = 0; reg_address <= 0xFF; reg_address++) {
ret = regmap_read(map->regmap, reg_address, &data);
if (ret < 0)
continue;
len += scnprintf(tmp + len, PAGE_SIZE - len, "%02x: %04x\n", reg_address, data);
}
if (len > 0)
len = simple_read_from_buffer(buf, count, ppos, tmp, strlen(tmp));
kfree(tmp);
return len;
}
BATTERY_DEBUG_ATTRIBUTE(debug_reg_all_fops, max77779_fg_show_reg_all, NULL);
static ssize_t max77779_fg_show_dbg_reg_all(struct file *filp, char __user *buf,
size_t count, loff_t *ppos)
{
struct max77779_fg_chip *chip = (struct max77779_fg_chip *)filp->private_data;
const struct max17x0x_regmap *map = &chip->regmap_debug;
u32 reg_address;
unsigned int data;
char *tmp;
int ret = 0, len = 0;
if (!map->regmap) {
pr_err("Failed to read, no regmap\n");
return -EIO;
}
tmp = kmalloc(PAGE_SIZE, GFP_KERNEL);
if (!tmp)
return -ENOMEM;
for (reg_address = 0; reg_address <= 0xFF; reg_address++) {
ret = regmap_read(map->regmap, reg_address, &data);
if (ret < 0)
continue;
len += scnprintf(tmp + len, PAGE_SIZE - len, "%02x: %04x\n", reg_address, data);
}
if (len > 0)
len = simple_read_from_buffer(buf, count, ppos, tmp, strlen(tmp));
kfree(tmp);
return len;
}
BATTERY_DEBUG_ATTRIBUTE(debug_reg_all_dbg_fops, max77779_fg_show_dbg_reg_all, NULL);
static ssize_t max77779_fg_force_psy_update(struct file *filp,
const char __user *user_buf,
size_t count, loff_t *ppos)
{
struct max77779_fg_chip *chip = (struct max77779_fg_chip *)filp->private_data;
if (chip->psy)
power_supply_changed(chip->psy);
return count;
}
BATTERY_DEBUG_ATTRIBUTE(debug_force_psy_update_fops, NULL,
max77779_fg_force_psy_update);
static int debug_cnhs_reset(void *data, u64 val)
{
struct max77779_fg_chip *chip = data;
u16 reset_val;
int ret;
reset_val = (u16)val;
ret = gbms_storage_write(GBMS_TAG_CNHS, &reset_val,
sizeof(reset_val));
dev_info(chip->dev, "reset CNHS to %d, (ret=%d)\n", reset_val, ret);
return ret > 0 ? 0 : ret;
}
DEFINE_SIMPLE_ATTRIBUTE(debug_reset_cnhs_fops, NULL, debug_cnhs_reset, "%llu\n");
static int debug_gmsr_reset(void *data, u64 val)
{
struct max77779_fg_chip *chip = data;
int ret;
ret = max77779_reset_state_data(chip->model_data);
dev_info(chip->dev, "reset GMSR (ret=%d)\n", ret);
return ret > 0 ? 0 : ret;
}
DEFINE_SIMPLE_ATTRIBUTE(debug_reset_gmsr_fops, NULL, debug_gmsr_reset, "%llu\n");
/*
* TODO: add the building blocks of google capacity
*
* case POWER_SUPPLY_PROP_DELTA_CC_SUM:
* val->intval = chip->cap_estimate.delta_cc_sum;
* break;
* case POWER_SUPPLY_PROP_DELTA_VFSOC_SUM:
* val->intval = chip->cap_estimate.delta_vfsoc_sum;
* break;
*/
static ssize_t act_impedance_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count) {
struct power_supply *psy = container_of(dev, struct power_supply, dev);
struct max77779_fg_chip *chip = power_supply_get_drvdata(psy);
int value, ret = 0;
ret = kstrtoint(buf, 0, &value);
if (ret < 0)
return ret;
mutex_lock(&chip->model_lock);
ret = max77779_fg_health_update_ai(chip, value);
if (ret == 0)
chip->bhi_acim = 0;
dev_info(chip->dev, "value=%d (%d)\n", value, ret);
mutex_unlock(&chip->model_lock);
return count;
}
static ssize_t act_impedance_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct power_supply *psy = container_of(dev, struct power_supply, dev);
struct max77779_fg_chip *chip = power_supply_get_drvdata(psy);
return scnprintf(buf, PAGE_SIZE, "%d\n", max77779_fg_health_get_ai(chip));
}
static DEVICE_ATTR_RW(act_impedance);
static int max77779_fg_init_sysfs(struct max77779_fg_chip *chip)
{
struct dentry *de;
de = debugfs_create_dir(chip->max77779_fg_psy_desc.name, 0);
if (IS_ERR_OR_NULL(de))
return -ENOENT;
debugfs_create_file("irq_none_cnt", 0644, de, chip, &irq_none_cnt_fops);
debugfs_create_file("fg_reset", 0400, de, chip, &debug_fg_reset_fops);
debugfs_create_file("ce_start", 0400, de, chip, &debug_ce_start_fops);
debugfs_create_file("fake_battery", 0400, de, chip, &debug_fake_battery_fops);
debugfs_create_file("batt_id", 0600, de, chip, &debug_batt_id_fops);
debugfs_create_file("force_psy_update", 0600, de, chip, &debug_force_psy_update_fops);
if (chip->regmap.reglog)
debugfs_create_file("regmap_writes", 0440, de,
chip->regmap.reglog,
&debug_reglog_writes_fops);
debugfs_create_bool("model_ok", 0444, de, &chip->model_ok);
debugfs_create_file("sync_model", 0400, de, chip, &debug_sync_model_fops);
/* new debug interface */
debugfs_create_u32("address", 0600, de, &chip->debug_reg_address);
debugfs_create_u32("debug_address", 0600, de, &chip->debug_dbg_reg_address);
debugfs_create_file("data", 0600, de, chip, &debug_reg_data_fops);
debugfs_create_file("debug_data", 0600, de, chip, &debug_reg_dbg_data_fops);
/* dump all registers */
debugfs_create_file("registers", 0444, de, chip, &debug_reg_all_fops);
debugfs_create_file("debug_registers", 0444, de, chip, &debug_reg_all_dbg_fops);
/* reset fg eeprom data for debugging */
debugfs_create_file("cnhs_reset", 0400, de, chip, &debug_reset_cnhs_fops);
debugfs_create_file("gmsr_reset", 0400, de, chip, &debug_reset_gmsr_fops);
/* capacity fade */
debugfs_create_u32("bhi_fcn_count", 0644, de, &chip->bhi_fcn_count);
return 0;
}
static u16 max77779_fg_read_rsense(const struct max77779_fg_chip *chip)
{
u32 rsense_default = 0;
u16 rsense = 200;
int ret;
ret = of_property_read_u32(chip->dev->of_node, "max77779,rsense-default",
&rsense_default);
if (ret == 0)
rsense = rsense_default;
return rsense;
}
static int max77779_fg_dump_param(struct max77779_fg_chip *chip)
{
int ret;
u16 data;
ret = REGMAP_READ(&chip->regmap, MAX77779_FG_Config, &chip->RConfig);
if (ret < 0)
return ret;
dev_info(chip->dev, "Config: 0x%04x\n", chip->RConfig);
ret = REGMAP_READ(&chip->regmap, MAX77779_FG_IChgTerm, &data);
if (ret < 0)
return ret;
dev_info(chip->dev, "IChgTerm: %d\n", reg_to_micro_amp(data, chip->RSense));
ret = REGMAP_READ(&chip->regmap, MAX77779_FG_VEmpty, &data);
if (ret < 0)
return ret;
dev_info(chip->dev, "VEmpty: VE=%dmV VR=%dmV\n",
reg_to_vempty(data), reg_to_vrecovery(data));
return 0;
}
static int max77779_fg_clear_por(struct max77779_fg_chip *chip)
{
u16 data;
int ret;
ret = REGMAP_READ(&chip->regmap, MAX77779_FG_Status, &data);
if (ret < 0)
return ret;
if ((data & MAX77779_FG_Status_PONR_MASK) == 0)
return 0;
return regmap_update_bits(chip->regmap.regmap,
MAX77779_FG_Status,
MAX77779_FG_Status_PONR_MASK,
0x1);
}
/* read state from fg (if needed) and set the next update field */
static int max77779_fg_set_next_update(struct max77779_fg_chip *chip)
{
int rc;
u16 reg_cycle;
/* do not save data when battery ID not clearly */
if (chip->batt_id == DEFAULT_BATTERY_ID)
return 0;
rc = REGMAP_READ(&chip->regmap, MAX77779_FG_Cycles, &reg_cycle);
if (rc < 0)
return rc;
if (chip->model_next_update && reg_cycle < chip->model_next_update)
return 0;
/* read new state from Fuel gauge, save to storage if needed */
rc = max77779_model_read_state(chip->model_data);
if (rc == 0) {
rc = max77779_model_check_state(chip->model_data);
if (rc < 0) {
pr_debug("%s: fg model state is corrupt rc=%d\n",
__func__, rc);
return -EINVAL;
}
}
if (rc == 0 && chip->model_next_update)
rc = max77779_save_state_data(chip->model_data);
if (rc == 0)
chip->model_next_update = (reg_cycle + (1 << 6)) &
~((1 << 6) - 1);
pr_debug("%s: reg_cycle=%d next_update=%d rc=%d\n", __func__,
reg_cycle, chip->model_next_update, rc);
return 0;
}
static int max77779_fg_model_load(struct max77779_fg_chip *chip)
{
int ret;
/* TODO: b/283487421 - Implement loading procedure */
/* retrieve model state from permanent storage only on boot */
if (!chip->model_state_valid) {
/*
* retrieve state from storage: retry on -EAGAIN as long as
* model_reload > _IDLE
*/
ret = max77779_load_state_data(chip->model_data);
if (ret == -EAGAIN)
return -EAGAIN;
if (ret < 0)
dev_warn(chip->dev, "Load Model Using Default State (%d)\n", ret);
/* use the state from the DT when GMSR is invalid */
}
/* failure on the gauge: retry as long as model_reload > IDLE */
ret = max77779_load_gauge_model(chip->model_data);
if (ret < 0) {
dev_err(chip->dev, "Load Model Failed ret=%d\n", ret);
return -EAGAIN;
}
/* mark model state as "safe" */
chip->reg_prop_capacity_raw = MAX77779_FG_RepSOC;
chip->model_state_valid = true;
return 0;
}
static void max77779_fg_model_work(struct work_struct *work)
{
struct max77779_fg_chip *chip = container_of(work, struct max77779_fg_chip,
model_work.work);
bool new_model = false;
u16 reg_cycle;
int rc;
if (!chip->model_data)
return;
mutex_lock(&chip->model_lock);
/* set model_reload to the #attempts, might change cycle count */
if (chip->model_reload >= MAX77779_LOAD_MODEL_REQUEST) {
rc = max77779_fg_model_load(chip);
if (rc == 0) {
rc = max77779_fg_clear_por(chip);
dev_info(chip->dev, "Model OK, Clear Power-On Reset (%d)\n",
rc);
/* TODO: keep trying to clear POR if the above fail */
max77779_fg_restore_battery_cycle(chip);
rc = REGMAP_READ(&chip->regmap, MAX77779_FG_Cycles, &reg_cycle);
if (rc == 0 && reg_cycle >= 0) {
chip->model_reload = MAX77779_LOAD_MODEL_IDLE;
chip->model_ok = true;
new_model = true;
/* saved new value in max77779_fg_set_next_update */
chip->model_next_update = reg_cycle > 0 ? reg_cycle - 1 : 0;
}
} else if (rc != -EAGAIN) {
chip->model_reload = MAX77779_LOAD_MODEL_DISABLED;
chip->model_ok = false;
} else if (chip->model_reload > MAX77779_LOAD_MODEL_IDLE) {
chip->model_reload -= 1;
}
}
if (chip->model_reload >= MAX77779_LOAD_MODEL_REQUEST) {
const unsigned long delay = msecs_to_jiffies(60 * 1000);
mod_delayed_work(system_wq, &chip->model_work, delay);
}
if (new_model) {
dev_info(chip->dev, "FG Model OK, ver=%d next_update=%d\n",
max77779_fg_model_version(chip->model_data),
chip->model_next_update);
power_supply_changed(chip->psy);
}
mutex_unlock(&chip->model_lock);
}
static int read_chip_property_u32(const struct max77779_fg_chip *chip,
char *property, u32 *data32)
{
int ret;
if (chip->batt_node) {
ret = of_property_read_u32(chip->batt_node, property, data32);
if (ret == 0)
return ret;
}
return of_property_read_u32(chip->dev->of_node, property, data32);
}
static int max77779_fg_check_config(struct max77779_fg_chip *chip)
{
u16 data;
int ret;
ret = REGMAP_READ(&chip->regmap, MAX77779_FG_Config, &data);
if (ret == 0 && (data & MAX77779_FG_Config_Ten_MASK) == 0)
return -EINVAL;
return 0;
}
static int max77779_fg_log_event(struct max77779_fg_chip *chip, gbms_tag_t tag)
{
u8 event_count;
int ret = 0;
ret = gbms_storage_read(tag, &event_count, sizeof(event_count));
if (ret < 0)
return ret;
/* max count */
if (event_count == 0xFE)
return 0;
/* initial value */
if (event_count == 0xFF)
event_count = 1;
else
event_count++;
ret = gbms_storage_write(tag, &event_count, sizeof(event_count));
if (ret < 0)
return ret;
dev_info(chip->dev, "tag:0x%X, event_count:%d\n", tag, event_count);
return 0;
}
/* handle recovery of FG state */
static int max77779_fg_init_model_data(struct max77779_fg_chip *chip)
{
int ret;
if (!chip->model_data)
return 0;
if (!max77779_fg_model_check_version(chip->model_data)) {
if (max77779_needs_reset_model_data(chip->model_data)) {
ret = max77779_reset_state_data(chip->model_data);
if (ret < 0)
dev_err(chip->dev, "GMSR: failed to erase RC2 saved model data"
" ret=%d\n", ret);
else
dev_warn(chip->dev, "GMSR: RC2 model data erased\n");
}
/* this is expected */
ret = max77779_fg_full_reset(chip);
dev_warn(chip->dev, "FG Version Changed, Reset (%d), Will Reload\n", ret);
return 0;
}
/* TODO add retries */
ret = max77779_model_read_state(chip->model_data);
if (ret < 0) {
dev_err(chip->dev, "FG Model Error (%d)\n", ret);
return -EPROBE_DEFER;
}
/* this is a real failure and must be logged */
ret = max77779_model_check_state(chip->model_data);
if (ret < 0) {
int rret = max77779_fg_full_reset(chip);
dev_err(chip->dev, "FG State Corrupt (%d), Reset (%d) Will reload\n", ret, rret);
ret = max77779_fg_log_event(chip, GBMS_TAG_SELC);
if (ret < 0)
dev_err(chip->dev, "Cannot log the event (%d)\n", ret);
return 0;
}
ret = max77779_fg_check_config(chip);
if (ret < 0) {
ret = max77779_fg_full_reset(chip);
dev_err(chip->dev, "Invalid config data, Reset (%d), Will reload\n", ret);
ret = max77779_fg_log_event(chip, GBMS_TAG_CELC);
if (ret < 0)
dev_err(chip->dev, "Cannot log the event (%d)\n", ret);
return 0;
}
ret = max77779_fg_set_next_update(chip);
if (ret < 0)
dev_warn(chip->dev, "Error on Next Update, Will retry\n");
dev_info(chip->dev, "FG Model OK, ver=%d next_update=%d\n",
max77779_model_read_version(chip->model_data),
chip->model_next_update);
chip->reg_prop_capacity_raw = MAX77779_FG_RepSOC;
chip->model_state_valid = true;
chip->model_ok = true;
return 0;
}
static int max77779_fg_init_chip(struct max77779_fg_chip *chip)
{
int ret;
u16 data = 0;
if (of_property_read_bool(chip->dev->of_node, "max77779,force-hard-reset"))
max77779_fg_full_reset(chip);
ret = REGMAP_READ(&chip->regmap, MAX77779_FG_Status, &data);
if (ret < 0)
return -EPROBE_DEFER;
chip->por = (data & MAX77779_FG_Status_PONR_MASK) != 0;
/* TODO: handle RSense 0 */
chip->RSense = max77779_fg_read_rsense(chip);
if (chip->RSense == 0)
dev_err(chip->dev, "no default RSense value\n");
/* set maxim,force-batt-id in DT to not delay the probe */
ret = max77779_fg_read_batt_id(&chip->batt_id, chip);
if (ret == -EPROBE_DEFER) {
if (chip->batt_id_defer_cnt) {
chip->batt_id_defer_cnt -= 1;
return -EPROBE_DEFER;
}
chip->batt_id = DEFAULT_BATTERY_ID;
dev_info(chip->dev, "default device battery ID = %d\n",
chip->batt_id);
} else {
dev_info(chip->dev, "device battery RID: %d kohm\n",
chip->batt_id);
}
/* TODO: b/283489811 - fix this */
/* do not request the interrupt if can't read battery or not present */
if (chip->batt_id == DEFAULT_BATTERY_ID || chip->batt_id == DUMMY_BATTERY_ID) {
ret = MAX77779_FG_REGMAP_WRITE(&chip->regmap, MAX77779_FG_CONFIG2, 0x0);
if (ret < 0)
dev_warn(chip->dev, "Cannot write 0x0 to Config(%d)\n", ret);
}
/* fuel gauge model needs to know the batt_id */
mutex_init(&chip->model_lock);
/*
* FG model is ony used for integrated FG (MW). Loading a model might
* change the capacity drift WAR algo_ver and design_capacity.
* NOTE: design_capacity used for drift might be updated after loading
* a FG model.
*/
ret = max77779_fg_init_model(chip);
if (ret < 0)
dev_err(chip->dev, "Cannot init FG model (%d)\n", ret);
ret = max77779_fg_dump_param(chip);
if (ret < 0)
return -EPROBE_DEFER;
dev_info(chip->dev, "RSense value %d micro Ohm\n", chip->RSense * 10);
ret = REGMAP_READ(&chip->regmap, MAX77779_FG_Status, &data);
if (!ret && data & MAX77779_FG_Status_Br_MASK) {
dev_info(chip->dev, "Clearing Battery Removal bit\n");
regmap_update_bits(chip->regmap.regmap, MAX77779_FG_Status,
MAX77779_FG_Status_Br_MASK, 0x1);
}
if (!ret && data & MAX77779_FG_Status_Bi_MASK) {
dev_info(chip->dev, "Clearing Battery Insertion bit\n");
regmap_update_bits(chip->regmap.regmap, MAX77779_FG_Status,
MAX77779_FG_Status_Bi_MASK, 0x1);
}
max77779_fg_restore_battery_cycle(chip);
/* triggers loading of the model in the irq handler on POR */
if (!chip->por) {
ret = max77779_fg_init_model_data(chip);
if (ret < 0)
return ret;
}
return 0;
}
/* ------------------------------------------------------------------------- */
/* TODO b/284191528 - Add to common code file */
#define REG_HALF_HIGH(reg) ((reg >> 8) & 0x00FF)
#define REG_HALF_LOW(reg) (reg & 0x00FF)
static int max77779_fg_collect_history_data(void *buff, size_t size,
struct max77779_fg_chip *chip)
{
struct max77779_fg_eeprom_history hist = { 0 };
u16 data, designcap;
int ret;
if (chip->por)
return -EINVAL;
ret = REGMAP_READ(&chip->regmap_debug, MAX77779_FG_DBG_nTempCo, &data);
if (ret)
return ret;
hist.tempco = data;
ret = REGMAP_READ(&chip->regmap_debug, MAX77779_FG_DBG_nRComp0, &data);
if (ret)
return ret;
hist.rcomp0 = data;
ret = REGMAP_READ(&chip->regmap, MAX77779_FG_TimerH, &data);
if (ret)
return ret;
/* Convert LSB from 3.2hours(192min) to 5days(7200min) */
hist.timerh = data * 192 / 7200;
ret = REGMAP_READ(&chip->regmap, MAX77779_FG_DesignCap, &designcap);
if (ret)
return ret;
/* multiply by 100 to convert from mAh to %, LSB 0.125% */
ret = REGMAP_READ(&chip->regmap, MAX77779_FG_FullCapNom, &data);
if (ret)
return ret;
data = data * 800 / designcap;
hist.fullcapnom = data > MAX_HIST_FULLCAP ? MAX_HIST_FULLCAP : data;
/* multiply by 100 to convert from mAh to %, LSB 0.125% */
ret = REGMAP_READ(&chip->regmap, MAX77779_FG_FullCapRep, &data);
if (ret)
return ret;
data = data * 800 / designcap;
hist.fullcaprep = data > MAX_HIST_FULLCAP ? MAX_HIST_FULLCAP : data;
ret = REGMAP_READ(&chip->regmap, MAX77779_FG_MixSOC, &data);
if (ret)
return ret;
/* Convert LSB from 1% to 2% */
hist.mixsoc = REG_HALF_HIGH(data) / 2;
ret = REGMAP_READ(&chip->regmap, MAX77779_FG_VFSOC, &data);
if (ret)
return ret;
/* Convert LSB from 1% to 2% */
hist.vfsoc = REG_HALF_HIGH(data) / 2;
ret = REGMAP_READ(&chip->regmap, MAX77779_FG_MaxMinVolt, &data);
if (ret)
return ret;
/* LSB is 20mV, store values from 4.2V min */
hist.maxvolt = (REG_HALF_HIGH(data) * 20 - 4200) / 20;
/* Convert LSB from 20mV to 10mV, store values from 2.5V min */
hist.minvolt = (REG_HALF_LOW(data) * 20 - 2500) / 10;
ret = REGMAP_READ(&chip->regmap, MAX77779_FG_MaxMinTemp, &data);
if (ret)
return ret;
/* Convert LSB from 1degC to 3degC, store values from 25degC min */
hist.maxtemp = (REG_HALF_HIGH(data) - 25) / 3;
/* Convert LSB from 1degC to 3degC, store values from -20degC min */
hist.mintemp = (REG_HALF_LOW(data) + 20) / 3;
ret = REGMAP_READ(&chip->regmap, MAX77779_FG_MaxMinCurr, &data);
if (ret)
return ret;
/* Convert LSB from 0.08A to 0.5A */
hist.maxchgcurr = REG_HALF_HIGH(data) * 8 / 50;
hist.maxdischgcurr = REG_HALF_LOW(data) * 8 / 50;
memcpy(buff, &hist, sizeof(hist));
return (size_t)sizeof(hist);
}
static int max77779_fg_prop_iter(int index, gbms_tag_t *tag, void *ptr)
{
static gbms_tag_t keys[] = {GBMS_TAG_CLHI};
const int count = ARRAY_SIZE(keys);
if (index >= 0 && index < count) {
*tag = keys[index];
return 0;
}
return -ENOENT;
}
static int max77779_fg_prop_read(gbms_tag_t tag, void *buff, size_t size,
void *ptr)
{
struct max77779_fg_chip *chip = (struct max77779_fg_chip *)ptr;
int ret = -ENOENT;
switch (tag) {
case GBMS_TAG_CLHI:
ret = max77779_fg_collect_history_data(buff, size, chip);
break;
default:
break;
}
return ret;
}
static struct gbms_storage_desc max77779_fg_prop_dsc = {
.iter = max77779_fg_prop_iter,
.read = max77779_fg_prop_read,
};
/* ------------------------------------------------------------------------- */
/* this must be not blocking */
static void max77779_fg_read_serial_number(struct max77779_fg_chip *chip)
{
char buff[32] = {0};
int ret = gbms_storage_read(GBMS_TAG_MINF, buff, GBMS_MINF_LEN);
if (ret >= 0)
strncpy(chip->serial_number, buff, ret);
else
chip->serial_number[0] = '\0';
}
static void max77779_fg_init_work(struct work_struct *work)
{
struct max77779_fg_chip *chip = container_of(work, struct max77779_fg_chip,
init_work.work);
int ret = 0;
if (chip->gauge_type != -1) {
/* these don't require nvm storage */
ret = gbms_storage_register(&max77779_fg_prop_dsc, "max7779fg", chip);
if (ret == -EBUSY)
ret = 0;
if (ret == 0)
ret = max77779_fg_init_chip(chip);
if (ret == -EPROBE_DEFER) {
schedule_delayed_work(&chip->init_work,
msecs_to_jiffies(MAX77779_FG_DELAY_INIT_MS));
return;
}
}
/* serial number might not be stored in the FG */
max77779_fg_read_serial_number(chip);
mutex_init(&chip->cap_estimate.batt_ce_lock);
chip->prev_charge_status = POWER_SUPPLY_STATUS_UNKNOWN;
chip->fake_capacity = -EINVAL;
chip->resume_complete = true;
chip->init_complete = true;
chip->bhi_acim = 0;
max77779_fg_init_sysfs(chip);
/*
* Handle any IRQ that might have been set before init
* NOTE: will clear the POR bit and trigger model load if needed
*/
max77779_fg_irq_thread_fn(-1, chip);
dev_info(chip->dev, "init_work done\n");
}
/* TODO: fix detection of 17301 for non samples looking at FW version too */
static int max77779_read_gauge_type(struct max77779_fg_chip *chip)
{
u8 reg = MAX77779_FG_DevName;
struct i2c_msg xfer[2];
uint8_t buf[2] = { };
int ret, gauge_type;
/* some maxim IF-PMIC corrupt reads w/o Rs b/152373060 */
xfer[0].addr = chip->primary->addr;
xfer[0].flags = 0;
xfer[0].len = 1;
xfer[0].buf = &reg;
xfer[1].addr = chip->primary->addr;
xfer[1].flags = I2C_M_RD;
xfer[1].len = 2;
xfer[1].buf = buf;
ret = i2c_transfer(chip->primary->adapter, xfer, 2);
if (ret != 2)
return -EIO;
/* it might need devname later */
chip->devname = buf[1] << 8 | buf[0];
dev_info(chip->dev, "chip devname:0x%X\n", chip->devname);
ret = of_property_read_u32(chip->dev->of_node, "max77779,gauge-type",
&gauge_type);
if (ret == 0) {
dev_warn(chip->dev, "forced gauge type to %d\n", gauge_type);
return gauge_type;
}
/* TODO b/283488733 add max77779 devname support */
ret = max77779_check_devname(chip->devname);
if (ret)
return MAX_M5_GAUGE_TYPE;
switch (chip->devname >> 4) {
case 0x404: /* max1730x sample */
case 0x405: /* max1730x pass2 silicon initial samples */
case 0x406: /* max1730x pass2 silicon */
gauge_type = MAX1730X_GAUGE_TYPE;
break;
default:
break;
}
switch (chip->devname & 0x000F) {
case 0x1: /* max17201 or max17211 */
case 0x5: /* max17205 or max17215 */
default:
gauge_type = MAX1720X_GAUGE_TYPE;
break;
}
return gauge_type;
}
static bool max77779_fg_dbg_is_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case 0x9D ... 0x9E:
case 0xA5 ... 0xA7:
case 0xB1 ... 0xB3:
case 0xC6:
case 0xC8 ... 0xCA:
return true;
}
return false;
}
const struct regmap_config max77779_fg_debug_regmap_cfg = {
.reg_bits = 8,
.val_bits = 16,
.val_format_endian = REGMAP_ENDIAN_NATIVE,
.max_register = MAX77779_FG_DBG_nThermCfg,
.readable_reg = max77779_fg_dbg_is_reg,
.volatile_reg = max77779_fg_dbg_is_reg,
};
static bool max77779_fg_is_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case 0x00 ... 0x14:
case 0x16 ... 0x1D:
case 0x1F ... 0x27:
case 0x29: /* ICHGTERM */
case 0x2E ... 0x35:
case 0x37: /* VFSOC */
return true;
case 0x39 ... 0x3A:
case 0x3D ... 0x3F:
case 0x42:
case 0x45 ... 0x48:
case 0x4C ... 0x4E:
case 0x52 ... 0x53:
case 0x80 ... 0x9F: /* Model */
case 0xA3: /* Model cfg */
case 0xB0:
case 0xB2:
case 0xB4:
case 0xBE:
case 0xD0 ... 0xD3:
case 0xD5 ... 0xDB:
case 0xE0 ... 0xE1: /* FG_Func*/
case 0xE9 ... 0xEA:
return true;
}
return false;
}
const struct regmap_config max77779_fg_model_regmap_cfg = {
.reg_bits = 8,
.val_bits = 16,
.val_format_endian = REGMAP_ENDIAN_NATIVE,
.max_register = MAX77779_FG_Command_ack,
.readable_reg = max77779_fg_is_reg,
.volatile_reg = max77779_fg_is_reg,
};
const struct max17x0x_reg max77779_fg_model[] = {
[MAX17X0X_TAG_avgc] = { ATOM_INIT_REG16(MAX77779_FG_AvgCurrent)},
[MAX17X0X_TAG_cnfg] = { ATOM_INIT_REG16(MAX77779_FG_Config)},
[MAX17X0X_TAG_mmdv] = { ATOM_INIT_REG16(MAX77779_FG_MaxMinVolt)},
[MAX17X0X_TAG_vcel] = { ATOM_INIT_REG16(MAX77779_FG_VCell)},
[MAX17X0X_TAG_temp] = { ATOM_INIT_REG16(MAX77779_FG_Temp)},
[MAX17X0X_TAG_curr] = { ATOM_INIT_REG16(MAX77779_FG_Current)},
[MAX17X0X_TAG_mcap] = { ATOM_INIT_REG16(MAX77779_FG_MixCap)},
[MAX17X0X_TAG_vfsoc] = { ATOM_INIT_REG16(MAX77779_FG_VFSOC)},
};
int max77779_max17x0x_regmap_init(struct max17x0x_regmap *regmap,
struct i2c_client *clnt,
const struct regmap_config *regmap_config,
bool tag)
{
struct regmap *map;
map = devm_regmap_init_i2c(clnt, regmap_config);
if (IS_ERR(map))
return IS_ERR_VALUE(map);
if (tag) {
regmap->regtags.max = ARRAY_SIZE(max77779_fg_model);
regmap->regtags.map = max77779_fg_model;
} else {
regmap->regtags.max = 0;
regmap->regtags.map = NULL;
}
regmap->regmap = map;
return 0;
}
/* NOTE: NEED TO COME BEFORE REGISTER ACCESS */
static int max77779_fg_regmap_init(struct max77779_fg_chip *chip)
{
int ret = max77779_max17x0x_regmap_init(&chip->regmap, chip->primary,
&max77779_fg_model_regmap_cfg,
true);
if (ret < 0) {
dev_err(chip->dev, "Failed to re-initialize regmap (%ld)\n",
IS_ERR_VALUE(chip->regmap.regmap));
return -EINVAL;
}
ret = max77779_max17x0x_regmap_init(&chip->regmap_debug, chip->secondary,
&max77779_fg_debug_regmap_cfg,
false);
if (ret < 0) {
dev_err(chip->dev, "Failed to re-initialize debug regmap (%ld)\n",
IS_ERR_VALUE(chip->regmap_debug.regmap));
return IS_ERR_VALUE(chip->regmap_debug.regmap);
}
return 0;
}
void *max77779_get_model_data(struct i2c_client *client)
{
struct max77779_fg_chip *chip = i2c_get_clientdata(client);
return chip ? chip->model_data : NULL;
}
static struct attribute *max77779_fg_attrs[] = {
&dev_attr_act_impedance.attr,
&dev_attr_offmode_charger.attr,
&dev_attr_resistance_id.attr,
&dev_attr_resistance.attr,
&dev_attr_gmsr.attr,
NULL,
};
static const struct attribute_group max77779_fg_attr_grp = {
.attrs = max77779_fg_attrs,
};
static int max77779_fg_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct max77779_fg_chip *chip;
struct device *dev = &client->dev;
struct power_supply_config psy_cfg = { };
const char *psy_name = NULL;
char monitor_name[32];
int ret = 0;
u32 data32;
if (client->irq < 0)
return -EPROBE_DEFER;
chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
if (!chip)
return -ENOMEM;
chip->dev = dev;
chip->fake_battery = of_property_read_bool(dev->of_node, "max77779,no-battery") ? 0 : -1;
chip->primary = client;
chip->batt_id_defer_cnt = DEFAULT_BATTERY_ID_RETRIES;
i2c_set_clientdata(client, chip);
chip->secondary = i2c_new_ancillary_device(chip->primary, "ndbg", MAX77779_FG_NDGB_ADDRESS);
if (IS_ERR(chip->secondary)) {
dev_err(dev, "Error setting up ancillary i2c bus(%ld)\n", IS_ERR_VALUE(chip->secondary));
goto i2c_unregister_primary;
}
i2c_set_clientdata(chip->secondary, chip);
/* needs chip->primary */
ret = max77779_fg_regmap_init(chip);
if (ret < 0) {
dev_err(dev, "Failed to initialize regmap(s)\n");
goto i2c_unregister;
}
/* NOTE: < 0 not available, it could be a bare MLB */
chip->gauge_type = max77779_read_gauge_type(chip);
if (chip->gauge_type < 0)
chip->gauge_type = -1;
ret = of_property_read_u32(dev->of_node, "max77779,status-charge-threshold-ma",
&data32);
if (ret == 0)
chip->status_charge_threshold_ma = data32;
else
chip->status_charge_threshold_ma = DEFAULT_STATUS_CHARGE_MA;
if (of_property_read_bool(dev->of_node, "max77779,log_writes")) {
bool debug_reglog;
debug_reglog = max77779_fg_reglog_init(chip);
dev_info(dev, "write log %savailable\n",
debug_reglog ? "" : "not ");
}
if (client->irq) {
ret = devm_request_threaded_irq(chip->dev, chip->primary->irq, NULL,
max77779_fg_irq_thread_fn,
IRQF_TRIGGER_LOW |
IRQF_SHARED |
IRQF_ONESHOT,
MAX77779_FG_I2C_DRIVER_NAME,
chip);
dev_info(chip->dev, "FG irq handler registered at %d (%d)\n",
chip->primary->irq, ret);
if (ret == 0)
enable_irq_wake(chip->primary->irq);
} else {
dev_err(dev, "cannot allocate irq\n");
goto i2c_unregister;
}
psy_cfg.drv_data = chip;
psy_cfg.of_node = chip->dev->of_node;
ret = of_property_read_string(dev->of_node,
"max77779,dual-battery", &psy_name);
if (ret == 0)
chip->max77779_fg_psy_desc.name = devm_kstrdup(dev, psy_name, GFP_KERNEL);
else
chip->max77779_fg_psy_desc.name = "max77779fg";
dev_info(dev, "max77779_fg_psy_desc.name=%s\n", chip->max77779_fg_psy_desc.name);
chip->max77779_fg_psy_desc.type = POWER_SUPPLY_TYPE_BATTERY;
chip->max77779_fg_psy_desc.get_property = max77779_fg_get_property;
chip->max77779_fg_psy_desc.set_property = max77779_fg_set_property;
chip->max77779_fg_psy_desc.property_is_writeable = max77779_fg_property_is_writeable;
chip->max77779_fg_psy_desc.properties = max77779_fg_battery_props;
chip->max77779_fg_psy_desc.num_properties = ARRAY_SIZE(max77779_fg_battery_props);
if (of_property_read_bool(dev->of_node, "max77779,psy-type-unknown"))
chip->max77779_fg_psy_desc.type = POWER_SUPPLY_TYPE_UNKNOWN;
chip->psy = devm_power_supply_register(dev, &chip->max77779_fg_psy_desc,
&psy_cfg);
if (IS_ERR(chip->psy)) {
dev_err(dev, "Couldn't register as power supply\n");
ret = PTR_ERR(chip->psy);
goto i2c_unregister;
}
ret = sysfs_create_group(&chip->psy->dev.kobj, &max77779_fg_attr_grp);
if (ret)
dev_err(dev, "Failed to create sysfs group\n");
/*
* TODO:
* POWER_SUPPLY_PROP_CHARGE_FULL_ESTIMATE -> GBMS_TAG_GCFE
* POWER_SUPPLY_PROP_RES_FILTER_COUNT -> GBMS_TAG_RFCN
*/
/* M5 battery model needs batt_id and is setup during init() */
chip->model_reload = MAX77779_LOAD_MODEL_DISABLED;
chip->ce_log = logbuffer_register(chip->max77779_fg_psy_desc.name);
if (IS_ERR(chip->ce_log)) {
ret = PTR_ERR(chip->ce_log);
dev_err(dev, "failed to obtain logbuffer, ret=%d\n", ret);
chip->ce_log = NULL;
}
scnprintf(monitor_name, sizeof(monitor_name), "%s_%s",
chip->max77779_fg_psy_desc.name, "monitor");
chip->monitor_log = logbuffer_register(monitor_name);
if (IS_ERR(chip->monitor_log)) {
ret = PTR_ERR(chip->monitor_log);
dev_err(dev, "failed to obtain logbuffer, ret=%d\n", ret);
chip->monitor_log = NULL;
}
ret = of_property_read_u32(dev->of_node, "google,bhi-fcn-count",
&chip->bhi_fcn_count);
if (ret < 0)
chip->bhi_fcn_count = BHI_CAP_FCN_COUNT;
/* use VFSOC until it can confirm that FG Model is running */
chip->reg_prop_capacity_raw = MAX77779_FG_VFSOC;
INIT_DELAYED_WORK(&chip->cap_estimate.settle_timer,
batt_ce_capacityfiltered_work);
INIT_DELAYED_WORK(&chip->init_work, max77779_fg_init_work);
/* TODO: b/283487421 - Implement model loading */
// INIT_DELAYED_WORK(&chip->model_work, max77779_fg_model_work);
schedule_delayed_work(&chip->init_work, 0);
return 0;
i2c_unregister:
i2c_unregister_device(chip->secondary);
i2c_unregister_primary:
i2c_unregister_device(chip->primary);
devm_kfree(dev, chip);
return ret;
}
static int max77779_fg_remove(struct i2c_client *client)
{
struct max77779_fg_chip *chip = i2c_get_clientdata(client);
if (chip->ce_log) {
logbuffer_unregister(chip->ce_log);
chip->ce_log = NULL;
}
max77779_free_data(chip->model_data);
cancel_delayed_work(&chip->init_work);
/* TODO: b/283487421 - Implement model loading */
// cancel_delayed_work(&chip->model_work);
if (chip->primary->irq)
free_irq(chip->primary->irq, chip);
if (chip->secondary)
i2c_unregister_device(chip->secondary);
power_supply_unregister(chip->psy);
return 0;
}
static const struct of_device_id max77779_of_match[] = {
{ .compatible = "maxim,max77779fg"},
{},
};
MODULE_DEVICE_TABLE(of, max77779_of_match);
static const struct i2c_device_id max77779_fg_id[] = {
{"max77779_fg", 0},
{}
};
MODULE_DEVICE_TABLE(i2c, max77779_fg_id);
#ifdef CONFIG_PM_SLEEP
static int max77779_pm_suspend(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct max77779_fg_chip *chip = i2c_get_clientdata(client);
pm_runtime_get_sync(chip->dev);
chip->resume_complete = false;
pm_runtime_put_sync(chip->dev);
return 0;
}
static int max77779_pm_resume(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct max77779_fg_chip *chip = i2c_get_clientdata(client);
pm_runtime_get_sync(chip->dev);
chip->resume_complete = true;
if (chip->irq_disabled) {
enable_irq(chip->primary->irq);
chip->irq_disabled = false;
}
pm_runtime_put_sync(chip->dev);
return 0;
}
#endif
static const struct dev_pm_ops max77779_pm_ops = {
SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(max77779_pm_suspend, max77779_pm_resume)
};
static struct i2c_driver max77779_fg_i2c_driver = {
.driver = {
.name = "max77779-fg",
.of_match_table = max77779_of_match,
.pm = &max77779_pm_ops,
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
},
.id_table = max77779_fg_id,
.probe = max77779_fg_probe,
.remove = max77779_fg_remove,
};
module_i2c_driver(max77779_fg_i2c_driver);
MODULE_AUTHOR("AleX Pelosi <[email protected]>");
MODULE_AUTHOR("Keewan Jung <[email protected]>");
MODULE_AUTHOR("Jenny Ho <[email protected]>");
MODULE_AUTHOR("Daniel Okazaki <[email protected]>");
MODULE_DESCRIPTION("MAX77779 Fuel Gauge");
MODULE_LICENSE("GPL");