blob: 623dbf5e20e1073fd6cb4a53cbe066e35502cc05 [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/debugfs.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include "max77779_fg.h"
/* sync from google/logbuffer.c */
#define LOG_BUFFER_ENTRY_SIZE 256
#define MAX77779_FG_TPOR_MS 800
#define MAX77779_FG_TICLR_MS 500
#define MAX77779_FG_I2C_DRIVER_NAME "max77779_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 */
#define MAX77779_FG_FWUPDATE_SOC 95
#define MAX77779_FG_FWUPDATE_SOC_RAW 0x5F00 /* soc 95% */
enum max77779_fg_command_bits {
MAX77779_FG_COMMAND_HARDWARE_RESET = 0x000F,
};
#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_EVENT_FULLCAPNOM_LOW BIT(0)
#define MAX77779_FG_EVENT_FULLCAPNOM_HIGH BIT(1)
#define MAX77779_FG_EVENT_REPSOC_EDET BIT(2)
#define MAX77779_FG_EVENT_REPSOC_FDET BIT(3)
#define MAX77779_FG_EVENT_REPSOC BIT(4)
#define MAX77779_FG_EVENT_VFOCV BIT(5)
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_update_cycle_count(struct max77779_fg_chip *chip);
static int max77779_fg_apply_n_register(struct max77779_fg_chip *chip);
static u16 max77779_fg_save_battery_cycle(struct max77779_fg_chip *chip, u16 reg_cycle);
/* Do not move reg_write_nolock to public header */
int max77779_external_fg_reg_write_nolock(struct device *dev, uint16_t reg, uint16_t val);
static struct mutex section_lock;
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_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 10μΩ */
return div_s64((s64) val * 156250, rsense);
}
static inline int reg_to_cycles(u32 val)
{
/* LSB: 25% of one cycle */
return DIV_ROUND_CLOSEST(val * 25, 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) * 10;
}
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_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 bool max77779_fg_reg_can_modify(struct max77779_fg_chip* chip)
{
/* model_lock is already acquired by the caller and chip is already valid */
if (chip->fw_update_mode || chip->por)
return false;
return true;
}
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_section(const struct maxfg_regmap *map, enum max77779_fg_reg_sections section, bool enabled)
{
int ret, i;
u16 data;
mutex_lock(&section_lock);
ret = REGMAP_READ(map, MAX77779_FG_USR, &data);
if (ret)
goto unlock_exit;
switch (section) {
case MAX77779_FG_RAM_SECTION: /* addr: 0x36, reg: 0x00 ... 0xDF */
data = _max77779_fg_usr_vlock_set(data, enabled);
break;
case MAX77779_FG_FUNC_SECTION: /* addr: 0x36, reg: 0xE0 ... 0xEE */
data = _max77779_fg_usr_rlock_set(data, enabled);
break;
case MAX77779_FG_NVM_SECTION: /* addr: 0x37 */
data = _max77779_fg_usr_nlock_set(data, enabled);
break;
case MAX77779_FG_ALL_SECTION:
data = _max77779_fg_usr_vlock_set(data, enabled);
data = _max77779_fg_usr_rlock_set(data, enabled);
data = _max77779_fg_usr_nlock_set(data, enabled);
break;
default:
pr_err("Failed to lock section %d\n", section);
goto unlock_exit;
}
/* Requires write twice */
for (i = 0; i < 2; i++) {
ret = REGMAP_WRITE(map, MAX77779_FG_USR, data);
if (ret)
goto unlock_exit;
}
unlock_exit:
mutex_unlock(&section_lock);
return ret;
}
static int max77779_fg_resume_check(struct max77779_fg_chip *chip)
{
int ret = 0;
pm_runtime_get_sync(chip->dev);
if (!chip->init_complete || !chip->resume_complete)
ret = -EAGAIN;
pm_runtime_put_sync(chip->dev);
return ret;
}
/* NOTE: it might not be static inline depending on how it's used */
static inline int max77779_fg_usr_lock(const struct maxfg_regmap *map, unsigned int reg, bool enabled) {
switch (reg) {
case 0x00 ... 0xDF:
return max77779_fg_usr_lock_section(map, MAX77779_FG_RAM_SECTION, enabled);
case 0xE0 ... 0xEE:
return max77779_fg_usr_lock_section(map, MAX77779_FG_FUNC_SECTION, enabled);
default:
pr_err("Failed to translate reg 0x%X to section\n", reg);
return -EINVAL;
}
}
int max77779_fg_register_write(const struct maxfg_regmap *map,
unsigned int reg, u16 value, bool verify)
{
int ret, rc;
ret = max77779_fg_usr_lock(map, reg, 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);
rc = max77779_fg_usr_lock(map, reg, true);
if (rc)
pr_err("Failed to lock ret=%d\n", rc);
return ret;
}
int max77779_fg_nregister_write(const struct maxfg_regmap *map,
const struct maxfg_regmap *debug_map,
unsigned int reg, u16 value, bool verify)
{
int ret, rc;
ret = max77779_fg_usr_lock_section(map, MAX77779_FG_NVM_SECTION, false);
if (ret) {
pr_err("Failed to unlock ret=%d\n", ret);
return ret;
}
if (verify)
ret = REGMAP_WRITE_VERIFY(debug_map, reg, value);
else
ret = REGMAP_WRITE(debug_map, reg, value);
if (ret)
pr_err("Failed to write reg verify=%d ret=%d\n", verify, ret);
rc = max77779_fg_usr_lock_section(map, MAX77779_FG_NVM_SECTION, true);
if (rc)
pr_err("Failed to lock ret=%d\n", rc);
return ret;
}
int max77779_external_fg_reg_read(struct device *dev, uint16_t reg, uint16_t *val)
{
struct max77779_fg_chip *chip = dev_get_drvdata(dev);
unsigned int tmp = 0;
int ret;
if (!chip || !chip->regmap.regmap)
return -ENODEV;
if (max77779_fg_resume_check(chip))
return -EAGAIN;
tmp = *val;
ret = regmap_read(chip->regmap.regmap, reg, &tmp);
if (ret < 0)
return ret;
*val = tmp & 0xFFFF;
return ret;
}
EXPORT_SYMBOL_GPL(max77779_external_fg_reg_read);
int max77779_external_fg_reg_write(struct device *dev, uint16_t reg, uint16_t val)
{
struct max77779_fg_chip *chip = dev_get_drvdata(dev);
int rc = -EBUSY;
if (!chip || !chip->regmap.regmap)
return -ENODEV;
if (max77779_fg_resume_check(chip))
return -EAGAIN;
mutex_lock(&chip->model_lock);
if (max77779_fg_reg_can_modify(chip))
rc = max77779_fg_register_write(&chip->regmap, reg, val, true);
mutex_unlock(&chip->model_lock);
return rc;
}
EXPORT_SYMBOL_GPL(max77779_external_fg_reg_write);
/*
* special reg_write only for max77779_fwupdate - do no use this API
* - it will not change the lock status
*/
int max77779_external_fg_reg_write_nolock(struct device *dev, uint16_t reg, uint16_t val)
{
struct max77779_fg_chip *chip = dev_get_drvdata(dev);
if (!chip || !chip->regmap.regmap)
return -ENODEV;
if (max77779_fg_resume_check(chip))
return -EAGAIN;
return regmap_write(chip->regmap.regmap, reg, val);
}
EXPORT_SYMBOL_GPL(max77779_external_fg_reg_write_nolock);
/*
* 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_FG_LOAD_MODEL_DISABLED;
const bool pending = chip->model_reload > MAX77779_FG_LOAD_MODEL_IDLE;
dev_info(chip->dev, "model_reload=%d force=%d pending=%d disabled=%d\n",
chip->model_reload, force, pending, disabled);
if (!force && (pending || disabled))
return -EEXIST;
gbms_logbuffer_devlog(chip->ce_log, chip->dev, LOGLEVEL_INFO, 0, LOGLEVEL_INFO,
"Schedule Load FG Model, ID=%d, ver:%d->%d",
chip->batt_id, max77779_model_read_version(chip->model_data),
max77779_fg_model_version(chip->model_data));
chip->model_reload = MAX77779_FG_LOAD_MODEL_REQUEST;
chip->model_ok = false;
chip->por = true;
mod_delayed_work(system_wq, &chip->model_work, 0);
return 0;
}
/* ----------------------------------------------------------------------- */
static ssize_t 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);
len += scnprintf(&buf[len], PAGE_SIZE - len, "ATT: %d FAIL: %d\n", chip->ml_cnt,
chip->ml_fails);
mutex_unlock(&chip->model_lock);
return len;
}
static DEVICE_ATTR_RO(model_state);
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",
maxfg_read_resistance(&chip->regmap, chip->RSense));
}
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)
{
if (chip->fw_update_mode) {
*data = MAX77779_FG_FWUPDATE_SOC_RAW;
return 0;
}
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;
if (chip->fw_update_mode)
return MAX77779_FG_FWUPDATE_SOC;
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;
if (chip->fw_update_mode)
return MAX77779_FG_FWUPDATE_SOC;
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)
{
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);
} 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);
}
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_update_battery_qh_based_capacity(struct max77779_fg_chip *chip)
{
u16 data;
int current_qh, err = 0;
if (chip->por)
return -EINVAL;
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;
}
/* max77779_fg_restore_battery_cycle need to be protected by chip->model_lock */
static int 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;
}
mutex_lock(&chip->save_data_lock);
ret = gbms_storage_read(GBMS_TAG_CNHS, &eeprom_cycle, sizeof(eeprom_cycle));
if (ret != sizeof(eeprom_cycle)) {
mutex_unlock(&chip->save_data_lock);
dev_info(chip->dev, "Fail to read eeprom cycle count (%d)", ret);
return ret;
}
if (eeprom_cycle == 0xFFFF) { /* empty storage */
mutex_unlock(&chip->save_data_lock);
max77779_fg_save_battery_cycle(chip, reg_cycle);
return -EINVAL;
}
chip->eeprom_cycle = eeprom_cycle;
mutex_unlock(&chip->save_data_lock);
dev_info(chip->dev, "reg_cycle:%d, eeprom_cycle:%d, update:%c",
reg_cycle, chip->eeprom_cycle, chip->eeprom_cycle > reg_cycle ? 'Y' : 'N');
if (chip->eeprom_cycle > reg_cycle) {
ret = MAX77779_FG_REGMAP_WRITE_VERIFY(&chip->regmap, MAX77779_FG_Cycles,
chip->eeprom_cycle);
if (ret < 0)
dev_warn(chip->dev, "fail to update cycles (%d)", ret);
}
return ret;
}
static u16 max77779_fg_save_battery_cycle(struct max77779_fg_chip *chip, u16 reg_cycle)
{
int ret = 0;
__pm_stay_awake(chip->fg_wake_lock);
mutex_lock(&chip->save_data_lock);
if (chip->por || reg_cycle == 0 || reg_cycle <= chip->eeprom_cycle)
goto max77779_fg_save_battery_cycle_exit;
ret = gbms_storage_write(GBMS_TAG_CNHS, &reg_cycle, sizeof(reg_cycle));
if (ret != sizeof(reg_cycle)) {
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", chip->eeprom_cycle, reg_cycle);
chip->eeprom_cycle = reg_cycle;
}
max77779_fg_save_battery_cycle_exit:
mutex_unlock(&chip->save_data_lock);
__pm_relax(chip->fg_wake_lock);
return chip->eeprom_cycle;
}
#define MAX17201_HIST_CYCLE_COUNT_OFFSET 0x4
#define MAX17201_HIST_TIME_OFFSET 0xf
static int max77779_fg_get_cycle_count(struct max77779_fg_chip *chip)
{
return chip->cycle_count;
}
static int max77779_fg_update_cycle_count(struct max77779_fg_chip *chip)
{
int err;
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;
/* If cycle register hasn't been successfully restored from eeprom */
if (reg_cycle < chip->eeprom_cycle) {
mutex_lock(&chip->model_lock);
err = max77779_fg_restore_battery_cycle(chip);
mutex_unlock(&chip->model_lock);
if (err)
return 0;
/* the value of MAX77779_FG_Cycles will be set as chip->eeprom_cycle */
reg_cycle = chip->eeprom_cycle;
} else {
max77779_fg_save_battery_cycle(chip, reg_cycle);
}
chip->cycle_count = reg_to_cycles((u32)reg_cycle);
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;
}
static int batt_ce_regmap_write(struct maxfg_regmap *map,
const struct maxfg_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 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;
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;
}
/* call holding chip->model_lock */
static int max77779_fg_check_impedance(struct max77779_fg_chip *chip, u16 *th)
{
struct maxfg_regmap *map = &chip->regmap;
int soc, temp, cycle_count, ret;
u16 data, timerh;
if (!chip->model_ok)
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;
}
/* 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 maxfg_read_resistance(&chip->regmap, chip->RSense);
}
/* 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)
return -ENODATA;
return reg_to_time_hr(timerh, chip);
}
static int max77779_fg_find_pmic(struct max77779_fg_chip *chip)
{
if (chip->pmic_dev)
return 0;
chip->pmic_dev = max77779_get_dev(chip->dev, MAX77779_PMIC_OF_NAME);
return chip->pmic_dev == NULL ? -ENXIO : 0;
}
static int max77779_fg_get_fw_ver(struct max77779_fg_chip *chip)
{
uint8_t fw_rev, fw_sub_rev, pmic_revision;
u16 fg_ic_info;
int ret;
ret = max77779_fg_find_pmic(chip);
if (ret) {
dev_err(chip->dev, "Error finding pmic\n");
return ret;
}
ret = max77779_external_pmic_reg_read(chip->pmic_dev, MAX77779_PMIC_RISCV_FW_REV, &fw_rev);
if (ret < 0)
return ret;
ret = max77779_external_pmic_reg_read(chip->pmic_dev, MAX77779_PMIC_RISCV_FW_SUB_REV,
&fw_sub_rev);
if (ret < 0)
return ret;
chip->fw_rev = fw_rev;
chip->fw_sub_rev = fw_sub_rev;
ret = max77779_external_pmic_reg_read(chip->pmic_dev, MAX77779_PMIC_REVISION,
&pmic_revision);
if (ret < 0)
return ret;
ret = REGMAP_READ(&chip->regmap, MAX77779_FG_ic_info, &fg_ic_info);
gbms_logbuffer_devlog(chip->ce_log, chip->dev, LOGLEVEL_INFO, 0, LOGLEVEL_INFO,
"FW_REV=%d, FW_SUB_REV=%d, PMIC_VER/REV=%d/PASS%d, TestProgramRev=%d",
chip->fw_rev, chip->fw_sub_rev,
_max77779_pmic_revision_ver_get(pmic_revision),
_max77779_pmic_revision_rev_get(pmic_revision),
_max77779_fg_ic_info_testprogramrev_get(fg_ic_info));
return 0;
}
/* Report fake temp 22 degree if firmware < 1.15 */
#define MAX77779_FG_FAKE_TEMP_FW_REV 1
#define MAX77779_FG_FAKE_TEMP_FW_SUBREV 15
#define MAX77779_FG_FAKE_TEMP 220
static int max77779_fg_get_temp(struct max77779_fg_chip *chip)
{
u16 data = 0;
int err = 0;
if (!chip->fw_rev && !chip->fw_sub_rev)
max77779_fg_get_fw_ver(chip);
if (chip->fw_rev == MAX77779_FG_FAKE_TEMP_FW_REV &&
chip->fw_sub_rev < MAX77779_FG_FAKE_TEMP_FW_SUBREV)
return MAX77779_FG_FAKE_TEMP;
err = REGMAP_READ(&chip->regmap, MAX77779_FG_Temp, &data);
if (err < 0)
return MAX77779_FG_FAKE_TEMP;
return reg_to_deci_deg_cel(data);
}
static int max77779_adjust_cgain(struct max77779_fg_chip *chip, unsigned int otp_revision)
{
u16 i_gtrim, i_otrim, ro_cgain, v_cgain;
int i_otrim_real;
int err;
err = REGMAP_READ(&chip->regmap, MAX77779_FG_TrimIbattGain, &i_gtrim);
if (err < 0)
return err;
err = REGMAP_READ(&chip->regmap, MAX77779_FG_TrimBattOffset, &i_otrim);
if (err < 0)
return err;
/* i_gtrim_real = ((-1) * (i_gtrim & 0x0800)) | (i_gtrim & 0x07FF); */
i_otrim_real = ((-1) * (i_otrim & 0x0080)) | (i_otrim & 0x007F);
err = REGMAP_READ(&chip->regmap, MAX77779_FG_CGain, &ro_cgain);
if (err < 0)
return err;
v_cgain = ro_cgain & 0xFFC0;
if (i_otrim_real > 32)
v_cgain = v_cgain | 0x20; /* -32 & 0x3F */
else if (i_otrim_real < -31)
v_cgain = v_cgain | 0x1F; /* 31 & 0x3F */
else
v_cgain = v_cgain | (((-1) * i_otrim_real) & 0x3F);
gbms_logbuffer_devlog(chip->ce_log, chip->dev, LOGLEVEL_INFO, 0, LOGLEVEL_INFO,
"OTP_VER:%d,%02X:%04X,%02X:%04X,%02X:%04X,trim:%d,new Cgain:%04X",
otp_revision, MAX77779_FG_TrimIbattGain, i_gtrim,
MAX77779_FG_TrimBattOffset, i_otrim, MAX77779_FG_CGain, ro_cgain,
i_otrim_real, v_cgain);
if (v_cgain == ro_cgain)
return 0;
err = MAX77779_FG_REGMAP_WRITE(&chip->regmap, MAX77779_FG_CGain, v_cgain);
if (err < 0)
return err;
return 0;
}
#define CHECK_CURRENT_OFFSET_OTP_REVISION 2
static void max77779_current_offset_check(struct max77779_fg_chip *chip)
{
uint8_t otp_revision;
int ret;
if (chip->current_offset_check_done)
return;
ret = max77779_fg_find_pmic(chip);
if (ret) {
dev_err(chip->dev, "Error finding pmic\n");
return;
}
ret = max77779_external_pmic_reg_read(chip->pmic_dev, MAX77779_PMIC_OTP_REVISION,
&otp_revision);
if (ret < 0) {
dev_err(chip->dev, "failed to read PMIC_OTP_REVISION\n");
return;
}
if (otp_revision > CHECK_CURRENT_OFFSET_OTP_REVISION)
goto done;
ret = max77779_adjust_cgain(chip, otp_revision);
if (ret < 0)
return;
done:
chip->current_offset_check_done = true;
}
static int max77779_fg_monitor_log_data(struct max77779_fg_chip *chip, bool force_log)
{
int ret, charge_counter = -1;
u16 repsoc, data;
char buf[256] = { 0 };
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 = maxfg_reg_log_data(&chip->regmap, &chip->regmap_debug, buf);
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_devlog(chip->monitor_log, chip->dev, LOGLEVEL_INFO, 0, LOGLEVEL_INFO,
"0x%04X %02X:%04X %s CC:%d", MONITOR_TAG_RM, MAX77779_FG_RepSOC, data,
buf, charge_counter);
chip->pre_repsoc = repsoc;
return ret;
}
static int max77779_is_relaxed(struct max77779_fg_chip *chip)
{
return maxfg_ce_relaxed(&chip->regmap, MAX77779_FG_FStat_RelDt_MASK,
(u16*)chip->cb_lh.latest_entry);
}
static int max77779_fg_monitor_log_learning(struct max77779_fg_chip *chip, bool force)
{
bool log_it, seed = !chip->cb_lh.latest_entry;
char* buf;
int ret;
/* do noting if no changes on dpacc/dqacc or relaxation */
log_it = force || seed ||
maxfg_ce_relaxed(&chip->regmap, MAX77779_FG_FStat_RelDt_MASK |
MAX77779_FG_FStat_RelDt2_MASK, (u16*)chip->cb_lh.latest_entry);
if (!log_it)
return 0;
ret = maxfg_capture_registers(&chip->cb_lh);
if (ret < 0) {
dev_err(chip->dev, "cannot read learning parameters (%d)\n", ret);
return ret;
}
/* no need to log at boot */
if (seed)
return 0;
buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
if (!buf) {
dev_err(chip->dev, "no memory for log string buffer\n");
return -ENOMEM;
}
mutex_lock(&chip->cb_lh.cb_wr_lock);
ret = maxfg_capture_to_cstr(&chip->cb_lh.config,
(u16 *)chip->cb_lh.latest_entry,
buf, PAGE_SIZE);
mutex_unlock(&chip->cb_lh.cb_wr_lock);
if (ret > 0)
gbms_logbuffer_devlog(chip->monitor_log, chip->dev,
LOGLEVEL_INFO, 0, LOGLEVEL_INFO,
"0x%04X %s", MONITOR_TAG_LH, buf);
kfree(buf);
kobject_uevent(&chip->dev->kobj, KOBJ_CHANGE);
return 0;
}
/* same as max77779_fg_nregister_write() */
static int max77779_dynrel_relaxcfg(struct max77779_fg_chip *chip, bool enable)
{
struct maxfg_regmap *regmap = &chip->regmap;
int ret, rc;
rc = max77779_fg_usr_lock_section(regmap, MAX77779_FG_NVM_SECTION, false);
if (rc < 0)
return -EIO;
/* enable use ->relcfg_allow , !enable -> relcfg_inhibit */
ret = maxfg_dynrel_relaxcfg(&chip->dynrel_state, &chip->regmap_debug,
enable);
dev_dbg(chip->dev, "dynrel: relaxcfg enable=%d (%d)\n",
enable, ret);
rc = max77779_fg_usr_lock_section(regmap, MAX77779_FG_NVM_SECTION, true);
if (rc)
return -EPERM;
return ret;
}
static int max77779_fg_get_learn_stage(struct maxfg_regmap *regmap)
{
u16 learncfg;
int ret;
ret = maxfg_reg_read(regmap, MAXFG_TAG_learn, &learncfg);
if (ret < 0)
return -EIO;
return _max77779_fg_learncfg_learnstage_get(learncfg);
}
/*
* on init and when the configuration changes
* <0 error, 0 success, dynrel disabled
*/
static int max77779_dynrel_config(struct max77779_fg_chip *chip)
{
struct logbuffer *mon = chip->ce_log;
bool relax_allowed;
int ret;
maxfg_dynrel_log_cfg(mon, chip->dev, &chip->dynrel_state);
/* always allow_relax when in override mode or when disabled */
relax_allowed = chip->dynrel_state.override_mode ||
chip->dynrel_state.vfsoc_delta == 0 ||
maxfg_dynrel_can_relax(&chip->dynrel_state, &chip->regmap);
/* set relaxconfig to a value consistent with mode */
ret = max77779_dynrel_relaxcfg(chip, relax_allowed);
if (ret < 0) {
dev_err(chip->dev, "dynrel: cannot configure relaxcfg=%d (%d)\n",
relax_allowed, ret);
ret = max77779_dynrel_relaxcfg(chip, true);
if (ret < 0) {
/* failed to change relax twice! disable dynrel */
dev_err(chip->dev, "dynrel: cannot force relaxcfg (%d)\n", ret);
chip->dynrel_state.vfsoc_delta = 0;
} else if (!relax_allowed) {
dev_err(chip->dev, "dynrel: cannot inhibit relax (%d)\n", ret);
relax_allowed = true;
}
}
chip->dynrel_state.relax_allowed = relax_allowed;
return ret;
}
static void max77779_fg_dynrelax(struct max77779_fg_chip *chip)
{
struct maxfg_dynrel_state *dr_state = &chip->dynrel_state;
struct maxfg_regmap *regmap = &chip->regmap;
struct logbuffer *mon = chip->ce_log;
int learn_stage, ret;
bool relaxed;
u16 fstat;
/* dynamic relaxation */
if (!dr_state->vfsoc_delta) {
dev_dbg(chip->dev, "dynrel: disabled vfsoc_delta=%d\n",
dr_state->vfsoc_delta);
return;
}
learn_stage = max77779_fg_get_learn_stage(regmap);
if (learn_stage < dr_state->learn_stage_min) {
dev_dbg(chip->dev, "dynrel: learn_stage=%d < %d\n",
learn_stage, dr_state->learn_stage_min);
return;
}
relaxed = maxfg_is_relaxed(regmap, &fstat, MAX77779_FG_FStat_RelDt_MASK);
if (!relaxed) {
bool can_relax;
can_relax = maxfg_dynrel_can_relax(dr_state, regmap);
dev_dbg(chip->dev, "dynrel: can_relax=%d relax_allowed=%d sticky=%d\n",
can_relax, dr_state->relax_allowed, dr_state->sticky_cnt);
if (can_relax != dr_state->relax_allowed) {
/*
* keeps ->relax_allowed aligned with can_relax
* doesn't really change relaxconfig in ->override_mode
*/
ret = max77779_dynrel_relaxcfg(chip, can_relax);
if (ret < 0) {
dev_err(chip->dev,
"dynrel: fail to change can_relax=%d (%d)\n",
can_relax, ret);
} else {
dr_state->relax_allowed = can_relax;
dr_state->mark_last = fstat;
dr_state->sticky_cnt = 0;
}
maxfg_dynrel_log(mon, chip->dev, fstat, dr_state);
} else {
mon = NULL; /* do not pollute the logbuffer */
}
return;
}
/* mark relaxation, and prevent more */
if (dr_state->relax_allowed) {
ret = maxfg_dynrel_mark_det(dr_state, regmap);
if (ret < 0) {
dev_err(chip->dev, "dynrel: cannot mark relax (%d)\n", ret);
return;
}
ret = max77779_dynrel_relaxcfg(chip, false);
if (ret == 0) {
dr_state->relax_allowed = false;
dr_state->mark_last = fstat;
dr_state->sticky_cnt = 0;
}
maxfg_dynrel_log_rel(mon, chip->dev, fstat, dr_state);
return;
}
/* relaxed when relaxation is NOT allowed, normal in override mode */
if (dr_state->override_mode) {
ret = maxfg_dynrel_override_dxacc(dr_state, regmap);
dev_dbg(chip->dev, "dynrel: dxacc override (%d)\n", ret);
if (ret < 0)
dev_err(chip->dev, "dynrel: allowed=%d sticky_cnt=%d (%d)\n",
dr_state->relax_allowed, dr_state->sticky_cnt, ret);
return;
}
/* relaxConfig mode: reldt clears shortly after changing relaxcfg */
ret = max77779_dynrel_relaxcfg(chip, false);
if (ret < 0 || dr_state->monitor)
dev_warn(chip->dev, "dynrel: allowed=%d sticky_cnt=%d (%d)\n",
dr_state->relax_allowed, dr_state->sticky_cnt, ret);
dr_state->sticky_cnt += 1;
}
static void max77779_fg_check_learning(struct max77779_fg_chip *chip)
{
/* check for relaxation event and log it */
max77779_fg_monitor_log_learning(chip, false);
/* run dynamic relax if enabled */
max77779_fg_dynrelax(chip);
}
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 maxfg_regmap *map = &chip->regmap;
int rc, err = 0;
u16 data = 0;
mutex_lock(&chip->model_lock);
if (max77779_fg_resume_check(chip) || !chip->model_ok) {
mutex_unlock(&chip->model_lock);
return -EAGAIN;
}
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
max77779_fg_check_learning(chip);
val->intval = max77779_fg_get_battery_status(chip);
if (val->intval < 0)
val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
/*
* Capacity estimation must run only once.
* NOTE: this is a getter with a side effect
*/
if (val->intval == POWER_SUPPLY_STATUS_FULL)
batt_ce_start(&chip->cap_estimate,
chip->cap_estimate.cap_tsettle);
break;
case POWER_SUPPLY_PROP_CAPACITY:
val->intval = max77779_fg_get_battery_soc(chip);
/* fake soc 50% on error */
if (val->intval < 0)
val->intval = DEFAULT_BATT_FAKE_CAPACITY;
break;
case POWER_SUPPLY_PROP_CHARGE_COUNTER:
rc = max77779_fg_update_battery_qh_based_capacity(chip);
/* use previous capacity on error */
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 25%
*/
rc = max77779_fg_get_cycle_count(chip);
if (rc < 0)
break;
/* rc is cycle_count */
if (rc <= FULLCAPNOM_STABILIZE_CYCLES)
rc = REGMAP_READ(map, MAX77779_FG_DesignCap, &data);
else
rc = REGMAP_READ(map, MAX77779_FG_FullCapNom, &data);
if (rc == 0)
val->intval = reg_to_capacity_uah(data, chip);
break;
case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
rc = REGMAP_READ(map, MAX77779_FG_DesignCap, &data);
if (rc == 0)
val->intval = reg_to_capacity_uah(data, chip);
break;
/* current is positive value when flowing to device */
case POWER_SUPPLY_PROP_CURRENT_AVG:
rc = REGMAP_READ(map, MAX77779_FG_AvgCurrent, &data);
if (rc == 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:
rc = REGMAP_READ(map, MAX77779_FG_Current, &data);
if (rc == 0)
val->intval = -reg_to_micro_amp(data, chip->RSense);
break;
case POWER_SUPPLY_PROP_CYCLE_COUNT:
rc = max77779_fg_get_cycle_count(chip);
if (rc < 0)
break;
/* rc is cycle_count */
val->intval = rc;
break;
case POWER_SUPPLY_PROP_PRESENT:
if (chip->fake_battery != -1) {
val->intval = chip->fake_battery;
} else {
rc = REGMAP_READ(map, MAX77779_FG_FG_INT_STS, &data);
if (rc < 0)
break;
/* BST is 0 when the battery is present */
val->intval = !(data & MAX77779_FG_FG_INT_MASK_Bst_m_MASK);
if (!val->intval)
break;
/*
* chip->por prevent garbage in cycle count
* detect POR interrupt and trigger irq thread
*/
if (!chip->por && (data & MAX77779_FG_FG_INT_MASK_POR_m_MASK)) {
/* trigger reload model */
mutex_unlock(&chip->model_lock);
max77779_fg_irq_thread_fn(-1, chip);
return err;
}
}
break;
case POWER_SUPPLY_PROP_TEMP:
val->intval = max77779_fg_get_temp(chip);
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:
rc = REGMAP_READ(map, MAX77779_FG_AvgVCell, &data);
if (rc == 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:
rc = REGMAP_READ(map, MAX77779_FG_VCell, &data);
if (rc == 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 == 0)
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;
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 = maxfg_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)
{
/* move gbms psp to max77779_gbms_fg_set_property */
return 0;
}
static int max77779_fg_property_is_writeable(struct power_supply *psy,
enum power_supply_property psp)
{
/* move gbms psp to max77779_gbms_fg_property_is_writeable */
return 0;
}
static int max77779_gbms_fg_get_property(struct power_supply *psy,
enum gbms_property psp,
union gbms_propval *val)
{
struct max77779_fg_chip *chip = (struct max77779_fg_chip *)
power_supply_get_drvdata(psy);
struct maxfg_regmap *map = &chip->regmap;
int err = 0;
u16 data = 0;
mutex_lock(&chip->model_lock);
if (max77779_fg_resume_check(chip) || !chip->model_ok ||
chip->model_reload != MAX77779_FG_LOAD_MODEL_IDLE) {
mutex_unlock(&chip->model_lock);
return -EAGAIN;
}
switch (psp) {
case GBMS_PROP_CAPACITY_RAW:
err = max77779_fg_get_capacity_raw(chip, &data);
if (err == 0)
val->prop.intval = (int)data;
break;
case GBMS_PROP_HEALTH_ACT_IMPEDANCE:
val->prop.intval = maxfg_health_get_ai(chip->dev, chip->bhi_acim, chip->RSense);
break;
case GBMS_PROP_HEALTH_IMPEDANCE:
val->prop.intval = max77779_fg_health_read_impedance(chip);
break;
case GBMS_PROP_RESISTANCE:
val->prop.intval = maxfg_read_resistance(map, chip->RSense);
break;
case GBMS_PROP_RESISTANCE_RAW:
val->prop.intval = maxfg_read_resistance_raw(map);
break;
case GBMS_PROP_RESISTANCE_AVG:
val->prop.intval = maxfg_read_resistance_avg(chip->RSense);
break;
case GBMS_PROP_BATTERY_AGE:
val->prop.intval = max77779_fg_get_age(chip);
break;
case GBMS_PROP_CHARGE_FULL_ESTIMATE:
val->prop.intval = batt_ce_full_estimate(&chip->cap_estimate);
break;
case GBMS_PROP_CAPACITY_FADE_RATE:
case GBMS_PROP_CAPACITY_FADE_RATE_FCR:
err = maxfg_get_fade_rate(chip->dev, chip->bhi_fcn_count, &val->prop.intval, psp);
break;
case GBMS_PROP_BATT_ID:
val->prop.intval = chip->batt_id;
break;
case GBMS_PROP_RECAL_FG:
/* TODO: under porting */
break;
default:
pr_debug("%s: route to max77779_fg_get_property, psp:%d\n", __func__, psp);
err = -ENODATA;
break;
}
if (err < 0)
pr_debug("error %d reading prop %d\n", err, psp);
mutex_unlock(&chip->model_lock);
return err;
}
static int max77779_gbms_fg_set_property(struct power_supply *psy,
enum gbms_property psp,
const union gbms_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);
if (max77779_fg_resume_check(chip) || chip->fw_update_mode) {
mutex_unlock(&chip->model_lock);
return -EAGAIN;
}
mutex_unlock(&chip->model_lock);
switch (psp) {
case GBMS_PROP_BATT_CE_CTRL:
mutex_lock(&ce->batt_ce_lock);
if (!chip->model_ok) {
mutex_unlock(&ce->batt_ce_lock);
return -EAGAIN;
}
if (val->prop.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->prop.intval);
mutex_unlock(&chip->model_lock);
break;
case GBMS_PROP_FG_REG_LOGGING:
max77779_fg_monitor_log_data(chip, !!val->prop.intval);
break;
case GBMS_PROP_RECAL_FG:
/* TODO: under porting */
break;
default:
pr_debug("%s: route to max77779_fg_set_property, psp:%d\n", __func__, psp);
return -ENODATA;
}
if (rc < 0)
return rc;
return 0;
}
static int max77779_gbms_fg_property_is_writeable(struct power_supply *psy,
enum gbms_property psp)
{
switch (psp) {
case GBMS_PROP_BATT_CE_CTRL:
case GBMS_PROP_HEALTH_ACT_IMPEDANCE:
return 1;
default:
break;
}
return 0;
}
static int max77779_fg_log_abnormal_events(struct max77779_fg_chip *chip, unsigned int curr_event,
unsigned int last_event)
{
int ret, i;
unsigned int changed;
char buf[LOG_BUFFER_ENTRY_SIZE] = {0};
ret = maxfg_reg_log_abnormal(&chip->regmap, &chip->regmap_debug, buf, sizeof(buf));
if (ret < 0)
return ret;
/* report when event changed (bitflip) */
changed = curr_event ^ last_event;
for (i = 1; changed > 0 ; i++, changed = changed >> 1, curr_event = curr_event >> 1) {
if (!(changed & 0x1))
continue;
gbms_logbuffer_devlog(chip->monitor_log, chip->dev,
LOGLEVEL_INFO, 0, LOGLEVEL_INFO,
"0x%04X %d %d%s",
MONITOR_TAG_AB, i, curr_event & 0x1, buf);
}
return 0;
}
static int max77779_fg_monitor_log_abnormal(struct max77779_fg_chip *chip)
{
int ret;
u16 data, fullcapnom, designcap, repsoc, mixsoc, edet, fdet, vfocv, avgvcell, ibat;
unsigned int curr_event;
ret = REGMAP_READ(&chip->regmap, MAX77779_FG_FullCapNom, &fullcapnom);
if (ret < 0)
return ret;
ret = REGMAP_READ(&chip->regmap, MAX77779_FG_DesignCap, &designcap);
if (ret < 0)
return ret;
ret = REGMAP_READ(&chip->regmap, MAX77779_FG_RepSOC, &data);
if (ret < 0)
return ret;
repsoc = (data >> 8) & 0x00FF;
ret = REGMAP_READ(&chip->regmap, MAX77779_FG_MixSOC, &data);
if (ret < 0)
return ret;
mixsoc = (data >> 8) & 0x00FF;
ret = REGMAP_READ(&chip->regmap, MAX77779_FG_FStat, &data);
if (ret < 0)
return ret;
edet = !!(data & MAX77779_FG_FStat_EDet_MASK);
ret = REGMAP_READ(&chip->regmap, MAX77779_FG_Status2, &data);
if (ret < 0)
return ret;
fdet = !!(data & MAX77779_FG_Status2_FullDet_MASK);
ret = REGMAP_READ(&chip->regmap, MAX77779_FG_VFOCV, &vfocv);
if (ret < 0)
return ret;
ret = REGMAP_READ(&chip->regmap, MAX77779_FG_AvgVCell, &avgvcell);
if (ret < 0)
return ret;
ret = REGMAP_READ(&chip->regmap, MAX77779_FG_Current, &ibat);
if (ret < 0)
return ret;
mutex_lock(&chip->check_event_lock);
curr_event = chip->abnormal_event_bits;
/*
* Always check stop condition first
*
* reason: unexpected FullCapNom Learning
* stop condition: next FullCapNom updated
* start condition: FullCapNom < DesignCap x 60%
*/
if (curr_event & MAX77779_FG_EVENT_FULLCAPNOM_LOW) {
if (fullcapnom != chip->last_fullcapnom)
curr_event &= ~MAX77779_FG_EVENT_FULLCAPNOM_LOW;
} else if (fullcapnom < (designcap * 60 / 100)) {
curr_event |= MAX77779_FG_EVENT_FULLCAPNOM_LOW;
chip->last_fullcapnom = fullcapnom;
}
/*
* reason: unexpected FullCapNom Learning
* stop condition: next FullCapNom updated
* start condition: FullCapNom > DesignCap x 115%
*/
if (curr_event & MAX77779_FG_EVENT_FULLCAPNOM_HIGH) {
if (fullcapnom != chip->last_fullcapnom)
curr_event &= ~MAX77779_FG_EVENT_FULLCAPNOM_HIGH;
} else if (fullcapnom > (designcap * 115 / 100)) {
curr_event |= MAX77779_FG_EVENT_FULLCAPNOM_HIGH;
chip->last_fullcapnom = fullcapnom;
}
/*
* reason: RepSoC not accurate
* stop condition: RepSoC > 20%
* start condition: RepSoC > 10% && Empty detection bit is set
*/
if (curr_event & MAX77779_FG_EVENT_REPSOC_EDET) {
if (repsoc > 20)
curr_event &= ~MAX77779_FG_EVENT_REPSOC_EDET;
} else if (repsoc > 10 && edet) {
curr_event |= MAX77779_FG_EVENT_REPSOC_EDET;
}
/*
* reason: RepSoC not accurate
* stop condition: RepSoc < 80%
* start condition: RepSoC < 90% && Full detection bit is set
*/
if (curr_event & MAX77779_FG_EVENT_REPSOC_FDET) {
if (repsoc < 80)
curr_event &= ~MAX77779_FG_EVENT_REPSOC_FDET;
} else if (repsoc < 90 && fdet) {
curr_event |= MAX77779_FG_EVENT_REPSOC_FDET;
}
/*
* reason: Repsoc not accurate
* stop condition: abs(MixSoC - RepSoC) < 20%
* start condition: abs(MixSoC - RepSoC) > 25%
*/
if (curr_event & MAX77779_FG_EVENT_REPSOC) {
if (abs(mixsoc - repsoc) < 20)
curr_event &= ~MAX77779_FG_EVENT_REPSOC;
} else if (abs(mixsoc - repsoc) > 25) {
curr_event |= MAX77779_FG_EVENT_REPSOC;
}
/*
* reason: VFOCV estimate might be wrong
* stop condition: VFOCV < (AvgVCell - 200mV) || VFOCV > (AvgVCell + 200mV)
* start condition: (VFOCV < (AvgVCell - 1V) || VFOCV > (AvgVCell + 1V))
* && abs(Current) < 5A
*/
if (curr_event & MAX77779_FG_EVENT_VFOCV) {
if (reg_to_micro_volt(vfocv) < (reg_to_micro_volt(avgvcell) - 200000) ||
reg_to_micro_volt(vfocv) > (reg_to_micro_volt(avgvcell) + 200000))
curr_event &= ~MAX77779_FG_EVENT_VFOCV;
} else if ((reg_to_micro_volt(vfocv) < (reg_to_micro_volt(avgvcell) - 1000000) ||
reg_to_micro_volt(vfocv) > (reg_to_micro_volt(avgvcell) + 1000000)) &&
abs(reg_to_micro_amp(ibat, chip->RSense)) < 5000000) {
curr_event |= MAX77779_FG_EVENT_VFOCV;
}
/* do nothing if no state change */
if (curr_event == chip->abnormal_event_bits) {
mutex_unlock(&chip->check_event_lock);
return 0;
}
ret = max77779_fg_log_abnormal_events(chip, curr_event, chip->abnormal_event_bits);
if (ret == 0)
kobject_uevent(&chip->dev->kobj, KOBJ_CHANGE);
chip->abnormal_event_bits = curr_event;
mutex_unlock(&chip->check_event_lock);
return ret;
}
/*
* A full reset restores the ICs to their power-up state the same as if power
* had been cycled.
*/
#define CMD_HW_RESET 0x000F
static int max77779_fg_full_reset(struct max77779_fg_chip *chip)
{
int ret = 0;
ret = max77779_fg_find_pmic(chip);
if (ret) {
dev_err(chip->dev, "Error finding pmic\n");
return ret;
}
ret = max77779_external_pmic_reg_write(chip->pmic_dev, MAX77779_PMIC_RISCV_COMMAND_HW,
CMD_HW_RESET);
dev_warn(chip->dev, "%s, ret=%d\n", __func__, ret);
if (ret == 0) {
msleep(MAX77779_FG_TPOR_MS);
/* check POR after reset */
max77779_fg_irq_thread_fn(-1, chip);
}
return ret;
}
static int max77779_fg_mask_por(struct max77779_fg_chip *chip, bool mask)
{
u16 fg_int_mask;
int err;
err = REGMAP_READ(&chip->regmap, MAX77779_FG_FG_INT_MASK, &fg_int_mask);
if (err)
return err;
if (mask)
fg_int_mask |= MAX77779_FG_FG_INT_MASK_POR_m_MASK;
else
fg_int_mask &= ~MAX77779_FG_FG_INT_MASK_POR_m_MASK;
err = MAX77779_FG_REGMAP_WRITE(&chip->regmap, MAX77779_FG_FG_INT_MASK, fg_int_mask);
return err;
}
static irqreturn_t max77779_fg_irq_thread_fn(int irq, void *obj)
{
struct max77779_fg_chip *chip = (struct max77779_fg_chip *)obj;
u16 fg_int_sts, fg_int_sts_clr;
int err = 0;
if (!chip || (irq != -1 && irq != chip->irq)) {
WARN_ON_ONCE(1);
return IRQ_NONE;
}
if (irq != -1 && max77779_fg_resume_check(chip)) {
dev_warn_ratelimited(chip->dev, "%s: irq skipped, irq%d\n", __func__, irq);
return IRQ_HANDLED;
}
/* b/336418454 lock to sync FG_INT_STS with model work */
mutex_lock(&chip->model_lock);
err = REGMAP_READ(&chip->regmap, MAX77779_FG_FG_INT_STS, &fg_int_sts);
if (err) {
dev_err_ratelimited(chip->dev, "%s i2c error reading INT status, IRQ_NONE\n", __func__);
mutex_unlock(&chip->model_lock);
return IRQ_NONE;
}
if (fg_int_sts == 0) {
dev_err_ratelimited(chip->dev, "fg_int_sts == 0, irq:%d\n", irq);
mutex_unlock(&chip->model_lock);
return IRQ_NONE;
}
dev_dbg(chip->dev, "FG_INT_STS:%04x\n", fg_int_sts);
/* only used to report health */
chip->health_status |= fg_int_sts;
fg_int_sts_clr = fg_int_sts;
if (fg_int_sts & MAX77779_FG_Status_PONR_MASK) {
/* Not clear POR interrupt here, model work will do */
fg_int_sts_clr &= ~MAX77779_FG_Status_PONR_MASK;
gbms_logbuffer_devlog(chip->ce_log, chip->dev,
LOGLEVEL_INFO, 0, LOGLEVEL_INFO,
"POR is set (FG_INT_STS:%04x), irq:%d, model_reload:%d",
fg_int_sts, irq, chip->model_reload);
/* trigger model load if not on-going */
err = max77779_fg_model_reload(chip, false);
if (err < 0)
dev_dbg(chip->dev, "unable to reload model, err=%d\n", err);
}
mutex_unlock(&chip->model_lock);
/* NOTE: should always clear everything except POR even if we lose state */
MAX77779_FG_REGMAP_WRITE(&chip->regmap, MAX77779_FG_FG_INT_STS, fg_int_sts_clr);
/* SOC interrupts need to go through all the time */
if (fg_int_sts & MAX77779_FG_Status_dSOCi_MASK) {
max77779_fg_monitor_log_data(chip, false);
max77779_fg_update_cycle_count(chip);
max77779_fg_monitor_log_abnormal(chip);
max77779_fg_check_learning(chip);
}
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 = 0;
mutex_lock(&chip->model_lock);
/* irq_disabled set by firmware update */
if (chip->irq_disabled)
ret = -EBUSY;
else if (val != 1)
ret = -EINVAL;
mutex_unlock(&chip->model_lock);
if (ret == 0)
ret = max77779_fg_full_reset(chip);
return ret;
}
DEFINE_SIMPLE_ATTRIBUTE(debug_fg_reset_fops, NULL, debug_fg_reset, "%llu\n");
int max77779_fg_enable_firmware_update(struct device *dev, bool enable) {
struct max77779_fg_chip *chip = dev_get_drvdata(dev);
int ret = -EAGAIN;
if (!chip)
return ret;
mutex_lock(&chip->model_lock);
if (max77779_fg_resume_check(chip))
goto max77779_fg_enable_firmware_update_exit;
/* enable/disable irq for firmware update */
if (enable && !chip->irq_disabled) {
chip->irq_disabled = true;
disable_irq_wake(chip->irq);
disable_irq(chip->irq);
} else if (!enable && chip->irq_disabled) {
chip->irq_disabled = false;
enable_irq(chip->irq);
enable_irq_wake(chip->irq);
}
chip->fw_update_mode = enable;
ret = 0;
max77779_fg_enable_firmware_update_exit:
mutex_unlock(&chip->model_lock);
return ret;
};
EXPORT_SYMBOL_GPL(max77779_fg_enable_firmware_update);
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");
static int max77779_log_learn_set(void *data, u64 val)
{
struct max77779_fg_chip *chip = (struct max77779_fg_chip *)data;
max77779_fg_monitor_log_learning(chip, true);
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(debug_log_learn_fops, NULL, max77779_log_learn_set, "%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, &chip->regmap_debug);
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_FG_LOAD_MODEL_DISABLED;
} else {
dev_info(chip->dev, "model_data ok for ID=%d\n", chip->batt_id);
chip->model_reload = MAX77779_FG_LOAD_MODEL_IDLE;
chip->designcap = max77779_get_designcap(chip->model_data);
}
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 int debug_fw_revision_get(void *data, u64 *val)
{
struct max77779_fg_chip *chip = (struct max77779_fg_chip *)data;
*val = chip->fw_rev;
return 0;
}
static int debug_fw_revision_set(void *data, u64 val)
{
struct max77779_fg_chip *chip = (struct max77779_fg_chip *)data;
chip->fw_rev = val;
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(debug_fw_revision_fops, debug_fw_revision_get,
debug_fw_revision_set, "%llu\n");
static int debug_fw_sub_revision_get(void *data, u64 *val)
{
struct max77779_fg_chip *chip = (struct max77779_fg_chip *)data;
*val = chip->fw_sub_rev;
return 0;
}
static int debug_fw_sub_revision_set(void *data, u64 val)
{
struct max77779_fg_chip *chip = (struct max77779_fg_chip *)data;
chip->fw_sub_rev = val;
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(debug_fw_sub_revision_fops, debug_fw_sub_revision_get,
debug_fw_sub_revision_set, "%llu\n");
static void max77779_fg_reglog_dump(struct maxfg_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 maxfg_reglog *reglog = (struct maxfg_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 ssize_t max77779_fg_show_custom_model(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 *tmp;
int len;
if (!chip->model_data)
return -EINVAL;
tmp = kmalloc(PAGE_SIZE, GFP_KERNEL);
if (!tmp)
return -ENOMEM;
mutex_lock(&chip->model_lock);
len = max77779_fg_model_cstr(tmp, PAGE_SIZE, chip->model_data);
mutex_unlock(&chip->model_lock);
if (len > 0)
len = simple_read_from_buffer(buf, count, ppos, tmp, len);
kfree(tmp);
return len;
}
static ssize_t max77779_fg_set_custom_model(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 *tmp;
int ret;
if (!chip->model_data)
return -EINVAL;
tmp = kmalloc(PAGE_SIZE, GFP_KERNEL);
if (!tmp)
return -ENOMEM;
ret = simple_write_to_buffer(tmp, PAGE_SIZE, ppos, user_buf, count);
if (!ret) {
kfree(tmp);
return -EFAULT;
}
mutex_lock(&chip->model_lock);
ret = max77779_fg_model_sscan(chip->model_data, tmp, count);
if (ret < 0)
count = ret;
mutex_unlock(&chip->model_lock);
kfree(tmp);
return count;
}
BATTERY_DEBUG_ATTRIBUTE(debug_custom_model_fops, max77779_fg_show_custom_model,
max77779_fg_set_custom_model);
static ssize_t max77779_fg_show_custom_param(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 *tmp;
int len;
if (!chip->model_data)
return -EINVAL;
tmp = kmalloc(PAGE_SIZE, GFP_KERNEL);
if (!tmp)
return -ENOMEM;
mutex_lock(&chip->model_lock);
len = max77779_fg_param_cstr(tmp, PAGE_SIZE, chip->model_data);
mutex_unlock(&chip->model_lock);
if (len > 0)
len = simple_read_from_buffer(buf, count, ppos, tmp, len);
kfree(tmp);
return len;
}
static ssize_t max77779_fg_set_custom_param(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 *tmp;
int ret;
if (!chip->model_data)
return -EINVAL;
tmp = kmalloc(PAGE_SIZE, GFP_KERNEL);
if (!tmp)
return -ENOMEM;
ret = simple_write_to_buffer(tmp, PAGE_SIZE, ppos, user_buf, count);
if (!ret) {
kfree(tmp);
return -EFAULT;
}
mutex_lock(&chip->model_lock);
ret = max77779_fg_param_sscan(chip->model_data, tmp, count);
if (ret < 0)
count = ret;
mutex_unlock(&chip->model_lock);
kfree(tmp);
return count;
}
BATTERY_DEBUG_ATTRIBUTE(debug_custom_param_fops, max77779_fg_show_custom_param,
max77779_fg_set_custom_param);
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 int debug_model_version_get(void *data, u64 *val)
{
struct max77779_fg_chip *chip = (struct max77779_fg_chip *)data;
*val = max77779_model_read_version(chip->model_data);
return 0;
}
static int debug_model_version_set(void *data, u64 val)
{
struct max77779_fg_chip *chip = (struct max77779_fg_chip *)data;
return max77779_model_write_version(chip->model_data, val);
}
DEFINE_SIMPLE_ATTRIBUTE(debug_model_version_fops, debug_model_version_get,
debug_model_version_set, "%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_N_REGMAP_WRITE(&chip->regmap, &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 maxfg_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 maxfg_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;
int ret;
ret = max77779_fg_save_battery_cycle(chip, (u16)val);
dev_info(chip->dev, "reset CNHS to %d, (ret=%d)\n", (int)val, ret);
return ret == sizeof(u16) ? 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;
}
DEFINE_SIMPLE_ATTRIBUTE(debug_reset_gmsr_fops, NULL, debug_gmsr_reset, "%llu\n");
static int debug_ini_reload(void *data, u64 val)
{
struct max77779_fg_chip *chip = data;
int ret;
if (chip->model_data)
max77779_free_data(chip->model_data);
/* re-init the model data (lookup in DT) */
ret = max77779_fg_init_model(chip);
dev_info(chip->dev, "ini_model (ret=%d)\n", ret);
return ret;
}
DEFINE_SIMPLE_ATTRIBUTE(debug_ini_reload_fops, NULL, debug_ini_reload, "%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 int fg_fw_update_set(void* data, u64 val) {
int ret = -EINVAL;
uint8_t op_st = (uint8_t)val;
struct max77779_fg_chip *chip = data;
if (chip)
ret = gbms_storage_write(GBMS_TAG_FGST, &op_st, 1);
dev_info(chip->dev, "set FG operation status: %02x, (ret=%d)\n", op_st, ret);
return 0;
}
static int fg_fw_update_get(void* data, u64* val) {
int ret = -EINVAL;
uint8_t op_st = 0xff;
struct max77779_fg_chip *chip = data;
if (chip)
ret = gbms_storage_read(GBMS_TAG_FGST, &op_st, 1);
*val = op_st;
dev_info(chip->dev, "get FG operation status: %02x, (ret=%d)\n", op_st, ret);
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(debug_fw_update_fops, fg_fw_update_get, fg_fw_update_set, "%llu\n");
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",
maxfg_health_get_ai(chip->dev, chip->bhi_acim, chip->RSense));
}
static DEVICE_ATTR_RW(act_impedance);
static ssize_t fg_abnormal_events_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, "%x\n", chip->abnormal_event_bits);
}
static DEVICE_ATTR_RO(fg_abnormal_events);
static ssize_t fg_learning_events_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 maxfg_show_captured_buffer(&chip->cb_lh, buf, PAGE_SIZE);
}
static ssize_t fg_learning_events_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;
if (value == 0)
maxfg_clear_capture_buf(&chip->cb_lh);
return count;
}
static DEVICE_ATTR_RW(fg_learning_events);
static int get_dr_vsoc_delta(void *data, u64 *val)
{
struct max77779_fg_chip *chip = data;
*val = chip->dynrel_state.vfsoc_delta;
return 0;
}
static int set_dr_vsoc_delta(void *data, u64 val)
{
struct max77779_fg_chip *chip = data;
int ret;
if (val > 100)
return -EINVAL;
/* set to 0 to disable */
chip->dynrel_state.vfsoc_delta = percentage_to_reg((int)val);
ret = max77779_dynrel_config(chip);
if (ret < 0)
dev_err(chip->dev, "dynrel: error enable=%d result=%d\n",
chip->dynrel_state.vfsoc_delta != 0, ret);
if (chip->dynrel_state.vfsoc_delta)
maxfg_dynrel_log(chip->ce_log, chip->dev, 0, &chip->dynrel_state);
return ret;
}
DEFINE_SIMPLE_ATTRIBUTE(dr_vsoc_delta_fops, get_dr_vsoc_delta,
set_dr_vsoc_delta, "%llu\n");
static void max77779_dynrel_init_sysfs(struct max77779_fg_chip *chip, struct dentry *de)
{
struct maxfg_dynrel_state *dr_state = &chip->dynrel_state;
debugfs_create_file("dr_vsoc_delta", 0644, de, chip, &dr_vsoc_delta_fops);
debugfs_create_u16("dr_learn_stage_min", 0644, de, &dr_state->learn_stage_min);
debugfs_create_u16("dr_temp_min", 0644, de, &dr_state->temp_qual.min);
debugfs_create_u16("dr_temp_max", 0644, de, &dr_state->temp_qual.max);
debugfs_create_u16("dr_vfocv_inhibit_min", 0644, de, &dr_state->vfocv_inhibit.min);
debugfs_create_u16("dr_vfocv_inhibit_max", 0644, de, &dr_state->vfocv_inhibit.max);
debugfs_create_u16("dr_relcfg_inhibit", 0644, de, &dr_state->relcfg_inhibit);
debugfs_create_u16("dr_relcfg_allow", 0644, de, &dr_state->relcfg_allow);
debugfs_create_bool("dr_override_mode", 0644, de, &dr_state->override_mode);
debugfs_create_bool("dr_monitor", 0644, de, &dr_state->monitor);
}
static void max77779_fg_init_sysfs(struct max77779_fg_chip *chip, struct dentry *de)
{
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);
debugfs_create_file("log_learn", 0400, de, chip, &debug_log_learn_fops);
if (chip->regmap.reglog)
debugfs_create_file("regmap_writes", 0440, de,
chip->regmap.reglog,
&debug_reglog_writes_fops);
debugfs_create_file("fg_model", 0444, de, chip, &debug_custom_model_fops);
debugfs_create_file("fg_param", 0444, de, chip, &debug_custom_param_fops);
debugfs_create_bool("model_ok", 0444, de, &chip->model_ok);
debugfs_create_file("sync_model", 0400, de, chip, &debug_sync_model_fops);
debugfs_create_file("model_version", 0600, de, chip, &debug_model_version_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);
/* reloaded INI */
debugfs_create_file("ini_reload", 0400, de, chip, &debug_ini_reload_fops);
/* capacity fade */
debugfs_create_u32("bhi_fcn_count", 0644, de, &chip->bhi_fcn_count);
/* fuel gauge operation status */
debugfs_create_file("fw_update", 0600, de, chip, &debug_fw_update_fops);
debugfs_create_file("fw_revision", 0600, de, chip, &debug_fw_revision_fops);
debugfs_create_file("fw_sub_revision", 0600, de, chip, &debug_fw_sub_revision_fops);
}
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;
ret = REGMAP_READ(&chip->regmap, MAX77779_FG_IChgTerm, &data);
if (ret < 0)
return ret;
dev_info(chip->dev, "Config: 0x%04x, IChgTerm: %d\n",
chip->RConfig, 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;
}
/* 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);
/*
* cycle register LSB is 25% of one cycle
* schedule next update at multiples of 4
*/
if (rc == 0)
chip->model_next_update = (reg_cycle + (1 << 2)) & ~((1 << 2) - 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;
/*
* 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);
/* get fw version from pmic if it's not ready during init */
if (!chip->fw_rev && !chip->fw_sub_rev)
max77779_fg_get_fw_ver(chip);
/* chip->model_lock is already locked by the caller */
chip->ml_cnt++;
/*
* failure on the gauge: retry as long as model_reload > IDLE
* pass current firmware revision to model load procedure
*/
ret = max77779_load_gauge_model(chip->model_data, chip->fw_rev, chip->fw_sub_rev);
if (ret < 0) {
dev_err(chip->dev, "Load Model Failed ret=%d\n", ret);
logbuffer_log(chip->ce_log, "max77779 Load Model Failed ret=%d\n", ret);
chip->ml_fails++;
return -EAGAIN;
}
chip->reg_prop_capacity_raw = MAX77779_FG_RepSOC;
return 0;
}
static void max77779_fg_init_setting(struct max77779_fg_chip *chip)
{
int ret;
/* dump registers */
max77779_fg_monitor_log_data(chip, true);
/* PASS1/1.5 */
max77779_current_offset_check(chip);
ret = max77779_fg_apply_n_register(chip);
if (ret < 0)
dev_err(chip->dev, "Fail to apply_n_register(%d)\n", ret);
}
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 = -EAGAIN;
if (!chip->model_data)
return;
__pm_stay_awake(chip->fg_wake_lock);
mutex_lock(&chip->model_lock);
if (chip->model_reload >= MAX77779_FG_LOAD_MODEL_REQUEST) {
/* will clear POR interrupt bit */
rc = max77779_fg_model_load(chip);
gbms_logbuffer_devlog(chip->ce_log, chip->dev,
LOGLEVEL_INFO, 0, LOGLEVEL_INFO,
"Model loading complete, rc=%d, reload=%d", rc,
chip->model_reload);
if (rc == 0) {
max77779_fg_restore_battery_cycle(chip);
rc = REGMAP_READ(&chip->regmap, MAX77779_FG_Cycles, &reg_cycle);
if (rc == 0) {
chip->model_reload = MAX77779_FG_LOAD_MODEL_IDLE;
chip->model_ok = true;
chip->por = false;
new_model = true;
/* saved new value in max77779_fg_set_next_update */
chip->model_next_update = reg_cycle > 0 ? reg_cycle - 1 : 0;
}
max77779_fg_monitor_log_data(chip, true);
} else if (rc != -EAGAIN) {
chip->model_reload = MAX77779_FG_LOAD_MODEL_DISABLED;
chip->model_ok = false;
}
}
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);
/* force check again after model loading */
chip->current_offset_check_done = false;
max77779_fg_init_setting(chip);
max77779_fg_prime_battery_qh_capacity(chip);
power_supply_changed(chip->psy);
} else if (chip->model_reload >= MAX77779_FG_LOAD_MODEL_REQUEST) {
chip->model_reload += 1;
mod_delayed_work(system_wq, &chip->model_work, msecs_to_jiffies(1000));
}
mutex_unlock(&chip->model_lock);
__pm_relax(chip->fg_wake_lock);
/*
* notify event only when no more model loading activities
* for rc == -EAGAIN, FG may try to load model again
*/
if (rc != -EAGAIN)
kobject_uevent(&chip->dev->kobj, KOBJ_CHANGE);
}
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_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) ||
!max77779_fg_check_state(chip->model_data)) {
ret = max77779_reset_state_data(chip->model_data);
if (ret < 0)
dev_err(chip->dev, "GMSR: model data didn't erase ret=%d\n", ret);
else
dev_warn(chip->dev, "GMSR: model data erased\n");
gbms_logbuffer_devlog(chip->ce_log, chip->dev,
LOGLEVEL_INFO, 0, LOGLEVEL_INFO,
"FG Version Changed, Reload");
ret = max77779_fg_full_reset(chip);
if (ret < 0)
dev_warn(chip->dev, "Reset unsuccessful, ret=%d\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;
}
ret = max77779_fg_set_next_update(chip);
if (ret < 0)
dev_warn(chip->dev, "Error on Next Update, Will retry\n");
gbms_logbuffer_devlog(chip->ce_log, chip->dev, LOGLEVEL_INFO, 0, LOGLEVEL_INFO,
"FG Model OK, ver=%d next_update=%d",
max77779_model_read_version(chip->model_data),
chip->model_next_update);
chip->reg_prop_capacity_raw = MAX77779_FG_RepSOC;
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);
}
/*
* 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_FG_INT_STS, &data);
if (!ret && data & MAX77779_FG_FG_INT_STS_Br_MASK) {
dev_info(chip->dev, "Clearing Battery Removal bit\n");
MAX77779_FG_REGMAP_WRITE(&chip->regmap, MAX77779_FG_FG_INT_STS,
MAX77779_FG_FG_INT_STS_Br_MASK);
}
if (!ret && data & MAX77779_FG_FG_INT_STS_Bi_MASK) {
dev_info(chip->dev, "Clearing Battery Insertion bit\n");
MAX77779_FG_REGMAP_WRITE(&chip->regmap, MAX77779_FG_FG_INT_STS,
MAX77779_FG_FG_INT_STS_Bi_MASK);
}
MAX77779_FG_REGMAP_WRITE(&chip->regmap, MAX77779_FG_FG_INT_MASK,
MAX77779_FG_FG_INT_MASK_dSOCi_m_CLEAR);
max77779_fg_update_cycle_count(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;
if (chip->model_ok)
max77779_fg_prime_battery_qh_capacity(chip);
}
return 0;
}
/* ------------------------------------------------------------------------- */
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 = maxfg_collect_history_data(buff, size, chip->por, chip->designcap,
chip->RSense, &chip->regmap, &chip->regmap_debug);
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);
struct dentry *de;
int ret = 0;
/* these don't require nvm storage */
ret = gbms_storage_register(&max77779_fg_prop_dsc, "max77779fg", 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;
ret = devm_request_threaded_irq(chip->dev, chip->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->irq, ret);
if (ret == 0) {
device_init_wakeup(chip->dev, true);
ret = enable_irq_wake(chip->irq);
if (ret)
dev_err(chip->dev, "Error enabling irq wake ret:%d\n", ret);
}
de = debugfs_create_dir(chip->max77779_fg_psy_desc.psy_dsc.name, 0);
if (IS_ERR_OR_NULL(de))
dev_warn(chip->dev, "debugfs not available (%ld)\n", PTR_ERR(de));
max77779_fg_init_sysfs(chip, de);
/* call after max77779_fg_init_chip */
chip->dynrel_state.relcfg_allow = max77779_get_relaxcfg(chip->model_data);
maxfg_dynrel_init(&chip->dynrel_state, chip->dev->of_node);
/* always reset relax to the correct state */
ret = max77779_dynrel_config(chip);
if (ret < 0)
gbms_logbuffer_devlog(chip->ce_log, chip->dev,
LOGLEVEL_INFO, 0, LOGLEVEL_INFO,
"dynrel: config error enable=%d (%d)",
chip->dynrel_state.vfsoc_delta != 0,
ret);
max77779_dynrel_init_sysfs(chip, de);
/*
* Handle any IRQ that might have been set before init
* NOTE: will trigger model load if needed
*/
max77779_fg_irq_thread_fn(-1, chip);
/* run after model loading done */
if (!chip->por)
max77779_fg_init_setting(chip);
dev_info(chip->dev, "init_work done\n");
}
bool max77779_fg_dbg_is_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case 0x8C ... 0x8F:
case 0x9C ... 0x9F:
case 0xA0 ... 0xA7:
case 0xA9:
case 0xAF:
case 0xB1 ... 0xB3:
case 0xB6 ... 0xB7:
case 0xBB ... 0xBC:
case 0xC0:
case 0xC6:
case 0xC8 ... 0xCA:
case 0xD6: /* nProtMiscTh */
return true;
}
return false;
}
EXPORT_SYMBOL_GPL(max77779_fg_dbg_is_reg);
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 0x2B: /* FullCapFltr */
case 0x2E ... 0x35:
case 0x37: /* VFSOC */
return true;
case 0x39 ... 0x3A:
case 0x3D ... 0x3F:
case 0x40: /* Can be used for boot completion check (0x82) */
case 0x42:
case 0x45 ... 0x48:
case 0x4C ... 0x4E:
case 0x52 ... 0x54:
case 0x62 ... 0x63:
case 0x6C: /* CurrentOffsetCal */
case 0x6F: /* secure update result */
case 0x80 ... 0x9F: /* Model */
case 0xA0: /* CGain */
case 0xA3: /* Model cfg */
case 0xAB:
case 0xB0:
case 0xB2:
case 0xB4:
case 0xBA:
case 0xBE ... 0xBF:
case 0xD0 ... 0xDB:
case 0xE0 ... 0xE1: /* FG_Func*/
case 0xE9 ... 0xEA:
case 0xFF:
return true;
}
return false;
}
EXPORT_SYMBOL_GPL(max77779_fg_is_reg);
void *max77779_get_model_data(struct device *dev)
{
struct max77779_fg_chip *chip = dev_get_drvdata(dev);
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,
&dev_attr_model_state.attr,
&dev_attr_fg_abnormal_events.attr,
&dev_attr_fg_learning_events.attr,
NULL,
};
static const struct attribute_group max77779_fg_attr_grp = {
.attrs = max77779_fg_attrs,
};
static int max77779_fg_apply_n_register(struct max77779_fg_chip *chip)
{
struct device_node *node = chip->dev->of_node;
const char *propname = "max77779,fg_n_regval";
int cnt, ret = 0, idx, err;
u16 *regs, data;
if (!node)
return 0;
cnt = of_property_count_elems_of_size(node, propname, sizeof(u16));
if (cnt <= 0)
return 0;
if (cnt & 1) {
dev_warn(chip->dev, "%s %s u16 elems count is not even: %d\n",
node->name, propname, cnt);
return -EINVAL;
}
regs = (u16 *)kmalloc_array(cnt, sizeof(u16), GFP_KERNEL);
if (!regs)
return -ENOMEM;
ret = of_property_read_u16_array(node, propname, regs, cnt);
if (ret) {
dev_warn(chip->dev, "failed to read %s %s: %d\n",
node->name, propname, ret);
goto register_out;
}
for (idx = 0; idx < cnt; idx += 2) {
if (!max77779_fg_dbg_is_reg(chip->dev, regs[idx]))
continue;
err = REGMAP_READ(&chip->regmap_debug, regs[idx], &data);
if (err) {
dev_warn(chip->dev, "%s: fail to read %#x(%d)\n",
__func__, regs[idx], err);
continue;
}
if (data != regs[idx + 1]) {
err = MAX77779_FG_N_REGMAP_WRITE(&chip->regmap, &chip->regmap_debug,
regs[idx], regs[idx + 1]);
if (err)
dev_warn(chip->dev, "%s: fail to write %#x to %#x(%d)\n",
__func__, regs[idx + 1], regs[idx], err);
}
}
register_out:
kfree(regs);
return ret;
}
static int max77779_init_fg_capture(struct max77779_fg_chip *chip)
{
/* config for FG Learning */
maxfg_init_fg_learn_capture_config(&chip->cb_lh.config,
&chip->regmap, &chip->regmap_debug);
return maxfg_alloc_capture_buf(&chip->cb_lh, MAX_FG_LEARN_PARAM_MAX_HIST);
}
/*
* Initialization requirements
* struct max77779_fg_chip *chip
* - dev
* - irq
* - regmap
* - regmap_debug
*/
int max77779_fg_init(struct max77779_fg_chip *chip)
{
struct device *dev = chip->dev;
struct power_supply_config psy_cfg = { };
const char *psy_name = NULL;
char monitor_name[32];
int ret = 0;
u32 data32;
if (!chip->irq) {
dev_err(dev, "cannot allocate irq\n");
return -1;
}
chip->fake_battery = of_property_read_bool(dev->of_node, "max77779,no-battery") ? 0 : -1;
chip->batt_id_defer_cnt = DEFAULT_BATTERY_ID_RETRIES;
mutex_init(&section_lock);
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 ");
}
/*
* mask all interrupts before request irq
* unmask in init_work
*/
ret = MAX77779_FG_REGMAP_WRITE(&chip->regmap, MAX77779_FG_FG_INT_MASK, 0xFFFF);
if (ret < 0)
dev_warn(chip->dev, "Unable to mask all interrupts (%d)\n", ret);
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.psy_dsc.name = devm_kstrdup(dev, psy_name, GFP_KERNEL);
else
chip->max77779_fg_psy_desc.psy_dsc.name = "max77779fg";
dev_info(dev, "max77779_fg_psy_desc.name=%s\n", chip->max77779_fg_psy_desc.psy_dsc.name);
chip->ce_log = logbuffer_register(chip->max77779_fg_psy_desc.psy_dsc.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;
goto irq_unregister;
}
scnprintf(monitor_name, sizeof(monitor_name), "%s_%s",
chip->max77779_fg_psy_desc.psy_dsc.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;
goto irq_unregister;
}
/* POWER_SUPPLY_PROP_TEMP and model load need the version info */
max77779_fg_get_fw_ver(chip);
/* fuel gauge model needs to know the batt_id */
mutex_init(&chip->model_lock);
mutex_init(&chip->save_data_lock);
mutex_init(&chip->check_event_lock);
chip->max77779_fg_psy_desc.psy_dsc.type = POWER_SUPPLY_TYPE_BATTERY;
chip->max77779_fg_psy_desc.psy_dsc.get_property = max77779_fg_get_property;
chip->max77779_fg_psy_desc.psy_dsc.set_property = max77779_fg_set_property;
chip->max77779_fg_psy_desc.psy_dsc.property_is_writeable = max77779_fg_property_is_writeable;
chip->max77779_fg_psy_desc.get_property = max77779_gbms_fg_get_property;
chip->max77779_fg_psy_desc.set_property = max77779_gbms_fg_set_property;
chip->max77779_fg_psy_desc.property_is_writeable = max77779_gbms_fg_property_is_writeable;
chip->max77779_fg_psy_desc.psy_dsc.properties = max77779_fg_battery_props;
chip->max77779_fg_psy_desc.psy_dsc.num_properties = ARRAY_SIZE(max77779_fg_battery_props);
chip->max77779_fg_psy_desc.forward = true;
if (of_property_read_bool(dev->of_node, "max77779,psy-type-unknown"))
chip->max77779_fg_psy_desc.psy_dsc.type = POWER_SUPPLY_TYPE_UNKNOWN;
chip->psy = devm_power_supply_register(dev, &chip->max77779_fg_psy_desc.psy_dsc, &psy_cfg);
if (IS_ERR(chip->psy)) {
dev_err(dev, "Couldn't register as power supply\n");
ret = PTR_ERR(chip->psy);
goto power_supply_unregister;
}
ret = sysfs_create_group(&chip->psy->dev.kobj, &max77779_fg_attr_grp);
if (ret)
dev_warn(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_FG_LOAD_MODEL_DISABLED;
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;
ret = max77779_init_fg_capture(chip);
if (ret < 0)
dev_err(dev, "Can not configure FG learning capture(%d)\n", ret);
/* 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);
INIT_DELAYED_WORK(&chip->model_work, max77779_fg_model_work);
chip->fg_wake_lock = wakeup_source_register(NULL, "max77779-fg");
if (!chip->fg_wake_lock)
dev_warn(dev, "failed to register wake source\n");
schedule_delayed_work(&chip->init_work, 0);
return 0;
power_supply_unregister:
power_supply_unregister(chip->psy);
irq_unregister:
free_irq(chip->irq, chip);
return ret;
}
EXPORT_SYMBOL_GPL(max77779_fg_init);
void max77779_fg_remove(struct max77779_fg_chip *chip)
{
if (chip->ce_log) {
logbuffer_unregister(chip->ce_log);
chip->ce_log = NULL;
}
if (chip->fg_wake_lock) {
wakeup_source_unregister(chip->fg_wake_lock);
chip->fg_wake_lock = NULL;
}
if (chip->model_data)
max77779_free_data(chip->model_data);
cancel_delayed_work(&chip->init_work);
cancel_delayed_work(&chip->model_work);
disable_irq_wake(chip->irq);
device_init_wakeup(chip->dev, false);
if (chip->irq)
free_irq(chip->irq, chip);
if (chip->psy)
power_supply_unregister(chip->psy);
maxfg_free_capture_buf(&chip->cb_lh);
}
EXPORT_SYMBOL_GPL(max77779_fg_remove);
#if IS_ENABLED(CONFIG_PM)
int max77779_fg_pm_suspend(struct device *dev)
{
struct max77779_fg_chip *chip = dev_get_drvdata(dev);
pm_runtime_get_sync(chip->dev);
dev_dbg(chip->dev, "%s\n", __func__);
chip->resume_complete = false;
pm_runtime_put_sync(chip->dev);
return 0;
}
EXPORT_SYMBOL_GPL(max77779_fg_pm_suspend);
int max77779_fg_pm_resume(struct device *dev)
{
struct max77779_fg_chip *chip = dev_get_drvdata(dev);
pm_runtime_get_sync(chip->dev);
dev_dbg(chip->dev, "%s\n", __func__);
chip->resume_complete = true;
pm_runtime_put_sync(chip->dev);
return 0;
}
EXPORT_SYMBOL_GPL(max77779_fg_pm_resume);
#endif
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");